1 /** @file p_map.cpp  Common map routines.
2  *
3  * @authors Copyright © 2003-2019 Jaakko Keränen <jaakko.keranen@iki.fi>
4  * @authors Copyright © 2005-2013 Daniel Swanson <danij@dengine.net>
5  * @authors Copyright © 1993-1996 by id Software, Inc.
6  *
7  * @par License
8  * GPL: http://www.gnu.org/licenses/gpl.html
9  *
10  * <small>This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by the
12  * Free Software Foundation; either version 2 of the License, or (at your
13  * option) any later version. This program is distributed in the hope that it
14  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
15  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
16  * Public License for more details. You should have received a copy of the GNU
17  * General Public License along with this program; if not, write to the Free
18  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
19  * 02110-1301 USA</small>
20  */
21 
22 #include "common.h"
23 #include "p_map.h"
24 
25 #include <cmath>
26 #include <cstdio>
27 #include <cstring>
28 #include "acs/system.h"
29 #include "d_net.h"
30 #include "d_netcl.h"
31 #include "d_netsv.h"
32 #include "g_common.h"
33 #include "gamesession.h"
34 #include "dmu_lib.h"
35 #include "p_mapspec.h"
36 #include "p_terraintype.h"
37 #include "p_tick.h"
38 #include "p_actor.h"
39 #include "player.h"
40 #include "p_mapsetup.h"
41 
42 /*
43  * Try move variables:
44  */
45 static AABoxd tmBox;
46 static mobj_t *tmThing;
47 dd_bool tmFloatOk; ///< @c true= move would be ok if within "tmFloorZ - tmCeilingZ".
48 coord_t tmFloorZ;
49 coord_t tmCeilingZ;
50 #if __JHEXEN__
51 static world_Material *tmFloorMaterial;
52 #endif
53 dd_bool tmFellDown; // $dropoff_fix
54 static coord_t tm[3];
55 static coord_t tmDropoffZ;
56 #if __JDOOM__ || __JDOOM64__ || __JHERETIC__
57 static Line *tmHitLine;
58 static int tmUnstuck; ///< $unstuck: used to check unsticking
59 #endif
60 Line *tmBlockingLine; // $unstuck: blocking line
61 #if __JHEXEN__
62 mobj_t *tmBlockingMobj;
63 #endif
64 // The following is used to keep track of the lines that clip the open
65 // height range e.g. PIT_CheckLine. They in turn are used with the &unstuck
66 // logic and to prevent missiles from exploding against sky hack walls.
67 Line *tmCeilingLine;
68 Line *tmFloorLine;
69 
70 /*
71  * Line aim/attack variables:
72  */
73 mobj_t *lineTarget; // Who got hit (or NULL).
74 static coord_t attackRange;
75 #if __JHEXEN__
76 mobj_t *PuffSpawned;
77 #endif
78 static coord_t shootZ; ///< Height if not aiming up or down.
79 static mobj_t *shooterThing;
80 static float aimSlope;
81 static float topSlope, bottomSlope; ///< Slopes to top and bottom of target.
82 
83 /// Sector >= Sector line-of-sight rejection.
84 static byte *rejectMatrix;
85 
P_GetGravity()86 coord_t P_GetGravity()
87 {
88     if(cfg.common.netGravity != -1)
89     {
90         return (coord_t) cfg.common.netGravity / 100;
91     }
92     return *((coord_t *) DD_GetVariable(DD_MAP_GRAVITY));
93 }
94 
95 /**
96  * Checks the reject matrix to find out if the two sectors are visible
97  * from each other.
98  */
checkReject(Sector * sec1,Sector * sec2)99 static dd_bool checkReject(Sector *sec1, Sector *sec2)
100 {
101     if(rejectMatrix)
102     {
103         // Determine BSP leaf entries in REJECT table.
104         int const pnum = P_ToIndex(sec1) * numsectors + P_ToIndex(sec2);
105         int const bytenum = pnum >> 3;
106         int const bitnum = 1 << (pnum & 7);
107 
108         // Check in REJECT table.
109         if(rejectMatrix[bytenum] & bitnum)
110         {
111             // Can't possibly be connected.
112             return false;
113         }
114     }
115 
116     return true;
117 }
118 
P_CheckSight(mobj_t const * beholder,mobj_t const * target)119 dd_bool P_CheckSight(mobj_t const *beholder, mobj_t const *target)
120 {
121     if(!beholder || !target) return false;
122 
123     // If either is unlinked, they can't see each other.
124     if(!Mobj_Sector(beholder)) return false;
125     if(!Mobj_Sector(target)) return false;
126 
127     // Cameramen are invisible.
128     if(P_MobjIsCamera(target)) return false;
129 
130     // Does a reject table exist and if so, should this line-of-sight fail?
131     if(!checkReject(Mobj_Sector(beholder), Mobj_Sector(target)))
132     {
133         return false;
134     }
135 
136     // The line-of-sight is from the "eyes" of the beholder.
137     vec3d_t from = { beholder->origin[VX], beholder->origin[VY], beholder->origin[VZ] };
138     if(!P_MobjIsCamera(beholder))
139     {
140         from[VZ] += beholder->height + -(beholder->height / 4);
141     }
142 
143     return P_CheckLineSight(from, target->origin, 0, target->height, 0);
144 }
145 
P_AimAtPoint2(coord_t const from[],coord_t const to[],dd_bool shadowed)146 angle_t P_AimAtPoint2(coord_t const from[], coord_t const to[], dd_bool shadowed)
147 {
148     angle_t angle = M_PointToAngle2(from, to);
149     if(shadowed)
150     {
151         // Accuracy is reduced when the target is partially shadowed.
152         angle += (P_Random() - P_Random()) << 21;
153     }
154     return angle;
155 }
156 
P_AimAtPoint(coord_t const from[],coord_t const to[])157 angle_t P_AimAtPoint(coord_t const from[], coord_t const to[])
158 {
159     return P_AimAtPoint2(from, to, false/* not shadowed*/);
160 }
161 
162 struct pit_stompthing_params_t
163 {
164     mobj_t *stompMobj; ///< Mobj doing the stomping.
165     vec2d_t location;  ///< Map space point being stomped.
166     bool alwaysStomp;  ///< Disable per-type/monster stomp exclussions.
167 };
168 
169 /// @return  Non-zero when the first unstompable mobj is found; otherwise @c 0.
PIT_StompThing(mobj_t * mo,void * context)170 static int PIT_StompThing(mobj_t *mo, void *context)
171 {
172     pit_stompthing_params_t &parm = *static_cast<pit_stompthing_params_t *>(context);
173 
174     // Don't ever attempt to stomp oneself.
175     if(mo == parm.stompMobj) return false;
176     // ...or non-shootables.
177     if(!(mo->flags & MF_SHOOTABLE)) return false;
178 
179     // Out of range?
180     coord_t const dist = mo->radius + parm.stompMobj->radius;
181     if(fabs(mo->origin[VX] - parm.location[VX]) >= dist ||
182        fabs(mo->origin[VY] - parm.location[VY]) >= dist)
183     {
184         return false;
185     }
186 
187     if(!parm.alwaysStomp)
188     {
189         // Is "this" mobj allowed to stomp?
190         if(!(parm.stompMobj->flags2 & MF2_TELESTOMP))
191             return true;
192 #if __JDOOM64__
193         // Monsters don't stomp.
194         if(!Mobj_IsPlayer(parm.stompMobj))
195             return true;
196 #elif __JDOOM__
197         // Monsters only stomp on a boss map.
198         if(!Mobj_IsPlayer(parm.stompMobj) && gfw_Session()->mapUri().path() != "MAP30")
199             return true;
200 #endif
201     }
202 
203     // Stomp!
204     P_DamageMobj(mo, parm.stompMobj, parm.stompMobj, 10000, true);
205 
206     return false; // Continue iteration.
207 }
208 
P_TeleportMove(mobj_t * mobj,coord_t x,coord_t y,dd_bool alwaysStomp)209 dd_bool P_TeleportMove(mobj_t *mobj, coord_t x, coord_t y, dd_bool alwaysStomp)
210 {
211     if(!mobj) return false;
212 
213     IterList_Clear(spechit); /// @todo necessary? -ds
214 
215     // Attempt to stomp any mobjs in the way.
216     pit_stompthing_params_t parm;
217     parm.stompMobj = mobj;
218     V2d_Set(parm.location, x, y);
219     parm.alwaysStomp = CPP_BOOL(alwaysStomp);
220 
221     coord_t const dist = mobj->radius + MAXRADIUS;
222     AABoxd const box(x - dist, y - dist, x + dist, y + dist);
223 
224     VALIDCOUNT++;
225     if(Mobj_BoxIterator(&box, PIT_StompThing, &parm))
226     {
227         return false;
228     }
229 
230     // The destination is clear.
231     P_MobjUnlink(mobj);
232     mobj->origin[VX] = parm.location[VX];
233     mobj->origin[VY] = parm.location[VY];
234     P_MobjLink(mobj);
235 
236     mobj->floorZ     = P_GetDoublep(Mobj_Sector(mobj), DMU_FLOOR_HEIGHT);
237     mobj->ceilingZ   = P_GetDoublep(Mobj_Sector(mobj), DMU_CEILING_HEIGHT);
238 #if !__JHEXEN__
239     mobj->dropOffZ   = mobj->floorZ;
240 #endif
241 
242     // Reset movement interpolation.
243     P_MobjClearSRVO(mobj);
244 
245     return true; // Success.
246 }
247 
P_Telefrag(mobj_t * thing)248 void P_Telefrag(mobj_t *thing)
249 {
250     DENG2_ASSERT(thing != 0);
251     P_TeleportMove(thing, thing->origin[VX], thing->origin[VY], false);
252 }
253 
P_TelefragMobjsTouchingPlayers()254 void P_TelefragMobjsTouchingPlayers()
255 {
256     for(uint i = 0; i < MAXPLAYERS; ++i)
257     {
258         player_t *plr = players + i;
259         ddplayer_t *ddplr = plr->plr;
260         if(!ddplr->inGame) continue;
261 
262         P_TeleportMove(ddplr->mo, ddplr->mo->origin[VX], ddplr->mo->origin[VY], true);
263     }
264 }
265 
266 struct pit_crossline_params_t
267 {
268     mobj_t *crossMobj;   ///< Mobj attempting to cross.
269     AABoxd crossAABox;   ///< Bounding box of the trajectory.
270     vec2d_t destination; ///< Would-be destination point.
271 };
272 
PIT_CrossLine(Line * line,void * context)273 static int PIT_CrossLine(Line *line, void *context)
274 {
275     pit_crossline_params_t &parm = *static_cast<pit_crossline_params_t *>(context);
276 
277     if((P_GetIntp(line, DMU_FLAGS) & DDLF_BLOCKING) ||
278        (P_ToXLine(line)->flags & ML_BLOCKMONSTERS) ||
279        (!P_GetPtrp(line, DMU_FRONT_SECTOR) || !P_GetPtrp(line, DMU_BACK_SECTOR)))
280     {
281         AABoxd *aaBox = (AABoxd *)P_GetPtrp(line, DMU_BOUNDING_BOX);
282 
283         if(!(parm.crossAABox.minX > aaBox->maxX ||
284              parm.crossAABox.maxX < aaBox->minX ||
285              parm.crossAABox.maxY < aaBox->minY ||
286              parm.crossAABox.minY > aaBox->maxY))
287         {
288             // Line blocks trajectory?
289             return    (Line_PointOnSide(line, parm.crossMobj->origin) < 0)
290                    != (Line_PointOnSide(line, parm.destination)       < 0);
291         }
292     }
293 
294     return false; // Continue iteration.
295 }
296 
P_CheckSides(mobj_t * mobj,coord_t x,coord_t y)297 dd_bool P_CheckSides(mobj_t *mobj, coord_t x, coord_t y)
298 {
299     /*
300      * Check to see if the trajectory crosses a blocking map line.
301      *
302      * Currently this assumes an infinite line, which is not quite correct.
303      * A more correct solution would be to check for an intersection of the
304      * trajectory and the line, but that takes longer and probably really isn't
305      * worth the effort.
306      */
307     pit_crossline_params_t parm;
308     parm.crossMobj  = mobj;
309     parm.crossAABox =
310         AABoxd(MIN_OF(mobj->origin[VX], x), MIN_OF(mobj->origin[VY], y),
311                MAX_OF(mobj->origin[VX], x), MAX_OF(mobj->origin[VY], y));
312     V2d_Set(parm.destination, x, y);
313 
314     VALIDCOUNT++;
315     return Line_BoxIterator(&parm.crossAABox, LIF_ALL, PIT_CrossLine, &parm);
316 }
317 
318 #if __JHEXEN__
checkForPushSpecial(Line * line,int side,mobj_t * mobj)319 static void checkForPushSpecial(Line *line, int side, mobj_t *mobj)
320 {
321     if(P_ToXLine(line)->special)
322     {
323         if(mobj->flags2 & MF2_PUSHWALL)
324         {
325             P_ActivateLine(line, mobj, side, SPAC_PUSH);
326         }
327         else if(mobj->flags2 & MF2_IMPACT)
328         {
329             P_ActivateLine(line, mobj, side, SPAC_IMPACT);
330         }
331     }
332 }
333 #endif // __JHEXEN__
334 
335 #if __JDOOM__ || __JDOOM64__ || __JHERETIC__
336 /**
337  * $unstuck: used to test intersection between thing and line assuming NO
338  * movement occurs -- used to avoid sticky situations.
339  */
untouched(Line * line,mobj_t * mobj)340 static int untouched(Line *line, mobj_t *mobj)
341 {
342     DENG_ASSERT(line != 0 && mobj != 0);
343 
344     coord_t const x      = mobj->origin[VX];
345     coord_t const y      = mobj->origin[VY];
346     coord_t const radius = mobj->radius;
347     AABoxd const *ldBox  = (AABoxd *)P_GetPtrp(line, DMU_BOUNDING_BOX);
348     AABoxd moBox;
349 
350     if(((moBox.minX = x - radius) >= ldBox->maxX) ||
351        ((moBox.minY = y - radius) >= ldBox->maxY) ||
352        ((moBox.maxX = x + radius) <= ldBox->minX) ||
353        ((moBox.maxY = y + radius) <= ldBox->minY) ||
354        Line_BoxOnSide(line, &moBox))
355     {
356         return true;
357     }
358 
359     return false;
360 }
361 #endif
362 
PIT_CheckThing(mobj_t * thing,void *)363 static int PIT_CheckThing(mobj_t *thing, void * /*context*/)
364 {
365     // Don't clip against oneself.
366     if(thing == tmThing)
367     {
368         return false;
369     }
370 
371 #if __JHEXEN__
372     // Don't clip on something we are stood on.
373     if(thing == tmThing->onMobj)
374     {
375         return false;
376     }
377 #endif
378 
379     if(!(thing->flags & (MF_SOLID | MF_SPECIAL | MF_SHOOTABLE)) ||
380        P_MobjIsCamera(thing) || P_MobjIsCamera(tmThing))
381     {
382         return false;
383     }
384 
385 #if !__JHEXEN__
386     // Player only.
387     dd_bool overlap = false;
388     if(tmThing->player && !FEQUAL(tm[VZ], DDMAXFLOAT) &&
389        (cfg.moveCheckZ || (tmThing->flags2 & MF2_PASSMOBJ)))
390     {
391         if((thing->origin[VZ] > tm[VZ] + tmThing->height) ||
392            (thing->origin[VZ] + thing->height < tm[VZ]))
393         {
394             return false; // Under or over it.
395         }
396 
397         overlap = true;
398     }
399 #endif
400 
401     coord_t blockdist = thing->radius + tmThing->radius;
402     if(fabs(thing->origin[VX] - tm[VX]) >= blockdist ||
403        fabs(thing->origin[VY] - tm[VY]) >= blockdist)
404     {
405         return false; // Didn't hit thing.
406     }
407 
408     if(IS_CLIENT)
409     {
410         // On clientside, missiles don't collide with mobjs.
411         if(tmThing->ddFlags & DDMF_MISSILE)
412         {
413             return false;
414         }
415 
416         // Players can't hit their own clmobjs.
417         if(tmThing->player && ClPlayer_ClMobj(tmThing->player - players) == thing)
418         {
419             return false;
420         }
421     }
422 
423 /*
424 #if __JHEXEN__
425     // Stop here if we are a client.
426     if(IS_CLIENT) return true;
427 #endif
428 */
429 
430 #if __JHEXEN__
431     tmBlockingMobj = thing;
432 #endif
433 
434 #if __JHEXEN__
435     if(tmThing->flags2 & MF2_PASSMOBJ)
436 #else
437     if(!tmThing->player && (tmThing->flags2 & MF2_PASSMOBJ))
438 #endif
439     {
440         // Check if a mobj passed over/under another object.
441 #if __JHERETIC__
442         if((tmThing->type == MT_IMP || tmThing->type == MT_WIZARD) &&
443            (thing->type == MT_IMP || thing->type == MT_WIZARD))
444         {
445             return true; // Don't let imps/wizards fly over other imps/wizards.
446         }
447 #elif __JHEXEN__
448         if(tmThing->type == MT_BISHOP && thing->type == MT_BISHOP)
449         {
450             return true; // Don't let bishops fly over other bishops.
451         }
452 #endif
453 
454         if(!(thing->flags & MF_SPECIAL))
455         {
456             if(tmThing->origin[VZ] > thing->origin[VZ] + thing->height ||
457                tmThing->origin[VZ] + tmThing->height < thing->origin[VZ])
458             {
459                 return false; // Over/under thing.
460             }
461         }
462     }
463 
464     // Check for skulls slamming into things.
465     if((tmThing->flags & MF_SKULLFLY) && (thing->flags & MF_SOLID))
466     {
467 #if __JHEXEN__
468         tmBlockingMobj = 0;
469 
470         if(tmThing->type == MT_MINOTAUR)
471         {
472             // Slamming minotaurs shouldn't move non-creatures.
473             if(!(thing->flags & MF_COUNTKILL))
474             {
475                 return true;
476             }
477         }
478         else if(tmThing->type == MT_HOLY_FX)
479         {
480             if((thing->flags & MF_SHOOTABLE) && thing != tmThing->target)
481             {
482                 if(IS_NETGAME && !gfw_Rule(deathmatch) && thing->player)
483                 {
484                     return false; // don't attack other co-op players
485                 }
486 
487                 if((thing->flags2 & MF2_REFLECTIVE) &&
488                    (thing->player || (thing->flags2 & MF2_BOSS)))
489                 {
490                     tmThing->tracer = tmThing->target;
491                     tmThing->target = thing;
492                     return false;
493                 }
494 
495                 if(thing->flags & MF_COUNTKILL || thing->player)
496                 {
497                     tmThing->tracer = thing;
498                 }
499 
500                 if(P_Random() < 96)
501                 {
502                     int damage = 12;
503                     if(thing->player || (thing->flags2 & MF2_BOSS))
504                     {
505                         damage = 3;
506                         // Ghost burns out faster when attacking players/bosses.
507                         tmThing->health -= 6;
508                     }
509 
510                     P_DamageMobj(thing, tmThing, tmThing->target, damage, false);
511                     if(P_Random() < 128)
512                     {
513                         P_SpawnMobj(MT_HOLY_PUFF, tmThing->origin, P_Random() << 24, 0);
514                         S_StartSound(SFX_SPIRIT_ATTACK, tmThing);
515 
516                         if((thing->flags & MF_COUNTKILL) && P_Random() < 128 &&
517                            !S_IsPlaying(SFX_PUPPYBEAT, thing))
518                         {
519                             if((thing->type == MT_CENTAUR) ||
520                                (thing->type == MT_CENTAURLEADER) ||
521                                (thing->type == MT_ETTIN))
522                             {
523                                 S_StartSound(SFX_PUPPYBEAT, thing);
524                             }
525                         }
526                     }
527                 }
528 
529                 if(thing->health <= 0)
530                 {
531                     tmThing->tracer = 0;
532                 }
533             }
534 
535             return false;
536         }
537 #endif
538 
539         int damage = tmThing->damage;
540 #if __JDOOM__
541         /// @attention Kludge:
542         /// Older save versions did not serialize the damage property,
543         /// so here we take the damage from the current Thing definition.
544         /// @fixme Do this during map state deserialization.
545         if(damage == DDMAXINT)
546         {
547             damage = tmThing->info->damage;
548         }
549 #endif
550 
551         damage *= (P_Random() % 8) + 1;
552         P_DamageMobj(thing, tmThing, tmThing, damage, false);
553 
554         tmThing->flags &= ~MF_SKULLFLY;
555         tmThing->mom[MX] = tmThing->mom[MY] = tmThing->mom[MZ] = 0;
556 
557 #if __JHERETIC__ || __JHEXEN__
558         P_MobjChangeState(tmThing, P_GetState(mobjtype_t(tmThing->type), SN_SEE));
559 #else
560         P_MobjChangeState(tmThing, P_GetState(mobjtype_t(tmThing->type), SN_SPAWN));
561 #endif
562 
563         return true; // Stop moving.
564     }
565 
566 #if __JHEXEN__
567     // Check for blasted thing running into another
568     if((tmThing->flags2 & MF2_BLASTED) && (thing->flags & MF_SHOOTABLE))
569     {
570         if(!(thing->flags2 & MF2_BOSS) && (thing->flags & MF_COUNTKILL))
571         {
572             thing->mom[MX] += tmThing->mom[MX];
573             thing->mom[MY] += tmThing->mom[MY];
574 
575             NetSv_PlayerMobjImpulse(thing, tmThing->mom[MX], tmThing->mom[VY], 0);
576 
577             if((thing->mom[MX] + thing->mom[MY]) > 3)
578             {
579                 P_DamageMobj(thing, tmThing, tmThing,
580                              (tmThing->info->mass / 100) + 1, false);
581 
582                 P_DamageMobj(tmThing, thing, thing,
583                              ((thing->info->mass / 100) + 1) >> 2, false);
584             }
585 
586             return true;
587         }
588     }
589 #endif
590 
591     // Missiles can hit other things.
592     if(tmThing->flags & MF_MISSILE)
593     {
594 #if __JHEXEN__
595         // Check for a non-shootable mobj.
596         if(thing->flags2 & MF2_NONSHOOTABLE)
597         {
598             return false;
599         }
600 #else
601         // Check for passing through a ghost.
602         if((thing->flags & MF_SHADOW) && (tmThing->flags2 & MF2_THRUGHOST))
603         {
604             return false;
605         }
606 #endif
607 
608         // See if it went over / under.
609         if(tmThing->origin[VZ] > thing->origin[VZ] + thing->height ||
610            tmThing->origin[VZ] + tmThing->height < thing->origin[VZ])
611         {
612             return false;
613         }
614 
615 #if __JHEXEN__
616         if(tmThing->flags2 & MF2_FLOORBOUNCE)
617         {
618             return !(tmThing->target == thing || !(thing->flags & MF_SOLID));
619         }
620 
621         if(tmThing->type == MT_LIGHTNING_FLOOR || tmThing->type == MT_LIGHTNING_CEILING)
622         {
623             if((thing->flags & MF_SHOOTABLE) && thing != tmThing->target)
624             {
625                 if(thing->info->mass != DDMAXINT)
626                 {
627                     thing->mom[MX] += tmThing->mom[MX] / 16;
628                     thing->mom[MY] += tmThing->mom[MY] / 16;
629 
630                     NetSv_PlayerMobjImpulse(thing, tmThing->mom[MX] / 16, tmThing->mom[MY] / 16, 0);
631                 }
632 
633                 if((!thing->player && !(thing->flags2 & MF2_BOSS)) ||
634                    !(mapTime & 1))
635                 {
636                     // Lightning does more damage to centaurs.
637                     if(thing->type == MT_CENTAUR || thing->type == MT_CENTAURLEADER)
638                     {
639                         P_DamageMobj(thing, tmThing, tmThing->target, 9, false);
640                     }
641                     else
642                     {
643                         P_DamageMobj(thing, tmThing, tmThing->target, 3, false);
644                     }
645 
646                     if(!S_IsPlaying(SFX_MAGE_LIGHTNING_ZAP, tmThing))
647                     {
648                         S_StartSound(SFX_MAGE_LIGHTNING_ZAP, tmThing);
649                     }
650 
651                     if((thing->flags & MF_COUNTKILL) && P_Random() < 64 &&
652                        !S_IsPlaying(SFX_PUPPYBEAT, thing))
653                     {
654                         if((thing->type == MT_CENTAUR) ||
655                            (thing->type == MT_CENTAURLEADER) ||
656                            (thing->type == MT_ETTIN))
657                         {
658                             S_StartSound(SFX_PUPPYBEAT, thing);
659                         }
660                     }
661                 }
662 
663                 tmThing->health--;
664                 if(tmThing->health <= 0 || thing->health <= 0)
665                 {
666                     return true;
667                 }
668 
669                 if(tmThing->type == MT_LIGHTNING_FLOOR)
670                 {
671                     if(tmThing->lastEnemy && !tmThing->lastEnemy->tracer)
672                     {
673                         tmThing->lastEnemy->tracer = thing;
674                     }
675                 }
676                 else if(!tmThing->tracer)
677                 {
678                     tmThing->tracer = thing;
679                 }
680             }
681 
682             return false; // Lightning zaps through all sprites.
683         }
684 
685         if(tmThing->type == MT_LIGHTNING_ZAP)
686         {
687             if((thing->flags & MF_SHOOTABLE) && thing != tmThing->target &&
688                tmThing->lastEnemy)
689             {
690                 mobj_t *lmo = tmThing->lastEnemy;
691 
692                 if(lmo->type == MT_LIGHTNING_FLOOR)
693                 {
694                     if(lmo->lastEnemy && !lmo->lastEnemy->tracer)
695                     {
696                         lmo->lastEnemy->tracer = thing;
697                     }
698                 }
699                 else if(!lmo->tracer)
700                 {
701                     lmo->tracer = thing;
702                 }
703 
704                 if(!(mapTime & 3))
705                 {
706                     lmo->health--;
707                 }
708             }
709         }
710         else if(tmThing->type == MT_MSTAFF_FX2 && thing != tmThing->target)
711         {
712             if(!thing->player && !(thing->flags2 & MF2_BOSS))
713             {
714                 switch(thing->type)
715                 {
716                 case MT_FIGHTER_BOSS:   // these not flagged boss
717                 case MT_CLERIC_BOSS:    // so they can be blasted
718                 case MT_MAGE_BOSS:
719                     break;
720 
721                 default:
722                     P_DamageMobj(thing, tmThing, tmThing->target, 10, false);
723                     return false;
724                 }
725             }
726         }
727 #endif
728 
729         // Don't hit same species as originator.
730 #if __JDOOM__ || __JDOOM64__
731         if(tmThing->target &&
732            (tmThing->target->type == thing->type ||
733            (tmThing->target->type == MT_KNIGHT && thing->type == MT_BRUISER) ||
734            (tmThing->target->type == MT_BRUISER && thing->type == MT_KNIGHT)))
735 #else
736         if(tmThing->target && tmThing->target->type == thing->type)
737 #endif
738         {
739             if(thing == tmThing->target)
740             {
741                 return false;
742             }
743 
744 #if __JHEXEN__
745             if(!thing->player)
746             {
747                 return true; // Hit same species as originator, explode, no damage
748             }
749 #else
750             if(!monsterInfight && thing->type != MT_PLAYER)
751             {
752                 // Explode, but do no damage.
753                 // Let players missile other players.
754                 return true;
755             }
756 #endif
757         }
758 
759         if(!(thing->flags & MF_SHOOTABLE))
760         {
761             return !!(thing->flags & MF_SOLID); // Didn't do any damage.
762         }
763 
764         if(tmThing->flags2 & MF2_RIP)
765         {
766 #if __JHEXEN__
767             if(!(thing->flags & MF_NOBLOOD) &&
768                !(thing->flags2 & MF2_REFLECTIVE) &&
769                !(thing->flags2 & MF2_INVULNERABLE))
770 #else
771             if(!(thing->flags & MF_NOBLOOD))
772 #endif
773             {   // Ok to spawn some blood.
774                 P_RipperBlood(tmThing);
775             }
776 
777 #if __JHERETIC__
778             S_StartSound(SFX_RIPSLOP, tmThing);
779 #endif
780 
781             int damage = tmThing->damage;
782 #if __JDOOM__
783             /// @attention Kludge:
784             /// Older save versions did not serialize the damage property,
785             /// so here we take the damage from the current Thing definition.
786             /// @fixme Do this during map state deserialization.
787             if(damage == DDMAXINT)
788             {
789                 damage = tmThing->info->damage;
790             }
791 #endif
792 
793             damage *= (P_Random() & 3) + 2;
794             P_DamageMobj(thing, tmThing, tmThing->target, damage, false);
795 
796             if((thing->flags2 & MF2_PUSHABLE) && !(tmThing->flags2 & MF2_CANNOTPUSH))
797             {
798                 // Push thing
799                 thing->mom[MX] += tmThing->mom[MX] / 4;
800                 thing->mom[MY] += tmThing->mom[MY] / 4;
801                 NetSv_PlayerMobjImpulse(thing, tmThing->mom[MX]/4, tmThing->mom[MY]/4, 0);
802             }
803 
804             IterList_Clear(spechit);
805             return false;
806         }
807 
808         // Do damage
809         int damage = tmThing->damage;
810 #if __JDOOM__
811         /// @attention Kludge:
812         /// Older save versions did not serialize the damage property,
813         /// so here we take the damage from the current Thing definition.
814         /// @fixme Do this during map state deserialization.
815         if(tmThing->damage == DDMAXINT)
816         {
817             damage = tmThing->info->damage;
818         }
819 #endif
820 
821         damage *= (P_Random() % 8) + 1;
822 #if __JDOOM__ || __JDOOM64__
823         P_DamageMobj(thing, tmThing, tmThing->target, damage, false);
824 #else
825         if(damage)
826         {
827 # if __JHERETIC__
828             if(!(thing->flags & MF_NOBLOOD) && P_Random() < 192)
829 # else //__JHEXEN__
830             if(!(thing->flags & MF_NOBLOOD) &&
831                !(thing->flags2 & MF2_REFLECTIVE) &&
832                !(thing->flags2 & MF2_INVULNERABLE) &&
833                !(tmThing->type == MT_TELOTHER_FX1) &&
834                !(tmThing->type == MT_TELOTHER_FX2) &&
835                !(tmThing->type == MT_TELOTHER_FX3) &&
836                !(tmThing->type == MT_TELOTHER_FX4) &&
837                !(tmThing->type == MT_TELOTHER_FX5) && (P_Random() < 192))
838 # endif
839             {
840                 P_SpawnBloodSplatter(tmThing->origin[VX], tmThing->origin[VY], tmThing->origin[VZ], thing);
841             }
842 
843             P_DamageMobj(thing, tmThing, tmThing->target, damage, false);
844         }
845 #endif
846 
847         // Don't traverse anymore.
848         return true;
849     }
850 
851     if((thing->flags2 & MF2_PUSHABLE) && !(tmThing->flags2 & MF2_CANNOTPUSH))
852     {
853         // Push thing.
854         coord_t pushImpulse[2] = {tmThing->mom[MX] / 4, tmThing->mom[MY] / 4};
855 
856         for (int axis = 0; axis < 2; ++axis)
857         {
858             // Do not exceed the momentum of the thing doing the pushing.
859             if (cfg.common.pushableMomentumLimitedToPusher)
860             {
861                 coord_t maxIncrement = tmThing->mom[axis] - thing->mom[axis];
862                 if (thing->mom[axis] > 0 && pushImpulse[axis] > 0)
863                 {
864                     pushImpulse[axis] = de::max(0.0, de::min(pushImpulse[axis], maxIncrement));
865                 }
866                 else if (thing->mom[axis] < 0 && pushImpulse[axis] < 0)
867                 {
868                     pushImpulse[axis] = de::min(0.0, de::max(pushImpulse[axis], maxIncrement));
869                 }
870             }
871 
872             thing->mom[axis] += pushImpulse[axis];
873         }
874 
875         if (!de::fequal(pushImpulse[MX], 0) || !de::fequal(pushImpulse[MY], 0))
876         {
877             NetSv_PlayerMobjImpulse(thing, float(pushImpulse[MX]), float(pushImpulse[MY]), 0);
878         }
879     }
880 
881     // @fixme Kludge: Always treat blood as a solid.
882     dd_bool solid;
883     if(tmThing->type == MT_BLOOD)
884     {
885         solid = true;
886     }
887     else
888     {
889         solid = (thing->flags & MF_SOLID) && !(thing->flags & MF_NOCLIP) &&
890                 (tmThing->flags & MF_SOLID);
891     }
892     // Kludge end.
893 
894 #if __JHEXEN__
895     if(tmThing->player && tmThing->onMobj && solid)
896     {
897         /// @todo Unify Hexen's onMobj logic with the other games.
898 
899         // We may be standing on more than one thing.
900         if(tmThing->origin[VZ] > thing->origin[VZ] + thing->height - 24)
901         {
902             // Stepping up on this is possible.
903             tmFloorZ = MAX_OF(tmFloorZ, thing->origin[VZ] + thing->height);
904             solid = false;
905         }
906     }
907 #endif
908 
909     // Check for special pickup.
910     if((thing->flags & MF_SPECIAL) && (tmThing->flags & MF_PICKUP))
911     {
912         P_TouchSpecialMobj(thing, tmThing); // Can remove thing.
913     }
914 #if !__JHEXEN__
915     else if(overlap && solid)
916     {
917         // How are we positioned, allow step up?
918         if(!(thing->flags & MF_CORPSE) && tm[VZ] > thing->origin[VZ] + thing->height - 24)
919         {
920             tmThing->onMobj = thing;
921             if(thing->origin[VZ] + thing->height > tmFloorZ)
922             {
923                 tmFloorZ = thing->origin[VZ] + thing->height;
924             }
925             return false;
926         }
927     }
928     else if(!tmThing->player && solid)
929     {
930         // A non-player object is contacting a solid object.
931         if(cfg.allowMonsterFloatOverBlocking && (tmThing->flags & MF_FLOAT) && !thing->player)
932         {
933             coord_t top = thing->origin[VZ] + thing->height;
934             tmThing->onMobj = thing;
935             tmFloorZ = MAX_OF(tmFloorZ, top);
936             return false;
937         }
938     }
939 #endif
940 
941     return solid;
942 }
943 
944 /**
945  * Adjusts tmFloorZ and tmCeilingZ as lines are contacted.
946  */
PIT_CheckLine(Line * ld,void *)947 static int PIT_CheckLine(Line *ld, void * /*context*/)
948 {
949     AABoxd const *aaBox = (AABoxd *)P_GetPtrp(ld, DMU_BOUNDING_BOX);
950     if(tmBox.minX >= aaBox->maxX || tmBox.minY >= aaBox->maxY ||
951        tmBox.maxX <= aaBox->minX || tmBox.maxY <= aaBox->minY)
952     {
953         return false;
954     }
955 
956     /*
957      * Real player mobjs are allowed to use high-precision, non-vanilla
958      * collision testing -- the rest of the playsim uses coord_t, and we don't
959      * want conflicting results (e.g., getting stuck in tight spaces).
960      */
961     if(Mobj_IsPlayer(tmThing) && !Mobj_IsVoodooDoll(tmThing))
962     {
963         if(Line_BoxOnSide(ld, &tmBox)) // double precision floats
964         {
965             return false;
966         }
967     }
968     else
969     {
970         // Fixed-precision math gives better compatibility with vanilla DOOM.
971         if(Line_BoxOnSide_FixedPrecision(ld, &tmBox))
972         {
973             return false;
974         }
975     }
976 
977     // A line has been hit.
978     xline_t *xline = P_ToXLine(ld);
979 
980 #if !__JHEXEN__
981     tmThing->wallHit = true;
982 
983     // A Hit event will be sent to special lines.
984     if(xline->special)
985     {
986         tmHitLine = ld;
987     }
988 #endif
989 
990     if(!P_GetPtrp(ld, DMU_BACK_SECTOR)) // One sided line.
991     {
992 #if __JHEXEN__
993         if(tmThing->flags2 & MF2_BLASTED)
994         {
995             P_DamageMobj(tmThing, NULL, NULL, tmThing->info->mass >> 5, false);
996         }
997 
998         checkForPushSpecial(ld, 0, tmThing);
999         return true;
1000 #else
1001         coord_t d1[2];
1002 
1003         P_GetDoublepv(ld, DMU_DXY, d1);
1004 
1005         /**
1006          * $unstuck: allow player to move out of 1s wall, to prevent
1007          * sticking. The moving thing's destination position will cross the
1008          * given line.
1009          * If this should not be allowed, return false.
1010          * If the line is special, keep track of it to process later if the
1011          * move is proven ok.
1012          *
1013          * @note Specials are NOT sorted by order, so two special lines that
1014          *       are only 8 units apart could be crossed in either order.
1015          */
1016 
1017         tmBlockingLine = ld;
1018         return !(tmUnstuck && !untouched(ld, tmThing) &&
1019             ((tm[VX] - tmThing->origin[VX]) * d1[1]) >
1020             ((tm[VY] - tmThing->origin[VY]) * d1[0]));
1021 #endif
1022     }
1023 
1024     /// @todo Will never pass this test due to above. Is the previous check
1025     ///       supposed to qualify player mobjs only?
1026 #if __JHERETIC__
1027     if(!P_GetPtrp(ld, DMU_BACK_SECTOR)) // one sided line
1028     {
1029         // Missiles can trigger impact specials
1030         if((tmThing->flags & MF_MISSILE) && xline->special)
1031         {
1032             IterList_PushBack(spechit, ld);
1033         }
1034         return true;
1035     }
1036 #endif
1037 
1038     if(!(tmThing->flags & MF_MISSILE))
1039     {
1040         // Explicitly blocking everything?
1041         if(P_GetIntp(ld, DMU_FLAGS) & DDLF_BLOCKING)
1042         {
1043 #if __JHEXEN__
1044             if(tmThing->flags2 & MF2_BLASTED)
1045             {
1046                 P_DamageMobj(tmThing, NULL, NULL, tmThing->info->mass >> 5, false);
1047             }
1048 
1049             checkForPushSpecial(ld, 0, tmThing);
1050             return true;
1051 #else
1052             // $unstuck: allow escape.
1053             return !(tmUnstuck && !untouched(ld, tmThing));
1054 #endif
1055         }
1056 
1057         // Block monsters only?
1058 #if __JHEXEN__
1059         if(!tmThing->player && tmThing->type != MT_CAMERA &&
1060            (xline->flags & ML_BLOCKMONSTERS))
1061 #elif __JHERETIC__
1062         if(!tmThing->player && tmThing->type != MT_POD &&
1063            (xline->flags & ML_BLOCKMONSTERS))
1064 #else
1065         if(!tmThing->player &&
1066            (xline->flags & ML_BLOCKMONSTERS))
1067 #endif
1068         {
1069 #if __JHEXEN__
1070             if(tmThing->flags2 & MF2_BLASTED)
1071             {
1072                 P_DamageMobj(tmThing, NULL, NULL, tmThing->info->mass >> 5, false);
1073             }
1074 #endif
1075             return true;
1076         }
1077     }
1078 
1079 #if __JDOOM64__
1080     if((tmThing->flags & MF_MISSILE) && (xline->flags & ML_BLOCKALL))
1081     {
1082         // $unstuck: allow escape.
1083         return !(tmUnstuck && !untouched(ld, tmThing));
1084     }
1085 #endif
1086 
1087     LineOpening opening; Line_Opening(ld, &opening);
1088 
1089     // Adjust floor / ceiling heights.
1090     if(opening.top < tmCeilingZ)
1091     {
1092         tmCeilingZ    = opening.top;
1093         tmCeilingLine = ld;
1094 #if !__JHEXEN__
1095         tmBlockingLine     = ld;
1096 #endif
1097     }
1098     if(opening.bottom > tmFloorZ)
1099     {
1100         tmFloorZ    = opening.bottom;
1101         tmFloorLine = ld;
1102 #if !__JHEXEN__
1103         tmBlockingLine   = ld;
1104 #endif
1105     }
1106     if(opening.lowFloor < tmDropoffZ)
1107     {
1108         tmDropoffZ  = opening.lowFloor;
1109     }
1110 
1111     // If contacted a special line, add it to the list.
1112     if(P_ToXLine(ld)->special)
1113     {
1114         IterList_PushBack(spechit, ld);
1115     }
1116 
1117 #if !__JHEXEN__
1118     tmThing->wallHit = false;
1119 #endif
1120 
1121     return false; // Continue iteration.
1122 }
1123 
P_CheckPositionXYZ(mobj_t * thing,coord_t x,coord_t y,coord_t z)1124 dd_bool P_CheckPositionXYZ(mobj_t *thing, coord_t x, coord_t y, coord_t z)
1125 {
1126 #if defined(__JHERETIC__)
1127     if (thing->type != MT_POD) // vanilla onMobj behavior for pods
1128     {
1129         thing->onMobj = nullptr;
1130     }
1131 #elif !__JHEXEN__
1132     thing->onMobj  = 0;
1133 #endif
1134     thing->wallHit = false;
1135 
1136     tmThing         = thing;
1137     V3d_Set(tm, x, y, z);
1138     tmBox           = AABoxd(tm[VX] - tmThing->radius, tm[VY] - tmThing->radius,
1139                              tm[VX] + tmThing->radius, tm[VY] + tmThing->radius);
1140 #if !__JHEXEN__
1141     tmHitLine       = 0;
1142 #endif
1143 
1144     // The base floor/ceiling is from the BSP leaf that contains the point.
1145     // Any contacted lines the step closer together will adjust them.
1146     Sector *newSector = Sector_AtPoint_FixedPrecision(tm);
1147 
1148     tmCeilingLine   = tmFloorLine = 0;
1149     tmFloorZ        = tmDropoffZ = P_GetDoublep(newSector, DMU_FLOOR_HEIGHT);
1150     tmCeilingZ      = P_GetDoublep(newSector, DMU_CEILING_HEIGHT);
1151 #if __JHEXEN__
1152     tmFloorMaterial = (world_Material *)P_GetPtrp(newSector, DMU_FLOOR_MATERIAL);
1153 #else
1154     tmBlockingLine  = 0;
1155     tmUnstuck       = Mobj_IsPlayer(thing) && !Mobj_IsVoodooDoll(thing);
1156 #endif
1157 
1158     IterList_Clear(spechit);
1159 
1160     if(tmThing->flags & MF_NOCLIP)
1161     {
1162 #if __JHEXEN__
1163         if(!(tmThing->flags & MF_SKULLFLY))
1164         {
1165             return true;
1166         }
1167 #else
1168         return true;
1169 #endif
1170     }
1171 
1172     VALIDCOUNT++;
1173 
1174     // Check things first, possibly picking things up;
1175 #if __JHEXEN__
1176     tmBlockingMobj = 0;
1177 #endif
1178 
1179     // The camera goes through all objects.
1180     if(!P_MobjIsCamera(thing))
1181     {
1182         /*
1183          * The bounding box is extended by MAXRADIUS because mobj_ts are grouped
1184          * into mapblocks based on their origin point and can overlap adjacent
1185          * blocks by up to MAXRADIUS units.
1186          */
1187         AABoxd tmBoxExpanded(tmBox.minX - MAXRADIUS, tmBox.minY - MAXRADIUS,
1188                              tmBox.maxX + MAXRADIUS, tmBox.maxY + MAXRADIUS);
1189 
1190         if(Mobj_BoxIterator(&tmBoxExpanded, PIT_CheckThing, 0))
1191         {
1192             return false;
1193         }
1194 
1195         if(thing->onMobj)
1196         {
1197             App_Log(DE2_DEV_MAP_XVERBOSE,
1198                     "thing->onMobj = %p/%i (solid:%i) [thing:%p/%i]", thing->onMobj,
1199                     thing->onMobj->thinker.id,
1200                     (thing->onMobj->flags & MF_SOLID) != 0,
1201                     thing, thing->thinker.id);
1202         }
1203     }
1204 
1205 #if __JHEXEN__
1206     if(tmThing->flags & MF_NOCLIP)
1207     {
1208         return true;
1209     }
1210 #endif
1211 
1212     // Check lines.
1213 #if __JHEXEN__
1214     tmBlockingMobj = 0;
1215 #endif
1216 
1217     return !Line_BoxIterator(&tmBox, LIF_ALL, PIT_CheckLine, 0);
1218 }
1219 
P_CheckPosition(mobj_t * thing,coord_t const pos[3])1220 dd_bool P_CheckPosition(mobj_t *thing, coord_t const pos[3])
1221 {
1222     return P_CheckPositionXYZ(thing, pos[VX], pos[VY], pos[VZ]);
1223 }
1224 
P_CheckPositionXY(mobj_t * thing,coord_t x,coord_t y)1225 dd_bool P_CheckPositionXY(mobj_t *thing, coord_t x, coord_t y)
1226 {
1227     return P_CheckPositionXYZ(thing, x, y, DDMAXFLOAT);
1228 }
1229 
Mobj_IsRemotePlayer(mobj_t * mo)1230 dd_bool Mobj_IsRemotePlayer(mobj_t *mo)
1231 {
1232     return (mo && ((IS_DEDICATED && mo->dPlayer) ||
1233                    (IS_CLIENT && mo->player && mo->player - players != CONSOLEPLAYER)));
1234 }
1235 
1236 #if __JDOOM64__ || __JHERETIC__
checkMissileImpact(mobj_t & mobj)1237 static void checkMissileImpact(mobj_t &mobj)
1238 {
1239     if(IS_CLIENT) return;
1240 
1241     if(!(mobj.flags & MF_MISSILE)) return;
1242     if(!mobj.target || !mobj.target->player) return;
1243 
1244     if(IterList_Empty(spechit)) return;
1245 
1246     IterList_SetIteratorDirection(spechit, ITERLIST_BACKWARD);
1247     IterList_RewindIterator(spechit);
1248 
1249     Line *line;
1250     while((line = (Line *)IterList_MoveIterator(spechit)) != 0)
1251     {
1252         P_ActivateLine(line, mobj.target, 0, SPAC_IMPACT);
1253     }
1254 }
1255 #endif
1256 
1257 /**
1258  * Attempt to move to a new position, crossing special lines unless
1259  * MF_TELEPORT is set. $dropoff_fix
1260  */
1261 #if __JHEXEN__
P_TryMove2(mobj_t * thing,coord_t x,coord_t y)1262 static dd_bool P_TryMove2(mobj_t *thing, coord_t x, coord_t y)
1263 #else
1264 static dd_bool P_TryMove2(mobj_t *thing, coord_t x, coord_t y, dd_bool dropoff)
1265 #endif
1266 {
1267     dd_bool const isRemotePlayer = Mobj_IsRemotePlayer(thing);
1268 
1269     // $dropoff_fix: tmFellDown.
1270     tmFloatOk  = false;
1271 #if !__JHEXEN__
1272     tmFellDown = false;
1273 #endif
1274 
1275 #if __JHEXEN__
1276     if(!P_CheckPositionXY(thing, x, y))
1277 #else
1278     if(!P_CheckPositionXYZ(thing, x, y, thing->origin[VZ]))
1279 #endif
1280     {
1281 #if __JHEXEN__
1282         if(!tmBlockingMobj || tmBlockingMobj->player || !thing->player)
1283         {
1284             goto pushline;
1285         }
1286         else if(tmBlockingMobj->origin[VZ] + tmBlockingMobj->height - thing->origin[VZ] > 24 ||
1287                 (P_GetDoublep(Mobj_Sector(tmBlockingMobj), DMU_CEILING_HEIGHT) -
1288                  (tmBlockingMobj->origin[VZ] + tmBlockingMobj->height) < thing->height) ||
1289                 (tmCeilingZ - (tmBlockingMobj->origin[VZ] + tmBlockingMobj->height) <
1290                  thing->height))
1291         {
1292             goto pushline;
1293         }
1294 #else
1295 #  if __JHERETIC__
1296         checkMissileImpact(*thing);
1297 #  endif
1298         // Would we hit another thing or a solid wall?
1299         if(!thing->onMobj || thing->wallHit)
1300             return false;
1301 #endif
1302     }
1303 
1304     if(!(thing->flags & MF_NOCLIP))
1305     {
1306 #if __JHEXEN__
1307         if(tmCeilingZ - tmFloorZ < thing->height)
1308         {   // Doesn't fit.
1309             goto pushline;
1310         }
1311 
1312         tmFloatOk = true;
1313 
1314         if(!(thing->flags & MF_TELEPORT) &&
1315            tmCeilingZ - thing->origin[VZ] < thing->height &&
1316            thing->type != MT_LIGHTNING_CEILING && !(thing->flags2 & MF2_FLY))
1317         {
1318             // Mobj must lower itself to fit.
1319             goto pushline;
1320         }
1321 #else
1322         // Possibly allow escape if otherwise stuck.
1323         dd_bool ret = (tmUnstuck &&
1324             !(tmCeilingLine && untouched(tmCeilingLine, tmThing)) &&
1325             !(tmFloorLine   && untouched(tmFloorLine, tmThing)));
1326 
1327         if(tmCeilingZ - tmFloorZ < thing->height)
1328         {
1329             return ret; // Doesn't fit.
1330         }
1331 
1332         // Mobj must lower to fit.
1333         tmFloatOk = true;
1334         if(!(thing->flags & MF_TELEPORT) && !(thing->flags2 & MF2_FLY) &&
1335            tmCeilingZ - thing->origin[VZ] < thing->height)
1336         {
1337             return ret;
1338         }
1339 
1340         // Too big a step up.
1341         if(!(thing->flags & MF_TELEPORT) &&
1342             !(thing->flags2 & MF2_FLY)
1343 # if __JHERETIC__
1344             && thing->type != MT_MNTRFX2 // The Minotaur floor fire (MT_MNTRFX2) can step up any amount
1345 # endif
1346             )
1347         {
1348             if(!isRemotePlayer && tmFloorZ - thing->origin[VZ] > 24)
1349             {
1350 # if __JHERETIC__
1351                 checkMissileImpact(*thing);
1352 # endif
1353                 return ret;
1354             }
1355         }
1356 # if __JHERETIC__
1357         if((thing->flags & MF_MISSILE) && tmFloorZ > thing->origin[VZ])
1358         {
1359             checkMissileImpact(*thing);
1360         }
1361 # endif
1362 #endif
1363         if(thing->flags2 & MF2_FLY)
1364         {
1365             if(thing->origin[VZ] + thing->height > tmCeilingZ)
1366             {
1367                 thing->mom[MZ] = -8;
1368 #if __JHEXEN__
1369                 goto pushline;
1370 #else
1371                 return false;
1372 #endif
1373             }
1374             else if(thing->origin[VZ] < tmFloorZ &&
1375                     tmFloorZ - tmDropoffZ > 24)
1376             {
1377                 thing->mom[MZ] = 8;
1378 #if __JHEXEN__
1379                 goto pushline;
1380 #else
1381                 return false;
1382 #endif
1383             }
1384         }
1385 
1386 #if __JHEXEN__
1387         if(!(thing->flags & MF_TELEPORT)
1388            // The Minotaur floor fire (MT_MNTRFX2) can step up any amount
1389            && thing->type != MT_MNTRFX2 && thing->type != MT_LIGHTNING_FLOOR
1390            && !isRemotePlayer
1391            && tmFloorZ - thing->origin[VZ] > 24)
1392         {
1393             goto pushline;
1394         }
1395 #endif
1396 
1397 #if __JHEXEN__
1398         if(!(thing->flags & (MF_DROPOFF | MF_FLOAT)) &&
1399            (tmFloorZ - tmDropoffZ > 24) &&
1400            !(thing->flags2 & MF2_BLASTED))
1401         {
1402             // Can't move over a dropoff unless it's been blasted.
1403             return false;
1404         }
1405 #else
1406 
1407         /*
1408          * Allow certain objects to drop off.
1409          * Prevent monsters from getting stuck hanging off ledges.
1410          * Allow dropoffs in controlled circumstances.
1411          * Improve symmetry of clipping on stairs.
1412          */
1413         {
1414             if (!(thing->flags & (MF_DROPOFF | MF_FLOAT)))
1415             {
1416                 // Dropoff height limit.
1417                 if (cfg.avoidDropoffs)
1418                 {
1419                     if (tmFloorZ - tmDropoffZ > 24)
1420                     {
1421                         return false; // Don't stand over dropoff.
1422                     }
1423                 }
1424                 else
1425                 {
1426                     coord_t floorZ = tmFloorZ;
1427                     if (thing->onMobj)
1428                     {
1429                         // Thing is stood on something so use our z position as the floor.
1430                         floorZ = (thing->origin[VZ] > tmFloorZ ? thing->origin[VZ] : tmFloorZ);
1431                     }
1432 
1433                     if (!dropoff)
1434                     {
1435                         if (thing->floorZ - floorZ > 24 || thing->dropOffZ - tmDropoffZ > 24)
1436                             return false;
1437                     }
1438                     else
1439                     {
1440                         tmFellDown = !(thing->flags & MF_NOGRAVITY) && thing->origin[VZ] - floorZ > 24;
1441                     }
1442                 }
1443             }
1444 #if defined (__JHERETIC__)
1445             else if (!thing->onMobj && (thing->flags & MF_DROPOFF) && !(thing->flags & MF_NOGRAVITY))
1446             {
1447                 // Allow gentle dropoff from great heights.
1448                 tmFellDown = (thing->origin[VZ] - tmFloorZ > 24);
1449             }
1450 #endif
1451         }
1452 #endif
1453 
1454 #if __JDOOM64__
1455         /// @todo D64 Mother demon fire attack.
1456         if(!(thing->flags & MF_TELEPORT) /*&& thing->type != MT_SPAWNFIRE*/
1457             && !isRemotePlayer
1458             && tmFloorZ - thing->origin[VZ] > 24)
1459         {
1460             // Too big a step up
1461             checkMissileImpact(*thing);
1462             return false;
1463         }
1464 #endif
1465 
1466 #if __JHEXEN__
1467         // Must stay within a sector of a certain floor type?
1468         if((thing->flags2 & MF2_CANTLEAVEFLOORPIC) &&
1469            (tmFloorMaterial != P_GetPtrp(Mobj_Sector(thing), DMU_FLOOR_MATERIAL) ||
1470             !FEQUAL(tmFloorZ, thing->origin[VZ])))
1471         {
1472             return false;
1473         }
1474 #endif
1475 
1476 #if !__JHEXEN__
1477         // $dropoff: prevent falling objects from going up too many steps.
1478         if(!thing->player && (thing->intFlags & MIF_FALLING) &&
1479            tmFloorZ - thing->origin[VZ] > (thing->mom[MX] * thing->mom[MX]) +
1480                                           (thing->mom[MY] * thing->mom[MY]))
1481         {
1482             return false;
1483         }
1484 #endif
1485     }
1486 
1487     vec3d_t oldPos; V3d_Copy(oldPos, thing->origin);
1488 
1489     // The move is ok, so link the thing into its new position.
1490     P_MobjUnlink(thing);
1491 
1492     thing->origin[VX] = x;
1493     thing->origin[VY] = y;
1494     thing->floorZ     = tmFloorZ;
1495     thing->ceilingZ   = tmCeilingZ;
1496 #if __JDOOM__ || __JDOOM64__ || __JHERETIC__
1497     thing->dropOffZ   = tmDropoffZ; // $dropoff_fix: keep track of dropoffs.
1498 #endif
1499 
1500     P_MobjLink(thing);
1501 
1502     if(thing->flags2 & MF2_FLOORCLIP)
1503     {
1504         thing->floorClip = 0;
1505 
1506         if(FEQUAL(thing->origin[VZ], P_GetDoublep(Mobj_Sector(thing), DMU_FLOOR_HEIGHT)))
1507         {
1508             terraintype_t const *tt = P_MobjFloorTerrain(thing);
1509             if(tt->flags & TTF_FLOORCLIP)
1510             {
1511                 thing->floorClip = 10;
1512             }
1513         }
1514     }
1515 
1516     // If any special lines were hit, do the effect.
1517     if(!(thing->flags & (MF_TELEPORT | MF_NOCLIP)))
1518     {
1519         Line *line;
1520         while((line = (Line *)IterList_Pop(spechit)) != 0)
1521         {
1522             // See if the line was crossed.
1523             if(P_ToXLine(line)->special)
1524             {
1525                 int side    = Line_PointOnSide(line, thing->origin) < 0;
1526                 int oldSide = Line_PointOnSide(line, oldPos) < 0;
1527 
1528                 if(side != oldSide)
1529                 {
1530 #if __JHEXEN__
1531                     if(thing->player)
1532                     {
1533                         P_ActivateLine(line, thing, oldSide, SPAC_CROSS);
1534                     }
1535                     else if(thing->flags2 & MF2_MCROSS)
1536                     {
1537                         P_ActivateLine(line, thing, oldSide, SPAC_MCROSS);
1538                     }
1539                     else if(thing->flags2 & MF2_PCROSS)
1540                     {
1541                         P_ActivateLine(line, thing, oldSide, SPAC_PCROSS);
1542                     }
1543 #else
1544 
1545                     if(!IS_CLIENT && thing->player)
1546                     {
1547                         App_Log(DE2_DEV_MAP_VERBOSE, "P_TryMove2: Mobj %i crossing line %i from %f,%f to %f,%f",
1548                                 thing->thinker.id, P_ToIndex(line),
1549                                 oldPos[VX], oldPos[VY],
1550                                 thing->origin[VX], thing->origin[VY]);
1551                     }
1552 
1553                     P_ActivateLine(line, thing, oldSide, SPAC_CROSS);
1554 #endif
1555                 }
1556             }
1557         }
1558     }
1559 
1560     return true;
1561 
1562 #if __JHEXEN__
1563   pushline:
1564     if(!(thing->flags & (MF_TELEPORT | MF_NOCLIP)))
1565     {
1566         if(tmThing->flags2 & MF2_BLASTED)
1567         {
1568             P_DamageMobj(tmThing, NULL, NULL, tmThing->info->mass >> 5, false);
1569         }
1570 
1571         IterList_SetIteratorDirection(spechit, ITERLIST_BACKWARD);
1572         IterList_RewindIterator(spechit);
1573 
1574         Line *line;
1575         while((line = (Line *)IterList_MoveIterator(spechit)) != 0)
1576         {
1577             // See if the line was crossed.
1578             int side = Line_PointOnSide(line, thing->origin) < 0;
1579             checkForPushSpecial(line, side, thing);
1580         }
1581     }
1582     return false;
1583 #endif
1584 }
1585 
1586 #if __JHEXEN__
P_TryMoveXY(mobj_t * thing,coord_t x,coord_t y)1587 dd_bool P_TryMoveXY(mobj_t *thing, coord_t x, coord_t y)
1588 #else
1589 dd_bool P_TryMoveXY(mobj_t *thing, coord_t x, coord_t y, dd_bool dropoff, dd_bool slide)
1590 #endif
1591 {
1592 #if __JHEXEN__
1593     return P_TryMove2(thing, x, y);
1594 #else
1595     // $dropoff_fix
1596     dd_bool res = P_TryMove2(thing, x, y, dropoff);
1597 
1598     if(!res && tmHitLine)
1599     {
1600         // Move not possible, see if the thing hit a line and send a Hit
1601         // event to it.
1602         XL_HitLine(tmHitLine, Line_PointOnSide(tmHitLine, thing->origin) < 0,
1603                    thing);
1604     }
1605 
1606     if(res && slide)
1607     {
1608         thing->wallRun = true;
1609     }
1610 
1611     return res;
1612 #endif
1613 }
1614 
P_TryMoveXYZ(mobj_t * thing,coord_t x,coord_t y,coord_t z)1615 dd_bool P_TryMoveXYZ(mobj_t* thing, coord_t x, coord_t y, coord_t z)
1616 {
1617     coord_t const oldZ = thing->origin[VZ];
1618 
1619     // Go to the new Z height.
1620     thing->origin[VZ] = z;
1621 
1622 #ifdef __JHEXEN__
1623     if(P_TryMoveXY(thing, x, y))
1624 #else
1625     if(P_TryMoveXY(thing, x, y, false, false))
1626 #endif
1627     {
1628         // The move was successful.
1629         return true;
1630     }
1631 
1632     // The move failed, so restore the original position.
1633     thing->origin[VZ] = oldZ;
1634     return false;
1635 }
1636 
spawnPuff(mobjtype_t type,const_pvec3d_t pos,bool noSpark=false)1637 static mobj_t *spawnPuff(mobjtype_t type, const_pvec3d_t pos, bool noSpark = false)
1638 {
1639 #if __JHERETIC__ || __JHEXEN__
1640     DENG_UNUSED(noSpark);
1641 #endif
1642 
1643     angle_t const angle = P_Random() << 24;
1644 
1645 #if !__JHEXEN__
1646     // Clients do not spawn puffs.
1647     if(IS_CLIENT) return 0;
1648 #endif
1649 
1650     mobjtype_t puffType = type;
1651     coord_t zOffset = 0;
1652 #if __JHERETIC__
1653     if(type == MT_BLASTERPUFF1)
1654     {
1655         puffType = MT_BLASTERPUFF2;
1656     }
1657     else
1658 #endif
1659     {
1660         zOffset = FIX2FLT((P_Random() - P_Random()) << 10);
1661     }
1662 
1663     mobj_t *puff = P_SpawnMobjXYZ(puffType, pos[VX], pos[VY], pos[VZ] + zOffset, angle, 0);
1664     if(puff)
1665     {
1666 #if __JHEXEN__
1667         if(lineTarget && puff->info->seeSound)
1668         {
1669             // Hit thing sound.
1670             S_StartSound(puff->info->seeSound, puff);
1671         }
1672         else if(puff->info->attackSound)
1673         {
1674             S_StartSound(puff->info->attackSound, puff);
1675         }
1676 
1677         switch(type)
1678         {
1679         case MT_PUNCHPUFF:  puff->mom[MZ] = 1;   break;
1680         case MT_HAMMERPUFF: puff->mom[MZ] = .8f; break;
1681 
1682         default: break;
1683         }
1684 #elif __JHERETIC__
1685         if(puffType == MT_BLASTERPUFF1)
1686         {
1687             S_StartSound(SFX_BLSHIT, puff);
1688         }
1689         else
1690         {
1691             if(puff->info->attackSound)
1692             {
1693                 S_StartSound(puff->info->attackSound, puff);
1694             }
1695 
1696             switch(type)
1697             {
1698             case MT_BEAKPUFF:
1699             case MT_STAFFPUFF:     puff->mom[MZ] = 1;   break;
1700 
1701             case MT_GAUNTLETPUFF1:
1702             case MT_GAUNTLETPUFF2: puff->mom[MZ] = .8f; break;
1703 
1704             default: break;
1705             }
1706         }
1707 #else
1708         puff->mom[MZ] = FIX2FLT(FRACUNIT);
1709 
1710         puff->tics -= P_Random() & 3;
1711         if(puff->tics < 1) puff->tics = 1; // Always at least one tic.
1712 
1713         // Don't make punches spark on the wall.
1714         if(noSpark)
1715         {
1716             P_MobjChangeState(puff, S_PUFF3);
1717         }
1718 #endif
1719     }
1720 
1721 #if __JHEXEN__
1722     PuffSpawned = puff;
1723 #endif
1724 
1725     return puff;
1726 }
1727 
1728 struct ptr_shoottraverse_params_t
1729 {
1730     mobj_t *shooterMobj; ///< Mobj doing the shooting.
1731     int damage;          ///< Damage to inflict.
1732     coord_t range;       ///< Maximum effective range from the trace origin.
1733     mobjtype_t puffType; ///< Type of puff to spawn.
1734     bool puffNoSpark;    ///< @c true= Advance the puff to the first non-spark state.
1735 };
1736 
1737 /**
1738  * @todo This routine has gotten way too big, split if(in->isaline)
1739  *       to a seperate routine?
1740  */
PTR_ShootTraverse(Intercept const * icpt,void * context)1741 static int PTR_ShootTraverse(Intercept const *icpt, void *context)
1742 {
1743     vec3d_t const tracePos = {
1744         Interceptor_Origin(icpt->trace)[VX], Interceptor_Origin(icpt->trace)[VY], shootZ
1745     };
1746 
1747     ptr_shoottraverse_params_t &parm = *static_cast<ptr_shoottraverse_params_t *>(context);
1748 
1749     if(icpt->type == ICPT_LINE)
1750     {
1751         bool lineWasHit = false;
1752 #ifdef __JHEXEN__
1753         DENG_UNUSED(lineWasHit);
1754 #endif
1755 
1756         Line *line = icpt->line;
1757         xline_t *xline = P_ToXLine(line);
1758 
1759         Sector *backSec = (Sector *)P_GetPtrp(line, DMU_BACK_SECTOR);
1760 
1761         if(!backSec || !(xline->flags & ML_TWOSIDED))
1762         {
1763             if(Line_PointOnSide(line, tracePos) < 0)
1764             {
1765                 return false; // Continue traversal.
1766             }
1767         }
1768 
1769         if(xline->special)
1770         {
1771             P_ActivateLine(line, parm.shooterMobj, 0, SPAC_IMPACT);
1772         }
1773 
1774         Sector *frontSec = 0;
1775         coord_t dist = 0;
1776         coord_t slope = 0;
1777 
1778         if(!backSec) goto hitline;
1779 
1780 #if __JDOOM64__
1781         if(xline->flags & ML_BLOCKALL) goto hitline;
1782 #endif
1783 
1784         // Crosses a two sided line.
1785         Interceptor_AdjustOpening(icpt->trace, line);
1786 
1787         frontSec = (Sector *)P_GetPtrp(line, DMU_FRONT_SECTOR);
1788 
1789         dist = parm.range * icpt->distance;
1790         slope = 0;
1791         if(!FEQUAL(P_GetDoublep(frontSec, DMU_FLOOR_HEIGHT),
1792                    P_GetDoublep(backSec,  DMU_FLOOR_HEIGHT)))
1793         {
1794             slope = (Interceptor_Opening(icpt->trace)->bottom - tracePos[VZ]) / dist;
1795 
1796             if(slope > aimSlope) goto hitline;
1797         }
1798 
1799         if(!FEQUAL(P_GetDoublep(frontSec, DMU_CEILING_HEIGHT),
1800                    P_GetDoublep(backSec,  DMU_CEILING_HEIGHT)))
1801         {
1802             slope = (Interceptor_Opening(icpt->trace)->top - tracePos[VZ]) / dist;
1803 
1804             if(slope < aimSlope) goto hitline;
1805         }
1806         // Shot continues...
1807         return false;
1808 
1809         // Hit a line.
1810       hitline:
1811 
1812         // Position a bit closer.
1813         coord_t frac = icpt->distance - (4 / parm.range);
1814         vec3d_t pos = { tracePos[VX] + Interceptor_Direction(icpt->trace)[VX] * frac,
1815                         tracePos[VY] + Interceptor_Direction(icpt->trace)[VY] * frac,
1816                         tracePos[VZ] + aimSlope * (frac * parm.range) };
1817 
1818         if(backSec)
1819         {
1820             // Is it a sky hack wall? If the hitpoint is beyond the visible
1821             // surface, no puff must be shown.
1822             if((P_GetIntp(P_GetPtrp(frontSec, DMU_CEILING_MATERIAL),
1823                           DMU_FLAGS) & MATF_SKYMASK) &&
1824                (pos[VZ] > P_GetDoublep(frontSec, DMU_CEILING_HEIGHT) ||
1825                 pos[VZ] > P_GetDoublep(backSec,  DMU_CEILING_HEIGHT)))
1826             {
1827                 return true;
1828             }
1829 
1830             if((P_GetIntp(P_GetPtrp(backSec, DMU_FLOOR_MATERIAL),
1831                           DMU_FLAGS) & MATF_SKYMASK) &&
1832                (pos[VZ] < P_GetDoublep(frontSec, DMU_FLOOR_HEIGHT) ||
1833                 pos[VZ] < P_GetDoublep(backSec,  DMU_FLOOR_HEIGHT)))
1834             {
1835                 return true;
1836             }
1837         }
1838 
1839         lineWasHit = true;
1840 
1841         // This is the sector where the trace originates.
1842         Sector *originSector = Sector_AtPoint_FixedPrecision(tracePos);
1843 
1844         vec3d_t d; V3d_Subtract(d, pos, tracePos);
1845 
1846         if(!INRANGE_OF(d[VZ], 0, .0001f)) // Epsilon
1847         {
1848             Sector *contact = Sector_AtPoint_FixedPrecision(pos);
1849             coord_t step    = M_ApproxDistance3(d[VX], d[VY], d[VZ] * 1.2/*aspect ratio*/);
1850             vec3d_t stepv   = { d[VX] / step, d[VY] / step, d[VZ] / step };
1851 
1852             // Backtrack until we find a non-empty sector.
1853             coord_t cFloor = P_GetDoublep(contact, DMU_FLOOR_HEIGHT);
1854             coord_t cCeil  = P_GetDoublep(contact, DMU_CEILING_HEIGHT);
1855             while(cCeil <= cFloor && contact != originSector)
1856             {
1857                 d[VX] -= 8 * stepv[VX];
1858                 d[VY] -= 8 * stepv[VY];
1859                 d[VZ] -= 8 * stepv[VZ];
1860                 pos[VX] = tracePos[VX] + d[VX];
1861                 pos[VY] = tracePos[VY] + d[VY];
1862                 pos[VZ] = tracePos[VZ] + d[VZ];
1863                 contact = Sector_AtPoint_FixedPrecision(pos);
1864             }
1865 
1866             // Should we backtrack to hit a plane instead?
1867             coord_t cTop    = cCeil - 4;
1868             coord_t cBottom = cFloor + 4;
1869             int divisor     = 2;
1870 
1871             // We must not hit a sky plane.
1872             if(pos[VZ] > cTop &&
1873                (P_GetIntp(P_GetPtrp(contact, DMU_CEILING_MATERIAL),
1874                             DMU_FLAGS) & MATF_SKYMASK))
1875             {
1876                 return true;
1877             }
1878 
1879             if(pos[VZ] < cBottom &&
1880                (P_GetIntp(P_GetPtrp(contact, DMU_FLOOR_MATERIAL),
1881                             DMU_FLAGS) & MATF_SKYMASK))
1882             {
1883                 return true;
1884             }
1885 
1886             // Find the approximate hitpoint by stepping back and
1887             // forth using smaller and smaller steps.
1888             while((pos[VZ] > cTop || pos[VZ] < cBottom) && divisor <= 128)
1889             {
1890                 // We aren't going to hit a line any more.
1891                 lineWasHit = false;
1892 
1893                 // Take a step backwards.
1894                 pos[VX] -= d[VX] / divisor;
1895                 pos[VY] -= d[VY] / divisor;
1896                 pos[VZ] -= d[VZ] / divisor;
1897 
1898                 // Divisor grows.
1899                 divisor *= 2;
1900 
1901                 // Can we get any closer?
1902                 if(IS_ZERO(d[VZ] / divisor))
1903                 {
1904                     break; // No.
1905                 }
1906 
1907                 // Move forward until limits breached.
1908                 while((d[VZ] > 0 && pos[VZ] <= cTop) ||
1909                       (d[VZ] < 0 && pos[VZ] >= cBottom))
1910                 {
1911                     pos[VX] += d[VX] / divisor;
1912                     pos[VY] += d[VY] / divisor;
1913                     pos[VZ] += d[VZ] / divisor;
1914                 }
1915             }
1916         }
1917 
1918         // Spawn bullet puffs.
1919         spawnPuff(parm.puffType, pos, parm.puffNoSpark);
1920 
1921 #if !__JHEXEN__
1922         if(lineWasHit && xline->special)
1923         {
1924             // Extended shoot events only happen when the bullet actually hits the line.
1925             XL_ShootLine(line, 0, parm.shooterMobj);
1926         }
1927 #endif
1928         // Don't go any farther.
1929         return true;
1930     }
1931 
1932     // Intercepted a mobj.
1933     mobj_t *th = icpt->mobj;
1934 
1935     if(th == parm.shooterMobj) return false; // Can't shoot oneself.
1936     if(!(th->flags & MF_SHOOTABLE)) return false; // Corpse or something.
1937 
1938 #if __JHERETIC__
1939     // Check for physical attacks on a ghost.
1940     if ((th->flags & MF_SHADOW) && Mobj_IsPlayer(parm.shooterMobj) &&
1941         parm.shooterMobj->player->readyWeapon == WT_FIRST)
1942     {
1943         if (!cfg.staffPowerDamageToGhosts || !parm.shooterMobj->player->powers[PT_WEAPONLEVEL2])
1944         {
1945             return false;
1946         }
1947     }
1948 #endif
1949 
1950     // Check angles to see if the thing can be aimed at
1951     coord_t dist = parm.range * icpt->distance;
1952     coord_t dz   = th->origin[VZ];
1953     if(!(th->player && (th->player->plr->flags & DDPF_CAMERA)))
1954     {
1955         dz += th->height;
1956     }
1957     dz -= tracePos[VZ];
1958 
1959     coord_t thingTopSlope = dz / dist;
1960     if(thingTopSlope < aimSlope)
1961     {
1962         return false; // Shot over the thing.
1963     }
1964 
1965     coord_t thingBottomSlope = (th->origin[VZ] - tracePos[VZ]) / dist;
1966     if(thingBottomSlope > aimSlope)
1967     {
1968         return false; // Shot under the thing.
1969     }
1970 
1971     // Hit thing.
1972 
1973     // Position a bit closer.
1974     coord_t frac = icpt->distance - (10 / parm.range);
1975     vec3d_t pos  = { tracePos[VX] + Interceptor_Direction(icpt->trace)[VX] * frac,
1976                      tracePos[VY] + Interceptor_Direction(icpt->trace)[VY] * frac,
1977                      tracePos[VZ] + aimSlope * (frac * parm.range) };
1978 
1979     // Spawn bullet puffs or blood spots, depending on target type.
1980 #if __JHERETIC__ || __JHEXEN__
1981     spawnPuff(parm.puffType, pos, parm.puffNoSpark);
1982 #endif
1983 
1984     if(parm.damage)
1985     {
1986 #if __JDOOM__ || __JDOOM64__
1987         angle_t attackAngle = M_PointToAngle2(parm.shooterMobj->origin, pos);
1988 #endif
1989 
1990         mobj_t *inflictor = parm.shooterMobj;
1991 #if __JHEXEN__
1992         if(parm.puffType == MT_FLAMEPUFF2)
1993         {
1994             // Cleric FlameStrike does fire damage.
1995             inflictor = P_LavaInflictor();
1996         }
1997 #endif
1998 
1999         int damageDone =
2000             P_DamageMobj(th, inflictor, parm.shooterMobj, parm.damage, false);
2001 
2002 #if __JHEXEN__
2003         if(!(icpt->mobj->flags2 & MF2_INVULNERABLE))
2004 #endif
2005         {
2006             if(!(icpt->mobj->flags & MF_NOBLOOD))
2007             {
2008                 if(damageDone > 0)
2009                 {
2010                     // Damage was inflicted, so shed some blood.
2011 #if __JDOOM__ || __JDOOM64__
2012                     P_SpawnBlood(pos[VX], pos[VY], pos[VZ], parm.damage, attackAngle + ANG180);
2013 #else
2014 # if __JHEXEN__
2015                     if(parm.puffType == MT_AXEPUFF || parm.puffType == MT_AXEPUFF_GLOW)
2016                     {
2017                         P_SpawnBloodSplatter2(pos[VX], pos[VY], pos[VZ], icpt->mobj);
2018                     }
2019                     else
2020 # endif
2021                     if(P_Random() < 192)
2022                     {
2023                         P_SpawnBloodSplatter(pos[VX], pos[VY], pos[VZ], icpt->mobj);
2024                     }
2025 #endif
2026                 }
2027             }
2028 #if __JDOOM__ || __JDOOM64__
2029             else
2030             {
2031                 spawnPuff(parm.puffType, pos, parm.puffNoSpark);
2032             }
2033 #endif
2034         }
2035     }
2036 
2037     // Don't go any farther.
2038     return true;
2039 }
2040 
2041 /**
2042  * Sets linetarget and aimSlope when a target is aimed at.
2043  */
PTR_AimTraverse(Intercept const * icpt,void *)2044 static int PTR_AimTraverse(Intercept const *icpt, void * /*context*/)
2045 {
2046     vec3d_t const tracePos = {
2047         Interceptor_Origin(icpt->trace)[VX], Interceptor_Origin(icpt->trace)[VY], shootZ
2048     };
2049 
2050     if(icpt->type == ICPT_LINE)
2051     {
2052         Line *line = icpt->line;
2053         Sector *backSec, *frontSec;
2054 
2055         if(!(P_ToXLine(line)->flags & ML_TWOSIDED) ||
2056            !(frontSec = (Sector *)P_GetPtrp(line, DMU_FRONT_SECTOR)) ||
2057            !(backSec  = (Sector *)P_GetPtrp(line, DMU_BACK_SECTOR)))
2058         {
2059             return !(Line_PointOnSide(line, tracePos) < 0);
2060         }
2061 
2062         // Crosses a two sided line.
2063         // A two sided line will restrict the possible target ranges.
2064         if(!Interceptor_AdjustOpening(icpt->trace, line))
2065         {
2066             return true; // Stop.
2067         }
2068 
2069         coord_t dist   = attackRange * icpt->distance;
2070         coord_t fFloor = P_GetDoublep(frontSec, DMU_FLOOR_HEIGHT);
2071         coord_t fCeil  = P_GetDoublep(frontSec, DMU_CEILING_HEIGHT);
2072         coord_t bFloor = P_GetDoublep(backSec, DMU_FLOOR_HEIGHT);
2073         coord_t bCeil  = P_GetDoublep(backSec, DMU_CEILING_HEIGHT);
2074 
2075         coord_t slope;
2076         if(!FEQUAL(fFloor, bFloor))
2077         {
2078             slope = (Interceptor_Opening(icpt->trace)->bottom - shootZ) / dist;
2079             if(slope > bottomSlope)
2080                 bottomSlope = slope;
2081         }
2082 
2083         if(!FEQUAL(fCeil, bCeil))
2084         {
2085             slope = (Interceptor_Opening(icpt->trace)->top - shootZ) / dist;
2086             if(slope < topSlope)
2087                 topSlope = slope;
2088         }
2089 
2090         return topSlope <= bottomSlope;
2091     }
2092 
2093     // Intercepted a mobj.
2094     mobj_t *th = icpt->mobj;
2095 
2096     if(th == shooterThing) return false; // Can't aim at oneself.
2097 
2098     if(!(th->flags & MF_SHOOTABLE)) return false; // Corpse or something (not shootable)?
2099 
2100 #if __JHERETIC__
2101     if(th->type == MT_POD) return false; // Can't auto-aim at pods.
2102 #endif
2103 
2104 #if __JDOOM__ || __JHEXEN__ || __JDOOM64__
2105     if(Mobj_IsPlayer(shooterThing) && Mobj_IsPlayer(th) &&
2106        IS_NETGAME && !gfw_Rule(deathmatch))
2107     {
2108         // In co-op, players don't aim at fellow players (although manually aiming is
2109         // always possible).
2110         return false;
2111     }
2112 #endif
2113 
2114     // Check angles to see if the thing can be aimed at.
2115     coord_t dist = attackRange * icpt->distance;
2116     coord_t posZ = th->origin[VZ];
2117 
2118     if(!(th->player && (th->player->plr->flags & DDPF_CAMERA)))
2119     {
2120         posZ += th->height;
2121     }
2122 
2123     coord_t thingTopSlope = (posZ - shootZ) / dist;
2124     if(thingTopSlope < bottomSlope)
2125     {
2126         return false; // Shot over the thing.
2127     }
2128 
2129     // Too far below?
2130     // $addtocfg $limitautoaimZ:
2131 #if __JHEXEN__
2132     if(posZ < shootZ - attackRange / 1.2f)
2133     {
2134         return false;
2135     }
2136 #endif
2137 
2138     coord_t thingBottomSlope = (th->origin[VZ] - shootZ) / dist;
2139     if(thingBottomSlope > topSlope)
2140     {
2141         return false; // Shot under the thing.
2142     }
2143 
2144     // Too far above?
2145     // $addtocfg $limitautoaimZ:
2146 #if __JHEXEN__
2147     if(th->origin[VZ] > shootZ + attackRange / 1.2f)
2148     {
2149         return false;
2150     }
2151 #endif
2152 
2153     // This thing can be hit!
2154     if(thingTopSlope > topSlope)
2155     {
2156         thingTopSlope = topSlope;
2157     }
2158     if(thingBottomSlope < bottomSlope)
2159     {
2160         thingBottomSlope = bottomSlope;
2161     }
2162 
2163     aimSlope = (thingTopSlope + thingBottomSlope) / 2;
2164     lineTarget = th;
2165 
2166     return true; // Don't go any farther.
2167 }
2168 
P_AimLineAttack(mobj_t * t1,angle_t angle,coord_t distance)2169 float P_AimLineAttack(mobj_t *t1, angle_t angle, coord_t distance)
2170 {
2171     uint an = angle >> ANGLETOFINESHIFT;
2172     vec2d_t target = { t1->origin[VX] + distance * FIX2FLT(finecosine[an]),
2173                        t1->origin[VY] + distance * FIX2FLT(finesine[an]) };
2174 
2175     // Determine the z trace origin.
2176     shootZ = t1->origin[VZ];
2177 #if __JHEXEN__
2178     if(t1->player &&
2179       (t1->player->class_ == PCLASS_FIGHTER ||
2180        t1->player->class_ == PCLASS_CLERIC ||
2181        t1->player->class_ == PCLASS_MAGE))
2182 #else
2183     if(t1->player && t1->type == MT_PLAYER)
2184 #endif
2185     {
2186         if(!(t1->player->plr->flags & DDPF_CAMERA))
2187         {
2188             shootZ += (cfg.common.plrViewHeight - 5);
2189         }
2190     }
2191     else
2192     {
2193         shootZ += (t1->height / 2) + 8;
2194     }
2195 
2196     /// @todo What about t1->floorClip ? -ds
2197 
2198     topSlope     = 100.0/160;
2199     bottomSlope  = -100.0/160;
2200     attackRange  = distance;
2201     lineTarget   = 0;
2202     shooterThing = t1;
2203 
2204     P_PathTraverse(t1->origin, target, PTR_AimTraverse, 0);
2205 
2206     if(lineTarget)
2207     {
2208         // While autoaiming, we accept this slope.
2209         if(!t1->player || !cfg.common.noAutoAim)
2210         {
2211             return aimSlope;
2212         }
2213     }
2214 
2215     if(t1->player && cfg.common.noAutoAim)
2216     {
2217         // The slope is determined by lookdir.
2218         return tan(LOOKDIR2RAD(t1->dPlayer->lookDir)) / 1.2;
2219     }
2220 
2221     return 0;
2222 }
2223 
P_LineAttack(mobj_t * t1,angle_t angle,coord_t distance,coord_t slope,int damage,mobjtype_t puffType)2224 void P_LineAttack(mobj_t *t1, angle_t angle, coord_t distance, coord_t slope,
2225     int damage, mobjtype_t puffType)
2226 {
2227     uint an = angle >> ANGLETOFINESHIFT;
2228     vec2d_t target = { t1->origin[VX] + distance * FIX2FLT(finecosine[an]),
2229                        t1->origin[VY] + distance * FIX2FLT(finesine[an]) };
2230 
2231     aimSlope = slope;
2232 
2233     // Determine the z trace origin.
2234     shootZ = t1->origin[VZ];
2235 #if __JHEXEN__
2236     if(t1->player &&
2237       (t1->player->class_ == PCLASS_FIGHTER ||
2238        t1->player->class_ == PCLASS_CLERIC ||
2239        t1->player->class_ == PCLASS_MAGE))
2240 #else
2241     if(t1->player && t1->type == MT_PLAYER)
2242 #endif
2243     {
2244         if(!(t1->player->plr->flags & DDPF_CAMERA))
2245         {
2246             shootZ += cfg.common.plrViewHeight - 5;
2247         }
2248     }
2249     else
2250     {
2251         shootZ += (t1->height / 2) + 8;
2252     }
2253     shootZ -= t1->floorClip;
2254 
2255     ptr_shoottraverse_params_t parm;
2256     parm.shooterMobj = t1;
2257     parm.range       = distance;
2258     parm.damage      = damage;
2259     parm.puffType    = puffType;
2260 #if __JDOOM__ || __JDOOM64__
2261     parm.puffNoSpark = attackRange == MELEERANGE;
2262 #else
2263     parm.puffNoSpark = false;
2264 #endif
2265 
2266     if(!P_PathTraverse(t1->origin, target, PTR_ShootTraverse, &parm))
2267     {
2268 #if __JHEXEN__
2269         switch(puffType)
2270         {
2271         case MT_PUNCHPUFF:
2272             S_StartSound(SFX_FIGHTER_PUNCH_MISS, t1);
2273             break;
2274 
2275         case MT_HAMMERPUFF:
2276         case MT_AXEPUFF:
2277         case MT_AXEPUFF_GLOW:
2278             S_StartSound(SFX_FIGHTER_HAMMER_MISS, t1);
2279             break;
2280 
2281         case MT_FLAMEPUFF: {
2282             vec3d_t pos = { target[VX], target[VY], shootZ + (slope * distance) };
2283             spawnPuff(puffType, pos);
2284             break; }
2285 
2286         default: break;
2287         }
2288 #endif
2289     }
2290 }
2291 
2292 struct pit_radiusattack_params_t
2293 {
2294     mobj_t *source;     ///< Mobj which caused the attack.
2295     mobj_t *bomb;       ///< Epicenter of the attack.
2296     int damage;         ///< Maximum damage to inflict.
2297     int distance;       ///< Maximum distance within which to afflict.
2298 #ifdef __JHEXEN__
2299     bool afflictSource; ///< @c true= Afflict the source, also.
2300 #endif
2301 };
2302 
PIT_RadiusAttack(mobj_t * thing,void * context)2303 static int PIT_RadiusAttack(mobj_t *thing, void *context)
2304 {
2305     pit_radiusattack_params_t &parm = *static_cast<pit_radiusattack_params_t *>(context);
2306 
2307     if(!(thing->flags & MF_SHOOTABLE))
2308     {
2309         return false;
2310     }
2311 
2312     // Boss spider and cyborg take no damage from concussion.
2313 #if __JHERETIC__
2314     if(thing->type == MT_MINOTAUR) return false;
2315     if(thing->type == MT_SORCERER1) return false;
2316     if(thing->type == MT_SORCERER2) return false;
2317 #elif __JDOOM__ || __JDOOM64__
2318     if(thing->type == MT_CYBORG) return false;
2319 # if __JDOOM__
2320     if(thing->type == MT_SPIDER) return false;
2321 # endif
2322 #endif
2323 
2324 #if __JHEXEN__
2325     // Is the source of the explosion immune to damage?
2326     if(thing == parm.source && !parm.afflictSource)
2327     {
2328         return false;
2329     }
2330 #endif
2331 
2332     vec3d_t delta = { fabs(thing->origin[VX] - parm.bomb->origin[VX]),
2333                       fabs(thing->origin[VY] - parm.bomb->origin[VY]),
2334                       fabs((thing->origin[VZ] + thing->height / 2) - parm.bomb->origin[VZ]) };
2335 
2336     coord_t dist = (delta[VX] > delta[VY]? delta[VX] : delta[VY]);
2337 #if __JHEXEN__
2338     if(!cfg.common.netNoMaxZRadiusAttack)
2339     {
2340         dist = (delta[VZ] > dist? delta[VZ] : dist);
2341     }
2342 #else
2343     if(!(cfg.common.netNoMaxZRadiusAttack || (thing->info->flags2 & MF2_INFZBOMBDAMAGE)))
2344     {
2345         dist = (delta[VZ] > dist? delta[VZ] : dist);
2346     }
2347 #endif
2348 
2349     dist = MAX_OF(dist - thing->radius, 0);
2350     if(dist >= parm.distance)
2351     {
2352         return false; // Out of range.
2353     }
2354 
2355     // Must be in direct path.
2356     if(P_CheckSight(thing, parm.bomb))
2357     {
2358         int damage = (parm.damage * (parm.distance - dist) / parm.distance) + 1;
2359 #if __JHEXEN__
2360         if(thing->player) damage /= 4;
2361 #endif
2362 
2363         P_DamageMobj(thing, parm.bomb, parm.source, damage, false);
2364     }
2365 
2366     return false;
2367 }
2368 
2369 #if __JHEXEN__
P_RadiusAttack(mobj_t * bomb,mobj_t * source,int damage,int distance,dd_bool afflictSource)2370 void P_RadiusAttack(mobj_t *bomb, mobj_t *source, int damage, int distance, dd_bool afflictSource)
2371 #else
2372 void P_RadiusAttack(mobj_t *bomb, mobj_t *source, int damage, int distance)
2373 #endif
2374 {
2375     coord_t const dist = distance + MAXRADIUS;
2376     AABoxd const box(bomb->origin[VX] - dist, bomb->origin[VY] - dist,
2377                      bomb->origin[VX] + dist, bomb->origin[VY] + dist);
2378 
2379     pit_radiusattack_params_t parm;
2380     parm.bomb          = bomb;
2381     parm.damage        = damage;
2382     parm.distance      = distance;
2383     parm.source        = source;
2384 #if __JHERETIC__
2385     if(bomb->type == MT_POD && bomb->target)
2386     {
2387         // The credit should go to the original source (chain-reaction kills).
2388         parm.source = bomb->target;
2389     }
2390 #endif
2391 #if __JHEXEN__
2392     parm.afflictSource = CPP_BOOL(afflictSource);
2393 #endif
2394 
2395     VALIDCOUNT++;
2396     Mobj_BoxIterator(&box, PIT_RadiusAttack, &parm);
2397 }
2398 
PTR_UseTraverse(Intercept const * icpt,void * context)2399 static int PTR_UseTraverse(Intercept const *icpt, void *context)
2400 {
2401     DENG_ASSERT(icpt->type == ICPT_LINE);
2402 
2403     mobj_t *activator = static_cast<mobj_t *>(context);
2404 
2405     xline_t *xline = P_ToXLine(icpt->line);
2406     if(!xline->special)
2407     {
2408         if(!Interceptor_AdjustOpening(icpt->trace, icpt->line))
2409         {
2410             if(Mobj_IsPlayer(activator))
2411             {
2412                 S_StartSound(PCLASS_INFO(activator->player->class_)->failUseSound, activator);
2413             }
2414 
2415             return true; // Can't use through a wall.
2416         }
2417 
2418 #if __JHEXEN__
2419         if(Mobj_IsPlayer(activator))
2420         {
2421             coord_t pheight = activator->origin[VZ] + activator->height/2;
2422 
2423             if(Interceptor_Opening(icpt->trace)->top < pheight ||
2424                Interceptor_Opening(icpt->trace)->bottom > pheight)
2425             {
2426                 S_StartSound(PCLASS_INFO(activator->player->class_)->failUseSound, activator);
2427             }
2428         }
2429 #endif
2430         // Not a special line, but keep checking.
2431         return false;
2432     }
2433 
2434     int side = Line_PointOnSide(icpt->line, activator->origin) < 0;
2435 
2436 #if __JHERETIC__ || __JHEXEN__
2437     if(side == 1) return true; // Don't use back side.
2438 #endif
2439 
2440     P_ActivateLine(icpt->line, activator, side, SPAC_USE);
2441 
2442 #if __JDOOM__ || __JHERETIC__ || __JDOOM64__
2443     // Can use multiple line specials in a row with the PassThru flag.
2444     if(xline->flags & ML_PASSUSE)
2445     {
2446         return false;
2447     }
2448 #endif
2449 
2450     // Can't use more than one special line in a row.
2451     return true;
2452 }
2453 
P_UseLines(player_t * player)2454 void P_UseLines(player_t *player)
2455 {
2456     if(!player) return;
2457 
2458     if(IS_CLIENT)
2459     {
2460         App_Log(DE2_DEV_NET_VERBOSE, "P_UseLines: Sending a use request for player %i", int(player - players));
2461 
2462         NetCl_PlayerActionRequest(player, GPA_USE, 0);
2463         return;
2464     }
2465 
2466     mobj_t *mo  = player->plr->mo;
2467     if(!mo) return;
2468 
2469     uint an     = mo->angle >> ANGLETOFINESHIFT;
2470     vec2d_t pos = { mo->origin[VX] + USERANGE * FIX2FLT(finecosine[an]),
2471                     mo->origin[VY] + USERANGE * FIX2FLT(finesine  [an]) };
2472 
2473     P_PathTraverse2(mo->origin, pos, PTF_LINE, PTR_UseTraverse, mo);
2474 }
2475 
2476 /**
2477  * Allows the player to slide along any angled walls by adjusting the
2478  * xmove / ymove so that the NEXT move will slide along the wall.
2479  *
2480  * @param line  The line being slid along.
2481  */
hitSlideLine(mobj_t * slideMo,Line * line,pvec2d_t move)2482 static void hitSlideLine(mobj_t *slideMo, Line *line, pvec2d_t move)
2483 {
2484     DENG_ASSERT(slideMo != 0 && line != 0);
2485 
2486     slopetype_t slopeType = slopetype_t(P_GetIntp(line, DMU_SLOPETYPE));
2487     if(slopeType == ST_HORIZONTAL)
2488     {
2489         move[MY] = 0;
2490         return;
2491     }
2492     else if(slopeType == ST_VERTICAL)
2493     {
2494         move[MX] = 0;
2495         return;
2496     }
2497 
2498     bool side = Line_PointOnSide(line, slideMo->origin) < 0;
2499     vec2d_t d1; P_GetDoublepv(line, DMU_DXY, d1);
2500 
2501     angle_t moveAngle = M_PointToAngle(move);
2502     angle_t lineAngle = M_PointToAngle(d1) + (side? ANG180 : 0);
2503 
2504     angle_t deltaAngle = moveAngle - lineAngle;
2505     if(deltaAngle > ANG180) deltaAngle += ANG180;
2506 
2507     coord_t moveLen = M_ApproxDistance(move[MX], move[MY]);
2508     coord_t newLen  = moveLen * FIX2FLT(finecosine[deltaAngle >> ANGLETOFINESHIFT]);
2509 
2510     uint an = lineAngle >> ANGLETOFINESHIFT;
2511     V2d_Set(move, newLen * FIX2FLT(finecosine[an]),
2512                   newLen * FIX2FLT(finesine  [an]));
2513 }
2514 
2515 struct ptr_slidetraverse_params_t
2516 {
2517     mobj_t *slideMobj;
2518     Line *bestLine;
2519     coord_t bestDistance;
2520 };
2521 
PTR_SlideTraverse(Intercept const * icpt,void * context)2522 static int PTR_SlideTraverse(Intercept const *icpt, void *context)
2523 {
2524     DENG_ASSERT(icpt->type == ICPT_LINE);
2525 
2526     ptr_slidetraverse_params_t &parm = *static_cast<ptr_slidetraverse_params_t *>(context);
2527 
2528     Line *line = icpt->line;
2529     if(!(P_ToXLine(line)->flags & ML_TWOSIDED) ||
2530        !P_GetPtrp(line, DMU_FRONT_SECTOR) || !P_GetPtrp(line, DMU_BACK_SECTOR))
2531     {
2532         if(Line_PointOnSide(line, parm.slideMobj->origin) < 0)
2533         {
2534             return false; // Don't hit the back side.
2535         }
2536 
2537         goto isblocking;
2538     }
2539 
2540 #if __JDOOM64__
2541     if(P_ToXLine(line)->flags & ML_BLOCKALL) // jd64
2542         goto isblocking;
2543 #endif
2544 
2545     Interceptor_AdjustOpening(icpt->trace, line);
2546 
2547     if(Interceptor_Opening(icpt->trace)->range < parm.slideMobj->height)
2548         goto isblocking; // Doesn't fit.
2549 
2550     if(Interceptor_Opening(icpt->trace)->top - parm.slideMobj->origin[VZ] < parm.slideMobj->height)
2551         goto isblocking; // mobj is too high.
2552 
2553     if(Interceptor_Opening(icpt->trace)->bottom - parm.slideMobj->origin[VZ] > 24)
2554         goto isblocking; // Too big a step up.
2555 
2556     // This line doesn't block movement.
2557     return false;
2558 
2559     // The line does block movement, see if it is closer than best so far.
2560   isblocking:
2561     if(icpt->distance < parm.bestDistance)
2562     {
2563         parm.bestDistance = icpt->distance;
2564         parm.bestLine     = line;
2565     }
2566 
2567     return true; // Stop.
2568 }
2569 
P_SlideMove(mobj_t * mo)2570 void P_SlideMove(mobj_t *mo)
2571 {
2572     if(!mo) return; // Huh?
2573 
2574     vec2d_t oldOrigin;
2575     V2d_Copy(oldOrigin, mo->origin);
2576 
2577     vec2d_t leadPos  = { 0, 0 };
2578     vec2d_t trailPos = { 0, 0 };
2579     vec2d_t tmMove   = { 0, 0 };
2580 
2581     int hitCount = 3;
2582     do
2583     {
2584         if(--hitCount == 0)
2585             goto stairstep; // Don't loop forever.
2586 
2587         // Trace along the three leading corners.
2588         leadPos[VX] = mo->origin[VX] + (mo->mom[MX] > 0? mo->radius : -mo->radius);
2589         leadPos[VY] = mo->origin[VY] + (mo->mom[MY] > 0? mo->radius : -mo->radius);
2590 
2591         trailPos[VX] = mo->origin[VX] - (mo->mom[MX] > 0? mo->radius : -mo->radius);
2592         trailPos[VY] = mo->origin[VY] - (mo->mom[MY] > 0? mo->radius : -mo->radius);
2593 
2594         ptr_slidetraverse_params_t parm;
2595         parm.slideMobj    = mo;
2596         parm.bestLine     = 0;
2597         parm.bestDistance = 1;
2598 
2599         P_PathXYTraverse2(leadPos[VX], leadPos[VY],
2600                           leadPos[VX] + mo->mom[MX], leadPos[VY] + mo->mom[MY],
2601                           PTF_LINE, PTR_SlideTraverse, &parm);
2602 
2603         P_PathXYTraverse2(trailPos[VX], leadPos[VY],
2604                           trailPos[VX] + mo->mom[MX], leadPos[VY] + mo->mom[MY],
2605                           PTF_LINE, PTR_SlideTraverse, &parm);
2606 
2607         P_PathXYTraverse2(leadPos[VX], trailPos[VY],
2608                           leadPos[VX] + mo->mom[MX], trailPos[VY] + mo->mom[MY],
2609                           PTF_LINE, PTR_SlideTraverse, &parm);
2610 
2611         // Move up to the wall.
2612         if(parm.bestDistance == 1)
2613         {
2614             // The move must have hit the middle, so stairstep. $dropoff_fix
2615           stairstep:
2616 
2617             /*
2618              * Ideally we would set the directional momentum of the mobj to zero
2619              * here should a move fail (to prevent noticeable stuttering against
2620              * the blocking surface/thing). However due to the mechanics of the
2621              * wall side algorithm this is not possible as it results in highly
2622              * unpredictable behaviour and resulting in the player sling-shoting
2623              * away from the wall.
2624              */
2625 #if __JHEXEN__
2626             if(!P_TryMoveXY(mo, mo->origin[VX], mo->origin[VY] + mo->mom[MY]))
2627                 P_TryMoveXY(mo, mo->origin[VX] + mo->mom[MX], mo->origin[VY]);
2628 #else
2629             if(!P_TryMoveXY(mo, mo->origin[VX], mo->origin[VY] + mo->mom[MY], true, true))
2630                 P_TryMoveXY(mo, mo->origin[VX] + mo->mom[MX], mo->origin[VY], true, true);
2631 #endif
2632             break;
2633         }
2634 
2635         // Fudge a bit to make sure it doesn't hit.
2636         parm.bestDistance -= (1.0f / 32);
2637 
2638         if(parm.bestDistance > 0)
2639         {
2640             vec2d_t newPos = { mo->origin[VX] + mo->mom[MX] * parm.bestDistance,
2641                                mo->origin[VY] + mo->mom[MY] * parm.bestDistance };
2642 
2643             // $dropoff_fix: Allow objects to drop off ledges
2644 #if __JHEXEN__
2645             if(!P_TryMoveXY(mo, newPos[VX], newPos[VY]))
2646 #else
2647             if(!P_TryMoveXY(mo, newPos[VX], newPos[VY], true, true))
2648 #endif
2649             {
2650                 goto stairstep;
2651             }
2652         }
2653 
2654         // Now continue along the wall.
2655         // First calculate remainder.
2656         parm.bestDistance = MIN_OF(1 - (parm.bestDistance + (1.0f / 32)), 1);
2657         if(parm.bestDistance <= 0)
2658         {
2659             break;
2660         }
2661 
2662         V2d_Set(tmMove, mo->mom[VX] * parm.bestDistance,
2663                         mo->mom[VY] * parm.bestDistance);
2664 
2665         hitSlideLine(mo, parm.bestLine, tmMove); // Clip the move.
2666 
2667         V2d_Copy(mo->mom, tmMove);
2668 
2669     // $dropoff_fix: Allow objects to drop off ledges:
2670 #if __JHEXEN__
2671     } while(!P_TryMoveXY(mo, mo->origin[VX] + tmMove[MX],
2672                              mo->origin[VY] + tmMove[MY]));
2673 #else
2674     } while(!P_TryMoveXY(mo, mo->origin[VX] + tmMove[MX],
2675                              mo->origin[VY] + tmMove[MY], true, true));
2676 #endif
2677 
2678     // Didn't move?
2679     if(mo->player && mo->origin[VX] == oldOrigin[VX] && mo->origin[VY] == oldOrigin[VY])
2680     {
2681         App_Log(DE2_DEV_MAP_MSG, "P_SlideMove: Mobj %i pos stays the same", mo->thinker.id);
2682     }
2683 }
2684 
2685 /**
2686  * SECTOR HEIGHT CHANGING
2687  * After modifying a sectors floor or ceiling height, call this routine
2688  * to adjust the positions of all things that touch the sector.
2689  *
2690  * If anything doesn't fit anymore, true will be returned.
2691  * If crushDamage is non-zero, they will take damage as they are being crushed.
2692  * If crushDamage is false, you should set the sector height back the way it
2693  * was and call P_ChangeSector again to undo the changes.
2694  */
2695 
2696 struct pit_changesector_params_t
2697 {
2698     int crushDamage;  ///< Damage amount;
2699     bool noFit;
2700 };
2701 
2702 /// @return  Always @c false for use as an interation callback.
PIT_ChangeSector(mobj_t * thing,void * context)2703 static int PIT_ChangeSector(mobj_t *thing, void *context)
2704 {
2705     pit_changesector_params_t &parm = *static_cast<pit_changesector_params_t *>(context);
2706 
2707     if(!thing->info)
2708     {
2709         // Likely a remote object we don't know enough about.
2710         return false;
2711     }
2712 
2713     // Skip mobjs that aren't blocklinked (supposedly immaterial).
2714     if(thing->info->flags & MF_NOBLOCKMAP)
2715     {
2716         return false;
2717     }
2718 
2719     // Update the Z position of the mobj and determine whether it physically
2720     // fits in the opening between floor and ceiling.
2721     if (!P_MobjIsCamera(thing))
2722     {
2723         const bool onfloor = de::fequal(thing->origin[VZ], thing->floorZ);
2724 
2725         P_CheckPosition(thing, thing->origin);
2726         thing->floorZ   = tmFloorZ;
2727         thing->ceilingZ = tmCeilingZ;
2728 #if !__JHEXEN__
2729         thing->dropOffZ = tmDropoffZ; // $dropoff_fix: remember dropoffs.
2730 #endif
2731 
2732         if (onfloor)
2733         {
2734 #if __JHEXEN__
2735             if((thing->origin[VZ] - thing->floorZ < 9) ||
2736                (thing->flags & MF_NOGRAVITY))
2737             {
2738                 thing->origin[VZ] = thing->floorZ;
2739             }
2740 #else
2741             // Update view offset of real players.
2742             if(Mobj_IsPlayer(thing) && !Mobj_IsVoodooDoll(thing))
2743             {
2744                 thing->player->viewZ += thing->floorZ - thing->origin[VZ];
2745             }
2746 
2747             // Walking monsters rise and fall with the floor.
2748             thing->origin[VZ] = thing->floorZ;
2749 
2750             // $dropoff_fix: Possibly upset balance of objects hanging off ledges.
2751             if((thing->intFlags & MIF_FALLING) && thing->gear >= MAXGEAR)
2752             {
2753                 thing->gear = 0;
2754             }
2755 #endif
2756         }
2757         else
2758         {
2759             // Don't adjust a floating monster unless forced to do so.
2760             if(thing->origin[VZ] + thing->height > thing->ceilingZ)
2761             {
2762                 thing->origin[VZ] = thing->ceilingZ - thing->height;
2763             }
2764         }
2765 
2766         // Does this mobj fit in the open space?
2767         if((thing->ceilingZ - thing->floorZ) >= thing->height)
2768         {
2769             return false;
2770         }
2771     }
2772 
2773     // Crunch bodies to giblets.
2774     if(Mobj_IsCrunchable(thing))
2775     {
2776 #if __JHEXEN__
2777         if(thing->flags & MF_NOBLOOD)
2778         {
2779             P_MobjRemove(thing, false);
2780             return false;
2781         }
2782 #endif
2783 
2784 #if __JHEXEN__
2785         if(thing->state != &STATES[S_GIBS1])
2786 #endif
2787         {
2788 #if __JHEXEN__
2789             P_MobjChangeState(thing, S_GIBS1);
2790 #elif __JDOOM__ || __JDOOM64__
2791             P_MobjChangeState(thing, S_GIBS);
2792 #endif
2793 
2794 #if !__JHEXEN__
2795             thing->flags &= ~MF_SOLID;
2796 #endif
2797             thing->height = 0;
2798             thing->radius = 0;
2799 
2800 #if __JHEXEN__
2801             S_StartSound(SFX_PLAYER_FALLING_SPLAT, thing);
2802 #elif __JDOOM64__
2803             S_StartSound(SFX_SLOP, thing);
2804 #endif
2805         }
2806 
2807         return false;
2808     }
2809 
2810     // Remove dropped items.
2811     if(Mobj_IsDroppedItem(thing))
2812     {
2813         P_MobjRemove(thing, false);
2814         return false;
2815     }
2816 
2817     if(!(thing->flags & MF_SHOOTABLE))
2818     {
2819         return false;
2820     }
2821 
2822     parm.noFit = true;
2823 
2824     if(parm.crushDamage > 0 && !(mapTime & 3))
2825     {
2826         P_DamageMobj(thing, NULL, NULL, parm.crushDamage, false);
2827 
2828 #if __JDOOM__ || __JDOOM64__ || __JHERETIC__
2829         if(!(thing->flags & MF_NOBLOOD))
2830 #elif __JHEXEN__
2831         if(!(thing->flags & MF_NOBLOOD) &&
2832            !(thing->flags2 & MF2_INVULNERABLE))
2833 #endif
2834         {
2835             // Spray blood in a random direction.
2836             if(mobj_t *mo = P_SpawnMobjXYZ(MT_BLOOD, thing->origin[VX], thing->origin[VY],
2837                                            thing->origin[VZ] + (thing->height /2),
2838                                            P_Random() << 24, 0))
2839             {
2840                 mo->mom[MX] = FIX2FLT((P_Random() - P_Random()) << 12);
2841                 mo->mom[MY] = FIX2FLT((P_Random() - P_Random()) << 12);
2842             }
2843         }
2844     }
2845 
2846     return false;
2847 }
2848 
P_ChangeSector(Sector * sector,int crush)2849 dd_bool P_ChangeSector(Sector *sector, int crush)
2850 {
2851     pit_changesector_params_t parm;
2852     parm.noFit       = false;
2853 #if __JHEXEN__
2854     parm.crushDamage = crush;
2855 #else
2856     parm.crushDamage = crush > 0? 10 : 0;
2857 #endif
2858 
2859     VALIDCOUNT++;
2860     Sector_TouchingMobjsIterator(sector, PIT_ChangeSector, &parm);
2861 
2862     return parm.noFit;
2863 }
2864 
P_HandleSectorHeightChange(int sectorIdx)2865 void P_HandleSectorHeightChange(int sectorIdx)
2866 {
2867     P_ChangeSector((Sector *)P_ToPtr(DMU_SECTOR, sectorIdx), false /*don't crush*/);
2868 }
2869 
P_IterateThinkers(thinkfunc_t func,const std::function<int (thinker_t *)> & callback)2870 int P_IterateThinkers(thinkfunc_t func, const std::function<int(thinker_t *)> &callback)
2871 {
2872     // Helper to convert the std::function to a basic C function pointer for the API call.
2873     struct IterContext
2874     {
2875         const std::function<int(thinker_t *)> *func;
2876 
2877         static int callback(thinker_t *thinker, void *ptr)
2878         {
2879             auto *context = reinterpret_cast<IterContext *>(ptr);
2880             return (*context->func)(thinker);
2881         }
2882     };
2883 
2884     IterContext context{&callback};
2885     return Thinker_Iterate(func, IterContext::callback, &context);
2886 }
2887 
2888 #if defined (__JHERETIC__) || defined(__JHEXEN__)
2889 
P_TestMobjLocation(mobj_t * mo)2890 dd_bool P_TestMobjLocation(mobj_t *mo)
2891 {
2892     int const oldFlags = mo->flags;
2893 
2894     mo->flags &= ~MF_PICKUP;
2895     if(!P_CheckPositionXY(mo, mo->origin[VX], mo->origin[VY]))
2896     {
2897         mo->flags = oldFlags;
2898         return false;
2899     }
2900     mo->flags = oldFlags;
2901 
2902     // XY is ok, now check Z
2903     return mo->origin[VZ] >= mo->floorZ && (mo->origin[VZ] + mo->height) <= mo->ceilingZ;
2904 }
2905 
2906 struct ptr_boucetraverse_params_t {
2907     mobj_t *bounceMobj;
2908     Line *  bestLine;
2909     coord_t bestDistance;
2910 };
2911 
PTR_BounceTraverse(Intercept const * icpt,void * context)2912 static int PTR_BounceTraverse(Intercept const *icpt, void *context)
2913 {
2914     DENG_ASSERT(icpt->type == ICPT_LINE);
2915 
2916     ptr_boucetraverse_params_t &parm = *static_cast<ptr_boucetraverse_params_t *>(context);
2917 
2918     Line *line = icpt->line;
2919     if (!P_GetPtrp(line, DMU_FRONT_SECTOR) || !P_GetPtrp(line, DMU_BACK_SECTOR))
2920     {
2921         if (Line_PointOnSide(line, parm.bounceMobj->origin) < 0)
2922         {
2923             return false; // Don't hit the back side.
2924         }
2925         goto bounceblocking;
2926     }
2927 
2928     Interceptor_AdjustOpening(icpt->trace, line);
2929 
2930     if (Interceptor_Opening(icpt->trace)->range < parm.bounceMobj->height)
2931     {
2932         goto bounceblocking; // Doesn't fit.
2933     }
2934     if (Interceptor_Opening(icpt->trace)->top - parm.bounceMobj->origin[VZ] <
2935         parm.bounceMobj->height)
2936     {
2937         goto bounceblocking; // Mobj is too high...
2938     }
2939     if (parm.bounceMobj->origin[VZ] - Interceptor_Opening(icpt->trace)->bottom < 0)
2940     {
2941         goto bounceblocking; // Mobj is too low...
2942     }
2943     // This line doesn't block movement...
2944     return false;
2945 
2946     // The line does block movement, see if it is closer than best so far.
2947 bounceblocking:
2948     if (icpt->distance < parm.bestDistance)
2949     {
2950         parm.bestDistance = icpt->distance;
2951         parm.bestLine     = line;
2952     }
2953     return false;
2954 }
2955 
P_BounceWall(mobj_t * mo)2956 dd_bool P_BounceWall(mobj_t *mo)
2957 {
2958     if (!mo) return false;
2959 
2960     // Trace a line from the origin to the would be destination point (which is
2961     // apparently not reachable) to find a line from which we'll calculate the
2962     // inverse "bounce" vector.
2963     vec2d_t leadPos = {mo->origin[VX] + (mo->mom[MX] > 0 ? mo->radius : -mo->radius),
2964                        mo->origin[VY] + (mo->mom[MY] > 0 ? mo->radius : -mo->radius)};
2965     vec2d_t destPos;
2966     V2d_Sum(destPos, leadPos, mo->mom);
2967 
2968     ptr_boucetraverse_params_t parm;
2969     parm.bounceMobj   = mo;
2970     parm.bestLine     = 0;
2971     parm.bestDistance = 1; // Intercept distances are normalized [0..1]
2972 
2973     P_PathTraverse2(leadPos, destPos, PTF_LINE, PTR_BounceTraverse, &parm);
2974 
2975     if (parm.bestLine)
2976     {
2977 //        fprintf(stderr, "mo %p: bouncing off line %p, dist=%f\n",
2978 //                mo, parm.bestLine, parm.bestDistance);
2979 
2980         int const side = Line_PointOnSide(parm.bestLine, mo->origin) < 0;
2981         vec2d_t   lineDirection;
2982         P_GetDoublepv(parm.bestLine, DMU_DXY, lineDirection);
2983 
2984         angle_t lineAngle  = M_PointToAngle(lineDirection) + (side ? ANG180 : 0);
2985         angle_t moveAngle  = M_PointToAngle(mo->mom);
2986         angle_t deltaAngle = (2 * lineAngle) - moveAngle;
2987 
2988         coord_t moveLen = M_ApproxDistance(mo->mom[MX], mo->mom[MY]) * 0.75f /*Friction*/;
2989         if (moveLen < 1) moveLen = 2;
2990 
2991         uint an = deltaAngle >> ANGLETOFINESHIFT;
2992         V2d_Set(mo->mom, moveLen * FIX2FLT(finecosine[an]), moveLen * FIX2FLT(finesine[an]));
2993 
2994 #if defined (__JHERETIC__)
2995         // Reduce momentum.
2996         mo->mom[MX] *= 0.9;
2997         mo->mom[MY] *= 0.9;
2998 
2999         // The same sound for all wall-bouncing things... Using an action function might be
3000         // a better idea.
3001         S_StartSound(SFX_BOUNCE, mo);
3002 #endif
3003         return true;
3004     }
3005     return false;
3006 }
3007 
3008 #endif
3009 
3010 #if __JHEXEN__
PIT_ThrustStompThing(mobj_t * thing,void * context)3011 static int PIT_ThrustStompThing(mobj_t *thing, void *context)
3012 {
3013     mobj_t *tsThing = static_cast<mobj_t *>(context);
3014 
3015     // Don't clip against self.
3016     if(thing == tsThing)
3017     {
3018         return false;
3019     }
3020 
3021     if(!(thing->flags & MF_SHOOTABLE))
3022     {
3023         return false;
3024     }
3025 
3026     coord_t blockdist = thing->radius + tsThing->radius;
3027     if(fabs(thing->origin[VX] - tsThing->origin[VX]) >= blockdist ||
3028        fabs(thing->origin[VY] - tsThing->origin[VY]) >= blockdist ||
3029        (thing->origin[VZ] > tsThing->origin[VZ] + tsThing->height))
3030     {
3031         return false; // Didn't hit it.
3032     }
3033 
3034     P_DamageMobj(thing, tsThing, tsThing, 10001, false);
3035     tsThing->args[1] = 1; // Mark thrust thing as bloody.
3036 
3037     return false;
3038 }
3039 
P_ThrustSpike(mobj_t * mobj)3040 void P_ThrustSpike(mobj_t *mobj)
3041 {
3042     if(!mobj) return;
3043 
3044     coord_t const radius = mobj->info->radius + MAXRADIUS;
3045     AABoxd const box(mobj->origin[VX] - radius, mobj->origin[VY] - radius,
3046                      mobj->origin[VX] + radius, mobj->origin[VY] + radius);
3047 
3048     VALIDCOUNT++;
3049     Mobj_BoxIterator(&box, PIT_ThrustStompThing, mobj);
3050 }
3051 
3052 struct pit_checkonmobjz_params_t
3053 {
3054     mobj_t *riderMobj;
3055     mobj_t *mountMobj;
3056 };
3057 
3058 struct SavedPhysicalState
3059 {
3060     coord_t origin[3];
3061     coord_t mom[3];
3062 
SavedPhysicalStateSavedPhysicalState3063     SavedPhysicalState(mobj_t const *mo)
3064     {
3065         memcpy(origin, mo->origin, sizeof(origin));
3066         memcpy(mom, mo->mom, sizeof(mom));
3067     }
3068 
restoreSavedPhysicalState3069     void restore(mobj_t *mo) const
3070     {
3071         memcpy(mo->origin, origin, sizeof(origin));
3072         memcpy(mo->mom, mom, sizeof(mom));
3073     }
3074 };
3075 
3076 /// @return  @c false= Continue iteration.
PIT_CheckOnMobjZ(mobj_t * cand,void * context)3077 static int PIT_CheckOnMobjZ(mobj_t *cand, void *context)
3078 {
3079     pit_checkonmobjz_params_t &parm = *static_cast<pit_checkonmobjz_params_t *>(context);
3080 
3081     // Can't ride oneself.
3082     if(cand == parm.riderMobj)
3083     {
3084         return false;
3085     }
3086 
3087     if(!(cand->flags & (MF_SOLID | MF_SPECIAL | MF_SHOOTABLE)))
3088     {
3089         return false; // Can't hit thing.
3090     }
3091 
3092     coord_t blockdist = cand->radius + parm.riderMobj->radius;
3093     if(fabs(cand->origin[VX] - parm.riderMobj->origin[VX]) >= blockdist ||
3094        fabs(cand->origin[VY] - parm.riderMobj->origin[VY]) >= blockdist)
3095     {
3096         return false; // Didn't hit thing.
3097     }
3098 
3099     if(IS_CLIENT)
3100     {
3101         // Players must not ride their clmobjs.
3102         if(Mobj_IsPlayer(parm.riderMobj))
3103         {
3104             if(cand == ClPlayer_ClMobj(parm.riderMobj->player - players))
3105             {
3106                 return false;
3107             }
3108         }
3109     }
3110 
3111     // Above or below?
3112     if(parm.riderMobj->origin[VZ] > cand->origin[VZ] + cand->height)
3113     {
3114         return false;
3115     }
3116     else if(parm.riderMobj->origin[VZ] + parm.riderMobj->height < cand->origin[VZ])
3117     {
3118         return false;
3119     }
3120 
3121     if(cand->flags & MF_SOLID)
3122     {
3123         parm.mountMobj = cand;
3124     }
3125 
3126     return (cand->flags & MF_SOLID) != 0;
3127 }
3128 
P_CheckOnMobj(mobj_t * mo)3129 mobj_t *P_CheckOnMobj(mobj_t *mo)
3130 {
3131     if(!mo) return 0;
3132     if(P_MobjIsCamera(mo)) return 0;
3133 
3134     // Players' clmobjs shouldn't do any on-mobj logic; the real player mobj
3135     // will interact with (cl)mobjs.
3136     if(Mobj_IsPlayerClMobj(mo)) return 0;
3137 
3138     // Save physical state so we can undo afterwards -- this is only a check.
3139     SavedPhysicalState savedState(mo);
3140 
3141     // Adjust Z-origin.
3142     mo->origin[VZ] += mo->mom[MZ];
3143 
3144     if((mo->flags & MF_FLOAT) && mo->target)
3145     {
3146         // Float down towards target if too close.
3147         if(!(mo->flags & MF_SKULLFLY) && !(mo->flags & MF_INFLOAT))
3148         {
3149             coord_t dist = M_ApproxDistance(mo->origin[VX] - mo->target->origin[VX],
3150                                             mo->origin[VY] - mo->target->origin[VY]);
3151 
3152             coord_t delta = mo->target->origin[VZ] + (mo->height / 2) - mo->origin[VZ];
3153 
3154             if(delta < 0 && dist < -(delta * 3))
3155             {
3156                 mo->origin[VZ] -= FLOATSPEED;
3157             }
3158             else if(delta > 0 && dist < (delta * 3))
3159             {
3160                 mo->origin[VZ] += FLOATSPEED;
3161             }
3162         }
3163     }
3164 
3165     if(Mobj_IsPlayer(mo) && (mo->flags2 & MF2_FLY) && !(mo->origin[VZ] <= mo->floorZ))
3166     {
3167         if(mapTime & 2)
3168         {
3169             mo->origin[VZ] += FIX2FLT(finesine[(FINEANGLES / 20 * mapTime >> 2) & FINEMASK]);
3170         }
3171     }
3172 
3173     // Clip momentum.
3174 
3175     // Hit the floor?
3176     bool hitFloor = mo->origin[VZ] <= mo->floorZ;
3177     if(hitFloor)
3178     {
3179         mo->origin[VZ] = mo->floorZ;
3180         if(mo->mom[MZ] < 0)
3181         {
3182             mo->mom[MZ] = 0;
3183         }
3184 
3185         if(mo->flags & MF_SKULLFLY)
3186         {
3187             mo->mom[MZ] = -mo->mom[MZ]; // The skull slammed into something
3188         }
3189     }
3190     else if(mo->flags2 & MF2_LOGRAV)
3191     {
3192         if(IS_ZERO(mo->mom[MZ]))
3193         {
3194             mo->mom[MZ] = -(P_GetGravity() / 32) * 2;
3195         }
3196         else
3197         {
3198             mo->mom[MZ] -= P_GetGravity() / 32;
3199         }
3200     }
3201     else if(!(mo->flags & MF_NOGRAVITY))
3202     {
3203         if(IS_ZERO(mo->mom[MZ]))
3204         {
3205             mo->mom[MZ] = -P_GetGravity() * 2;
3206         }
3207         else
3208         {
3209             mo->mom[MZ] -= P_GetGravity();
3210         }
3211     }
3212 
3213     if(!(hitFloor && P_GetState(mobjtype_t(mo->type), SN_CRASH) && (mo->flags & MF_CORPSE)))
3214     {
3215         if(mo->origin[VZ] + mo->height > mo->ceilingZ)
3216         {
3217             mo->origin[VZ] = mo->ceilingZ - mo->height;
3218 
3219             if(mo->mom[MZ] > 0)
3220             {
3221                 mo->mom[MZ] = 0;
3222             }
3223 
3224             if(mo->flags & MF_SKULLFLY)
3225             {
3226                 mo->mom[MZ] = -mo->mom[MZ]; // The skull slammed into something.
3227             }
3228         }
3229     }
3230 
3231     /*tmCeilingLine = tmFloorLine = 0;
3232 
3233     // The base floor/ceiling is from the BSP leaf that contains the point.
3234     // Any contacted lines the step closer together will adjust them.
3235 
3236     Sector *newSector = Sector_AtPoint_FixedPrecision(mo->origin);
3237 
3238     tmFloorZ        = tmDropoffZ = P_GetDoublep(newSector, DMU_FLOOR_HEIGHT);
3239     tmCeilingZ      = P_GetDoublep(newSector, DMU_CEILING_HEIGHT);
3240     tmFloorMaterial = (Material *)P_GetPtrp(newSector, DMU_FLOOR_MATERIAL);
3241 
3242     IterList_Clear(spechit);*/
3243 
3244     if(!(mo->flags & MF_NOCLIP))
3245     {
3246         int blockdist = mo->radius + MAXRADIUS;
3247         AABoxd aaBox(mo->origin[VX] - blockdist, mo->origin[VY] - blockdist,
3248                      mo->origin[VX] + blockdist, mo->origin[VY] + blockdist);
3249 
3250         pit_checkonmobjz_params_t parm;
3251         parm.riderMobj = mo;
3252         parm.mountMobj = 0;
3253 
3254         VALIDCOUNT++;
3255         if(Mobj_BoxIterator(&aaBox, PIT_CheckOnMobjZ, &parm))
3256         {
3257             savedState.restore(mo);
3258             return parm.mountMobj;
3259         }
3260     }
3261 
3262     savedState.restore(mo);
3263     return 0;
3264 }
3265 
usePuzzleItemFailSound(mobj_t * user)3266 static sfxenum_t usePuzzleItemFailSound(mobj_t *user)
3267 {
3268     if(Mobj_IsPlayer(user))
3269     {
3270         /// @todo Get this from ClassInfo.
3271         switch(user->player->class_)
3272         {
3273         case PCLASS_FIGHTER: return SFX_PUZZLE_FAIL_FIGHTER;
3274         case PCLASS_CLERIC:  return SFX_PUZZLE_FAIL_CLERIC;
3275         case PCLASS_MAGE:    return SFX_PUZZLE_FAIL_MAGE;
3276 
3277         default: break;
3278         }
3279     }
3280     return SFX_NONE;
3281 }
3282 
3283 struct ptr_puzzleitemtraverse_params_t
3284 {
3285     mobj_t *useMobj;
3286     int itemType;
3287     bool activated;
3288 };
3289 
PTR_PuzzleItemTraverse(Intercept const * icpt,void * context)3290 static int PTR_PuzzleItemTraverse(Intercept const *icpt, void *context)
3291 {
3292     int const USE_PUZZLE_ITEM_SPECIAL = 129;
3293 
3294     auto &parm = *static_cast<ptr_puzzleitemtraverse_params_t *>(context);
3295 
3296     switch(icpt->type)
3297     {
3298     case ICPT_LINE: {
3299         xline_t *xline = P_ToXLine(icpt->line);
3300         DENG2_ASSERT(xline);
3301 
3302         if(xline->special != USE_PUZZLE_ITEM_SPECIAL)
3303         {
3304             // Items cannot be used through a wall.
3305             if(!Interceptor_AdjustOpening(icpt->trace, icpt->line))
3306             {
3307                 // No opening.
3308                 S_StartSound(usePuzzleItemFailSound(parm.useMobj), parm.useMobj);
3309                 return true;
3310             }
3311 
3312             return false;
3313         }
3314 
3315         // Don't use the back side of lines.
3316         if(Line_PointOnSide(icpt->line, parm.useMobj->origin) < 0)
3317         {
3318             return true;
3319         }
3320 
3321         // Item type must match.
3322         if(parm.itemType != xline->arg1)
3323         {
3324             return true;
3325         }
3326 
3327         // A known ACScript?
3328         if(gfw_Session()->acsSystem().hasScript(xline->arg2))
3329         {
3330             /// @todo fixme: Really interpret the first byte of xline_t::flags as a
3331             /// script argument? (I wonder if any scripts rely on this). -ds
3332             gfw_Session()->acsSystem()
3333                     .script(xline->arg2)
3334                         .start(acs::Script::Args(&xline->arg3, 4/*3*/), parm.useMobj,
3335                                icpt->line, 0);
3336         }
3337         xline->special = 0;
3338         parm.activated = true;
3339 
3340         // Stop searching.
3341         return true; }
3342 
3343     case ICPT_MOBJ: {
3344         DENG2_ASSERT(icpt->mobj);
3345         mobj_t &mob = *icpt->mobj;
3346 
3347         // Special id must match.
3348         if(mob.special != USE_PUZZLE_ITEM_SPECIAL)
3349         {
3350             return false;
3351         }
3352 
3353         // Item type must match.
3354         if(mob.args[0] != parm.itemType)
3355         {
3356             return false;
3357         }
3358 
3359         // A known ACScript?
3360         if(gfw_Session()->acsSystem().hasScript(mob.args[1]))
3361         {
3362             /// @todo fixme: Really interpret the first byte of mobj_t::turnTime as a
3363             /// script argument? (I wonder if any scripts rely on this). -ds
3364             gfw_Session()->acsSystem()
3365                     .script(mob.args[1])
3366                         .start(acs::Script::Args(&mob.args[2], 4/*3*/), parm.useMobj,
3367                                nullptr, 0);
3368         }
3369         mob.special = 0;
3370         parm.activated = true;
3371 
3372         // Stop searching.
3373         return true; }
3374 
3375     default: DENG2_ASSERT(!"Unknown intercept type");
3376         return false;
3377     }
3378 }
3379 
P_UsePuzzleItem(player_t * player,int itemType)3380 dd_bool P_UsePuzzleItem(player_t *player, int itemType)
3381 {
3382     DENG_ASSERT(player != 0);
3383 
3384     mobj_t *mobj = player->plr->mo;
3385     if(!mobj) return false; // Huh?
3386 
3387     ptr_puzzleitemtraverse_params_t parm;
3388     parm.useMobj   = mobj;
3389     parm.itemType  = itemType;
3390     parm.activated = false;
3391 
3392     uint an = mobj->angle >> ANGLETOFINESHIFT;
3393     vec2d_t farUsePoint = { mobj->origin[VX] + FIX2FLT(USERANGE * finecosine[an]),
3394                             mobj->origin[VY] + FIX2FLT(USERANGE * finesine  [an]) };
3395 
3396     P_PathTraverse(mobj->origin, farUsePoint, PTR_PuzzleItemTraverse, &parm);
3397 
3398     if(!parm.activated)
3399     {
3400         P_SetYellowMessage(player, TXT_USEPUZZLEFAILED);
3401     }
3402 
3403     return parm.activated;
3404 }
3405 #endif
3406 
3407 #ifdef __JHEXEN__
3408 struct countmobjoftypeparams_t
3409 {
3410     mobjtype_t type;
3411     int count;
3412 };
3413 
countMobjOfType(thinker_t * th,void * context)3414 static int countMobjOfType(thinker_t *th, void *context)
3415 {
3416     countmobjoftypeparams_t *params = (countmobjoftypeparams_t *) context;
3417     mobj_t *mo = (mobj_t *) th;
3418 
3419     // Does the type match?
3420     if(mo->type != params->type)
3421         return false; // Continue iteration.
3422 
3423     // Minimum health requirement?
3424     if((mo->flags & MF_COUNTKILL) && mo->health <= 0)
3425         return false; // Continue iteration.
3426 
3427     params->count++;
3428 
3429     return false; // Continue iteration.
3430 }
3431 
P_MobjCount(int type,int tid)3432 int P_MobjCount(int type, int tid)
3433 {
3434     if(!type && !tid) return 0;
3435 
3436     mobjtype_t moType = TranslateThingType[type];
3437 
3438     if(tid)
3439     {
3440         // Count mobjs by TID.
3441         int count = 0;
3442         mobj_t *mo;
3443         int searcher = -1;
3444 
3445         while((mo = P_FindMobjFromTID(tid, &searcher)))
3446         {
3447             if(type == 0)
3448             {
3449                 // Just count TIDs.
3450                 count++;
3451             }
3452             else if(moType == mo->type)
3453             {
3454                 // Don't count dead monsters.
3455                 if((mo->flags & MF_COUNTKILL) && mo->health <= 0)
3456                 {
3457                     continue;
3458                 }
3459                 count++;
3460             }
3461         }
3462         return count;
3463     }
3464 
3465     // Count mobjs by type only.
3466     countmobjoftypeparams_t params;
3467     params.type  = moType;
3468     params.count = 0;
3469     Thinker_Iterate(P_MobjThinker, countMobjOfType, &params);
3470 
3471     return params.count;
3472 }
3473 #endif // __JHEXEN__
3474