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, ¶ms);
3470
3471 return params.count;
3472 }
3473 #endif // __JHEXEN__
3474