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.
724 lines
24 KiB
Text
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;
|
|
}
|
|
}
|