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_REL_LINE_FC
37 static VCvarB
mv_new_slope_code("mv_new_slope_code", true, "Use experimental slope walking code?", CVAR_Archive
|CVAR_NoShadow
);
40 //==========================================================================
42 // VEntity::CheckRelPosition
44 // This is purely informative, nothing is modified
45 // (except things picked up).
48 // a mobj_t (can be valid or invalid)
49 // a position to be checked
50 // (doesn't need to be related to the mobj_t->x,y)
53 // special things are touched if MF_PICKUP
54 // early out on solid lines?
61 // the lowest point contacted
62 // (monsters won't move to a dropoff)
65 // VEntity *BlockingMobj = pointer to thing that blocked position (nullptr if not
66 // blocked, or blocked by a line).
68 //==========================================================================
69 bool VEntity::CheckRelPosition (tmtrace_t
&tmtrace
, TVec Pos
,
70 bool noPickups
, bool ignoreMonsters
, bool ignorePlayers
)
72 //if (IsPlayer()) GCon->Logf(NAME_Debug, "*** CheckRelPosition: pos=(%g,%g,%g)", Pos.x, Pos.y, Pos.z);
73 memset((void *)&tmtrace
, 0, sizeof(tmtrace
));
77 const float rad
= GetMoveRadius();
79 tmtrace
.BBox
[BOX2D_TOP
] = Pos
.y
+rad
;
80 tmtrace
.BBox
[BOX2D_BOTTOM
] = Pos
.y
-rad
;
81 tmtrace
.BBox
[BOX2D_RIGHT
] = Pos
.x
+rad
;
82 tmtrace
.BBox
[BOX2D_LEFT
] = Pos
.x
-rad
;
84 subsector_t
*newsubsec
= XLevel
->PointInSubsector_Buggy(Pos
);
85 //tmtrace.CeilingLine = nullptr;
87 tmtrace
.setupGap(XLevel
, newsubsec
->sector
, Height
);
89 XLevel
->IncrementValidCount();
90 tmtrace
.SpecHit
.resetNoDtor(); // was `Clear()`
92 //GCon->Logf(NAME_Debug, "xxx: %s(%u): CheckRelPosition (vc=%d)", GetClass()->GetName(), GetUniqueId(), validcount);
94 tmtrace
.BlockingMobj
= nullptr;
95 tmtrace
.StepThing
= nullptr;
96 VEntity
*thingblocker
= nullptr;
98 // check things first, possibly picking things up.
99 if (EntityFlags
&EF_ColideWithThings
) {
100 // the bounding box is extended by MAXRADIUS
101 // because mobj_ts are grouped into mapblocks
102 // based on their origin point, and can overlap
103 // into adjacent blocks by up to MAXRADIUS units.
104 DeclareMakeBlockMapCoordsBBox2DMaxRadius(tmtrace
.BBox
, xl
, yl
, xh
, yh
);
105 for (int bx
= xl
; bx
<= xh
; ++bx
) {
106 for (int by
= yl
; by
<= yh
; ++by
) {
107 for (auto &&it
: XLevel
->allBlockThings(bx
, by
)) {
108 VEntity
*ent
= it
.entity();
109 if (ignoreMonsters
|| ignorePlayers
) {
110 if (ignorePlayers
&& ent
->IsPlayer()) continue;
111 if (ignoreMonsters
&& (ent
->IsAnyMissile() || ent
->IsMonster())) continue;
113 if (!CheckRelThing(tmtrace
, ent
, noPickups
)) {
114 // continue checking for other things in to see if we hit something
115 if (!tmtrace
.BlockingMobj
|| tmtrace
.BlockingMobj
->IsNoPassOver()) {
116 // slammed into something
118 } else if (!tmtrace
.BlockingMobj
->Player
&&
119 !(EntityFlags
&(VEntity::EF_Float
|VEntity::EF_Missile
)) &&
120 tmtrace
.BlockingMobj
->Origin
.z
+tmtrace
.BlockingMobj
->Height
-tmtrace
.End
.z
<= MaxStepHeight
)
122 if (!thingblocker
|| tmtrace
.BlockingMobj
->Origin
.z
> thingblocker
->Origin
.z
) thingblocker
= tmtrace
.BlockingMobj
;
123 tmtrace
.BlockingMobj
= nullptr;
124 } else if (Player
&& tmtrace
.End
.z
+Height
-tmtrace
.BlockingMobj
->Origin
.z
<= MaxStepHeight
) {
126 // something to step up on, set it as the blocker so that we don't step up
129 // nothing is blocking, but this object potentially could
130 // if there is something else to step on
131 tmtrace
.BlockingMobj
= nullptr;
142 float thingdropoffz
= tmtrace
.FloorZ
;
143 tmtrace
.FloorZ
= tmtrace
.DropOffZ
;
144 tmtrace
.BlockingMobj
= nullptr;
146 //bool gotNewValid = false;
149 if (EntityFlags
&EF_ColideWithWorld
) {
150 #ifdef VV_DBG_VERBOSE_REL_LINE_FC
151 if (IsPlayer()) GCon
->Logf(NAME_Debug
, "xxx: %s(%u): checking lines...; FloorZ=%g", GetClass()->GetName(), GetUniqueId(), tmtrace
.FloorZ
);
153 //XLevel->IncrementValidCount();
154 //gotNewValid = true;
156 DeclareMakeBlockMapCoordsBBox2D(tmtrace
.BBox
, xl
, yl
, xh
, yh
);
158 line_t
*fuckhit
= nullptr;
159 float lastFrac
= 1e7f
;
160 for (int bx
= xl
; bx
<= xh
; ++bx
) {
161 for (int by
= yl
; by
<= yh
; ++by
) {
162 for (auto &&it
: XLevel
->allBlockLines(bx
, by
)) {
163 line_t
*ld
= it
.line();
164 //good &= CheckRelLine(tmtrace, ld);
165 // if we don't want pickups, don't activate specials
166 if (!CheckRelLine(tmtrace
, ld
, noPickups
)) {
167 #ifdef VV_DBG_VERBOSE_REL_LINE_FC
168 if (IsPlayer()) GCon
->Logf(NAME_Debug
, "%s: BLOCKED by line #%d (FloorZ=%g)", GetClass()->GetName(), (int)(ptrdiff_t)(ld
-&XLevel
->Lines
[0]), tmtrace
.FloorZ
);
171 // find the fractional intercept point along the trace line
172 const float den
= ld
->normal
.dot(tmtrace
.End
-Pos
);
177 const float num
= ld
->dist
-Pos
.dot(ld
->normal
);
178 const float frac
= fabsf(num
/den
);
179 if (frac
< lastFrac
) {
185 #ifdef VV_DBG_VERBOSE_REL_LINE_FC
187 if (IsPlayer()) GCon
->Logf(NAME_Debug
, "%s: OK line #%d (FloorZ=%g)", GetClass()->GetName(), (int)(ptrdiff_t)(ld
-&XLevel
->Lines
[0]), tmtrace
.FloorZ
);
194 polyobj_t
*inpobj
= nullptr;
195 // check if we can stand inside some polyobject
196 if (XLevel
->Has3DPolyObjects()) {
197 //if (!gotNewValid) XLevel->IncrementValidCount();
198 //GCon->Logf(NAME_Debug, "xxx: %s(%u): checking pobjs (%d)... (vc=%d)", GetClass()->GetName(), GetUniqueId(), XLevel->NumPolyObjs, validcount);
199 const float z1
= tmtrace
.End
.z
+max2(0.0f
, Height
);
200 for (int bx
= xl
; bx
<= xh
; ++bx
) {
201 for (int by
= yl
; by
<= yh
; ++by
) {
203 for (VBlockPObjIterator
It(XLevel
, bx
, by
, &po
); It
.GetNext(); ) {
204 //GCon->Logf(NAME_Debug, "000: %s(%u): checking pobj #%d... (%g:%g) (%g:%g)", GetClass()->GetName(), GetUniqueId(), po->tag, po->pofloor.minz, po->poceiling.maxz, tmtrace.End.z, z1);
205 if (po
->pofloor
.minz
>= po
->poceiling
.maxz
) continue;
206 if (!Are2DBBoxesOverlap(po
->bbox2d
, tmtrace
.BBox
)) continue;
207 //GCon->Logf(NAME_Debug, "001: %s(%u): checking pobj #%d...", GetClass()->GetName(), GetUniqueId(), po->tag);
208 if (!XLevel
->IsBBox2DTouchingSector(po
->GetSector(), tmtrace
.BBox
)) continue;
209 //GCon->Logf(NAME_Debug, "002: %s(%u): HIT pobj #%d (fz=%g; cz=%g); z0=%g; z1=%g", GetClass()->GetName(), GetUniqueId(), po->tag, tmtrace.FloorZ, tmtrace.CeilingZ, tmtrace.End.z, z1);
210 const float oldFZ
= tmtrace
.FloorZ
;
211 if (Copy3DPObjFloorCeiling(tmtrace
, po
, tmtrace
.End
.z
, z1
)) {
212 if (oldFZ
!= tmtrace
.FloorZ
) tmtrace
.DropOffZ
= tmtrace
.FloorZ
;
214 //GCon->Logf(NAME_Debug, "003: %s(%u): pobj #%d (fz=%g; cz=%g); z0=%g; z1=%g; pz=(%g : %g)", GetClass()->GetName(), GetUniqueId(), po->tag, tmtrace.FloorZ, tmtrace.CeilingZ, tmtrace.End.z, z1, po->pofloor.minz, po->poceiling.maxz);
222 if (!tmtrace
.BlockingLine
) tmtrace
.BlockingLine
= fuckhit
;
223 if (!tmtrace
.AnyBlockingLine
) tmtrace
.AnyBlockingLine
= fuckhit
;
229 tmtrace
.BlockingMobj
= nullptr;
230 tmtrace
.BlockingLine
= nullptr;
231 tmtrace
.AnyBlockingLine
= nullptr;
232 tmtrace
.PolyObj
= inpobj
;
236 if (tmtrace
.CeilingZ
-tmtrace
.FloorZ
< Height
) {
238 if (!tmtrace
.CeilingLine
&& !tmtrace
.FloorLine
&& !tmtrace
.BlockingLine
) {
239 // this can happen when you're crouching, for example
240 // `fuckhit` is not set in that case too
241 GCon
->Logf(NAME_Warning
, "CheckRelPosition for `%s` is height-blocked, but no block line determined!", GetClass()->GetName());
242 tmtrace
.BlockingLine
= fuckhit
;
244 if (!tmtrace
.AnyBlockingLine
) tmtrace
.AnyBlockingLine
= fuckhit
;
250 if (tmtrace
.StepThing
) tmtrace
.DropOffZ
= thingdropoffz
;
252 tmtrace
.BlockingMobj
= thingblocker
;
253 if (tmtrace
.BlockingMobj
) return false;
255 #ifdef VV_DBG_VERBOSE_REL_LINE_FC
256 if (IsPlayer()) GCon
->Logf(NAME_Debug
, "xxx: %s(%u): VALID; FloorZ=%g", GetClass()->GetName(), GetUniqueId(), tmtrace
.FloorZ
);
263 //==========================================================================
265 // VEntity::CheckRelThing
267 // returns `false` when blocked
269 //==========================================================================
270 bool VEntity::CheckRelThing (tmtrace_t
&tmtrace
, VEntity
*Other
, bool noPickups
) {
271 // don't clip against self
272 if (Other
== this) return true;
273 //if (OwnerSUId && Other && Other->ServerUId == OwnerSUId) return true;
276 if (!(Other
->EntityFlags
&EF_ColideWithThings
)) return true;
278 const float rad
= GetMoveRadius();
279 const float otherrad
= Other
->GetMoveRadius();
281 const float blockdist
= otherrad
+rad
;
283 if (fabsf(Other
->Origin
.x
-tmtrace
.End
.x
) >= blockdist
||
284 fabsf(Other
->Origin
.y
-tmtrace
.End
.y
) >= blockdist
)
290 // can't walk on corpses (but can touch them)
291 const bool isOtherCorpse
= !!(Other
->EntityFlags
&EF_Corpse
);
293 if (!isOtherCorpse
) {
294 //tmtrace.BlockingMobj = Other;
297 if (!Other
->IsNoPassOver() &&
298 !(EntityFlags
&(EF_Float
|EF_Missile
|EF_NoGravity
)) &&
299 (Other
->EntityFlags
&(EF_Solid
|EF_ActLikeBridge
)) == (EF_Solid
|EF_ActLikeBridge
))
301 // allow actors to walk on other actors as well as floors
302 if (fabsf(Other
->Origin
.x
-tmtrace
.End
.x
) < otherrad
||
303 fabsf(Other
->Origin
.y
-tmtrace
.End
.y
) < otherrad
)
305 const float ohgt
= GetBlockingHeightFor(Other
);
306 if (Other
->Origin
.z
+ohgt
>= tmtrace
.FloorZ
&&
307 Other
->Origin
.z
+ohgt
<= tmtrace
.End
.z
+MaxStepHeight
)
309 //tmtrace.BlockingMobj = Other;
310 tmtrace
.StepThing
= Other
;
311 tmtrace
.FloorZ
= Other
->Origin
.z
+ohgt
;
317 //if (!(tmtrace.Thing->EntityFlags & VEntity::EF_NoPassMobj) || Actor(Other).bSpecial)
318 if ((EntityFlags
&EF_Missile
) ||
319 (((EntityFlags
&EF_PassMobj
)|(Other
->EntityFlags
&EF_ActLikeBridge
)) && !Other
->IsNoPassOver()))
321 // prevent some objects from overlapping
322 if (!isOtherCorpse
&& ((EntityFlags
&Other
->EntityFlags
)&EF_DontOverlap
)) {
323 tmtrace
.BlockingMobj
= Other
;
326 // check if a mobj passed over/under another object
327 if (tmtrace
.End
.z
>= Other
->Origin
.z
+GetBlockingHeightFor(Other
)) return true; // overhead
328 if (tmtrace
.End
.z
+Height
<= Other
->Origin
.z
) return true; // underneath
331 if (!eventTouch(Other
, noPickups
)) {
333 tmtrace
.BlockingMobj
= Other
;
341 //==========================================================================
343 // VEntity::CheckRelLine
345 // Adjusts tmtrace.FloorZ and tmtrace.CeilingZ as lines are contacted
347 // returns `false` if blocked
349 //==========================================================================
350 bool VEntity::CheckRelLine (tmtrace_t
&tmtrace
, line_t
*ld
, bool skipSpecials
) {
351 if (GGameInfo
->NetMode
== NM_Client
) skipSpecials
= true;
353 //if (IsPlayer()) GCon->Logf(NAME_Debug, " trying line: %d", (int)(ptrdiff_t)(ld-&XLevel->Lines[0]));
354 if (!ld
->Box2DHit(tmtrace
.BBox
)) return true; // no intersection
356 // a line has been hit
358 // The moving thing's destination position will cross the given line.
359 // If this should not be allowed, return false.
360 // If the line is special, keep track of it to process later if the move is proven ok.
361 // NOTE: specials are NOT sorted by order, so two special lines that are only 8 pixels apart
362 // could be crossed in either order.
364 // k8: this code is fuckin' mess. why some lines are processed immediately, and
365 // other lines are pushed to be processed later? what the fuck is going on here?!
366 // it seems that the original intent was to immediately process blocking lines,
367 // but push non-blocking lines. wtf?!
369 if (!ld
->backsector
|| !(ld
->flags
&ML_TWOSIDED
)) {
371 if (!skipSpecials
) BlockedByLine(ld
);
372 // mark the line as blocking line
373 tmtrace
.BlockingLine
= ld
;
377 const float hgt
= max2(0.0f
, Height
);
378 //TVec hitPoint = tmtrace.End-(tmtrace.End.dot(ld->normal)-ld->dist)*ld->normal;
379 const TVec hitPoint
= tmtrace
.End
-(ld
->PointDistance(tmtrace
.End
)*ld
->normal
);
382 polyobj_t
*po
= ld
->pobj();
384 //if (IsPlayer()) GCon->Logf(NAME_Debug, "pobj #%d line #%d, blocking=%d", po->tag, (int)(ptrdiff_t)(ld-&XLevel->Lines[0]), (int)IsBlockingLine(ld));
385 if (po
->validcount
== validcount
) return true; // already checked and stuck, ignore it
386 // non-3d polyobjects
388 po
->validcount
= validcount
; // do not check if we are inside of it, we definitely are
389 // check for non-3d pobj with midtex
390 if (ld
->flags
&ML_3DMIDTEX
) {
392 const int side
= 0; //ld->PointOnSide(tmtrace.End);
393 float pz0
= 0.0f
, pz1
= 0.0f
;
394 if (!XLevel
->GetMidTexturePosition(ld
, side
, &pz0
, &pz1
)) return true; // no middle texture, so no collision
395 if (hitPoint
.z
>= pz1
|| hitPoint
.z
+hgt
<= pz0
) return true; // no collision
397 if (!IsBlockingLine(ld
)) return true;
399 // blocking non-3d polyobject line
400 if (!skipSpecials
) BlockedByLine(ld
);
401 tmtrace
.BlockingLine
= ld
;
404 vassert(po
->Is3D()); // invariant
405 // check top texture, if it blocking
406 if (ld
->flags
&ML_CLIP_MIDTEX
) {
407 //bool doBlock = (IsPlayer() || IsMonster());
408 //if (!doBlock && IsMissile() && (ld->flags&ML_BLOCKPROJECTILE)) doBlock = true;
409 const bool doBlock
= IsBlocking3DPobjLineTop(ld
);
411 // check if we are above the pobj (at least partially)
412 const float ptopz
= po
->poceiling
.minz
;
413 if (/*hitPoint.z >= ptopz ||*/ hitPoint
.z
+hgt
> ptopz
) {
414 // side doesn't matter, as it is guaranteed that both sides have the texture with the same height
415 const side_t
*tside
= &XLevel
->Sides
[ld
->sidenum
[0]];
416 if (tside
->TopTexture
> 0) {
417 VTexture
*ttex
= GTextureManager(tside
->TopTexture
);
418 if (ttex
&& ttex
->Type
!= TEXTYPE_Null
) {
419 const float texh
= ttex
->GetScaledHeightF()/tside
->Top
.ScaleY
;
420 if (hitPoint
.z
< ptopz
+texh
&& hitPoint
.z
+hgt
> ptopz
) {
421 // blocked by top texture
422 po
->validcount
= validcount
; // do not check if we are inside of it, we definitely are
423 if (!skipSpecials
) BlockedByLine(ld
);
424 tmtrace
.BlockingLine
= ld
;
425 //TODO: set proper FloorZ, CeilingZ, and DropOffZ!
426 if (FloorZ
< ptopz
) DropOffZ
= FloorZ
; // fix dropoff
428 EFloor
.set(&po
->poceiling
, false);
436 //if (!IsBlockingLine(ld)) return true; // always blocking
437 if (!Copy3DPObjFloorCeiling(tmtrace
, po
, hitPoint
.z
, hitPoint
.z
+max2(0.0f
, Height
))) {
438 // stuck inside, blocked
439 po
->validcount
= validcount
; // do not check if we are inside of it, we definitely are
440 if (!skipSpecials
) BlockedByLine(ld
);
441 tmtrace
.BlockingLine
= ld
;
447 if (IsBlockingLine(ld
)) {
448 if (!skipSpecials
) BlockedByLine(ld
);
449 tmtrace
.BlockingLine
= ld
;
453 // set openrange, opentop, openbottom
454 opening_t
*open
= XLevel
->LineOpenings(ld
, hitPoint
, SPF_NOBLOCKING
, !(EntityFlags
&EF_Missile
)/*do3dmidtex*/); // missiles ignores 3dmidtex
458 GCon->Logf(NAME_Debug, "line #%d: end=(%g,%g,%g); hp=(%g,%g,%g)",
459 (int)(ptrdiff_t)(ld-&XLevel->Lines[0]),
460 tmtrace.End.x, tmtrace.End.y, tmtrace.End.z,
461 hitPoint.x, hitPoint.y, hitPoint.z);
465 #ifdef VV_DBG_VERBOSE_REL_LINE_FC
467 GCon
->Logf(NAME_Debug
, " checking line: %d; sz=%g; ez=%g; hgt=%g; traceFZ=%g; traceCZ=%g", (int)(ptrdiff_t)(ld
-&XLevel
->Lines
[0]), tmtrace
.End
.z
, tmtrace
.End
.z
+hgt
, hgt
, tmtrace
.FloorZ
, tmtrace
.CeilingZ
);
468 for (opening_t
*op
= open
; op
; op
= op
->next
) {
469 GCon
->Logf(NAME_Debug
, " %p: bot=%g; top=%g; range=%g; lowfloor=%g; fnormz=%g", op
, op
->bottom
, op
->top
, op
->range
, op
->lowfloor
, op
->efloor
.GetNormalZSafe());
474 open
= VLevel::FindRelOpening(open
, tmtrace
.End
.z
, tmtrace
.End
.z
+hgt
);
475 #ifdef VV_DBG_VERBOSE_REL_LINE_FC
476 if (IsPlayer()) GCon
->Logf(NAME_Debug
, " open=%p; railing=%d", open
, (int)!!(ld
->flags
&ML_RAILING
));
479 if (open
&& (ld
->flags
&ML_RAILING
)) {
480 open
->bottom
+= 32.0f
;
481 open
->range
-= 32.0f
;
482 if (open
->range
<= 0.0f
) {
485 const float z0
= tmtrace
.End
.z
;
486 const float z1
= z0
+hgt
;
487 if (z1
< open
->bottom
|| z0
> open
->top
) open
= nullptr;
490 //if (ld->flags&ML_RAILING) tmtrace.FloorZ += 32.0f;
493 // adjust floor / ceiling heights
494 if ((open
->eceiling
.splane
->flags
&SPF_NOBLOCKING
) == 0) {
496 if (open
->eceiling
.GetNormalZ() != -1.0f
) {
497 // slope; use epsilon
498 replaceIt
= (tmtrace
.CeilingZ
-open
->top
> 0.1f
);
500 replaceIt
= (tmtrace
.CeilingZ
> open
->top
|| (open
->top
== tmtrace
.CeilingZ
&& tmtrace
.ECeiling
.isSlope()));
503 /*if (!skipSpecials || open->top >= Origin.z+hgt)*/ {
504 #ifdef VV_DBG_VERBOSE_REL_LINE_FC
505 if (IsPlayer()) GCon
->Logf(NAME_Debug
, " copy ceiling; hgt=%g; z+hgt=%g; top=%g; curcz-top=%g", hgt
, Origin
.z
+hgt
, open
->top
, tmtrace
.CeilingZ
-open
->top
);
507 tmtrace
.CopyOpenCeiling(open
);
508 tmtrace
.CeilingLine
= ld
;
513 if ((open
->efloor
.splane
->flags
&SPF_NOBLOCKING
) == 0) {
515 if (open
->efloor
.GetNormalZ() != 1.0f
) {
516 // slope; use epsilon
517 replaceIt
= (open
->bottom
-tmtrace
.FloorZ
> 0.1f
);
518 #ifdef VV_DBG_VERBOSE_REL_LINE_FC
519 if (IsPlayer()) GCon
->Logf(NAME_Debug
, " !floorcheck; slopez=%g; open->bottom=%g; tmtrace.FloorZ=%g; >=%d (%d)", open
->efloor
.GetNormalZ(), open
->bottom
, tmtrace
.FloorZ
, (int)(open
->bottom
> tmtrace
.FloorZ
), (int)replaceIt
);
521 // this is required for proper slope processing
522 if (replaceIt
&& mv_new_slope_code
.asBool()) {
523 if (open
->bottom
-tmtrace
.FloorZ
<= MaxStepHeight
) replaceIt
= false;
526 replaceIt
= (open
->bottom
> tmtrace
.FloorZ
|| (open
->bottom
== tmtrace
.FloorZ
&& tmtrace
.EFloor
.isSlope()));
527 #ifdef VV_DBG_VERBOSE_REL_LINE_FC
528 if (IsPlayer()) GCon
->Logf(NAME_Debug
, " !floorcheck; open->bottom=%g; tmtrace.FloorZ=%g; >=%d (%d)", open
->bottom
, tmtrace
.FloorZ
, (int)(open
->bottom
> tmtrace
.FloorZ
), (int)replaceIt
);
532 /*if (!skipSpecials || open->bottom <= Origin.z)*/ {
533 #ifdef VV_DBG_VERBOSE_REL_LINE_FC
534 if (IsPlayer()) GCon
->Logf(NAME_Debug
, " copy floor; z=%g; bot=%g; curfz-bot=%g", Origin
.z
, open
->bottom
, tmtrace
.FloorZ
-open
->bottom
);
536 tmtrace
.CopyOpenFloor(open
);
537 tmtrace
.FloorLine
= ld
;
541 #ifdef VV_DBG_VERBOSE_REL_LINE_FC
542 if (IsPlayer()) GCon
->Logf(NAME_Debug
, "...skip floor");
546 if (open
->lowfloor
< tmtrace
.DropOffZ
) tmtrace
.DropOffZ
= open
->lowfloor
;
548 //if (ld->flags&ML_RAILING) tmtrace.FloorZ += 32.0f;
551 tmtrace
.CeilingZ
= tmtrace
.FloorZ
;
552 //k8: oops; it seems that we have to return `false` here
553 // but only if this is not a special line, otherwise monsters cannot open doors
555 //GCon->Logf(NAME_Debug, "BLK: %s (hgt=%g); line=%d", GetClass()->GetName(), hgt, (int)(ptrdiff_t)(ld-&XLevel->Lines[0]));
556 if (!skipSpecials
) BlockedByLine(ld
);
557 tmtrace
.BlockingLine
= ld
;
560 // this is special line, don't block movement (but remember this line as blocking!)
561 tmtrace
.BlockingLine
= ld
;
565 // if contacted a special line, add it to the list
566 if (!skipSpecials
&& ld
->special
) tmtrace
.SpecHit
.Append(ld
);
572 //==========================================================================
574 // VEntity::CheckRelPositionPoint
576 //==========================================================================
577 bool VEntity::CheckRelPositionPoint (tmtrace_t
&tmtrace
, TVec Pos
) {
578 memset((void *)&tmtrace
, 0, sizeof(tmtrace
));
582 const subsector_t
*sub
= XLevel
->PointInSubsector_Buggy(Pos
);
583 sector_t
*sec
= sub
->sector
;
585 const float ffz
= sec
->floor
.GetPointZClamped(Pos
);
586 const float fcz
= sec
->ceiling
.GetPointZClamped(Pos
);
588 tmtrace
.EFloor
.set(&sec
->floor
, false);
589 tmtrace
.FloorZ
= tmtrace
.DropOffZ
= ffz
;
590 tmtrace
.ECeiling
.set(&sec
->ceiling
, false);
591 tmtrace
.CeilingZ
= fcz
;
594 if (Pos
.z
< ffz
|| Pos
.z
> fcz
) return false;
597 if (ffz
>= fcz
) return false;
599 // on the floor, or on the ceiling?
600 if (Pos
.z
== ffz
|| Pos
.z
== fcz
) return true;
605 if (sec
->Has3DFloors()) {
606 for (sec_region_t
*reg
= sec
->eregions
->next
; reg
; reg
= reg
->next
) {
607 if (reg
->regflags
&(sec_region_t::RF_BaseRegion
|sec_region_t::RF_OnlyVisual
|sec_region_t::RF_NonSolid
)) continue;
608 //if (((reg->efloor.splane->flags|reg->eceiling.splane->flags)&flagmask) != 0) continue; // bad flags
609 // get opening points
610 const float fz
= reg
->efloor
.GetPointZClamped(Pos
);
611 const float cz
= reg
->eceiling
.GetPointZClamped(Pos
);
612 if (fz
>= cz
) continue; // ignore paper-thin regions
614 // below, fix ceiling
615 if (cz
< tmtrace
.CeilingZ
) {
616 tmtrace
.ECeiling
= reg
->eceiling
;
617 tmtrace
.CeilingZ
= cz
;
623 if (fz
> tmtrace
.FloorZ
) {
624 tmtrace
.EFloor
= reg
->efloor
;
630 res
= false; // blocked
635 if (XLevel
->CanHave3DPolyObjAt2DPoint(Pos
.x
, Pos
.y
)) {
636 for (auto &&it
: sub
->PObjFirst()) {
637 polyobj_t
*po
= it
.pobj();
638 if (!po
->Is3D()) continue;
639 if (!IsPointInside2DBBox(Pos
.x
, Pos
.y
, po
->bbox2d
)) continue;
640 const float pz0
= po
->pofloor
.minz
;
641 const float pz1
= po
->poceiling
.maxz
;
642 if (pz0
>= pz1
) continue; // paper-thin
643 if (Pos
.z
> pz0
&& Pos
.z
< pz1
) { res
= false; continue; } // inside, nothing to do
644 // fix floor and ceiling
646 // below, fix ceiling
647 if (pz0
< tmtrace
.CeilingZ
) {
648 if (XLevel
->IsPointInsideSector2D(po
->GetSector(), Pos
.x
, Pos
.y
)) {
649 tmtrace
.ECeiling
.set(&po
->pofloor
, false);
650 tmtrace
.CeilingZ
= pz0
;
657 if (pz1
> tmtrace
.FloorZ
) {
658 if (XLevel
->IsPointInsideSector2D(po
->GetSector(), Pos
.x
, Pos
.y
)) {
659 tmtrace
.EFloor
.set(&po
->poceiling
, false);
660 tmtrace
.FloorZ
= pz1
;
673 //==========================================================================
677 //==========================================================================
679 // native final bool CheckRelPosition (out tmtrace_t tmtrace, TVec Pos, optional bool noPickups/*=false*/, optional bool ignoreMonsters, optional bool ignorePlayers);
680 IMPLEMENT_FUNCTION(VEntity
, CheckRelPosition
) {
682 tmtrace_t
*tmtrace
= nullptr;
684 VOptParamBool
noPickups(false);
685 VOptParamBool
ignoreMonsters(false);
686 VOptParamBool
ignorePlayers(false);
687 vobjGetParamSelf(tmtrace
, Pos
, noPickups
, ignoreMonsters
, ignorePlayers
);
688 if (!tmtrace
) tmtrace
= &tmp
;
689 RET_BOOL(Self
->CheckRelPosition(*tmtrace
, Pos
, noPickups
, ignoreMonsters
, ignorePlayers
));
692 // native final bool CheckRelPositionPoint (out tmtrace_t tmtrace, TVec Pos);
693 IMPLEMENT_FUNCTION(VEntity
, CheckRelPositionPoint
) {
695 tmtrace_t
*tmtrace
= nullptr;
697 vobjGetParamSelf(tmtrace
, Pos
);
698 if (!tmtrace
) tmtrace
= &tmp
;
699 RET_BOOL(Self
->CheckRelPositionPoint(*tmtrace
, Pos
));