- Try to get rid of all implicit casts from string to name, color or class. - Use FindClass where needed. - Used a map in a case where a dictionary was unneeded. - Use new bounce flags where needed. - Replace Legacy of Rust weapons/ammo.
695 lines
23 KiB
Text
695 lines
23 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, 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;
|
|
}
|
|
}
|