swwmgz_m/zscript/utility/swwm_utility_map.zsc
Marisa the Magician 8d0a087e6c Reduce perf hit of bullet trails by only spawning underwater bubbles actually underwater, instead of always spawning them and then relying on them despawning themselves after checking their own water level.
Further optimization could be possible if I found a way to divide the trace results into segments based on intersections with water volumes. This would save on countless calls to Vec3Offset and PointInSector. Something to keep in mind, I suppose.
While I was at it, I did make the underwater check into a new utility function. It is more or less based on how updating water level is done internally, but saves time by just checking a single point rather than an actor's full height.
Not sure if I'll need to use it elsewhere, but if that turns out to not be the case I'll change it to a private function of the SWWMBulletTrail class afterwards.
2025-03-14 16:23:20 +01:00

724 lines
24 KiB
Text

// map interaction/info functions
enum EExitType
{
ET_Normal,
ET_Secret,
ET_EndGame,
ET_NewMap,
};
extend Class SWWMUtility
{
// Thanks to ZZYZX and Nash
static play void SetToSlopeSpecific( Actor a, double dang, readonly<SecPlane> plane, bool flipnorm )
{
Vector3 fnormal;
if ( flipnorm ) fnormal = -plane.Normal;
else fnormal = plane.Normal;
vector2 fnormalp1 = ((fnormal.x != 0) || (fnormal.y != 0))?(fnormal.x,fnormal.y).Unit():(0,0);
vector2 fnormalp2 = ((fnormal.x,fnormal.y).Length(),fnormal.z);
double fang = atan2(fnormalp1.y,fnormalp1.x); // floor angle (not pitch!)
double fpitch = atan2(fnormalp2.x,fnormalp2.y); // floor pitch
double ddiff1 = sin(fang-dang);
double ddiff2 = cos(fang-dang);
a.pitch = fpitch*ddiff2;
a.roll = -fpitch*ddiff1;
a.angle = dang;
}
static play void SetToSlope( Actor a, double dang, bool ceil = false )
{
Sector sect;
SecPlane plane;
Vector3 fnormal;
bool flipnorm;
if ( ceil )
{
sect = a.CeilingSector;
plane = sect.ceilingplane;
flipnorm = true;
fnormal = -sect.ceilingplane.Normal;
}
else
{
sect = a.FloorSector;
plane = sect.floorplane;
flipnorm = false;
fnormal = sect.floorplane.Normal;
}
// find closest 3d floor for its normal
F3DFloor ff;
for ( int i=0; i<sect.Get3DFloorCount(); i++ )
{
if ( !(sect.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
if ( !ceil && !(sect.Get3DFloor(i).top.ZAtPoint(a.pos.xy) ~== a.floorz) ) continue;
else if ( ceil && !(sect.Get3DFloor(i).top.ZAtPoint(a.pos.xy) ~== a.ceilingz) ) continue;
ff = sect.Get3DFloor(i);
break;
}
if ( ff )
{
if ( ceil )
{
plane = ff.bottom;
flipnorm = false;
fnormal = ff.bottom.Normal;
}
else
{
plane = ff.top;
flipnorm = true;
fnormal = -ff.top.Normal;
}
}
SetToSlopeSpecific(a,dang,plane,flipnorm);
}
static int GetLineLock( Line l )
{
int locknum = l.locknumber;
if ( !locknum )
{
// check the special
switch ( l.special )
{
case FS_Execute:
locknum = l.Args[2];
break;
case Door_LockedRaise:
case Door_Animated:
locknum = l.Args[3];
break;
case ACS_LockedExecute:
case ACS_LockedExecuteDoor:
case Generic_Door:
locknum = l.Args[4];
break;
}
}
return locknum;
}
// return if a line is an exit, and additionally the type of exit
static bool, int IsExitLine( Line l )
{
if ( l.special == Exit_Secret )
return true, ET_Secret;
if ( l.special == Exit_Normal )
return true, ET_Normal;
if ( l.special == Teleport_EndGame )
return true, ET_EndGame;
if ( l.special == Teleport_NewMap )
return true, ET_NewMap;
// E1M8 compat
if ( (l.special == ACS_Execute) && (l.Args[0] == -Int('E1M8_KNOCKOUT')) )
return true, ET_Normal;
// spooktober™
if ( ((l.special == ACS_Execute) || (l.special == ACS_ExecuteAlways)) && (l.Args[0] == -Int('MapFadeOut')) )
{
if ( level.levelnum == 1 )
{
let lv = levelinfo.FindLevelByNum(l.Args[2]);
if ( lv && lv.mapname.Left(6) ~== "SECRET" )
return true, ET_Secret;
else return true, ET_NewMap;
}
return true, ET_Normal;
}
return false, ET_Normal;
}
static bool IsTeleportLine( Line l, bool all = false )
{
// must be two-sided and crossable
if ( !l.sidedef[1] || !(l.Activation&(SPAC_Cross|SPAC_MCross|SPAC_PCross|SPAC_AnyCross)) ) return false;
// filter lines that aren't player-activated (unless checking all)
if ( !all && !(l.Activation&SPAC_PlayerActivate) ) return false;
// typical teleports
if ( (l.special == Teleport) || (l.special == Teleport_NoStop) )
return true;
// if checking all, also include sneaky teleports
if ( all && ((l.special == Teleport_Line) || (l.special == Teleport_NoFog)) )
return true;
// exits are included too
if ( IsExitLine(l) )
return true;
return false;
}
static bool IsDoorSector( Sector s, int part )
{
// super-easy mode: check for boss special sectors
if ( (level.mapname ~== "E1M8") || (level.mapname ~== "E2M8") || (level.mapname ~== "E3M8")
|| (level.mapname ~== "E4M6") || (level.mapname ~== "E4M8") || (level.mapname ~== "E5M8")
|| (level.mapname ~== "MAP07") )
{
let si = level.CreateSectorTagIterator(666);
int idx;
while ( (idx = si.Next()) != -1 )
if ( level.Sectors[idx] == s )
return true;
if ( level.mapname ~== "MAP07" )
{
let si2 = level.CreateSectorTagIterator(667);
while ( (idx = si.Next()) != -1 )
if ( level.Sectors[idx] == s )
return true;
}
}
// moderate: see if it's a busted crusher, we need to be able to break those in case they cause a softlock
let ti = ThinkerIterator.Create('SWWMCrusherBroken',Thinker.STAT_USER);
SWWMCrusherBroken cb;
while ( cb = SWWMCrusherBroken(ti.Next()) )
{
if ( (part == 0) && (cb.fsec == s) ) return true;
if ( (part == 1) && (cb.csec == s) ) return true;
}
// hard mode: look for all lines/actors with movement specials referencing us
foreach ( l:level.Lines )
{
if ( !l.special ) continue;
if ( (part && (l.special >= 10) && (l.special <= 13))
|| (!part && (l.special >= 20) && (l.special <= 25))
|| (!part && (l.special == 28))
|| ((l.special >= 29) && (l.special <= 30))
|| (!part && (l.special >= 35) && (l.special <= 37))
|| (part && (l.special >= 40) && (l.special <= 45))
|| (!part && (l.special == 46))
|| (part && (l.special == 47))
|| (!part && (l.special >= 60) && (l.special <= 68))
|| (part && (l.special == 69))
|| ((l.special >= 94) && (l.special <= 96))
|| (part && (l.special == 97))
|| (!part && (l.special == 99))
|| (part && (l.special == 104))
|| (part && (l.special >= 105) && (l.special <= 106))
|| (part && (l.special >= 168) && (l.special <= 169))
|| (!part && (l.special == 172))
|| (part && (l.special >= 192) && (l.special <= 199))
|| (!part && (l.special == 200))
|| (part && (l.special >= 201) && (l.special <= 202))
|| (!part && (l.special == 203))
|| (part && (l.special == 205))
|| (!part && (l.special >= 206) && (l.special <= 207))
|| (!part && (l.special == 228))
|| (!part && (l.special >= 230) && (l.special <= 231))
|| (!part && (l.special >= 238) && (l.special <= 242))
|| ((l.special >= 245) && (l.special <= 247))
|| (part && (l.special == 249))
|| (!part && (l.special >= 250) && (l.special <= 251))
|| (part && (l.special >= 251) && (l.special <= 255))
|| (!part && (l.special >= 256) && (l.special <= 261))
|| (part && (l.special >= 262) && (l.special <= 269))
|| (!part && (l.special == 275))
|| (part && (l.special == 276))
|| (!part && (l.special == 279))
|| (part && (l.special == 280)) )
{
let si = level.CreateSectorTagIterator(l.Args[0],l);
int idx;
while ( (idx = si.Next()) != -1 )
if ( level.Sectors[idx] == s )
return true;
}
}
ti = ThinkerIterator.Create('Actor');
Actor a;
while ( a = Actor(ti.Next()) )
{
if ( !a.special || !a.Args[0] ) continue;
if ( (part && (a.special >= 10) && (a.special <= 13))
|| (!part && (a.special >= 20) && (a.special <= 25))
|| (!part && (a.special == 28))
|| ((a.special >= 29) && (a.special <= 30))
|| (!part && (a.special >= 35) && (a.special <= 37))
|| (part && (a.special >= 40) && (a.special <= 45))
|| (!part && (a.special == 46))
|| (part && (a.special == 47))
|| (!part && (a.special >= 60) && (a.special <= 68))
|| (part && (a.special == 69))
|| ((a.special >= 94) && (a.special <= 96))
|| (part && (a.special == 97))
|| (!part && (a.special == 99))
|| (part && (a.special == 104))
|| (part && (a.special >= 105) && (a.special <= 106))
|| (part && (a.special >= 168) && (a.special <= 169))
|| (!part && (a.special == 172))
|| (part && (a.special >= 192) && (a.special <= 199))
|| (!part && (a.special == 200))
|| (part && (a.special >= 201) && (a.special <= 202))
|| (!part && (a.special == 203))
|| (part && (a.special == 205))
|| (!part && (a.special >= 206) && (a.special <= 207))
|| (!part && (a.special == 228))
|| (!part && (a.special >= 230) && (a.special <= 231))
|| (!part && (a.special >= 238) && (a.special <= 242))
|| ((a.special >= 245) && (a.special <= 247))
|| (part && (a.special == 249))
|| (!part && (a.special >= 250) && (a.special <= 251))
|| (part && (a.special >= 251) && (a.special <= 255))
|| (!part && (a.special >= 256) && (a.special <= 261))
|| (part && (a.special >= 262) && (a.special <= 269))
|| (!part && (a.special == 275))
|| (part && (a.special == 276))
|| (!part && (a.special == 279))
|| (part && (a.special == 280)) )
{
let si = level.CreateSectorTagIterator(a.Args[0]);
int idx;
while ( (idx = si.Next()) != -1 )
if ( level.Sectors[idx] == s )
return true;
}
}
return false;
}
// the stupidest thing ever, it's called BlockingLine but it's not always blocking us
// note: MovementBlockingLine as an alternative seems prone to issues at the moment, needs further investigation
static bool BlockingLineIsBlocking( Actor a, int blockflags = Line.ML_BLOCKEVERYTHING, Line testline = null )
{
Line l = testline?testline:a.BlockingLine;
// not blocked
if ( !l ) return false;
// one-sided always blocking
if ( !l.sidedef[1] ) return true;
// same for block everything lines
if ( l.flags&blockflags ) return true;
// lower and upper bounds hit?
double afloor = l.frontsector.floorplane.ZAtPoint(a.pos.xy),
bfloor = l.backsector.floorplane.ZAtPoint(a.pos.xy),
aceil = l.frontsector.ceilingplane.ZAtPoint(a.pos.xy),
bceil = l.backsector.ceilingplane.ZAtPoint(a.pos.xy);
if ( (min(a.pos.z+a.height,a.ceilingz) > min(aceil,bceil)) || (max(a.pos.z,a.floorz) < max(afloor,bfloor)) )
return true;
// solid 3d floor bounds hit?
for ( int i=0; i<l.frontsector.Get3DFloorCount(); i++ )
{
F3DFloor ff = l.frontsector.Get3DFloor(i);
if ( !(ff.flags&(F3DFloor.FF_EXISTS|F3DFloor.FF_SOLID)) ) continue;
double floor = ff.top.ZAtPoint(a.pos.xy);
double ceil = ff.bottom.ZAtPoint(a.pos.xy);
if ( (a.pos.z+a.height > ceil) && (a.pos.z < floor) )
return true;
}
for ( int i=0; i<l.backsector.Get3DFloorCount(); i++ )
{
F3DFloor ff = l.backsector.Get3DFloor(i);
if ( !(ff.flags&(F3DFloor.FF_EXISTS|F3DFloor.FF_SOLID)) ) continue;
double floor = ff.top.ZAtPoint(a.pos.xy);
double ceil = ff.bottom.ZAtPoint(a.pos.xy);
if ( (a.pos.z+a.height > ceil) && (a.pos.z < floor) )
return true;
}
return false;
}
static Vector3 UseLinePos( Line l )
{
Vector3 al, ah, bl, bh;
if ( !l.sidedef[1] )
{
// just the whole line
al = (l.v1.p,l.frontsector.floorplane.ZatPoint(l.v1.p));
ah = (l.v1.p,l.frontsector.ceilingplane.ZatPoint(l.v1.p));
bl = (l.v2.p,l.frontsector.floorplane.ZatPoint(l.v2.p));
bh = (l.v2.p,l.frontsector.ceilingplane.ZatPoint(l.v2.p));
return (al+ah+bl+bh)*.25;
}
SecPlane highestfloor, lowestfloor, lowestceiling, highestceiling;
if ( (l.frontsector.floorplane.ZatPoint(l.v1.p) > l.backsector.floorplane.ZatPoint(l.v1.p))
&& (l.frontsector.floorplane.ZatPoint(l.v2.p) > l.backsector.floorplane.ZatPoint(l.v2.p)) )
{
highestfloor = l.frontsector.floorplane;
lowestfloor = l.backsector.floorplane;
}
else
{
highestfloor = l.backsector.floorplane;
lowestfloor = l.frontsector.floorplane;
}
if ( (l.frontsector.ceilingplane.ZatPoint(l.v1.p) < l.backsector.ceilingplane.ZatPoint(l.v1.p))
&& (l.frontsector.ceilingplane.ZatPoint(l.v2.p) < l.backsector.ceilingplane.ZatPoint(l.v2.p)) )
{
lowestceiling = l.frontsector.ceilingplane;
highestceiling = l.backsector.ceilingplane;
}
else
{
lowestceiling = l.backsector.ceilingplane;
highestceiling = l.frontsector.ceilingplane;
}
// try to guess what the part that triggers this is
if ( l.Activation&SPAC_Cross )
{
// pick the "intersection"
al = (l.v1.p,highestfloor.ZatPoint(l.v1.p));
ah = (l.v1.p,lowestceiling.ZatPoint(l.v1.p));
bl = (l.v2.p,highestfloor.ZatPoint(l.v2.p));
bh = (l.v2.p,lowestceiling.ZatPoint(l.v2.p));
return (al+ah+bl+bh)*.25;
}
// check if lower part available
al = (l.v1.p,lowestfloor.ZatPoint(l.v1.p));
ah = (l.v1.p,highestfloor.ZatPoint(l.v1.p));
bl = (l.v2.p,lowestfloor.ZatPoint(l.v2.p));
bh = (l.v2.p,highestfloor.ZatPoint(l.v2.p));
if ( ((al-ah).length() > 0) && ((bl-bh).length() > 0) )
return (al+ah+bl+bh)*.25;
// check if upper part available
al = (l.v1.p,lowestceiling.ZatPoint(l.v1.p));
ah = (l.v1.p,highestceiling.ZatPoint(l.v1.p));
bl = (l.v2.p,lowestceiling.ZatPoint(l.v2.p));
bh = (l.v2.p,highestceiling.ZatPoint(l.v2.p));
if ( ((al-ah).length() > 0) && ((bl-bh).length() > 0) )
return (al+ah+bl+bh)*.25;
// check for 3d floors
bool floorfound = false;
Vector3 fal, fah, fbl, fbh;
for ( int i=0; i<l.backsector.Get3DFloorCount(); i++ )
{
let ff = l.backsector.Get3DFloor(i);
fal = (l.v1.p,ff.model.floorplane.ZAtPoint(l.v1.p));
fah = (l.v1.p,ff.model.floorplane.ZAtPoint(l.v1.p));
fbl = (l.v2.p,ff.model.ceilingplane.ZAtPoint(l.v2.p));
fbh = (l.v2.p,ff.model.ceilingplane.ZAtPoint(l.v2.p));
// skip if higher, we'll go with the lowest 3d floor (may not be right, but whatever)
if ( floorfound && (fah.z > ah.z) && (fbh.z > bh.z) && (fal.z > al.z) && (fbl.z > bl.z) ) continue;
al = fal;
ah = fah;
bl = fbl;
bh = fbh;
floorfound = true;
}
if ( floorfound ) return (al+ah+bl+bh)*.25;
for ( int i=0; i<l.frontsector.Get3DFloorCount(); i++ )
{
let ff = l.frontsector.Get3DFloor(i);
fal = (l.v1.p,ff.model.floorplane.ZAtPoint(l.v1.p));
fah = (l.v1.p,ff.model.floorplane.ZAtPoint(l.v1.p));
fbl = (l.v2.p,ff.model.ceilingplane.ZAtPoint(l.v2.p));
fbh = (l.v2.p,ff.model.ceilingplane.ZAtPoint(l.v2.p));
// skip if higher, we'll go with the lowest 3d floor (may not be right, but whatever)
if ( floorfound && (fah.z > ah.z) && (fbh.z > bh.z) && (fal.z > al.z) && (fbl.z > bl.z) ) continue;
al = fal;
ah = fah;
bl = fbl;
bh = fbh;
floorfound = true;
}
if ( floorfound ) return (al+ah+bl+bh)*.25;
// check for midtex
if ( !l.sidedef[0].GetTexture(1).IsNull() )
{
let [valid,low,high] = l.GetMidTexturePosition(0);
if ( valid )
{
al = (l.v1.p,low);
bl = (l.v2.p,low);
ah = (l.v1.p,high);
bh = (l.v2.p,high);
return (al+ah+bl+bh)*.25;
}
}
if ( !l.sidedef[1].GetTexture(1).IsNull() )
{
let [valid,low,high] = l.GetMidTexturePosition(1);
if ( valid )
{
al = (l.v1.p,low);
bl = (l.v2.p,low);
ah = (l.v1.p,high);
bh = (l.v2.p,high);
return (al+ah+bl+bh)*.25;
}
}
// just use the intersection
al = (l.v1.p,highestfloor.ZatPoint(l.v1.p));
ah = (l.v1.p,lowestceiling.ZatPoint(l.v1.p));
bl = (l.v2.p,highestfloor.ZatPoint(l.v2.p));
bh = (l.v2.p,lowestceiling.ZatPoint(l.v2.p));
return (al+ah+bl+bh)*.25;
}
// gets the hit normal vector for projectiles and hitscan
static Vector3 GetActorHitNormal( Actor a )
{
Vector3 HitNormal = (0,0,0);
F3DFloor ff;
if ( a.BlockingMobj )
{
let mo = a.BlockingMobj;
Vector3 diff = level.Vec3Diff(mo.pos,a.pos);
if ( diff.x >= mo.radius ) HitNormal += (1,0,0);
else if ( diff.x <= -mo.radius ) HitNormal += (-1,0,0);
if ( diff.y >= mo.radius ) HitNormal += (0,1,0);
else if ( diff.y <= -mo.radius ) HitNormal += (0,-1,0);
if ( diff.z >= mo.height ) HitNormal += (0,0,1);
else if ( diff.z <= 0. ) HitNormal += (0,0,-1);
double len = HitNormal.length();
if ( len < double.epsilon ) HitNormal = Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90));
else HitNormal /= len;
}
else if ( a.BlockingFloor )
{
// find closest 3d floor for its normal
for ( int i=0; i<a.BlockingFloor.Get3DFloorCount(); i++ )
{
if ( !(a.BlockingFloor.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
if ( !(a.BlockingFloor.Get3DFloor(i).top.ZAtPoint(a.pos.xy) ~== a.floorz) ) continue;
ff = a.BlockingFloor.Get3DFloor(i);
break;
}
if ( ff ) HitNormal = -ff.top.Normal;
else HitNormal = a.BlockingFloor.floorplane.Normal;
}
else if ( a.BlockingCeiling )
{
// find closest 3d floor for its normal
for ( int i=0; i<a.BlockingCeiling.Get3DFloorCount(); i++ )
{
if ( !(a.BlockingCeiling.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
if ( !(a.BlockingCeiling.Get3DFloor(i).bottom.ZAtPoint(a.pos.xy) ~== a.ceilingz) ) continue;
ff = a.BlockingCeiling.Get3DFloor(i);
break;
}
if ( ff ) HitNormal = -ff.bottom.Normal;
else HitNormal = a.BlockingCeiling.ceilingplane.Normal;
}
else if ( a.BlockingLine && BlockingLineIsBlocking(a,Line.ML_BLOCKEVERYTHING|Line.ML_BLOCKPROJECTILE,a.BlockingLine) )
{
HitNormal = (-a.BlockingLine.delta.y,a.BlockingLine.delta.x,0).unit();
if ( !Level.PointOnLineSide(a.pos.xy,a.BlockingLine) )
HitNormal *= -1;
}
else
{
double len = a.vel.length();
if ( len > 0. ) HitNormal = -a.vel/len;
}
return HitNormal;
}
static Vector3 GetActorBounceHitNormal( Actor a, Actor bounceMobj, Line bounceLine, readonly<SecPlane> bouncePlane, bool is3DFloor )
{
Vector3 HitNormal = (0,0,0);
if ( bounceMobj )
{
Vector3 diff = level.Vec3Diff(bounceMobj.pos,a.pos);
if ( diff.x >= bounceMobj.radius ) HitNormal += (1,0,0);
else if ( diff.x <= -bounceMobj.radius ) HitNormal += (-1,0,0);
if ( diff.y >= bounceMobj.radius ) HitNormal += (0,1,0);
else if ( diff.y <= -bounceMobj.radius ) HitNormal += (0,-1,0);
if ( diff.z >= bounceMobj.height ) HitNormal += (0,0,1);
else if ( diff.z <= 0. ) HitNormal += (0,0,-1);
double len = HitNormal.length();
if ( len < double.epsilon ) HitNormal = Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90));
else HitNormal /= len;
}
else if ( bouncePlane )
{
if ( is3DFloor ) HitNormal = -bouncePlane.Normal;
else HitNormal = bouncePlane.Normal;
}
else if ( bounceLine )
{
HitNormal = (-bounceLine.delta.y,bounceLine.delta.x,0).unit();
if ( !Level.PointOnLineSide(a.pos.xy,bounceLine) )
HitNormal *= -1;
}
return HitNormal;
}
static Vector3 GetLineTraceHitNormal( FLineTraceData d )
{
Vector3 HitNormal = (0,0,0);
if ( d.HitType == TRACE_HitActor )
{
let mo = d.HitActor;
Vector3 diff = level.Vec3Diff(mo.pos,d.HitLocation);
if ( diff.x >= mo.radius ) HitNormal += (1,0,0);
else if ( diff.x <= -mo.radius ) HitNormal += (-1,0,0);
if ( diff.y >= mo.radius ) HitNormal += (0,1,0);
else if ( diff.y <= -mo.radius ) HitNormal += (0,-1,0);
if ( diff.z >= mo.height ) HitNormal += (0,0,1);
else if ( diff.z <= 0. ) HitNormal += (0,0,-1);
double len = HitNormal.length();
if ( len < double.epsilon ) HitNormal = Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90));
else HitNormal /= len;
}
else if ( d.HitType == TRACE_HitFloor )
{
if ( d.Hit3DFloor ) HitNormal = -d.Hit3DFloor.top.Normal;
else HitNormal = d.HitSector.floorplane.Normal;
}
else if ( d.HitType == TRACE_HitCeiling )
{
if ( d.Hit3DFloor ) HitNormal = -d.Hit3DFloor.bottom.Normal;
else HitNormal = d.HitSector.ceilingplane.Normal;
}
else if ( d.HitType == TRACE_HitWall )
{
HitNormal = (-d.HitLine.delta.y,d.HitLine.delta.x,0).unit();
if ( !d.LineSide ) HitNormal *= -1;
}
else HitNormal = -d.HitDir;
return HitNormal;
}
static Vector3 GetLineTracerHitNormal( TraceResults r )
{
Vector3 HitNormal = (0,0,0);
if ( r.HitType == TRACE_HitActor )
{
let mo = r.HitActor;
Vector3 diff = level.Vec3Diff(mo.pos,r.HitPos);
if ( diff.x >= mo.radius ) HitNormal += (1,0,0);
else if ( diff.x <= -mo.radius ) HitNormal += (-1,0,0);
if ( diff.y >= mo.radius ) HitNormal += (0,1,0);
else if ( diff.y <= -mo.radius ) HitNormal += (0,-1,0);
if ( diff.z >= mo.height ) HitNormal += (0,0,1);
else if ( diff.z <= 0. ) HitNormal += (0,0,-1);
double len = HitNormal.length();
if ( len < double.epsilon ) HitNormal = Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90));
else HitNormal /= len;
}
else if ( r.HitType == TRACE_HitFloor )
{
if ( r.ffloor ) HitNormal = -r.ffloor.top.Normal;
else HitNormal = r.HitSector.floorplane.Normal;
}
else if ( r.HitType == TRACE_HitCeiling )
{
if ( r.ffloor ) HitNormal = -r.ffloor.bottom.Normal;
else HitNormal = r.HitSector.ceilingplane.Normal;
}
else if ( r.HitType == TRACE_HitWall )
{
HitNormal = (-r.HitLine.delta.y,r.HitLine.delta.x,0).unit();
if ( !r.Side ) HitNormal *= -1;
}
else HitNormal = -r.HitVector;
return HitNormal;
}
static bool PointInWater( Vector3 p )
{
Sector s = level.PointInSector(p.xy);
if ( s.moreflags&Sector.SECMF_UNDERWATER ) // check underwater sector
return true;
else if ( s.heightsec && (s.heightsec.moreflags&Sector.SECMF_UNDERWATERMASK) ) // check height transfer
{
let hsec = s.heightsec;
double fh = hsec.floorplane.ZAtPoint(p.xy);
if ( p.z <= fh )
return true;
if ( !(hsec.moreflags&Sector.SECMF_FAKEFLOORONLY) && (p.z > hsec.ceilingplane.ZAtPoint(p.xy)) )
return true;
}
else // check 3D floors
{
for ( int i=0; i<s.Get3DFloorCount(); i++ )
{
let ff = s.Get3DFloor(i);
if ( !(ff.flags&F3DFloor.FF_EXISTS) || (ff.flags&F3DFloor.FF_SOLID) || !(ff.flags&F3DFloor.FF_SWIMMABLE) ) continue;
double ff_bottom = ff.bottom.ZAtPoint(p.xy);
double ff_top = ff.top.ZAtPoint(p.xy);
if ( (ff_top <= p.z) || (ff_bottom > p.z) ) continue;
return true;
}
}
return false;
}
static bool, TextureID DefaceTexture( TextureID checkme )
{
String tn = TexMan.GetName(checkme);
// special case: alt texture names in Doom 2 In Spain Only
if ( (tn ~== "MARBFAC2") || (tn ~== "SP_MAR01") )
return true, TexMan.CheckForTexture("defaced_MARBFAC2");
if ( (tn ~== "MARBFAC3") || (tn ~== "SP_MAR02"))
return true, TexMan.CheckForTexture("defaced_MARBFAC3");
if ( (tn ~== "MARBFAC4") || (tn ~== "SP_MAR03") )
return true, TexMan.CheckForTexture("defaced_MARBFAC4");
if ( (tn ~== "MARBFACE") || (tn ~== "SP_MAR04") )
return true, TexMan.CheckForTexture("defaced_MARBFACE");
if ( (tn ~== "ZZWOLF2") )
return true, TexMan.CheckForTexture("defaced_ZZWOLF2");
if ( (tn ~== "ZZWOLF3") )
return true, TexMan.CheckForTexture("defaced_ZZWOLF3");
if ( (tn ~== "ZZWOLF4") )
return true, TexMan.CheckForTexture("defaced_ZZWOLF4");
if ( (tn ~== "ZZWOLF6") )
return true, TexMan.CheckForTexture("defaced_ZZWOLF6");
if ( (tn ~== "ZZWOLF7") )
return true, TexMan.CheckForTexture("defaced_ZZWOLF7");
if ( (tn ~== "ZZWOLF12") )
return true, TexMan.CheckForTexture("defaced_ZZWOLF12");
if ( (tn ~== "ZZWOLF13") )
return true, TexMan.CheckForTexture("defaced_ZZWOLF13");
return false, checkme;
}
// iterate through polyobjects and see if this line is part of one (returning which, if any)
static bool IsPolyLine( Line l, swwm_PolyobjectHandle &o )
{
let pi = swwm_PolyobjectIterator.Create();
swwm_PolyobjectHandle p;
while ( p = pi.Next() )
{
if ( p.Lines.Find(l) >= p.Lines.Size() ) continue;
o = p;
return true;
}
o = null;
return false;
}
// checks if the specified world coordinate is inside the polyobject
// this check is very naive but it should handle most "normal" shapes
// (yeah, sorry if you somehow want to play this mod with lilith.pk3)
static bool PointInPolyobj( Vector2 p, swwm_PolyobjectHandle o )
{
// first pass, find which vertex out of all lines is closest
Vertex v = o.StartLine.v1;
double dist = (v.p-p).length();
foreach ( l:o.Lines )
{
double dist2 = (l.v1.p-p).length();
if ( dist2 < dist )
{
v = l.v1;
dist = dist2;
}
dist2 = (l.v2.p-p).length();
if ( dist2 < dist )
{
v = l.v2;
dist = dist2;
}
}
// second pass, find which two lines share that vertex
// (in theory there should only be two)
Line a = null, b = null;
foreach ( l:o.Lines )
{
if ( (l.v1 == v) || (l.v2 == v) )
{
if ( !a ) a = l;
else if ( !b ) b = l;
else break;
}
}
// is the point behind both lines?
return (Level.PointOnLineSide(p,a) && Level.PointOnLineSide(p,b));
}
static bool SameSpecial( Line a, Line b )
{
if ( a.special != b.special ) return false;
for ( int i=0; i<5; i++ )
{
if ( a.args[i] != b.args[i] )
return false;
}
return true;
}
}