· 4 years ago · Aug 21, 2021, 09:26 PM
1// Emacs style mode select -*- C++ -*-
2//-----------------------------------------------------------------------------
3//
4// $Id:$
5//
6// Copyright (C) 1993-1996 by id Software, Inc.
7//
8// This source is available for distribution and/or modification
9// only under the terms of the DOOM Source Code License as
10// published by id Software. All rights reserved.
11//
12// The source is distributed in the hope that it will be useful,
13// but WITHOUT ANY WARRANTY; without even the implied warranty of
14// FITNESS FOR A PARTICULAR PURPOSE. See the DOOM Source Code License
15// for more details.
16//
17// $Log:$
18//
19// DESCRIPTION:
20// Movement, collision handling.
21// Shooting and aiming.
22//
23//-----------------------------------------------------------------------------
24
25#include <stdlib.h>
26#include <math.h>
27
28#include "templates.h"
29
30#include "m_bbox.h"
31#include "m_random.h"
32#include "i_system.h"
33#include "c_dispatch.h"
34
35#include "doomdef.h"
36#include "p_local.h"
37#include "p_lnspec.h"
38#include "p_effect.h"
39#include "p_terrain.h"
40#include "p_trace.h"
41#include "p_3dmidtex.h"
42
43#include "s_sound.h"
44#include "decallib.h"
45
46// State.
47#include "doomstat.h"
48#include "r_state.h"
49
50#include "gi.h"
51
52#include "a_sharedglobal.h"
53#include "p_conversation.h"
54#include "r_data/r_translate.h"
55#include "g_level.h"
56// [BB] New #includes.
57#include "deathmatch.h"
58#include "team.h"
59#include "network.h"
60#include "g_game.h"
61#include "cooperative.h"
62#include "sv_commands.h"
63#include "cl_demo.h"
64#include "cl_main.h"
65#include "gamemode.h"
66#include "unlagged.h"
67#include "d_netinf.h"
68#include "v_video.h"
69
70// [BB] Helper function to handle ZADF_UNBLOCK_PLAYERS.
71bool P_CheckUnblock(AActor* pActor1, AActor* pActor2)
72{
73 if ((pActor1 == NULL) || (pActor2 == NULL))
74 return false;
75
76 if ((pActor1->IsKindOf(RUNTIME_CLASS(APlayerPawn)) == false)
77 || (pActor2->IsKindOf(RUNTIME_CLASS(APlayerPawn)) == false))
78 {
79 return false;
80 }
81
82 // [TP] Unblock if sv_unblockallies is true and these are teammates.
83 if ((zadmflags & ZADF_UNBLOCK_ALLIES) && (pActor1->IsTeammate(pActor2)))
84 return true;
85
86 if (zadmflags & ZADF_UNBLOCK_PLAYERS)
87 return true;
88
89 return false;
90}
91
92CVAR(Bool, cl_bloodsplats, true, CVAR_ARCHIVE)
93CVAR(Int, sv_smartaim, 0, CVAR_ARCHIVE | CVAR_SERVERINFO)
94CVAR(Bool, cl_doautoaim, false, CVAR_ARCHIVE)
95
96static void CheckForPushSpecial(line_t* line, int side, AActor* mobj, bool windowcheck);
97static void SpawnShootDecal(AActor* t1, const FTraceResults& trace);
98static void SpawnDeepSplash(AActor* t1, const FTraceResults& trace, AActor* puff,
99 fixed_t vx, fixed_t vy, fixed_t vz, fixed_t shootz, bool ffloor = false);
100
101static FRandom pr_tracebleed("TraceBleed");
102static FRandom pr_checkthing("CheckThing");
103static FRandom pr_lineattack("LineAttack");
104static FRandom pr_crunch("DoCrunch");
105
106static int tmunstuck; /* killough 8/1/98: whether to allow unsticking */
107
108// keep track of special lines as they are hit,
109// but don't process them until the move is proven valid
110TArray<line_t*> spechit;
111
112// Temporary holder for thing_sectorlist threads
113msecnode_t* sector_list = NULL; // phares 3/16/98
114
115//==========================================================================
116//
117// PIT_FindFloorCeiling
118//
119// only3d set means to only check against 3D floors and midtexes.
120//
121//==========================================================================
122
123static bool PIT_FindFloorCeiling(line_t* ld, const FBoundingBox& box, FCheckPosition& tmf, int flags)
124{
125 if (box.Right() <= ld->bbox[BOXLEFT]
126 || box.Left() >= ld->bbox[BOXRIGHT]
127 || box.Top() <= ld->bbox[BOXBOTTOM]
128 || box.Bottom() >= ld->bbox[BOXTOP])
129 return true;
130
131 if (box.BoxOnLineSide(ld) != -1)
132 return true;
133
134 // A line has been hit
135
136 if (!ld->backsector)
137 { // One sided line
138 return true;
139 }
140
141 fixed_t sx, sy;
142 FLineOpening open;
143
144 // set openrange, opentop, openbottom
145 if ((((ld->frontsector->floorplane.a | ld->frontsector->floorplane.b) |
146 (ld->backsector->floorplane.a | ld->backsector->floorplane.b) |
147 (ld->frontsector->ceilingplane.a | ld->frontsector->ceilingplane.b) |
148 (ld->backsector->ceilingplane.a | ld->backsector->ceilingplane.b)) == 0)
149 && ld->backsector->e->XFloor.ffloors.Size() == 0 && ld->frontsector->e->XFloor.ffloors.Size() == 0)
150 {
151 P_LineOpening(open, tmf.thing, ld, sx = tmf.x, sy = tmf.y, tmf.x, tmf.y, flags);
152 }
153 else
154 { // Find the point on the line closest to the actor's center, and use
155 // that to calculate openings
156 double dx = ld->dx;
157 double dy = ld->dy;
158 fixed_t r = xs_CRoundToInt(((double)(tmf.x - ld->v1->x) * dx +
159 (double)(tmf.y - ld->v1->y) * dy) /
160 (dx * dx + dy * dy) * 16777216.f);
161 if (r <= 0)
162 {
163 P_LineOpening(open, tmf.thing, ld, sx = ld->v1->x, sy = ld->v1->y, tmf.x, tmf.y, flags);
164 }
165 else if (r >= (1 << 24))
166 {
167 P_LineOpening(open, tmf.thing, ld, sx = ld->v2->x, sy = ld->v2->y, tmf.thing->x, tmf.thing->y, flags);
168 }
169 else
170 {
171 P_LineOpening(open, tmf.thing, ld, sx = ld->v1->x + MulScale24(r, ld->dx),
172 sy = ld->v1->y + MulScale24(r, ld->dy), tmf.x, tmf.y, flags);
173 }
174 }
175
176 // adjust floor / ceiling heights
177 if (open.top < tmf.ceilingz)
178 {
179 tmf.ceilingz = open.top;
180 }
181
182 if (open.bottom > tmf.floorz)
183 {
184 tmf.floorz = open.bottom;
185 if (open.bottomsec != NULL) tmf.floorsector = open.bottomsec;
186 tmf.touchmidtex = open.touchmidtex;
187 tmf.abovemidtex = open.abovemidtex;
188 }
189 else if (open.bottom == tmf.floorz)
190 {
191 tmf.touchmidtex |= open.touchmidtex;
192 tmf.abovemidtex |= open.abovemidtex;
193 }
194
195 if (open.lowfloor < tmf.dropoffz)
196 tmf.dropoffz = open.lowfloor;
197
198 return true;
199}
200
201
202//==========================================================================
203//
204//
205//
206//==========================================================================
207
208void P_GetFloorCeilingZ(FCheckPosition& tmf, int flags)
209{
210 sector_t* sec;
211 if (!(flags & FFCF_ONLYSPAWNPOS))
212 {
213 sec = !(flags & FFCF_SAMESECTOR) ? P_PointInSector(tmf.x, tmf.y) : tmf.thing->Sector;
214 tmf.floorsector = sec;
215 tmf.ceilingsector = sec;
216
217 tmf.floorz = tmf.dropoffz = sec->floorplane.ZatPoint(tmf.x, tmf.y);
218 tmf.ceilingz = sec->ceilingplane.ZatPoint(tmf.x, tmf.y);
219 tmf.floorpic = sec->GetTexture(sector_t::floor);
220 tmf.ceilingpic = sec->GetTexture(sector_t::ceiling);
221 }
222 else
223 {
224 sec = tmf.thing->Sector;
225 }
226
227#ifdef _3DFLOORS
228 for (unsigned int i = 0; i < sec->e->XFloor.ffloors.Size(); i++)
229 {
230 F3DFloor* rover = sec->e->XFloor.ffloors[i];
231
232 if (!(rover->flags & FF_SOLID) || !(rover->flags & FF_EXISTS)) continue;
233
234 fixed_t ff_bottom = rover->bottom.plane->ZatPoint(tmf.x, tmf.y);
235 fixed_t ff_top = rover->top.plane->ZatPoint(tmf.x, tmf.y);
236
237 if (ff_top > tmf.floorz)
238 {
239 // [BB] Added FFCF_INCLUDE3DFLOORS as workaround for client side spawning.
240 if (ff_top <= tmf.z || ((!(flags & FFCF_3DRESTRICT) || (flags & FFCF_INCLUDE3DFLOORS)) && (tmf.thing != NULL && ff_bottom < tmf.z && ff_top < tmf.z + tmf.thing->MaxStepHeight)))
241 {
242 tmf.dropoffz = tmf.floorz = ff_top;
243 tmf.floorpic = *rover->top.texture;
244 }
245 }
246 if (ff_bottom <= tmf.ceilingz && ff_bottom > tmf.z + tmf.thing->height)
247 {
248 tmf.ceilingz = ff_bottom;
249 tmf.ceilingpic = *rover->bottom.texture;
250 }
251 }
252#endif
253}
254
255//==========================================================================
256//
257// P_FindFloorCeiling
258//
259//==========================================================================
260
261void P_FindFloorCeiling(AActor* actor, int flags)
262{
263 FCheckPosition tmf;
264
265 tmf.thing = actor;
266 tmf.x = actor->x;
267 tmf.y = actor->y;
268 tmf.z = actor->z;
269
270 if (flags & FFCF_ONLYSPAWNPOS)
271 {
272 flags |= FFCF_3DRESTRICT;
273 }
274 if (!(flags & FFCF_ONLYSPAWNPOS))
275 {
276 P_GetFloorCeilingZ(tmf, flags);
277 }
278 else
279 {
280 tmf.ceilingsector = tmf.floorsector = actor->Sector;
281
282 tmf.floorz = tmf.dropoffz = actor->floorz;
283 tmf.ceilingz = actor->ceilingz;
284 tmf.floorpic = actor->floorpic;
285 tmf.ceilingpic = actor->ceilingpic;
286 P_GetFloorCeilingZ(tmf, flags);
287 }
288 actor->floorz = tmf.floorz;
289 actor->dropoffz = tmf.dropoffz;
290 actor->ceilingz = tmf.ceilingz;
291 actor->floorpic = tmf.floorpic;
292 actor->floorsector = tmf.floorsector;
293 actor->ceilingpic = tmf.ceilingpic;
294 actor->ceilingsector = tmf.ceilingsector;
295
296 FBoundingBox box(tmf.x, tmf.y, actor->radius);
297
298 tmf.touchmidtex = false;
299 tmf.abovemidtex = false;
300 validcount++;
301
302 FBlockLinesIterator it(box);
303 line_t* ld;
304
305 while ((ld = it.Next()))
306 {
307 PIT_FindFloorCeiling(ld, box, tmf, flags);
308 }
309
310 if (tmf.touchmidtex) tmf.dropoffz = tmf.floorz;
311
312 if (!(flags & FFCF_ONLYSPAWNPOS) || (tmf.abovemidtex && (tmf.floorz <= actor->z)))
313 {
314 actor->floorz = tmf.floorz;
315 actor->dropoffz = tmf.dropoffz;
316 actor->ceilingz = tmf.ceilingz;
317 actor->floorpic = tmf.floorpic;
318 actor->floorsector = tmf.floorsector;
319 actor->ceilingpic = tmf.ceilingpic;
320 actor->ceilingsector = tmf.ceilingsector;
321 }
322 else
323 {
324 actor->floorsector = actor->ceilingsector = actor->Sector;
325 // [BB] Don't forget to update floorpic and ceilingpic.
326 if (actor->Sector != NULL)
327 {
328 actor->floorpic = actor->Sector->GetTexture(sector_t::floor);
329 actor->ceilingpic = actor->Sector->GetTexture(sector_t::ceiling);
330 }
331 }
332}
333
334//==========================================================================
335//
336// TELEPORT MOVE
337//
338
339//
340// P_TeleportMove
341//
342// [RH] Added telefrag parameter: When true, anything in the spawn spot
343// will always be telefragged, and the move will be successful.
344// Added z parameter. Originally, the thing's z was set *after* the
345// move was made, so the height checking I added for 1.13 could
346// potentially erroneously indicate the move was okay if the thing
347// was being teleported between two non-overlapping height ranges.
348//
349//==========================================================================
350
351bool P_TeleportMove(AActor* thing, fixed_t x, fixed_t y, fixed_t z, bool telefrag)
352{
353 FCheckPosition tmf;
354 sector_t* oldsec = thing->Sector;
355
356 // kill anything occupying the position
357
358
359 // The base floor/ceiling is from the subsector that contains the point.
360 // Any contacted lines the step closer together will adjust them.
361 tmf.thing = thing;
362 tmf.x = x;
363 tmf.y = y;
364 tmf.z = z;
365 tmf.touchmidtex = false;
366 tmf.abovemidtex = false;
367 P_GetFloorCeilingZ(tmf, 0);
368
369 spechit.Clear();
370
371 bool StompAlwaysFrags = ((thing->flags2 & MF2_TELESTOMP) || (level.flags & LEVEL_MONSTERSTELEFRAG) || telefrag) && !(thing->flags7 & MF7_NOTELESTOMP);
372
373 FBoundingBox box(x, y, thing->radius);
374 FBlockLinesIterator it(box);
375 line_t* ld;
376
377 // P_LineOpening requires the thing's z to be the destination z in order to work.
378 fixed_t savedz = thing->z;
379 thing->z = z;
380 while ((ld = it.Next()))
381 {
382 PIT_FindFloorCeiling(ld, box, tmf, 0);
383 }
384 thing->z = savedz;
385
386 if (tmf.touchmidtex) tmf.dropoffz = tmf.floorz;
387
388 FBlockThingsIterator it2(FBoundingBox(x, y, thing->radius));
389 AActor* th;
390
391 while ((th = it2.Next()))
392 {
393 // [BC] Don't allow spectators to telefrag/be telefragged.
394 if (((th->player) && (th->player->bSpectating)) || ((thing->player) && (thing->player->bSpectating)))
395 continue;
396
397 if (!(th->flags & MF_SHOOTABLE))
398 continue;
399
400 // don't clip against self
401 if (th == thing)
402 continue;
403
404 fixed_t blockdist = th->radius + tmf.thing->radius;
405 if (abs(th->x - tmf.x) >= blockdist || abs(th->y - tmf.y) >= blockdist)
406 continue;
407
408 if ((th->flags2 | tmf.thing->flags2) & MF2_THRUACTORS)
409 continue;
410
411 if (tmf.thing->flags6 & MF6_THRUSPECIES && tmf.thing->GetSpecies() == th->GetSpecies())
412 continue;
413
414 // [RH] Z-Check
415 // But not if not MF2_PASSMOBJ or MF3_DONTOVERLAP are set!
416 // Otherwise those things would get stuck inside each other.
417 if ((thing->flags2 & MF2_PASSMOBJ || th->flags4 & MF4_ACTLIKEBRIDGE) && !(i_compatflags & COMPATF_NO_PASSMOBJ))
418 {
419 if (!(th->flags3 & thing->flags3 & MF3_DONTOVERLAP))
420 {
421 if (z > th->z + th->height || // overhead
422 z + thing->height < th->z) // underneath
423 continue;
424 }
425 }
426
427 // monsters don't stomp things except on boss level
428 // [RH] Some Heretic/Hexen monsters can telestomp
429 // ... and some items can never be telefragged while others will be telefragged by everything that teleports upon them.
430 if ((StompAlwaysFrags && !(th->flags6 & MF6_NOTELEFRAG)) || (th->flags7 & MF7_ALWAYSTELEFRAG))
431 {
432 // [BC] Damage is never done client-side.
433 if (NETWORK_InClientMode() == false)
434 P_DamageMobj(th, thing, thing, TELEFRAG_DAMAGE, NAME_Telefrag, DMG_THRUSTLESS);
435 continue;
436 }
437 return false;
438 }
439
440 // the move is ok, so link the thing into its new position
441 thing->SetOrigin(x, y, z);
442 thing->floorz = tmf.floorz;
443 thing->ceilingz = tmf.ceilingz;
444 thing->floorsector = tmf.floorsector;
445 thing->floorpic = tmf.floorpic;
446 thing->ceilingsector = tmf.ceilingsector;
447 thing->ceilingpic = tmf.ceilingpic;
448 thing->dropoffz = tmf.dropoffz; // killough 11/98
449 thing->BlockingLine = NULL;
450
451 if (thing->flags2 & MF2_FLOORCLIP)
452 {
453 thing->AdjustFloorClip();
454 }
455
456 if (thing == players[consoleplayer].camera)
457 {
458 R_ResetViewInterpolation();
459 }
460
461 thing->PrevX = x;
462 thing->PrevY = y;
463 thing->PrevZ = z;
464
465 // If this teleport was caused by a move, P_TryMove() will handle the
466 // sector transition messages better than we can here.
467 if (!(thing->flags6 & MF6_INTRYMOVE))
468 {
469 thing->CheckSectorTransition(oldsec);
470 }
471
472 return true;
473}
474
475//==========================================================================
476//
477// [RH] P_PlayerStartStomp
478//
479// Like P_TeleportMove, but it doesn't move anything, and only monsters and other
480// players get telefragged.
481//
482//==========================================================================
483
484void P_PlayerStartStomp(AActor* actor)
485{
486 // [BB] Spectators don't telefrag anything.
487 if (actor->player && actor->player->bSpectating)
488 return;
489
490 AActor* th;
491 FBlockThingsIterator it(FBoundingBox(actor->x, actor->y, actor->radius));
492
493 while ((th = it.Next()))
494 {
495 if (!(th->flags & MF_SHOOTABLE))
496 continue;
497
498 // don't clip against self, and don't kill your own voodoo dolls
499 if (th == actor || (th->player == actor->player && th->player != NULL))
500 continue;
501
502 if (!th->intersects(actor))
503 continue;
504
505 // only kill monsters and other players
506 if (th->player == NULL && !(th->flags3 & MF3_ISMONSTER))
507 continue;
508
509 if (actor->z > th->z + th->height)
510 continue; // overhead
511 if (actor->z + actor->height < th->z)
512 continue; // underneath
513
514 // [BB] ST distinguishes between NAME_SpawnTelefrag and NAME_Telefrag.
515 P_DamageMobj(th, actor, actor, TELEFRAG_DAMAGE, NAME_SpawnTelefrag);
516 }
517}
518
519//==========================================================================
520//
521//
522//
523//==========================================================================
524
525inline fixed_t secfriction(const sector_t* sec)
526{
527 fixed_t friction = Terrains[TerrainTypes[sec->GetTexture(sector_t::floor)]].Friction;
528 return friction != 0 ? friction : sec->friction;
529}
530
531inline fixed_t secmovefac(const sector_t* sec)
532{
533 fixed_t movefactor = Terrains[TerrainTypes[sec->GetTexture(sector_t::floor)]].MoveFactor;
534 return movefactor != 0 ? movefactor : sec->movefactor;
535}
536
537//==========================================================================
538//
539// killough 8/28/98:
540//
541// P_GetFriction()
542//
543// Returns the friction associated with a particular mobj.
544//
545//==========================================================================
546
547int P_GetFriction(const AActor* mo, int* frictionfactor)
548{
549 int friction = ORIG_FRICTION;
550 int movefactor = ORIG_FRICTION_FACTOR;
551 fixed_t newfriction;
552 const msecnode_t* m;
553 const sector_t* sec;
554
555 if (mo->IsNoClip2())
556 {
557 // The default values are fine for noclip2 mode
558 }
559 else if (mo->flags2 & MF2_FLY && mo->flags & MF_NOGRAVITY)
560 {
561 friction = FRICTION_FLY;
562 }
563 else if ((!(mo->flags & MF_NOGRAVITY) && mo->waterlevel > 1) ||
564 (mo->waterlevel == 1 && mo->z > mo->floorz + 6 * FRACUNIT))
565 {
566 friction = secfriction(mo->Sector);
567 movefactor = secmovefac(mo->Sector) >> 1;
568 }
569 else if (var_friction && !(mo->flags & (MF_NOCLIP | MF_NOGRAVITY)))
570 { // When the object is straddling sectors with the same
571 // floor height that have different frictions, use the lowest
572 // friction value (muddy has precedence over icy).
573
574 for (m = mo->touching_sectorlist; m; m = m->m_tnext)
575 {
576 sec = m->m_sector;
577
578#ifdef _3DFLOORS
579 // 3D floors must be checked, too
580 for (unsigned i = 0; i < sec->e->XFloor.ffloors.Size(); i++)
581 {
582 F3DFloor* rover = sec->e->XFloor.ffloors[i];
583 if (!(rover->flags & FF_EXISTS)) continue;
584 if (!(rover->flags & FF_SOLID)) continue;
585
586 // Player must be on top of the floor to be affected...
587 if (mo->z != rover->top.plane->ZatPoint(mo->x, mo->y)) continue;
588 newfriction = secfriction(rover->model);
589 if (newfriction < friction || friction == ORIG_FRICTION)
590 {
591 friction = newfriction;
592 movefactor = secmovefac(rover->model);
593 }
594 }
595#endif
596
597 if (!(sec->special & FRICTION_MASK) &&
598 Terrains[TerrainTypes[sec->GetTexture(sector_t::floor)]].Friction == 0)
599 {
600 continue;
601 }
602 newfriction = secfriction(sec);
603 if ((newfriction < friction || friction == ORIG_FRICTION) &&
604 (mo->z <= sec->floorplane.ZatPoint(mo->x, mo->y) ||
605 (sec->GetHeightSec() != NULL &&
606 mo->z <= sec->heightsec->floorplane.ZatPoint(mo->x, mo->y))))
607 {
608 friction = newfriction;
609 movefactor = secmovefac(sec);
610 }
611 }
612 }
613
614 if (frictionfactor)
615 *frictionfactor = movefactor;
616
617 return friction;
618}
619
620//==========================================================================
621//
622// phares 3/19/98
623// P_GetMoveFactor() returns the value by which the x,y
624// movements are multiplied to add to player movement.
625//
626// killough 8/28/98: rewritten
627//
628//==========================================================================
629
630int P_GetMoveFactor(const AActor* mo, int* frictionp)
631{
632 int movefactor, friction;
633
634 // If the floor is icy or muddy, it's harder to get moving. This is where
635 // the different friction factors are applied to 'trying to move'. In
636 // p_mobj.c, the friction factors are applied as you coast and slow down.
637
638 if ((friction = P_GetFriction(mo, &movefactor)) < ORIG_FRICTION)
639 {
640 // phares 3/11/98: you start off slowly, then increase as
641 // you get better footing
642
643 int velocity = P_AproxDistance(mo->velx, mo->vely);
644
645 if (velocity > MORE_FRICTION_VELOCITY << 2)
646 movefactor <<= 3;
647 else if (velocity > MORE_FRICTION_VELOCITY << 1)
648 movefactor <<= 2;
649 else if (velocity > MORE_FRICTION_VELOCITY)
650 movefactor <<= 1;
651 }
652
653 if (frictionp)
654 *frictionp = friction;
655
656 return movefactor;
657}
658
659//
660// MOVEMENT ITERATOR FUNCTIONS
661//
662
663// [BB] This old function still lives on in the bot code...
664int P_BoxOnLineSide(const fixed_t* tmbox, const line_t* ld);
665// [BC] ugh old code for people who just have to have doom2.exe style movement.
666/* killough 8/1/98: used to test intersection between thing and line
667 * assuming NO movement occurs -- used to avoid sticky situations.
668 */
669static int untouched(line_t* ld, FCheckPosition& tm)
670{
671 fixed_t x, y, tmbbox[4];
672 return
673 (tmbbox[BOXRIGHT] = (x = tm.thing->x) + tm.thing->radius) <= ld->bbox[BOXLEFT] ||
674 (tmbbox[BOXLEFT] = x - tm.thing->radius) >= ld->bbox[BOXRIGHT] ||
675 (tmbbox[BOXTOP] = (y = tm.thing->y) + tm.thing->radius) <= ld->bbox[BOXBOTTOM] ||
676 (tmbbox[BOXBOTTOM] = y - tm.thing->radius) >= ld->bbox[BOXTOP] ||
677 P_BoxOnLineSide(tmbbox, ld) != -1;
678}
679
680//==========================================================================
681//
682//
683// PIT_CheckLine
684// Adjusts tmfloorz and tmceilingz as lines are contacted
685//
686//
687//==========================================================================
688
689static // killough 3/26/98: make static
690bool PIT_CheckLine(line_t* ld, const FBoundingBox& box, FCheckPosition& tm)
691{
692 bool rail = false;
693
694 if (box.Right() <= ld->bbox[BOXLEFT]
695 || box.Left() >= ld->bbox[BOXRIGHT]
696 || box.Top() <= ld->bbox[BOXBOTTOM]
697 || box.Bottom() >= ld->bbox[BOXTOP])
698 return true;
699
700 if (box.BoxOnLineSide(ld) != -1)
701 return true;
702
703 // A line has been hit
704 /*
705 =
706 = The moving thing's destination position will cross the given line.
707 = If this should not be allowed, return false.
708 = If the line is special, keep track of it to process later if the move
709 = is proven ok. NOTE: specials are NOT sorted by order, so two special lines
710 = that are only 8 pixels apart could be crossed in either order.
711 */
712
713 if (!ld->backsector)
714 { // One sided line
715 if (tm.thing->flags2 & MF2_BLASTED)
716 {
717 P_DamageMobj(tm.thing, NULL, NULL, tm.thing->Mass >> 5, NAME_Melee);
718 }
719 tm.thing->BlockingLine = ld;
720 CheckForPushSpecial(ld, 0, tm.thing, false);
721 return false;
722 }
723
724 // MBF bouncers are treated as missiles here.
725 bool Projectile = (tm.thing->flags & MF_MISSILE || tm.thing->BounceFlags & BOUNCE_MBF);
726 // MBF considers that friendly monsters are not blocked by monster-blocking lines.
727 // This is added here as a compatibility option. Note that monsters that are dehacked
728 // into being friendly with the MBF flag automatically gain MF3_NOBLOCKMONST, so this
729 // just optionally generalizes the behavior to other friendly monsters.
730 bool NotBlocked = ((tm.thing->flags3 & MF3_NOBLOCKMONST)
731 || ((i_compatflags & COMPATF_NOBLOCKFRIENDS) && (tm.thing->flags & MF_FRIENDLY)));
732
733 if (!(Projectile) || (ld->flags & (ML_BLOCKEVERYTHING | ML_BLOCKPROJECTILE)))
734 {
735 if (ld->flags & ML_RAILING)
736 {
737 rail = true;
738 }
739 else if ((ld->flags & (ML_BLOCKING | ML_BLOCKEVERYTHING)) || // explicitly blocking everything
740 (!(NotBlocked) && (ld->flags & ML_BLOCKMONSTERS)) || // block monsters only
741 (tm.thing->player != NULL && (ld->flags & ML_BLOCK_PLAYERS)) || // block players
742 ((Projectile) && (ld->flags & ML_BLOCKPROJECTILE)) || // block projectiles
743 ((tm.thing->flags & MF_FLOAT) && (ld->flags & ML_BLOCK_FLOATERS))) // block floaters
744 {
745 if (tm.thing->flags2 & MF2_BLASTED)
746 {
747 P_DamageMobj(tm.thing, NULL, NULL, tm.thing->Mass >> 5, NAME_Melee);
748 }
749 tm.thing->BlockingLine = ld;
750 // Calculate line side based on the actor's original position, not the new one.
751 CheckForPushSpecial(ld, P_PointOnLineSide(tm.thing->x, tm.thing->y, ld), tm.thing, false);
752 return false;
753 }
754 }
755
756 // [RH] Steep sectors count as dropoffs (unless already in one)
757 if (!(tm.thing->flags & MF_DROPOFF) &&
758 !(tm.thing->flags & (MF_NOGRAVITY | MF_NOCLIP)))
759 {
760 secplane_t frontplane = ld->frontsector->floorplane;
761 secplane_t backplane = ld->backsector->floorplane;
762#ifdef _3DFLOORS
763 // Check 3D floors as well
764 frontplane = P_FindFloorPlane(ld->frontsector, tm.thing->x, tm.thing->y, tm.thing->floorz);
765 backplane = P_FindFloorPlane(ld->backsector, tm.thing->x, tm.thing->y, tm.thing->floorz);
766#endif
767 if (frontplane.c < STEEPSLOPE || backplane.c < STEEPSLOPE)
768 {
769 const msecnode_t* node = tm.thing->touching_sectorlist;
770 bool allow = false;
771 int count = 0;
772 while (node != NULL)
773 {
774 count++;
775 if (node->m_sector->floorplane.c < STEEPSLOPE)
776 {
777 allow = true;
778 break;
779 }
780 node = node->m_tnext;
781 }
782 if (!allow)
783 {
784 return false;
785 }
786 }
787 }
788
789 fixed_t sx = 0, sy = 0;
790 FLineOpening open;
791
792 // set openrange, opentop, openbottom
793 if ((((ld->frontsector->floorplane.a | ld->frontsector->floorplane.b) |
794 (ld->backsector->floorplane.a | ld->backsector->floorplane.b) |
795 (ld->frontsector->ceilingplane.a | ld->frontsector->ceilingplane.b) |
796 (ld->backsector->ceilingplane.a | ld->backsector->ceilingplane.b)) == 0)
797 && ld->backsector->e->XFloor.ffloors.Size() == 0 && ld->frontsector->e->XFloor.ffloors.Size() == 0)
798 {
799 P_LineOpening(open, tm.thing, ld, sx = tm.x, sy = tm.y, tm.x, tm.y);
800 }
801 else
802 { // Find the point on the line closest to the actor's center, and use
803 // that to calculate openings
804 float dx = (float)ld->dx;
805 float dy = (float)ld->dy;
806 fixed_t r = (fixed_t)(((float)(tm.x - ld->v1->x) * dx +
807 (float)(tm.y - ld->v1->y) * dy) /
808 (dx * dx + dy * dy) * 16777216.f);
809 /* Printf ("%d:%d: %d (%d %d %d %d) (%d %d %d %d)\n", level.time, ld-lines, r,
810 ld->frontsector->floorplane.a,
811 ld->frontsector->floorplane.b,
812 ld->frontsector->floorplane.c,
813 ld->frontsector->floorplane.ic,
814 ld->backsector->floorplane.a,
815 ld->backsector->floorplane.b,
816 ld->backsector->floorplane.c,
817 ld->backsector->floorplane.ic);*/
818 if (r <= 0)
819 {
820 P_LineOpening(open, tm.thing, ld, sx = ld->v1->x, sy = ld->v1->y, tm.x, tm.y);
821 }
822 else if (r >= (1 << 24))
823 {
824 P_LineOpening(open, tm.thing, ld, sx = ld->v2->x, sy = ld->v2->y, tm.thing->x, tm.thing->y);
825 }
826 else
827 {
828 P_LineOpening(open, tm.thing, ld, sx = ld->v1->x + MulScale24(r, ld->dx),
829 sy = ld->v1->y + MulScale24(r, ld->dy), tm.x, tm.y);
830 }
831
832 // the floorplane on both sides is identical with the current one
833 // so don't mess around with the z-position
834 if (ld->frontsector->floorplane == ld->backsector->floorplane &&
835 ld->frontsector->floorplane == tm.thing->Sector->floorplane &&
836 !ld->frontsector->e->XFloor.ffloors.Size() && !ld->backsector->e->XFloor.ffloors.Size() &&
837 !open.abovemidtex)
838 {
839 open.bottom = INT_MIN;
840 }
841 /* Printf (" %d %d %d\n", sx, sy, openbottom);*/
842 }
843
844 if (rail &&
845 // Eww! Gross! This check means the rail only exists if you stand on the
846 // high side of the rail. So if you're walking on the low side of the rail,
847 // it's possible to get stuck in the rail until you jump out. Unfortunately,
848 // there is an area on Strife MAP04 that requires this behavior. Still, it's
849 // better than Strife's handling of rails, which lets you jump into rails
850 // from either side. How long until somebody reports this as a bug and I'm
851 // forced to say, "It's not a bug. It's a feature?" Ugh.
852 (!(level.flags2 & LEVEL2_RAILINGHACK) ||
853 open.bottom == tm.thing->Sector->floorplane.ZatPoint(sx, sy)))
854 {
855 open.bottom += 32 * FRACUNIT;
856 }
857
858 // adjust floor / ceiling heights
859 if (open.top < tm.ceilingz)
860 {
861 tm.ceilingz = open.top;
862 tm.ceilingsector = open.topsec;
863 tm.ceilingpic = open.ceilingpic;
864 tm.ceilingline = ld;
865 tm.thing->BlockingLine = ld;
866 }
867
868 if (open.bottom > tm.floorz)
869 {
870 tm.floorz = open.bottom;
871 tm.floorsector = open.bottomsec;
872 tm.floorpic = open.floorpic;
873 tm.touchmidtex = open.touchmidtex;
874 tm.abovemidtex = open.abovemidtex;
875 tm.thing->BlockingLine = ld;
876 }
877 else if (open.bottom == tm.floorz)
878 {
879 tm.touchmidtex |= open.touchmidtex;
880 tm.abovemidtex |= open.abovemidtex;
881 }
882
883 if (open.lowfloor < tm.dropoffz)
884 tm.dropoffz = open.lowfloor;
885
886 // if contacted a special line, add it to the list
887 if (ld->special)
888 {
889 spechit.Push(ld);
890 }
891
892 return true;
893}
894
895//==========================================================================
896//
897// PIT_CheckThing
898//
899//==========================================================================
900
901bool PIT_CheckThing(AActor* thing, FCheckPosition& tm)
902{
903 fixed_t topz;
904 bool solid;
905 int damage;
906#if defined(CS_UNBLOCK_PROJECTILES)
907 auto tmthing = tm.thing;
908 if (tmthing->isMissile(false)) {
909
910 auto whoshot = tmthing->target;
911 if (whoshot && thing && whoshot->IsKindOf(RUNTIME_CLASS(APlayerPawn)) && thing->IsKindOf(RUNTIME_CLASS(APlayerPawn))) {
912 return true;
913 }
914 }
915#endif
916 if (!((thing->flags & (MF_SOLID | MF_SPECIAL | MF_SHOOTABLE)) || thing->flags6 & MF6_TOUCHY))
917 return true; // can't hit thing
918
919 fixed_t blockdist = thing->radius + tm.thing->radius;
920 if (abs(thing->x - tm.x) >= blockdist || abs(thing->y - tm.y) >= blockdist)
921 return true;
922
923 // don't clip against self
924 if (thing == tm.thing)
925 return true;
926
927 if ((thing->flags2 | tm.thing->flags2) & MF2_THRUACTORS)
928 return true;
929
930 // [BB] Adapted this for ZADF_UNBLOCK_PLAYERS.
931
932 if (P_CheckUnblock(tm.thing, thing) || ((tm.thing->flags6 & MF6_THRUSPECIES) && (tm.thing->GetSpecies() == thing->GetSpecies())))
933 return true;
934
935 tm.thing->BlockingMobj = thing;
936 topz = thing->z + thing->height;
937 if (!(i_compatflags & COMPATF_NO_PASSMOBJ) && !(tm.thing->flags & (MF_FLOAT | MF_MISSILE | MF_SKULLFLY | MF_NOGRAVITY)) &&
938 (thing->flags & MF_SOLID) && (thing->flags4 & MF4_ACTLIKEBRIDGE))
939 {
940 // [RH] Let monsters walk on actors as well as floors
941 if ((tm.thing->flags3 & MF3_ISMONSTER) &&
942 topz >= tm.floorz && topz <= tm.thing->z + tm.thing->MaxStepHeight)
943 {
944 // The commented-out if is an attempt to prevent monsters from walking off a
945 // thing further than they would walk off a ledge. I can't think of an easy
946 // way to do this, so I restrict them to only walking on bridges instead.
947 // Uncommenting the if here makes it almost impossible for them to walk on
948 // anything, bridge or otherwise.
949 // if (abs(thing->x - tmx) <= thing->radius &&
950 // abs(thing->y - tmy) <= thing->radius)
951 {
952 tm.stepthing = thing;
953 tm.floorz = topz;
954 }
955 }
956 }
957
958 // Both things overlap in x or y direction
959 bool unblocking = false;
960
961 if (tm.FromPMove)
962 {
963 // Both actors already overlap. To prevent them from remaining stuck allow the move if it
964 // takes them further apart or the move does not change the position (when called from P_ChangeSector.)
965 if (tm.x == tm.thing->x && tm.y == tm.thing->y)
966 {
967 unblocking = true;
968 }
969 else
970 {
971 fixed_t newdist = P_AproxDistance(thing->x - tm.x, thing->y - tm.y);
972 fixed_t olddist = P_AproxDistance(thing->x - tm.thing->x, thing->y - tm.thing->y);
973
974 if (newdist > olddist)
975 {
976 // ... but not if they did not overlap in z-direction before but would after the move.
977 unblocking = !((tm.thing->z >= thing->z + thing->height && tm.z < thing->z + thing->height) ||
978 (tm.thing->z + tm.thing->height <= thing->z && tm.z + tm.thing->height > thing->z));
979 }
980 }
981 }
982
983 // [RH] If the other thing is a bridge, then treat the moving thing as if it had MF2_PASSMOBJ, so
984 // you can use a scrolling floor to move scenery items underneath a bridge.
985 if ((tm.thing->flags2 & MF2_PASSMOBJ || thing->flags4 & MF4_ACTLIKEBRIDGE) && !(i_compatflags & COMPATF_NO_PASSMOBJ))
986 { // check if a mobj passed over/under another object
987 if (!(tm.thing->flags & MF_MISSILE) ||
988 !(tm.thing->flags2 & MF2_RIP) ||
989 (thing->flags5 & MF5_DONTRIP) ||
990 ((tm.thing->flags6 & MF6_NOBOSSRIP) && (thing->flags2 & MF2_BOSS)))
991 {
992 if (tm.thing->flags3 & thing->flags3 & MF3_DONTOVERLAP)
993 { // Some things prefer not to overlap each other, if possible
994 return unblocking;
995 }
996 if ((tm.thing->z >= topz) || (tm.thing->z + tm.thing->height <= thing->z))
997 return true;
998 }
999 }
1000
1001 if (tm.thing->player == NULL) // || !(tm.thing->player->cheats & CF_PREDICTING)) // [BB]
1002 {
1003 // touchy object is alive, toucher is solid
1004 if (thing->flags6 & MF6_TOUCHY && tm.thing->flags & MF_SOLID && thing->health > 0 &&
1005 // Thing is an armed mine or a sentient thing
1006 (thing->flags6 & MF6_ARMED || thing->IsSentient()) &&
1007 // either different classes or players
1008 (thing->player || thing->GetClass() != tm.thing->GetClass()) &&
1009 // or different species if DONTHARMSPECIES
1010 (!(thing->flags6 & MF6_DONTHARMSPECIES) || thing->GetSpecies() != tm.thing->GetSpecies()) &&
1011 // touches vertically
1012 thing->z + thing->height >= tm.thing->z && tm.thing->z + tm.thing->height >= thing->z &&
1013 // prevents lost souls from exploding when fired by pain elementals
1014 (thing->master != tm.thing && tm.thing->master != thing))
1015 // Difference with MBF: MBF hardcodes the LS/PE check and lets actors of the same species
1016 // but different classes trigger the touchiness, but that seems less straightforwards.
1017 {
1018 thing->flags6 &= ~MF6_ARMED; // Disarm
1019 P_DamageMobj(thing, NULL, NULL, thing->health, NAME_None, DMG_FORCED); // kill object
1020 return true;
1021 }
1022
1023 /* [BB] Zandronum keeps Skulltag's BUMPSPECIAL implementation for now.
1024 // Check for MF6_BUMPSPECIAL
1025 // By default, only players can activate things by bumping into them
1026 if ((thing->flags6 & MF6_BUMPSPECIAL) && ((tm.thing->player != NULL)
1027 || ((thing->activationtype & THINGSPEC_MonsterTrigger) && (tm.thing->flags3 & MF3_ISMONSTER))
1028 || ((thing->activationtype & THINGSPEC_MissileTrigger) && (tm.thing->flags & MF_MISSILE))
1029 ) && (level.maptime > thing->lastbump)) // Leave the bumper enough time to go away
1030 {
1031 if (P_ActivateThingSpecial(thing, tm.thing))
1032 thing->lastbump = level.maptime + TICRATE;
1033 }
1034 */
1035 }
1036
1037 // Check for skulls slamming into things
1038 if (tm.thing->flags & MF_SKULLFLY)
1039 {
1040 bool res = tm.thing->Slam(tm.thing->BlockingMobj);
1041 tm.thing->BlockingMobj = NULL;
1042 return res;
1043 }
1044
1045 // [ED850] Player Prediction ends here. There is nothing else they could/should do.
1046 /* [BB] Zandronum handles prediction differently.
1047 if (tm.thing->player != NULL && (tm.thing->player->cheats & CF_PREDICTING))
1048 {
1049 solid = (thing->flags & MF_SOLID) &&
1050 !(thing->flags & MF_NOCLIP) &&
1051 ((tm.thing->flags & MF_SOLID) || (tm.thing->flags6 & MF6_BLOCKEDBYSOLIDACTORS));
1052
1053 return !solid || unblocking;
1054 }
1055 */
1056
1057 // Check for blasted thing running into another
1058 if ((tm.thing->flags2 & MF2_BLASTED) && (thing->flags & MF_SHOOTABLE))
1059 {
1060 if (!(thing->flags2 & MF2_BOSS) && (thing->flags3 & MF3_ISMONSTER) && !(thing->flags3 & MF3_DONTBLAST))
1061 {
1062 // ideally this should take the mass factor into account
1063 thing->velx += tm.thing->velx;
1064 thing->vely += tm.thing->vely;
1065 if ((thing->velx + thing->vely) > 3 * FRACUNIT)
1066 {
1067 int newdam;
1068 damage = (tm.thing->Mass / 100) + 1;
1069 newdam = P_DamageMobj(thing, tm.thing, tm.thing, damage, tm.thing->DamageType);
1070 P_TraceBleed(newdam > 0 ? newdam : damage, thing, tm.thing);
1071 damage = (thing->Mass / 100) + 1;
1072 newdam = P_DamageMobj(tm.thing, thing, thing, damage >> 2, tm.thing->DamageType);
1073 P_TraceBleed(newdam > 0 ? newdam : damage, tm.thing, thing);
1074 }
1075 return false;
1076 }
1077 }
1078 // Check for missile or non-solid MBF bouncer
1079 if (tm.thing->flags & MF_MISSILE || ((tm.thing->BounceFlags & BOUNCE_MBF) && !(tm.thing->flags & MF_SOLID)))
1080 {
1081 // Check for a non-shootable mobj
1082 if (thing->flags2 & MF2_NONSHOOTABLE)
1083 {
1084 return true;
1085 }
1086 // Check for passing through a ghost
1087 if ((thing->flags3 & MF3_GHOST) && (tm.thing->flags2 & MF2_THRUGHOST))
1088 {
1089 return true;
1090 }
1091
1092 if ((tm.thing->flags6 & MF6_MTHRUSPECIES)
1093 && tm.thing->target // NULL pointer check
1094 && (tm.thing->target->GetSpecies() == thing->GetSpecies()))
1095 return true;
1096
1097 // Check for rippers passing through corpses
1098 if ((thing->flags & MF_CORPSE) && (tm.thing->flags2 & MF2_RIP) && !(thing->flags & MF_SHOOTABLE))
1099 {
1100 return true;
1101 }
1102
1103 int clipheight;
1104
1105 if (thing->projectilepassheight > 0)
1106 {
1107 clipheight = thing->projectilepassheight;
1108 }
1109 else if (thing->projectilepassheight < 0 && (i_compatflags & COMPATF_MISSILECLIP))
1110 {
1111 clipheight = -thing->projectilepassheight;
1112 }
1113 else
1114 {
1115 clipheight = thing->height;
1116 }
1117
1118 // Check if it went over / under
1119 if (tm.thing->z > thing->z + clipheight)
1120 { // Over thing
1121 return true;
1122 }
1123 if (tm.thing->z + tm.thing->height < thing->z)
1124 { // Under thing
1125 return true;
1126 }
1127
1128 // [RH] What is the point of this check, again? In Hexen, it is unconditional,
1129 // but here we only do it if the missile's damage is 0.
1130 // MBF bouncer might have a non-0 damage value, but they must not deal damage on impact either.
1131 if ((tm.thing->BounceFlags & BOUNCE_Actors) && (tm.thing->Damage == 0 || !(tm.thing->flags & MF_MISSILE)))
1132 {
1133 return (tm.thing->target == thing || !(thing->flags & MF_SOLID));
1134 }
1135
1136 switch (tm.thing->SpecialMissileHit(thing))
1137 {
1138 case 0: return false;
1139 case 1: return true;
1140 default: break;
1141 }
1142
1143 // [RH] Extend DeHacked infighting to allow for monsters
1144 // to never fight each other
1145
1146 // [Graf Zahl] Why do I have the feeling that this didn't really work anymore now
1147 // that ZDoom supports friendly monsters?
1148
1149
1150 if (tm.thing->target != NULL)
1151 {
1152 if (thing == tm.thing->target)
1153 { // Don't missile self
1154 return true;
1155 }
1156
1157 // players are never subject to infighting settings and are always allowed
1158 // to harm / be harmed by anything.
1159 if (!thing->player && !tm.thing->target->player)
1160 {
1161 int infight;
1162 if (level.flags2 & LEVEL2_TOTALINFIGHTING) infight = 1;
1163 else if (level.flags2 & LEVEL2_NOINFIGHTING) infight = -1;
1164 else infight = infighting;
1165
1166 // [BC] No infighting during invasion mode.
1167 if (infight < 0 || invasion)
1168 {
1169 // -1: Monsters cannot hurt each other, but make exceptions for
1170 // friendliness and hate status.
1171 if (tm.thing->target->flags & MF_SHOOTABLE)
1172 {
1173 // Question: Should monsters be allowed to shoot barrels in this mode?
1174 // The old code does not.
1175 if (thing->flags3 & MF3_ISMONSTER)
1176 {
1177 // Monsters that are clearly hostile can always hurt each other
1178 if (!thing->IsHostile(tm.thing->target))
1179 {
1180 // The same if the shooter hates the target
1181 if (thing->tid == 0 || tm.thing->target->TIDtoHate != thing->tid)
1182 {
1183 return false;
1184 }
1185 }
1186 }
1187 }
1188 }
1189 else if (infight == 0)
1190 {
1191 // 0: Monsters cannot hurt same species except
1192 // cases where they are clearly supposed to do that
1193 if (thing->IsFriend(tm.thing->target))
1194 {
1195 // Friends never harm each other
1196 return false;
1197 }
1198 if (thing->TIDtoHate != 0 && thing->TIDtoHate == tm.thing->target->TIDtoHate)
1199 {
1200 // [RH] Don't hurt monsters that hate the same thing as you do
1201 return false;
1202 }
1203 if (thing->GetSpecies() == tm.thing->target->GetSpecies() && !(thing->flags6 & MF6_DOHARMSPECIES))
1204 {
1205 // Don't hurt same species or any relative -
1206 // but only if the target isn't one's hostile.
1207 if (!thing->IsHostile(tm.thing->target))
1208 {
1209 // Allow hurting monsters the shooter hates.
1210 if (thing->tid == 0 || tm.thing->target->TIDtoHate != thing->tid)
1211 {
1212 return false;
1213 }
1214 }
1215 }
1216 }
1217 // else if (infight==1) any shot hurts anything - no further tests
1218 }
1219 }
1220 if (!(thing->flags & MF_SHOOTABLE))
1221 { // Didn't do any damage
1222 return !(thing->flags & MF_SOLID);
1223 }
1224 if ((thing->flags4 & MF4_SPECTRAL) && !(tm.thing->flags4 & MF4_SPECTRAL))
1225 {
1226 return true;
1227 }
1228 if (tm.DoRipping && !(thing->flags5 & MF5_DONTRIP))
1229 {
1230 if (!(tm.thing->flags6 & MF6_NOBOSSRIP) || !(thing->flags2 & MF2_BOSS))
1231 {
1232 if (tm.LastRipped != thing)
1233 {
1234 tm.LastRipped = thing;
1235 if (!(thing->flags & MF_NOBLOOD) &&
1236 !(thing->flags2 & MF2_REFLECTIVE) &&
1237 !(tm.thing->flags3 & MF3_BLOODLESSIMPACT) &&
1238 !(thing->flags2 & (MF2_INVULNERABLE | MF2_DORMANT)))
1239 { // Ok to spawn blood
1240 P_RipperBlood(tm.thing, thing);
1241 }
1242 S_Sound(tm.thing, CHAN_BODY, "misc/ripslop", 1, ATTN_IDLE);
1243
1244 // Do poisoning (if using new style poison)
1245 if (tm.thing->PoisonDamage > 0 && tm.thing->PoisonDuration != INT_MIN)
1246 {
1247 P_PoisonMobj(thing, tm.thing, tm.thing->target, tm.thing->PoisonDamage, tm.thing->PoisonDuration, tm.thing->PoisonPeriod, tm.thing->PoisonDamageType);
1248 }
1249
1250 damage = tm.thing->GetMissileDamage(3, 2);
1251 // [BB] The server handles the damage of RIPPER weapons.
1252 int newdam = 0;
1253 if (NETWORK_InClientMode() == false)
1254 newdam = P_DamageMobj(thing, tm.thing, tm.thing->target, damage, tm.thing->DamageType);
1255 if (!(tm.thing->flags3 & MF3_BLOODLESSIMPACT))
1256 {
1257 P_TraceBleed(newdam > 0 ? newdam : damage, thing, tm.thing);
1258 }
1259 if (thing->flags2 & MF2_PUSHABLE
1260 && !(tm.thing->flags2 & MF2_CANNOTPUSH))
1261 { // Push thing
1262 if (thing->lastpush != tm.PushTime)
1263 {
1264 thing->velx += FixedMul(tm.thing->velx, thing->pushfactor);
1265 thing->vely += FixedMul(tm.thing->vely, thing->pushfactor);
1266 thing->lastpush = tm.PushTime;
1267 }
1268 }
1269 }
1270 spechit.Clear();
1271 return true;
1272 }
1273 }
1274
1275 // Do poisoning (if using new style poison)
1276 if (tm.thing->PoisonDamage > 0 && tm.thing->PoisonDuration != INT_MIN)
1277 {
1278 P_PoisonMobj(thing, tm.thing, tm.thing->target, tm.thing->PoisonDamage, tm.thing->PoisonDuration, tm.thing->PoisonPeriod, tm.thing->PoisonDamageType);
1279 }
1280
1281 // Do damage
1282 damage = tm.thing->GetMissileDamage((tm.thing->flags4 & MF4_STRIFEDAMAGE) ? 3 : 7, 1);
1283 if (NETWORK_InClientMode() == false)
1284 {
1285 if ((damage > 0) || (tm.thing->flags6 & MF6_FORCEPAIN))
1286 {
1287 if ((tm.thing->target) &&
1288 (tm.thing->target->player) &&
1289 (thing->player) &&
1290 (thing->IsTeammate(tm.thing->target) == false))
1291 {
1292 tm.thing->target->player->bStruckPlayer = true;
1293 }
1294
1295 int newdam = P_DamageMobj(thing, tm.thing, tm.thing->target, damage, tm.thing->DamageType);
1296 if (damage > 0)
1297 {
1298 if ((tm.thing->flags5 & MF5_BLOODSPLATTER) &&
1299 !(thing->flags & MF_NOBLOOD) &&
1300 !(thing->flags2 & MF2_REFLECTIVE) &&
1301 !(thing->flags2 & (MF2_INVULNERABLE | MF2_DORMANT)) &&
1302 !(tm.thing->flags3 & MF3_BLOODLESSIMPACT) &&
1303 (pr_checkthing() < 192))
1304 {
1305 P_BloodSplatter(tm.thing->x, tm.thing->y, tm.thing->z, thing);
1306 }
1307 if (!(tm.thing->flags3 & MF3_BLOODLESSIMPACT))
1308 {
1309 P_TraceBleed(newdam > 0 ? newdam : damage, thing, tm.thing);
1310 }
1311 }
1312 }
1313 else
1314 {
1315 P_GiveBody(thing, -damage);
1316
1317 // [BB] If this affected a player, let the clients know.
1318 if (thing->player && (NETWORK_GetState() == NETSTATE_SERVER))
1319 SERVERCOMMANDS_SetPlayerHealth(static_cast<ULONG> (thing->player - players));
1320 }
1321 }
1322 return false; // don't traverse any more
1323 }
1324 // [BC] Don't push things in client mode.
1325 if (thing->flags2 & MF2_PUSHABLE && !(tm.thing->flags2 & MF2_CANNOTPUSH)
1326 && (NETWORK_InClientMode() == false))
1327 { // Push thing
1328 if (thing->lastpush != tm.PushTime)
1329 {
1330 thing->velx += FixedMul(tm.thing->velx, thing->pushfactor);
1331 thing->vely += FixedMul(tm.thing->vely, thing->pushfactor);
1332 thing->lastpush = tm.PushTime;
1333 }
1334
1335 // [BC] If we're the server, tell clients to update the thing's position and
1336 // velocity.
1337 if (NETWORK_GetState() == NETSTATE_SERVER)
1338 SERVERCOMMANDS_MoveThingExact(thing, CM_X | CM_Y | CM_Z | CM_VELX | CM_VELY | CM_VELZ);
1339 }
1340 solid = (thing->flags & MF_SOLID) &&
1341 !(thing->flags & MF_NOCLIP) &&
1342 ((tm.thing->flags & MF_SOLID) || (tm.thing->flags6 & MF6_BLOCKEDBYSOLIDACTORS));
1343
1344 // Check for special pickup
1345 if ((thing->flags & MF_SPECIAL) && (tm.thing->flags & MF_PICKUP)
1346 // [RH] The next condition is to compensate for the extra height
1347 // that gets added by P_CheckPosition() so that you cannot pick
1348 // up things that are above your true height.
1349 && thing->z < tm.thing->z + tm.thing->height - tm.thing->MaxStepHeight)
1350 { // Can be picked up by tmthing
1351 // Server decides what items are touched.
1352 if (NETWORK_InClientMode() == false)
1353 {
1354 P_TouchSpecialThing(thing, tm.thing); // can remove thing
1355 }
1356 }
1357
1358 // If this object has the bumpspecial flag, try to activate the item's special.
1359 if ((NETWORK_InClientMode() == false) &&
1360 (solid) &&
1361 (thing->flags6 & MF6_BUMPSPECIAL))
1362 {
1363 if ((TEAM_GetSimpleCTFSTMode()) && (tm.thing->player) && (tm.thing->player->bOnTeam))
1364 {
1365 // [BB] With the addition of sv_maxteams and the subsequent definition of four teams in
1366 // zandronum.pk3, we can't check thing->args[0] against teams.Size( ) anymore. As workaround,
1367 // we check against the number of teams that have starts on the map to guess how many teams
1368 // the mapper thought were available. Note: This is not going to work properly, if the map
1369 // has starts for teams 0 and 2, but not for team 1 for example.
1370 if ((thing->ulSTFlags & STFL_SCOREPILLAR) &&
1371 (TEAM_FindOpposingTeamsItemInPlayersInventory(tm.thing->player)) &&
1372 (thing->args[0] == static_cast<int> (TEAM_GetNumTeamsWithStarts()) || static_cast<signed>(tm.thing->player->ulTeam) == thing->args[0]) &&
1373 (thing->args[1] > 0))
1374 {
1375 if (!TEAM_GetItemTaken(tm.thing->player->ulTeam))
1376 TEAM_ScoreSkulltagPoint(tm.thing->player, thing->args[1], thing);
1377 else
1378 TEAM_DisplayNeedToReturnSkullMessage(tm.thing->player);
1379 }
1380 }
1381
1382 LineSpecials[thing->special](NULL, tm.thing, false, thing->args[0],
1383 thing->args[1], thing->args[2], thing->args[3], thing->args[4]);
1384 }
1385
1386 // killough 3/16/98: Allow non-solid moving objects to move through solid
1387 // ones, by allowing the moving thing (tmthing) to move if it's non-solid,
1388 // despite another solid thing being in the way.
1389 // killough 4/11/98: Treat no-clipping things as not blocking
1390
1391 return !solid || unblocking;
1392
1393 // return !(thing->flags & MF_SOLID); // old code -- killough
1394}
1395
1396
1397
1398/*
1399===============================================================================
1400
1401MOVEMENT CLIPPING
1402
1403===============================================================================
1404*/
1405
1406//==========================================================================
1407//
1408// P_CheckPosition
1409// This is purely informative, nothing is modified
1410// (except things picked up and missile damage applied).
1411//
1412// in:
1413// a AActor (can be valid or invalid)
1414// a position to be checked
1415// (doesn't need to be related to the AActor->x,y)
1416//
1417// during:
1418// special things are touched if MF_PICKUP
1419// early out on solid lines?
1420//
1421// out:
1422// newsubsec
1423// floorz
1424// ceilingz
1425// tmdropoffz = the lowest point contacted (monsters won't move to a dropoff)
1426// speciallines[]
1427// numspeciallines
1428// AActor *BlockingMobj = pointer to thing that blocked position (NULL if not
1429// blocked, or blocked by a line).
1430//
1431//==========================================================================
1432
1433bool P_CheckPosition(AActor* thing, fixed_t x, fixed_t y, FCheckPosition& tm, bool actorsonly)
1434{
1435 sector_t* newsec;
1436 AActor* thingblocker;
1437 fixed_t realheight = thing->height;
1438
1439 tm.thing = thing;
1440
1441 tm.x = x;
1442 tm.y = y;
1443
1444 newsec = P_PointInSector(x, y);
1445 tm.ceilingline = thing->BlockingLine = NULL;
1446
1447 // The base floor / ceiling is from the subsector that contains the point.
1448 // Any contacted lines the step closer together will adjust them.
1449 tm.floorz = tm.dropoffz = newsec->floorplane.ZatPoint(x, y);
1450 tm.ceilingz = newsec->ceilingplane.ZatPoint(x, y);
1451 tm.floorpic = newsec->GetTexture(sector_t::floor);
1452 tm.floorsector = newsec;
1453 tm.ceilingpic = newsec->GetTexture(sector_t::ceiling);
1454 tm.ceilingsector = newsec;
1455 tm.touchmidtex = false;
1456 tm.abovemidtex = false;
1457
1458 //Added by MC: Fill the tmsector.
1459 tm.sector = newsec;
1460
1461#ifdef _3DFLOORS
1462 //Check 3D floors
1463 if (!thing->IsNoClip2() && newsec->e->XFloor.ffloors.Size())
1464 {
1465 F3DFloor* rover;
1466 fixed_t delta1;
1467 fixed_t delta2;
1468 int thingtop = thing->z + (thing->height == 0 ? 1 : thing->height);
1469
1470 for (unsigned i = 0; i < newsec->e->XFloor.ffloors.Size(); i++)
1471 {
1472 rover = newsec->e->XFloor.ffloors[i];
1473 if (!(rover->flags & FF_SOLID) || !(rover->flags & FF_EXISTS)) continue;
1474
1475 fixed_t ff_bottom = rover->bottom.plane->ZatPoint(x, y);
1476 fixed_t ff_top = rover->top.plane->ZatPoint(x, y);
1477
1478 delta1 = thing->z - (ff_bottom + ((ff_top - ff_bottom) / 2));
1479 delta2 = thingtop - (ff_bottom + ((ff_top - ff_bottom) / 2));
1480
1481 if (ff_top > tm.floorz && abs(delta1) < abs(delta2))
1482 {
1483 tm.floorz = tm.dropoffz = ff_top;
1484 tm.floorpic = *rover->top.texture;
1485 }
1486 if (ff_bottom < tm.ceilingz && abs(delta1) >= abs(delta2))
1487 {
1488 tm.ceilingz = ff_bottom;
1489 tm.ceilingpic = *rover->bottom.texture;
1490 }
1491 }
1492 }
1493#endif
1494
1495 validcount++;
1496 spechit.Clear();
1497
1498 if ((thing->flags & MF_NOCLIP) && !(thing->flags & MF_SKULLFLY))
1499 return true;
1500
1501 // Check things first, possibly picking things up.
1502 thing->BlockingMobj = NULL;
1503 thingblocker = NULL;
1504 if (thing->player)
1505 { // [RH] Fake taller height to catch stepping up into things.
1506 thing->height = realheight + thing->MaxStepHeight;
1507 }
1508
1509 tm.stepthing = NULL;
1510 FBoundingBox box(x, y, thing->radius);
1511
1512 {
1513 FBlockThingsIterator it2(box);
1514 AActor* th;
1515 while ((th = it2.Next()))
1516 {
1517 if (!PIT_CheckThing(th, tm))
1518 { // [RH] If a thing can be stepped up on, we need to continue checking
1519 // other things in the blocks and see if we hit something that is
1520 // definitely blocking. Otherwise, we need to check the lines, or we
1521 // could end up stuck inside a wall.
1522 AActor* BlockingMobj = thing->BlockingMobj;
1523
1524 if (BlockingMobj == NULL || (i_compatflags & COMPATF_NO_PASSMOBJ))
1525 { // Thing slammed into something; don't let it move now.
1526 thing->height = realheight;
1527 return false;
1528 }
1529 else if (!BlockingMobj->player && !(thing->flags & (MF_FLOAT | MF_MISSILE | MF_SKULLFLY)) &&
1530 BlockingMobj->z + BlockingMobj->height - thing->z <= thing->MaxStepHeight)
1531 {
1532 if (thingblocker == NULL ||
1533 BlockingMobj->z > thingblocker->z)
1534 {
1535 thingblocker = BlockingMobj;
1536 }
1537 thing->BlockingMobj = NULL;
1538 }
1539 else if (thing->player &&
1540 thing->z + thing->height - BlockingMobj->z <= thing->MaxStepHeight)
1541 {
1542 if (thingblocker)
1543 { // There is something to step up on. Return this thing as
1544 // the blocker so that we don't step up.
1545 thing->height = realheight;
1546 return false;
1547 }
1548 // Nothing is blocking us, but this actor potentially could
1549 // if there is something else to step on.
1550 thing->BlockingMobj = NULL;
1551 }
1552 else
1553 { // Definitely blocking
1554 thing->height = realheight;
1555 return false;
1556 }
1557 }
1558 }
1559 }
1560
1561 // check lines
1562
1563 // [RH] We need to increment validcount again, because a function above may
1564 // have already set some lines to equal the current validcount.
1565 //
1566 // Specifically, when DehackedPickup spawns a new item in its TryPickup()
1567 // function, that new actor will set the lines around it to match validcount
1568 // when it links itself into the world. If we just leave validcount alone,
1569 // that will give the player the freedom to walk through walls at will near
1570 // a pickup they cannot get, because their validcount will prevent them from
1571 // being considered for collision with the player.
1572 validcount++;
1573
1574 thing->BlockingMobj = NULL;
1575 thing->height = realheight;
1576 if (actorsonly || (thing->flags & MF_NOCLIP))
1577 return (thing->BlockingMobj = thingblocker) == NULL;
1578
1579 FBlockLinesIterator it(box);
1580 line_t* ld;
1581
1582 fixed_t thingdropoffz = tm.floorz;
1583 //bool onthing = (thingdropoffz != tmdropoffz);
1584 tm.floorz = tm.dropoffz;
1585
1586 bool good = true;
1587
1588 while ((ld = it.Next()))
1589 {
1590 good &= PIT_CheckLine(ld, box, tm);
1591 }
1592 if (!good)
1593 {
1594 return false;
1595 }
1596 if (tm.ceilingz - tm.floorz < thing->height)
1597 {
1598 return false;
1599 }
1600 if (tm.touchmidtex)
1601 {
1602 tm.dropoffz = tm.floorz;
1603 }
1604 else if (tm.stepthing != NULL)
1605 {
1606 tm.dropoffz = thingdropoffz;
1607 }
1608
1609 return (thing->BlockingMobj = thingblocker) == NULL;
1610}
1611
1612bool P_CheckPosition(AActor* thing, fixed_t x, fixed_t y, bool actorsonly)
1613{
1614 FCheckPosition tm;
1615 return P_CheckPosition(thing, x, y, tm, actorsonly);
1616}
1617
1618//----------------------------------------------------------------------------
1619//
1620// FUNC P_TestMobjLocation
1621//
1622// Returns true if the mobj is not blocked by anything at its current
1623// location, otherwise returns false.
1624//
1625//----------------------------------------------------------------------------
1626
1627bool P_TestMobjLocation(AActor* mobj)
1628{
1629 int flags;
1630
1631 flags = mobj->flags;
1632 mobj->flags &= ~MF_PICKUP;
1633 if (P_CheckPosition(mobj, mobj->x, mobj->y))
1634 { // XY is ok, now check Z
1635 mobj->flags = flags;
1636 if ((mobj->z < mobj->floorz) || (mobj->z + mobj->height > mobj->ceilingz))
1637 { // Bad Z
1638 return false;
1639 }
1640 return true;
1641 }
1642 mobj->flags = flags;
1643 return false;
1644}
1645
1646
1647//=============================================================================
1648//
1649// P_CheckOnmobj(AActor *thing)
1650//
1651// Checks if the new Z position is legal
1652//=============================================================================
1653
1654AActor* P_CheckOnmobj(AActor* thing)
1655{
1656 fixed_t oldz;
1657 bool good;
1658 AActor* onmobj;
1659
1660 oldz = thing->z;
1661 P_FakeZMovement(thing);
1662 good = P_TestMobjZ(thing, false, &onmobj);
1663 thing->z = oldz;
1664
1665 return good ? NULL : onmobj;
1666}
1667
1668//=============================================================================
1669//
1670// P_TestMobjZ
1671//
1672//=============================================================================
1673
1674bool P_TestMobjZ(AActor* actor, bool quick, AActor** pOnmobj)
1675{
1676 AActor* onmobj = NULL;
1677 if (actor->flags & MF_NOCLIP)
1678 {
1679 if (pOnmobj) *pOnmobj = NULL;
1680 return true;
1681 }
1682
1683 // [BB] For people want to have the buggy behavior of non-SOLID things dropping through bridges.
1684 if ((zacompatflags & ZACOMPATF_OLD_BRIDGE_DROPS) && !(actor->flags & MF_SOLID))
1685 {
1686 if (pOnmobj) *pOnmobj = NULL;
1687 return true;
1688 }
1689
1690 FBlockThingsIterator it(FBoundingBox(actor->x, actor->y, actor->radius));
1691 AActor* thing;
1692
1693 while ((thing = it.Next()))
1694 {
1695 if (!thing->intersects(actor))
1696 {
1697 continue;
1698 }
1699 if ((actor->flags2 | thing->flags2) & MF2_THRUACTORS)
1700 {
1701 continue;
1702 }
1703 // [BB] Adapted this for ZADF_UNBLOCK_PLAYERS.
1704 if (P_CheckUnblock(actor, thing) || ((actor->flags6 & MF6_THRUSPECIES) && (thing->GetSpecies() == actor->GetSpecies())))
1705 {
1706 continue;
1707 }
1708 if (!(thing->flags & MF_SOLID))
1709 { // Can't hit thing
1710 continue;
1711 }
1712 if (thing->flags & (MF_SPECIAL | MF_NOCLIP))
1713 { // [RH] Specials and noclippers don't block moves
1714 continue;
1715 }
1716 if (thing->flags & (MF_CORPSE))
1717 { // Corpses need a few more checks
1718 if (!(actor->flags & MF_ICECORPSE))
1719 continue;
1720 }
1721 if (!(thing->flags4 & MF4_ACTLIKEBRIDGE) && (actor->flags & MF_SPECIAL))
1722 { // [RH] Only bridges block pickup items
1723 continue;
1724 }
1725 if (thing == actor)
1726 { // Don't clip against self
1727 continue;
1728 }
1729 if ((actor->flags & MF_MISSILE))
1730 { // Don't clip against whoever shot the missile.
1731 if (thing == actor->target)
1732 continue;
1733#if defined(CS_UNBLOCK_PROJECTILES)
1734 //dont clip against other players
1735 auto whoshot = actor->target;
1736 if (whoshot && whoshot->IsKindOf(RUNTIME_CLASS(APlayerPawn)) && thing->IsKindOf(RUNTIME_CLASS(APlayerPawn))) {
1737 continue;
1738 }
1739#endif
1740 }
1741 if (actor->z > thing->z + thing->height)
1742 { // over thing
1743 continue;
1744 }
1745 else if (actor->z + actor->height <= thing->z)
1746 { // under thing
1747 continue;
1748 }
1749 else if (!quick && onmobj != NULL && thing->z + thing->height < onmobj->z + onmobj->height)
1750 { // something higher is in the way
1751 continue;
1752 }
1753 onmobj = thing;
1754 if (quick) break;
1755 }
1756
1757 if (pOnmobj) *pOnmobj = onmobj;
1758 return onmobj == NULL;
1759}
1760
1761//=============================================================================
1762//
1763// P_FakeZMovement
1764//
1765// Fake the zmovement so that we can check if a move is legal
1766//=============================================================================
1767
1768void P_FakeZMovement(AActor* mo)
1769{
1770 //
1771 // adjust height
1772 //
1773 mo->z += mo->velz;
1774 if ((mo->flags & MF_FLOAT) && mo->target)
1775 { // float down towards target if too close
1776 if (!(mo->flags & MF_SKULLFLY) && !(mo->flags & MF_INFLOAT))
1777 {
1778 fixed_t dist = P_AproxDistance(mo->x - mo->target->x, mo->y - mo->target->y);
1779 fixed_t delta = (mo->target->z + (mo->height >> 1)) - mo->z;
1780 if (delta < 0 && dist < -(delta * 3))
1781 mo->z -= mo->FloatSpeed;
1782 else if (delta > 0 && dist < (delta * 3))
1783 mo->z += mo->FloatSpeed;
1784 }
1785 }
1786 if (mo->player && mo->flags & MF_NOGRAVITY && (mo->z > mo->floorz) && !mo->IsNoClip2())
1787 {
1788 mo->z += finesine[(FINEANGLES / 80 * level.maptime) & FINEMASK] / 8;
1789 }
1790
1791 //
1792 // clip movement
1793 //
1794 if (mo->z <= mo->floorz)
1795 { // hit the floor
1796 mo->z = mo->floorz;
1797 }
1798
1799 if (mo->z + mo->height > mo->ceilingz)
1800 { // hit the ceiling
1801 mo->z = mo->ceilingz - mo->height;
1802 }
1803}
1804
1805//===========================================================================
1806//
1807// CheckForPushSpecial
1808//
1809//===========================================================================
1810
1811static void CheckForPushSpecial(line_t* line, int side, AActor* mobj, bool windowcheck)
1812{
1813 if (line->special && !(mobj->flags6 & MF6_NOTRIGGER))
1814 {
1815 if (windowcheck && !(i_compatflags2 & COMPATF2_PUSHWINDOW) && line->backsector != NULL)
1816 { // Make sure this line actually blocks us and is not a window
1817 // or similar construct we are standing inside of.
1818 fixed_t fzt = line->frontsector->ceilingplane.ZatPoint(mobj->x, mobj->y);
1819 fixed_t fzb = line->frontsector->floorplane.ZatPoint(mobj->x, mobj->y);
1820 fixed_t bzt = line->backsector->ceilingplane.ZatPoint(mobj->x, mobj->y);
1821 fixed_t bzb = line->backsector->floorplane.ZatPoint(mobj->x, mobj->y);
1822 if (fzt >= mobj->z + mobj->height && bzt >= mobj->z + mobj->height &&
1823 fzb <= mobj->z && bzb <= mobj->z)
1824 {
1825 if (line->flags & ML_3DMIDTEX)
1826 {
1827 fixed_t top, bot;
1828 P_GetMidTexturePosition(line, side, &top, &bot);
1829 if (bot < mobj->z + mobj->height && top > mobj->z)
1830 {
1831 goto isblocking;
1832 }
1833 }
1834 // we must also check if some 3D floor in the backsector may be blocking
1835#ifdef _3DFLOORS
1836 for (unsigned int i = 0; i < line->backsector->e->XFloor.ffloors.Size(); i++)
1837 {
1838 F3DFloor* rover = line->backsector->e->XFloor.ffloors[i];
1839
1840 if (!(rover->flags & FF_SOLID) || !(rover->flags & FF_EXISTS)) continue;
1841
1842 fixed_t ff_bottom = rover->bottom.plane->ZatPoint(mobj->x, mobj->y);
1843 fixed_t ff_top = rover->top.plane->ZatPoint(mobj->x, mobj->y);
1844
1845 if (ff_bottom < mobj->z + mobj->height && ff_top > mobj->z)
1846 {
1847 goto isblocking;
1848 }
1849 }
1850#endif
1851 return;
1852 }
1853 }
1854 isblocking:
1855 // [BC] In a netgame, since mobj->target is never valid, we must go this route to avoid a crash.
1856 if ((mobj->flags2 & MF2_PUSHWALL) || NETWORK_InClientMode())
1857 {
1858 P_ActivateLine(line, mobj, side, SPAC_Push);
1859 }
1860 else if (mobj->flags2 & MF2_IMPACT)
1861 {
1862 if ((level.flags2 & LEVEL2_MISSILESACTIVATEIMPACT) ||
1863 !(mobj->flags & MF_MISSILE) ||
1864 (mobj->target == NULL))
1865 {
1866 P_ActivateLine(line, mobj, side, SPAC_Impact);
1867 }
1868 else
1869 {
1870 P_ActivateLine(line, mobj->target, side, SPAC_Impact);
1871 }
1872 }
1873 }
1874}
1875
1876//==========================================================================
1877//
1878// P_TryMove
1879// Attempt to move to a new position,
1880// crossing special lines unless MF_TELEPORT is set.
1881//
1882//==========================================================================
1883
1884bool P_TryMove(AActor* thing, fixed_t x, fixed_t y,
1885 int dropoff, // killough 3/15/98: allow dropoff as option
1886 const secplane_t* onfloor, // [RH] Let P_TryMove keep the thing on the floor
1887 FCheckPosition& tm,
1888 bool missileCheck) // [GZ] Fired missiles ignore the drop-off test
1889{
1890 fixed_t oldx;
1891 fixed_t oldy;
1892 fixed_t oldz;
1893 int side;
1894 int oldside;
1895 line_t* ld;
1896 sector_t* oldsec = thing->Sector; // [RH] for sector actions
1897 sector_t* newsec;
1898
1899 tm.floatok = false;
1900 oldz = thing->z;
1901 if (onfloor)
1902 {
1903 thing->z = onfloor->ZatPoint(x, y);
1904 }
1905 thing->flags6 |= MF6_INTRYMOVE;
1906 // [WS] For clients, check to see if we are allowed to clip our actor's movement.
1907 if (!P_CheckPosition(thing, x, y, tm) && CLIENT_CanClipMovement(thing))
1908 {
1909 AActor* BlockingMobj = thing->BlockingMobj;
1910 // Solid wall or thing
1911 if (!BlockingMobj || BlockingMobj->player || !thing->player)
1912 {
1913 goto pushline;
1914 }
1915 else
1916 {
1917 if (BlockingMobj->player || !thing->player)
1918 {
1919 goto pushline;
1920 }
1921 else if (BlockingMobj->z + BlockingMobj->height - thing->z
1922 > thing->MaxStepHeight
1923 || (BlockingMobj->Sector->ceilingplane.ZatPoint(x, y)
1924 - (BlockingMobj->z + BlockingMobj->height) < thing->height)
1925 || (tm.ceilingz - (BlockingMobj->z + BlockingMobj->height)
1926 < thing->height))
1927 {
1928 goto pushline;
1929 }
1930 }
1931 if (!(tm.thing->flags2 & MF2_PASSMOBJ) || (i_compatflags & COMPATF_NO_PASSMOBJ))
1932 {
1933 thing->z = oldz;
1934 thing->flags6 &= ~MF6_INTRYMOVE;
1935 return false;
1936 }
1937 }
1938
1939 if (thing->flags3 & MF3_FLOORHUGGER)
1940 {
1941 thing->z = tm.floorz;
1942 }
1943 else if (thing->flags3 & MF3_CEILINGHUGGER)
1944 {
1945 thing->z = tm.ceilingz - thing->height;
1946 }
1947
1948 if (onfloor && tm.floorsector == thing->floorsector)
1949 {
1950 thing->z = tm.floorz;
1951 }
1952 // [WS] For clients, check to see if we are allowed to clip our actor's movement.
1953 if (!(thing->flags & MF_NOCLIP) && CLIENT_CanClipMovement(thing))
1954 {
1955 if (tm.ceilingz - tm.floorz < thing->height)
1956 {
1957 goto pushline; // doesn't fit
1958 }
1959
1960 tm.floatok = true;
1961
1962 if (!(thing->flags & MF_TELEPORT)
1963 && tm.ceilingz - thing->z < thing->height
1964 && !(thing->flags3 & MF3_CEILINGHUGGER)
1965 && (!(thing->flags2 & MF2_FLY) || !(thing->flags & MF_NOGRAVITY)))
1966 {
1967 goto pushline; // mobj must lower itself to fit
1968 }
1969 if (thing->flags2 & MF2_FLY && thing->flags & MF_NOGRAVITY)
1970 {
1971#if 1
1972 if (thing->z + thing->height > tm.ceilingz)
1973 goto pushline;
1974#else
1975 // When flying, slide up or down blocking lines until the actor
1976 // is not blocked.
1977 if (thing->z + thing->height > tm.ceilingz)
1978 {
1979 thing->velz = -8 * FRACUNIT;
1980 goto pushline;
1981 }
1982 else if (thing->z < tm.floorz && tm.floorz - tm.dropoffz > thing->MaxDropOffHeight)
1983 {
1984 thing->velz = 8 * FRACUNIT;
1985 goto pushline;
1986 }
1987#endif
1988 }
1989 if (!(thing->flags & MF_TELEPORT) && !(thing->flags3 & MF3_FLOORHUGGER))
1990 {
1991 if ((thing->flags & MF_MISSILE) && !(thing->flags6 & MF6_STEPMISSILE) && tm.floorz > thing->z)
1992 { // [RH] Don't let normal missiles climb steps
1993 goto pushline;
1994 }
1995 if (tm.floorz - thing->z > thing->MaxStepHeight)
1996 { // too big a step up
1997 goto pushline;
1998 }
1999 else if (thing->z < tm.floorz)
2000 { // [RH] Check to make sure there's nothing in the way for the step up
2001 fixed_t savedz = thing->z;
2002 bool good;
2003 thing->z = tm.floorz;
2004 good = P_TestMobjZ(thing);
2005 thing->z = savedz;
2006 if (!good)
2007 {
2008 goto pushline;
2009 }
2010 if (thing->flags6 & MF6_STEPMISSILE)
2011 {
2012 thing->z = tm.floorz;
2013 // If moving down, cancel vertical component of the velocity
2014 if (thing->velz < 0)
2015 {
2016 // If it's a bouncer, let it bounce off its new floor, too.
2017 if (thing->BounceFlags & BOUNCE_Floors)
2018 {
2019 thing->FloorBounceMissile(tm.floorsector->floorplane);
2020 }
2021 else
2022 {
2023 thing->velz = 0;
2024 }
2025 }
2026 }
2027 }
2028 }
2029
2030 // compatibility check: Doom originally did not allow monsters to cross dropoffs at all.
2031 // If the compatibility flag is on, only allow this when the velocity comes from a scroller
2032 if ((i_compatflags & COMPATF_CROSSDROPOFF) && !(thing->flags4 & MF4_SCROLLMOVE))
2033 {
2034 dropoff = false;
2035 }
2036
2037 if (dropoff == 2 && // large jump down (e.g. dogs)
2038 (tm.floorz - tm.dropoffz > 128 * FRACUNIT || thing->target == NULL || thing->target->z > tm.dropoffz))
2039 {
2040 dropoff = false;
2041 }
2042
2043
2044 // killough 3/15/98: Allow certain objects to drop off
2045 if ((!dropoff && !(thing->flags & (MF_DROPOFF | MF_FLOAT | MF_MISSILE))) || (thing->flags5 & MF5_NODROPOFF))
2046 {
2047 if (!(thing->flags5 & MF5_AVOIDINGDROPOFF))
2048 {
2049 fixed_t floorz = tm.floorz;
2050 // [RH] If the thing is standing on something, use its current z as the floorz.
2051 // This is so that it does not walk off of things onto a drop off.
2052 if (thing->flags2 & MF2_ONMOBJ)
2053 {
2054 floorz = MAX(thing->z, tm.floorz);
2055 }
2056
2057 if (floorz - tm.dropoffz > thing->MaxDropOffHeight &&
2058 !(thing->flags2 & MF2_BLASTED) && !missileCheck)
2059 { // Can't move over a dropoff unless it's been blasted
2060 // [GZ] Or missile-spawned
2061 thing->z = oldz;
2062 thing->flags6 &= ~MF6_INTRYMOVE;
2063 return false;
2064 }
2065 }
2066 else
2067 {
2068 // special logic to move a monster off a dropoff
2069 // this intentionally does not check for standing on things.
2070 if (thing->floorz - tm.floorz > thing->MaxDropOffHeight ||
2071 thing->dropoffz - tm.dropoffz > thing->MaxDropOffHeight)
2072 {
2073 thing->flags6 &= ~MF6_INTRYMOVE;
2074 return false;
2075 }
2076 }
2077 }
2078 if (thing->flags2 & MF2_CANTLEAVEFLOORPIC
2079 && (tm.floorpic != thing->floorpic
2080 || tm.floorz - thing->z != 0))
2081 { // must stay within a sector of a certain floor type
2082 // [BB] For some reason the client slightly mispredicts the actor position,
2083 // i.e. if an actor with MF2_CANTLEAVEFLOORPIC touches the boundary of the floorpic
2084 // region, the client sometimes thinks the actor is outside the region.
2085 // Therefore, doing the following on the client would completely mess up the
2086 // client side monster movement prediction.
2087 if (NETWORK_InClientMode() == false)
2088 {
2089 thing->z = oldz;
2090 thing->flags6 &= ~MF6_INTRYMOVE;
2091 return false;
2092 }
2093 }
2094
2095 /* [BB] Zandronum doesn't use ZDoom's bot code.
2096 //Added by MC: To prevent bot from getting into dangerous sectors.
2097 if (thing->player && thing->player->isbot && thing->flags & MF_SHOOTABLE)
2098 {
2099 if (tm.sector != thing->Sector
2100 && bglobal.IsDangerous(tm.sector))
2101 {
2102 thing->player->prev = thing->player->dest;
2103 thing->player->dest = NULL;
2104 thing->velx = 0;
2105 thing->vely = 0;
2106 thing->z = oldz;
2107 thing->flags6 &= ~MF6_INTRYMOVE;
2108 return false;
2109 }
2110 }
2111 */
2112 }
2113
2114 // [RH] Check status of eyes against fake floor/ceiling in case
2115 // it slopes or the player's eyes are bobbing in and out.
2116
2117 bool oldAboveFakeFloor, oldAboveFakeCeiling;
2118 fixed_t viewheight;
2119
2120 viewheight = thing->player ? thing->player->viewheight : thing->height / 2;
2121 oldAboveFakeFloor = oldAboveFakeCeiling = false; // pacify GCC
2122
2123 if (oldsec->heightsec)
2124 {
2125 fixed_t eyez = oldz + viewheight;
2126
2127 oldAboveFakeFloor = eyez > oldsec->heightsec->floorplane.ZatPoint(thing->x, thing->y);
2128 oldAboveFakeCeiling = eyez > oldsec->heightsec->ceilingplane.ZatPoint(thing->x, thing->y);
2129 }
2130
2131 // Borrowed from MBF:
2132 if (thing->BounceFlags & BOUNCE_MBF && // killough 8/13/98
2133 !(thing->flags & (MF_MISSILE | MF_NOGRAVITY)) &&
2134 !thing->IsSentient() && tm.floorz - thing->z > 16 * FRACUNIT)
2135 { // too big a step up for MBF bouncers under gravity
2136 thing->flags6 &= ~MF6_INTRYMOVE;
2137 return false;
2138 }
2139
2140 // the move is ok, so link the thing into its new position
2141 thing->UnlinkFromWorld();
2142
2143 oldx = thing->x;
2144 oldy = thing->y;
2145 thing->floorz = tm.floorz;
2146 thing->ceilingz = tm.ceilingz;
2147 thing->dropoffz = tm.dropoffz; // killough 11/98: keep track of dropoffs
2148 thing->floorpic = tm.floorpic;
2149 thing->floorsector = tm.floorsector;
2150 thing->ceilingpic = tm.ceilingpic;
2151 thing->ceilingsector = tm.ceilingsector;
2152 thing->x = x;
2153 thing->y = y;
2154
2155 // [BC] Flag this thing as having moved.
2156 thing->ulSTFlags |= STFL_POSITIONCHANGED;
2157
2158 thing->LinkToWorld();
2159
2160 if (thing->flags2 & MF2_FLOORCLIP)
2161 {
2162 thing->AdjustFloorClip();
2163 }
2164 /*
2165 // [RH] Don't activate anything if just predicting
2166 if (thing->player && (thing->player->cheats & CF_PREDICTING))
2167 {
2168 thing->flags6 &= ~MF6_INTRYMOVE;
2169 return true;
2170 }
2171 */
2172 // if any special lines were hit, do the effect
2173 if (!(thing->flags & (MF_TELEPORT | MF_NOCLIP)))
2174 {
2175 while (spechit.Pop(ld))
2176 {
2177 // see if the line was crossed
2178 side = P_PointOnLineSide(thing->x, thing->y, ld);
2179 oldside = P_PointOnLineSide(oldx, oldy, ld);
2180 if (side != oldside && ld->special && !(thing->flags6 & MF6_NOTRIGGER))
2181 {
2182 if (thing->player)
2183 {
2184 P_ActivateLine(ld, thing, oldside, SPAC_Cross);
2185 }
2186 else if (thing->flags2 & MF2_MCROSS)
2187 {
2188 P_ActivateLine(ld, thing, oldside, SPAC_MCross);
2189 }
2190 else if (thing->flags2 & MF2_PCROSS)
2191 {
2192 P_ActivateLine(ld, thing, oldside, SPAC_PCross);
2193 }
2194 else if ((ld->special == Teleport ||
2195 ld->special == Teleport_NoFog ||
2196 ld->special == Teleport_Line))
2197 { // [RH] Just a little hack for BOOM compatibility
2198 P_ActivateLine(ld, thing, oldside, SPAC_MCross);
2199 }
2200 else
2201 {
2202 P_ActivateLine(ld, thing, oldside, SPAC_AnyCross);
2203 }
2204 }
2205 }
2206 }
2207
2208 // [RH] Check for crossing fake floor/ceiling
2209 newsec = thing->Sector;
2210 if (newsec->heightsec && oldsec->heightsec && newsec->SecActTarget)
2211 {
2212 const sector_t* hs = newsec->heightsec;
2213 fixed_t eyez = thing->z + viewheight;
2214 fixed_t fakez = hs->floorplane.ZatPoint(x, y);
2215
2216 if (!oldAboveFakeFloor && eyez > fakez)
2217 { // View went above fake floor
2218 newsec->SecActTarget->TriggerAction(thing, SECSPAC_EyesSurface);
2219 }
2220 else if (oldAboveFakeFloor && eyez <= fakez)
2221 { // View went below fake floor
2222 newsec->SecActTarget->TriggerAction(thing, SECSPAC_EyesDive);
2223 }
2224
2225 if (!(hs->MoreFlags & SECF_FAKEFLOORONLY))
2226 {
2227 fakez = hs->ceilingplane.ZatPoint(x, y);
2228 if (!oldAboveFakeCeiling && eyez > fakez)
2229 { // View went above fake ceiling
2230 newsec->SecActTarget->TriggerAction(thing, SECSPAC_EyesAboveC);
2231 }
2232 else if (oldAboveFakeCeiling && eyez <= fakez)
2233 { // View went below fake ceiling
2234 newsec->SecActTarget->TriggerAction(thing, SECSPAC_EyesBelowC);
2235 }
2236 }
2237 }
2238
2239 // [RH] If changing sectors, trigger transitions
2240 thing->CheckSectorTransition(oldsec);
2241 thing->flags6 &= ~MF6_INTRYMOVE;
2242 return true;
2243
2244pushline:
2245 thing->flags6 &= ~MF6_INTRYMOVE;
2246 /*
2247 // [RH] Don't activate anything if just predicting
2248 if (thing->player && (thing->player->cheats & CF_PREDICTING))
2249 {
2250 return false;
2251 }
2252 */
2253 thing->z = oldz;
2254 if (!(thing->flags & (MF_TELEPORT | MF_NOCLIP)))
2255 {
2256 int numSpecHitTemp;
2257
2258 if (tm.thing->flags2 & MF2_BLASTED)
2259 {
2260 P_DamageMobj(tm.thing, NULL, NULL, tm.thing->Mass >> 5, NAME_Melee);
2261 }
2262 numSpecHitTemp = (int)spechit.Size();
2263 while (numSpecHitTemp > 0)
2264 {
2265 // see which lines were pushed
2266 ld = spechit[--numSpecHitTemp];
2267 side = P_PointOnLineSide(thing->x, thing->y, ld);
2268 CheckForPushSpecial(ld, side, thing, true);
2269 }
2270 }
2271 return false;
2272}
2273
2274bool P_TryMove(AActor* thing, fixed_t x, fixed_t y,
2275 int dropoff, // killough 3/15/98: allow dropoff as option
2276 const secplane_t* onfloor) // [RH] Let P_TryMove keep the thing on the floor
2277{
2278 FCheckPosition tm;
2279 return P_TryMove(thing, x, y, dropoff, onfloor, tm);
2280}
2281
2282//*****************************************************************************
2283//
2284// [BC] ugh old code for people who just have to have doom2.exe style movement.
2285bool P_OldTryMove(AActor* thing, fixed_t x, fixed_t y,
2286 bool dropoff, // killough 3/15/98: allow dropoff as option
2287 bool /*onfloor*/, // [RH] Let P_TryMove keep the thing on the floor
2288 FCheckPosition& tm)
2289{
2290 fixed_t oldx, oldy;
2291 line_t* ld;
2292 int side;
2293 int oldside;
2294
2295 // felldown =
2296 tm.floatok = false; // killough 11/98
2297
2298// if (!P_OldCheckPosition (thing, x, y))
2299 if (!P_CheckPosition(thing, x, y, tm))
2300 return false; // solid wall or thing
2301
2302 if (!(thing->flags & MF_NOCLIP))
2303 {
2304 // killough 7/26/98: reformatted slightly
2305 // killough 8/1/98: Possibly allow escape if otherwise stuck
2306
2307 if (tm.ceilingz - tm.floorz < thing->height || // doesn't fit
2308 // mobj must lower to fit
2309 (tm.floatok = true, !(thing->flags & MF_TELEPORT) &&
2310 tm.ceilingz - thing->z < thing->height) ||
2311 // too big a step up
2312 (!(thing->flags & MF_TELEPORT) &&
2313 tm.floorz - thing->z > 24 * FRACUNIT))
2314 {
2315 return tmunstuck
2316 && !(tm.ceilingline && untouched(tm.ceilingline, tm));
2317 /*&& !( floorline && untouched( floorline));*/
2318 }
2319
2320 /* killough 3/15/98: Allow certain objects to drop off
2321 * killough 7/24/98, 8/1/98:
2322 * Prevent monsters from getting stuck hanging off ledges
2323 * killough 10/98: Allow dropoffs in controlled circumstances
2324 * killough 11/98: Improve symmetry of clipping on stairs
2325 */
2326
2327 if (!(thing->flags & (MF_DROPOFF | MF_FLOAT)))
2328 {
2329 // if (comp[comp_dropoff])
2330 if (1)
2331 {
2332 if ((0/*compatibility*/ || !dropoff) && (tm.floorz - tm.dropoffz > 24 * FRACUNIT))
2333 return false; // don't stand over a dropoff
2334 }
2335 else
2336 {
2337 // [BB] dropoff is a bool, it can't be equal to 2
2338 if (!dropoff || (/*dropoff==2 &&*/ // large jump down (e.g. dogs)
2339 (tm.floorz - tm.dropoffz > 128 * FRACUNIT ||
2340 !thing->target || thing->target->z > tm.dropoffz)))
2341 {
2342 if (/*!monkeys ||*/ /*!mbf_features*/ 1 ?
2343 (tm.floorz - tm.dropoffz > 24 * FRACUNIT) :
2344 thing->floorz - tm.floorz > 24 * FRACUNIT ||
2345 thing->dropoffz - tm.dropoffz > 24 * FRACUNIT)
2346 {
2347 return false;
2348 }
2349 }
2350 else
2351 { /* dropoff allowed -- check for whether it fell more than 24 */
2352// felldown = !(thing->flags & MF_NOGRAVITY) &&
2353// thing->z - tm.floorz > 24*FRACUNIT;
2354 }
2355 }
2356
2357 if (thing->BounceFlags && // killough 8/13/98
2358 !(thing->flags & (MF_MISSILE | MF_NOGRAVITY)) &&
2359 /*!sentient(thing) &&*/ tm.floorz - thing->z > 16 * FRACUNIT)
2360 return false; // too big a step up for bouncers under gravity
2361/*
2362 // killough 11/98: prevent falling objects from going up too many steps
2363 if (thing->intflags & MIF_FALLING && tm.floorz - thing->z >
2364 FixedMul(thing->velx,thing->velx)+FixedMul(thing->vely,thing->vely))
2365 {
2366 return false;
2367 }
2368*/
2369 }
2370 }
2371
2372 // the move is ok,
2373 // so unlink from the old position and link into the new position
2374
2375 thing->UnlinkFromWorld();
2376
2377 oldx = thing->x;
2378 oldy = thing->y;
2379 thing->floorz = tm.floorz;
2380 thing->ceilingz = tm.ceilingz;
2381 thing->dropoffz = tm.dropoffz; // killough 11/98: keep track of dropoffs
2382 thing->x = x;
2383 thing->y = y;
2384
2385 thing->LinkToWorld();
2386
2387 // if any special lines were hit, do the effect
2388 if (!(thing->flags & (MF_TELEPORT | MF_NOCLIP)))
2389 {
2390 while (spechit.Pop(ld))
2391 {
2392 // see if the line was crossed
2393 side = P_PointOnLineSide(thing->x, thing->y, ld);
2394 oldside = P_PointOnLineSide(oldx, oldy, ld);
2395 if (side != oldside && ld->special)
2396 {
2397 if (thing->player)
2398 {
2399 P_ActivateLine(ld, thing, oldside, SPAC_Cross);
2400 }
2401 else if (thing->flags2 & MF2_MCROSS)
2402 {
2403 P_ActivateLine(ld, thing, oldside, SPAC_MCross);
2404 }
2405 else if (thing->flags2 & MF2_PCROSS)
2406 {
2407 P_ActivateLine(ld, thing, oldside, SPAC_PCross);
2408 }
2409 else if ((ld->special == Teleport ||
2410 ld->special == Teleport_NoFog ||
2411 ld->special == Teleport_Line))
2412 { // [RH] Just a little hack for BOOM compatibility
2413 P_ActivateLine(ld, thing, oldside, SPAC_MCross);
2414 }
2415 }
2416 }
2417 }
2418
2419 return true;
2420}
2421
2422//*****************************************************************************
2423//
2424bool P_OldTryMove(AActor* thing, fixed_t x, fixed_t y,
2425 bool dropoff, // killough 3/15/98: allow dropoff as option
2426 bool onfloor) // [RH] Let P_TryMove keep the thing on the floor
2427{
2428 FCheckPosition tm;
2429 return P_OldTryMove(thing, x, y, dropoff, onfloor, tm);
2430}
2431
2432//==========================================================================
2433//
2434// P_CheckMove
2435// Similar to P_TryMove but doesn't actually move the actor. Used for polyobject crushing
2436//
2437//==========================================================================
2438
2439bool P_CheckMove(AActor* thing, fixed_t x, fixed_t y)
2440{
2441 FCheckPosition tm;
2442 fixed_t newz = thing->z;
2443
2444 if (!P_CheckPosition(thing, x, y, tm))
2445 {
2446 return false;
2447 }
2448
2449 if (thing->flags3 & MF3_FLOORHUGGER)
2450 {
2451 newz = tm.floorz;
2452 }
2453 else if (thing->flags3 & MF3_CEILINGHUGGER)
2454 {
2455 newz = tm.ceilingz - thing->height;
2456 }
2457
2458 if (!(thing->flags & MF_NOCLIP))
2459 {
2460 if (tm.ceilingz - tm.floorz < thing->height)
2461 {
2462 return false;
2463 }
2464
2465 if (!(thing->flags & MF_TELEPORT)
2466 && tm.ceilingz - newz < thing->height
2467 && !(thing->flags3 & MF3_CEILINGHUGGER)
2468 && (!(thing->flags2 & MF2_FLY) || !(thing->flags & MF_NOGRAVITY)))
2469 {
2470 return false;
2471 }
2472 if (thing->flags2 & MF2_FLY && thing->flags & MF_NOGRAVITY)
2473 {
2474 if (thing->z + thing->height > tm.ceilingz)
2475 return false;
2476 }
2477 if (!(thing->flags & MF_TELEPORT) && !(thing->flags3 & MF3_FLOORHUGGER))
2478 {
2479 if (tm.floorz - newz > thing->MaxStepHeight)
2480 { // too big a step up
2481 return false;
2482 }
2483 else if ((thing->flags & MF_MISSILE) && !(thing->flags6 & MF6_STEPMISSILE) && tm.floorz > newz)
2484 { // [RH] Don't let normal missiles climb steps
2485 return false;
2486 }
2487 else if (newz < tm.floorz)
2488 { // [RH] Check to make sure there's nothing in the way for the step up
2489 fixed_t savedz = thing->z;
2490 thing->z = newz = tm.floorz;
2491 bool good = P_TestMobjZ(thing);
2492 thing->z = savedz;
2493 if (!good)
2494 {
2495 return false;
2496 }
2497 }
2498 }
2499
2500 if (thing->flags2 & MF2_CANTLEAVEFLOORPIC
2501 && (tm.floorpic != thing->floorpic
2502 || tm.floorz - newz != 0))
2503 { // must stay within a sector of a certain floor type
2504 return false;
2505 }
2506 }
2507
2508 return true;
2509}
2510
2511
2512
2513//==========================================================================
2514//
2515// SLIDE MOVE
2516// Allows the player to slide along any angled walls.
2517//
2518//==========================================================================
2519
2520struct FSlide
2521{
2522 fixed_t bestslidefrac;
2523 fixed_t secondslidefrac;
2524
2525 line_t* bestslideline;
2526 line_t* secondslideline;
2527
2528 AActor* slidemo;
2529
2530 fixed_t tmxmove;
2531 fixed_t tmymove;
2532
2533 void HitSlideLine(line_t* ld);
2534 void SlideTraverse(fixed_t startx, fixed_t starty, fixed_t endx, fixed_t endy);
2535 void SlideMove(AActor* mo, fixed_t tryx, fixed_t tryy, int numsteps);
2536
2537 // [BB] Old code for people who just have to have doom2.exe style movement, converted to work with the latest ZDoom version.
2538 void OldHitSlideLine(line_t* ld);
2539 void OldSlideMove(AActor* mo);
2540 void OldSlideTraverse(fixed_t startx, fixed_t starty, fixed_t endx, fixed_t endy);
2541
2542 // The bouncing code uses the same data structure
2543 bool BounceTraverse(fixed_t startx, fixed_t starty, fixed_t endx, fixed_t endy);
2544 bool BounceWall(AActor* mo);
2545};
2546
2547//==========================================================================
2548//
2549// P_HitSlideLine
2550// Adjusts the xmove / ymove
2551// so that the next move will slide along the wall.
2552// If the floor is icy, then you can bounce off a wall. // phares
2553//
2554//==========================================================================
2555
2556void FSlide::HitSlideLine(line_t* ld)
2557{
2558 int side;
2559
2560 angle_t lineangle;
2561 angle_t moveangle;
2562 angle_t deltaangle;
2563
2564 fixed_t movelen;
2565 bool icyfloor; // is floor icy? // phares
2566 // |
2567 // Under icy conditions, if the angle of approach to the wall // V
2568 // is more than 45 degrees, then you'll bounce and lose half
2569 // your velocity. If less than 45 degrees, you'll slide along
2570 // the wall. 45 is arbitrary and is believable.
2571
2572 // Check for the special cases of horz or vert walls.
2573
2574 // killough 10/98: only bounce if hit hard (prevents wobbling)
2575 icyfloor =
2576 (P_AproxDistance(tmxmove, tmymove) > 4 * FRACUNIT) &&
2577 var_friction && // killough 8/28/98: calc friction on demand
2578 slidemo->z <= slidemo->floorz &&
2579 P_GetFriction(slidemo, NULL) > ORIG_FRICTION;
2580
2581 if (ld->slopetype == ST_HORIZONTAL)
2582 {
2583 if (icyfloor && (abs(tmymove) > abs(tmxmove)))
2584 {
2585 tmxmove /= 2; // absorb half the velocity
2586 tmymove = -tmymove / 2;
2587 // [BB] Adapted to Skulltag's prediction.
2588 if (slidemo->player && (slidemo->player->bSpectating == false) && slidemo->health > 0 && (CLIENT_PREDICT_IsPredicting() == false))// && !(slidemo->player->cheats & CF_PREDICTING))
2589 {
2590 S_Sound(slidemo, CHAN_VOICE, "*grunt", 1, ATTN_IDLE); // oooff!
2591
2592 // [BC] Tell clients of the "oof" sound.
2593 // [BB] Skip the player who made the sound, he plays it himself while predicting.
2594 if (NETWORK_GetState() == NETSTATE_SERVER)
2595 SERVERCOMMANDS_SoundActor(slidemo, CHAN_VOICE, "*grunt", 1, ATTN_IDLE, static_cast<ULONG> (slidemo->player - players), SVCF_SKIPTHISCLIENT);
2596 }
2597 }
2598 else
2599 tmymove = 0; // no more movement in the Y direction
2600 return;
2601 }
2602
2603 if (ld->slopetype == ST_VERTICAL)
2604 {
2605 if (icyfloor && (abs(tmxmove) > abs(tmymove)))
2606 {
2607 tmxmove = -tmxmove / 2; // absorb half the velocity
2608 tmymove /= 2;
2609 // [BB/WS] Adapted to Skulltag's prediction.
2610 if (slidemo->player && (slidemo->player->bSpectating == false) && slidemo->health > 0 && (CLIENT_PREDICT_IsPredicting() == false))// && !(slidemo->player->cheats & CF_PREDICTING))
2611 {
2612 S_Sound(slidemo, CHAN_VOICE, "*grunt", 1, ATTN_IDLE); // oooff!// ^
2613
2614 // [BC] Tell clients of the "oof" sound.
2615 // [BB/WS] Skip the player who made the sound, he plays it himself while predicting.
2616 if (NETWORK_GetState() == NETSTATE_SERVER)
2617 SERVERCOMMANDS_SoundActor(slidemo, CHAN_VOICE, "*grunt", 1, ATTN_IDLE, static_cast<ULONG> (slidemo->player - players), SVCF_SKIPTHISCLIENT);
2618 }
2619 } // |
2620 else // phares
2621 tmxmove = 0; // no more movement in the X direction
2622 return;
2623 }
2624
2625 // The wall is angled. Bounce if the angle of approach is // phares
2626 // less than 45 degrees. // phares
2627
2628 side = P_PointOnLineSide(slidemo->x, slidemo->y, ld);
2629
2630 lineangle = R_PointToAngle2(0, 0, ld->dx, ld->dy);
2631
2632 if (side == 1)
2633 lineangle += ANG180;
2634
2635 moveangle = R_PointToAngle2(0, 0, tmxmove, tmymove);
2636
2637 moveangle += 10; // prevents sudden path reversal due to // phares
2638 // rounding error // |
2639 deltaangle = moveangle - lineangle; // V
2640 movelen = P_AproxDistance(tmxmove, tmymove);
2641 if (icyfloor && (deltaangle > ANG45) && (deltaangle < ANG90 + ANG45))
2642 {
2643 moveangle = lineangle - deltaangle;
2644 movelen /= 2; // absorb
2645 // [BB/WS] Adapted to Skulltag's prediction.
2646 if (slidemo->player && (slidemo->player->bSpectating == false) && slidemo->health > 0 && (CLIENT_PREDICT_IsPredicting() == false))// && !(slidemo->player->cheats & CF_PREDICTING))
2647 {
2648 S_Sound(slidemo, CHAN_VOICE, "*grunt", 1, ATTN_IDLE); // oooff!
2649
2650 // [WS] Tell clients of the "oof" sound.
2651 // [BB/WS] Skip the player who made the sound, he plays it himself while predicting.
2652 if (NETWORK_GetState() == NETSTATE_SERVER)
2653 SERVERCOMMANDS_SoundActor(slidemo, CHAN_VOICE, "*grunt", 1, ATTN_IDLE, static_cast<ULONG> (slidemo->player - players), SVCF_SKIPTHISCLIENT);
2654 }
2655 moveangle >>= ANGLETOFINESHIFT;
2656 tmxmove = FixedMul(movelen, finecosine[moveangle]);
2657 tmymove = FixedMul(movelen, finesine[moveangle]);
2658 } // ^
2659 else // |
2660 { // phares
2661 // Doom's original algorithm here does not work well due to imprecisions of the sine table.
2662 // However, keep it active if the wallrunning compatibility flag is on
2663 if (i_compatflags & COMPATF_WALLRUN)
2664 {
2665 fixed_t newlen;
2666
2667 if (deltaangle > ANG180)
2668 deltaangle += ANG180;
2669 // I_Error ("SlideLine: ang>ANG180");
2670
2671 lineangle >>= ANGLETOFINESHIFT;
2672 deltaangle >>= ANGLETOFINESHIFT;
2673
2674 newlen = FixedMul(movelen, finecosine[deltaangle]);
2675
2676 tmxmove = FixedMul(newlen, finecosine[lineangle]);
2677 tmymove = FixedMul(newlen, finesine[lineangle]);
2678 }
2679 else
2680 {
2681 divline_t dll, dlv;
2682 fixed_t inter1, inter2, inter3;
2683
2684 P_MakeDivline(ld, &dll);
2685
2686 dlv.x = slidemo->x;
2687 dlv.y = slidemo->y;
2688 dlv.dx = dll.dy;
2689 dlv.dy = -dll.dx;
2690
2691 inter1 = P_InterceptVector(&dll, &dlv);
2692
2693 dlv.dx = tmxmove;
2694 dlv.dy = tmymove;
2695 inter2 = P_InterceptVector(&dll, &dlv);
2696 inter3 = P_InterceptVector(&dlv, &dll);
2697
2698 if (inter3 != 0)
2699 {
2700 tmxmove = Scale(inter2 - inter1, dll.dx, inter3);
2701 tmymove = Scale(inter2 - inter1, dll.dy, inter3);
2702 }
2703 else
2704 {
2705 tmxmove = tmymove = 0;
2706 }
2707 }
2708 } // phares
2709}
2710
2711// [BC] ugh old code for people who just have to have doom2.exe style movement.
2712void FSlide::OldHitSlideLine(line_t* ld)
2713{
2714 int side;
2715 angle_t lineangle;
2716 angle_t moveangle;
2717 angle_t deltaangle;
2718 fixed_t movelen;
2719 fixed_t newlen;
2720 bool icyfloor; // is floor icy? // phares
2721 // |
2722 // Under icy conditions, if the angle of approach to the wall // V
2723 // is more than 45 degrees, then you'll bounce and lose half
2724 // your momentum. If less than 45 degrees, you'll slide along
2725 // the wall. 45 is arbitrary and is believable.
2726
2727 // Check for the special cases of horz or vert walls.
2728
2729 /* killough 10/98: only bounce if hit hard (prevents wobbling)
2730 * cph - DEMOSYNC - should only affect players in Boom demos? */
2731 icyfloor = 0;/*
2732 (mbf_features ?
2733 P_AproxDistance(tmxmove, tmymove) > 4*FRACUNIT : !compatibility) &&
2734 variable_friction && // killough 8/28/98: calc friction on demand
2735 slidemo->z <= slidemo->floorz &&
2736 P_GetFriction(slidemo, NULL) > ORIG_FRICTION;
2737 */
2738 if (ld->slopetype == ST_HORIZONTAL)
2739 {
2740 if (icyfloor && (abs(tmymove) > abs(tmxmove)))
2741 {
2742 tmxmove /= 2; // absorb half the momentum
2743 tmymove = -tmymove / 2;
2744 //S_StartSound(slidemo,sfx_oof); // oooff!
2745 }
2746 else
2747 tmymove = 0; // no more movement in the Y direction
2748 return;
2749 }
2750
2751 if (ld->slopetype == ST_VERTICAL)
2752 {
2753 if (icyfloor && (abs(tmxmove) > abs(tmymove)))
2754 {
2755 tmxmove = -tmxmove / 2; // absorb half the momentum
2756 tmymove /= 2;
2757 //S_StartSound(slidemo,sfx_oof); // oooff! // ^
2758 } // |
2759 else // phares
2760 tmxmove = 0; // no more movement in the X direction
2761 return;
2762 }
2763
2764 // The wall is angled. Bounce if the angle of approach is // phares
2765 // less than 45 degrees. // phares
2766
2767 side = P_PointOnLineSide(slidemo->x, slidemo->y, ld);
2768
2769 lineangle = R_PointToAngle2(0, 0, ld->dx, ld->dy);
2770 if (side == 1)
2771 lineangle += ANG180;
2772 moveangle = R_PointToAngle2(0, 0, tmxmove, tmymove);
2773
2774 // killough 3/2/98:
2775 // The moveangle+=10 breaks v1.9 demo compatibility in
2776 // some demos, so it needs demo_compatibility switch.
2777
2778 if (0)//!demo_compatibility)
2779 moveangle += 10; // prevents sudden path reversal due to // phares
2780 // rounding error // |
2781 deltaangle = moveangle - lineangle; // V
2782 movelen = P_AproxDistance(tmxmove, tmymove);
2783 if (icyfloor && (deltaangle > ANG45) && (deltaangle < ANG90 + ANG45))
2784 {
2785 moveangle = lineangle - deltaangle;
2786 movelen /= 2; // absorb
2787 //S_StartSound(slidemo,sfx_oof); // oooff!
2788 moveangle >>= ANGLETOFINESHIFT;
2789 tmxmove = FixedMul(movelen, finecosine[moveangle]);
2790 tmymove = FixedMul(movelen, finesine[moveangle]);
2791 } // ^
2792 else // |
2793 { // phares
2794 if (deltaangle > ANG180)
2795 deltaangle += ANG180;
2796
2797 // I_Error ("SlideLine: ang>ANG180");
2798
2799 lineangle >>= ANGLETOFINESHIFT;
2800 deltaangle >>= ANGLETOFINESHIFT;
2801 newlen = FixedMul(movelen, finecosine[deltaangle]);
2802 tmxmove = FixedMul(newlen, finecosine[lineangle]);
2803 tmymove = FixedMul(newlen, finesine[lineangle]);
2804 } // phares
2805}
2806
2807
2808//==========================================================================
2809//
2810// PTR_SlideTraverse
2811//
2812//==========================================================================
2813
2814void FSlide::SlideTraverse(fixed_t startx, fixed_t starty, fixed_t endx, fixed_t endy)
2815{
2816 FLineOpening open;
2817 FPathTraverse it(startx, starty, endx, endy, PT_ADDLINES);
2818 intercept_t* in;
2819
2820 while ((in = it.Next()))
2821 {
2822 line_t* li;
2823
2824 if (!in->isaline)
2825 {
2826 // should never happen
2827 Printf("PTR_SlideTraverse: not a line?");
2828 continue;
2829 }
2830
2831 li = in->d.line;
2832
2833 if (!(li->flags & ML_TWOSIDED) || !li->backsector)
2834 {
2835 if (P_PointOnLineSide(slidemo->x, slidemo->y, li))
2836 {
2837 // don't hit the back side
2838 continue;
2839 }
2840 goto isblocking;
2841 }
2842 if (li->flags & (ML_BLOCKING | ML_BLOCKEVERYTHING))
2843 {
2844 goto isblocking;
2845 }
2846 if (li->flags & ML_BLOCK_PLAYERS && slidemo->player != NULL)
2847 {
2848 goto isblocking;
2849 }
2850 if (li->flags & ML_BLOCKMONSTERS && !((slidemo->flags3 & MF3_NOBLOCKMONST)
2851 || ((i_compatflags & COMPATF_NOBLOCKFRIENDS) && (slidemo->flags & MF_FRIENDLY))))
2852 {
2853 goto isblocking;
2854 }
2855
2856 // set openrange, opentop, openbottom
2857 P_LineOpening(open, slidemo, li, it.Trace().x + FixedMul(it.Trace().dx, in->frac),
2858 it.Trace().y + FixedMul(it.Trace().dy, in->frac));
2859
2860 if (open.range < slidemo->height)
2861 goto isblocking; // doesn't fit
2862
2863 if (open.top - slidemo->z < slidemo->height)
2864 goto isblocking; // mobj is too high
2865
2866 if (open.bottom - slidemo->z > slidemo->MaxStepHeight)
2867 {
2868 goto isblocking; // too big a step up
2869 }
2870 else if (slidemo->z < open.bottom)
2871 { // [RH] Check to make sure there's nothing in the way for the step up
2872 fixed_t savedz = slidemo->z;
2873 slidemo->z = open.bottom;
2874 bool good = P_TestMobjZ(slidemo);
2875 slidemo->z = savedz;
2876 if (!good)
2877 {
2878 goto isblocking;
2879 }
2880 }
2881
2882 // this line doesn't block movement
2883 continue;
2884
2885 // the line does block movement,
2886 // see if it is closer than best so far
2887 isblocking:
2888 if (in->frac < bestslidefrac)
2889 {
2890 secondslidefrac = bestslidefrac;
2891 secondslideline = bestslideline;
2892 bestslidefrac = in->frac;
2893 bestslideline = li;
2894 }
2895
2896 return; // stop
2897 }
2898}
2899
2900// [BC] ugh old code for people who just have to have doom2.exe style movement.
2901void FSlide::OldSlideTraverse(fixed_t startx, fixed_t starty, fixed_t endx, fixed_t endy)
2902{
2903 line_t* li;
2904 FLineOpening open;
2905
2906 FPathTraverse it(startx, starty, endx, endy, PT_ADDLINES);
2907 intercept_t* in;
2908
2909 while ((in = it.Next()))
2910 {
2911 if (!in->isaline)
2912 I_Error("PTR_SlideTraverse: not a line?");
2913
2914 li = in->d.line;
2915
2916 if (!(li->flags & ML_TWOSIDED))
2917 {
2918 if (P_PointOnLineSide(slidemo->x, slidemo->y, li))
2919 continue; // don't hit the back side
2920 goto isblocking;
2921 }
2922
2923 // set openrange, opentop, openbottom.
2924 // These define a 'window' from one sector to another across a line
2925
2926 // P_LineOpening (li);
2927
2928 P_LineOpening(open, slidemo, li, it.Trace().x + FixedMul(it.Trace().dx, in->frac),
2929 it.Trace().y + FixedMul(it.Trace().dy, in->frac)); // set openrange, opentop, openbottom
2930
2931 if (open.range < slidemo->height)
2932 goto isblocking; // doesn't fit
2933
2934 if (open.top - slidemo->z < slidemo->height)
2935 goto isblocking; // mobj is too high
2936
2937 if (open.bottom - slidemo->z > 24 * FRACUNIT)
2938 goto isblocking; // too big a step up
2939
2940 // this line doesn't block movement
2941
2942 continue;
2943
2944 // the line does block movement,
2945 // see if it is closer than best so far
2946
2947 isblocking:
2948
2949 if (in->frac < bestslidefrac)
2950 {
2951 secondslidefrac = bestslidefrac;
2952 secondslideline = bestslideline;
2953 bestslidefrac = in->frac;
2954 bestslideline = li;
2955 }
2956
2957 return; // stop
2958 }
2959}
2960
2961
2962//==========================================================================
2963//
2964// P_SlideMove
2965//
2966// The velx / vely move is bad, so try to slide along a wall.
2967//
2968// Find the first line hit, move flush to it, and slide along it
2969//
2970// This is a kludgy mess.
2971//
2972//==========================================================================
2973
2974void FSlide::SlideMove(AActor* mo, fixed_t tryx, fixed_t tryy, int numsteps)
2975{
2976 fixed_t leadx, leady;
2977 fixed_t trailx, traily;
2978 fixed_t newx, newy;
2979 fixed_t xmove, ymove;
2980 const secplane_t* walkplane;
2981 int hitcount;
2982
2983 hitcount = 3;
2984 slidemo = mo;
2985
2986 if (mo->player && mo->player->mo == mo && mo->reactiontime > 0)
2987 return; // player coming right out of a teleporter.
2988
2989retry:
2990 if (!--hitcount)
2991 goto stairstep; // don't loop forever
2992
2993 // trace along the three leading corners
2994 if (tryx > 0)
2995 {
2996 leadx = mo->x + mo->radius;
2997 trailx = mo->x - mo->radius;
2998 }
2999 else
3000 {
3001 leadx = mo->x - mo->radius;
3002 trailx = mo->x + mo->radius;
3003 }
3004
3005 if (tryy > 0)
3006 {
3007 leady = mo->y + mo->radius;
3008 traily = mo->y - mo->radius;
3009 }
3010 else
3011 {
3012 leady = mo->y - mo->radius;
3013 traily = mo->y + mo->radius;
3014 }
3015
3016 bestslidefrac = FRACUNIT + 1;
3017
3018 SlideTraverse(leadx, leady, leadx + tryx, leady + tryy);
3019 SlideTraverse(trailx, leady, trailx + tryx, leady + tryy);
3020 SlideTraverse(leadx, traily, leadx + tryx, traily + tryy);
3021
3022 // move up to the wall
3023 if (bestslidefrac == FRACUNIT + 1)
3024 {
3025 // the move must have hit the middle, so stairstep
3026 stairstep:
3027 // killough 3/15/98: Allow objects to drop off ledges
3028 xmove = 0, ymove = tryy;
3029 walkplane = P_CheckSlopeWalk(mo, xmove, ymove);
3030 if (!P_TryMove(mo, mo->x + xmove, mo->y + ymove, true, walkplane))
3031 {
3032 xmove = tryx, ymove = 0;
3033 walkplane = P_CheckSlopeWalk(mo, xmove, ymove);
3034 P_TryMove(mo, mo->x + xmove, mo->y + ymove, true, walkplane);
3035 }
3036 return;
3037 }
3038
3039 // fudge a bit to make sure it doesn't hit
3040 bestslidefrac -= FRACUNIT / 32;
3041 if (bestslidefrac > 0)
3042 {
3043 newx = FixedMul(tryx, bestslidefrac);
3044 newy = FixedMul(tryy, bestslidefrac);
3045
3046 // [BL] We need to abandon this function if we end up going through a teleporter
3047 const fixed_t startvelx = mo->velx;
3048 const fixed_t startvely = mo->vely;
3049
3050 // killough 3/15/98: Allow objects to drop off ledges
3051 if (!P_TryMove(mo, mo->x + newx, mo->y + newy, true))
3052 goto stairstep;
3053
3054 if (mo->velx != startvelx || mo->vely != startvely)
3055 return;
3056 }
3057
3058 // Now continue along the wall.
3059 bestslidefrac = FRACUNIT - (bestslidefrac + FRACUNIT / 32); // remainder
3060 if (bestslidefrac > FRACUNIT)
3061 bestslidefrac = FRACUNIT;
3062 else if (bestslidefrac <= 0)
3063 return;
3064
3065 tryx = tmxmove = FixedMul(tryx, bestslidefrac);
3066 tryy = tmymove = FixedMul(tryy, bestslidefrac);
3067
3068 HitSlideLine(bestslideline); // clip the moves
3069
3070 mo->velx = tmxmove * numsteps;
3071 mo->vely = tmymove * numsteps;
3072
3073 // killough 10/98: affect the bobbing the same way (but not voodoo dolls)
3074 if (mo->player && mo->player->mo == mo)
3075 {
3076 if (abs(mo->player->velx) > abs(mo->velx))
3077 mo->player->velx = mo->velx;
3078 if (abs(mo->player->vely) > abs(mo->vely))
3079 mo->player->vely = mo->vely;
3080 }
3081
3082 walkplane = P_CheckSlopeWalk(mo, tmxmove, tmymove);
3083
3084 // killough 3/15/98: Allow objects to drop off ledges
3085 if (!P_TryMove(mo, mo->x + tmxmove, mo->y + tmymove, true, walkplane))
3086 {
3087 goto retry;
3088 }
3089}
3090
3091void P_SlideMove(AActor* mo, fixed_t tryx, fixed_t tryy, int numsteps)
3092{
3093 FSlide slide;
3094 slide.SlideMove(mo, tryx, tryy, numsteps);
3095}
3096
3097//*****************************************************************************
3098//
3099// [BC] ugh old code for people who just have to have doom2.exe style movement.
3100void FSlide::OldSlideMove(AActor* mo)
3101{
3102 int hitcount = 3;
3103
3104 slidemo = mo; // the object that's sliding
3105
3106 do
3107 {
3108 fixed_t leadx, leady, trailx, traily;
3109
3110 if (!--hitcount)
3111 goto stairstep; // don't loop forever
3112
3113 // trace along the three leading corners
3114
3115 if (mo->velx > 0)
3116 leadx = mo->x + mo->radius, trailx = mo->x - mo->radius;
3117 else
3118 leadx = mo->x - mo->radius, trailx = mo->x + mo->radius;
3119
3120 if (mo->vely > 0)
3121 leady = mo->y + mo->radius, traily = mo->y - mo->radius;
3122 else
3123 leady = mo->y - mo->radius, traily = mo->y + mo->radius;
3124
3125 bestslidefrac = FRACUNIT + 1;
3126
3127 OldSlideTraverse(leadx, leady, leadx + mo->velx, leady + mo->vely);
3128 OldSlideTraverse(trailx, leady, trailx + mo->velx, leady + mo->vely);
3129 OldSlideTraverse(leadx, traily, leadx + mo->velx, traily + mo->vely);
3130
3131 // move up to the wall
3132
3133 if (bestslidefrac == FRACUNIT + 1)
3134 {
3135 // the move must have hit the middle, so stairstep
3136
3137 stairstep:
3138
3139 /* killough 3/15/98: Allow objects to drop off ledges
3140 *
3141 * phares 5/4/98: kill momentum if you can't move at all
3142 * This eliminates player bobbing if pressed against a wall
3143 * while on ice.
3144 *
3145 * killough 10/98: keep buggy code around for old Boom demos
3146 *
3147 * cph 2000/09//23: buggy code was only in Boom v2.01
3148 */
3149
3150 if (!P_OldTryMove(mo, mo->x, mo->y + mo->vely, true))
3151 if (!P_OldTryMove(mo, mo->x + mo->velx, mo->y, true))
3152 if (0)//compatibility_level == boom_201_compatibility)
3153 mo->velx = mo->vely = 0;
3154
3155 break;
3156 }
3157
3158 // fudge a bit to make sure it doesn't hit
3159
3160 if ((bestslidefrac -= 0x800) > 0)
3161 {
3162 fixed_t newx = FixedMul(mo->velx, bestslidefrac);
3163 fixed_t newy = FixedMul(mo->vely, bestslidefrac);
3164
3165 // killough 3/15/98: Allow objects to drop off ledges
3166
3167 if (!P_OldTryMove(mo, mo->x + newx, mo->y + newy, true))
3168 goto stairstep;
3169 }
3170
3171 // Now continue along the wall.
3172 // First calculate remainder.
3173
3174 bestslidefrac = FRACUNIT - (bestslidefrac + 0x800);
3175
3176 if (bestslidefrac > FRACUNIT)
3177 bestslidefrac = FRACUNIT;
3178
3179 if (bestslidefrac <= 0)
3180 break;
3181
3182 tmxmove = FixedMul(mo->velx, bestslidefrac);
3183 tmymove = FixedMul(mo->vely, bestslidefrac);
3184
3185 OldHitSlideLine(bestslideline); // clip the moves
3186
3187 mo->velx = tmxmove;
3188 mo->vely = tmymove;
3189
3190 /* killough 10/98: affect the bobbing the same way (but not voodoo dolls)
3191 * cph - DEMOSYNC? */
3192 if (mo->player && mo->player->mo == mo)
3193 {
3194 if (abs(mo->player->velx) > abs(tmxmove))
3195 mo->player->velx = tmxmove;
3196 if (abs(mo->player->vely) > abs(tmymove))
3197 mo->player->vely = tmymove;
3198 }
3199 } // killough 3/15/98: Allow objects to drop off ledges:
3200 while (!P_OldTryMove(mo, mo->x + tmxmove, mo->y + tmymove, true));
3201}
3202
3203//*****************************************************************************
3204//
3205void P_OldSlideMove(AActor* mo)
3206{
3207 FSlide slide;
3208 slide.OldSlideMove(mo);
3209}
3210
3211//============================================================================
3212//
3213// P_CheckSlopeWalk
3214//
3215//============================================================================
3216
3217const secplane_t* P_CheckSlopeWalk(AActor* actor, fixed_t& xmove, fixed_t& ymove)
3218{
3219 static secplane_t copyplane;
3220 if (actor->flags & MF_NOGRAVITY)
3221 {
3222 return NULL;
3223 }
3224
3225 const secplane_t* plane = &actor->floorsector->floorplane;
3226 fixed_t planezhere = plane->ZatPoint(actor->x, actor->y);
3227
3228#ifdef _3DFLOORS
3229 for (unsigned int i = 0; i < actor->floorsector->e->XFloor.ffloors.Size(); i++)
3230 {
3231 F3DFloor* rover = actor->floorsector->e->XFloor.ffloors[i];
3232 if (!(rover->flags & FF_SOLID) || !(rover->flags & FF_EXISTS)) continue;
3233
3234 fixed_t thisplanez = rover->top.plane->ZatPoint(actor->x, actor->y);
3235
3236 if (thisplanez > planezhere && thisplanez <= actor->z + actor->MaxStepHeight)
3237 {
3238 copyplane = *rover->top.plane;
3239 if (copyplane.c < 0) copyplane.FlipVert();
3240 plane = ©plane;
3241 planezhere = thisplanez;
3242 }
3243 }
3244
3245 if (actor->floorsector != actor->Sector)
3246 {
3247 for (unsigned int i = 0; i < actor->Sector->e->XFloor.ffloors.Size(); i++)
3248 {
3249 F3DFloor* rover = actor->Sector->e->XFloor.ffloors[i];
3250 if (!(rover->flags & FF_SOLID) || !(rover->flags & FF_EXISTS)) continue;
3251
3252 fixed_t thisplanez = rover->top.plane->ZatPoint(actor->x, actor->y);
3253
3254 if (thisplanez > planezhere && thisplanez <= actor->z + actor->MaxStepHeight)
3255 {
3256 copyplane = *rover->top.plane;
3257 if (copyplane.c < 0) copyplane.FlipVert();
3258 plane = ©plane;
3259 planezhere = thisplanez;
3260 }
3261 }
3262 }
3263#endif
3264
3265 if (actor->floorsector != actor->Sector)
3266 {
3267 // this additional check prevents sliding on sloped dropoffs
3268 if (planezhere > actor->floorz + 4 * FRACUNIT)
3269 return NULL;
3270 }
3271
3272 if (actor->z - planezhere > FRACUNIT)
3273 { // not on floor
3274 return NULL;
3275 }
3276
3277 if ((plane->a | plane->b) != 0)
3278 {
3279 fixed_t destx, desty;
3280 fixed_t t;
3281
3282 destx = actor->x + xmove;
3283 desty = actor->y + ymove;
3284 t = TMulScale16(plane->a, destx, plane->b, desty, plane->c, actor->z) + plane->d;
3285 if (t < 0)
3286 { // Desired location is behind (below) the plane
3287 // (i.e. Walking up the plane)
3288 if (plane->c < STEEPSLOPE)
3289 { // Can't climb up slopes of ~45 degrees or more
3290 if (actor->flags & MF_NOCLIP)
3291 {
3292 return (actor->floorsector == actor->Sector) ? plane : NULL;
3293 }
3294 else
3295 {
3296 const msecnode_t* node;
3297 bool dopush = true;
3298
3299 if (plane->c > STEEPSLOPE * 2 / 3)
3300 {
3301 for (node = actor->touching_sectorlist; node; node = node->m_tnext)
3302 {
3303 const sector_t* sec = node->m_sector;
3304 if (sec->floorplane.c >= STEEPSLOPE)
3305 {
3306 if (sec->floorplane.ZatPoint(destx, desty) >= actor->z - actor->MaxStepHeight)
3307 {
3308 dopush = false;
3309 break;
3310 }
3311 }
3312 }
3313 }
3314 if (dopush)
3315 {
3316 xmove = actor->velx = plane->a * 2;
3317 ymove = actor->vely = plane->b * 2;
3318 }
3319 return (actor->floorsector == actor->Sector) ? plane : NULL;
3320 }
3321 }
3322 // Slide the desired location along the plane's normal
3323 // so that it lies on the plane's surface
3324 destx -= FixedMul(plane->a, t);
3325 desty -= FixedMul(plane->b, t);
3326 xmove = destx - actor->x;
3327 ymove = desty - actor->y;
3328 return (actor->floorsector == actor->Sector) ? plane : NULL;
3329 }
3330 else if (t > 0)
3331 { // Desired location is in front of (above) the plane
3332 if (planezhere == actor->z)
3333 { // Actor's current spot is on/in the plane, so walk down it
3334 // Same principle as walking up, except reversed
3335 destx += FixedMul(plane->a, t);
3336 desty += FixedMul(plane->b, t);
3337 xmove = destx - actor->x;
3338 ymove = desty - actor->y;
3339 return (actor->floorsector == actor->Sector) ? plane : NULL;
3340 }
3341 }
3342 }
3343 return NULL;
3344}
3345
3346//============================================================================
3347//
3348// PTR_BounceTraverse
3349//
3350//============================================================================
3351
3352bool FSlide::BounceTraverse(fixed_t startx, fixed_t starty, fixed_t endx, fixed_t endy)
3353{
3354 FLineOpening open;
3355 FPathTraverse it(startx, starty, endx, endy, PT_ADDLINES);
3356 intercept_t* in;
3357
3358 while ((in = it.Next()))
3359 {
3360
3361 line_t* li;
3362
3363 if (!in->isaline)
3364 {
3365 Printf("PTR_BounceTraverse: not a line?");
3366 continue;
3367 }
3368
3369 li = in->d.line;
3370 assert(((size_t)li - (size_t)lines) % sizeof(line_t) == 0);
3371 if (li->flags & ML_BLOCKEVERYTHING)
3372 {
3373 goto bounceblocking;
3374 }
3375 if (!(li->flags & ML_TWOSIDED) || !li->backsector)
3376 {
3377 if (P_PointOnLineSide(slidemo->x, slidemo->y, li))
3378 continue; // don't hit the back side
3379 goto bounceblocking;
3380 }
3381
3382
3383 P_LineOpening(open, slidemo, li, it.Trace().x + FixedMul(it.Trace().dx, in->frac),
3384 it.Trace().y + FixedMul(it.Trace().dy, in->frac)); // set openrange, opentop, openbottom
3385 if (open.range < slidemo->height)
3386 goto bounceblocking; // doesn't fit
3387
3388 if (open.top - slidemo->z < slidemo->height)
3389 goto bounceblocking; // mobj is too high
3390
3391 if (open.bottom > slidemo->z)
3392 goto bounceblocking; // mobj is too low
3393
3394 continue; // this line doesn't block movement
3395
3396 // the line does block movement, see if it is closer than best so far
3397 bounceblocking:
3398 if (in->frac < bestslidefrac)
3399 {
3400 secondslidefrac = bestslidefrac;
3401 secondslideline = bestslideline;
3402 bestslidefrac = in->frac;
3403 bestslideline = li;
3404 }
3405 return false; // stop
3406 }
3407 return true;
3408}
3409
3410//============================================================================
3411//
3412// P_BounceWall
3413//
3414//============================================================================
3415
3416bool FSlide::BounceWall(AActor* mo)
3417{
3418 fixed_t leadx, leady;
3419 int side;
3420 angle_t lineangle, moveangle, deltaangle;
3421 fixed_t movelen;
3422 line_t* line;
3423
3424 if (!(mo->BounceFlags & BOUNCE_Walls))
3425 {
3426 return false;
3427 }
3428
3429 slidemo = mo;
3430 //
3431 // trace along the three leading corners
3432 //
3433 if (mo->velx > 0)
3434 {
3435 leadx = mo->x + mo->radius;
3436 }
3437 else
3438 {
3439 leadx = mo->x - mo->radius;
3440 }
3441 if (mo->vely > 0)
3442 {
3443 leady = mo->y + mo->radius;
3444 }
3445 else
3446 {
3447 leady = mo->y - mo->radius;
3448 }
3449 bestslidefrac = FRACUNIT + 1;
3450 bestslideline = mo->BlockingLine;
3451 if (BounceTraverse(leadx, leady, leadx + mo->velx, leady + mo->vely) && mo->BlockingLine == NULL)
3452 { // Could not find a wall, so bounce off the floor/ceiling instead.
3453 fixed_t floordist = mo->z - mo->floorz;
3454 fixed_t ceildist = mo->ceilingz - mo->z;
3455 if (floordist <= ceildist)
3456 {
3457 mo->FloorBounceMissile(mo->Sector->floorplane);
3458 return true;
3459 }
3460 else
3461 {
3462 mo->FloorBounceMissile(mo->Sector->ceilingplane);
3463 return true;
3464 }
3465 }
3466 line = bestslideline;
3467
3468 if (line->special == Line_Horizon)
3469 {
3470 mo->SeeSound = 0; // it might make a sound otherwise
3471 mo->Destroy();
3472 return true;
3473 }
3474
3475 // The amount of bounces is limited
3476 if (mo->bouncecount > 0 && --mo->bouncecount == 0)
3477 {
3478 if (mo->flags & MF_MISSILE)
3479 P_ExplodeMissile(mo, line, NULL);
3480 else
3481 mo->Die(NULL, NULL);
3482 return true;
3483 }
3484
3485 side = P_PointOnLineSide(mo->x, mo->y, line);
3486 lineangle = R_PointToAngle2(0, 0, line->dx, line->dy);
3487 if (side == 1)
3488 {
3489 lineangle += ANG180;
3490 }
3491 moveangle = R_PointToAngle2(0, 0, mo->velx, mo->vely);
3492 deltaangle = (2 * lineangle) - moveangle;
3493 mo->angle = deltaangle;
3494
3495 lineangle >>= ANGLETOFINESHIFT;
3496 deltaangle >>= ANGLETOFINESHIFT;
3497
3498 movelen = fixed_t(sqrt(double(mo->velx) * mo->velx + double(mo->vely) * mo->vely));
3499 movelen = FixedMul(movelen, mo->wallbouncefactor);
3500
3501 FBoundingBox box(mo->x, mo->y, mo->radius);
3502 if (box.BoxOnLineSide(line) == -1)
3503 {
3504 mo->SetOrigin(mo->x + FixedMul(mo->radius,
3505 finecosine[deltaangle]), mo->y + FixedMul(mo->radius, finesine[deltaangle]), mo->z);
3506 }
3507 if (movelen < FRACUNIT)
3508 {
3509 movelen = 2 * FRACUNIT;
3510 }
3511 mo->velx = FixedMul(movelen, finecosine[deltaangle]);
3512 mo->vely = FixedMul(movelen, finesine[deltaangle]);
3513 if (mo->BounceFlags & BOUNCE_UseBounceState)
3514 {
3515 FState* bouncestate = mo->FindState(NAME_Bounce, NAME_Wall);
3516 if (bouncestate != NULL)
3517 {
3518 mo->SetState(bouncestate);
3519 }
3520 }
3521 return true;
3522}
3523
3524bool P_BounceWall(AActor* mo)
3525{
3526 FSlide slide;
3527 return slide.BounceWall(mo);
3528}
3529
3530//==========================================================================
3531//
3532//
3533//
3534//==========================================================================
3535
3536extern FRandom pr_bounce;
3537bool P_BounceActor(AActor* mo, AActor* BlockingMobj, bool ontop)
3538{
3539 if (mo && BlockingMobj && ((mo->BounceFlags & BOUNCE_AllActors)
3540 || ((mo->flags & MF_MISSILE) && (!(mo->flags2 & MF2_RIP) || (BlockingMobj->flags5 & MF5_DONTRIP) || ((mo->flags6 & MF6_NOBOSSRIP) && (BlockingMobj->flags2 & MF2_BOSS))) && (BlockingMobj->flags2 & MF2_REFLECTIVE))
3541 || ((BlockingMobj->player == NULL) && (!(BlockingMobj->flags3 & MF3_ISMONSTER)))
3542 ))
3543 {
3544 if (mo->bouncecount > 0 && --mo->bouncecount == 0) return false;
3545
3546 if (mo->flags7 & MF7_HITTARGET) mo->target = BlockingMobj;
3547 if (mo->flags7 & MF7_HITMASTER) mo->master = BlockingMobj;
3548 if (mo->flags7 & MF7_HITTRACER) mo->tracer = BlockingMobj;
3549
3550 if (!ontop)
3551 {
3552 fixed_t speed;
3553 angle_t angle = R_PointToAngle2(BlockingMobj->x,
3554 BlockingMobj->y, mo->x, mo->y) + ANGLE_1 * ((pr_bounce() % 16) - 8);
3555 speed = P_AproxDistance(mo->velx, mo->vely);
3556 speed = FixedMul(speed, mo->wallbouncefactor); // [GZ] was 0.75, using wallbouncefactor seems more consistent
3557 mo->angle = angle;
3558 angle >>= ANGLETOFINESHIFT;
3559 mo->velx = FixedMul(speed, finecosine[angle]);
3560 mo->vely = FixedMul(speed, finesine[angle]);
3561 mo->PlayBounceSound(true);
3562 if (mo->BounceFlags & BOUNCE_UseBounceState)
3563 {
3564 FName names[] = { NAME_Bounce, NAME_Actor, NAME_Creature };
3565 FState* bouncestate;
3566 int count = 2;
3567
3568 if ((BlockingMobj->flags & MF_SHOOTABLE) && !(BlockingMobj->flags & MF_NOBLOOD))
3569 {
3570 count = 3;
3571 }
3572 bouncestate = mo->FindState(count, names);
3573 if (bouncestate != NULL)
3574 {
3575 mo->SetState(bouncestate);
3576 }
3577 }
3578 }
3579 else
3580 {
3581 fixed_t dot = mo->velz;
3582
3583 if (mo->BounceFlags & (BOUNCE_HereticType | BOUNCE_MBF))
3584 {
3585 mo->velz -= MulScale15(FRACUNIT, dot);
3586 if (!(mo->BounceFlags & BOUNCE_MBF)) // Heretic projectiles die, MBF projectiles don't.
3587 {
3588 mo->flags |= MF_INBOUNCE;
3589 mo->SetState(mo->FindState(NAME_Death));
3590 mo->flags &= ~MF_INBOUNCE;
3591 return false;
3592 }
3593 else
3594 {
3595 mo->velz = FixedMul(mo->velz, mo->bouncefactor);
3596 }
3597 }
3598 else // Don't run through this for MBF-style bounces
3599 {
3600 // The reflected velocity keeps only about 70% of its original speed
3601 mo->velz = FixedMul(mo->velz - MulScale15(FRACUNIT, dot), mo->bouncefactor);
3602 }
3603
3604 mo->PlayBounceSound(true);
3605 if (mo->BounceFlags & BOUNCE_MBF) // Bring it to rest below a certain speed
3606 {
3607 if (abs(mo->velz) < (fixed_t)(mo->Mass * mo->GetGravity() / 64))
3608 mo->velz = 0;
3609 }
3610 else if (mo->BounceFlags & (BOUNCE_AutoOff | BOUNCE_AutoOffFloorOnly))
3611 {
3612 if (!(mo->flags & MF_NOGRAVITY) && (mo->velz < 3 * FRACUNIT))
3613 mo->BounceFlags &= ~BOUNCE_TypeMask;
3614 }
3615 }
3616
3617 // [BB] Inform the clients.
3618 if (NETWORK_GetState() == NETSTATE_SERVER)
3619 {
3620 SERVERCOMMANDS_PlayBounceSound(mo, true);
3621 // [BB] We need to inform the clients about the new velocity and sync the position,
3622 // but can only do this after calling P_ZMovement. Mark the actor accordingly.
3623 mo->ulNetworkFlags |= NETFL_BOUNCED_OFF_ACTOR;
3624 }
3625
3626 return true;
3627 }
3628 return false;
3629}
3630
3631//============================================================================
3632//
3633// Aiming
3634//
3635//============================================================================
3636
3637struct aim_t
3638{
3639 fixed_t aimpitch;
3640 fixed_t attackrange;
3641 fixed_t shootz; // Height if not aiming up or down
3642 AActor* shootthing;
3643 AActor* friender; // actor to check friendliness again
3644
3645 fixed_t toppitch, bottompitch;
3646 AActor* linetarget;
3647 AActor* thing_friend, * thing_other;
3648 angle_t pitch_friend, pitch_other;
3649 int flags;
3650#ifdef _3DFLOORS
3651 sector_t* lastsector;
3652 secplane_t* lastfloorplane;
3653 secplane_t* lastceilingplane;
3654
3655 bool crossedffloors;
3656
3657 bool AimTraverse3DFloors(const divline_t& trace, intercept_t* in);
3658#endif
3659
3660 void AimTraverse(fixed_t startx, fixed_t starty, fixed_t endx, fixed_t endy, AActor* target = NULL);
3661
3662};
3663
3664#ifdef _3DFLOORS
3665//============================================================================
3666//
3667// AimTraverse3DFloors
3668//
3669//============================================================================
3670bool aim_t::AimTraverse3DFloors(const divline_t& trace, intercept_t* in)
3671{
3672 sector_t* nextsector;
3673 secplane_t* nexttopplane, * nextbottomplane;
3674 line_t* li = in->d.line;
3675
3676 nextsector = NULL;
3677 nexttopplane = nextbottomplane = NULL;
3678
3679 if (li->backsector == NULL) return true; // shouldn't really happen but crashed once for me...
3680 if (li->frontsector->e->XFloor.ffloors.Size() || li->backsector->e->XFloor.ffloors.Size())
3681 {
3682 int frontflag;
3683 F3DFloor* rover;
3684 int highpitch, lowpitch;
3685
3686 fixed_t trX = trace.x + FixedMul(trace.dx, in->frac);
3687 fixed_t trY = trace.y + FixedMul(trace.dy, in->frac);
3688 fixed_t dist = FixedMul(attackrange, in->frac);
3689
3690
3691 int dir = aimpitch < 0 ? 1 : aimpitch > 0 ? -1 : 0;
3692
3693 frontflag = P_PointOnLineSide(shootthing->x, shootthing->y, li);
3694
3695 // 3D floor check. This is not 100% accurate but normally sufficient when
3696 // combined with a final sight check
3697 for (int i = 1; i <= 2; i++)
3698 {
3699 sector_t* s = i == 1 ? li->frontsector : li->backsector;
3700
3701 for (unsigned k = 0; k < s->e->XFloor.ffloors.Size(); k++)
3702 {
3703 crossedffloors = true;
3704 rover = s->e->XFloor.ffloors[k];
3705
3706 if ((rover->flags & FF_SHOOTTHROUGH) || !(rover->flags & FF_EXISTS)) continue;
3707
3708 fixed_t ff_bottom = rover->bottom.plane->ZatPoint(trX, trY);
3709 fixed_t ff_top = rover->top.plane->ZatPoint(trX, trY);
3710
3711
3712 highpitch = -(int)R_PointToAngle2(0, shootz, dist, ff_top);
3713 lowpitch = -(int)R_PointToAngle2(0, shootz, dist, ff_bottom);
3714
3715 if (highpitch <= toppitch)
3716 {
3717 // blocks completely
3718 if (lowpitch >= bottompitch) return false;
3719 // blocks upper edge of view
3720 if (lowpitch > toppitch)
3721 {
3722 toppitch = lowpitch;
3723 if (frontflag != i - 1)
3724 {
3725 nexttopplane = rover->bottom.plane;
3726 }
3727 }
3728 }
3729 else if (lowpitch >= bottompitch)
3730 {
3731 // blocks lower edge of view
3732 if (highpitch < bottompitch)
3733 {
3734 bottompitch = highpitch;
3735 if (frontflag != i - 1)
3736 {
3737 nextbottomplane = rover->top.plane;
3738 }
3739 }
3740 }
3741 // trace is leaving a sector with a 3d-floor
3742
3743 if (frontflag == i - 1)
3744 {
3745 if (s == lastsector)
3746 {
3747 // upper slope intersects with this 3d-floor
3748 if (rover->bottom.plane == lastceilingplane && lowpitch > toppitch)
3749 {
3750 toppitch = lowpitch;
3751 }
3752 // lower slope intersects with this 3d-floor
3753 if (rover->top.plane == lastfloorplane && highpitch < bottompitch)
3754 {
3755 bottompitch = highpitch;
3756 }
3757 }
3758 }
3759 if (toppitch >= bottompitch) return false; // stop
3760 }
3761 }
3762 }
3763
3764 lastsector = nextsector;
3765 lastceilingplane = nexttopplane;
3766 lastfloorplane = nextbottomplane;
3767 return true;
3768}
3769#endif
3770
3771//============================================================================
3772//
3773// PTR_AimTraverse
3774// Sets linetaget and aimpitch when a target is aimed at.
3775//
3776//============================================================================
3777
3778void aim_t::AimTraverse(fixed_t startx, fixed_t starty, fixed_t endx, fixed_t endy, AActor* target)
3779{
3780 FPathTraverse it(startx, starty, endx, endy, PT_ADDLINES | PT_ADDTHINGS | PT_COMPATIBLE);
3781 intercept_t* in;
3782
3783 while ((in = it.Next()))
3784 {
3785 line_t* li;
3786 AActor* th;
3787 fixed_t pitch;
3788 fixed_t thingtoppitch;
3789 fixed_t thingbottompitch;
3790 fixed_t dist;
3791 int thingpitch;
3792
3793 if (in->isaline)
3794 {
3795 li = in->d.line;
3796
3797 if (!(li->flags & ML_TWOSIDED) || (li->flags & ML_BLOCKEVERYTHING))
3798 return; // stop
3799
3800 // Crosses a two sided line.
3801 // A two sided line will restrict the possible target ranges.
3802 FLineOpening open;
3803 P_LineOpening(open, NULL, li, it.Trace().x + FixedMul(it.Trace().dx, in->frac),
3804 it.Trace().y + FixedMul(it.Trace().dy, in->frac));
3805
3806 if (open.bottom >= open.top)
3807 return; // stop
3808
3809 dist = FixedMul(attackrange, in->frac);
3810
3811 pitch = -(int)R_PointToAngle2(0, shootz, dist, open.bottom);
3812 if (pitch < bottompitch)
3813 bottompitch = pitch;
3814
3815 pitch = -(int)R_PointToAngle2(0, shootz, dist, open.top);
3816 if (pitch > toppitch)
3817 toppitch = pitch;
3818
3819 if (toppitch >= bottompitch)
3820 return; // stop
3821
3822#ifdef _3DFLOORS
3823 if (!AimTraverse3DFloors(it.Trace(), in)) return;
3824#endif
3825 continue; // shot continues
3826 }
3827
3828 // shoot a thing
3829 th = in->d.thing;
3830 if (th == shootthing)
3831 continue; // can't shoot self
3832
3833 if (target != NULL && th != target)
3834 continue; // only care about target, and you're not it
3835
3836 // If we want to start a conversation anything that has one should be
3837 // found, regardless of other settings.
3838 if (!(flags & ALF_CHECKCONVERSATION) || th->Conversation == NULL)
3839 {
3840 if (!(flags & ALF_CHECKNONSHOOTABLE)) // For info CCMD, ignore stuff about GHOST and SHOOTABLE flags
3841 {
3842 if (!(th->flags & MF_SHOOTABLE))
3843 continue; // corpse or something
3844
3845 // check for physical attacks on a ghost
3846 if ((th->flags3 & MF3_GHOST) &&
3847 shootthing->player && // [RH] Be sure shootthing is a player
3848 shootthing->player->ReadyWeapon &&
3849 (shootthing->player->ReadyWeapon->flags2 & MF2_THRUGHOST))
3850 {
3851 continue;
3852 }
3853 }
3854 }
3855 dist = FixedMul(attackrange, in->frac);
3856
3857 // Don't autoaim certain special actors
3858 if (!cl_doautoaim && th->flags6 & MF6_NOTAUTOAIMED)
3859 {
3860 continue;
3861 }
3862
3863#ifdef _3DFLOORS
3864 // we must do one last check whether the trace has crossed a 3D floor
3865 if (lastsector == th->Sector && th->Sector->e->XFloor.ffloors.Size())
3866 {
3867 if (lastceilingplane)
3868 {
3869 fixed_t ff_top = lastceilingplane->ZatPoint(th->x, th->y);
3870 fixed_t pitch = -(int)R_PointToAngle2(0, shootz, dist, ff_top);
3871 // upper slope intersects with this 3d-floor
3872 if (pitch > toppitch)
3873 {
3874 toppitch = pitch;
3875 }
3876 }
3877 if (lastfloorplane)
3878 {
3879 fixed_t ff_bottom = lastfloorplane->ZatPoint(th->x, th->y);
3880 fixed_t pitch = -(int)R_PointToAngle2(0, shootz, dist, ff_bottom);
3881 // lower slope intersects with this 3d-floor
3882 if (pitch < bottompitch)
3883 {
3884 bottompitch = pitch;
3885 }
3886 }
3887 }
3888#endif
3889
3890 // check angles to see if the thing can be aimed at
3891
3892 thingtoppitch = -(int)R_PointToAngle2(0, shootz, dist, th->z + th->height);
3893
3894 if (thingtoppitch > bottompitch)
3895 continue; // shot over the thing
3896
3897 thingbottompitch = -(int)R_PointToAngle2(0, shootz, dist, th->z);
3898
3899 if (thingbottompitch < toppitch)
3900 continue; // shot under the thing
3901
3902#ifdef _3DFLOORS
3903 if (crossedffloors)
3904 {
3905 // if 3D floors were in the way do an extra visibility check for safety
3906 if (!P_CheckSight(shootthing, th, SF_IGNOREVISIBILITY | SF_IGNOREWATERBOUNDARY))
3907 {
3908 // the thing can't be seen so we can safely exclude its range from our aiming field
3909 if (thingtoppitch < toppitch)
3910 {
3911 if (thingbottompitch > toppitch) toppitch = thingbottompitch;
3912 }
3913 else if (thingbottompitch > bottompitch)
3914 {
3915 if (thingtoppitch < bottompitch) bottompitch = thingtoppitch;
3916 }
3917 if (toppitch < bottompitch) continue;
3918 else return;
3919 }
3920 }
3921#endif
3922
3923 // this thing can be hit!
3924 if (thingtoppitch < toppitch)
3925 thingtoppitch = toppitch;
3926
3927 if (thingbottompitch > bottompitch)
3928 thingbottompitch = bottompitch;
3929
3930 thingpitch = thingtoppitch / 2 + thingbottompitch / 2;
3931
3932 if (flags & ALF_CHECK3D)
3933 {
3934 // We need to do a 3D distance check here because this is nearly always used in
3935 // combination with P_LineAttack. P_LineAttack uses 3D distance but FPathTraverse
3936 // only 2D. This causes some problems with Hexen's weapons that use different
3937 // attack modes based on distance to target
3938 fixed_t cosine = finecosine[thingpitch >> ANGLETOFINESHIFT];
3939 if (cosine != 0)
3940 {
3941 fixed_t d3 = FixedDiv(FixedMul(P_AproxDistance(it.Trace().dx, it.Trace().dy), in->frac), cosine);
3942 if (d3 > attackrange)
3943 {
3944 return;
3945 }
3946 }
3947 }
3948
3949 if ((flags & ALF_NOFRIENDS) && th->IsFriend(friender))
3950 {
3951 continue;
3952 }
3953 else if (sv_smartaim != 0 && !(flags & ALF_FORCENOSMART))
3954 {
3955 // try to be a little smarter about what to aim at!
3956 // In particular avoid autoaiming at friends and barrels.
3957 if (th->IsFriend(friender)
3958 // [BB] If shooter and target are both players, they are only considered to be friends,
3959 // if they are teammates. For the time being I don't want to put this in IsFriend
3960 // to not risk any negative side effects in 97d3.
3961 && (th->IsTeammate(friender) || (th->player == NULL) || (friender->player == NULL)))
3962 {
3963 if (sv_smartaim < 2)
3964 {
3965 // friends don't aim at friends (except players), at least not first
3966 thing_friend = th;
3967 pitch_friend = thingpitch;
3968 }
3969 }
3970 else if (!(th->flags3 & MF3_ISMONSTER) && th->player == NULL)
3971 {
3972 if (sv_smartaim < 3)
3973 {
3974 // don't autoaim at barrels and other shootable stuff unless no monsters have been found
3975 thing_other = th;
3976 pitch_other = thingpitch;
3977 }
3978 }
3979 else
3980 {
3981 linetarget = th;
3982 aimpitch = thingpitch;
3983 return;
3984 }
3985 }
3986 else
3987 {
3988 linetarget = th;
3989 aimpitch = thingpitch;
3990 return;
3991 }
3992 }
3993}
3994
3995//============================================================================
3996//
3997// P_AimLineAttack
3998//
3999//============================================================================
4000
4001fixed_t P_AimLineAttack(AActor* t1, angle_t angle, fixed_t distance, AActor** pLineTarget, fixed_t vrange,
4002 int flags, AActor* target, AActor* friender)
4003{
4004 fixed_t x2;
4005 fixed_t y2;
4006 aim_t aim;
4007
4008 // [Spleen]
4009 if (!(flags & ALF_NOUNLAGGED))
4010 {
4011 UNLAGGED_Reconcile(t1);
4012 }
4013
4014 angle >>= ANGLETOFINESHIFT;
4015 aim.flags = flags;
4016 aim.shootthing = t1;
4017 aim.friender = (friender == NULL) ? t1 : friender;
4018
4019 x2 = t1->x + (distance >> FRACBITS) * finecosine[angle];
4020 y2 = t1->y + (distance >> FRACBITS) * finesine[angle];
4021 aim.shootz = t1->z + (t1->height >> 1) - t1->floorclip;
4022 // [BB] In ST, right after a map change, mo apparently can be zero.
4023 if ((t1->player != NULL) && (t1->player->mo != NULL))
4024 {
4025 aim.shootz += FixedMul(t1->player->mo->AttackZOffset, t1->player->crouchfactor);
4026 }
4027 else
4028 {
4029 aim.shootz += 8 * FRACUNIT;
4030 }
4031
4032 // can't shoot outside view angles
4033 if (vrange == 0)
4034 {
4035 if (t1->player == NULL || !level.IsFreelookAllowed())
4036 {
4037 vrange = ANGLE_1 * 35;
4038 }
4039 else
4040 {
4041 // [BB] Disable autoaim on weapons with WIF_NOAUTOAIM.
4042 AWeapon* weapon = t1->player->ReadyWeapon;
4043 if (weapon && (weapon->WeaponFlags & WIF_NOAUTOAIM))
4044 {
4045 vrange = ANGLE_1 / 2;
4046 }
4047 else
4048 {
4049 // 35 degrees is approximately what Doom used. You cannot have a
4050 // vrange of 0 degrees, because then toppitch and bottompitch will
4051 // be equal, and PTR_AimTraverse will never find anything to shoot at
4052 // if it crosses a line.
4053 vrange = clamp(t1->player->userinfo.GetAimDist(), ANGLE_1 / 2, ANGLE_1 * 35);
4054 }
4055 }
4056 }
4057 aim.toppitch = t1->pitch - vrange;
4058 aim.bottompitch = t1->pitch + vrange;
4059
4060 aim.attackrange = distance;
4061 aim.linetarget = NULL;
4062
4063 // for smart aiming
4064 aim.thing_friend = aim.thing_other = NULL;
4065
4066 // Information for tracking crossed 3D floors
4067 aim.aimpitch = t1->pitch;
4068
4069#ifdef _3DFLOORS
4070 aim.crossedffloors = t1->Sector->e->XFloor.ffloors.Size() != 0;
4071 aim.lastsector = t1->Sector;
4072 aim.lastfloorplane = aim.lastceilingplane = NULL;
4073
4074 // set initial 3d-floor info
4075 for (unsigned i = 0; i < t1->Sector->e->XFloor.ffloors.Size(); i++)
4076 {
4077 F3DFloor* rover = t1->Sector->e->XFloor.ffloors[i];
4078 fixed_t bottomz = rover->bottom.plane->ZatPoint(t1->x, t1->y);
4079
4080 if (bottomz >= t1->z + t1->height) aim.lastceilingplane = rover->bottom.plane;
4081
4082 bottomz = rover->top.plane->ZatPoint(t1->x, t1->y);
4083 if (bottomz <= t1->z) aim.lastfloorplane = rover->top.plane;
4084 }
4085#endif
4086
4087 aim.AimTraverse(t1->x, t1->y, x2, y2, target);
4088
4089 if (!aim.linetarget)
4090 {
4091 if (aim.thing_other)
4092 {
4093 aim.linetarget = aim.thing_other;
4094 aim.aimpitch = aim.pitch_other;
4095 }
4096 else if (aim.thing_friend)
4097 {
4098 aim.linetarget = aim.thing_friend;
4099 aim.aimpitch = aim.pitch_friend;
4100 }
4101 }
4102 if (pLineTarget)
4103 {
4104 *pLineTarget = aim.linetarget;
4105 }
4106
4107 // [Spleen]
4108 if (!(flags & ALF_NOUNLAGGED))
4109 {
4110 UNLAGGED_Restore(t1);
4111 }
4112
4113 return aim.linetarget ? aim.aimpitch : t1->pitch;
4114}
4115
4116
4117//==========================================================================
4118//
4119//
4120//
4121//==========================================================================
4122
4123struct Origin
4124{
4125 AActor* Caller;
4126 bool hitGhosts;
4127 bool hitSameSpecies;
4128};
4129
4130static ETraceStatus CheckForActor(FTraceResults& res, void* userdata)
4131{
4132 if (res.HitType != TRACE_HitActor)
4133 {
4134 return TRACE_Stop;
4135 }
4136
4137 Origin* data = (Origin*)userdata;
4138
4139
4140 // check for physical attacks on spectrals
4141 if (res.Actor->flags4 & MF4_SPECTRAL)
4142 {
4143 return TRACE_Skip;
4144 }
4145
4146 if (data->hitSameSpecies && res.Actor->GetSpecies() == data->Caller->GetSpecies())
4147 {
4148 return TRACE_Skip;
4149 }
4150 if (data->hitGhosts && res.Actor->flags3 & MF3_GHOST)
4151 {
4152 return TRACE_Skip;
4153 }
4154#if defined (CS_UNBLOCK_PROJECTILES)
4155 if (data->Caller->IsKindOf(RUNTIME_CLASS(APlayerPawn)) && res.Actor->IsKindOf(RUNTIME_CLASS(APlayerPawn))) {
4156 return TRACE_Skip;
4157 }
4158#endif
4159 return TRACE_Stop;
4160}
4161
4162//==========================================================================
4163//
4164// P_LineAttack
4165//
4166// if damage == 0, it is just a test trace that will leave linetarget set
4167//
4168//==========================================================================
4169
4170AActor* P_LineAttack(AActor* t1, angle_t angle, fixed_t distance,
4171 int pitch, int damage, FName damageType, const PClass* pufftype, int flags, AActor** victim, int* actualdamage)
4172{
4173 // [BB] The only reason the client should try to execute P_LineAttack, is the online hitscan decal fix.
4174 // [CK] And also predicted puffs and blood decals.
4175 if (NETWORK_InClientMode()
4176 && cl_hitscandecalhack == false
4177 && CLIENT_ShouldPredictPuffs() == false)
4178 {
4179 return NULL;
4180 }
4181 fixed_t vx, vy, vz, shootz;
4182 FTraceResults trace;
4183 Origin TData;
4184 TData.Caller = t1;
4185 angle_t srcangle = angle;
4186 int srcpitch = pitch;
4187 bool killPuff = false;
4188 AActor* puff = NULL;
4189 int pflag = 0;
4190 int puffFlags = (flags & LAF_ISMELEEATTACK) ? PF_MELEERANGE : 0;
4191 if (flags & LAF_NORANDOMPUFFZ)
4192 puffFlags |= PF_NORANDOMZ;
4193
4194 if (victim != NULL)
4195 {
4196 *victim = NULL;
4197 }
4198 if (actualdamage != NULL)
4199 {
4200 *actualdamage = 0;
4201 }
4202
4203 angle >>= ANGLETOFINESHIFT;
4204 pitch = (angle_t)(pitch) >> ANGLETOFINESHIFT;
4205
4206 vx = FixedMul(finecosine[pitch], finecosine[angle]);
4207 vy = FixedMul(finecosine[pitch], finesine[angle]);
4208 vz = -finesine[pitch];
4209
4210 // [Spleen]
4211 UNLAGGED_Reconcile(t1);
4212
4213 shootz = t1->z - t1->floorclip + (t1->height >> 1);
4214 if (t1->player != NULL)
4215 {
4216 shootz += FixedMul(t1->player->mo->AttackZOffset, t1->player->crouchfactor);
4217 if (damageType == NAME_Melee || damageType == NAME_Hitscan)
4218 {
4219 // this is coming from a weapon attack function which needs to transfer information to the obituary code,
4220 // We need to preserve this info from the damage type because the actual damage type can get overridden by the puff
4221 pflag = DMG_PLAYERATTACK;
4222 }
4223 }
4224 else
4225 {
4226 shootz += 8 * FRACUNIT;
4227 }
4228
4229 // We need to check the defaults of the replacement here
4230 AActor* puffDefaults = GetDefaultByType(pufftype->GetReplacement());
4231
4232 TData.hitGhosts = (t1->player != NULL &&
4233 t1->player->ReadyWeapon != NULL &&
4234 (t1->player->ReadyWeapon->flags2 & MF2_THRUGHOST)) ||
4235 (puffDefaults && (puffDefaults->flags2 & MF2_THRUGHOST));
4236
4237 TData.hitSameSpecies = (puffDefaults && (puffDefaults->flags6 & MF6_MTHRUSPECIES));
4238
4239 // if the puff uses a non-standard damage type, this will override default, hitscan and melee damage type.
4240 // All other explicitly passed damage types (currenty only MDK) will be preserved.
4241 if ((damageType == NAME_None || damageType == NAME_Melee || damageType == NAME_Hitscan) &&
4242 puffDefaults != NULL && puffDefaults->DamageType != NAME_None)
4243 {
4244 damageType = puffDefaults->DamageType;
4245 }
4246
4247 int tflags;
4248 if (puffDefaults != NULL && puffDefaults->flags6 & MF6_NOTRIGGER) tflags = TRACE_NoSky;
4249 else tflags = TRACE_NoSky | TRACE_Impact;
4250
4251 // [Spleen]
4252 const bool hitSomething = Trace(t1->x, t1->y, shootz, t1->Sector, vx, vy, vz, distance,
4253 MF_SHOOTABLE, ML_BLOCKEVERYTHING | ML_BLOCKHITSCAN, t1, trace,
4254 tflags, CheckForActor, &TData);
4255
4256 // [Spleen]
4257 UNLAGGED_Restore(t1);
4258
4259 if (!hitSomething)
4260 { // hit nothing
4261 // [BB] No decal will be spawned, so the client stops here.
4262 // [CK] But continue on if we want clientside puffs since it may occur.
4263 if (NETWORK_InClientMode() && CLIENT_ShouldPredictPuffs() == false)
4264 return NULL;
4265
4266 if (puffDefaults == NULL)
4267 {
4268 }
4269 else if (puffDefaults->ActiveSound)
4270 { // Play miss sound
4271 S_Sound(t1, CHAN_WEAPON, puffDefaults->ActiveSound, 1, ATTN_NORM);
4272
4273 // [BC] Play the hit sound to clients.
4274 if (NETWORK_GetState() == NETSTATE_SERVER)
4275 SERVERCOMMANDS_SoundActor(t1, CHAN_WEAPON, S_GetName(puffDefaults->ActiveSound), 1, ATTN_NORM);
4276 }
4277 if (puffDefaults != NULL && puffDefaults->flags3 & MF3_ALWAYSPUFF)
4278 { // Spawn the puff anyway
4279 puff = P_SpawnPuff(t1, pufftype, trace.X, trace.Y, trace.Z, angle - ANG180, 2, puffFlags);
4280
4281 // [CK] We don't want this function returning an actor if it's a
4282 // client predicting. The client would be done regardless.
4283 if (NETWORK_InClientMode())
4284 return NULL;
4285 }
4286 else
4287 {
4288 return NULL;
4289 }
4290 }
4291 else
4292 {
4293 fixed_t hitx = 0, hity = 0, hitz = 0;
4294
4295 if (trace.HitType != TRACE_HitActor)
4296 {
4297 // [BB] The client only spawns decals, no puffs.
4298 // [CK] Now there is an option for clientside puffs.
4299 if ((NETWORK_InClientMode() == false) || CLIENT_ShouldPredictPuffs())
4300 {
4301 // position a bit closer for puffs
4302 if (trace.HitType != TRACE_HitWall || trace.Line->special != Line_Horizon)
4303 {
4304 fixed_t closer = trace.Distance - 4 * FRACUNIT;
4305 puff = P_SpawnPuff(t1, pufftype, t1->x + FixedMul(vx, closer),
4306 t1->y + FixedMul(vy, closer),
4307 shootz + FixedMul(vz, closer), angle - ANG90, 0, puffFlags);
4308 }
4309 }
4310
4311 // [CK] If we don't want decals, stop before entering.
4312 if (NETWORK_InClientMode() && cl_hitscandecalhack == false)
4313 return NULL;
4314
4315 // [RH] Spawn a decal
4316 if (trace.HitType == TRACE_HitWall && trace.Line->special != Line_Horizon && !(flags & LAF_NOIMPACTDECAL) && !(puffDefaults->flags7 & MF7_NODECAL))
4317 {
4318 // [TN] If the actor or weapon has a decal defined, use that one.
4319 if (t1->DecalGenerator != NULL ||
4320 (t1->player != NULL && t1->player->ReadyWeapon != NULL && t1->player->ReadyWeapon->DecalGenerator != NULL))
4321 {
4322 // [ZK] If puff has FORCEDECAL set, do not use the weapon's decal
4323 if (puffDefaults->flags7 & MF7_FORCEDECAL && puff != NULL && puff->DecalGenerator)
4324 SpawnShootDecal(puff, trace);
4325 else
4326 SpawnShootDecal(t1, trace);
4327 }
4328
4329 // Else, look if the bulletpuff has a decal defined.
4330 else if (puff != NULL && puff->DecalGenerator)
4331 {
4332 SpawnShootDecal(puff, trace);
4333 }
4334
4335 // [BB] Clients dont' spawn the puff, so we have to look if the default puff has a decal defined.
4336 else if (NETWORK_InClientMode()
4337 && pufftype && (GetDefaultByType(pufftype) != NULL) && GetDefaultByType(pufftype)->DecalGenerator)
4338 {
4339 SpawnShootDecal(GetDefaultByType(pufftype), trace);
4340 }
4341
4342 else
4343 {
4344 SpawnShootDecal(t1, trace);
4345 }
4346 }
4347 else if (puff != NULL &&
4348 trace.CrossedWater == NULL &&
4349 trace.Sector->heightsec == NULL &&
4350 trace.HitType == TRACE_HitFloor)
4351 {
4352 // [CK] We are not predicting water splashes.
4353 if (NETWORK_InClientMode())
4354 return NULL;
4355
4356 // Using the puff's position is not accurate enough.
4357 // Instead make it splash at the actual hit position
4358 hitx = t1->x + FixedMul(vx, trace.Distance);
4359 hity = t1->y + FixedMul(vy, trace.Distance);
4360 hitz = shootz + FixedMul(vz, trace.Distance);
4361 P_HitWater(puff, P_PointInSector(hitx, hity), hitx, hity, hitz);
4362 }
4363 // [BB] Decal has been spawned, so the client stops here.
4364 if (NETWORK_InClientMode())
4365 {
4366 return NULL;
4367 }
4368 }
4369 else
4370 {
4371 // [BB] No decal will be spawned, so the client stops here.
4372 // [CK] Also exit if we don't want puffs, or blood decals.
4373 if (NETWORK_InClientMode()
4374 && CLIENT_ShouldPredictPuffs() == false
4375 && cl_hitscandecalhack == false)
4376 return NULL;
4377
4378 bool bloodsplatter = (t1->flags5 & MF5_BLOODSPLATTER) ||
4379 (t1->player != NULL && t1->player->ReadyWeapon != NULL &&
4380 (t1->player->ReadyWeapon->WeaponFlags & WIF_AXEBLOOD));
4381
4382 bool axeBlood = (t1->player != NULL &&
4383 t1->player->ReadyWeapon != NULL &&
4384 (t1->player->ReadyWeapon->WeaponFlags & WIF_AXEBLOOD));
4385
4386 // Hit a thing, so it could be either a puff or blood
4387 fixed_t dist = trace.Distance;
4388 // position a bit closer for puffs/blood if using compatibility mode.
4389 if (i_compatflags & COMPATF_HITSCAN) dist -= 10 * FRACUNIT;
4390 hitx = t1->x + FixedMul(vx, dist);
4391 hity = t1->y + FixedMul(vy, dist);
4392 hitz = shootz + FixedMul(vz, dist);
4393
4394 // [BB] If reconciliation moved the actor we hit, move the hit accordingly.
4395 hitx += trace.unlaggedHitOffset[0];
4396 hity += trace.unlaggedHitOffset[1];
4397 hitz += trace.unlaggedHitOffset[2];
4398
4399
4400
4401 // Spawn bullet puffs or blood spots, depending on target type.
4402 // [CK] We don't want to enter here unless we're predicting puffs.
4403 if (((puffDefaults != NULL && puffDefaults->flags3 & MF3_PUFFONACTORS) ||
4404 (trace.Actor->flags & MF_NOBLOOD) ||
4405 (trace.Actor->flags2 & (MF2_INVULNERABLE | MF2_DORMANT)))
4406 && (NETWORK_InClientMode() == false || CLIENT_ShouldPredictPuffs()))
4407 {
4408 if (!(trace.Actor->flags & MF_NOBLOOD))
4409 puffFlags |= PF_HITTHINGBLEED;
4410
4411 // We must pass the unreplaced puff type here
4412 puff = P_SpawnPuff(t1, pufftype, hitx, hity, hitz, angle - ANG180, 2, puffFlags | PF_HITTHING, trace.Actor);
4413 }
4414
4415 // [CK] The client by this point has predicted their desired
4416 // puff and should only be here if they want puff prediction,
4417 // so we can exit. We will only continue on if we want blood decals.
4418 if (NETWORK_InClientMode() && cl_hitscandecalhack == false)
4419 return NULL;
4420
4421 // Allow puffs to inflict poison damage, so that hitscans can poison, too.
4422 // [EP] Skip this for clients.
4423 if ((NETWORK_InClientMode() == false) && puffDefaults != NULL && puffDefaults->PoisonDamage > 0 && puffDefaults->PoisonDuration != INT_MIN)
4424 {
4425 P_PoisonMobj(trace.Actor, puff ? puff : t1, t1, puffDefaults->PoisonDamage, puffDefaults->PoisonDuration, puffDefaults->PoisonPeriod, puffDefaults->PoisonDamageType);
4426 }
4427
4428 // [GZ] If MF6_FORCEPAIN is set, we need to call P_DamageMobj even if damage is 0!
4429 // Note: The puff may not yet be spawned here so we must check the class defaults, not the actor.
4430 int newdam = damage;
4431 // [EP] Skip this for clients.
4432 if ((NETWORK_InClientMode() == false) && (damage || (puffDefaults != NULL && puffDefaults->flags6 & MF6_FORCEPAIN)))
4433 {
4434 int dmgflags = DMG_INFLICTOR_IS_PUFF | pflag;
4435 // Allow MF5_PIERCEARMOR on a weapon as well.
4436 if (t1->player != NULL && (dmgflags & DMG_PLAYERATTACK) && t1->player->ReadyWeapon != NULL &&
4437 t1->player->ReadyWeapon->flags5 & MF5_PIERCEARMOR)
4438 {
4439 dmgflags |= DMG_NO_ARMOR;
4440 }
4441
4442 if (puff == NULL)
4443 {
4444 // Since the puff is the damage inflictor we need it here
4445 // regardless of whether it is displayed or not.
4446 // [BB] In case the puff has a custom obituary, the clients need to spawn it too.
4447 const bool bTellClientToSpawn = pufftype && (pufftype->Meta.GetMetaString(AMETA_Obituary) != NULL);
4448 puff = P_SpawnPuff(t1, pufftype, hitx, hity, hitz, angle - ANG180, 2, puffFlags | PF_HITTHING | PF_TEMPORARY, NULL, bTellClientToSpawn);
4449 killPuff = true;
4450 }
4451 newdam = P_DamageMobj(trace.Actor, puff ? puff : t1, t1, damage, damageType, dmgflags);
4452 if (actualdamage != NULL)
4453 {
4454 *actualdamage = newdam;
4455 }
4456 }
4457 if (!(puffDefaults != NULL && puffDefaults->flags3 & MF3_BLOODLESSIMPACT))
4458 {
4459 // [CK] Do not perform if we are a client.
4460 if (!bloodsplatter && !axeBlood &&
4461 !(trace.Actor->flags & MF_NOBLOOD) &&
4462 !(trace.Actor->flags2 & (MF2_INVULNERABLE | MF2_DORMANT)) &&
4463 NETWORK_InClientMode() == false)
4464 {
4465 P_SpawnBlood(hitx, hity, hitz, angle - ANG180, newdam > 0 ? newdam : damage, trace.Actor);
4466 }
4467
4468 if (damage)
4469 {
4470 // [CK] Do not do any blood splatters, that is not the function
4471 // we intend to predict with blood decals.
4472 if ((bloodsplatter || axeBlood) && NETWORK_InClientMode() == false)
4473 {
4474 if (!(trace.Actor->flags & MF_NOBLOOD) &&
4475 !(trace.Actor->flags2 & (MF2_INVULNERABLE | MF2_DORMANT)))
4476 {
4477 if (axeBlood)
4478 {
4479 P_BloodSplatter2(hitx, hity, hitz, trace.Actor);
4480 }
4481 if (pr_lineattack() < 192)
4482 {
4483 P_BloodSplatter(hitx, hity, hitz, trace.Actor);
4484 }
4485 }
4486 }
4487
4488 // [BC] If the attacker is a player and struck another player, flag it.
4489 if ((trace.Actor->player) && (t1->player) && (t1->IsTeammate(trace.Actor) == false))
4490 t1->player->bStruckPlayer = true;
4491
4492 // [RH] Stick blood to walls
4493 P_TraceBleed(newdam > 0 ? newdam : damage, trace.X, trace.Y, trace.Z,
4494 trace.Actor, srcangle, srcpitch);
4495 }
4496 }
4497 if (victim != NULL)
4498 {
4499 *victim = trace.Actor;
4500 }
4501 }
4502 if (trace.Crossed3DWater || trace.CrossedWater)
4503 {
4504 // [CK] We do not want to predict splashes right now. This puff is
4505 // destroyed further down, so we can assume it would be bad to do
4506 // any prediction here.
4507 if (NETWORK_InClientMode())
4508 return NULL;
4509
4510 if (puff == NULL)
4511 { // Spawn puff just to get a mass for the splash
4512 // [TP] Parameters to vict and bTellClientToSpawn were added here.
4513 puff = P_SpawnPuff(t1, pufftype, hitx, hity, hitz, angle - ANG180, 2, puffFlags | PF_HITTHING | PF_TEMPORARY, NULL, false);
4514 killPuff = true;
4515 }
4516 SpawnDeepSplash(t1, trace, puff, vx, vy, vz, shootz, trace.Crossed3DWater != NULL);
4517 }
4518 }
4519
4520 // [CK] In case the client ever gets this far, it should end now.
4521 if (NETWORK_InClientMode())
4522 return NULL;
4523
4524 if (killPuff && puff != NULL)
4525 {
4526 // [BB] Remove the temporary puff from the clients.
4527 if ((NETWORK_GetState() == NETSTATE_SERVER) && (puff->lNetID != -1))
4528 SERVERCOMMANDS_DestroyThing(puff);
4529
4530 puff->Destroy();
4531 puff = NULL;
4532 }
4533 return puff;
4534}
4535
4536AActor* P_LineAttack(AActor* t1, angle_t angle, fixed_t distance,
4537 int pitch, int damage, FName damageType, FName pufftype, int flags, AActor** victim, int* actualdamage)
4538{
4539 const PClass* type = PClass::FindClass(pufftype);
4540 if (victim != NULL)
4541 {
4542 *victim = NULL;
4543 }
4544 if (type == NULL)
4545 {
4546 Printf("Attempt to spawn unknown actor type '%s'\n", pufftype.GetChars());
4547 }
4548 else
4549 {
4550 return P_LineAttack(t1, angle, distance, pitch, damage, damageType, type, flags, victim, actualdamage);
4551 }
4552 return NULL;
4553}
4554
4555//==========================================================================
4556//
4557// P_LinePickActor
4558//
4559//==========================================================================
4560
4561AActor* P_LinePickActor(AActor* t1, angle_t angle, fixed_t distance, int pitch,
4562 DWORD actorMask, DWORD wallMask)
4563{
4564 fixed_t vx, vy, vz, shootz;
4565
4566 angle >>= ANGLETOFINESHIFT;
4567 pitch = (angle_t)(pitch) >> ANGLETOFINESHIFT;
4568
4569 vx = FixedMul(finecosine[pitch], finecosine[angle]);
4570 vy = FixedMul(finecosine[pitch], finesine[angle]);
4571 vz = -finesine[pitch];
4572
4573 shootz = t1->z - t1->floorclip + (t1->height >> 1);
4574 if (t1->player != NULL && t1->player->mo) // [BB] Added mo check.
4575 {
4576 shootz += FixedMul(t1->player->mo->AttackZOffset, t1->player->crouchfactor);
4577 }
4578 else
4579 {
4580 shootz += 8 * FRACUNIT;
4581 }
4582
4583 FTraceResults trace;
4584 Origin TData;
4585
4586 TData.Caller = t1;
4587 TData.hitGhosts = true;
4588
4589 if (Trace(t1->x, t1->y, shootz, t1->Sector, vx, vy, vz, distance,
4590 actorMask, wallMask, t1, trace, TRACE_NoSky, CheckForActor, &TData))
4591 {
4592 if (trace.HitType == TRACE_HitActor)
4593 {
4594 return trace.Actor;
4595 }
4596 }
4597
4598 return NULL;
4599}
4600
4601//==========================================================================
4602//
4603//
4604//
4605//==========================================================================
4606
4607void P_TraceBleed(int damage, fixed_t x, fixed_t y, fixed_t z, AActor* actor, angle_t angle, int pitch)
4608{
4609 if (!cl_bloodsplats)
4610 return;
4611
4612 const char* bloodType = "BloodSplat";
4613 int count;
4614 int noise;
4615
4616
4617 if ((actor->flags & MF_NOBLOOD) ||
4618 (actor->flags5 & MF5_NOBLOODDECALS) ||
4619 (actor->flags2 & (MF2_INVULNERABLE | MF2_DORMANT)) ||
4620 (actor->player && actor->player->cheats & CF_GODMODE))
4621 {
4622 return;
4623 }
4624 if (damage < 15)
4625 { // For low damages, there is a chance to not spray blood at all
4626 if (damage <= 10)
4627 {
4628 if (pr_tracebleed() < 160)
4629 {
4630 return;
4631 }
4632 }
4633 count = 1;
4634 noise = 18;
4635 }
4636 else if (damage < 25)
4637 {
4638 count = 2;
4639 noise = 19;
4640 }
4641 else
4642 { // For high damages, there is a chance to spray just one big glob of blood
4643 if (pr_tracebleed() < 24)
4644 {
4645 bloodType = "BloodSmear";
4646 count = 1;
4647 noise = 20;
4648 }
4649 else
4650 {
4651 count = 3;
4652 noise = 20;
4653 }
4654 }
4655
4656 for (; count; --count)
4657 {
4658 FTraceResults bleedtrace;
4659
4660 angle_t bleedang = (angle + ((pr_tracebleed() - 128) << noise)) >> ANGLETOFINESHIFT;
4661 angle_t bleedpitch = (angle_t)(pitch + ((pr_tracebleed() - 128) << noise)) >> ANGLETOFINESHIFT;
4662 fixed_t vx = FixedMul(finecosine[bleedpitch], finecosine[bleedang]);
4663 fixed_t vy = FixedMul(finecosine[bleedpitch], finesine[bleedang]);
4664 fixed_t vz = -finesine[bleedpitch];
4665
4666 if (Trace(x, y, z, actor->Sector,
4667 vx, vy, vz, 172 * FRACUNIT, 0, ML_BLOCKEVERYTHING, actor,
4668 bleedtrace, TRACE_NoSky))
4669 {
4670 if (bleedtrace.HitType == TRACE_HitWall)
4671 {
4672 PalEntry bloodcolor = actor->GetBloodColor();
4673 if (bloodcolor != 0)
4674 {
4675 bloodcolor.r >>= 1; // the full color is too bright for blood decals
4676 bloodcolor.g >>= 1;
4677 bloodcolor.b >>= 1;
4678 bloodcolor.a = 1;
4679 }
4680
4681 // [BC] Servers don't need to spawn decals.
4682 if (NETWORK_GetState() != NETSTATE_SERVER)
4683 {
4684 DImpactDecal::StaticCreate(bloodType,
4685 bleedtrace.X, bleedtrace.Y, bleedtrace.Z,
4686 bleedtrace.Line->sidedef[bleedtrace.Side],
4687 bleedtrace.ffloor,
4688 bloodcolor);
4689 }
4690 }
4691 }
4692 }
4693}
4694
4695void P_TraceBleed(int damage, AActor* target, angle_t angle, int pitch)
4696{
4697 P_TraceBleed(damage, target->x, target->y, target->z + target->height / 2,
4698 target, angle, pitch);
4699}
4700
4701//==========================================================================
4702//
4703//
4704//
4705//==========================================================================
4706
4707void P_TraceBleed(int damage, AActor* target, AActor* missile)
4708{
4709 int pitch;
4710
4711 if (target == NULL || missile->flags3 & MF3_BLOODLESSIMPACT)
4712 {
4713 return;
4714 }
4715
4716 if (missile->velz != 0)
4717 {
4718 double aim;
4719
4720 aim = atan((double)missile->velz / (double)P_AproxDistance(missile->x - target->x, missile->y - target->y));
4721 pitch = -(int)(aim * ANGLE_180 / PI);
4722 }
4723 else
4724 {
4725 pitch = 0;
4726 }
4727 P_TraceBleed(damage, target->x, target->y, target->z + target->height / 2,
4728 target, R_PointToAngle2(missile->x, missile->y, target->x, target->y),
4729 pitch);
4730}
4731
4732//==========================================================================
4733//
4734//
4735//
4736//==========================================================================
4737
4738void P_TraceBleed(int damage, AActor* target)
4739{
4740 if (target != NULL)
4741 {
4742 fixed_t one = pr_tracebleed() << 24;
4743 fixed_t two = (pr_tracebleed() - 128) << 16;
4744
4745 P_TraceBleed(damage, target->x, target->y, target->z + target->height / 2,
4746 target, one, two);
4747 }
4748}
4749
4750//==========================================================================
4751//
4752// [RH] Rail gun stuffage
4753//
4754//==========================================================================
4755
4756struct SRailHit
4757{
4758 AActor* HitActor;
4759 fixed_t Distance;
4760};
4761struct RailData
4762{
4763 TArray<SRailHit> RailHits;
4764 bool StopAtOne;
4765 bool StopAtInvul;
4766};
4767
4768static ETraceStatus ProcessRailHit(FTraceResults& res, void* userdata)
4769{
4770 RailData* data = (RailData*)userdata;
4771 if (res.HitType != TRACE_HitActor)
4772 {
4773 return TRACE_Stop;
4774 }
4775
4776 // Invulnerable things completely block the shot
4777 if (data->StopAtInvul && res.Actor->flags2 & MF2_INVULNERABLE)
4778 {
4779 return TRACE_Stop;
4780 }
4781
4782 // Save this thing for damaging later, and continue the trace
4783 SRailHit newhit;
4784 newhit.HitActor = res.Actor;
4785 newhit.Distance = res.Distance - 10 * FRACUNIT; // put blood in front
4786 data->RailHits.Push(newhit);
4787
4788 return data->StopAtOne ? TRACE_Stop : TRACE_Continue;
4789}
4790
4791//==========================================================================
4792//
4793//
4794//
4795//==========================================================================
4796void P_RailAttack(AActor* source, int damage, int offset_xy, fixed_t offset_z, int color1, int color2, float maxdiff, int railflags, const PClass* puffclass, angle_t angleoffset, angle_t pitchoffset, fixed_t distance, int duration, float sparsity, float drift, const PClass* spawnclass)
4797{
4798 fixed_t vx, vy, vz;
4799 angle_t angle, pitch;
4800 fixed_t x1, y1;
4801 FVector3 start, end;
4802 FTraceResults trace;
4803 fixed_t shootz;
4804
4805 // [Spleen]
4806 UNLAGGED_Reconcile(source);
4807
4808 if (puffclass == NULL) puffclass = PClass::FindClass(NAME_BulletPuff);
4809
4810 pitch = ((angle_t)(-source->pitch) + pitchoffset) >> ANGLETOFINESHIFT;
4811 angle = (source->angle + angleoffset) >> ANGLETOFINESHIFT;
4812
4813 vx = FixedMul(finecosine[pitch], finecosine[angle]);
4814 vy = FixedMul(finecosine[pitch], finesine[angle]);
4815 vz = finesine[pitch];
4816
4817 x1 = source->x;
4818 y1 = source->y;
4819
4820 shootz = source->z - source->floorclip + (source->height >> 1) + offset_z;
4821
4822 if (!(railflags & RAF_CENTERZ))
4823 {
4824 if (source->player != NULL)
4825 {
4826 shootz += FixedMul(source->player->mo->AttackZOffset, source->player->crouchfactor);
4827 }
4828 else
4829 {
4830 shootz += 8 * FRACUNIT;
4831 }
4832 }
4833
4834 angle = ((source->angle + angleoffset) - ANG90) >> ANGLETOFINESHIFT;
4835 x1 += offset_xy * finecosine[angle];
4836 y1 += offset_xy * finesine[angle];
4837
4838 RailData rail_data;
4839
4840 rail_data.StopAtOne = !!(railflags & RAF_NOPIERCE);
4841 start.X = FIXED2FLOAT(x1);
4842 start.Y = FIXED2FLOAT(y1);
4843 start.Z = FIXED2FLOAT(shootz);
4844
4845 int flags;
4846
4847 assert(puffclass != NULL); // Because we set it to a default above
4848 AActor* puffDefaults = GetDefaultByType(puffclass->GetReplacement()); //Contains all the flags such as FOILINVUL, etc.
4849
4850 flags = (puffDefaults->flags6 & MF6_NOTRIGGER) ? 0 : TRACE_PCross | TRACE_Impact;
4851 rail_data.StopAtInvul = (puffDefaults->flags3 & MF3_FOILINVUL) ? false : true;
4852
4853 Trace(x1, y1, shootz, source->Sector, vx, vy, vz,
4854 distance, MF_SHOOTABLE, ML_BLOCKEVERYTHING, source, trace,
4855 flags, ProcessRailHit, &rail_data);
4856
4857 // [Spleen]
4858 UNLAGGED_Restore(source);
4859
4860 // Hurt anything the trace hit
4861 unsigned int i;
4862 FName damagetype = (puffDefaults == NULL || puffDefaults->DamageType == NAME_None) ? FName(NAME_Railgun) : puffDefaults->DamageType;
4863
4864 // used as damage inflictor
4865 AActor* thepuff = NULL;
4866
4867 if (puffclass != NULL) thepuff = Spawn(puffclass, source->x, source->y, source->z, ALLOW_REPLACE);
4868
4869 // [Spleen] Don't do damage, don't award medals, don't spawn puffs,
4870 // and don't spawn blood in clients on a network.
4871 // [BB] Actually client spawn the puffs to draw decals / splashes.
4872 if (NETWORK_InClientMode() == false)
4873 {
4874 for (i = 0; i < rail_data.RailHits.Size(); i++)
4875 {
4876 fixed_t x, y, z;
4877 bool spawnpuff;
4878 bool bleed = false;
4879
4880 int puffflags = PF_HITTHING;
4881 AActor* hitactor = rail_data.RailHits[i].HitActor;
4882 fixed_t hitdist = rail_data.RailHits[i].Distance;
4883
4884 x = x1 + FixedMul(hitdist, vx);
4885 y = y1 + FixedMul(hitdist, vy);
4886 z = shootz + FixedMul(hitdist, vz);
4887
4888 if ((hitactor->flags & MF_NOBLOOD) ||
4889 (hitactor->flags2 & (MF2_DORMANT | MF2_INVULNERABLE)))
4890 {
4891 spawnpuff = (puffclass != NULL);
4892 }
4893 else
4894 {
4895 spawnpuff = (puffclass != NULL && puffDefaults->flags3 & MF3_ALWAYSPUFF);
4896 puffflags |= PF_HITTHINGBLEED; // [XA] Allow for puffs to jump to XDeath state.
4897 if (!(puffDefaults->flags3 & MF3_BLOODLESSIMPACT))
4898 {
4899 bleed = true;
4900 }
4901 }
4902 if (spawnpuff)
4903 {
4904 P_SpawnPuff(source, puffclass, x, y, z, (source->angle + angleoffset) - ANG90, 1, puffflags, hitactor);
4905 }
4906 // [BC] Damage is server side.
4907 if (NETWORK_InClientMode() == false)
4908 {
4909 if (puffDefaults && puffDefaults->PoisonDamage > 0 && puffDefaults->PoisonDuration != INT_MIN)
4910 {
4911 P_PoisonMobj(hitactor, thepuff ? thepuff : source, source, puffDefaults->PoisonDamage, puffDefaults->PoisonDuration, puffDefaults->PoisonPeriod, puffDefaults->PoisonDamageType);
4912 }
4913 // [BC/BB] Support for instagib.
4914 if (instagib)
4915 damage = 999;
4916
4917 // [RK] If the attack source is a player, send the DMG_PLAYERATTACK flag.
4918 int newdam = P_DamageMobj(hitactor, thepuff ? thepuff : source, source, damage, damagetype, DMG_INFLICTOR_IS_PUFF | (source->player ? DMG_PLAYERATTACK : 0));
4919
4920 if (bleed)
4921 {
4922 P_SpawnBlood(x, y, z, (source->angle + angleoffset) - ANG180, newdam > 0 ? newdam : damage, hitactor);
4923 P_TraceBleed(newdam > 0 ? newdam : damage, x, y, z, hitactor, source->angle, pitch);
4924 }
4925 }
4926
4927 if ((hitactor->player) && (source->IsTeammate(hitactor) == false))
4928 {
4929 if (source->player)
4930 {
4931 source->player->ulConsecutiveRailgunHits++;
4932
4933 // If the player has made 2 straight consecutive hits with the railgun, award a medal.
4934 if ((source->player->ulConsecutiveRailgunHits % 2) == 0)
4935 {
4936 // If the player gets 4+ straight hits with the railgun, award a "Most Impressive" medal.
4937 if (source->player->ulConsecutiveRailgunHits >= 4)
4938 {
4939 if (NETWORK_InClientMode() == false)
4940 MEDAL_GiveMedal(ULONG(source->player - players), MEDAL_MOSTIMPRESSIVE);
4941
4942 // Tell clients about the medal that been given.
4943 if (NETWORK_GetState() == NETSTATE_SERVER)
4944 SERVERCOMMANDS_GivePlayerMedal(ULONG(source->player - players), MEDAL_MOSTIMPRESSIVE);
4945 }
4946 // Otherwise, award an "Impressive" medal.
4947 else
4948 {
4949 if (NETWORK_InClientMode() == false)
4950 MEDAL_GiveMedal(ULONG(source->player - players), MEDAL_IMPRESSIVE);
4951
4952 // Tell clients about the medal that been given.
4953 if (NETWORK_GetState() == NETSTATE_SERVER)
4954 SERVERCOMMANDS_GivePlayerMedal(ULONG(source->player - players), MEDAL_IMPRESSIVE);
4955 }
4956 }
4957 }
4958 }
4959 }
4960 }
4961
4962 // Spawn a decal or puff at the point where the trace ended.
4963 if (trace.HitType == TRACE_HitWall)
4964 {
4965 SpawnShootDecal(source, trace);
4966 if (puffclass != NULL && puffDefaults->flags3 & MF3_ALWAYSPUFF)
4967 {
4968 P_SpawnPuff(source, puffclass, trace.X, trace.Y, trace.Z, (source->angle + angleoffset) - ANG90, 1, 0);
4969 }
4970
4971 }
4972 if (thepuff != NULL)
4973 {
4974 if (trace.HitType == TRACE_HitFloor &&
4975 trace.CrossedWater == NULL &&
4976 trace.Sector->heightsec == NULL)
4977 {
4978 thepuff->SetOrigin(trace.X, trace.Y, trace.Z);
4979 P_HitWater(thepuff, trace.Sector);
4980 }
4981 if (trace.Crossed3DWater || trace.CrossedWater)
4982 {
4983 SpawnDeepSplash(source, trace, thepuff, vx, vy, vz, shootz, trace.Crossed3DWater != NULL);
4984 }
4985 thepuff->Destroy();
4986 }
4987
4988 // Draw the slug's trail.
4989 end.X = FIXED2FLOAT(trace.X);
4990 end.Y = FIXED2FLOAT(trace.Y);
4991 end.Z = FIXED2FLOAT(trace.Z);
4992 P_DrawRailTrail(source, start, end, color1, color2, maxdiff, railflags, spawnclass, source->angle + angleoffset, duration, sparsity, drift);
4993
4994 // [BC] If we're the server, tell clients to create a railgun trail.
4995 if (NETWORK_GetState() == NETSTATE_SERVER)
4996 {
4997 const ULONG ulPlayer = source->player ? static_cast<ULONG> (source->player - players) : MAXPLAYERS;
4998 SERVERCOMMANDS_WeaponRailgun(source, start, end, color1, color2, maxdiff, railflags,
4999 angleoffset, spawnclass, duration, sparsity, drift, ulPlayer,
5000 UNLAGGED_DrawRailClientside(source) ? SVCF_SKIPTHISCLIENT : ServerCommandFlags(0));
5001 }
5002}
5003
5004void P_RailAttackWithPossibleSpread(AActor* source, int damage, int offset_xy, fixed_t offset_z, int color1, int color2, float maxdiff, int railflags, const PClass* puffclass, angle_t angleoffset, angle_t pitchoffset, fixed_t distance, int duration, float sparsity, float drift, const PClass* spawnclass)
5005{
5006 // [BB] Sanity check.
5007 if (source == NULL)
5008 return;
5009
5010 // [BC]
5011 LONG lOuterColor;
5012 LONG lInnerColor;
5013
5014 // [BC] If this is a player, use the player's custom colors.
5015 // [BB] Only apply the color change if color1 and color2 are at the default value.
5016 if (source->player && (color1 == 0) && (color2 == 0))
5017 {
5018 if ((GAMEMODE_GetCurrentFlags() & GMF_PLAYERSONTEAMS) &&
5019 (source->player->bOnTeam))
5020 {
5021 lOuterColor = TEAM_GetRailgunColor(source->player->ulTeam);
5022 lInnerColor = PLAYER_GetRailgunColor(source->player);
5023 }
5024 else
5025 {
5026 lOuterColor = PLAYER_GetRailgunColor(source->player);
5027 lInnerColor = V_GetColorFromString(NULL, "ff ff ff");
5028 }
5029 }
5030 else
5031 {
5032 lOuterColor = color1;
5033 lInnerColor = color2;
5034 }
5035
5036 // [BB] Recall ulConsecutiveRailgunHits from before the attack to handle medals.
5037 const ULONG ulConsecutiveRailgunHitsBefore = (source->player) ? source->player->ulConsecutiveRailgunHits : 0;
5038
5039 P_RailAttack(source, damage, offset_xy, offset_z, lOuterColor, lInnerColor, maxdiff, railflags, puffclass, angleoffset, pitchoffset, distance, duration, sparsity, drift, spawnclass);
5040
5041 // [BB] Apply spread and handle the Railgun medals.
5042 if (NULL != source->player)
5043 {
5044 if (source->player->cheats2 & CF2_SPREAD)
5045 {
5046 fixed_t SavedActorAngle;
5047
5048 SavedActorAngle = source->angle;
5049
5050 source->angle += (ANGLE_45 / 3);
5051 P_RailAttack(source, damage, offset_xy, offset_z, lOuterColor, lInnerColor, maxdiff, railflags, puffclass, angleoffset, pitchoffset, distance, duration, sparsity, drift, spawnclass);
5052 source->angle = SavedActorAngle;
5053
5054 source->angle -= (ANGLE_45 / 3);
5055 P_RailAttack(source, damage, offset_xy, offset_z, lOuterColor, lInnerColor, maxdiff, railflags, puffclass, angleoffset, pitchoffset, distance, duration, sparsity, drift, spawnclass);
5056 source->angle = SavedActorAngle;
5057 }
5058
5059 // Player did not strike a player with his railgun. Reset consecutive hits to 0.
5060 if (ulConsecutiveRailgunHitsBefore == source->player->ulConsecutiveRailgunHits)
5061 source->player->ulConsecutiveRailgunHits = 0;
5062 }
5063}
5064//==========================================================================
5065//
5066// [RH] P_AimCamera
5067//
5068//==========================================================================
5069
5070CVAR(Float, chase_height, -8.f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
5071// [BB] Negative chase_dist values don't make sense, you wouldn't be chasing anymore.
5072CUSTOM_CVAR(Float, chase_dist, 90.f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG)
5073{
5074 // [BB] Don't allow negative chase_dist values.
5075 if (self < 0)
5076 self = 0;
5077}
5078
5079void P_AimCamera(AActor* t1, fixed_t& CameraX, fixed_t& CameraY, fixed_t& CameraZ, sector_t*& CameraSector)
5080{
5081 fixed_t distance = (fixed_t)(chase_dist * FRACUNIT);
5082 angle_t angle = (t1->angle - ANG180) >> ANGLETOFINESHIFT;
5083 angle_t pitch = (angle_t)(t1->pitch) >> ANGLETOFINESHIFT;
5084 FTraceResults trace;
5085 fixed_t vx, vy, vz, sz;
5086
5087 vx = FixedMul(finecosine[pitch], finecosine[angle]);
5088 vy = FixedMul(finecosine[pitch], finesine[angle]);
5089 vz = finesine[pitch];
5090
5091 sz = t1->z - t1->floorclip + t1->height + (fixed_t)(chase_height * FRACUNIT);
5092
5093 if (Trace(t1->x, t1->y, sz, t1->Sector,
5094 vx, vy, vz, distance, 0, 0, NULL, trace) &&
5095 trace.Distance > 10 * FRACUNIT)
5096 {
5097 // Position camera slightly in front of hit thing
5098 fixed_t dist = trace.Distance - 5 * FRACUNIT;
5099 CameraX = t1->x + FixedMul(vx, dist);
5100 CameraY = t1->y + FixedMul(vy, dist);
5101 CameraZ = sz + FixedMul(vz, dist);
5102 }
5103 else
5104 {
5105 CameraX = trace.X;
5106 CameraY = trace.Y;
5107 CameraZ = trace.Z;
5108 }
5109 CameraSector = trace.Sector;
5110}
5111
5112
5113//==========================================================================
5114//
5115// P_TalkFacing
5116//
5117// Looks for something within 5.625 degrees left or right of the player
5118// to talk to.
5119//
5120//==========================================================================
5121
5122bool P_TalkFacing(AActor* player)
5123{
5124 AActor* linetarget;
5125
5126 P_AimLineAttack(player, player->angle, TALKRANGE, &linetarget, ANGLE_1 * 35, ALF_FORCENOSMART | ALF_CHECKCONVERSATION);
5127 if (linetarget == NULL)
5128 {
5129 P_AimLineAttack(player, player->angle + (ANGLE_90 >> 4), TALKRANGE, &linetarget, ANGLE_1 * 35, ALF_FORCENOSMART | ALF_CHECKCONVERSATION);
5130 if (linetarget == NULL)
5131 {
5132 P_AimLineAttack(player, player->angle - (ANGLE_90 >> 4), TALKRANGE, &linetarget, ANGLE_1 * 35, ALF_FORCENOSMART | ALF_CHECKCONVERSATION);
5133 if (linetarget == NULL)
5134 {
5135 return false;
5136 }
5137 }
5138 }
5139 // Dead things can't talk.
5140 if (linetarget->health <= 0)
5141 {
5142 return false;
5143 }
5144 // Fighting things don't talk either.
5145 if (linetarget->flags4 & MF4_INCOMBAT)
5146 {
5147 return false;
5148 }
5149 if (linetarget->Conversation != NULL)
5150 {
5151 // Give the NPC a chance to play a brief animation
5152 linetarget->ConversationAnimation(0);
5153 P_StartConversation(linetarget, player, true, true);
5154 return true;
5155 }
5156 return false;
5157}
5158
5159//==========================================================================
5160//
5161// USE LINES
5162//
5163//==========================================================================
5164
5165bool P_UseTraverse(AActor* usething, fixed_t endx, fixed_t endy, bool& foundline)
5166{
5167 FPathTraverse it(usething->x, usething->y, endx, endy, PT_ADDLINES | PT_ADDTHINGS);
5168 intercept_t* in;
5169
5170 while ((in = it.Next()))
5171 {
5172 // [RH] Check for things to talk with or use a puzzle item on
5173 if (!in->isaline)
5174 {
5175 if (usething == in->d.thing)
5176 continue;
5177 // Check thing
5178
5179 // Check for puzzle item use or USESPECIAL flag
5180 // Extended to use the same activationtype mechanism as BUMPSPECIAL does
5181 if (in->d.thing->flags5 & MF5_USESPECIAL || in->d.thing->special == UsePuzzleItem)
5182 {
5183 if (P_ActivateThingSpecial(in->d.thing, usething))
5184 return true;
5185 }
5186 continue;
5187 }
5188
5189 FLineOpening open;
5190 if (in->d.line->special == 0 || !(in->d.line->activation & (SPAC_Use | SPAC_UseThrough | SPAC_UseBack)))
5191 {
5192 blocked:
5193 if (in->d.line->flags & (ML_BLOCKEVERYTHING | ML_BLOCKUSE))
5194 {
5195 open.range = 0;
5196 }
5197 else
5198 {
5199 P_LineOpening(open, NULL, in->d.line, it.Trace().x + FixedMul(it.Trace().dx, in->frac),
5200 it.Trace().y + FixedMul(it.Trace().dy, in->frac));
5201 }
5202 if (open.range <= 0 ||
5203 (in->d.line->special != 0 && (i_compatflags & COMPATF_USEBLOCKING)))
5204 {
5205 // [RH] Give sector a chance to intercept the use
5206
5207 sector_t* sec;
5208
5209 sec = usething->Sector;
5210
5211 // [BC] Just skip right over this in client mode so we don't try to trigger any
5212 // actions.
5213 if ((NETWORK_InClientMode() == false) && sec->SecActTarget && sec->SecActTarget->TriggerAction(usething, SECSPAC_Use))
5214 {
5215 return true;
5216 }
5217
5218 sec = P_PointOnLineSide(usething->x, usething->y, in->d.line) == 0 ?
5219 in->d.line->frontsector : in->d.line->backsector;
5220
5221 if (sec != NULL && sec->SecActTarget &&
5222 sec->SecActTarget->TriggerAction(usething, SECSPAC_UseWall))
5223 {
5224 return true;
5225 }
5226
5227 if (usething->player)
5228 {
5229 // [BC] Tell clients of the sound.
5230 if (NETWORK_GetState() == NETSTATE_SERVER)
5231 SERVERCOMMANDS_SoundActor(usething, CHAN_VOICE, "*usefail", 1, ATTN_IDLE, ULONG(usething->player - players), SVCF_SKIPTHISCLIENT);
5232
5233 S_Sound(usething, CHAN_VOICE, "*usefail", 1, ATTN_IDLE);
5234 }
5235 return true; // can't use through a wall
5236 }
5237 foundline = true;
5238 continue; // not a special line, but keep checking
5239 }
5240
5241 if (P_PointOnLineSide(usething->x, usething->y, in->d.line) == 1)
5242 {
5243 if (!(in->d.line->activation & SPAC_UseBack))
5244 {
5245 // [RH] continue traversal for two-sided lines
5246 //return in->d.line->backsector != NULL; // don't use back side
5247 goto blocked; // do a proper check for back sides of triggers
5248 }
5249 else
5250 {
5251 P_ActivateLine(in->d.line, usething, 1, SPAC_UseBack);
5252 return true;
5253 }
5254 }
5255 else
5256 {
5257 if ((in->d.line->activation & (SPAC_Use | SPAC_UseThrough | SPAC_UseBack)) == SPAC_UseBack)
5258 {
5259 goto blocked; // Line cannot be used from front side so treat it as a non-trigger line
5260 }
5261
5262 P_ActivateLine(in->d.line, usething, 0, SPAC_Use);
5263
5264 //WAS can't use more than one special line in a row
5265 //jff 3/21/98 NOW multiple use allowed with enabling line flag
5266 //[RH] And now I've changed it again. If the line is of type
5267 // SPAC_USE, then it eats the use. Everything else passes
5268 // it through, including SPAC_USETHROUGH.
5269 if (i_compatflags & COMPATF_USEBLOCKING)
5270 {
5271 if (in->d.line->activation & SPAC_UseThrough) continue;
5272 else return true;
5273 }
5274 else
5275 {
5276 if (!(in->d.line->activation & SPAC_Use)) continue;
5277 else return true;
5278 }
5279
5280 }
5281
5282 }
5283 return false;
5284}
5285
5286//==========================================================================
5287//
5288// Returns false if a "oof" sound should be made because of a blocking
5289// linedef. Makes 2s middles which are impassable, as well as 2s uppers
5290// and lowers which block the player, cause the sound effect when the
5291// player tries to activate them. Specials are excluded, although it is
5292// assumed that all special linedefs within reach have been considered
5293// and rejected already (see P_UseLines).
5294//
5295// by Lee Killough
5296//
5297//==========================================================================
5298
5299bool P_NoWayTraverse(AActor* usething, fixed_t endx, fixed_t endy)
5300{
5301 FPathTraverse it(usething->x, usething->y, endx, endy, PT_ADDLINES);
5302 intercept_t* in;
5303
5304 while ((in = it.Next()))
5305 {
5306 line_t* ld = in->d.line;
5307 FLineOpening open;
5308
5309 // [GrafZahl] de-obfuscated. Was I the only one who was unable to make sense out of
5310 // this convoluted mess?
5311 if (ld->special) continue;
5312 if (ld->flags & (ML_BLOCKING | ML_BLOCKEVERYTHING | ML_BLOCK_PLAYERS)) return true;
5313 P_LineOpening(open, NULL, ld, it.Trace().x + FixedMul(it.Trace().dx, in->frac),
5314 it.Trace().y + FixedMul(it.Trace().dy, in->frac));
5315 if (open.range <= 0 ||
5316 open.bottom > usething->z + usething->MaxStepHeight ||
5317 open.top < usething->z + usething->height) return true;
5318 }
5319 return false;
5320}
5321
5322//==========================================================================
5323//
5324// P_UseLines
5325//
5326// Looks for special lines in front of the player to activate
5327//
5328//==========================================================================
5329
5330void P_UseLines(player_t* player)
5331{
5332 angle_t angle;
5333 fixed_t x1, y1, usedist;
5334 bool foundline;
5335
5336 foundline = false;
5337
5338 angle = player->mo->angle >> ANGLETOFINESHIFT;
5339 usedist = player->mo->UseRange;
5340
5341 // [NS] Now queries the Player's UseRange.
5342 x1 = player->mo->x + FixedMul(usedist, finecosine[angle]);
5343 y1 = player->mo->y + FixedMul(usedist, finesine[angle]);
5344
5345 // old code:
5346 //
5347 // P_PathTraverse ( x1, y1, x2, y2, PT_ADDLINES, PTR_UseTraverse );
5348 //
5349 // This added test makes the "oof" sound work on 2s lines -- killough:
5350
5351 if (!P_UseTraverse(player->mo, x1, y1, foundline))
5352 { // [RH] Give sector a chance to eat the use
5353 sector_t* sec = player->mo->Sector;
5354 int spac = SECSPAC_Use;
5355 if (foundline) spac |= SECSPAC_UseWall;
5356 // [BC] Don't try to trigger sector actions in client mode.
5357 if ((NETWORK_InClientMode() ||
5358 !sec->SecActTarget || !sec->SecActTarget->TriggerAction(player->mo, spac)) &&
5359 P_NoWayTraverse(player->mo, x1, y1))
5360 {
5361 S_Sound(player->mo, CHAN_VOICE, "*usefail", 1, ATTN_IDLE);
5362
5363 // [BC] Tell clients of the "oof" sound.
5364 if (NETWORK_GetState() == NETSTATE_SERVER)
5365 SERVERCOMMANDS_SoundActor(player->mo, CHAN_VOICE, "*usefail", 1, ATTN_IDLE, ULONG(player - players), SVCF_SKIPTHISCLIENT);
5366 }
5367 }
5368}
5369
5370/*
5371================
5372=
5373= [BC] P_UseItems
5374=
5375= Looks for special items in front of the player to activate
5376================
5377*/
5378
5379// [BB] Not compatible with the latest ZDoom changes, but do we really need this?
5380/*
5381void P_UseItems( player_t *pPlayer )
5382{
5383 fixed_t x1;
5384 fixed_t y1;
5385 fixed_t x2;
5386 fixed_t y2;
5387 fixed_t vrange;
5388
5389 usething = pPlayer->mo;
5390 if ( usething == NULL )
5391 return;
5392
5393 x1 = usething->x;
5394 y1 = usething->y;
5395 x2 = x1 + ((USERANGE+8)>>FRACBITS)*finecosine[usething->angle >> ANGLETOFINESHIFT];
5396 y2 = y1 + ((USERANGE+8)>>FRACBITS)*finesine[usething->angle >> ANGLETOFINESHIFT];
5397 vrange = clamp( pPlayer->userinfo.aimdist, ANGLE_1/2, ANGLE_1*35 );
5398 aim.toppitch = usething->pitch - vrange;
5399 aim.bottompitch = usething->pitch + vrange;
5400
5401 linetarget = NULL;
5402
5403 P_PathTraverse( x1, y1, x2, y2, PT_ADDTHINGS, PTR_UsethingTraverse );
5404
5405 if ( linetarget )
5406 {
5407 // Don't try to trigger sector actions in client mode.
5408 if (( NETWORK_InClientMode() == false ) &&
5409 ( linetarget->special ) &&
5410 ( linetarget->ulSTFlags & STFL_USESPECIAL ))
5411 {
5412 LineSpecials[linetarget->special]( NULL, usething, false, linetarget->args[0],
5413 linetarget->args[1], linetarget->args[2],
5414 linetarget->args[3], linetarget->args[4] );
5415 }
5416 else
5417 {
5418 S_Sound( usething, CHAN_VOICE, "*usefail", 1, ATTN_IDLE );
5419
5420 // Tell clients of the "oof" sound.
5421 if ( NETWORK_GetState( ) == NETSTATE_SERVER )
5422 SERVERCOMMANDS_SoundActor( usething, CHAN_VOICE, "*usefail", 1, ATTN_IDLE, ULONG( pPlayer - players ), SVCF_SKIPTHISCLIENT );
5423 }
5424 }
5425}
5426*/
5427/*
5428================
5429=
5430= P_PlayerScan
5431=
5432= Looks for other players directly in front of the player.
5433================
5434*/
5435
5436player_t* P_PlayerScan(AActor* pSource)
5437{
5438 fixed_t vx, vy, vz, eyez;
5439 FTraceResults trace;
5440 int pitch;
5441 angle_t angle;
5442
5443 angle = pSource->angle >> ANGLETOFINESHIFT;
5444 pitch = (angle_t)(pSource->pitch) >> ANGLETOFINESHIFT;
5445
5446 vx = FixedMul(finecosine[pitch], finecosine[angle]);
5447 vy = FixedMul(finecosine[pitch], finesine[angle]);
5448 vz = -finesine[pitch];
5449
5450 if (pSource->player)
5451 eyez = pSource->player->viewz;
5452 else
5453 eyez = pSource->z + pSource->height / 2;
5454
5455 if (Trace(pSource->x, // Actor x
5456 pSource->y, // Actor y
5457 eyez, // Actor z
5458 pSource->Sector,
5459 vx,
5460 vy,
5461 vz,
5462 (32 * 64 * FRACUNIT) /* MISSILERANGE */, // Maximum distance
5463 MF_SHOOTABLE, // Actor mask
5464 ML_BLOCKEVERYTHING, // Wall mask
5465 pSource, // Actor to ignore
5466 trace, // Result
5467 TRACE_NoSky, // Trace flags
5468 NULL) == false) // Callback
5469 // Did not spot anything anything.
5470 {
5471 return (NULL);
5472 }
5473 else
5474 {
5475 // Return NULL if we did not hit an actor.
5476 if (trace.HitType != TRACE_HitActor)
5477 return (NULL);
5478
5479 // Return NULL if the actor we hit is not a player.
5480 if (trace.Actor->player == NULL)
5481 return (NULL);
5482
5483 // Return the player we found.
5484 return (trace.Actor->player);
5485 }
5486}
5487
5488//==========================================================================
5489//
5490// P_UsePuzzleItem
5491//
5492// Returns true if the puzzle item was used on a line or a thing.
5493//
5494//==========================================================================
5495
5496bool P_UsePuzzleItem(AActor* PuzzleItemUser, int PuzzleItemType)
5497{
5498 int angle;
5499 fixed_t x1, y1, x2, y2, usedist;
5500
5501 angle = PuzzleItemUser->angle >> ANGLETOFINESHIFT;
5502 x1 = PuzzleItemUser->x;
5503 y1 = PuzzleItemUser->y;
5504
5505 // [NS] If it's a Player, get their UseRange.
5506 if (PuzzleItemUser->player)
5507 usedist = PuzzleItemUser->player->mo->UseRange;
5508 else
5509 usedist = USERANGE;
5510
5511 x2 = x1 + FixedMul(usedist, finecosine[angle]);
5512 y2 = y1 + FixedMul(usedist, finesine[angle]);
5513
5514 FPathTraverse it(x1, y1, x2, y2, PT_ADDLINES | PT_ADDTHINGS);
5515 intercept_t* in;
5516
5517 while ((in = it.Next()))
5518 {
5519 AActor* mobj;
5520 FLineOpening open;
5521
5522 if (in->isaline)
5523 { // Check line
5524 if (in->d.line->special != UsePuzzleItem)
5525 {
5526 P_LineOpening(open, NULL, in->d.line, it.Trace().x + FixedMul(it.Trace().dx, in->frac),
5527 it.Trace().y + FixedMul(it.Trace().dy, in->frac));
5528 if (open.range <= 0)
5529 {
5530 return false; // can't use through a wall
5531 }
5532 continue;
5533 }
5534 if (P_PointOnLineSide(PuzzleItemUser->x, PuzzleItemUser->y, in->d.line) == 1)
5535 { // Don't use back sides
5536 return false;
5537 }
5538 if (PuzzleItemType != in->d.line->args[0])
5539 { // Item type doesn't match
5540 return false;
5541 }
5542 int args[3] = { in->d.line->args[2], in->d.line->args[3], in->d.line->args[4] };
5543 P_StartScript(PuzzleItemUser, in->d.line, in->d.line->args[1], NULL, args, 3, ACS_ALWAYS);
5544 in->d.line->special = 0;
5545 return true;
5546 }
5547 // Check thing
5548 mobj = in->d.thing;
5549 if (mobj->special != UsePuzzleItem)
5550 { // Wrong special
5551 continue;
5552 }
5553 if (PuzzleItemType != mobj->args[0])
5554 { // Item type doesn't match
5555 continue;
5556 }
5557 int args[3] = { mobj->args[2], mobj->args[3], mobj->args[4] };
5558 P_StartScript(PuzzleItemUser, NULL, mobj->args[1], NULL, args, 3, ACS_ALWAYS);
5559 mobj->special = 0;
5560 return true;
5561 }
5562 return false;
5563}
5564
5565//==========================================================================
5566//
5567// RADIUS ATTACK
5568//
5569//
5570//==========================================================================
5571
5572
5573// [RH] Damage scale to apply to thing that shot the missile.
5574static float selfthrustscale;
5575
5576CUSTOM_CVAR(Float, splashfactor, 1.f, CVAR_SERVERINFO)
5577{
5578 if (self <= 0.f)
5579 self = 1.f;
5580 else
5581 selfthrustscale = 1.f / self;
5582}
5583
5584//==========================================================================
5585//
5586// P_RadiusAttack
5587// Source is the creature that caused the explosion at spot.
5588//
5589//==========================================================================
5590
5591void P_RadiusAttack(AActor* bombspot, AActor* bombsource, int bombdamage, int bombdistance, FName bombmod,
5592 int flags, int fulldamagedistance)
5593{
5594 if (bombdistance <= 0)
5595 return;
5596 fulldamagedistance = clamp<int>(fulldamagedistance, 0, bombdistance - 1);
5597
5598 double bombdistancefloat = 1.f / (double)(bombdistance - fulldamagedistance);
5599 double bombdamagefloat = (double)bombdamage;
5600
5601 FVector3 bombvec(FIXED2FLOAT(bombspot->x), FIXED2FLOAT(bombspot->y), FIXED2FLOAT(bombspot->z));
5602
5603 FBlockThingsIterator it(FBoundingBox(bombspot->x, bombspot->y, bombdistance << FRACBITS));
5604 AActor* thing;
5605
5606 if (flags & RADF_SOURCEISSPOT)
5607 { // The source is actually the same as the spot, even if that wasn't what we received.
5608 bombsource = bombspot;
5609 }
5610
5611 while ((thing = it.Next()))
5612 {
5613 // Vulnerable actors can be damaged by radius attacks even if not shootable
5614 // Used to emulate MBF's vulnerability of non-missile bouncers to explosions.
5615 if (!((thing->flags & MF_SHOOTABLE) || (thing->flags6 & MF6_VULNERABLE)))
5616 continue;
5617
5618 // Boss spider and cyborg and Heretic's ep >= 2 bosses
5619 // take no damage from concussion.
5620 if (thing->flags3 & MF3_NORADIUSDMG && !(bombspot->flags4 & MF4_FORCERADIUSDMG))
5621 continue;
5622
5623 if (!(flags & RADF_HURTSOURCE) && (thing == bombsource || thing == bombspot))
5624 { // don't damage the source of the explosion
5625 continue;
5626 }
5627
5628 // a much needed option: monsters that fire explosive projectiles cannot
5629 // be hurt by projectiles fired by a monster of the same type.
5630 // Controlled by the DONTHARMCLASS and DONTHARMSPECIES flags.
5631 if ((bombsource && !thing->player) // code common to both checks
5632 && ( // Class check first
5633 ((bombsource->flags4 & MF4_DONTHARMCLASS) && (thing->GetClass() == bombsource->GetClass()))
5634 || // Nigh-identical species check second
5635 ((bombsource->flags6 & MF6_DONTHARMSPECIES) && (thing->GetSpecies() == bombsource->GetSpecies()))
5636 )
5637 ) continue;
5638
5639 // Barrels always use the original code, since this makes
5640 // them far too "active." BossBrains also use the old code
5641 // because some user levels require they have a height of 16,
5642 // which can make them near impossible to hit with the new code.
5643 if ((flags & RADF_NODAMAGE) || (!((bombspot->flags5 | thing->flags5) & MF5_OLDRADIUSDMG)
5644 && !(zacompatflags & ZACOMPATF_OLDRADIUSDMG)))
5645 {
5646 // [RH] New code. The bounding box only covers the
5647 // height of the thing and not the height of the map.
5648 double points;
5649 double len;
5650 fixed_t dx, dy;
5651 double boxradius;
5652
5653 dx = abs(thing->x - bombspot->x);
5654 dy = abs(thing->y - bombspot->y);
5655 boxradius = double(thing->radius);
5656
5657 // The damage pattern is square, not circular.
5658 len = double(dx > dy ? dx : dy);
5659
5660 if (bombspot->z < thing->z || bombspot->z >= thing->z + thing->height)
5661 {
5662 double dz;
5663
5664 if (bombspot->z > thing->z)
5665 {
5666 dz = double(bombspot->z - thing->z - thing->height);
5667 }
5668 else
5669 {
5670 dz = double(thing->z - bombspot->z);
5671 }
5672 if (len <= boxradius)
5673 {
5674 len = dz;
5675 }
5676 else
5677 {
5678 len -= boxradius;
5679 len = sqrt(len * len + dz * dz);
5680 }
5681 }
5682 else
5683 {
5684 len -= boxradius;
5685 if (len < 0.f)
5686 len = 0.f;
5687 }
5688 len /= FRACUNIT;
5689 len = clamp<double>(len - (double)fulldamagedistance, 0, len);
5690 points = bombdamagefloat * (1.f - len * bombdistancefloat);
5691 if (thing == bombsource)
5692 {
5693 points = points * splashfactor;
5694 }
5695 points *= thing->GetClass()->Meta.GetMetaFixed(AMETA_RDFactor, FRACUNIT) / (double)FRACUNIT;
5696
5697 // points and bombdamage should be the same sign
5698 if ((points * bombdamage) > 0 && P_CheckSight(thing, bombspot, SF_IGNOREVISIBILITY | SF_IGNOREWATERBOUNDARY))
5699 { // OK to damage; target is in direct path
5700 double velz;
5701 double thrust;
5702 // [BB] We need to store these values for ZACOMPATF_OLD_EXPLOSION_THRUST.
5703 const fixed_t origvelx = thing->velx;
5704 const fixed_t origvely = thing->vely;
5705 int damage = abs((int)points);
5706 int newdam = damage;
5707
5708 // [BC] Damage is server side.
5709 if (NETWORK_InClientMode() == false)
5710 {
5711 if (!(flags & RADF_NODAMAGE))
5712 newdam = P_DamageMobj(thing, bombspot, bombsource, damage, bombmod);
5713 else if (thing->player == NULL && !(flags & RADF_NOIMPACTDAMAGE))
5714 {
5715 thing->flags2 |= MF2_BLASTED;
5716
5717 // [BB] If we're the server, tell clients to update the flags of the object.
5718 if (NETWORK_GetState() == NETSTATE_SERVER)
5719 SERVERCOMMANDS_SetThingFlags(thing, FLAGSET_FLAGS2);
5720 }
5721 }
5722
5723 // [BC] If this explosion damaged a player, and the explosion originated from a
5724 // player, mark the source player as striking a player, to potentially reward an
5725 // accuracy medal.
5726 if ((bombsource) &&
5727 (bombsource->player) &&
5728 (thing != bombsource) &&
5729 (thing->player) &&
5730 (thing->IsTeammate(bombsource) == false))
5731 {
5732 bombsource->player->bStruckPlayer = true;
5733 }
5734
5735 if (!(thing->flags & MF_ICECORPSE))
5736 {
5737 if (!(flags & RADF_NODAMAGE) && !(bombspot->flags3 & MF3_BLOODLESSIMPACT))
5738 P_TraceBleed(newdam > 0 ? newdam : damage, thing, bombspot);
5739
5740 if ((flags & RADF_NODAMAGE) || !(bombspot->flags2 & MF2_NODMGTHRUST))
5741 {
5742 if (bombsource == NULL || !(bombsource->flags2 & MF2_NODMGTHRUST))
5743 {
5744 thrust = points * 0.5f / (double)thing->Mass;
5745 if (bombsource == thing)
5746 {
5747 thrust *= selfthrustscale;
5748 }
5749 velz = (double)(thing->z + (thing->height >> 1) - bombspot->z) * thrust;
5750 if (bombsource != thing)
5751 {
5752 velz *= 0.5f;
5753 }
5754 else
5755 {
5756 velz *= 0.8f;
5757 }
5758
5759 // [BB] Potentially use the horizontal thrust of old ZDoom versions.
5760 if (zacompatflags & ZACOMPATF_OLD_EXPLOSION_THRUST)
5761 {
5762 thing->velx = origvelx + static_cast<fixed_t>((thing->x - bombspot->x) * thrust);
5763 thing->vely = origvely + static_cast<fixed_t>((thing->y - bombspot->y) * thrust);
5764 }
5765 else
5766 {
5767 angle_t ang = R_PointToAngle2(bombspot->x, bombspot->y, thing->x, thing->y) >> ANGLETOFINESHIFT;
5768 thing->velx += fixed_t(finecosine[ang] * thrust);
5769 thing->vely += fixed_t(finesine[ang] * thrust);
5770 }
5771
5772 // [BB] If ZADF_NO_ROCKET_JUMPING is on, don't give players any z-velocity if the attack was made by a player.
5773 if (((zadmflags & ZADF_NO_ROCKET_JUMPING) == false) ||
5774 (bombsource == NULL) || (bombsource->player == NULL) || (thing->player == NULL))
5775 {
5776 if (!(flags & RADF_NODAMAGE))
5777 thing->velz += (fixed_t)velz; // this really doesn't work well
5778 }
5779 }
5780
5781 // [BC] If we're the server, update the thing's velocity.
5782 // [BB] Use SERVER_UpdateThingVelocity to prevent sync problems.
5783 if (NETWORK_GetState() == NETSTATE_SERVER)
5784 SERVER_UpdateThingVelocity(thing, true);
5785 }
5786 }
5787 }
5788 }
5789 else
5790 {
5791 // [RH] Old code just for barrels
5792 fixed_t dx, dy, dist;
5793
5794 dx = abs(thing->x - bombspot->x);
5795 dy = abs(thing->y - bombspot->y);
5796
5797 dist = dx > dy ? dx : dy;
5798 dist = (dist - thing->radius) >> FRACBITS;
5799
5800 if (dist < 0)
5801 dist = 0;
5802
5803 if (dist >= bombdistance)
5804 continue; // out of range
5805
5806 if (P_CheckSight(thing, bombspot, SF_IGNOREVISIBILITY | SF_IGNOREWATERBOUNDARY))
5807 { // OK to damage; target is in direct path
5808 dist = clamp<int>(dist - fulldamagedistance, 0, dist);
5809 int damage = Scale(bombdamage, bombdistance - dist, bombdistance);
5810 damage = (int)((double)damage * splashfactor);
5811
5812 damage = Scale(damage, thing->GetClass()->Meta.GetMetaFixed(AMETA_RDFactor, FRACUNIT), FRACUNIT);
5813 if (damage > 0)
5814 {
5815 // [BC/BB] Damage is server side.
5816 int newdam = 0;
5817 if ((NETWORK_GetState() != NETSTATE_CLIENT) && (CLIENTDEMO_IsPlaying() == false))
5818 newdam = P_DamageMobj(thing, bombspot, bombsource, damage, bombmod);
5819 P_TraceBleed(newdam > 0 ? newdam : damage, thing, bombspot);
5820 }
5821 }
5822 }
5823 }
5824
5825 // [BB] If the bombsource is a player and hit another player with his attack, potentially give him a medal.
5826 PLAYER_CheckStruckPlayer(bombsource);
5827}
5828
5829//==========================================================================
5830//
5831// SECTOR HEIGHT CHANGING
5832// After modifying a sector's floor or ceiling height,
5833// call this routine to adjust the positions
5834// of all things that touch the sector.
5835//
5836// If anything doesn't fit anymore, true will be returned.
5837//
5838// [RH] If crushchange is non-negative, they will take the
5839// specified amount of damage as they are being crushed.
5840// If crushchange is negative, you should set the sector
5841// height back the way it was and call P_ChangeSector()
5842// again to undo the changes.
5843// Note that this is very different from the original
5844// true/false usage of crushchange! If you want regular
5845// DOOM crushing behavior set crushchange to 10 or -1
5846// if no crushing is desired.
5847//
5848//==========================================================================
5849
5850
5851struct FChangePosition
5852{
5853 sector_t* sector;
5854 int moveamt;
5855 int crushchange;
5856 bool nofit;
5857 bool movemidtex;
5858};
5859
5860TArray<AActor*> intersectors;
5861
5862EXTERN_CVAR(Int, cl_bloodtype)
5863
5864//=============================================================================
5865//
5866// P_AdjustFloorCeil
5867//
5868//=============================================================================
5869
5870bool P_AdjustFloorCeil(AActor* thing, FChangePosition* cpos)
5871{
5872 int flags2 = thing->flags2 & MF2_PASSMOBJ;
5873 FCheckPosition tm;
5874
5875 if ((thing->flags2 & MF2_PASSMOBJ) && (thing->flags3 & MF3_ISMONSTER))
5876 {
5877 tm.FromPMove = true;
5878 }
5879
5880 if (cpos->movemidtex)
5881 {
5882 // From Eternity:
5883 // ALL things must be treated as PASSMOBJ when moving
5884 // 3DMidTex lines, otherwise you get stuck in them.
5885 thing->flags2 |= MF2_PASSMOBJ;
5886 }
5887
5888 bool isgood = P_CheckPosition(thing, thing->x, thing->y, tm);
5889 thing->floorz = tm.floorz;
5890 thing->ceilingz = tm.ceilingz;
5891 thing->dropoffz = tm.dropoffz; // killough 11/98: remember dropoffs
5892 thing->floorpic = tm.floorpic;
5893 thing->floorsector = tm.floorsector;
5894 thing->ceilingpic = tm.ceilingpic;
5895 thing->ceilingsector = tm.ceilingsector;
5896
5897 // restore the PASSMOBJ flag but leave the other flags alone.
5898 thing->flags2 = (thing->flags2 & ~MF2_PASSMOBJ) | flags2;
5899
5900 return isgood;
5901}
5902
5903//=============================================================================
5904//
5905// P_OldAdjustFloorCeil
5906//
5907//=============================================================================
5908
5909bool P_OldAdjustFloorCeil(AActor* thing)
5910{
5911 FCheckPosition tm;
5912 bool isgood = P_CheckPosition(thing, thing->x, thing->y, tm);
5913 thing->floorz = tm.floorz;
5914 thing->ceilingz = tm.ceilingz;
5915 thing->dropoffz = tm.dropoffz; // killough 11/98: remember dropoffs
5916 thing->floorpic = tm.floorpic;
5917 thing->floorsector = tm.floorsector;
5918 thing->ceilingpic = tm.ceilingpic;
5919 thing->ceilingsector = tm.ceilingsector;
5920 return isgood;
5921}
5922
5923//=============================================================================
5924//
5925// P_FindAboveIntersectors
5926//
5927//=============================================================================
5928
5929void P_FindAboveIntersectors(AActor* actor)
5930{
5931 if (actor->flags & MF_NOCLIP)
5932 return;
5933
5934 if (!(actor->flags & MF_SOLID))
5935 return;
5936
5937 AActor* thing;
5938 FBlockThingsIterator it(FBoundingBox(actor->x, actor->y, actor->radius));
5939 while ((thing = it.Next()))
5940 {
5941 if (!thing->intersects(actor))
5942 {
5943 continue;
5944 }
5945 if (!(thing->flags & MF_SOLID))
5946 { // Can't hit thing
5947 continue;
5948 }
5949 if (thing->flags & (MF_SPECIAL))
5950 { // [RH] Corpses and specials don't block moves
5951 continue;
5952 }
5953 if (thing->flags & (MF_CORPSE))
5954 { // Corpses need a few more checks
5955 if (!(actor->flags & MF_ICECORPSE))
5956 continue;
5957 }
5958 if (thing == actor)
5959 { // Don't clip against self
5960 continue;
5961 }
5962 if (!((thing->flags2 | actor->flags2) & MF2_PASSMOBJ) && !((thing->flags3 | actor->flags3) & MF3_ISMONSTER))
5963 {
5964 // Don't bother if both things don't have MF2_PASSMOBJ set and aren't monsters.
5965 // These things would always block each other which in nearly every situation is
5966 // not what is wanted here.
5967 continue;
5968 }
5969 if (thing->z >= actor->z &&
5970 thing->z <= actor->z + actor->height)
5971 { // Thing intersects above the base
5972 intersectors.Push(thing);
5973 }
5974 }
5975}
5976
5977//=============================================================================
5978//
5979// P_FindBelowIntersectors
5980//
5981//=============================================================================
5982
5983void P_FindBelowIntersectors(AActor* actor)
5984{
5985 if (actor->flags & MF_NOCLIP)
5986 return;
5987
5988 if (!(actor->flags & MF_SOLID))
5989 return;
5990
5991 AActor* thing;
5992 FBlockThingsIterator it(FBoundingBox(actor->x, actor->y, actor->radius));
5993 while ((thing = it.Next()))
5994 {
5995 if (!thing->intersects(actor))
5996 {
5997 continue;
5998 }
5999 if (!(thing->flags & MF_SOLID))
6000 { // Can't hit thing
6001 continue;
6002 }
6003 if (thing->flags & (MF_SPECIAL))
6004 { // [RH] Corpses and specials don't block moves
6005 continue;
6006 }
6007 if (thing->flags & (MF_CORPSE))
6008 { // Corpses need a few more checks
6009 if (!(actor->flags & MF_ICECORPSE))
6010 continue;
6011 }
6012 if (thing == actor)
6013 { // Don't clip against self
6014 continue;
6015 }
6016 if (!((thing->flags2 | actor->flags2) & MF2_PASSMOBJ) && !((thing->flags3 | actor->flags3) & MF3_ISMONSTER))
6017 {
6018 // Don't bother if both things don't have MF2_PASSMOBJ set and aren't monsters.
6019 // These things would always block each other which in nearly every situation is
6020 // not what is wanted here.
6021 continue;
6022 }
6023 if (thing->z + thing->height <= actor->z + actor->height &&
6024 thing->z + thing->height > actor->z)
6025 { // Thing intersects below the base
6026 intersectors.Push(thing);
6027 }
6028 }
6029}
6030
6031//=============================================================================
6032//
6033// P_DoCrunch
6034//
6035//=============================================================================
6036
6037void P_DoCrunch(AActor* thing, FChangePosition* cpos)
6038{
6039 ULONG ulIdx;
6040
6041 // [BC] Don't handle the respawning of crunched items on the client end.
6042 if (NETWORK_InClientMode() == false)
6043 {
6044 // [BC] If this item is a flag belonging to a team, return it, and/or don't do shit!
6045 for (ulIdx = 0; ulIdx < teams.Size(); ulIdx++)
6046 {
6047 if (thing->GetClass() == TEAM_GetItem(ulIdx))
6048 {
6049 if (thing->flags & MF_DROPPED)
6050 TEAM_ExecuteReturnRoutine(ulIdx, NULL);
6051
6052 return;
6053 }
6054 }
6055
6056 // [BC] Do the same thing, except for the white flag.
6057 if (thing->GetClass() == PClass::FindClass("WhiteFlag"))
6058 {
6059 if (thing->flags & MF_DROPPED)
6060 TEAM_ExecuteReturnRoutine(teams.Size(), NULL);
6061
6062 return;
6063 }
6064
6065 // [BC] If this is the terminator or possession artifact, respawn them on the map.
6066 if ((terminator) || (possession) || (teampossession))
6067 {
6068 if (thing->GetClass()->IsDescendantOf(RUNTIME_CLASS(APowerupGiver)))
6069 {
6070 if (static_cast<APowerupGiver*>(thing)->PowerupType == RUNTIME_CLASS(APowerTerminatorArtifact))
6071 GAME_SpawnTerminatorArtifact();
6072
6073 if (static_cast<APowerupGiver*>(thing)->PowerupType == RUNTIME_CLASS(APowerPossessionArtifact))
6074 GAME_SpawnPossessionArtifact();
6075 }
6076 }
6077 }
6078
6079
6080 if (!(thing && thing->Grind(true) && cpos)) return;
6081 cpos->nofit = true;
6082
6083 if ((cpos->crushchange > 0) && !(level.maptime & 3))
6084 {
6085 // [BC] Damage is done server-side.
6086 int newdam = 0;
6087 if (NETWORK_InClientMode() == false)
6088 newdam = P_DamageMobj(thing, NULL, NULL, cpos->crushchange, NAME_Crush);
6089
6090 // spray blood in a random direction
6091 if (!(thing->flags2 & (MF2_INVULNERABLE | MF2_DORMANT)))
6092 {
6093 if (!(thing->flags & MF_NOBLOOD))
6094 {
6095 PalEntry bloodcolor = thing->GetBloodColor();
6096 const PClass* bloodcls = thing->GetBloodType();
6097
6098 P_TraceBleed(newdam > 0 ? newdam : cpos->crushchange, thing);
6099
6100 if (bloodcls != NULL || (NETWORK_GetState() == NETSTATE_SERVER)) // [BB]
6101 {
6102 AActor* mo;
6103
6104 mo = Spawn(bloodcls, thing->x, thing->y,
6105 thing->z + thing->height / 2, ALLOW_REPLACE);
6106
6107 mo->velx = pr_crunch.Random2() << 12;
6108 mo->vely = pr_crunch.Random2() << 12;
6109 if (bloodcolor != 0 && !(mo->flags2 & MF2_DONTTRANSLATE))
6110 {
6111 mo->Translation = TRANSLATION(TRANSLATION_Blood, bloodcolor.a);
6112 }
6113
6114 if (!(cl_bloodtype <= 1)) mo->renderflags |= RF_INVISIBLE;
6115 }
6116
6117 angle_t an;
6118 an = (M_Random() - 128) << 24;
6119 if (cl_bloodtype >= 1)
6120 {
6121 P_DrawSplash2(32, thing->x, thing->y, thing->z + thing->height / 2, an, 2, bloodcolor);
6122 }
6123 }
6124 if (thing->CrushPainSound != 0 && !S_GetSoundPlayingInfo(thing, thing->CrushPainSound))
6125 {
6126 S_Sound(thing, CHAN_VOICE, thing->CrushPainSound, 1.f, ATTN_NORM);
6127 }
6128 }
6129 }
6130
6131 // keep checking (crush other things)
6132 return;
6133}
6134
6135//=============================================================================
6136//
6137// P_PushUp
6138//
6139// Returns 0 if thing fits, 1 if ceiling got in the way, or 2 if something
6140// above it didn't fit.
6141//=============================================================================
6142
6143int P_PushUp(AActor* thing, FChangePosition* cpos)
6144{
6145 unsigned int firstintersect = intersectors.Size();
6146 unsigned int lastintersect;
6147 int mymass = thing->Mass;
6148
6149 if (thing->z + thing->height > thing->ceilingz)
6150 {
6151 return 1;
6152 }
6153 // [GZ] Skip thing intersect test for THRUACTORS things.
6154 if (thing->flags2 & MF2_THRUACTORS)
6155 return 0;
6156 P_FindAboveIntersectors(thing);
6157 lastintersect = intersectors.Size();
6158 for (; firstintersect < lastintersect; firstintersect++)
6159 {
6160 AActor* intersect = intersectors[firstintersect];
6161 // [GZ] Skip this iteration for THRUSPECIES things
6162 // Should there be MF2_THRUGHOST / MF3_GHOST checks there too for consistency?
6163 // Or would that risk breaking established behavior? THRUGHOST, like MTHRUSPECIES,
6164 // is normally for projectiles which would have exploded by now anyway...
6165 // [BB] Adapted this for ZADF_UNBLOCK_PLAYERS.
6166 if (P_CheckUnblock(thing, intersect) || (thing->flags6 & MF6_THRUSPECIES && thing->GetSpecies() == intersect->GetSpecies()))
6167 continue;
6168 if (!(intersect->flags2 & MF2_PASSMOBJ) ||
6169 (!(intersect->flags3 & MF3_ISMONSTER) && intersect->Mass > mymass) ||
6170 (intersect->flags4 & MF4_ACTLIKEBRIDGE)
6171 )
6172 {
6173 // Can't push bridges or things more massive than ourself
6174 return 2;
6175 }
6176 fixed_t oldz = intersect->z;
6177 P_AdjustFloorCeil(intersect, cpos);
6178 intersect->z = thing->z + thing->height + 1;
6179 if (P_PushUp(intersect, cpos))
6180 { // Move blocked
6181 P_DoCrunch(intersect, cpos);
6182 intersect->z = oldz;
6183 return 2;
6184 }
6185 }
6186 return 0;
6187}
6188
6189//=============================================================================
6190//
6191// P_PushDown
6192//
6193// Returns 0 if thing fits, 1 if floor got in the way, or 2 if something
6194// below it didn't fit.
6195//=============================================================================
6196
6197int P_PushDown(AActor* thing, FChangePosition* cpos)
6198{
6199 unsigned int firstintersect = intersectors.Size();
6200 unsigned int lastintersect;
6201 int mymass = thing->Mass;
6202
6203 if (thing->z <= thing->floorz)
6204 {
6205 return 1;
6206 }
6207 P_FindBelowIntersectors(thing);
6208 lastintersect = intersectors.Size();
6209 for (; firstintersect < lastintersect; firstintersect++)
6210 {
6211 AActor* intersect = intersectors[firstintersect];
6212 if (!(intersect->flags2 & MF2_PASSMOBJ) ||
6213 (!(intersect->flags3 & MF3_ISMONSTER) && intersect->Mass > mymass) ||
6214 (intersect->flags4 & MF4_ACTLIKEBRIDGE)
6215 )
6216 {
6217 // Can't push bridges or things more massive than ourself
6218 return 2;
6219 }
6220 fixed_t oldz = intersect->z;
6221 P_AdjustFloorCeil(intersect, cpos);
6222 if (oldz > thing->z - intersect->height)
6223 { // Only push things down, not up.
6224 intersect->z = thing->z - intersect->height;
6225 if (P_PushDown(intersect, cpos))
6226 { // Move blocked
6227 P_DoCrunch(intersect, cpos);
6228 intersect->z = oldz;
6229 return 2;
6230 }
6231 }
6232 }
6233 return 0;
6234}
6235
6236//=============================================================================
6237//
6238// PIT_FloorDrop
6239//
6240//=============================================================================
6241
6242void PIT_FloorDrop(AActor* thing, FChangePosition* cpos)
6243{
6244 fixed_t oldfloorz = thing->floorz;
6245
6246 P_AdjustFloorCeil(thing, cpos);
6247
6248 if (oldfloorz == thing->floorz) return;
6249 if (thing->flags4 & MF4_ACTLIKEBRIDGE) return; // do not move bridge things
6250
6251 if (thing->velz == 0 &&
6252 (!(thing->flags & MF_NOGRAVITY) ||
6253 (thing->z == oldfloorz && !(thing->flags & MF_NOLIFTDROP))))
6254 {
6255 fixed_t oldz = thing->z;
6256
6257 if ((thing->flags & MF_NOGRAVITY) || (thing->flags5 & MF5_MOVEWITHSECTOR) ||
6258 (((cpos->sector->Flags & SECF_FLOORDROP) || cpos->moveamt < 9 * FRACUNIT)
6259 && thing->z - thing->floorz <= cpos->moveamt))
6260 {
6261 thing->z = thing->floorz;
6262 P_CheckFakeFloorTriggers(thing, oldz);
6263 }
6264
6265 // [BC] Mark this thing as having moved.
6266 thing->ulSTFlags |= STFL_POSITIONCHANGED;
6267 }
6268 else if ((thing->z != oldfloorz && !(thing->flags & MF_NOLIFTDROP)))
6269 {
6270 fixed_t oldz = thing->z;
6271 if ((thing->flags & MF_NOGRAVITY) && (thing->flags6 & MF6_RELATIVETOFLOOR))
6272 {
6273 thing->z = thing->z - oldfloorz + thing->floorz;
6274 P_CheckFakeFloorTriggers(thing, oldz);
6275 }
6276 }
6277}
6278
6279//=============================================================================
6280//
6281// PIT_FloorRaise
6282//
6283//=============================================================================
6284
6285void PIT_FloorRaise(AActor* thing, FChangePosition* cpos)
6286{
6287 fixed_t oldfloorz = thing->floorz;
6288 fixed_t oldz = thing->z;
6289
6290 P_AdjustFloorCeil(thing, cpos);
6291
6292 if (oldfloorz == thing->floorz) return;
6293
6294 // Move things intersecting the floor up
6295 if (thing->z <= thing->floorz)
6296 {
6297 if (thing->flags4 & MF4_ACTLIKEBRIDGE)
6298 {
6299 cpos->nofit = true;
6300 return; // do not move bridge things
6301 }
6302 intersectors.Clear();
6303 thing->z = thing->floorz;
6304
6305 // [BC] Mark this thing as having moved.
6306 thing->ulSTFlags |= STFL_POSITIONCHANGED;
6307 }
6308 else
6309 {
6310 if ((thing->flags & MF_NOGRAVITY) && (thing->flags6 & MF6_RELATIVETOFLOOR))
6311 {
6312 intersectors.Clear();
6313 thing->z = thing->z - oldfloorz + thing->floorz;
6314
6315 // [BC/BB] Mark this thing as having moved.
6316 thing->ulSTFlags |= STFL_POSITIONCHANGED;
6317 }
6318 else return;
6319 }
6320 switch (P_PushUp(thing, cpos))
6321 {
6322 default:
6323 P_CheckFakeFloorTriggers(thing, oldz);
6324 break;
6325 case 1:
6326 P_DoCrunch(thing, cpos);
6327 P_CheckFakeFloorTriggers(thing, oldz);
6328 break;
6329 case 2:
6330 P_DoCrunch(thing, cpos);
6331 thing->z = oldz;
6332 break;
6333 }
6334}
6335
6336//=============================================================================
6337//
6338// PIT_CeilingLower
6339//
6340//=============================================================================
6341
6342void PIT_CeilingLower(AActor* thing, FChangePosition* cpos)
6343{
6344 bool onfloor;
6345
6346 onfloor = thing->z <= thing->floorz;
6347 P_AdjustFloorCeil(thing, cpos);
6348
6349 if (thing->z + thing->height > thing->ceilingz)
6350 {
6351 if (thing->flags4 & MF4_ACTLIKEBRIDGE)
6352 {
6353 cpos->nofit = true;
6354 return; // do not move bridge things
6355 }
6356 intersectors.Clear();
6357 fixed_t oldz = thing->z;
6358 if (thing->ceilingz - thing->height >= thing->floorz)
6359 {
6360 thing->z = thing->ceilingz - thing->height;
6361 }
6362 else
6363 {
6364 thing->z = thing->floorz;
6365 }
6366
6367 // [BC] Mark this thing as having moved.
6368 thing->ulSTFlags |= STFL_POSITIONCHANGED;
6369
6370 switch (P_PushDown(thing, cpos))
6371 {
6372 case 2:
6373 // intentional fall-through
6374 case 1:
6375 if (onfloor)
6376 thing->z = thing->floorz;
6377 P_DoCrunch(thing, cpos);
6378 P_CheckFakeFloorTriggers(thing, oldz);
6379 break;
6380 default:
6381 P_CheckFakeFloorTriggers(thing, oldz);
6382 break;
6383 }
6384 }
6385}
6386
6387//=============================================================================
6388//
6389// PIT_CeilingRaise
6390//
6391//=============================================================================
6392
6393void PIT_CeilingRaise(AActor* thing, FChangePosition* cpos)
6394{
6395 bool isgood = P_AdjustFloorCeil(thing, cpos);
6396
6397 if (thing->flags4 & MF4_ACTLIKEBRIDGE) return; // do not move bridge things
6398
6399 // For DOOM compatibility, only move things that are inside the floor.
6400 // (or something else?) Things marked as hanging from the ceiling will
6401 // stay where they are.
6402 if (thing->z < thing->floorz &&
6403 thing->z + thing->height >= thing->ceilingz - cpos->moveamt &&
6404 !(thing->flags & MF_NOLIFTDROP))
6405 {
6406 fixed_t oldz = thing->z;
6407 thing->z = thing->floorz;
6408 if (thing->z + thing->height > thing->ceilingz)
6409 {
6410 thing->z = thing->ceilingz - thing->height;
6411 }
6412 P_CheckFakeFloorTriggers(thing, oldz);
6413
6414 // [BC] Mark this thing as having moved.
6415 thing->ulSTFlags |= STFL_POSITIONCHANGED;
6416 }
6417 else if ((thing->flags2 & MF2_PASSMOBJ) && !isgood && thing->z + thing->height < thing->ceilingz)
6418 {
6419 AActor* onmobj;
6420 if (!P_TestMobjZ(thing, true, &onmobj) && onmobj->z <= thing->z)
6421 {
6422 thing->z = MIN(thing->ceilingz - thing->height,
6423 onmobj->z + onmobj->height);
6424
6425 // [BC] Mark this thing as having moved.
6426 thing->ulSTFlags |= STFL_POSITIONCHANGED;
6427 }
6428 }
6429}
6430
6431//=============================================================================
6432//
6433// P_ChangeSector [RH] Was P_CheckSector in BOOM
6434//
6435// jff 3/19/98 added to just check monsters on the periphery
6436// of a moving sector instead of all in bounding box of the
6437// sector. Both more accurate and faster.
6438//
6439//=============================================================================
6440
6441bool P_ChangeSector(sector_t* sector, int crunch, int amt, int floorOrCeil, bool isreset)
6442{
6443 FChangePosition cpos;
6444 void(*iterator)(AActor*, FChangePosition*);
6445 void(*iterator2)(AActor*, FChangePosition*) = NULL;
6446 msecnode_t* n;
6447
6448 cpos.nofit = false;
6449 cpos.crushchange = crunch;
6450 cpos.moveamt = abs(amt);
6451 cpos.movemidtex = false;
6452 cpos.sector = sector;
6453
6454#ifdef _3DFLOORS
6455 // Also process all sectors that have 3D floors transferred from the
6456 // changed sector.
6457 if (sector->e->XFloor.attached.Size())
6458 {
6459 unsigned i;
6460 sector_t* sec;
6461
6462
6463 // Use different functions for the four different types of sector movement.
6464 // for 3D-floors the meaning of floor and ceiling is inverted!!!
6465 if (floorOrCeil == 1)
6466 {
6467 iterator = (amt >= 0) ? PIT_FloorRaise : PIT_FloorDrop;
6468 }
6469 else
6470 {
6471 iterator = (amt >= 0) ? PIT_CeilingRaise : PIT_CeilingLower;
6472 }
6473
6474 for (i = 0; i < sector->e->XFloor.attached.Size(); i++)
6475 {
6476 sec = sector->e->XFloor.attached[i];
6477 P_Recalculate3DFloors(sec); // Must recalculate the 3d floor and light lists
6478
6479 // no thing checks for attached sectors because of heightsec
6480 if (sec->heightsec == sector) continue;
6481
6482 for (n = sec->touching_thinglist; n; n = n->m_snext) n->visited = false;
6483 do
6484 {
6485 for (n = sec->touching_thinglist; n; n = n->m_snext)
6486 {
6487 if (!n->visited)
6488 {
6489 n->visited = true;
6490 if (!(n->m_thing->flags & MF_NOBLOCKMAP) || //jff 4/7/98 don't do these
6491 (n->m_thing->flags5 & MF5_MOVEWITHSECTOR))
6492 {
6493 iterator(n->m_thing, &cpos);
6494 }
6495 break;
6496 }
6497 }
6498 } while (n);
6499 }
6500 }
6501 P_Recalculate3DFloors(sector); // Must recalculate the 3d floor and light lists
6502#endif
6503
6504
6505 // [RH] Use different functions for the four different types of sector
6506 // movement.
6507 switch (floorOrCeil)
6508 {
6509 case 0:
6510 // floor
6511 iterator = (amt < 0) ? PIT_FloorDrop : PIT_FloorRaise;
6512 break;
6513
6514 case 1:
6515 // ceiling
6516 iterator = (amt < 0) ? PIT_CeilingLower : PIT_CeilingRaise;
6517 break;
6518
6519 case 2:
6520 // 3dmidtex
6521 // This must check both floor and ceiling
6522 iterator = (amt < 0) ? PIT_FloorDrop : PIT_FloorRaise;
6523 iterator2 = (amt < 0) ? PIT_CeilingLower : PIT_CeilingRaise;
6524 cpos.movemidtex = true;
6525 break;
6526
6527 default:
6528 // invalid
6529 assert(floorOrCeil > 0 && floorOrCeil < 2);
6530 return false;
6531 }
6532
6533 // killough 4/4/98: scan list front-to-back until empty or exhausted,
6534 // restarting from beginning after each thing is processed. Avoids
6535 // crashes, and is sure to examine all things in the sector, and only
6536 // the things which are in the sector, until a steady-state is reached.
6537 // Things can arbitrarily be inserted and removed and it won't mess up.
6538 //
6539 // killough 4/7/98: simplified to avoid using complicated counter
6540
6541 // Mark all things invalid
6542
6543 for (n = sector->touching_thinglist; n; n = n->m_snext)
6544 n->visited = false;
6545
6546 do
6547 {
6548 for (n = sector->touching_thinglist; n; n = n->m_snext) // go through list
6549 {
6550 if (!n->visited) // unprocessed thing found
6551 {
6552 n->visited = true; // mark thing as processed
6553 if (!(n->m_thing->flags & MF_NOBLOCKMAP) || //jff 4/7/98 don't do these
6554 (n->m_thing->flags5 & MF5_MOVEWITHSECTOR))
6555 {
6556 iterator(n->m_thing, &cpos); // process it
6557 if (iterator2 != NULL) iterator2(n->m_thing, &cpos);
6558 }
6559 break; // exit and start over
6560 }
6561 }
6562 } while (n); // repeat from scratch until all things left are marked valid
6563
6564 if (!cpos.nofit && !isreset /* && sector->MoreFlags & (SECF_UNDERWATERMASK)*/)
6565 {
6566 // If this is a control sector for a deep water transfer, all actors in affected
6567 // sectors need to have their waterlevel information updated and if applicable,
6568 // execute appropriate sector actions.
6569 // Only check if the sector move was successful.
6570 TArray<sector_t*>& secs = sector->e->FakeFloor.Sectors;
6571 for (unsigned i = 0; i < secs.Size(); i++)
6572 {
6573 sector_t* s = secs[i];
6574
6575 for (n = s->touching_thinglist; n; n = n->m_snext)
6576 n->visited = false;
6577
6578 do
6579 {
6580 for (n = s->touching_thinglist; n; n = n->m_snext) // go through list
6581 {
6582 if (!n->visited && n->m_thing->Sector == s) // unprocessed thing found
6583 {
6584 n->visited = true; // mark thing as processed
6585
6586 n->m_thing->UpdateWaterLevel(n->m_thing->z, false);
6587 P_CheckFakeFloorTriggers(n->m_thing, n->m_thing->z - amt);
6588 }
6589 }
6590 } while (n); // repeat from scratch until all things left are marked valid
6591 }
6592
6593 }
6594
6595 return cpos.nofit;
6596}
6597
6598//=============================================================================
6599// phares 3/21/98
6600//
6601// Maintain a freelist of msecnode_t's to reduce memory allocs and frees.
6602//=============================================================================
6603
6604msecnode_t* headsecnode = NULL;
6605
6606//=============================================================================
6607//
6608// P_GetSecnode
6609//
6610// Retrieve a node from the freelist. The calling routine
6611// should make sure it sets all fields properly.
6612//
6613//=============================================================================
6614
6615msecnode_t* P_GetSecnode()
6616{
6617 msecnode_t* node;
6618
6619 if (headsecnode)
6620 {
6621 node = headsecnode;
6622 headsecnode = headsecnode->m_snext;
6623 }
6624 else
6625 {
6626 node = (msecnode_t*)M_Malloc(sizeof(*node));
6627 }
6628 return node;
6629}
6630
6631//=============================================================================
6632//
6633// P_PutSecnode
6634//
6635// Returns a node to the freelist.
6636//
6637//=============================================================================
6638
6639void P_PutSecnode(msecnode_t* node)
6640{
6641 node->m_snext = headsecnode;
6642 headsecnode = node;
6643}
6644
6645//=============================================================================
6646// phares 3/16/98
6647//
6648// P_AddSecnode
6649//
6650// Searches the current list to see if this sector is
6651// already there. If not, it adds a sector node at the head of the list of
6652// sectors this object appears in. This is called when creating a list of
6653// nodes that will get linked in later. Returns a pointer to the new node.
6654//
6655//=============================================================================
6656
6657msecnode_t* P_AddSecnode(sector_t* s, AActor* thing, msecnode_t* nextnode)
6658{
6659 msecnode_t* node;
6660
6661 if (s == 0)
6662 {
6663 I_FatalError("AddSecnode of 0 for %s\n", thing->_StaticType.TypeName.GetChars());
6664 }
6665
6666 node = nextnode;
6667 while (node)
6668 {
6669 if (node->m_sector == s) // Already have a node for this sector?
6670 {
6671 node->m_thing = thing; // Yes. Setting m_thing says 'keep it'.
6672 return nextnode;
6673 }
6674 node = node->m_tnext;
6675 }
6676
6677 // Couldn't find an existing node for this sector. Add one at the head
6678 // of the list.
6679
6680 node = P_GetSecnode();
6681
6682 // killough 4/4/98, 4/7/98: mark new nodes unvisited.
6683 node->visited = 0;
6684
6685 node->m_sector = s; // sector
6686 node->m_thing = thing; // mobj
6687 node->m_tprev = NULL; // prev node on Thing thread
6688 node->m_tnext = nextnode; // next node on Thing thread
6689 if (nextnode)
6690 nextnode->m_tprev = node; // set back link on Thing
6691
6692 // Add new node at head of sector thread starting at s->touching_thinglist
6693
6694 node->m_sprev = NULL; // prev node on sector thread
6695 node->m_snext = s->touching_thinglist; // next node on sector thread
6696 if (s->touching_thinglist)
6697 node->m_snext->m_sprev = node;
6698 s->touching_thinglist = node;
6699 return node;
6700}
6701
6702//=============================================================================
6703//
6704// P_DelSecnode
6705//
6706// Deletes a sector node from the list of
6707// sectors this object appears in. Returns a pointer to the next node
6708// on the linked list, or NULL.
6709//
6710//=============================================================================
6711
6712msecnode_t* P_DelSecnode(msecnode_t* node)
6713{
6714 msecnode_t* tp; // prev node on thing thread
6715 msecnode_t* tn; // next node on thing thread
6716 msecnode_t* sp; // prev node on sector thread
6717 msecnode_t* sn; // next node on sector thread
6718
6719 if (node)
6720 {
6721 // Unlink from the Thing thread. The Thing thread begins at
6722 // sector_list and not from AActor->touching_sectorlist.
6723
6724 tp = node->m_tprev;
6725 tn = node->m_tnext;
6726 if (tp)
6727 tp->m_tnext = tn;
6728 if (tn)
6729 tn->m_tprev = tp;
6730
6731 // Unlink from the sector thread. This thread begins at
6732 // sector_t->touching_thinglist.
6733
6734 sp = node->m_sprev;
6735 sn = node->m_snext;
6736 if (sp)
6737 sp->m_snext = sn;
6738 else
6739 node->m_sector->touching_thinglist = sn;
6740 if (sn)
6741 sn->m_sprev = sp;
6742
6743 // Return this node to the freelist
6744
6745 P_PutSecnode(node);
6746 return tn;
6747 }
6748 return NULL;
6749} // phares 3/13/98
6750
6751//=============================================================================
6752//
6753// P_DelSector_List
6754//
6755// Deletes the sector_list and NULLs it.
6756//
6757//=============================================================================
6758
6759void P_DelSector_List()
6760{
6761 if (sector_list != NULL)
6762 {
6763 P_DelSeclist(sector_list);
6764 sector_list = NULL;
6765 }
6766}
6767
6768//=============================================================================
6769//
6770// P_DelSeclist
6771//
6772// Delete an entire sector list
6773//
6774//=============================================================================
6775
6776void P_DelSeclist(msecnode_t* node)
6777{
6778 while (node)
6779 node = P_DelSecnode(node);
6780}
6781
6782//=============================================================================
6783// phares 3/14/98
6784//
6785// P_CreateSecNodeList
6786//
6787// Alters/creates the sector_list that shows what sectors the object resides in
6788//
6789//=============================================================================
6790
6791void P_CreateSecNodeList(AActor* thing, fixed_t x, fixed_t y)
6792{
6793 msecnode_t* node;
6794
6795 // First, clear out the existing m_thing fields. As each node is
6796 // added or verified as needed, m_thing will be set properly. When
6797 // finished, delete all nodes where m_thing is still NULL. These
6798 // represent the sectors the Thing has vacated.
6799
6800 node = sector_list;
6801 while (node)
6802 {
6803 node->m_thing = NULL;
6804 node = node->m_tnext;
6805 }
6806
6807 FBoundingBox box(thing->x, thing->y, thing->radius);
6808 FBlockLinesIterator it(box);
6809 line_t* ld;
6810
6811 while ((ld = it.Next()))
6812 {
6813 if (box.Right() <= ld->bbox[BOXLEFT] ||
6814 box.Left() >= ld->bbox[BOXRIGHT] ||
6815 box.Top() <= ld->bbox[BOXBOTTOM] ||
6816 box.Bottom() >= ld->bbox[BOXTOP])
6817 continue;
6818
6819 if (box.BoxOnLineSide(ld) != -1)
6820 continue;
6821
6822 // This line crosses through the object.
6823
6824 // Collect the sector(s) from the line and add to the
6825 // sector_list you're examining. If the Thing ends up being
6826 // allowed to move to this position, then the sector_list
6827 // will be attached to the Thing's AActor at touching_sectorlist.
6828
6829 sector_list = P_AddSecnode(ld->frontsector, thing, sector_list);
6830
6831 // Don't assume all lines are 2-sided, since some Things
6832 // like MT_TFOG are allowed regardless of whether their radius takes
6833 // them beyond an impassable linedef.
6834
6835 // killough 3/27/98, 4/4/98:
6836 // Use sidedefs instead of 2s flag to determine two-sidedness.
6837
6838 if (ld->backsector)
6839 sector_list = P_AddSecnode(ld->backsector, thing, sector_list);
6840 }
6841
6842 // Add the sector of the (x,y) point to sector_list.
6843
6844 sector_list = P_AddSecnode(thing->Sector, thing, sector_list);
6845
6846 // Now delete any nodes that won't be used. These are the ones where
6847 // m_thing is still NULL.
6848
6849 node = sector_list;
6850 while (node)
6851 {
6852 if (node->m_thing == NULL)
6853 {
6854 if (node == sector_list)
6855 sector_list = node->m_tnext;
6856 node = P_DelSecnode(node);
6857 }
6858 else
6859 {
6860 node = node->m_tnext;
6861 }
6862 }
6863}
6864
6865//==========================================================================
6866//
6867//
6868//
6869//==========================================================================
6870
6871void SpawnShootDecal(AActor* t1, const FTraceResults& trace)
6872{
6873 FDecalBase* decalbase = NULL;
6874
6875 // [BC] Servers don't need to spawn decals.
6876 if (NETWORK_GetState() == NETSTATE_SERVER)
6877 return;
6878
6879 if (t1->player != NULL && t1->player->ReadyWeapon != NULL)
6880 {
6881 decalbase = t1->player->ReadyWeapon->GetDefault()->DecalGenerator;
6882 }
6883 else
6884 {
6885 decalbase = t1->DecalGenerator;
6886 }
6887 if (decalbase != NULL)
6888 {
6889 DImpactDecal::StaticCreate(decalbase->GetDecal(),
6890 trace.X, trace.Y, trace.Z, trace.Line->sidedef[trace.Side], trace.ffloor);
6891 }
6892}
6893
6894//==========================================================================
6895//
6896//
6897//
6898//==========================================================================
6899
6900static void SpawnDeepSplash(AActor* t1, const FTraceResults& trace, AActor* puff,
6901 fixed_t vx, fixed_t vy, fixed_t vz, fixed_t shootz, bool ffloor)
6902{
6903 const secplane_t* plane;
6904 if (ffloor && trace.Crossed3DWater)
6905 plane = trace.Crossed3DWater->top.plane;
6906 else if (trace.CrossedWater && trace.CrossedWater->heightsec)
6907 plane = &trace.CrossedWater->heightsec->floorplane;
6908 else return;
6909
6910 fixed_t num, den, hitdist;
6911
6912 // [BB] Gross hack to prevent the crashes in thunpeak.zip most likely caused
6913 // by the rewrite of the interpolation code in GZDoom revision 117.
6914 if (plane < reinterpret_cast<const secplane_t*>(static_cast<size_t>(0x0001000)))
6915 {
6916 Printf("Warning, invalid pointer in SpawnDeepSplash!\n");
6917 return;
6918 }
6919 den = TMulScale16(plane->a, vx, plane->b, vy, plane->c, vz);
6920 if (den != 0)
6921 {
6922 num = TMulScale16(plane->a, t1->x, plane->b, t1->y, plane->c, shootz) + plane->d;
6923 hitdist = FixedDiv(-num, den);
6924
6925 if (hitdist >= 0 && hitdist <= trace.Distance)
6926 {
6927 fixed_t hitx = t1->x + FixedMul(vx, hitdist);
6928 fixed_t hity = t1->y + FixedMul(vy, hitdist);
6929 fixed_t hitz = shootz + FixedMul(vz, hitdist);
6930
6931 P_HitWater(puff != NULL ? puff : t1, P_PointInSector(hitx, hity), hitx, hity, hitz);
6932 }
6933 }
6934}
6935
6936//=============================================================================
6937//
6938// P_ActivateThingSpecial
6939//
6940// Handles the code for things activated by death, USESPECIAL or BUMPSPECIAL
6941//
6942//=============================================================================
6943
6944bool P_ActivateThingSpecial(AActor* thing, AActor* trigger, bool death)
6945{
6946 bool res = false;
6947
6948 // Target switching mechanism
6949 if (thing->activationtype & THINGSPEC_ThingTargets) thing->target = trigger;
6950 if (thing->activationtype & THINGSPEC_TriggerTargets) trigger->target = thing;
6951
6952 // State change mechanism. The thing needs to be not dead and to have at least one of the relevant flags
6953 if (!death && (thing->activationtype & (THINGSPEC_Activate | THINGSPEC_Deactivate | THINGSPEC_Switch)))
6954 {
6955 // If a switchable thing does not know whether it should be activated
6956 // or deactivated, the default is to activate it.
6957 if ((thing->activationtype & THINGSPEC_Switch)
6958 && !(thing->activationtype & (THINGSPEC_Activate | THINGSPEC_Deactivate)))
6959 {
6960 thing->activationtype |= THINGSPEC_Activate;
6961 }
6962 // Can it be activated?
6963 if (thing->activationtype & THINGSPEC_Activate)
6964 {
6965 thing->activationtype &= ~THINGSPEC_Activate; // Clear flag
6966 if (thing->activationtype & THINGSPEC_Switch) // Set other flag if switching
6967 thing->activationtype |= THINGSPEC_Deactivate;
6968 thing->Activate(trigger);
6969 res = true;
6970 }
6971 // If not, can it be deactivated?
6972 else if (thing->activationtype & THINGSPEC_Deactivate)
6973 {
6974 thing->activationtype &= ~THINGSPEC_Deactivate; // Clear flag
6975 if (thing->activationtype & THINGSPEC_Switch) // Set other flag if switching
6976 thing->activationtype |= THINGSPEC_Activate;
6977 thing->Deactivate(trigger);
6978 res = true;
6979 }
6980 }
6981
6982 // Run the special, if any
6983 if (thing->special)
6984 {
6985 res = !!P_ExecuteSpecial(thing->special, NULL,
6986 // TriggerActs overrides the level flag, which only concerns thing activated by death
6987 (((death && level.flags & LEVEL_ACTOWNSPECIAL && !(thing->activationtype & THINGSPEC_TriggerActs))
6988 || (thing->activationtype & THINGSPEC_ThingActs)) // Who triggers?
6989 ? thing : trigger),
6990 false, thing->args[0], thing->args[1], thing->args[2], thing->args[3], thing->args[4]);
6991
6992 // Clears the special if it was run on thing's death or if flag is set.
6993 if (death || (thing->activationtype & THINGSPEC_ClearSpecial && res)) thing->special = 0;
6994 }
6995
6996 // Returns the result
6997 return res;
6998}
6999