// Emacs style mode select -*- C++ -*- //----------------------------------------------------------------------------- // // $Id:$ // // Copyright (C) 1993-1996 by id Software, Inc. // // This source is available for distribution and/or modification // only under the terms of the DOOM Source Code License as // published by id Software. All rights reserved. // // The source is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License // for more details. // // $Log:$ // // DESCRIPTION: // Movement, collision handling. // Shooting and aiming. // //----------------------------------------------------------------------------- #include #include #include "templates.h" #include "vectors.h" #include "m_alloc.h" #include "m_bbox.h" #include "m_random.h" #include "i_system.h" #include "doomdef.h" #include "p_local.h" #include "p_lnspec.h" #include "p_effect.h" #include "p_terrain.h" #include "p_trace.h" #include "s_sound.h" #include "decallib.h" // State. #include "doomstat.h" #include "r_state.h" #include "gi.h" #include "a_sharedglobal.h" #include "a_doomglobal.h" #include "p_conversation.h" #define WATER_SINK_FACTOR 3 #define WATER_SINK_SMALL_FACTOR 4 #define WATER_SINK_SPEED (FRACUNIT/2) #define WATER_JUMP_SPEED (FRACUNIT*7/2) #define USE_PUZZLE_ITEM_SPECIAL 129 CVAR (Bool, cl_bloodsplats, true, CVAR_ARCHIVE) static void CheckForPushSpecial (line_t *line, int side, AActor *mobj); static void SpawnShootDecal (AActor *t1, const FTraceResults &trace); static void SpawnDeepSplash (AActor *t1, const FTraceResults &trace, AActor *puff, fixed_t vx, fixed_t vy, fixed_t vz); fixed_t tmbbox[4]; AActor *tmthing; static int tmflags; static fixed_t tmx; static fixed_t tmy; static fixed_t tmz; // [RH] Needed for third dimension of teleporters static fixed_t pe_x; // Pain Elemental position for Lost Soul checks // phares static fixed_t pe_y; // Pain Elemental position for Lost Soul checks // phares static fixed_t ls_x; // Lost Soul position for Lost Soul checks // phares static fixed_t ls_y; // Lost Soul position for Lost Soul checks // phares static FRandom pr_tracebleed ("TraceBleed"); static FRandom pr_checkthing ("CheckThing"); static FRandom pr_lineattack ("LineAttack"); static FRandom pr_crunch ("DoCrunch"); // If "floatok" true, move would be ok // if within "tmfloorz - tmceilingz". BOOL floatok; fixed_t tmfloorz; fixed_t tmceilingz; fixed_t tmdropoffz; int tmfloorpic; sector_t *tmfloorsector; int tmceilingpic; sector_t *tmceilingsector; static fixed_t tmfbbox[4]; static AActor *tmfthing; fixed_t tmffloorz; fixed_t tmfceilingz; fixed_t tmfdropoffz; fixed_t tmffloorpic; sector_t *tmffloorsector; fixed_t tmfceilingpic; sector_t *tmfceilingsector; //Added by MC: So bot will know what kind of sector it's entering. sector_t* tmsector; // keep track of the line that lowers the ceiling, // so missiles don't explode against sky hack walls line_t* ceilingline; line_t *BlockingLine; // keep track of special lines as they are hit, // but don't process them until the move is proven valid TArray spechit; AActor *onmobj; // generic global onmobj...used for landing on pods/players AActor *BlockingMobj; // Temporary holder for thing_sectorlist threads msecnode_t* sector_list = NULL; // phares 3/16/98 extern sector_t *openbottomsec; extern sector_t *opentopsec; bool DoRipping; AActor *LastRipped; //========================================================================== // // PIT_FindFloorCeiling // //========================================================================== static BOOL PIT_FindFloorCeiling (line_t *ld) { if (tmfbbox[BOXRIGHT] <= ld->bbox[BOXLEFT] || tmfbbox[BOXLEFT] >= ld->bbox[BOXRIGHT] || tmfbbox[BOXTOP] <= ld->bbox[BOXBOTTOM] || tmfbbox[BOXBOTTOM] >= ld->bbox[BOXTOP] ) return true; if (P_BoxOnLineSide (tmfbbox, ld) != -1) return true; // A line has been hit if (!ld->backsector) { // One sided line return true; } fixed_t sx, sy; // set openrange, opentop, openbottom if (((ld->frontsector->floorplane.a | ld->frontsector->floorplane.b) | (ld->backsector->floorplane.a | ld->backsector->floorplane.b) | (ld->frontsector->ceilingplane.a | ld->frontsector->ceilingplane.b) | (ld->backsector->ceilingplane.a | ld->backsector->ceilingplane.b)) == 0) { P_LineOpening (ld, sx=tmx, sy=tmy, tmx, tmy); } else { // Find the point on the line closest to the actor's center, and use // that to calculate openings float dx = (float)ld->dx; float dy = (float)ld->dy; fixed_t r = (fixed_t)(((float)(tmx - ld->v1->x) * dx + (float)(tmy - ld->v1->y) * dy) / (dx*dx + dy*dy) * 16777216.f); if (r <= 0) { P_LineOpening (ld, sx=ld->v1->x, sy=ld->v1->y, tmx, tmy); } else if (r >= (1<<24)) { P_LineOpening (ld, sx=ld->v2->x, sy=ld->v2->y, tmfthing->x, tmfthing->y); } else { P_LineOpening (ld, sx=ld->v1->x + MulScale24 (r, ld->dx), sy=ld->v1->y + MulScale24 (r, ld->dy), tmx, tmy); } } // adjust floor / ceiling heights if (opentop < tmfceilingz) { tmfceilingz = opentop; BlockingLine = ld; } if (openbottom > tmffloorz) { tmffloorz = openbottom; tmffloorsector = openbottomsec; BlockingLine = ld; } if (lowfloor < tmfdropoffz) tmfdropoffz = lowfloor; return true; } //========================================================================== // // P_FindFloorCeiling // //========================================================================== void P_FindFloorCeiling (AActor *actor) { int xl,xh,yl,yh,bx,by; fixed_t x, y; sector_t *sec; x = actor->x; y = actor->y; tmfbbox[BOXTOP] = y + actor->radius; tmfbbox[BOXBOTTOM] = y - actor->radius; tmfbbox[BOXRIGHT] = x + actor->radius; tmfbbox[BOXLEFT] = x - actor->radius; sec = R_PointInSubsector (x, y)->sector; tmffloorz = tmfdropoffz = sec->floorplane.ZatPoint (x, y); tmfceilingz = sec->ceilingplane.ZatPoint (x, y); tmffloorpic = sec->floorpic; tmffloorsector = sec; tmfceilingpic = sec->ceilingpic; tmfceilingsector = sec; tmfthing = actor; validcount++; xl = (tmfbbox[BOXLEFT] - bmaporgx) >> MAPBLOCKSHIFT; xh = (tmfbbox[BOXRIGHT] - bmaporgx) >> MAPBLOCKSHIFT; yl = (tmfbbox[BOXBOTTOM] - bmaporgy) >> MAPBLOCKSHIFT; yh = (tmfbbox[BOXTOP] - bmaporgy) >> MAPBLOCKSHIFT; for (bx = xl; bx <= xh; bx++) for (by = yl; by <= yh; by++) if (!P_BlockLinesIterator (bx, by, PIT_FindFloorCeiling)) return; } // // TELEPORT MOVE // // // PIT_StompThing // static BOOL StompAlwaysFrags; BOOL PIT_StompThing (AActor *thing) { fixed_t blockdist; if (!(thing->flags & MF_SHOOTABLE)) return true; // don't clip against self if (thing == tmthing) return true; blockdist = thing->radius + tmthing->radius; if (abs(thing->x - tmx) >= blockdist || abs(thing->y - tmy) >= blockdist) { // didn't hit it return true; } // [RH] Z-Check // But not if not MF2_PASSMOBJ or MF3_DONTOVERLAP are set! // Otherwise those things would get stuck inside each other. if ((tmthing->flags2 & MF2_PASSMOBJ || thing->flags4 & MF4_ACTLIKEBRIDGE) && !(i_compatflags & COMPATF_NO_PASSMOBJ)) { if (!(thing->flags3 & tmthing->flags3 & MF3_DONTOVERLAP)) { if (tmz > thing->z + thing->height) return true; // overhead if (tmz+tmthing->height < thing->z) return true; // underneath } } // monsters don't stomp things except on boss level // [RH] Some Heretic/Hexen monsters can telestomp if (StompAlwaysFrags || (tmthing->flags2 & MF2_TELESTOMP)) { P_DamageMobj (thing, tmthing, tmthing, 1000000, MOD_TELEFRAG); return true; } return false; } // // P_TeleportMove // // [RH] Added telefrag parameter: When true, anything in the spawn spot // will always be telefragged, and the move will be successful. // Added z parameter. Originally, the thing's z was set *after* the // move was made, so the height checking I added for 1.13 could // potentially erroneously indicate the move was okay if the thing // was being teleported between two non-overlapping height ranges. BOOL P_TeleportMove (AActor *thing, fixed_t x, fixed_t y, fixed_t z, BOOL telefrag) { static TArray telebt; int xl; int xh; int yl; int yh; int bx; int by; subsector_t* newsubsec; // kill anything occupying the position tmthing = thing; tmflags = thing->flags; tmx = x; tmy = y; tmz = z; tmfbbox[BOXTOP] = tmbbox[BOXTOP] = y + tmthing->radius; tmfbbox[BOXBOTTOM] = tmbbox[BOXBOTTOM] = y - tmthing->radius; tmfbbox[BOXRIGHT] = tmbbox[BOXRIGHT] = x + tmthing->radius; tmfbbox[BOXLEFT] = tmbbox[BOXLEFT] = x - tmthing->radius; newsubsec = R_PointInSubsector (x,y); ceilingline = NULL; // The base floor/ceiling is from the subsector that contains the point. // Any contacted lines the step closer together will adjust them. tmffloorz = tmfdropoffz = newsubsec->sector->floorplane.ZatPoint (x, y); tmfceilingz = newsubsec->sector->ceilingplane.ZatPoint (x, y); tmffloorpic = newsubsec->sector->floorpic; tmffloorsector = newsubsec->sector; tmfceilingpic = newsubsec->sector->ceilingpic; tmfceilingsector = newsubsec->sector; tmfthing = tmthing; validcount++; spechit.Clear (); telebt.Clear(); StompAlwaysFrags = tmthing->player || (level.flags & LEVEL_MONSTERSTELEFRAG) || telefrag; // stomp on any things contacted xl = (tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT; xh = (tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT; yl = (tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT; yh = (tmbbox[BOXTOP] - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT; for (bx=xl ; bx<=xh ; bx++) { for (by=yl ; by<=yh ; by++) { if (!P_BlockLinesIterator(bx,by,PIT_FindFloorCeiling)) { return false; } } } fixed_t savefloorz = tmffloorz; fixed_t saveceilingz = tmfceilingz; sector_t *savesector = tmffloorsector; int savepic = tmffloorpic; sector_t *savecsector = tmffloorsector; int savecpic = tmffloorpic; fixed_t savedropoff = tmfdropoffz; for (bx=xl ; bx<=xh ; bx++) { for (by=yl ; by<=yh ; by++) { if (!P_BlockThingsIterator(bx,by,PIT_StompThing,telebt)) { return false; } } } // the move is ok, so link the thing into its new position thing->SetOrigin (x, y, z); thing->floorz = savefloorz; thing->ceilingz = saveceilingz; thing->floorsector = savesector; thing->floorpic = savepic; thing->ceilingsector = savecsector; thing->ceilingpic = savecpic; thing->dropoffz = savedropoff; // killough 11/98 if (thing->flags2 & MF2_FLOORCLIP) { thing->AdjustFloorClip (); } if (thing == players[consoleplayer].camera) { R_ResetViewInterpolation (); } thing->PrevX = x; thing->PrevY = y; thing->PrevZ = z; return true; } // // [RH] P_PlayerStartStomp // // Like P_TeleportMove, but it doesn't move anything, and only monsters and other // players get telefragged. // BOOL PIT_StompThing2 (AActor *thing) { fixed_t blockdist; if (!(thing->flags & MF_SHOOTABLE)) return true; // don't clip against self, and don't kill your own voodoo dolls if (thing == tmthing || (thing->player == tmthing->player && thing->player != NULL)) return true; // only kill monsters and other players if (thing->player == NULL && !(thing->flags3 & MF3_ISMONSTER)) return true; blockdist = thing->radius + tmthing->radius; if (abs(thing->x - tmthing->x) >= blockdist || abs(thing->y - tmthing->y) >= blockdist) { // didn't hit it return true; } if (tmthing->z > thing->z + thing->height) return true; // overhead if (tmthing->z + tmthing->height < thing->z) return true; // underneath P_DamageMobj (thing, tmthing, tmthing, 1000000, MOD_TELEFRAG); return true; } void P_PlayerStartStomp (AActor *actor) { static TArray telebt; int xl; int xh; int yl; int yh; int bx; int by; tmthing = actor; tmflags = actor->flags; tmbbox[BOXTOP] = actor->y + actor->radius; tmbbox[BOXBOTTOM] = actor->y - actor->radius; tmbbox[BOXRIGHT] = actor->x + actor->radius; tmbbox[BOXLEFT] = actor->x - actor->radius; telebt.Clear(); // stomp on any things contacted xl = (tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT; xh = (tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT; yl = (tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT; yh = (tmbbox[BOXTOP] - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT; for (bx = xl; bx <= xh; bx++) { for (by = yl; by <= yh; by++) { P_BlockThingsIterator (bx, by, PIT_StompThing2, telebt); } } } inline fixed_t secfriction (const sector_t *sec) { fixed_t friction = Terrains[TerrainTypes[sec->floorpic]].Friction; return friction != 0 ? friction : sec->friction; } inline fixed_t secmovefac (const sector_t *sec) { fixed_t movefactor = Terrains[TerrainTypes[sec->floorpic]].MoveFactor; return movefactor != 0 ? movefactor : sec->movefactor; } // // killough 8/28/98: // // P_GetFriction() // // Returns the friction associated with a particular mobj. int P_GetFriction (const AActor *mo, int *frictionfactor) { int friction = ORIG_FRICTION; int movefactor = ORIG_FRICTION_FACTOR; const msecnode_t *m; const sector_t *sec; if (mo->flags2 & MF2_FLY && mo->flags & MF_NOGRAVITY) { friction = FRICTION_FLY; } else if (!(mo->flags & MF_NOGRAVITY) && mo->waterlevel > 1 || (mo->waterlevel == 1 && mo->z > mo->floorz + 6*FRACUNIT)) { friction = secfriction (mo->Sector); movefactor = secmovefac (mo->Sector) >> 1; } else if (var_friction && !(mo->flags & (MF_NOCLIP|MF_NOGRAVITY))) { // When the object is straddling sectors with the same // floor height that have different frictions, use the lowest // friction value (muddy has precedence over icy). for (m = mo->touching_sectorlist; m; m = m->m_tnext) { sec = m->m_sector; if (!(sec->special & FRICTION_MASK) && Terrains[TerrainTypes[sec->floorpic]].Friction == 0) { continue; } if ((secfriction(sec) < friction || friction == ORIG_FRICTION) && (mo->z <= sec->floorplane.ZatPoint (mo->x, mo->y) || (sec->heightsec && !(sec->heightsec->MoreFlags & SECF_IGNOREHEIGHTSEC) && mo->z <= sec->heightsec->floorplane.ZatPoint (mo->x, mo->y)))) { friction = secfriction (sec); movefactor = secmovefac (sec); } } } if (frictionfactor) *frictionfactor = movefactor; return friction; } // phares 3/19/98 // P_GetMoveFactor() returns the value by which the x,y // movements are multiplied to add to player movement. // // killough 8/28/98: rewritten int P_GetMoveFactor (const AActor *mo, int *frictionp) { int movefactor, friction; // If the floor is icy or muddy, it's harder to get moving. This is where // the different friction factors are applied to 'trying to move'. In // p_mobj.c, the friction factors are applied as you coast and slow down. if ((friction = P_GetFriction(mo, &movefactor)) < ORIG_FRICTION) { // phares 3/11/98: you start off slowly, then increase as // you get better footing int momentum = P_AproxDistance(mo->momx,mo->momy); if (momentum > MORE_FRICTION_MOMENTUM<<2) movefactor <<= 3; else if (momentum > MORE_FRICTION_MOMENTUM<<1) movefactor <<= 2; else if (momentum > MORE_FRICTION_MOMENTUM) movefactor <<= 1; } if (frictionp) *frictionp = friction; return movefactor; } // // MOVEMENT ITERATOR FUNCTIONS // // // phares // PIT_CrossLine // | // Checks to see if a PE->LS trajectory line crosses a blocking // V // line. Returns false if it does. // // tmbbox holds the bounding box of the trajectory. If that box // does not touch the bounding box of the line in question, // then the trajectory is not blocked. If the PE is on one side // of the line and the LS is on the other side, then the // trajectory is blocked. // // Currently this assumes an infinite line, which is not quite // correct. A more correct solution would be to check for an // intersection of the trajectory and the line, but that takes // longer and probably really isn't worth the effort. // static // killough 3/26/98: make static BOOL PIT_CrossLine (line_t* ld) { if (!(ld->flags & ML_TWOSIDED) || (ld->flags & (ML_BLOCKING|ML_BLOCKMONSTERS|ML_BLOCKEVERYTHING))) if (!(tmbbox[BOXLEFT] > ld->bbox[BOXRIGHT] || tmbbox[BOXRIGHT] < ld->bbox[BOXLEFT] || tmbbox[BOXTOP] < ld->bbox[BOXBOTTOM] || tmbbox[BOXBOTTOM] > ld->bbox[BOXTOP])) if (P_PointOnLineSide(pe_x,pe_y,ld) != P_PointOnLineSide(ls_x,ls_y,ld)) return(false); // line blocks trajectory // ^ return(true); // line doesn't block trajectory // | } // phares // // PIT_CheckLine // Adjusts tmfloorz and tmceilingz as lines are contacted // static // killough 3/26/98: make static BOOL PIT_CheckLine (line_t *ld) { bool rail = false; if (tmbbox[BOXRIGHT] <= ld->bbox[BOXLEFT] || tmbbox[BOXLEFT] >= ld->bbox[BOXRIGHT] || tmbbox[BOXTOP] <= ld->bbox[BOXBOTTOM] || tmbbox[BOXBOTTOM] >= ld->bbox[BOXTOP] ) return true; if (P_BoxOnLineSide (tmbbox, ld) != -1) return true; // A line has been hit /* = = The moving thing's destination position will cross the given line. = If this should not be allowed, return false. = If the line is special, keep track of it to process later if the move = is proven ok. NOTE: specials are NOT sorted by order, so two special lines = that are only 8 pixels apart could be crossed in either order. */ if (!ld->backsector) { // One sided line if (tmthing->flags2 & MF2_BLASTED) { P_DamageMobj (tmthing, NULL, NULL, tmthing->Mass >> 5, MOD_HIT); } BlockingLine = ld; CheckForPushSpecial (ld, 0, tmthing); return false; } if (!(tmthing->flags & MF_MISSILE) || (ld->flags & ML_BLOCKEVERYTHING)) { if (ld->flags & ML_RAILING) { rail = true; } else if ((ld->flags & (ML_BLOCKING|ML_BLOCKEVERYTHING)) || // explicitly blocking everything (!(tmthing->flags3 & MF3_NOBLOCKMONST) && (ld->flags & ML_BLOCKMONSTERS)) || // block monsters only ((ld->flags & ML_BLOCK_FLOATERS) && (tmthing->flags & MF_FLOAT))) // block floaters { if (tmthing->flags2 & MF2_BLASTED) { P_DamageMobj (tmthing, NULL, NULL, tmthing->Mass >> 5, MOD_HIT); } BlockingLine = ld; CheckForPushSpecial (ld, 0, tmthing); return false; } } // [RH] Steep sectors count as dropoffs (unless already in one) if (!(tmthing->flags & MF_DROPOFF) && !(tmthing->flags & (MF_NOGRAVITY|MF_NOCLIP))) { if (ld->frontsector->floorplane.c < STEEPSLOPE || ld->backsector->floorplane.c < STEEPSLOPE) { const msecnode_t *node = tmthing->touching_sectorlist; bool allow = false; int count = 0; while (node != NULL) { count++; if (node->m_sector->floorplane.c < STEEPSLOPE) { allow = true; break; } node = node->m_tnext; } if (!allow) { return false; } } } fixed_t sx, sy; // set openrange, opentop, openbottom if (((ld->frontsector->floorplane.a | ld->frontsector->floorplane.b) | (ld->backsector->floorplane.a | ld->backsector->floorplane.b) | (ld->frontsector->ceilingplane.a | ld->frontsector->ceilingplane.b) | (ld->backsector->ceilingplane.a | ld->backsector->ceilingplane.b)) == 0) { P_LineOpening (ld, sx=tmx, sy=tmy, tmx, tmy); } else { // Find the point on the line closest to the actor's center, and use // that to calculate openings float dx = (float)ld->dx; float dy = (float)ld->dy; fixed_t r = (fixed_t)(((float)(tmx - ld->v1->x) * dx + (float)(tmy - ld->v1->y) * dy) / (dx*dx + dy*dy) * 16777216.f); /* Printf ("%d:%d: %d (%d %d %d %d) (%d %d %d %d)\n", level.time, ld-lines, r, ld->frontsector->floorplane.a, ld->frontsector->floorplane.b, ld->frontsector->floorplane.c, ld->frontsector->floorplane.ic, ld->backsector->floorplane.a, ld->backsector->floorplane.b, ld->backsector->floorplane.c, ld->backsector->floorplane.ic);*/ if (r <= 0) { P_LineOpening (ld, sx=ld->v1->x, sy=ld->v1->y, tmx, tmy); } else if (r >= (1<<24)) { P_LineOpening (ld, sx=ld->v2->x, sy=ld->v2->y, tmthing->x, tmthing->y); } else { P_LineOpening (ld, sx=ld->v1->x + MulScale24 (r, ld->dx), sy=ld->v1->y + MulScale24 (r, ld->dy), tmx, tmy); } /* Printf (" %d %d %d\n", sx, sy, openbottom);*/ } if (rail && // Eww! Gross! This check means the rail only exists if you stand on the // high side of the rail. So if you're walking on the low side of the rail, // it's possible to get stuck in the rail until you jump out. Unfortunately, // there is an area on Strife MAP04 that requires this behavior. Still, it's // better than Strife's handling of rails, which lets you jump into rails // from either side. How long until somebody reports this as a bug and I'm // forced to say, "It's not a bug. It's a feature?" Ugh. (gameinfo.gametype != GAME_Strife || level.flags & LEVEL_HEXENFORMAT || openbottom == tmthing->Sector->floorplane.ZatPoint (sx, sy))) { openbottom += 32*FRACUNIT; } // adjust floor / ceiling heights if (opentop < tmceilingz) { tmceilingz = opentop; tmceilingsector = opentopsec; tmceilingpic = opentopsec->ceilingpic; ceilingline = ld; BlockingLine = ld; } if (openbottom > tmfloorz) { tmfloorz = openbottom; tmfloorsector = openbottomsec; tmfloorpic = openbottomsec->floorpic; BlockingLine = ld; } if (lowfloor < tmdropoffz) tmdropoffz = lowfloor; // if contacted a special line, add it to the list if (ld->special) { spechit.Push (ld); } return true; } //========================================================================== // // PIT_CheckThing // //========================================================================== static AActor *stepthing; BOOL PIT_CheckThing (AActor *thing) { fixed_t topz; fixed_t blockdist; BOOL solid; int damage; // don't clip against self if (thing == tmthing) return true; if (!(thing->flags & (MF_SOLID|MF_SPECIAL|MF_SHOOTABLE)) ) return true; // can't hit thing blockdist = thing->radius + tmthing->radius; if (abs(thing->x - tmx) >= blockdist || abs(thing->y - tmy) >= blockdist) { // didn't hit thing return true; } BlockingMobj = thing; topz = thing->z + thing->height; if (!(i_compatflags & COMPATF_NO_PASSMOBJ) && !(tmthing->flags & (MF_FLOAT|MF_MISSILE|MF_SKULLFLY|MF_NOGRAVITY)) && (thing->flags & MF_SOLID) && (thing->flags4 & MF4_ACTLIKEBRIDGE)) { // [RH] Let monsters walk on actors as well as floors if ((tmthing->flags3 & MF3_ISMONSTER) && topz >= tmfloorz && topz <= tmthing->z + tmthing->MaxStepHeight) { // The commented-out if is an attempt to prevent monsters from walking off a // thing further than they would walk off a ledge. I can't think of an easy // way to do this, so I restrict them to only walking on bridges instead. // Uncommenting the if here makes it almost impossible for them to walk on // anything, bridge or otherwise. // if (abs(thing->x - tmx) <= thing->radius && // abs(thing->y - tmy) <= thing->radius) { stepthing = thing; tmfloorz = topz; } } } // [RH] If the other thing is a bridge, then treat the moving thing as if it had MF2_PASSMOBJ, so // you can use a scrolling floor to move scenery items underneath a bridge. if ((tmthing->flags2 & MF2_PASSMOBJ || thing->flags4 & MF4_ACTLIKEBRIDGE) && !(i_compatflags & COMPATF_NO_PASSMOBJ)) { // check if a mobj passed over/under another object if (tmthing->flags3 & thing->flags3 & MF3_DONTOVERLAP) { // Some things prefer not to overlap each other, if possible return false; } if ((tmthing->z >= topz) || (tmthing->z + tmthing->height <= thing->z)) { return true; } } // Check for skulls slamming into things if (tmthing->flags & MF_SKULLFLY) { bool res = tmthing->Slam (BlockingMobj); BlockingMobj = NULL; return res; } // Check for blasted thing running into another if ((tmthing->flags2 & MF2_BLASTED) && (thing->flags & MF_SHOOTABLE)) { if (!(thing->flags2 & MF2_BOSS) && (thing->flags3 & MF3_ISMONSTER)) { thing->momx += tmthing->momx; thing->momy += tmthing->momy; if ((thing->momx + thing->momy) > 3*FRACUNIT) { damage = (tmthing->Mass / 100) + 1; P_DamageMobj (thing, tmthing, tmthing, damage, tmthing->DamageType); P_TraceBleed (damage, thing, tmthing); damage = (thing->Mass / 100) + 1; P_DamageMobj (tmthing, thing, thing, damage >> 2, tmthing->DamageType); P_TraceBleed (damage, tmthing, thing); } return false; } } // Check for missile if (tmthing->flags & MF_MISSILE) { // Check for a non-shootable mobj if (thing->flags2 & MF2_NONSHOOTABLE) { return true; } // Check for passing through a ghost if ((thing->flags3 & MF3_GHOST) && (tmthing->flags2 & MF2_THRUGHOST)) { return true; } // Check if it went over / under if (tmthing->z > thing->z + thing->height) { // Over thing return true; } if (tmthing->z+tmthing->height < thing->z) { // Under thing return true; } if (tmthing->flags2 & MF2_BOUNCE2) { if (tmthing->damage == 0) { return (tmthing->target == thing || !(thing->flags & MF_SOLID)); } } switch (tmthing->SpecialMissileHit (thing)) { case 0: return false; case 1: return true; default: break; } // [RH] Extend DeHacked infighting to allow for monsters // to never fight each other // [Graf Zahl] Why do I have the feeling that this didn't really work anymore now // that ZDoom supports friendly monsters? if (tmthing->target != NULL) { if (thing == tmthing->target) { // Don't missile self return true; } // players are never subject to infighting settings and are always allowed // to harm / be harmed by anything. if (!thing->player && !tmthing->target->player) { if (infighting < 0) { // -1: Monsters cannot hurt each other, but make exceptions for // friendliness and hate status. if (tmthing->target->flags & MF_SHOOTABLE) { if (!(thing->flags3 & MF3_ISMONSTER)) { return false; // Question: Should monsters be allowed to shoot barrels in this mode? // The old code does not. } // Monsters that are clearly hostile can always hurt each other if (!thing->IsHostile (tmthing->target)) { // The same if the shooter hates the target if (thing->tid == 0 || tmthing->target->TIDtoHate != thing->tid) { return false; } } } } else if (infighting == 0) { // 0: Monsters cannot hurt same species except // cases where they are clearly supposed to do that if (thing->IsFriend (tmthing->target)) { // Friends never harm each other return false; } if (thing->TIDtoHate != 0 && thing->TIDtoHate == tmthing->target->TIDtoHate) { // [RH] Don't hurt monsters that hate the same thing as you do return false; } if (thing->GetSpecies() == tmthing->target->GetSpecies()) { // Don't hurt same species or any relative - // but only if the target isn't one's hostile. if (!thing->IsHostile (tmthing->target)) { // Allow hurting monsters the shooter hates. if (thing->tid == 0 || tmthing->target->TIDtoHate != thing->tid) { return false; } } } } // else if (infighting==1) any shot hurts anything - no further tests } } if (!(thing->flags & MF_SHOOTABLE)) { // Didn't do any damage return !(thing->flags & MF_SOLID); } if ((thing->flags4 & MF4_SPECTRAL) && !(tmthing->flags4 & MF4_SPECTRAL)) { return true; } if (DoRipping) { if (LastRipped != thing) { LastRipped = thing; if (!(thing->flags & MF_NOBLOOD) && !(thing->flags2 & MF2_REFLECTIVE) && !(tmthing->flags3 & MF3_BLOODLESSIMPACT) && !(thing->flags2 & (MF2_INVULNERABLE|MF2_DORMANT))) { // Ok to spawn blood P_RipperBlood (tmthing, thing); } S_Sound (tmthing, CHAN_BODY, "misc/ripslop", 1, ATTN_IDLE); damage = ((pr_checkthing()&3)+2)*tmthing->damage; P_DamageMobj (thing, tmthing, tmthing->target, damage, tmthing->DamageType); if (!(tmthing->flags3 & MF3_BLOODLESSIMPACT)) { P_TraceBleed (damage, thing, tmthing); } if (thing->flags2 & MF2_PUSHABLE && !(tmthing->flags2 & MF2_CANNOTPUSH)) { // Push thing thing->momx += tmthing->momx>>2; thing->momy += tmthing->momy>>2; } } spechit.Clear (); return true; } // Do damage damage = pr_checkthing(); if (tmthing->flags4 & MF4_STRIFEDAMAGE) { damage &= 3; } else { damage &= 7; } damage = (damage + 1) * tmthing->damage; if (damage > 0) { P_DamageMobj (thing, tmthing, tmthing->target, damage, tmthing->DamageType); if ((tmthing->flags5 & MF5_BLOODSPLATTER) && !(thing->flags & MF_NOBLOOD) && !(thing->flags2 & MF2_REFLECTIVE) && !(thing->flags2 & (MF2_INVULNERABLE|MF2_DORMANT)) && !(tmthing->flags3 & MF3_BLOODLESSIMPACT) && (pr_checkthing() < 192)) { P_BloodSplatter (tmthing->x, tmthing->y, tmthing->z, thing); } if (!(tmthing->flags3 & MF3_BLOODLESSIMPACT)) { P_TraceBleed (damage, thing, tmthing); } } else if (damage < 0) { P_GiveBody (thing, -damage); } return false; // don't traverse any more } if (thing->flags2 & MF2_PUSHABLE && !(tmthing->flags2 & MF2_CANNOTPUSH) && (tmthing->player == NULL || !(tmthing->player->cheats & CF_PREDICTING))) { // Push thing thing->momx += tmthing->momx >> 2; thing->momy += tmthing->momy >> 2; } solid = (thing->flags & MF_SOLID) && !(thing->flags & MF_NOCLIP) && (tmthing->flags & MF_SOLID); // Check for special pickup if ((thing->flags & MF_SPECIAL) && (tmflags & MF_PICKUP) // [RH] The next condition is to compensate for the extra height // that gets added by P_CheckPosition() so that you cannot pick // up things that are above your true height. && thing->z < tmthing->z + tmthing->height - tmthing->MaxStepHeight) { // Can be picked up by tmthing P_TouchSpecialThing (thing, tmthing); // can remove thing } // killough 3/16/98: Allow non-solid moving objects to move through solid // ones, by allowing the moving thing (tmthing) to move if it's non-solid, // despite another solid thing being in the way. // killough 4/11/98: Treat no-clipping things as not blocking return !solid; // return !(thing->flags & MF_SOLID); // old code -- killough } // This routine checks for Lost Souls trying to be spawned // phares // across 1-sided lines, impassible lines, or "monsters can't // | // cross" lines. Draw an imaginary line between the PE // V // and the new Lost Soul spawn spot. If that line crosses // a 'blocking' line, then disallow the spawn. Only search // lines in the blocks of the blockmap where the bounding box // of the trajectory line resides. Then check bounding box // of the trajectory vs. the bounding box of each blocking // line to see if the trajectory and the blocking line cross. // Then check the PE and LS to see if they're on different // sides of the blocking line. If so, return true, otherwise // false. BOOL Check_Sides(AActor* actor, int x, int y) { int bx,by,xl,xh,yl,yh; pe_x = actor->x; pe_y = actor->y; ls_x = x; ls_y = y; // Here is the bounding box of the trajectory tmbbox[BOXLEFT] = pe_x < x ? pe_x : x; tmbbox[BOXRIGHT] = pe_x > x ? pe_x : x; tmbbox[BOXTOP] = pe_y > y ? pe_y : y; tmbbox[BOXBOTTOM] = pe_y < y ? pe_y : y; // Determine which blocks to look in for blocking lines xl = (tmbbox[BOXLEFT] - bmaporgx)>>MAPBLOCKSHIFT; xh = (tmbbox[BOXRIGHT] - bmaporgx)>>MAPBLOCKSHIFT; yl = (tmbbox[BOXBOTTOM] - bmaporgy)>>MAPBLOCKSHIFT; yh = (tmbbox[BOXTOP] - bmaporgy)>>MAPBLOCKSHIFT; // xl->xh, yl->yh determine the mapblock set to search validcount++; // prevents checking same line twice for (bx = xl ; bx <= xh ; bx++) for (by = yl ; by <= yh ; by++) if (!P_BlockLinesIterator(bx,by,PIT_CrossLine)) return true; // ^ return(false); // | } // phares //--------------------------------------------------------------------------- // // PIT_CheckOnmobjZ // //--------------------------------------------------------------------------- BOOL PIT_CheckOnmobjZ (AActor *thing) { if (!(thing->flags & MF_SOLID)) { // Can't hit thing return true; } if (thing->flags & (MF_SPECIAL|MF_NOCLIP|MF_CORPSE)) { // [RH] Corpses and specials and noclippers don't block moves return true; } if (!(thing->flags4 & MF4_ACTLIKEBRIDGE) && (tmthing->flags & MF_SPECIAL)) { // [RH] Only bridges block pickup items return true; } if (thing == tmthing) { // Don't clip against self return true; } if (tmthing->z > thing->z+thing->height) { // over thing return true; } else if (tmthing->z+tmthing->height <= thing->z) { // under thing return true; } fixed_t blockdist = thing->radius+tmthing->radius; if (abs(thing->x-tmx) >= blockdist || abs(thing->y-tmy) >= blockdist) { // Didn't hit thing return true; } onmobj = thing; return false; } /* =============================================================================== MOVEMENT CLIPPING =============================================================================== */ //---------------------------------------------------------------------------- // // FUNC P_TestMobjLocation // // Returns true if the mobj is not blocked by anything at its current // location, otherwise returns false. // //---------------------------------------------------------------------------- BOOL P_TestMobjLocation (AActor *mobj) { int flags; flags = mobj->flags; mobj->flags &= ~MF_PICKUP; if (P_CheckPosition(mobj, mobj->x, mobj->y)) { // XY is ok, now check Z mobj->flags = flags; fixed_t z = mobj->z; if (mobj->flags2 & MF2_FLOATBOB) { z -= FloatBobOffsets[(mobj->FloatBobPhase + level.maptime - 1) & 63]; } if ((z < mobj->floorz) || (z + mobj->height > mobj->ceilingz)) { // Bad Z return false; } return true; } mobj->flags = flags; return false; } // // P_CheckPosition // This is purely informative, nothing is modified // (except things picked up and missile damage applied). // // in: // a AActor (can be valid or invalid) // a position to be checked // (doesn't need to be related to the AActor->x,y) // // during: // special things are touched if MF_PICKUP // early out on solid lines? // // out: // newsubsec // floorz // ceilingz // tmdropoffz = the lowest point contacted (monsters won't move to a dropoff) // speciallines[] // numspeciallines // AActor *BlockingMobj = pointer to thing that blocked position (NULL if not // blocked, or blocked by a line). BOOL P_CheckPosition (AActor *thing, fixed_t x, fixed_t y) { static TArray checkpbt; int xl, xh; int yl, yh; int bx, by; subsector_t *newsubsec; AActor *thingblocker; AActor *fakedblocker; fixed_t realheight = thing->height; tmthing = thing; tmflags = thing->flags; tmx = x; tmy = y; tmbbox[BOXTOP] = y + thing->radius; tmbbox[BOXBOTTOM] = y - thing->radius; tmbbox[BOXRIGHT] = x + thing->radius; tmbbox[BOXLEFT] = x - thing->radius; newsubsec = R_PointInSubsector (x,y); ceilingline = BlockingLine = NULL; // The base floor / ceiling is from the subsector that contains the point. // Any contacted lines the step closer together will adjust them. tmfloorz = tmdropoffz = newsubsec->sector->floorplane.ZatPoint (x, y); tmceilingz = newsubsec->sector->ceilingplane.ZatPoint (x, y); tmfloorpic = newsubsec->sector->floorpic; tmfloorsector = newsubsec->sector; tmceilingpic = newsubsec->sector->ceilingpic; tmceilingsector = newsubsec->sector; //Added by MC: Fill the tmsector. tmsector = newsubsec->sector; validcount++; spechit.Clear (); checkpbt.Clear (); if ((tmflags & MF_NOCLIP) && !(tmflags & MF_SKULLFLY)) return true; // Check things first, possibly picking things up. // The bounding box is extended by MAXRADIUS // because DActors are grouped into mapblocks // based on their origin point, and can overlap // into adjacent blocks by up to MAXRADIUS units. xl = (tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT; xh = (tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT; yl = (tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT; yh = (tmbbox[BOXTOP] - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT; BlockingMobj = NULL; thingblocker = NULL; fakedblocker = NULL; if (thing->player) { // [RH] Fake taller height to catch stepping up into things. thing->height = realheight + thing->MaxStepHeight; } stepthing = NULL; for (bx = xl; bx <= xh; bx++) { for (by = yl; by <= yh; by++) { AActor *robin = NULL; do { if (!P_BlockThingsIterator (bx, by, PIT_CheckThing, checkpbt, robin)) { // [RH] If a thing can be stepped up on, we need to continue checking // other things in the blocks and see if we hit something that is // definitely blocking. Otherwise, we need to check the lines, or we // could end up stuck inside a wall. if (BlockingMobj == NULL || (i_compatflags & COMPATF_NO_PASSMOBJ)) { // Thing slammed into something; don't let it move now. thing->height = realheight; return false; } else if (!BlockingMobj->player && !(thing->flags & (MF_FLOAT|MF_MISSILE|MF_SKULLFLY)) && BlockingMobj->z+BlockingMobj->height-thing->z <= thing->MaxStepHeight) { if (thingblocker == NULL || BlockingMobj->z > thingblocker->z) { thingblocker = BlockingMobj; } robin = BlockingMobj; BlockingMobj = NULL; } else if (thing->player && thing->z + thing->height - BlockingMobj->z <= thing->MaxStepHeight) { if (thingblocker) { // There is something to step up on. Return this thing as // the blocker so that we don't step up. thing->height = realheight; return false; } // Nothing is blocking us, but this actor potentially could // if there is something else to step on. fakedblocker = BlockingMobj; robin = BlockingMobj; BlockingMobj = NULL; } else { // Definitely blocking thing->height = realheight; return false; } } else { robin = NULL; } } while (robin); } } // check lines // [RH] We need to increment validcount again, because a function above may // have already set some lines to equal the current validcount. // // Specifically, when DehackedPickup spawns a new item in its TryPickup() // function, that new actor will set the lines around it to match validcount // when it links itself into the world. If we just leave validcount alone, // that will give the player the freedom to walk through walls at will near // a pickup they cannot get, because their validcount will prevent them from // being considered for collision with the player. validcount++; BlockingMobj = NULL; thing->height = realheight; if (tmflags & MF_NOCLIP) return (BlockingMobj = thingblocker) == NULL; xl = (tmbbox[BOXLEFT] - bmaporgx)>>MAPBLOCKSHIFT; xh = (tmbbox[BOXRIGHT] - bmaporgx)>>MAPBLOCKSHIFT; yl = (tmbbox[BOXBOTTOM] - bmaporgy)>>MAPBLOCKSHIFT; yh = (tmbbox[BOXTOP] - bmaporgy)>>MAPBLOCKSHIFT; fixed_t thingdropoffz = tmfloorz; //bool onthing = (thingdropoffz != tmdropoffz); tmfloorz = tmdropoffz; for (bx=xl ; bx<=xh ; bx++) for (by=yl ; by<=yh ; by++) if (!P_BlockLinesIterator (bx,by,PIT_CheckLine)) return false; if (tmceilingz - tmfloorz < thing->height) return false; if (stepthing != NULL) { tmdropoffz = thingdropoffz; } return (BlockingMobj = thingblocker) == NULL; } //============================================================================= // // P_CheckOnmobj(AActor *thing) // // Checks if the new Z position is legal //============================================================================= AActor *P_CheckOnmobj (AActor *thing) { fixed_t oldz; bool good; oldz = thing->z; P_FakeZMovement (thing); good = P_TestMobjZ (thing); thing->z = oldz; return good ? NULL : onmobj; } //============================================================================= // // P_TestMobjZ // //============================================================================= bool P_TestMobjZ (AActor *actor) { static TArray mobjzbt; int xl,xh,yl,yh,bx,by; fixed_t x, y; if (actor->flags & MF_NOCLIP) return true; tmx = x = actor->x; tmy = y = actor->y; tmthing = actor; tmbbox[BOXTOP] = y + actor->radius; tmbbox[BOXBOTTOM] = y - actor->radius; tmbbox[BOXRIGHT] = x + actor->radius; tmbbox[BOXLEFT] = x - actor->radius; // // the bounding box is extended by MAXRADIUS because actors are grouped // into mapblocks based on their origin point, and can overlap into adjacent // blocks by up to MAXRADIUS units // xl = (tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT; xh = (tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT; yl = (tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT; yh = (tmbbox[BOXTOP] - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT; mobjzbt.Clear(); for (bx = xl; bx <= xh; bx++) for (by = yl; by <= yh; by++) if (!P_BlockThingsIterator (bx, by, PIT_CheckOnmobjZ, mobjzbt)) return false; return true; } //============================================================================= // // P_FakeZMovement // // Fake the zmovement so that we can check if a move is legal //============================================================================= void P_FakeZMovement (AActor *mo) { // // adjust height // mo->z += mo->momz; if ((mo->flags&MF_FLOAT) && mo->target) { // float down towards target if too close if (!(mo->flags & MF_SKULLFLY) && !(mo->flags & MF_INFLOAT)) { fixed_t dist = P_AproxDistance (mo->x - mo->target->x, mo->y - mo->target->y); fixed_t delta = (mo->target->z + (mo->height>>1)) - mo->z; if (delta < 0 && dist < -(delta*3)) mo->z -= mo->FloatSpeed; else if (delta > 0 && dist < (delta*3)) mo->z += mo->FloatSpeed; } } if (mo->player && mo->flags&MF_NOGRAVITY && (mo->z > mo->floorz)) { mo->z += finesine[(FINEANGLES/80*level.maptime)&FINEMASK]/8; } // // clip movement // if (mo->z <= mo->floorz) { // hit the floor mo->z = mo->floorz; } if (mo->z + mo->height > mo->ceilingz) { // hit the ceiling mo->z = mo->ceilingz - mo->height; } } //=========================================================================== // // CheckForPushSpecial // //=========================================================================== static void CheckForPushSpecial (line_t *line, int side, AActor *mobj) { if (line->special) { if (mobj->flags2 & MF2_PUSHWALL) { P_ActivateLine (line, mobj, side, SPAC_PUSH); } else if (mobj->flags2 & MF2_IMPACT) { P_ActivateLine (line, ((mobj->flags & MF_MISSILE) && (mobj->target != NULL)) ? mobj->target : mobj, side, SPAC_IMPACT); } } } // // P_TryMove // Attempt to move to a new position, // crossing special lines unless MF_TELEPORT is set. // BOOL P_TryMove (AActor *thing, fixed_t x, fixed_t y, BOOL dropoff, // killough 3/15/98: allow dropoff as option bool onfloor) // [RH] Let P_TryMove keep the thing on the floor { fixed_t oldx; fixed_t oldy; fixed_t oldz; int side; int oldside; line_t* ld; sector_t* oldsec = thing->Sector; // [RH] for sector actions sector_t* newsec; floatok = false; oldz = thing->z; if (onfloor) { thing->z = thing->floorsector->floorplane.ZatPoint (x, y); } if (!P_CheckPosition (thing, x, y)) { // Solid wall or thing if (!BlockingMobj || BlockingMobj->player || !thing->player) { goto pushline; } else { if (BlockingMobj->player || !thing->player) { goto pushline; } else if (BlockingMobj->z+BlockingMobj->height-thing->z > thing->MaxStepHeight || (BlockingMobj->Sector->ceilingplane.ZatPoint (x, y) - (BlockingMobj->z+BlockingMobj->height) < thing->height) || (tmceilingz-(BlockingMobj->z+BlockingMobj->height) < thing->height)) { goto pushline; } } if (!(tmthing->flags2 & MF2_PASSMOBJ) || (i_compatflags & COMPATF_NO_PASSMOBJ)) { thing->z = oldz; return false; } } if (onfloor && tmfloorsector == thing->floorsector) { thing->z = tmfloorz; } if (!(thing->flags & MF_NOCLIP)) { if (tmceilingz - tmfloorz < thing->height) { goto pushline; // doesn't fit } floatok = true; if (!(thing->flags & MF_TELEPORT) && tmceilingz - thing->z < thing->height && !(thing->flags3 & MF3_CEILINGHUGGER) && (!(thing->flags2 & MF2_FLY) || !(thing->flags & MF_NOGRAVITY))) { goto pushline; // mobj must lower itself to fit } if (thing->flags2 & MF2_FLY && thing->flags & MF_NOGRAVITY) { #if 1 if (thing->z+thing->height > tmceilingz) goto pushline; #else // When flying, slide up or down blocking lines until the actor // is not blocked. if (thing->z+thing->height > tmceilingz) { thing->momz = -8*FRACUNIT; goto pushline; } else if (thing->z < tmfloorz && tmfloorz-tmdropoffz > thing->MaxDropOffHeight) { thing->momz = 8*FRACUNIT; goto pushline; } #endif } if (!(thing->flags & MF_TELEPORT) && !(thing->flags3 & MF3_FLOORHUGGER)) { if (tmfloorz-thing->z > thing->MaxStepHeight) { // too big a step up goto pushline; } else if ((thing->flags & MF_MISSILE) && tmfloorz > thing->z) { // [RH] Don't let normal missiles climb steps goto pushline; } else if (thing->z < tmfloorz) { // [RH] Check to make sure there's nothing in the way for the step up fixed_t savedz = thing->z; bool good; thing->z = tmfloorz; good = P_TestMobjZ (thing); thing->z = savedz; if (!good) { goto pushline; } } } // killough 3/15/98: Allow certain objects to drop off if ((!dropoff && !(thing->flags & (MF_DROPOFF|MF_FLOAT|MF_MISSILE))) || (thing->flags5&MF5_NODROPOFF)) { fixed_t floorz = tmfloorz; // [RH] If the thing is standing on something, use its current z as the floorz. // This is so that it does not walk off of things onto a drop off. if (thing->flags2 & MF2_ONMOBJ) { floorz = MAX(thing->z, tmfloorz); } if (!(thing->flags5&MF5_AVOIDINGDROPOFF)) { if (floorz - tmdropoffz > thing->MaxDropOffHeight && !(thing->flags2 & MF2_BLASTED)) { // Can't move over a dropoff unless it's been blasted thing->z = oldz; return false; } } else { // special logic to move a monster off a dropoff if (thing->floorz - floorz > thing->MaxDropOffHeight || thing->dropoffz - tmdropoffz > thing->MaxDropOffHeight) return false; } } if (thing->flags2 & MF2_CANTLEAVEFLOORPIC && (tmfloorpic != thing->Sector->floorpic || tmfloorz - thing->z != 0)) { // must stay within a sector of a certain floor type thing->z = oldz; return false; } //Added by MC: To prevent bot from getting into dangerous sectors. if (thing->player && thing->player->isbot && thing->flags & MF_SHOOTABLE) { if (tmsector != thing->Sector && bglobal.IsDangerous (tmsector)) { thing->player->prev = thing->player->dest; thing->player->dest = NULL; thing->momx = 0; thing->momy = 0; thing->z = oldz; return false; } } } // [RH] Check status of eyes against fake floor/ceiling in case // it slopes or the player's eyes are bobbing in and out. bool oldAboveFakeFloor, oldAboveFakeCeiling; fixed_t viewheight; viewheight = thing->player ? thing->player->viewheight : thing->height / 2; oldAboveFakeFloor = oldAboveFakeCeiling = false; // pacify GCC if (oldsec->heightsec) { fixed_t eyez = oldz + viewheight; oldAboveFakeFloor = eyez > oldsec->heightsec->floorplane.ZatPoint (thing->x, thing->y); oldAboveFakeCeiling = eyez > oldsec->heightsec->ceilingplane.ZatPoint (thing->x, thing->y); } // the move is ok, so link the thing into its new position thing->UnlinkFromWorld (); oldx = thing->x; oldy = thing->y; thing->floorz = tmfloorz; thing->ceilingz = tmceilingz; thing->dropoffz = tmdropoffz; // killough 11/98: keep track of dropoffs thing->floorpic = tmfloorpic; thing->floorsector = tmfloorsector; thing->ceilingpic = tmceilingpic; thing->ceilingsector = tmceilingsector; thing->x = x; thing->y = y; thing->LinkToWorld (); if (thing->flags2 & MF2_FLOORCLIP) { thing->AdjustFloorClip (); } // [RH] Don't activate anything if just predicting if (thing->player && (thing->player->cheats & CF_PREDICTING)) { return true; } // if any special lines were hit, do the effect if (!(thing->flags & (MF_TELEPORT|MF_NOCLIP))) { while (spechit.Pop (ld)) { // see if the line was crossed side = P_PointOnLineSide (thing->x, thing->y, ld); oldside = P_PointOnLineSide (oldx, oldy, ld); if (side != oldside && ld->special) { if (thing->player) { P_ActivateLine (ld, thing, oldside, SPAC_CROSS); } else if (thing->flags2 & MF2_MCROSS) { P_ActivateLine (ld, thing, oldside, SPAC_MCROSS); } else if (thing->flags2 & MF2_PCROSS) { P_ActivateLine (ld, thing, oldside, SPAC_PCROSS); } else if ((ld->special == Teleport || ld->special == Teleport_NoFog || ld->special == Teleport_Line)) { // [RH] Just a little hack for BOOM compatibility P_ActivateLine (ld, thing, oldside, SPAC_MCROSS); } else { // I don't think allowing non-monsters to activate // monster-allowed lines will hurt Hexen compatibility. P_ActivateLine (ld, thing, oldside, SPAC_OTHERCROSS); } } } } // [RH] Check for crossing fake floor/ceiling newsec = thing->Sector; if (newsec->heightsec && oldsec->heightsec && newsec->SecActTarget) { const sector_t *hs = newsec->heightsec; fixed_t eyez = thing->z + viewheight; fixed_t fakez = hs->floorplane.ZatPoint (x, y); if (!oldAboveFakeFloor && eyez > fakez) { // View went above fake floor newsec->SecActTarget->TriggerAction (thing, SECSPAC_EyesSurface); } else if (oldAboveFakeFloor && eyez <= fakez) { // View went below fake floor newsec->SecActTarget->TriggerAction (thing, SECSPAC_EyesDive); } if (!(hs->MoreFlags & SECF_FAKEFLOORONLY)) { fakez = hs->ceilingplane.ZatPoint (x, y); if (!oldAboveFakeCeiling && eyez > fakez) { // View went above fake ceiling newsec->SecActTarget->TriggerAction (thing, SECSPAC_EyesAboveC); } else if (oldAboveFakeCeiling && eyez <= fakez) { // View went below fake ceiling newsec->SecActTarget->TriggerAction (thing, SECSPAC_EyesBelowC); } } } // [RH] If changing sectors, trigger transitions if (oldsec != newsec) { if (oldsec->SecActTarget) { oldsec->SecActTarget->TriggerAction (thing, SECSPAC_Exit); } if (newsec->SecActTarget) { int act = SECSPAC_Enter; if (thing->z <= newsec->floorplane.ZatPoint (thing->x, thing->y)) { act |= SECSPAC_HitFloor; } if (thing->z + thing->height >= newsec->ceilingplane.ZatPoint (thing->x, thing->y)) { act |= SECSPAC_HitCeiling; } if (newsec->heightsec && thing->z == newsec->heightsec->floorplane.ZatPoint (thing->x, thing->y)) { act |= SECSPAC_HitFakeFloor; } newsec->SecActTarget->TriggerAction (thing, act); } } return true; pushline: // [RH] Don't activate anything if just predicting if (thing->player && (thing->player->cheats & CF_PREDICTING)) { return false; } thing->z = oldz; if (!(thing->flags&(MF_TELEPORT|MF_NOCLIP))) { int numSpecHitTemp; if (tmthing->flags2 & MF2_BLASTED) { P_DamageMobj (tmthing, NULL, NULL, tmthing->Mass >> 5, MOD_HIT); } numSpecHitTemp = (int)spechit.Size (); while (numSpecHitTemp > 0) { // see which lines were pushed ld = spechit[--numSpecHitTemp]; side = P_PointOnLineSide (thing->x, thing->y, ld); CheckForPushSpecial (ld, side, thing); } } return false; } // // SLIDE MOVE // Allows the player to slide along any angled walls. // fixed_t bestslidefrac; fixed_t secondslidefrac; line_t* bestslideline; line_t* secondslideline; AActor* slidemo; fixed_t tmxmove; fixed_t tmymove; extern BOOL onground; // // P_HitSlideLine // Adjusts the xmove / ymove // so that the next move will slide along the wall. // If the floor is icy, then you can bounce off a wall. // phares // void P_HitSlideLine (line_t* ld) { int side; angle_t lineangle; angle_t moveangle; angle_t deltaangle; fixed_t movelen; BOOL icyfloor; // is floor icy? // phares // | // Under icy conditions, if the angle of approach to the wall // V // is more than 45 degrees, then you'll bounce and lose half // your momentum. If less than 45 degrees, you'll slide along // the wall. 45 is arbitrary and is believable. // Check for the special cases of horz or vert walls. // killough 10/98: only bounce if hit hard (prevents wobbling) icyfloor = (P_AproxDistance(tmxmove, tmymove) > 4*FRACUNIT) && var_friction && // killough 8/28/98: calc friction on demand slidemo->z <= slidemo->floorz && P_GetFriction (slidemo, NULL) > ORIG_FRICTION; if (ld->slopetype == ST_HORIZONTAL) { if (icyfloor && (abs(tmymove) > abs(tmxmove))) { tmxmove /= 2; // absorb half the momentum tmymove = -tmymove/2; if (slidemo->player && slidemo->health > 0 && !(slidemo->player->cheats & CF_PREDICTING)) { S_Sound (slidemo, CHAN_VOICE, "*grunt", 1, ATTN_IDLE); // oooff! } } else tmymove = 0; // no more movement in the Y direction return; } if (ld->slopetype == ST_VERTICAL) { if (icyfloor && (abs(tmxmove) > abs(tmymove))) { tmxmove = -tmxmove/2; // absorb half the momentum tmymove /= 2; if (slidemo->player && slidemo->health > 0 && !(slidemo->player->cheats & CF_PREDICTING)) { S_Sound (slidemo, CHAN_VOICE, "*grunt", 1, ATTN_IDLE); // oooff!// ^ } } // | else // phares tmxmove = 0; // no more movement in the X direction return; } // The wall is angled. Bounce if the angle of approach is // phares // less than 45 degrees. // phares side = P_PointOnLineSide (slidemo->x, slidemo->y, ld); lineangle = R_PointToAngle2 (0,0, ld->dx, ld->dy); if (side == 1) lineangle += ANG180; moveangle = R_PointToAngle2 (0,0, tmxmove, tmymove); moveangle += 10; // prevents sudden path reversal due to // phares // rounding error // | deltaangle = moveangle-lineangle; // V movelen = P_AproxDistance (tmxmove, tmymove); if (icyfloor && (deltaangle > ANG45) && (deltaangle < ANG90+ANG45)) { moveangle = lineangle - deltaangle; movelen /= 2; // absorb if (slidemo->player && slidemo->health > 0 && !(slidemo->player->cheats & CF_PREDICTING)) { S_Sound (slidemo, CHAN_VOICE, "*grunt", 1, ATTN_IDLE); // oooff! } moveangle >>= ANGLETOFINESHIFT; tmxmove = FixedMul (movelen, finecosine[moveangle]); tmymove = FixedMul (movelen, finesine[moveangle]); } // ^ else // | { // phares #if 0 fixed_t newlen; if (deltaangle > ANG180) deltaangle += ANG180; // I_Error ("SlideLine: ang>ANG180"); lineangle >>= ANGLETOFINESHIFT; deltaangle >>= ANGLETOFINESHIFT; newlen = FixedMul (movelen, finecosine[deltaangle]); tmxmove = FixedMul (newlen, finecosine[lineangle]); tmymove = FixedMul (newlen, finesine[lineangle]); #else divline_t dll, dlv; fixed_t inter1, inter2, inter3; P_MakeDivline (ld, &dll); dlv.x = slidemo->x; dlv.y = slidemo->y; dlv.dx = dll.dy; dlv.dy = -dll.dx; inter1 = P_InterceptVector(&dll, &dlv); dlv.dx = tmxmove; dlv.dy = tmymove; inter2 = P_InterceptVector (&dll, &dlv); inter3 = P_InterceptVector (&dlv, &dll); if (inter3 != 0) { tmxmove = Scale (inter2-inter1, dll.dx, inter3); tmymove = Scale (inter2-inter1, dll.dy, inter3); } else { tmxmove = tmymove = 0; } #endif } // phares } // // PTR_SlideTraverse // BOOL PTR_SlideTraverse (intercept_t* in) { line_t* li; if (!in->isaline) I_Error ("PTR_SlideTraverse: not a line?"); li = in->d.line; if ( !(li->flags & ML_TWOSIDED) ) { if (P_PointOnLineSide (slidemo->x, slidemo->y, li)) { // don't hit the back side return true; } goto isblocking; } if (li->flags & (ML_BLOCKING|ML_BLOCKEVERYTHING)) { goto isblocking; } // set openrange, opentop, openbottom P_LineOpening (li, trace.x + FixedMul (trace.dx, in->frac), trace.y + FixedMul (trace.dy, in->frac)); if (openrange < slidemo->height) goto isblocking; // doesn't fit if (opentop - slidemo->z < slidemo->height) goto isblocking; // mobj is too high if (openbottom - slidemo->z > slidemo->MaxStepHeight) { goto isblocking; // too big a step up } else if (slidemo->z < openbottom) { // [RH] Check to make sure there's nothing in the way for the step up fixed_t savedz = slidemo->z; slidemo->z = openbottom; bool good = P_TestMobjZ (slidemo); slidemo->z = savedz; if (!good) { goto isblocking; } } // this line doesn't block movement return true; // the line does block movement, // see if it is closer than best so far isblocking: if (in->frac < bestslidefrac) { secondslidefrac = bestslidefrac; secondslideline = bestslideline; bestslidefrac = in->frac; bestslideline = li; } return false; // stop } // // P_SlideMove // // The momx / momy move is bad, so try to slide along a wall. // // Find the first line hit, move flush to it, and slide along it // // This is a kludgy mess. // void P_SlideMove (AActor *mo, fixed_t tryx, fixed_t tryy, int numsteps) { fixed_t leadx, leady; fixed_t trailx, traily; fixed_t newx, newy; fixed_t xmove, ymove; bool walkplane; int hitcount; slidemo = mo; hitcount = 3; retry: if (!--hitcount) goto stairstep; // don't loop forever // trace along the three leading corners if (tryx > 0) { leadx = mo->x + mo->radius; trailx = mo->x - mo->radius; } else { leadx = mo->x - mo->radius; trailx = mo->x + mo->radius; } if (tryy > 0) { leady = mo->y + mo->radius; traily = mo->y - mo->radius; } else { leady = mo->y - mo->radius; traily = mo->y + mo->radius; } bestslidefrac = FRACUNIT+1; P_PathTraverse (leadx, leady, leadx+tryx, leady+tryy, PT_ADDLINES, PTR_SlideTraverse); P_PathTraverse (trailx, leady, trailx+tryx, leady+tryy, PT_ADDLINES, PTR_SlideTraverse); P_PathTraverse (leadx, traily, leadx+tryx, traily+tryy, PT_ADDLINES, PTR_SlideTraverse); // move up to the wall if (bestslidefrac == FRACUNIT+1) { // the move must have hit the middle, so stairstep stairstep: // killough 3/15/98: Allow objects to drop off ledges xmove = 0, ymove = tryy; walkplane = P_CheckSlopeWalk (mo, xmove, ymove); if (!P_TryMove (mo, mo->x + xmove, mo->y + ymove, true, walkplane)) { xmove = tryx, ymove = 0; walkplane = P_CheckSlopeWalk (mo, xmove, ymove); P_TryMove (mo, mo->x + xmove, mo->y + ymove, true, walkplane); } return; } // fudge a bit to make sure it doesn't hit bestslidefrac -= FRACUNIT/32; if (bestslidefrac > 0) { newx = FixedMul (tryx, bestslidefrac); newy = FixedMul (tryy, bestslidefrac); // killough 3/15/98: Allow objects to drop off ledges if (!P_TryMove (mo, mo->x+newx, mo->y+newy, true)) goto stairstep; } // Now continue along the wall. bestslidefrac = FRACUNIT - (bestslidefrac + FRACUNIT/32); // remainder if (bestslidefrac > FRACUNIT) bestslidefrac = FRACUNIT; else if (bestslidefrac <= 0) return; tryx = tmxmove = FixedMul (tryx, bestslidefrac); tryy = tmymove = FixedMul (tryy, bestslidefrac); P_HitSlideLine (bestslideline); // clip the moves mo->momx = tmxmove * numsteps; mo->momy = tmymove * numsteps; // killough 10/98: affect the bobbing the same way (but not voodoo dolls) if (mo->player && mo->player->mo == mo) { if (abs(mo->player->momx) > abs(mo->momx)) mo->player->momx = mo->momx; if (abs(mo->player->momy) > abs(mo->momy)) mo->player->momy = mo->momy; } walkplane = P_CheckSlopeWalk (mo, tmxmove, tmymove); // killough 3/15/98: Allow objects to drop off ledges if (!P_TryMove (mo, mo->x+tmxmove, mo->y+tmymove, true, walkplane)) { goto retry; } } //============================================================================ // // P_CheckSlopeWalk // //============================================================================ bool P_CheckSlopeWalk (AActor *actor, fixed_t &xmove, fixed_t &ymove) { if (actor->flags & MF_NOGRAVITY) { return false; } const secplane_t *plane = &actor->floorsector->floorplane; fixed_t planezhere = plane->ZatPoint (actor->x, actor->y); if (actor->floorsector != actor->Sector) { // this additional check prevents sliding on sloped dropoffs if (planezhere>actor->floorz+4*FRACUNIT) return false; } if (actor->z - planezhere > FRACUNIT) { // not on floor return false; } if ((plane->a | plane->b) != 0) { fixed_t destx, desty; fixed_t t; destx = actor->x + xmove; desty = actor->y + ymove; t = TMulScale16 (plane->a, destx, plane->b, desty, plane->c, actor->z) + plane->d; if (t < 0) { // Desired location is behind (below) the plane // (i.e. Walking up the plane) if (plane->c < STEEPSLOPE) { // Can't climb up slopes of ~45 degrees or more if (actor->flags & MF_NOCLIP) { return true; } else { const msecnode_t *node; bool dopush = true; if (plane->c > STEEPSLOPE*2/3) { for (node = actor->touching_sectorlist; node; node = node->m_tnext) { const sector_t *sec = node->m_sector; if (sec->floorplane.c >= STEEPSLOPE) { if (sec->floorplane.ZatPoint (destx, desty) >= actor->z - actor->MaxStepHeight) { dopush = false; break; } } } } if (dopush) { xmove = actor->momx = plane->a * 2; ymove = actor->momy = plane->b * 2; } return false; } } // Slide the desired location along the plane's normal // so that it lies on the plane's surface destx -= FixedMul (plane->a, t); desty -= FixedMul (plane->b, t); xmove = destx - actor->x; ymove = desty - actor->y; return true; } else if (t > 0) { // Desired location is in front of (above) the plane if (planezhere == actor->z) { // Actor's current spot is on/in the plane, so walk down it // Same principle as walking up, except reversed destx += FixedMul (plane->a, t); desty += FixedMul (plane->b, t); xmove = destx - actor->x; ymove = desty - actor->y; return true;//(plane->c >= STEEPSLOPE); } } } return false; } //============================================================================ // // PTR_BounceTraverse // //============================================================================ BOOL PTR_BounceTraverse (intercept_t *in) { line_t *li; if (!in->isaline) I_Error ("PTR_BounceTraverse: not a line?"); li = in->d.line; if (li->flags & ML_BLOCKEVERYTHING) { goto bounceblocking; } if (!(li->flags&ML_TWOSIDED)) { if (P_PointOnLineSide (slidemo->x, slidemo->y, li)) return true; // don't hit the back side goto bounceblocking; } P_LineOpening (li, trace.x + FixedMul (trace.dx, in->frac), trace.y + FixedMul (trace.dy, in->frac)); // set openrange, opentop, openbottom if (openrange < slidemo->height) goto bounceblocking; // doesn't fit if (opentop - slidemo->z < slidemo->height) goto bounceblocking; // mobj is too high return true; // this line doesn't block movement // the line does block movement, see if it is closer than best so far bounceblocking: if (in->frac < bestslidefrac) { secondslidefrac = bestslidefrac; secondslideline = bestslideline; bestslidefrac = in->frac; bestslideline = li; } return false; // stop } //============================================================================ // // P_BounceWall // //============================================================================ bool P_BounceWall (AActor *mo) { fixed_t leadx, leady; int side; angle_t lineangle, moveangle, deltaangle; fixed_t movelen; line_t *line; if (!(mo->flags2 & MF2_BOUNCE2)) { return false; } if (BlockingLine != NULL) { line = BlockingLine; } else { slidemo = mo; // // trace along the three leading corners // if (mo->momx > 0) { leadx = mo->x+mo->radius; } else { leadx = mo->x-mo->radius; } if (mo->momy > 0) { leady = mo->y+mo->radius; } else { leady = mo->y-mo->radius; } bestslidefrac = FRACUNIT+1; if (P_PathTraverse(leadx, leady, leadx+mo->momx, leady+mo->momy, PT_ADDLINES, PTR_BounceTraverse)) { // Could not find a wall, so bounce off the floor/ceiling instead. fixed_t floordist = mo->z - mo->floorz; fixed_t ceildist = mo->ceilingz - mo->z; if (floordist <= ceildist) { mo->FloorBounceMissile (mo->Sector->floorplane); return true; } else { mo->FloorBounceMissile (mo->Sector->ceilingplane); return true; } /* else { return (mo->flags2 & MF2_BOUNCE2) != 0; } */ } line = bestslideline; } if (line->special == Line_Horizon) { mo->SeeSound = 0; // it might make a sound otherwise mo->Destroy(); return true; } side = P_PointOnLineSide (mo->x, mo->y, line); lineangle = R_PointToAngle2 (0, 0, line->dx, line->dy); if (side == 1) { lineangle += ANG180; } moveangle = R_PointToAngle2 (0, 0, mo->momx, mo->momy); deltaangle = (2*lineangle)-moveangle; mo->angle = deltaangle; lineangle >>= ANGLETOFINESHIFT; deltaangle >>= ANGLETOFINESHIFT; movelen = P_AproxDistance (mo->momx, mo->momy); movelen = (movelen * 192) >> 8; // friction fixed_t box[4]; box[BOXTOP] = mo->y + mo->radius; box[BOXBOTTOM] = mo->y - mo->radius; box[BOXLEFT] = mo->x - mo->radius; box[BOXRIGHT] = mo->x + mo->radius; if (P_BoxOnLineSide (box, line) == -1) { mo->SetOrigin (mo->x + FixedMul(mo->radius, finecosine[deltaangle]), mo->y + FixedMul(mo->radius, finesine[deltaangle]), mo->z);; } if (movelen < FRACUNIT) { movelen = 2*FRACUNIT; } mo->momx = FixedMul(movelen, finecosine[deltaangle]); mo->momy = FixedMul(movelen, finesine[deltaangle]); return true; } // // P_LineAttack // AActor* linetarget; // who got hit (or NULL) AActor* shootthing; // Height if not aiming up or down // ???: use slope for monsters? fixed_t shootz; fixed_t attackrange; fixed_t aimpitch; // slopes to top and bottom of target // killough 4/20/98: make static instead of using ones in p_sight.c // [RH] made these angles instead of slopes static fixed_t toppitch; static fixed_t bottompitch; // // PTR_AimTraverse // Sets linetaget and aimpitch when a target is aimed at. // BOOL PTR_AimTraverse (intercept_t* in) { line_t* li; AActor* th; fixed_t pitch; fixed_t thingtoppitch; fixed_t thingbottompitch; fixed_t dist; if (in->isaline) { li = in->d.line; if ( !(li->flags & ML_TWOSIDED) || (li->flags & ML_BLOCKEVERYTHING) ) return false; // stop // Crosses a two sided line. // A two sided line will restrict the possible target ranges. P_LineOpening (li, trace.x + FixedMul (trace.dx, in->frac), trace.y + FixedMul (trace.dy, in->frac)); if (openbottom >= opentop) return false; // stop dist = FixedMul (attackrange, in->frac); pitch = -(int)R_PointToAngle2 (0, shootz, dist, openbottom); if (pitch < bottompitch) bottompitch = pitch; pitch = -(int)R_PointToAngle2 (0, shootz, dist, opentop); if (pitch > toppitch) toppitch = pitch; if (toppitch >= bottompitch) return false; // stop return true; // shot continues } // shoot a thing th = in->d.thing; if (th == shootthing) return true; // can't shoot self if (!(th->flags&MF_SHOOTABLE)) return true; // corpse or something // check for physical attacks on a ghost if ((th->flags3 & MF3_GHOST) && shootthing->player && // [RH] Be sure shootthing is a player shootthing->player->ReadyWeapon && (shootthing->player->ReadyWeapon->flags2 & MF2_THRUGHOST)) { return true; } // check angles to see if the thing can be aimed at dist = FixedMul (attackrange, in->frac); thingtoppitch = -(int)R_PointToAngle2 (0, shootz, dist, th->z + th->height); if (thingtoppitch > bottompitch) return true; // shot over the thing thingbottompitch = -(int)R_PointToAngle2 (0, shootz, dist, th->z); if (thingbottompitch < toppitch) return true; // shot under the thing // this thing can be hit! if (thingtoppitch < toppitch) thingtoppitch = toppitch; if (thingbottompitch > bottompitch) thingbottompitch = bottompitch; aimpitch = thingtoppitch/2 + thingbottompitch/2; linetarget = th; return false; // don't go any farther } // // P_AimLineAttack // fixed_t P_AimLineAttack (AActor *t1, angle_t angle, fixed_t distance, fixed_t vrange) { fixed_t x2; fixed_t y2; angle >>= ANGLETOFINESHIFT; shootthing = t1; x2 = t1->x + (distance>>FRACBITS)*finecosine[angle]; y2 = t1->y + (distance>>FRACBITS)*finesine[angle]; shootz = t1->z + (t1->height>>1) + 8*FRACUNIT; // can't shoot outside view angles if (vrange == 0) { if (t1->player == NULL || dmflags & DF_NO_FREELOOK) { vrange = ANGLE_1*35; } else { // 35 degrees is approximately what Doom used. You cannot have a // vrange of 0 degrees, because then toppitch and bottompitch will // be equal, and PTR_AimTraverse will never find anything to shoot at // if it crosses a line. vrange = clamp (t1->player->userinfo.aimdist, ANGLE_1/2, ANGLE_1*35); } } toppitch = t1->pitch - vrange; bottompitch = t1->pitch + vrange; attackrange = distance; linetarget = NULL; P_PathTraverse (t1->x, t1->y, x2, y2, PT_ADDLINES|PT_ADDTHINGS, PTR_AimTraverse); if (linetarget) return aimpitch; return t1->pitch; } /* ================= = = P_LineAttack = = if damage == 0, it is just a test trace that will leave linetarget set = ================= */ static bool CheckForGhost (FTraceResults &res) { if (res.HitType != TRACE_HitActor) { return false; } // check for physical attacks on a ghost if (res.Actor->flags3 & MF3_GHOST || res.Actor->flags4 & MF4_SPECTRAL) { res.HitType = TRACE_HitNone; return true; } return false; } static bool CheckForSpectral (FTraceResults &res) { if (res.HitType != TRACE_HitActor) { return false; } // check for physical attacks on spectrals if (res.Actor->flags4 & MF4_SPECTRAL) { res.HitType = TRACE_HitNone; return true; } return false; } void P_LineAttack (AActor *t1, angle_t angle, fixed_t distance, int pitch, int damage, int damageType, const PClass *pufftype) { fixed_t vx, vy, vz, shootz; FTraceResults trace; angle_t srcangle = angle; int srcpitch = pitch; bool hitGhosts; angle >>= ANGLETOFINESHIFT; pitch = (angle_t)(pitch) >> ANGLETOFINESHIFT; vx = FixedMul (finecosine[pitch], finecosine[angle]); vy = FixedMul (finecosine[pitch], finesine[angle]); vz = -finesine[pitch]; shootz = t1->z - t1->floorclip + (t1->height>>1) + 8*FRACUNIT; attackrange = distance; aimpitch = pitch; hitGhosts = (t1->player != NULL && t1->player->ReadyWeapon != NULL && (t1->player->ReadyWeapon->flags2 & MF2_THRUGHOST)); if (!Trace (t1->x, t1->y, shootz, t1->Sector, vx, vy, vz, distance, MF_SHOOTABLE, ML_BLOCKEVERYTHING, t1, trace, TRACE_NoSky|TRACE_Impact, hitGhosts ? CheckForGhost : CheckForSpectral)) { // hit nothing AActor *puffDefaults = GetDefaultByType (pufftype); if (puffDefaults->ActiveSound) { // Play miss sound S_SoundID (t1, CHAN_WEAPON, puffDefaults->ActiveSound, 1, ATTN_NORM); } if (puffDefaults->flags3 & MF3_ALWAYSPUFF) { // Spawn the puff anyway P_SpawnPuff (pufftype, trace.X, trace.Y, trace.Z, angle - ANG180, 2); } else { return; } } else { fixed_t hitx = 0, hity = 0, hitz = 0; AActor *puff = NULL; if (trace.HitType != TRACE_HitActor) { // position a bit closer for puffs if (trace.HitType != TRACE_HitWall || trace.Line->special != Line_Horizon) { fixed_t closer = trace.Distance - 4*FRACUNIT; puff = P_SpawnPuff (pufftype, t1->x + FixedMul (vx, closer), t1->y + FixedMul (vy, closer), shootz + FixedMul (vz, closer), angle - ANG90, 0); } // [RH] Spawn a decal if (trace.HitType == TRACE_HitWall && trace.Line->special != Line_Horizon) { SpawnShootDecal (t1, trace); } else if (puff != NULL && trace.CrossedWater == NULL && trace.Sector->heightsec == NULL && trace.HitType == TRACE_HitFloor) { P_HitWater (puff, trace.Sector); } } else { bool bloodsplatter = (t1->flags5 & MF5_BLOODSPLATTER) || (t1->player != NULL && t1->player->ReadyWeapon != NULL && (t1->player->ReadyWeapon->WeaponFlags & WIF_AXEBLOOD)); bool axeBlood = (t1->player != NULL && t1->player->ReadyWeapon != NULL && (t1->player->ReadyWeapon->WeaponFlags & WIF_AXEBLOOD)); // Hit a thing, so it could be either a puff or blood hitx = t1->x + FixedMul (vx, trace.Distance); hity = t1->y + FixedMul (vy, trace.Distance); hitz = shootz + FixedMul (vz, trace.Distance); // Spawn bullet puffs or blood spots, depending on target type. AActor *puffDefaults = GetDefaultByType (pufftype); if ((puffDefaults->flags3 & MF3_PUFFONACTORS) || (trace.Actor->flags & MF_NOBLOOD) || (trace.Actor->flags2 & (MF2_INVULNERABLE|MF2_DORMANT))) { puff = P_SpawnPuff (pufftype, hitx, hity, hitz, angle - ANG180, 2, true); } if (!(GetDefaultByType(pufftype)->flags3&MF3_BLOODLESSIMPACT)) { if (!bloodsplatter && !axeBlood && !(trace.Actor->flags & MF_NOBLOOD) && !(trace.Actor->flags2 & (MF2_INVULNERABLE|MF2_DORMANT))) { P_SpawnBlood (hitx, hity, hitz, angle - ANG180, damage, trace.Actor); } if (damage) { if (bloodsplatter || axeBlood) { if (!(trace.Actor->flags&MF_NOBLOOD) && !(trace.Actor->flags2&(MF2_INVULNERABLE|MF2_DORMANT))) { if (axeBlood) { P_BloodSplatter2 (hitx, hity, hitz, trace.Actor); } if (pr_lineattack() < 192) { P_BloodSplatter (hitx, hity, hitz, trace.Actor); } } } // [RH] Stick blood to walls P_TraceBleed (damage, trace.X, trace.Y, trace.Z, trace.Actor, srcangle, srcpitch); } } if (damage) P_DamageMobj (trace.Actor, puff ? puff : t1, t1, damage, damageType); } if (trace.CrossedWater) { bool killPuff = false; if (puff == NULL) { // Spawn puff just to get a mass for the splash puff = P_SpawnPuff (pufftype, hitx, hity, hitz, angle - ANG180, 2, true); killPuff = true; } SpawnDeepSplash (t1, trace, puff, vx, vy, vz); if (killPuff) { puff->Destroy(); } } } } void P_TraceBleed (int damage, fixed_t x, fixed_t y, fixed_t z, AActor *actor, angle_t angle, int pitch) { if (!cl_bloodsplats) return; const char *bloodType = "BloodSplat"; int count; int noise; if ((actor->flags & MF_NOBLOOD) || (actor->flags2 & (MF2_INVULNERABLE|MF2_DORMANT)) || (actor->player && actor->player->cheats & CF_GODMODE)) { return; } if (damage < 15) { // For low damages, there is a chance to not spray blood at all if (damage <= 10) { if (pr_tracebleed() < 160) { return; } } count = 1; noise = 18; } else if (damage < 25) { count = 2; noise = 19; } else { // For high damages, there is a chance to spray just one big glob of blood if (pr_tracebleed() < 24) { bloodType = "BloodSmear"; count = 1; noise = 20; } else { count = 3; noise = 20; } } for (; count; --count) { FTraceResults bleedtrace; angle_t bleedang = (angle + ((pr_tracebleed()-128) << noise)) >> ANGLETOFINESHIFT; angle_t bleedpitch = (angle_t)(pitch + ((pr_tracebleed()-128) << noise)) >> ANGLETOFINESHIFT; fixed_t vx = FixedMul (finecosine[bleedpitch], finecosine[bleedang]); fixed_t vy = FixedMul (finecosine[bleedpitch], finesine[bleedang]); fixed_t vz = -finesine[bleedpitch]; if (Trace (x, y, z, actor->Sector, vx, vy, vz, 172*FRACUNIT, 0, ML_BLOCKEVERYTHING, actor, bleedtrace, TRACE_NoSky)) { if (bleedtrace.HitType == TRACE_HitWall) { PalEntry bloodcolor = (PalEntry)actor->GetClass()->Meta.GetMetaInt(AMETA_BloodColor); if (bloodcolor != 0) { bloodcolor.r>>=1; // the full color is too bright for blood decals bloodcolor.g>>=1; bloodcolor.b>>=1; bloodcolor.a=1; } DImpactDecal::StaticCreate (bloodType, bleedtrace.X, bleedtrace.Y, bleedtrace.Z, sides + bleedtrace.Line->sidenum[bleedtrace.Side], bloodcolor); } } } } void P_TraceBleed (int damage, AActor *target, angle_t angle, int pitch) { P_TraceBleed (damage, target->x, target->y, target->z + target->height/2, target, angle, pitch); } void P_TraceBleed (int damage, AActor *target, AActor *missile) { int pitch; if (target == NULL || missile->flags3 & MF3_BLOODLESSIMPACT) { return; } if (missile->momz != 0) { double aim; aim = atan ((double)missile->momz / (double)P_AproxDistance (missile->x - target->x, missile->y - target->y)); pitch = -(int)(aim * ANGLE_180/PI); } else { pitch = 0; } P_TraceBleed (damage, target->x, target->y, target->z + target->height/2, target, R_PointToAngle2 (missile->x, missile->y, target->x, target->y), pitch); } void P_TraceBleed (int damage, AActor *target) { fixed_t one = pr_tracebleed() << 24; fixed_t two = (pr_tracebleed()-128) << 16; P_TraceBleed (damage, target->x, target->y, target->z + target->height/2, target, one, two); } // // [RH] Rail gun stuffage // struct SRailHit { AActor *HitActor; fixed_t Distance; }; static TArray RailHits (16); static bool ProcessRailHit (FTraceResults &res) { if (res.HitType != TRACE_HitActor) { return false; } // Invulnerable things completely block the shot if (res.Actor->flags2 & MF2_INVULNERABLE) { return false; } // Save this thing for damaging later, and continue the trace SRailHit newhit; newhit.HitActor = res.Actor; newhit.Distance = res.Distance - 10*FRACUNIT; // put blood in front RailHits.Push (newhit); return true; } void P_RailAttack (AActor *source, int damage, int offset, int color1, int color2, float maxdiff, bool silent) { fixed_t vx, vy, vz; angle_t angle, pitch; fixed_t x1, y1; vec3_t start, end; FTraceResults trace; pitch = (angle_t)(-source->pitch) >> ANGLETOFINESHIFT; angle = source->angle >> ANGLETOFINESHIFT; vx = FixedMul (finecosine[pitch], finecosine[angle]); vy = FixedMul (finecosine[pitch], finesine[angle]); vz = finesine[pitch]; x1 = source->x; y1 = source->y; shootz = source->z - source->floorclip + (source->height >> 1) + 8*FRACUNIT; angle = (source->angle - ANG90) >> ANGLETOFINESHIFT; x1 += offset*finecosine[angle]; y1 += offset*finesine[angle]; RailHits.Clear (); VectorFixedSet (start, x1, y1, shootz); Trace (x1, y1, shootz, source->Sector, vx, vy, vz, 8192*FRACUNIT, MF_SHOOTABLE, ML_BLOCKEVERYTHING, source, trace, TRACE_PCross|TRACE_Impact, ProcessRailHit); if (trace.HitType == TRACE_HitWall) { SpawnShootDecal (source, trace); } if (trace.HitType == TRACE_HitFloor && trace.CrossedWater == NULL && trace.Sector->heightsec == NULL) { fixed_t savex, savey, savez; fixed_t savefloor, saveceil, savedropoff; int savefloorpic; sector_t *savefloorsec; int saveceilingpic; sector_t *saveceilingsec; savex = source->x; savey = source->y; savez = source->z; savefloor = source->floorz; saveceil = source->ceilingz; savedropoff = source->dropoffz; savefloorpic = source->floorpic; savefloorsec = source->floorsector; saveceilingpic = source->ceilingpic; saveceilingsec = source->ceilingsector; source->SetOrigin (trace.X, trace.Y, trace.Z); P_HitWater (source, trace.Sector); source->SetOrigin (savex, savey, savez); source->floorz = savefloor; source->ceilingz = saveceil; source->dropoffz = savedropoff; source->floorpic = savefloorpic; source->floorsector = savefloorsec; source->ceilingpic = saveceilingpic; source->ceilingsector = saveceilingsec; } if (trace.CrossedWater) { AActor *puff = Spawn (0, 0, 0, ALLOW_REPLACE); if (puff != NULL) { SpawnDeepSplash (source, trace, puff, vx, vy, vz); puff->Destroy (); } } // Now hurt anything the trace hit unsigned int i; for (i = 0; i < RailHits.Size (); i++) { fixed_t x, y, z; x = x1 + FixedMul (RailHits[i].Distance, vx); y = y1 + FixedMul (RailHits[i].Distance, vy); z = shootz + FixedMul (RailHits[i].Distance, vz); if ((RailHits[i].HitActor->flags & MF_NOBLOOD) || (RailHits[i].HitActor->flags2 & (MF2_DORMANT|MF2_INVULNERABLE))) { P_SpawnPuff (RUNTIME_CLASS(ABulletPuff), x, y, z, source->angle - ANG180, 1, true); } else { P_SpawnBlood (x, y, z, source->angle - ANG180, damage, RailHits[i].HitActor); } P_DamageMobj (RailHits[i].HitActor, source, source, damage, MOD_RAILGUN); P_TraceBleed (damage, x, y, z, RailHits[i].HitActor, angle, pitch); } VectorFixedSet (end, trace.X, trace.Y, trace.Z); P_DrawRailTrail (source, start, end, color1, color2, maxdiff, silent); } // // [RH] P_AimCamera // CVAR (Float, chase_height, -8.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) CVAR (Float, chase_dist, 90.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) fixed_t CameraX, CameraY, CameraZ; sector_t *CameraSector; void P_AimCamera (AActor *t1) { fixed_t distance = (fixed_t)(chase_dist * FRACUNIT); angle_t angle = (t1->angle - ANG180) >> ANGLETOFINESHIFT; angle_t pitch = (angle_t)(t1->pitch) >> ANGLETOFINESHIFT; FTraceResults trace; fixed_t vx, vy, vz, sz; vx = FixedMul (finecosine[pitch], finecosine[angle]); vy = FixedMul (finecosine[pitch], finesine[angle]); vz = finesine[pitch]; sz = t1->z - t1->floorclip + t1->height + (fixed_t)(chase_height * FRACUNIT); if (Trace (t1->x, t1->y, sz, t1->Sector, vx, vy, vz, distance, 0, 0, NULL, trace) && trace.Distance > 10*FRACUNIT) { // Position camera slightly in front of hit thing fixed_t dist = trace.Distance - 5*FRACUNIT; CameraX = t1->x + FixedMul (vx, dist); CameraY = t1->y + FixedMul (vy, dist); CameraZ = sz + FixedMul (vz, dist); } else { CameraX = trace.X; CameraY = trace.Y; CameraZ = trace.Z; } CameraSector = trace.Sector; } // // USE LINES // AActor *usething; bool foundline; BOOL PTR_UseTraverse (intercept_t *in) { // [RH] Check for things to talk with or use a puzzle item on if (!in->isaline) { if (usething==in->d.thing) return true; // Check thing // Check for puzzle item use if (in->d.thing->special == USE_PUZZLE_ITEM_SPECIAL) { if (LineSpecials[USE_PUZZLE_ITEM_SPECIAL] (NULL, usething, false, in->d.thing->args[0], in->d.thing->args[1], in->d.thing->args[2], in->d.thing->args[3], in->d.thing->args[4])) return false; } // Dead things can't talk. if (in->d.thing->health <= 0) { return true; } // Fighting things don't talk either. if (in->d.thing->flags4 & MF4_INCOMBAT) { return true; } if (in->d.thing->Conversation != NULL) { // Give the NPC a chance to play a brief animation in->d.thing->ConversationAnimation (0); P_StartConversation (in->d.thing, usething, true, true); return false; } return true; } // [RH] The range passed to P_PathTraverse was doubled so that it could // find things up to 128 units away (for Strife), but it should still reject // lines further than 64 units away. if (in->frac > FRACUNIT/2) { P_LineOpening (in->d.line, trace.x + FixedMul (trace.dx, in->frac), trace.y + FixedMul (trace.dy, in->frac)); return openrange>0; } if (in->d.line->special == 0 || (GET_SPAC(in->d.line->flags) != SPAC_USETHROUGH && GET_SPAC(in->d.line->flags) != SPAC_USE)) { blocked: if (in->d.line->flags & ML_BLOCKEVERYTHING) { openrange = 0; } else { P_LineOpening (in->d.line, trace.x + FixedMul (trace.dx, in->frac), trace.y + FixedMul (trace.dy, in->frac)); } if (openrange <= 0 || (in->d.line->special != 0 && (i_compatflags & COMPATF_USEBLOCKING))) { // [RH] Give sector a chance to intercept the use sector_t * sec; sec = usething->Sector; if (sec->SecActTarget && sec->SecActTarget->TriggerAction (usething, SECSPAC_Use)) { return false; } sec = P_PointOnLineSide(usething->x, usething->y, in->d.line) == 0? in->d.line->frontsector : in->d.line->backsector; if (sec != NULL && sec->SecActTarget && sec->SecActTarget->TriggerAction (usething, SECSPAC_UseWall)) { return false; } if (usething->player) { S_Sound (usething, CHAN_VOICE, "*usefail", 1, ATTN_IDLE); } return false; // can't use through a wall } foundline = true; return true; // not a special line, but keep checking } if (P_PointOnLineSide (usething->x, usething->y, in->d.line) == 1) // [RH] continue traversal for two-sided lines //return in->d.line->backsector != NULL; // don't use back side goto blocked; // do a proper check for back sides of triggers P_ActivateLine (in->d.line, usething, 0, SPAC_USE); //WAS can't use more than one special line in a row //jff 3/21/98 NOW multiple use allowed with enabling line flag //[RH] And now I've changed it again. If the line is of type // SPAC_USE, then it eats the use. Everything else passes // it through, including SPAC_USETHROUGH. if (i_compatflags & COMPATF_USEBLOCKING) { return GET_SPAC(in->d.line->flags) == SPAC_USETHROUGH; } else { return GET_SPAC(in->d.line->flags) != SPAC_USE; } } // Returns false if a "oof" sound should be made because of a blocking // linedef. Makes 2s middles which are impassable, as well as 2s uppers // and lowers which block the player, cause the sound effect when the // player tries to activate them. Specials are excluded, although it is // assumed that all special linedefs within reach have been considered // and rejected already (see P_UseLines). // // by Lee Killough // BOOL PTR_NoWayTraverse (intercept_t *in) { line_t *ld = in->d.line; // [GrafZahl] de-obfuscated. Was I the only one who was unable to makes sense out of // this convoluted mess? if (ld->special) return true; if (ld->flags&(ML_BLOCKING|ML_BLOCKEVERYTHING)) return false; P_LineOpening(ld, trace.x+FixedMul(trace.dx, in->frac),trace.y+FixedMul(trace.dy, in->frac)); return openrange >0 && openbottom <= usething->z + usething->MaxStepHeight && opentop >= usething->z + usething->height; } /* ================ = = P_UseLines = = Looks for special lines in front of the player to activate ================ */ void P_UseLines (player_t *player) { angle_t angle; fixed_t x1, y1, x2, y2; usething = player->mo; foundline = false; angle = player->mo->angle >> ANGLETOFINESHIFT; x1 = player->mo->x; y1 = player->mo->y; x2 = x1 + (USERANGE>>FRACBITS)*finecosine[angle]*2; y2 = y1 + (USERANGE>>FRACBITS)*finesine[angle]*2; // old code: // // P_PathTraverse ( x1, y1, x2, y2, PT_ADDLINES, PTR_UseTraverse ); // // This added test makes the "oof" sound work on 2s lines -- killough: if (P_PathTraverse (x1, y1, x2, y2, PT_ADDLINES|PT_ADDTHINGS, PTR_UseTraverse)) { // [RH] Give sector a chance to eat the use sector_t *sec = usething->Sector; int spac = SECSPAC_Use; if (foundline) spac |= SECSPAC_UseWall; if ((!sec->SecActTarget || !sec->SecActTarget->TriggerAction (usething, spac)) && !P_PathTraverse (x1, y1, x2, y2, PT_ADDLINES, PTR_NoWayTraverse)) { S_Sound (usething, CHAN_VOICE, "*usefail", 1, ATTN_IDLE); } } } //========================================================================== // // PTR_PuzzleItemTraverse // //========================================================================== static AActor *PuzzleItemUser; static int PuzzleItemType; static bool PuzzleActivated; BOOL PTR_PuzzleItemTraverse (intercept_t *in) { AActor *mobj; if (in->isaline) { // Check line if (in->d.line->special != USE_PUZZLE_ITEM_SPECIAL) { P_LineOpening (in->d.line, trace.x + FixedMul (trace.dx, in->frac), trace.y + FixedMul (trace.dy, in->frac)); if (openrange <= 0) { return false; // can't use through a wall } return true; // Continue searching } if (P_PointOnLineSide (PuzzleItemUser->x, PuzzleItemUser->y, in->d.line) == 1) { // Don't use back sides return false; } if (PuzzleItemType != in->d.line->args[0]) { // Item type doesn't match return false; } P_StartScript (PuzzleItemUser, in->d.line, in->d.line->args[1], NULL, 0, in->d.line->args[2], in->d.line->args[3], in->d.line->args[4], true, false); in->d.line->special = 0; PuzzleActivated = true; return false; // Stop searching } // Check thing mobj = in->d.thing; if (mobj->special != USE_PUZZLE_ITEM_SPECIAL) { // Wrong special return true; } if (PuzzleItemType != mobj->args[0]) { // Item type doesn't match return true; } P_StartScript (PuzzleItemUser, NULL, mobj->args[1], NULL, 0, mobj->args[2], mobj->args[3], mobj->args[4], true, false); mobj->special = 0; PuzzleActivated = true; return false; // Stop searching } //========================================================================== // // P_UsePuzzleItem // // Returns true if the puzzle item was used on a line or a thing. // //========================================================================== bool P_UsePuzzleItem (AActor *actor, int itemType) { int angle; fixed_t x1, y1, x2, y2; PuzzleItemType = itemType; PuzzleItemUser = actor; PuzzleActivated = false; angle = actor->angle>>ANGLETOFINESHIFT; x1 = actor->x; y1 = actor->y; x2 = x1+(USERANGE>>FRACBITS)*finecosine[angle]; y2 = y1+(USERANGE>>FRACBITS)*finesine[angle]; P_PathTraverse (x1, y1, x2, y2, PT_ADDLINES|PT_ADDTHINGS, PTR_PuzzleItemTraverse); return PuzzleActivated; } // // RADIUS ATTACK // AActor* bombsource; AActor* bombspot; int bombdamage; float bombdamagefloat; int bombdistance; float bombdistancefloat; bool DamageSource; int bombmod; vec3_t bombvec; bool bombthrustless; bool bombdodamage; //============================================================================= // // PIT_RadiusAttack // // "bombsource" is the creature that caused the explosion at "bombspot". // [RH] Now it knows about vertical distances and can thrust things vertically. //============================================================================= // [RH] Damage scale to apply to thing that shot the missile. static float selfthrustscale; CUSTOM_CVAR (Float, splashfactor, 1.f, CVAR_SERVERINFO) { if (self <= 0.f) self = 1.f; else selfthrustscale = 1.f / self; } BOOL PIT_RadiusAttack (AActor *thing) { if (!(thing->flags & MF_SHOOTABLE) ) return true; // Boss spider and cyborg and Heretic's ep >= 2 bosses // take no damage from concussion. if (thing->flags3 & MF3_NORADIUSDMG) return true; if (!DamageSource && thing == bombsource) { // don't damage the source of the explosion return true; } // a much needed option: monsters that fire explosive projectiles cannot // be hurt by projectiles fired by a monster of the same type. // Controlled by the DONTHURTSPECIES flag. if (bombsource && thing->GetClass() == bombsource->GetClass() && !thing->player && bombsource->flags4 & MF4_DONTHURTSPECIES ) return true; // Barrels always use the original code, since this makes // them far too "active." BossBrains also use the old code // because some user levels require they have a height of 16, // which can make them near impossible to hit with the new code. if (!bombdodamage || !((bombspot->flags5 | thing->flags5) & MF5_OLDRADIUSDMG)) { // [RH] New code. The bounding box only covers the // height of the thing and not the height of the map. float points; float len; fixed_t dx, dy; float boxradius; dx = abs (thing->x - bombspot->x); dy = abs (thing->y - bombspot->y); boxradius = float (thing->radius); // The damage pattern is square, not circular. len = float (dx > dy ? dx : dy); if (bombspot->z < thing->z || bombspot->z >= thing->z + thing->height) { float dz; if (bombspot->z > thing->z) { dz = float (bombspot->z - thing->z - thing->height); } else { dz = float (thing->z - bombspot->z); } if (len <= boxradius) { len = dz; } else { len -= boxradius; len = sqrtf (len*len + dz*dz); } } else { len -= boxradius; if (len < 0.f) len = 0.f; } len /= FRACUNIT; points = bombdamagefloat * (1.f - len * bombdistancefloat); if (thing == bombsource) { points = points * splashfactor; } points *= thing->GetClass()->Meta.GetMetaFixed(AMETA_RDFactor, FRACUNIT)/(float)FRACUNIT; if (points > 0.f && P_CheckSight (thing, bombspot, 1)) { // OK to damage; target is in direct path float momz; float thrust; int damage = (int)points; if (bombdodamage) P_DamageMobj (thing, bombspot, bombsource, damage, bombmod); else thing->flags2 |= MF2_BLASTED; if (!(bombspot->flags2 & MF2_NODMGTHRUST) && !(thing->flags & MF_ICECORPSE)) { if (bombdodamage) P_TraceBleed (damage, thing, bombspot); if (!bombthrustless) { thrust = points * 0.5f / (float)thing->Mass; if (bombsource == thing) { thrust *= selfthrustscale; } momz = (float)(thing->z + (thing->height>>1) - bombspot->z) * thrust; if (bombsource != thing) { momz *= 0.5f; } else { momz *= 0.8f; } angle_t ang = R_PointToAngle2 (bombspot->x, bombspot->y, thing->x, thing->y) >> ANGLETOFINESHIFT; thing->momx += fixed_t (finecosine[ang] * thrust); thing->momy += fixed_t (finesine[ang] * thrust); thing->momz += (fixed_t)momz; } } } } else { // [RH] Old code just for barrels fixed_t dx, dy, dist; dx = abs (thing->x - bombspot->x); dy = abs (thing->y - bombspot->y); dist = dx>dy ? dx : dy; dist = (dist - thing->radius) >> FRACBITS; if (dist < 0) dist = 0; if (dist >= bombdistance) return true; // out of range if (P_CheckSight (thing, bombspot, 1)) { // OK to damage; target is in direct path int damage = Scale (bombdamage, bombdistance-dist, bombdistance); damage = (int)((float)damage * splashfactor); damage = Scale(damage, thing->GetClass()->Meta.GetMetaFixed(AMETA_RDFactor, FRACUNIT), FRACUNIT); if (damage > 0) { P_DamageMobj (thing, bombspot, bombsource, damage, bombmod); P_TraceBleed (damage, thing, bombspot); } } } return true; } // // P_RadiusAttack // Source is the creature that caused the explosion at spot. // void P_RadiusAttack (AActor *spot, AActor *source, int damage, int distance, int damageType, bool hurtSource, bool thrustless, bool dodamage) { static TArray radbt; int x, y; int xl, xh, yl, yh; fixed_t dist; if (distance <= 0) return; dist = (distance + MAXRADIUS)<y + dist - bmaporgy)>>MAPBLOCKSHIFT; yl = (spot->y - dist - bmaporgy)>>MAPBLOCKSHIFT; xh = (spot->x + dist - bmaporgx)>>MAPBLOCKSHIFT; xl = (spot->x - dist - bmaporgx)>>MAPBLOCKSHIFT; bombspot = spot; bombsource = source; bombdamage = damage; bombdistance = distance; bombdistancefloat = 1.f / (float)distance; bombthrustless = thrustless; DamageSource = hurtSource; bombdamagefloat = (float)damage; bombmod = damageType; bombdodamage = dodamage; VectorPosition (spot, bombvec); radbt.Clear(); for (y = yl; y <= yh; y++) for (x = xl; x <= xh; x++) P_BlockThingsIterator (x, y, PIT_RadiusAttack, radbt); } // // SECTOR HEIGHT CHANGING // After modifying a sector's floor or ceiling height, // call this routine to adjust the positions // of all things that touch the sector. // // If anything doesn't fit anymore, true will be returned. // // [RH] If crushchange is non-negative, they will take the // specified amount of damage as they are being crushed. // If crushchange is negative, you should set the sector // height back the way it was and call P_ChangeSector() // again to undo the changes. // Note that this is very different from the original // true/false usage of crushchange! If you want regular // DOOM crushing behavior set crushchange to 10 or -1 // if no crushing is desired. // static int moveamt; int crushchange; static sector_t *movesec; bool nofit; TArray intersectors; EXTERN_CVAR (Int, cl_bloodtype) //============================================================================= // // P_AdjustFloorCeil // //============================================================================= BOOL P_AdjustFloorCeil (AActor *thing) { BOOL isgood = P_CheckPosition (thing, thing->x, thing->y); thing->floorz = tmfloorz; thing->ceilingz = tmceilingz; thing->dropoffz = tmdropoffz; // killough 11/98: remember dropoffs thing->floorpic = tmfloorpic; thing->floorsector = tmfloorsector; thing->ceilingpic = tmceilingpic; thing->ceilingsector = tmceilingsector; return isgood; } //============================================================================= // // PIT_FindAboveIntersectors // //============================================================================= BOOL PIT_FindAboveIntersectors (AActor *thing) { if (!(thing->flags & MF_SOLID)) { // Can't hit thing return true; } if (thing->flags & (MF_CORPSE|MF_SPECIAL)) { // [RH] Corpses and specials don't block moves return true; } if (thing == tmthing) { // Don't clip against self return true; } fixed_t blockdist = thing->radius+tmthing->radius; if (abs(thing->x-tmx) >= blockdist || abs(thing->y-tmy) >= blockdist) { // Didn't hit thing return true; } if (thing->z >= tmthing->z && thing->z <= tmthing->z + tmthing->height) { // Thing intersects above the base intersectors.Push (thing); } return true; } //============================================================================= // // PIT_FindBelowIntersectors // //============================================================================= BOOL PIT_FindBelowIntersectors (AActor *thing) { if (!(thing->flags & MF_SOLID)) { // Can't hit thing return true; } if (thing->flags & (MF_CORPSE|MF_SPECIAL)) { // [RH] Corpses and specials don't block moves return true; } if (thing == tmthing) { // Don't clip against self return true; } fixed_t blockdist = thing->radius+tmthing->radius; if (abs(thing->x-tmx) >= blockdist || abs(thing->y-tmy) >= blockdist) { // Didn't hit thing return true; } if (thing->z + thing->height <= tmthing->z + tmthing->height && thing->z + thing->height > tmthing->z) { // Thing intersects below the base intersectors.Push (thing); } return true; } //============================================================================= // // P_FindAboveIntersectors // //============================================================================= void P_FindAboveIntersectors (AActor *actor) { static TArray abovebt; int xl,xh,yl,yh,bx,by; fixed_t x, y; if (actor->flags & MF_NOCLIP) return; if (!(actor->flags & MF_SOLID)) return; tmx = x = actor->x; tmy = y = actor->y; tmthing = actor; tmbbox[BOXTOP] = y + actor->radius; tmbbox[BOXBOTTOM] = y - actor->radius; tmbbox[BOXRIGHT] = x + actor->radius; tmbbox[BOXLEFT] = x - actor->radius; // // the bounding box is extended by MAXRADIUS because actors are grouped // into mapblocks based on their origin point, and can overlap into adjacent // blocks by up to MAXRADIUS units // xl = (tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT; xh = (tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT; yl = (tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT; yh = (tmbbox[BOXTOP] - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT; abovebt.Clear(); for (bx = xl; bx <= xh; bx++) for (by = yl; by <= yh; by++) if (!P_BlockThingsIterator (bx, by, PIT_FindAboveIntersectors, abovebt)) return; return; } //============================================================================= // // P_FindBelowIntersectors // //============================================================================= void P_FindBelowIntersectors (AActor *actor) { static TArray belowbt; int xl,xh,yl,yh,bx,by; fixed_t x, y; if (actor->flags & MF_NOCLIP) return; if (!(actor->flags & MF_SOLID)) return; tmx = x = actor->x; tmy = y = actor->y; tmthing = actor; tmbbox[BOXTOP] = y + actor->radius; tmbbox[BOXBOTTOM] = y - actor->radius; tmbbox[BOXRIGHT] = x + actor->radius; tmbbox[BOXLEFT] = x - actor->radius; // // the bounding box is extended by MAXRADIUS because actors are grouped // into mapblocks based on their origin point, and can overlap into adjacent // blocks by up to MAXRADIUS units // xl = (tmbbox[BOXLEFT] - bmaporgx - MAXRADIUS)>>MAPBLOCKSHIFT; xh = (tmbbox[BOXRIGHT] - bmaporgx + MAXRADIUS)>>MAPBLOCKSHIFT; yl = (tmbbox[BOXBOTTOM] - bmaporgy - MAXRADIUS)>>MAPBLOCKSHIFT; yh = (tmbbox[BOXTOP] - bmaporgy + MAXRADIUS)>>MAPBLOCKSHIFT; belowbt.Clear(); for (bx = xl; bx <= xh; bx++) for (by = yl; by <= yh; by++) if (!P_BlockThingsIterator (bx, by, PIT_FindBelowIntersectors, belowbt)) return; return; } //============================================================================= // // P_DoCrunch // //============================================================================= void P_DoCrunch (AActor *thing) { // crunch bodies to giblets if ((thing->flags & MF_CORPSE) && !(thing->flags3 & MF3_DONTGIB) && (thing->health <= 0)) { if (thing->CrushState && !(thing->flags & MF_ICECORPSE)) { // Clear MF_CORPSE so that this isn't done more than once thing->flags &= ~(MF_CORPSE|MF_SOLID); thing->height = thing->radius = 0; thing->SetState (thing->CrushState); return; } if (!(thing->flags & MF_NOBLOOD)) { AActor *gib = Spawn (thing->x, thing->y, thing->z, ALLOW_REPLACE); gib->RenderStyle = thing->RenderStyle; gib->alpha = thing->alpha; gib->height = 0; gib->radius = 0; S_Sound (thing, CHAN_BODY, "misc/fallingsplat", 1, ATTN_IDLE); PalEntry bloodcolor = (PalEntry)thing->GetClass()->Meta.GetMetaInt(AMETA_BloodColor); if (bloodcolor!=0) gib->Translation = TRANSLATION(TRANSLATION_Blood, bloodcolor.a); } if (thing->flags & MF_ICECORPSE) { thing->tics = 1; thing->momx = thing->momy = thing->momz = 0; } else if (thing->player) { thing->flags |= MF_NOCLIP; thing->flags3 |= MF3_DONTGIB; thing->renderflags |= RF_INVISIBLE; } else { thing->Destroy (); } return; // keep checking } // crunch dropped items if (thing->flags & MF_DROPPED) { thing->Destroy (); return; // keep checking } if (!(thing->flags & MF_SOLID) || (thing->flags & MF_NOCLIP)) { return; } if (!(thing->flags & MF_SHOOTABLE)) { return; // assume it is bloody gibs or something } nofit = true; if ((crushchange > 0) && !(level.maptime & 3)) { P_DamageMobj (thing, NULL, NULL, crushchange, MOD_CRUSH); // spray blood in a random direction if ((!(thing->flags&MF_NOBLOOD)) && (!(thing->flags2&(MF2_INVULNERABLE|MF2_DORMANT)))) { PalEntry bloodcolor = (PalEntry)thing->GetClass()->Meta.GetMetaInt(AMETA_BloodColor); P_TraceBleed (crushchange, thing); if (cl_bloodtype <= 1) { AActor *mo; mo = Spawn (thing->x, thing->y, thing->z + thing->height/2, ALLOW_REPLACE); mo->momx = pr_crunch.Random2 () << 12; mo->momy = pr_crunch.Random2 () << 12; if (bloodcolor!=0) mo->Translation = TRANSLATION(TRANSLATION_Blood, bloodcolor.a); } if (cl_bloodtype >= 1) { angle_t an; an = (M_Random () - 128) << 24; P_DrawSplash2 (32, thing->x, thing->y, thing->z + thing->height/2, an, 2, bloodcolor); } } } // keep checking (crush other things) return; } //============================================================================= // // P_PushUp // // Returns 0 if thing fits, 1 if ceiling got in the way, or 2 if something // above it didn't fit. //============================================================================= int P_PushUp (AActor *thing) { unsigned int firstintersect = intersectors.Size (); unsigned int lastintersect; int mymass = thing->Mass; if (thing->z + thing->height > thing->ceilingz) { return 1; } P_FindAboveIntersectors (thing); lastintersect = intersectors.Size (); for (; firstintersect < lastintersect; firstintersect++) { AActor *intersect = intersectors[firstintersect]; if (!(intersect->flags2 & MF2_PASSMOBJ) || (!(intersect->flags3 & MF3_ISMONSTER) && intersect->Mass > mymass)) { // Can't push things more massive than ourself return 2; } fixed_t oldz = intersect->z; P_AdjustFloorCeil (intersect); intersect->z = thing->z + thing->height + 1; if (P_PushUp (intersect)) { // Move blocked P_DoCrunch (intersect); intersect->z = oldz; return 2; } } return 0; } //============================================================================= // // P_PushDown // // Returns 0 if thing fits, 1 if floor got in the way, or 2 if something // below it didn't fit. //============================================================================= int P_PushDown (AActor *thing) { unsigned int firstintersect = intersectors.Size (); unsigned int lastintersect; int mymass = thing->Mass; if (thing->z <= thing->floorz) { return 1; } P_FindBelowIntersectors (thing); lastintersect = intersectors.Size (); for (; firstintersect < lastintersect; firstintersect++) { AActor *intersect = intersectors[firstintersect]; if (!(intersect->flags2 & MF2_PASSMOBJ) || (!(intersect->flags3 & MF3_ISMONSTER) && intersect->Mass > mymass)) { // Can't push things more massive than ourself return 2; } fixed_t oldz = intersect->z; P_AdjustFloorCeil (intersect); if (oldz > thing->z - intersect->height) { // Only push things down, not up. intersect->z = thing->z - intersect->height; if (P_PushDown (intersect)) { // Move blocked P_DoCrunch (intersect); intersect->z = oldz; return 2; } } } return 0; } //============================================================================= // // PIT_FloorDrop // //============================================================================= void PIT_FloorDrop (AActor *thing) { fixed_t oldfloorz = thing->floorz; P_AdjustFloorCeil (thing); if (thing->momz == 0 && (!(thing->flags & MF_NOGRAVITY) || (thing->z == oldfloorz && !(thing->flags & MF_NOLIFTDROP)))) { fixed_t oldz = thing->z; // If float bob, always stay the same approximate distance above // the floor; otherwise only move things standing on the floor, // and only do it if the drop is slow enough. if (thing->flags2 & MF2_FLOATBOB) { thing->z = thing->z - oldfloorz + thing->floorz; P_CheckFakeFloorTriggers (thing, oldz); } else if ((thing->flags & MF_NOGRAVITY) || ((!(level.flags & LEVEL_HEXENFORMAT) || moveamt < 9*FRACUNIT) && thing->z - thing->floorz <= moveamt)) { thing->z = thing->floorz; P_CheckFakeFloorTriggers (thing, oldz); } } } //============================================================================= // // PIT_FloorRaise // //============================================================================= void PIT_FloorRaise (AActor *thing) { fixed_t oldfloorz = thing->floorz; P_AdjustFloorCeil (thing); // Move things intersecting the floor up if (thing->z <= thing->floorz || (!(thing->flags & MF_NOGRAVITY) && (thing->flags2 & MF2_FLOATBOB))) { intersectors.Clear (); fixed_t oldz = thing->z; if (!(thing->flags2 & MF2_FLOATBOB)) { thing->z = thing->floorz; } else { thing->z = thing->z - oldfloorz + thing->floorz; } switch (P_PushUp (thing)) { default: P_CheckFakeFloorTriggers (thing, oldz); break; case 1: P_DoCrunch (thing); P_CheckFakeFloorTriggers (thing, oldz); break; case 2: P_DoCrunch (thing); thing->z = oldz; break; } } } //============================================================================= // // PIT_CeilingLower // //============================================================================= void PIT_CeilingLower (AActor *thing) { bool onfloor; onfloor = thing->z <= thing->floorz; P_AdjustFloorCeil (thing); if (thing->z + thing->height > thing->ceilingz) { intersectors.Clear (); fixed_t oldz = thing->z; if (thing->ceilingz - thing->height >= thing->floorz) { thing->z = thing->ceilingz - thing->height; } else { thing->z = thing->floorz; } switch (P_PushDown (thing)) { case 2: // intentional fall-through case 1: if (onfloor) thing->z = thing->floorz; P_DoCrunch (thing); P_CheckFakeFloorTriggers (thing, oldz); break; default: P_CheckFakeFloorTriggers (thing, oldz); break; } } } //============================================================================= // // PIT_CeilingRaise // //============================================================================= void PIT_CeilingRaise (AActor *thing) { BOOL isgood = P_AdjustFloorCeil (thing); // For DOOM compatibility, only move things that are inside the floor. // (or something else?) Things marked as hanging from the ceiling will // stay where they are. if (thing->z < thing->floorz && thing->z + thing->height >= thing->ceilingz - moveamt && !(thing->flags & MF_NOLIFTDROP)) { fixed_t oldz = thing->z; thing->z = thing->floorz; if (thing->z + thing->height > thing->ceilingz) { thing->z = thing->ceilingz - thing->height; } P_CheckFakeFloorTriggers (thing, oldz); } else if ((thing->flags & MF2_PASSMOBJ) && !isgood && thing->z + thing->height < thing->ceilingz) { if (!P_TestMobjZ (thing) && onmobj->z <= thing->z) { thing->z = MIN (thing->ceilingz - thing->height, onmobj->z + onmobj->height); } } } //============================================================================= // // P_ChangeSector [RH] Was P_CheckSector in BOOM // // jff 3/19/98 added to just check monsters on the periphery // of a moving sector instead of all in bounding box of the // sector. Both more accurate and faster. // //============================================================================= bool P_ChangeSector (sector_t *sector, int crunch, int amt, int floorOrCeil) { void (*iterator)(AActor *); msecnode_t *n; nofit = false; crushchange = crunch; moveamt = abs (amt); movesec = sector; // [RH] Use different functions for the four different types of sector // movement. Also update the soundorg's z-coordinate for 3D sound. if (floorOrCeil == 0) { // floor iterator = (amt < 0) ? PIT_FloorDrop : PIT_FloorRaise; sector->soundorg[2] = sector->floorplane.ZatPoint (sector->soundorg[0], sector->soundorg[1]); } else { // ceiling iterator = (amt < 0) ? PIT_CeilingLower : PIT_CeilingRaise; sector->soundorg[2] = sector->ceilingplane.ZatPoint (sector->soundorg[0], sector->soundorg[1]); } // killough 4/4/98: scan list front-to-back until empty or exhausted, // restarting from beginning after each thing is processed. Avoids // crashes, and is sure to examine all things in the sector, and only // the things which are in the sector, until a steady-state is reached. // Things can arbitrarily be inserted and removed and it won't mess up. // // killough 4/7/98: simplified to avoid using complicated counter // Mark all things invalid for (n = sector->touching_thinglist; n; n = n->m_snext) n->visited = false; do { for (n = sector->touching_thinglist; n; n = n->m_snext) // go through list { if (!n->visited) // unprocessed thing found { n->visited = true; // mark thing as processed if (!(n->m_thing->flags & MF_NOBLOCKMAP)) //jff 4/7/98 don't do these iterator (n->m_thing); // process it break; // exit and start over } } } while (n); // repeat from scratch until all things left are marked valid return nofit; } //============================================================================= // phares 3/21/98 // // Maintain a freelist of msecnode_t's to reduce memory allocs and frees. //============================================================================= msecnode_t *headsecnode = NULL; //============================================================================= // // P_GetSecnode // // Retrieve a node from the freelist. The calling routine // should make sure it sets all fields properly. // //============================================================================= msecnode_t *P_GetSecnode() { msecnode_t *node; if (headsecnode) { node = headsecnode; headsecnode = headsecnode->m_snext; } else { node = (msecnode_t *)M_Malloc (sizeof(*node)); } return node; } //============================================================================= // // P_PutSecnode // // Returns a node to the freelist. // //============================================================================= void P_PutSecnode (msecnode_t *node) { node->m_snext = headsecnode; headsecnode = node; } //============================================================================= // phares 3/16/98 // // P_AddSecnode // // Searches the current list to see if this sector is // already there. If not, it adds a sector node at the head of the list of // sectors this object appears in. This is called when creating a list of // nodes that will get linked in later. Returns a pointer to the new node. // //============================================================================= msecnode_t *P_AddSecnode (sector_t *s, AActor *thing, msecnode_t *nextnode) { msecnode_t *node; if (s == 0) { I_FatalError ("AddSecnode of 0 for %s\n", thing->_StaticType.TypeName.GetChars()); } node = nextnode; while (node) { if (node->m_sector == s) // Already have a node for this sector? { node->m_thing = thing; // Yes. Setting m_thing says 'keep it'. return nextnode; } node = node->m_tnext; } // Couldn't find an existing node for this sector. Add one at the head // of the list. node = P_GetSecnode(); // killough 4/4/98, 4/7/98: mark new nodes unvisited. node->visited = 0; node->m_sector = s; // sector node->m_thing = thing; // mobj node->m_tprev = NULL; // prev node on Thing thread node->m_tnext = nextnode; // next node on Thing thread if (nextnode) nextnode->m_tprev = node; // set back link on Thing // Add new node at head of sector thread starting at s->touching_thinglist node->m_sprev = NULL; // prev node on sector thread node->m_snext = s->touching_thinglist; // next node on sector thread if (s->touching_thinglist) node->m_snext->m_sprev = node; s->touching_thinglist = node; return node; } //============================================================================= // // P_DelSecnode // // Deletes a sector node from the list of // sectors this object appears in. Returns a pointer to the next node // on the linked list, or NULL. // //============================================================================= msecnode_t *P_DelSecnode (msecnode_t *node) { msecnode_t* tp; // prev node on thing thread msecnode_t* tn; // next node on thing thread msecnode_t* sp; // prev node on sector thread msecnode_t* sn; // next node on sector thread if (node) { // Unlink from the Thing thread. The Thing thread begins at // sector_list and not from AActor->touching_sectorlist. tp = node->m_tprev; tn = node->m_tnext; if (tp) tp->m_tnext = tn; if (tn) tn->m_tprev = tp; // Unlink from the sector thread. This thread begins at // sector_t->touching_thinglist. sp = node->m_sprev; sn = node->m_snext; if (sp) sp->m_snext = sn; else node->m_sector->touching_thinglist = sn; if (sn) sn->m_sprev = sp; // Return this node to the freelist P_PutSecnode(node); return tn; } return NULL; } // phares 3/13/98 //============================================================================= // // P_DelSector_List // // Deletes the sector_list and NULLs it. // //============================================================================= void P_DelSector_List () { if (sector_list != NULL) { P_DelSeclist (sector_list); sector_list = NULL; } } //============================================================================= // // P_DelSeclist // // Delete an entire sector list // //============================================================================= void P_DelSeclist (msecnode_t *node) { while (node) node = P_DelSecnode (node); } //============================================================================= // phares 3/14/98 // // PIT_GetSectors // // Locates all the sectors the object is in by looking at the lines that // cross through it. You have already decided that the object is allowed // at this location, so don't bother with checking impassable or // blocking lines. //============================================================================= BOOL PIT_GetSectors (line_t *ld) { if (tmbbox[BOXRIGHT] <= ld->bbox[BOXLEFT] || tmbbox[BOXLEFT] >= ld->bbox[BOXRIGHT] || tmbbox[BOXTOP] <= ld->bbox[BOXBOTTOM] || tmbbox[BOXBOTTOM] >= ld->bbox[BOXTOP]) return true; if (P_BoxOnLineSide (tmbbox, ld) != -1) return true; // This line crosses through the object. // Collect the sector(s) from the line and add to the // sector_list you're examining. If the Thing ends up being // allowed to move to this position, then the sector_list // will be attached to the Thing's AActor at touching_sectorlist. sector_list = P_AddSecnode (ld->frontsector,tmthing,sector_list); // Don't assume all lines are 2-sided, since some Things // like MT_TFOG are allowed regardless of whether their radius takes // them beyond an impassable linedef. // killough 3/27/98, 4/4/98: // Use sidedefs instead of 2s flag to determine two-sidedness. if (ld->backsector) sector_list = P_AddSecnode(ld->backsector, tmthing, sector_list); return true; } //============================================================================= // phares 3/14/98 // // P_CreateSecNodeList // // Alters/creates the sector_list that shows what sectors the object resides in // //============================================================================= void P_CreateSecNodeList (AActor *thing, fixed_t x, fixed_t y) { int xl, xh, yl, yh, bx, by; msecnode_t *node; // [RH] Save old tm* values. AActor *thingsave = tmthing; int flagssave = tmflags; fixed_t xsave = tmx; fixed_t ysave = tmy; fixed_t bboxsave[4]; memcpy (bboxsave, tmbbox, sizeof(bboxsave)); // First, clear out the existing m_thing fields. As each node is // added or verified as needed, m_thing will be set properly. When // finished, delete all nodes where m_thing is still NULL. These // represent the sectors the Thing has vacated. node = sector_list; while (node) { node->m_thing = NULL; node = node->m_tnext; } tmthing = thing; tmflags = thing->flags; tmx = x; tmy = y; tmbbox[BOXTOP] = y + tmthing->radius; tmbbox[BOXBOTTOM] = y - tmthing->radius; tmbbox[BOXRIGHT] = x + tmthing->radius; tmbbox[BOXLEFT] = x - tmthing->radius; validcount++; // used to make sure we only process a line once xl = (tmbbox[BOXLEFT] - bmaporgx)>>MAPBLOCKSHIFT; xh = (tmbbox[BOXRIGHT] - bmaporgx)>>MAPBLOCKSHIFT; yl = (tmbbox[BOXBOTTOM] - bmaporgy)>>MAPBLOCKSHIFT; yh = (tmbbox[BOXTOP] - bmaporgy)>>MAPBLOCKSHIFT; for (bx = xl; bx <= xh; bx++) for (by = yl; by <= yh; by++) P_BlockLinesIterator (bx,by,PIT_GetSectors); // Add the sector of the (x,y) point to sector_list. sector_list = P_AddSecnode (thing->Sector, thing, sector_list); // Now delete any nodes that won't be used. These are the ones where // m_thing is still NULL. node = sector_list; while (node) { if (node->m_thing == NULL) { if (node == sector_list) sector_list = node->m_tnext; node = P_DelSecnode (node); } else { node = node->m_tnext; } } // [RH] Restore old tm* values. tmthing = thingsave; tmflags = flagssave; tmx = xsave; tmy = ysave; memcpy (tmbbox, bboxsave, sizeof(bboxsave)); } void SpawnShootDecal (AActor *t1, const FTraceResults &trace) { FDecalBase *decalbase = NULL; if (t1->player != NULL && t1->player->ReadyWeapon != NULL) { decalbase = t1->player->ReadyWeapon->GetDefault()->DecalGenerator; } else { decalbase = t1->DecalGenerator; } if (decalbase != NULL) { DImpactDecal::StaticCreate (decalbase->GetDecal (), trace.X, trace.Y, trace.Z, sides + trace.Line->sidenum[trace.Side]); } } static void SpawnDeepSplash (AActor *t1, const FTraceResults &trace, AActor *puff, fixed_t vx, fixed_t vy, fixed_t vz) { fixed_t num, den, hitdist; const secplane_t *plane = &trace.CrossedWater->heightsec->floorplane; den = TMulScale16 (plane->a, vx, plane->b, vy, plane->c, vz); if (den != 0) { num = TMulScale16 (plane->a, t1->x, plane->b, t1->y, plane->c, shootz) + plane->d; hitdist = FixedDiv (-num, den); if (hitdist >= 0 && hitdist <= trace.Distance) { fixed_t savex, savey, savez; // Move the puff onto the water surface, splash, then move it back. if (puff == NULL) { puff = t1; } savex = puff->x; savey = puff->y; savez = puff->z; puff->SetOrigin (t1->x+FixedMul (vx, hitdist), t1->y+FixedMul (vy, hitdist), shootz+FixedMul (vz, hitdist)); P_HitWater (puff, puff->Sector); puff->SetOrigin (savex, savey, savez); } } }