1 //**************************************************************************
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
13 //** This program is free software: you can redistribute it and/or modify
14 //** it under the terms of the GNU General Public License as published by
15 //** the Free Software Foundation, version 3 of the License ONLY.
17 //** This program is distributed in the hope that it will be useful,
18 //** but WITHOUT ANY WARRANTY; without even the implied warranty of
19 //** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 //** GNU General Public License for more details.
22 //** You should have received a copy of the GNU General Public License
23 //** along with this program. If not, see <http://www.gnu.org/licenses/>.
25 //**************************************************************************
27 //** VEntity collision, physics and related methods.
29 //**************************************************************************
30 #include "../gamedefs.h"
32 #include "p_levelinfo.h"
35 //#define VV_DBG_VERBOSE_TRYMOVE
38 #ifdef VV_DBG_VERBOSE_TRYMOVE
39 # define TMDbgF(...) GCon->Logf(NAME_Debug, __VA_ARGS__)
41 # define TMDbgF(...) do {} while (0)
46 //==========================================================================
50 //==========================================================================
51 void VEntity::PushLine (const tmtrace_t
&tmtrace
, bool checkOnly
) {
52 if (checkOnly
|| GGameInfo
->NetMode
== NM_Client
) return;
53 if (EntityFlags
&EF_ColideWithWorld
) {
54 if (EntityFlags
&EF_Blasted
) eventBlastedHitLine();
55 int NumSpecHitTemp
= tmtrace
.SpecHit
.length();
56 while (NumSpecHitTemp
> 0) {
58 // see if the line was crossed
59 line_t
*ld
= tmtrace
.SpecHit
[NumSpecHitTemp
];
60 int side
= ld
->PointOnSide(Origin
);
61 eventCheckForPushSpecial(ld
, side
);
68 //**************************************************************************
72 //**************************************************************************
74 //==========================================================================
78 // attempt to move to a new position, crossing special lines.
79 // returns `false` if move is blocked.
81 //==========================================================================
82 bool VEntity::TryMove (tmtrace_t
&tmtrace
, TVec newPos
, bool AllowDropOff
, bool checkOnly
, bool noPickups
) {
86 sector_t
*OldSec
= Sector
;
88 if (IsGoingToDie() || !Sector
) {
89 memset((void *)&tmtrace
, 0, sizeof(tmtrace
));
90 return false; // just in case, dead object is immovable
93 const bool isClient
= (GGameInfo
->NetMode
== NM_Client
);
95 bool skipEffects
= (checkOnly
|| noPickups
);
97 TMDbgF("%s: *** trying to move from (%g,%g,%g) to (%g,%g,%g); Height=%g; Radius=%g", GetClass()->GetName(), Origin
.x
, Origin
.y
, Origin
.z
, newPos
.x
, newPos
.y
, newPos
.z
, Height
, GetMoveRadius());
98 check
= CheckRelPosition(tmtrace
, newPos
, skipEffects
);
99 tmtrace
.TraceFlags
&= ~tmtrace_t::TF_FloatOk
;
100 //if (IsPlayer()) GCon->Logf(NAME_Debug, "trying to move from (%g,%g,%g) to (%g,%g,%g); check=%d", Origin.x, Origin.y, Origin.z, newPos.x, newPos.y, newPos.z, (int)check);
101 TMDbgF("%s: trying to move from (%g,%g,%g) to (%g,%g,%g); check=%d; tmt.FloorZ=%g; tmt.DropOffZ=%g; tmt.CeilingZ=%g", GetClass()->GetName(), Origin
.x
, Origin
.y
, Origin
.z
, newPos
.x
, newPos
.y
, newPos
.z
, (int)check
, tmtrace
.FloorZ
, tmtrace
.DropOffZ
, tmtrace
.CeilingZ
);
105 GCon->Logf(NAME_Debug, "trying to move from (%g,%g,%g) to (%g,%g,%g); check=%d; npp=%d",
106 Origin.x, Origin.y, Origin.z, newPos.x, newPos.y, newPos.z,
107 (int)check, (int)noPickups);
111 if (isClient
) skipEffects
= true;
114 // cannot fit into destination point
115 VEntity
*O
= tmtrace
.BlockingMobj
;
116 TMDbgF("%s: HIT: entity=%s, line #%d", GetClass()->GetName(), (O
? O
->GetClass()->GetName() : "<none>"), (tmtrace
.BlockingLine
? (int)(ptrdiff_t)(tmtrace
.BlockingLine
-&XLevel
->Lines
[0]) : -1));
118 // can't step up or doesn't fit
119 PushLine(tmtrace
, skipEffects
);
123 const float ohgt
= GetBlockingHeightFor(O
);
124 if (!(EntityFlags
&EF_IsPlayer
) ||
125 (O
->EntityFlags
&EF_IsPlayer
) ||
126 O
->Origin
.z
+ohgt
-Origin
.z
> MaxStepHeight
||
127 O
->CeilingZ
-(O
->Origin
.z
+ohgt
) < Height
||
128 tmtrace
.CeilingZ
-(O
->Origin
.z
+ohgt
) < Height
)
130 // can't step up or doesn't fit
131 PushLine(tmtrace
, skipEffects
);
135 if (!(EntityFlags
&EF_PassMobj
) || O
->IsNoPassOver()) {
143 if (EntityFlags
&EF_ColideWithWorld
) {
144 if (tmtrace
.CeilingZ
-tmtrace
.FloorZ
< Height
) {
146 PushLine(tmtrace
, skipEffects
);
147 TMDbgF("%s: DOESN'T FIT(0)!", GetClass()->GetName());
148 if (!tmtrace
.BlockingLine
&& tmtrace
.FloorLine
) tmtrace
.BlockingLine
= tmtrace
.FloorLine
;
152 tmtrace
.TraceFlags
|= tmtrace_t::TF_FloatOk
;
154 if (tmtrace
.CeilingZ
-Origin
.z
< Height
&& !(EntityFlags
&EF_Fly
) && !(EntityFlags
&EF_IgnoreCeilingStep
)) {
155 // mobj must lower itself to fit
156 PushLine(tmtrace
, skipEffects
);
157 TMDbgF("%s: DOESN'T FIT(1)! ZBox=(%g,%g); ceilz=%g", GetClass()->GetName(), Origin
.z
, Origin
.z
+Height
, tmtrace
.CeilingZ
);
158 if (!tmtrace
.BlockingLine
&& tmtrace
.CeilingLine
) tmtrace
.BlockingLine
= tmtrace
.CeilingLine
;
162 if (EntityFlags
&EF_Fly
) {
163 // when flying, slide up or down blocking lines until the actor is not blocked
164 if (Origin
.z
+Height
> tmtrace
.CeilingZ
) {
165 // if sliding down, make sure we don't have another object below
166 if (!checkOnly
&& !isClient
) {
167 if (/*(!tmtrace.BlockingMobj || !tmtrace.BlockingMobj->CheckOnmobj() ||
168 (tmtrace.BlockingMobj->CheckOnmobj() && tmtrace.BlockingMobj->CheckOnmobj() != this)) &&
169 (!CheckOnmobj() || (CheckOnmobj() && CheckOnmobj() != tmtrace.BlockingMobj))*/
170 CheckBlockingMobj(tmtrace
.BlockingMobj
))
172 Velocity
.z
= -8.0f
*35.0f
;
175 PushLine(tmtrace
, skipEffects
);
177 } else if (Origin
.z
< tmtrace
.FloorZ
&& tmtrace
.FloorZ
-tmtrace
.DropOffZ
> MaxStepHeight
) {
178 // check to make sure there's nothing in the way for the step up
179 if (!checkOnly
&& !isClient
) {
180 if (/*(!tmtrace.BlockingMobj || !tmtrace.BlockingMobj->CheckOnmobj() ||
181 (tmtrace.BlockingMobj->CheckOnmobj() && tmtrace.BlockingMobj->CheckOnmobj() != this)) &&
182 (!CheckOnmobj() || (CheckOnmobj() && CheckOnmobj() != tmtrace.BlockingMobj))*/
183 CheckBlockingMobj(tmtrace
.BlockingMobj
))
185 Velocity
.z
= 8.0f
*35.0f
;
188 PushLine(tmtrace
, skipEffects
);
193 if (!(EntityFlags
&EF_IgnoreFloorStep
)) {
194 if (tmtrace
.FloorZ
-Origin
.z
> MaxStepHeight
) {
196 if ((EntityFlags
&EF_CanJump
) && Health
> 0) {
197 // check to make sure there's nothing in the way for the step up
198 if (!Velocity
.z
|| tmtrace
.FloorZ
-Origin
.z
> 48.0f
||
199 (tmtrace
.BlockingMobj
&& tmtrace
.BlockingMobj
->CheckOnmobj()) ||
200 TestMobjZ(TVec(newPos
.x
, newPos
.y
, tmtrace
.FloorZ
)))
202 PushLine(tmtrace
, skipEffects
);
203 TMDbgF("%s: FLOORSTEP(0)!", GetClass()->GetName());
207 PushLine(tmtrace
, skipEffects
);
208 TMDbgF("%s: FLOORSTEP(1)!", GetClass()->GetName());
213 if ((EntityFlags
&EF_Missile
) && !(EntityFlags
&EF_StepMissile
) && tmtrace
.FloorZ
> Origin
.z
) {
214 PushLine(tmtrace
, skipEffects
);
215 TMDbgF("%s: FLOORX(0)! z=%g; fl=%g; spechits=%d; Origin.z=%g to %g; FloorZ=%g", GetClass()->GetName(), Origin
.z
, tmtrace
.FloorZ
, tmtrace
.SpecHit
.length(), Origin
.z
, Origin
.z
+Height
, tmtrace
.FloorZ
);
219 if (Origin
.z
< tmtrace
.FloorZ
) {
220 if (EntityFlags
&EF_StepMissile
) {
221 Origin
.z
= tmtrace
.FloorZ
;
222 // if moving down, cancel vertical component of velocity
223 if (Velocity
.z
< 0) Velocity
.z
= 0.0f
;
225 // check to make sure there's nothing in the way for the step up
226 if (TestMobjZ(TVec(newPos
.x
, newPos
.y
, tmtrace
.FloorZ
))) {
227 PushLine(tmtrace
, skipEffects
);
228 TMDbgF("%s: OBJZ(0)!", GetClass()->GetName());
234 // killough 3/15/98: Allow certain objects to drop off
235 if ((!AllowDropOff
&& !(EntityFlags
&EF_DropOff
) &&
236 !(EntityFlags
&EF_Float
) && !(EntityFlags
&EF_Missile
)) ||
237 (EntityFlags
&EF_NoDropOff
))
239 if (!(EntityFlags
&EF_AvoidingDropoff
)) {
240 float floorz
= tmtrace
.FloorZ
;
241 // [RH] If the thing is standing on something, use its current z as the floorz.
242 // this is so that it does not walk off of things onto a drop off.
243 if (EntityFlags
&EF_OnMobj
) floorz
= max2(Origin
.z
, tmtrace
.FloorZ
);
245 if ((floorz
-tmtrace
.DropOffZ
> MaxDropoffHeight
) && !(EntityFlags
&EF_Blasted
)) {
246 // can't move over a dropoff unless it's been blasted
247 TMDbgF("%s: DROPOFF(0)!", GetClass()->GetName());
251 // special logic to move a monster off a dropoff
252 // this intentionally does not check for standing on things
253 // k8: ...and due to this, the monster can stuck into another monster. what a great idea!
254 if (FloorZ
-tmtrace
.FloorZ
> MaxDropoffHeight
|| DropOffZ
-tmtrace
.DropOffZ
> MaxDropoffHeight
) {
255 TMDbgF("%s: DROPOFF(1)!", GetClass()->GetName());
258 // check to make sure there's nothing in the way
259 if (!CheckBlockingMobj(tmtrace
.BlockingMobj
)) {
265 if ((EntityFlags
&EF_CantLeaveFloorpic
) &&
266 (tmtrace
.EFloor
.splane
->pic
!= EFloor
.splane
->pic
|| tmtrace
.FloorZ
!= Origin
.z
))
268 // must stay within a sector of a certain floor type
269 TMDbgF("%s: SECTORSTAY(0)!", GetClass()->GetName());
272 } // (EntityFlags&EF_ColideWithWorld)
274 bool OldAboveFakeFloor
= false;
275 bool OldAboveFakeCeiling
= false;
276 if (Sector
->heightsec
) {
277 float EyeZ
= (Player
? Player
->ViewOrg
.z
: Origin
.z
+Height
*0.5f
);
278 OldAboveFakeFloor
= (EyeZ
> Sector
->heightsec
->floor
.GetPointZClamped(Origin
));
279 OldAboveFakeCeiling
= (EyeZ
> Sector
->heightsec
->ceiling
.GetPointZClamped(Origin
));
282 if (checkOnly
) return true;
284 TMDbgF("%s: ***OK-TO-MOVE", GetClass()->GetName());
286 // the move is ok, so link the thing into its new position
294 FloorZ
= tmtrace
.FloorZ
;
295 CeilingZ
= tmtrace
.CeilingZ
;
296 DropOffZ
= tmtrace
.DropOffZ
;
297 CopyTraceFloor(&tmtrace
, false);
298 CopyTraceCeiling(&tmtrace
, false);
300 if (EntityFlags
&EF_FloorClip
) {
301 eventHandleFloorclip();
306 if (!noPickups
&& !isClient
) {
307 // if any special lines were hit, do the effect
308 if (EntityFlags
&EF_ColideWithWorld
) {
311 GCon->Logf(NAME_Debug, "trying to move from (%g,%g,%g) to (%g,%g,%g); check=%d; npp=%d; hl=%d",
312 Origin.x, Origin.y, Origin.z, newPos.x, newPos.y, newPos.z,
313 (int)check, (int)noPickups, tmtrace.SpecHit.length());
316 while (tmtrace
.SpecHit
.length() > 0) {
317 // see if the line was crossed
318 ld
= tmtrace
.SpecHit
[tmtrace
.SpecHit
.length()-1];
319 tmtrace
.SpecHit
.setLength
<false>(tmtrace
.SpecHit
.length()-1);
320 // some moron once placed the entity *EXACTLY* on the fuckin' linedef! what a brilliant idea!
321 // this will *NEVER* be fixed, it is a genuine map bug (it's impossible to fix it with our freestep engine anyway)
322 // let's try use "front inclusive" here; it won't solve all cases, but *may* solve the one above
323 const int oldside
= ld
->PointOnSideFri(oldorg
);
324 const int side
= ld
->PointOnSideFri(Origin
);
325 if (side
!= oldside
) {
327 //if (!IsPlayer()) GCon->Logf(NAME_Debug, " CROSS: special=%d; line=%d", ld->special, (int)(ptrdiff_t)(ld-&XLevel->Lines[0]));
328 eventCrossSpecialLine(ld
, oldside
);
334 // do additional check here to avoid calling progs
335 if ((OldSec
->heightsec
&& Sector
->heightsec
&& Sector
->ActionList
) ||
336 (OldSec
!= Sector
&& (OldSec
->ActionList
|| Sector
->ActionList
)))
338 eventCheckForSectorActions(OldSec
, OldAboveFakeFloor
, OldAboveFakeCeiling
);
347 //==========================================================================
351 //==========================================================================
352 IMPLEMENT_FUNCTION(VEntity
, TryMove
) {
356 VOptParamBool
checkOnly(false);
357 vobjGetParamSelf(Pos
, AllowDropOff
, checkOnly
);
358 RET_BOOL(Self
->TryMove(tmtrace
, Pos
, AllowDropOff
, checkOnly
));
361 IMPLEMENT_FUNCTION(VEntity
, TryMoveEx
) {
363 tmtrace_t
*tmtrace
= nullptr;
366 VOptParamBool
checkOnly(false);
367 vobjGetParamSelf(tmtrace
, Pos
, AllowDropOff
, checkOnly
);
368 if (!tmtrace
) tmtrace
= &tmp
;
369 RET_BOOL(Self
->TryMove(*tmtrace
, Pos
, AllowDropOff
, checkOnly
));