libvwad: updated -- vwadwrite: free file buffers on close (otherwise archive creation...
[k8vavoom.git] / source / psim / p_entity_checkrelpos.cpp
blobe315030e8c27576364a7a51220a6c0f897109900
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_world.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).
47 // in:
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)
52 // during:
53 // special things are touched if MF_PICKUP
54 // early out on solid lines?
56 // out:
57 // newsubsec
58 // floorz
59 // ceilingz
60 // tmdropoffz
61 // the lowest point contacted
62 // (monsters won't move to a dropoff)
63 // speciallines[]
64 // numspeciallines
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));
75 tmtrace.End = Pos;
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
117 return false;
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) {
125 if (thingblocker) {
126 // something to step up on, set it as the blocker so that we don't step up
127 return false;
129 // nothing is blocking, but this object potentially could
130 // if there is something else to step on
131 tmtrace.BlockingMobj = nullptr;
132 } else {
133 // blocking
134 return false;
142 float thingdropoffz = tmtrace.FloorZ;
143 tmtrace.FloorZ = tmtrace.DropOffZ;
144 tmtrace.BlockingMobj = nullptr;
146 //bool gotNewValid = false;
148 // check lines
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);
152 #endif
153 //XLevel->IncrementValidCount();
154 //gotNewValid = true;
156 DeclareMakeBlockMapCoordsBBox2D(tmtrace.BBox, xl, yl, xh, yh);
157 bool good = true;
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);
169 #endif
170 good = false;
171 // find the fractional intercept point along the trace line
172 const float den = ld->normal.dot(tmtrace.End-Pos);
173 if (den == 0.0f) {
174 fuckhit = ld;
175 lastFrac = 0;
176 } else {
177 const float num = ld->dist-Pos.dot(ld->normal);
178 const float frac = fabsf(num/den);
179 if (frac < lastFrac) {
180 fuckhit = ld;
181 lastFrac = frac;
185 #ifdef VV_DBG_VERBOSE_REL_LINE_FC
186 else {
187 if (IsPlayer()) GCon->Logf(NAME_Debug, "%s: OK line #%d (FloorZ=%g)", GetClass()->GetName(), (int)(ptrdiff_t)(ld-&XLevel->Lines[0]), tmtrace.FloorZ);
189 #endif
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) {
202 polyobj_t *po;
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);
220 if (!good) {
221 if (fuckhit) {
222 if (!tmtrace.BlockingLine) tmtrace.BlockingLine = fuckhit;
223 if (!tmtrace.AnyBlockingLine) tmtrace.AnyBlockingLine = fuckhit;
225 return false;
228 if (inpobj) {
229 tmtrace.BlockingMobj = nullptr;
230 tmtrace.BlockingLine = nullptr;
231 tmtrace.AnyBlockingLine = nullptr;
232 tmtrace.PolyObj = inpobj;
233 return false;
236 if (tmtrace.CeilingZ-tmtrace.FloorZ < Height) {
237 if (fuckhit) {
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;
246 return false;
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);
257 #endif
259 return true;
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;
275 // can't hit thing
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)
286 // didn't hit it
287 return true;
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;
296 // check bridges
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;
324 return false;
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)) {
332 // just in case
333 tmtrace.BlockingMobj = Other;
334 return false;
337 return true;
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)) {
370 // one sided line
371 if (!skipSpecials) BlockedByLine(ld);
372 // mark the line as blocking line
373 tmtrace.BlockingLine = ld;
374 return false;
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);
381 // check polyobject
382 polyobj_t *po = ld->pobj();
383 if (po) {
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
387 if (!po->Is3D()) {
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) {
391 // use front side
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
396 } else {
397 if (!IsBlockingLine(ld)) return true;
399 // blocking non-3d polyobject line
400 if (!skipSpecials) BlockedByLine(ld);
401 tmtrace.BlockingLine = ld;
402 return false;
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);
410 if (doBlock) {
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
427 FloorZ = ptopz;
428 EFloor.set(&po->poceiling, false);
429 return 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;
442 return false;
444 return true;
447 if (IsBlockingLine(ld)) {
448 if (!skipSpecials) BlockedByLine(ld);
449 tmtrace.BlockingLine = ld;
450 return false;
453 // set openrange, opentop, openbottom
454 opening_t *open = XLevel->LineOpenings(ld, hitPoint, SPF_NOBLOCKING, !(EntityFlags&EF_Missile)/*do3dmidtex*/); // missiles ignores 3dmidtex
457 if (IsPlayer()) {
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
466 if (IsPlayer()) {
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());
472 #endif
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));
477 #endif
478 // process railings
479 if (open && (ld->flags&ML_RAILING)) {
480 open->bottom += 32.0f;
481 open->range -= 32.0f;
482 if (open->range <= 0.0f) {
483 open = nullptr;
484 } else {
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;
492 if (open) {
493 // adjust floor / ceiling heights
494 if ((open->eceiling.splane->flags&SPF_NOBLOCKING) == 0) {
495 bool replaceIt;
496 if (open->eceiling.GetNormalZ() != -1.0f) {
497 // slope; use epsilon
498 replaceIt = (tmtrace.CeilingZ-open->top > 0.1f);
499 } else {
500 replaceIt = (tmtrace.CeilingZ > open->top || (open->top == tmtrace.CeilingZ && tmtrace.ECeiling.isSlope()));
502 if (replaceIt) {
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);
506 #endif
507 tmtrace.CopyOpenCeiling(open);
508 tmtrace.CeilingLine = ld;
513 if ((open->efloor.splane->flags&SPF_NOBLOCKING) == 0) {
514 bool replaceIt;
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);
520 #endif
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;
525 } else {
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);
529 #endif
531 if (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);
535 #endif
536 tmtrace.CopyOpenFloor(open);
537 tmtrace.FloorLine = ld;
540 } else {
541 #ifdef VV_DBG_VERBOSE_REL_LINE_FC
542 if (IsPlayer()) GCon->Logf(NAME_Debug, "...skip floor");
543 #endif
546 if (open->lowfloor < tmtrace.DropOffZ) tmtrace.DropOffZ = open->lowfloor;
548 //if (ld->flags&ML_RAILING) tmtrace.FloorZ += 32.0f;
549 } else {
550 // no opening
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
554 if (!ld->special) {
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;
558 return false;
559 } else {
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);
568 return true;
572 //==========================================================================
574 // VEntity::CheckRelPositionPoint
576 //==========================================================================
577 bool VEntity::CheckRelPositionPoint (tmtrace_t &tmtrace, TVec Pos) {
578 memset((void *)&tmtrace, 0, sizeof(tmtrace));
580 tmtrace.End = Pos;
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;
593 // below or above?
594 if (Pos.z < ffz || Pos.z > fcz) return false;
596 // closed sector?
597 if (ffz >= fcz) return false;
599 // on the floor, or on the ceiling?
600 if (Pos.z == ffz || Pos.z == fcz) return true;
602 bool res = true;
604 // check regions
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
613 if (Pos.z <= cz) {
614 // below, fix ceiling
615 if (cz < tmtrace.CeilingZ) {
616 tmtrace.ECeiling = reg->eceiling;
617 tmtrace.CeilingZ = cz;
619 continue;
621 if (Pos.z >= fz) {
622 // above, fix floor
623 if (fz > tmtrace.FloorZ) {
624 tmtrace.EFloor = reg->efloor;
625 tmtrace.FloorZ = fz;
627 continue;
629 // inside, ignore it
630 res = false; // blocked
634 // check polyobjects
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
645 if (Pos.z <= pz0) {
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;
653 continue;
655 if (Pos.z >= pz1) {
656 // above, fix floor
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;
663 continue;
668 return res;
673 //==========================================================================
675 // Script natives
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) {
681 tmtrace_t tmp;
682 tmtrace_t *tmtrace = nullptr;
683 TVec Pos;
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) {
694 tmtrace_t tmp;
695 tmtrace_t *tmtrace = nullptr;
696 TVec Pos;
697 vobjGetParamSelf(tmtrace, Pos);
698 if (!tmtrace) tmtrace = &tmp;
699 RET_BOOL(Self->CheckRelPositionPoint(*tmtrace, Pos));