fixed ticket [f9c074e766ade1bd] (pickups should not be infinitely tall) (i hope ;-)
[k8vavoom.git] / source / psim / p_entity_worldtrymove.cpp
blob21590e9f180210940ca2890b5c2d6396ba2a99e2
1 //**************************************************************************
2 //**
3 //** ## ## ## ## ## #### #### ### ###
4 //** ## ## ## ## ## ## ## ## ## ## #### ####
5 //** ## ## ## ## ## ## ## ## ## ## ## ## ## ##
6 //** ## ## ######## ## ## ## ## ## ## ## ### ##
7 //** ### ## ## ### ## ## ## ## ## ##
8 //** # ## ## # #### #### ## ##
9 //**
10 //** Copyright (C) 1999-2006 Jānis Legzdiņš
11 //** Copyright (C) 2018-2023 Ketmar Dark
12 //**
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.
16 //**
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.
21 //**
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/>.
24 //**
25 //**************************************************************************
26 //**
27 //** VEntity collision, physics and related methods.
28 //**
29 //**************************************************************************
30 #include "../gamedefs.h"
31 #include "p_entity.h"
32 #include "p_levelinfo.h"
33 #include "p_player.h"
35 //#define VV_DBG_VERBOSE_TRYMOVE
38 #ifdef VV_DBG_VERBOSE_TRYMOVE
39 # define TMDbgF(...) GCon->Logf(NAME_Debug, __VA_ARGS__)
40 #else
41 # define TMDbgF(...) do {} while (0)
42 #endif
46 //==========================================================================
48 // VEntity::PushLine
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) {
57 --NumSpecHitTemp;
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 //**************************************************************************
70 // MOVEMENT CLIPPING
72 //**************************************************************************
74 //==========================================================================
76 // VEntity::TryMove
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) {
83 bool check;
84 TVec oldorg(0, 0, 0);
85 line_t *ld;
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);
104 if (!IsPlayer()) {
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;
113 if (!check) {
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));
117 if (!O) {
118 // can't step up or doesn't fit
119 PushLine(tmtrace, skipEffects);
120 return false;
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);
132 return false;
135 if (!(EntityFlags&EF_PassMobj) || O->IsNoPassOver()) {
136 // can't go over
137 return false;
141 // check passed
143 if (EntityFlags&EF_ColideWithWorld) {
144 if (tmtrace.CeilingZ-tmtrace.FloorZ < Height) {
145 // doesn't fit
146 PushLine(tmtrace, skipEffects);
147 TMDbgF("%s: DOESN'T FIT(0)!", GetClass()->GetName());
148 if (!tmtrace.BlockingLine && tmtrace.FloorLine) tmtrace.BlockingLine = tmtrace.FloorLine;
149 return false;
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;
159 return false;
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);
176 return false;
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);
189 return false;
193 if (!(EntityFlags&EF_IgnoreFloorStep)) {
194 if (tmtrace.FloorZ-Origin.z > MaxStepHeight) {
195 // too big a step up
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());
204 return false;
206 } else {
207 PushLine(tmtrace, skipEffects);
208 TMDbgF("%s: FLOORSTEP(1)!", GetClass()->GetName());
209 return false;
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);
216 return false;
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());
229 return false;
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());
248 return false;
250 } else {
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());
256 return false;
258 // check to make sure there's nothing in the way
259 if (!CheckBlockingMobj(tmtrace.BlockingMobj)) {
260 return false;
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());
270 return false;
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
287 UnlinkFromWorld();
289 oldorg = Origin;
290 Origin = newPos;
292 LinkToWorld();
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();
302 } else {
303 FloorClip = 0.0f;
306 if (!noPickups && !isClient) {
307 // if any special lines were hit, do the effect
308 if (EntityFlags&EF_ColideWithWorld) {
310 if (!IsPlayer()) {
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) {
326 if (ld->special) {
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);
342 return true;
347 //==========================================================================
349 // Script natives
351 //==========================================================================
352 IMPLEMENT_FUNCTION(VEntity, TryMove) {
353 tmtrace_t tmtrace;
354 TVec Pos;
355 bool AllowDropOff;
356 VOptParamBool checkOnly(false);
357 vobjGetParamSelf(Pos, AllowDropOff, checkOnly);
358 RET_BOOL(Self->TryMove(tmtrace, Pos, AllowDropOff, checkOnly));
361 IMPLEMENT_FUNCTION(VEntity, TryMoveEx) {
362 tmtrace_t tmp;
363 tmtrace_t *tmtrace = nullptr;
364 TVec Pos;
365 bool AllowDropOff;
366 VOptParamBool checkOnly(false);
367 vobjGetParamSelf(tmtrace, Pos, AllowDropOff, checkOnly);
368 if (!tmtrace) tmtrace = &tmp;
369 RET_BOOL(Self->TryMove(*tmtrace, Pos, AllowDropOff, checkOnly));