swwmgz_m/zscript/swwm_utility.zsc
Marisa Kirisame 8b7656cef3 Weredragons and Ettins are pettable now.
Befriended monsters activate their death specials, if they have any.
Add colored icons to monster tags to denote bosses and friends.
Minigames will be part of a second free DLC update.
2020-11-07 13:54:21 +01:00

946 lines
27 KiB
Text

// Misc. Utility code
enum EDoExplosionFlags
{
DE_BLAST = 1, // sets BLASTED flag on pushed actors
DE_NOBLEED = 2, // does not spawn blood decals on hit
DE_NOSPLASH = 4, // like XF_NOSPLASH
DE_THRUWALLS = 8, // damages through geometry (no sight check)
DE_NOTMISSILE = 16, // instigator is the source itself (normally it'd be its target pointer)
DE_EXTRAZTHRUST = 32, // applies a higher Z thrust to enemies on ground
};
const FallbackTag = "AWESOME IT'S PENIS"; // used on tag processing, please don't mind the actual string used)
Class SWWMUtility
{
// thanks zscript
static clearscope double fract( double a )
{
return a-floor(a);
}
// not sure if I should use this, looks a bit ugly
static clearscope void ThousandsStr( out String s )
{
String nstr = s;
s.Truncate(0);
int len = nstr.CodePointCount();
int t = len;
if ( nstr.Left(1) == "-" ) t++;
for ( int i=0, pos=0; i<len; i++ )
{
int ch;
[ch, pos] = nstr.GetNextCodePoint(pos);
s.AppendCharacter(ch);
t = (t-1)%3;
if ( (pos < len) && !t )
s.AppendCharacter(0x2C); // comma
}
}
static clearscope String ThousandsNum( int n )
{
String nstr = String.Format("%d",n);
ThousandsStr(nstr);
return nstr;
}
// this can probably be simplified, but I'm lazy
static clearscope Vector3 HSVtoRGB( Vector3 hsv )
{
Vector3 p;
p.x = abs(fract(hsv.x+1.)*6.-3.);
p.y = abs(fract(hsv.x+(2./3.))*6.-3.);
p.z = abs(fract(hsv.x+(1./3.))*6.-3.);
Vector3 p2;
p2.x = (1.-hsv.y)+clamp(p.x-1.,0.,1.)*hsv.y;
p2.y = (1.-hsv.y)+clamp(p.y-1.,0.,1.)*hsv.y;
p2.z = (1.-hsv.y)+clamp(p.z-1.,0.,1.)*hsv.y;
return p2*hsv.z;
}
static clearscope void StripColor( out String str )
{
int len = str.CodePointCount();
for ( int i=0, pos=0; i<len; i++ )
{
int remlen = 0;
int cplen = 0;
int ch, nxt;
[ch, nxt] = str.GetNextCodePoint(pos);
if ( ch != 0x1C )
{
pos = nxt;
continue;
}
remlen++;
cplen++;
[ch, nxt] = str.GetNextCodePoint(pos+remlen);
if ( ch == 0x5B )
{
int ch2;
do
{
[ch2, nxt] = str.GetNextCodePoint(pos+remlen);
remlen += nxt-(pos+remlen);
cplen++;
}
while ( ch2 != 0x5D );
}
remlen++;
str.Remove(pos,remlen);
len -= cplen;
i--;
}
}
static clearscope String SuperscriptNum( int val )
{
// unicode is fun
static const int digs[] = {0x2070,0x00B9,0x00B2,0x00B3,0x2074,0x2075,0x2076,0x2077,0x2078,0x2079};
String str = "";
int digits = int(Log10(val));
for ( int i=digits; i>=0; i-- )
{
int d = int(val/(10**i))%10;
str.AppendCharacter(digs[d]);
}
return str;
}
static clearscope void BeautifyClassName( out String str )
{
String workstr = str;
str.Truncate(0);
workstr.Replace("_"," ");
int len = workstr.CodePointCount();
for ( int i=0, pos=0; i<len; i++ )
{
int cp1;
[cp1, pos] = workstr.GetNextCodePoint(pos);
str.AppendCharacter(cp1);
if ( i < len-1 )
{
int cp2 = workstr.GetNextCodePoint(pos);
// this looks awkward, but I have to also account for non-letter characters
// uppercase after lowercase
if ( (String.CharUpper(cp1) != cp1) && (String.CharLower(cp2) != cp2) )
str.AppendCharacter(0x20);
// uppercase after non-letter
else if ( (String.CharUpper(cp1) == cp1) && (String.CharLower(cp1) == cp1) && (String.CharLower(cp2) != cp2) )
str.AppendCharacter(0x20);
// non-letter after lowercase
else if ( (String.CharUpper(cp1) != cp1) && (String.CharLower(cp2) == cp2) && (String.CharUpper(cp2) == cp2) )
str.AppendCharacter(0x20);
}
}
}
static double PitchTo( Actor a, Actor b, double hfact = 1. )
{
if ( !a || !b ) return 0;
Vector3 thispos = a.player?a.Vec2OffsetZ(0,0,a.player.viewz):a.Vec3Offset(0,0,a.missileheight);
Vector3 otherpos = b.Vec3Offset(0,0,b.height*hfact);
Vector3 diff = level.Vec3Diff(thispos,otherpos);
double dist = diff.length();
if ( dist > 0 ) return -asin(diff.z/dist);
return 0;
}
static clearscope 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;
}
static clearscope bool IsExitLine( Line l )
{
if ( (l.special == Exit_Normal) || (l.special == Exit_Secret) || (l.special == Teleport_EndGame) || (l.special == Teleport_NewMap) )
return true;
return false;
}
// how the fuck is this not available to ZScript?
// copied from P_PointOnLineSidePrecise()
static clearscope int PointOnLineSide( Vector2 p, Line l )
{
if ( !l ) return 0;
return (((p.y-l.v1.p.y)*l.delta.x+(l.v1.p.x-p.x)*l.delta.y) > double.epsilon);
}
// haha another one
// copied from BoxOnLineSide()
static clearscope int BoxOnLineSide( double top, double bottom, double left, double right, Line l )
{
if ( !l ) return 0;
int p1, p2;
if ( l.delta.x == 0 )
{
// ST_VERTICAL:
p1 = (right < l.v1.p.x);
p2 = (left < l.v1.p.x);
if ( l.delta.y < 0 )
{
p1 ^= 1;
p2 ^= 1;
}
}
else if ( l.delta.y == 0 )
{
// ST_HORIZONTAL:
p1 = (top > l.v1.p.y);
p2 = (bottom > l.v1.p.y);
if ( l.delta.x < 0 )
{
p1 ^= 1;
p2 ^= 1;
}
}
else if ( (l.delta.x*l.delta.y) >= 0 )
{
// ST_POSITIVE:
p1 = PointOnLineSide((left,top),l);
p2 = PointOnLineSide((right,bottom),l);
}
else
{
// ST_NEGATIVE:
p1 = PointOnLineSide((right,top),l);
p2 = PointOnLineSide((left,bottom),l);
}
return (p1==p2)?p1:-1;
}
// wrapper
static clearscope int ActorOnLineSide( Actor a, Line l )
{
double box[4];
box[0] = a.pos.y+a.radius;
box[1] = a.pos.y-a.radius;
box[2] = a.pos.x-a.radius;
box[3] = a.pos.x+a.radius;
return BoxOnLineSide(box[0],box[1],box[2],box[3],l);
}
// box intersection check, for collision detection
static clearscope bool BoxIntersect( Actor a, Actor b, Vector3 ofs = (0,0,0), int pad = 0 )
{
Vector3 diff = level.Vec3Diff(level.Vec3Offset(a.pos,ofs),b.pos);
if ( (abs(diff.x) > (a.radius+b.radius+pad)) || (abs(diff.y) > (a.radius+b.radius+pad)) ) return false;
if ( (diff.z > a.height+pad) || (diff.z < -(b.height+pad)) ) return false;
return true;
}
// extruded box intersection check, useful when checking things that might be hit along a path
static clearscope bool ExtrudeIntersect( Actor a, Actor b, Vector3 range, int steps, int pad = 0 )
{
if ( steps <= 0 ) return BoxIntersect(a,b,pad:pad);
double step = 1./steps;
for ( double i=step; i<=1.; i+=step )
{
if ( BoxIntersect(a,b,range*i,pad) )
return true;
}
return false;
}
// sphere intersection check, useful for proximity detection
static clearscope bool SphereIntersect( Actor a, Vector3 p, double radius )
{
Vector3 ap = p+level.Vec3Diff(p,a.pos); // portal-relative actor position
Vector3 amin = ap+(-a.radius,-a.radius,0),
amax = ap+(a.radius,a.radius,a.height);
double distsq = 0.;
if ( p.x < amin.x ) distsq += (amin.x-p.x)**2;
if ( p.x > amax.x ) distsq += (p.x-amax.x)**2;
if ( p.y < amin.y ) distsq += (amin.y-p.y)**2;
if ( p.y > amax.y ) distsq += (p.y-amax.y)**2;
if ( p.z < amin.z ) distsq += (amin.z-p.z)**2;
if ( p.z > amax.z ) distsq += (p.z-amax.z)**2;
return (distsq <= (radius**2));
}
// THANKS FOR NOT GIVING US ANY OTHER WAY TO CHECK IF A LOCK NUMBER IS VALID
static clearscope bool IsValidLockNum( int l )
{
if ( (l < 1) || (l > 255) ) return true;
Array<Int> valid;
valid.Clear();
for ( int i=0; i<Wads.GetNumLumps(); i++ )
{
String lname = Wads.GetLumpName(i);
if ( !(lname ~== "LOCKDEFS") ) continue;
String data = Wads.ReadLump(i);
Array<String> lines;
lines.Clear();
data.Split(lines,"\n");
for ( int j=0; j<lines.Size(); j++ )
{
if ( lines[j].Left(10) ~== "CLEARLOCKS" ) valid.Clear();
else if ( Lines[j].Left(5) ~== "LOCK " )
{
Array<String> spl;
spl.Clear();
lines[j].Split(spl," ",TOK_SKIPEMPTY);
// check game string (if any)
if ( spl.Size() > 2 )
{
if ( (spl[2] ~== "DOOM") && !(gameinfo.gametype&GAME_Doom) ) continue;
else if ( (spl[2] ~== "HERETIC") && !(gameinfo.gametype&GAME_Heretic) ) continue;
else if ( (spl[2] ~== "HEXEN") && !(gameinfo.gametype&GAME_Hexen) ) continue;
else if ( (spl[2] ~== "STRIFE") && !(gameinfo.gametype&GAME_Strife) ) continue;
else if ( (spl[2] ~== "CHEX") && !(gameinfo.gametype&GAME_Chex) ) continue;
}
valid.Push(spl[1].ToInt());
}
}
}
for ( int i=0; i<valid.Size(); i++ )
{
if ( valid[i] == l ) return true;
}
return false;
}
// wheeeeeeee, let's play a game of "who is who"
static clearscope bool IsCivilian( Actor a )
{
if ( a is 'Beggar' )
{
if ( (a.level.mapname ~== "MAP32") && (a is 'Beggar1') )
return false; // Prisoner (sorry but we have to)
return true;
}
if ( a is 'Peasant' )
{
// exclude certain key NPCs
if ( (a.level.mapname ~== "MAP01") && (a is 'Peasant9') )
return false; // Beldin (sorry but we have to)
if ( (a.level.mapname ~== "MAP02") && (a is 'Peasant22') )
return false; // Mourel (fuck that guy)
if ( (a.level.mapname ~== "MAP02") && (a is 'Peasant4') )
return false; // Harris (also fuck that guy)
if ( (a.level.mapname ~== "MAP04") && (a is 'Peasant5') )
return false; // Derwin (fat bastard)
if ( (a.level.mapname ~== "MAP04") && (a is 'Peasant7') )
return false; // Ketrick (THIS IS GARBAGE)
if ( (a.level.mapname ~== "MAP05") && (a is 'Peasant7') )
return false; // Montag (gimme the damn key)
if ( (a.level.mapname ~== "MAP05") && (a is 'Peasant8') )
return false; // Wolenick (gimme a hand)
if ( (a.level.mapname ~== "MAP33") && (a is 'Peasant5') )
return false; // Harris (also fuck that guy)
return true;
}
return false;
}
// Thanks to ZZYZX and Nash
static play void SetToSlope( Actor a, double dang, bool ceil = false )
{
Sector sect;
Vector3 fnormal;
if ( ceil )
{
sect = a.CeilingSector;
fnormal = -sect.ceilingplane.Normal;
}
else
{
sect = a.FloorSector;
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 ) fnormal = ff.bottom.Normal;
else fnormal = -ff.top.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 clearscope int Round100( double x )
{
return int(ceil(x/100.)*100.);
}
// can't make clearscope because of SectorEffect.GetSector(), isn't that amazing?
static play 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;
}
}
// easy mode: check if it's already moving
if ( part )
{
let ti = ThinkerIterator.Create("MovingCeiling",Thinker.STAT_SECTOREFFECT);
MovingCeiling mc;
while ( mc = MovingCeiling(ti.Next()) )
if ( mc.GetSector() == s )
return true;
}
else
{
let ti = ThinkerIterator.Create("MovingFloor",Thinker.STAT_SECTOREFFECT);
MovingFloor mf;
while ( mf = MovingFloor(ti.Next()) )
if ( mf.GetSector() == s )
return true;
}
// hard mode: look for all lines/actors with movement specials referencing us
for ( int i=0; i<level.Lines.Size(); i++ )
{
Line l = level.Lines[i];
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;
}
}
let 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;
}
// because GetTag() returns the localized string, we need to do things the hard way
static clearscope String GetFunTag( Actor a, String defstr = "" )
{
if ( a is 'SWWMMonster' ) return SWWMMonster(a).GetFunTag(defstr);
int ntags = 1;
String basetag = "";
switch ( a.GetClassName() )
{
// Doom
case 'ZombieMan':
case 'StealthZombieMan':
basetag = "ZOMBIE";
break;
case 'ShotgunGuy':
case 'StealthShotgunGuy':
basetag = "SHOTGUN";
break;
case 'ChaingunGuy':
case 'StealthChaingunGuy':
basetag = "HEAVY";
break;
case 'DoomImp':
case 'StealthDoomImp':
basetag = "IMP";
break;
case 'Demon':
case 'StealthDemon':
basetag = "DEMON";
break;
case 'Spectre':
basetag = "SPECTRE";
break;
case 'LostSoul':
basetag = "LOST";
break;
case 'Cacodemon':
case 'StealthCacodemon':
basetag = "CACO";
break;
case 'HellKnight':
case 'StealthHellKnight':
basetag = "HELL";
break;
case 'BaronOfHell':
case 'StealthBaron':
basetag = "BARON";
break;
case 'Arachnotron':
case 'StealthArachnotron':
basetag = "ARACH";
break;
case 'PainElemental':
basetag = "PAIN";
break;
case 'Revenant':
case 'StealthRevenant':
basetag = "REVEN";
break;
case 'Fatso':
case 'StealthFatso':
basetag = "MANCU";
break;
case 'Archvile':
case 'StealthArchvile':
basetag = "ARCH";
break;
case 'SpiderMastermind':
basetag = "SPIDER";
break;
case 'Cyberdemon':
basetag = "CYBER";
break;
case 'BossBrain':
basetag = "BOSSBRAIN";
break;
case 'WolfensteinSS':
basetag = "WOLFSS";
break;
case 'CommanderKeen':
case 'SWWMHangingKeen':
basetag = "KEEN";
break;
case 'MBFHelperDog':
basetag = "DOG";
break;
// Heretic
case 'Chicken':
basetag = "CHICKEN";
break;
case 'Beast':
basetag = "BEAST";
break;
case 'Clink':
basetag = "CLINK";
break;
case 'Sorcerer1':
case 'Sorcerer2':
basetag = "DSPARIL";
break;
case 'HereticImp':
case 'HereticImpLeader':
basetag = "HERETICIMP";
break;
case 'Ironlich':
basetag = "IRONLICH";
break;
case 'Knight':
case 'KnightGhost':
basetag = "BONEKNIGHT";
break;
case 'Minotaur':
case 'MinotaurFriend':
basetag = "MINOTAUR";
break;
case 'Mummy':
case 'MummyGhost':
basetag = "MUMMY";
break;
case 'MummyLeader':
case 'MummyLeaderGhost':
basetag = "MUMMYLEADER";
break;
case 'Snake':
basetag = "SNAKE";
break;
case 'Wizard':
basetag = "WIZARD";
break;
// Hexen
case 'FireDemon':
basetag = "FIREDEMON";
break;
case 'Demon1':
case 'Demon1Mash':
case 'Demon2':
case 'Demon2Mash':
basetag = "DEMON1";
break;
case 'Ettin':
case 'EttinMash':
basetag = "ETTIN";
break;
case 'Centaur':
case 'CentaurMash':
basetag = "CENTAUR";
break;
case 'CentaurLeader':
basetag = "SLAUGHTAUR";
break;
case 'Bishop':
basetag = "BISHOP";
break;
case 'IceGuy':
basetag = "ICEGUY";
break;
case 'Serpent':
case 'SerpentLeader':
basetag = "SERPENT";
break;
case 'Wraith':
case 'WraithBuried':
basetag = "WRAITH";
break;
case 'Dragon':
basetag = "DRAGON";
break;
case 'Korax':
basetag = "KORAX";
break;
case 'FighterBoss':
basetag = "FBOSS";
break;
case 'MageBoss':
basetag = "MBOSS";
break;
case 'ClericBoss':
basetag = "CBOSS";
break;
case 'Heresiarch':
basetag = "HERESIARCH";
break;
}
if ( basetag == "" ) return a.GetTag(defstr);
String funtag = "FN_"..basetag.."_FUN";
String lfuntag = StringTable.Localize(funtag,false);
if ( lfuntag != funtag ) return lfuntag;
String nfuntag = "FN_"..basetag.."_FUNN";
String lnfuntag = StringTable.Localize(nfuntag,false);
if ( lnfuntag == nfuntag ) return a.GetTag(defstr);
ntags = lnfuntag.ToInt();
return StringTable.Localize(String.Format("$FN_%s_FUN%d",basetag,Random[FunTags](1,ntags)));
}
// Apply full 3D knockback in a specific direction, useful for hitscan
static play void DoKnockback( Actor Victim, Vector3 HitDirection, double MomentumTransfer )
{
if ( !Victim )
return;
if ( Victim.bDORMANT ) // no dormant knockback
return;
if ( !Victim.bSHOOTABLE && !Victim.bVULNERABLE )
return;
if ( Victim.bDONTTHRUST || (Victim.Mass >= Actor.LARGE_MASS) )
return;
Vector3 Momentum = HitDirection*MomentumTransfer;
if ( (Victim.pos.z <= Victim.floorz) || !Victim.TestMobjZ() )
Momentum.z = max(Momentum.z,.1*Momentum.length());
Momentum /= Thinker.TICRATE*max(50,Victim.Mass);
Victim.vel += Momentum;
}
// complete spherical and more accurate replacement of A_Explode
// 100% free of the buggery GZDoom's own splash damage has
// returns the number of shootables hit/killed
static play int, int DoExplosion( Actor Source, double Damage, double MomentumTransfer, double ExplosionRadius, double FullDamageRadius = 0., int flags = 0, Name DamageType = '', Actor ignoreme = null )
{
// debug, display radius sphere
if ( swwm_debugblast )
{
let s = Actor.Spawn("RadiusDebugSphere",Source.pos);
s.Scale *= ExplosionRadius;
s.SetShade((Damage>0)?"Green":"Blue");
if ( FullDamageRadius > 0. )
{
let s = Actor.Spawn("RadiusDebugSphere",Source.pos);
s.Scale *= FullDamageRadius;
s.SetShade("Red");
}
}
if ( !(flags&DE_NOSPLASH) ) Source.CheckSplash(ExplosionRadius);
double brange = 1./(ExplosionRadius-FullDamageRadius);
Actor Instigator = (flags&DE_NOTMISSILE)?Source:Source.target;
BlockThingsIterator bi = BlockThingsIterator.Create(Source,ExplosionRadius*2); // test with doubled radius, just to be sure
int nhit = 0, nkill = 0;
while ( bi.Next() )
{
Actor a = bi.Thing;
// early checks for self and ignored actor (usually the instigator)
if ( !a || (a == ignoreme) || (a == Source) )
continue;
// can't be affected
if ( !a.bSHOOTABLE && !a.bVULNERABLE )
continue;
// no blasting if no radius dmg (unless forced)
if ( a.bNORADIUSDMG && !Source.bFORCERADIUSDMG )
continue;
// check the DONTHARMCLASS/DONTHARMSPECIES flags
if ( !a.player && ((Source.bDONTHARMCLASS && (a.GetClass() == Source.GetClass())) || (Source.bDONTHARMSPECIES && (a.GetSpecies() == Source.GetSpecies()))) )
continue;
// can we see it
if ( !(flags&DE_THRUWALLS) && !Source.CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) )
continue;
// intersecting?
if ( !SWWMUtility.SphereIntersect(a,Source.pos,ExplosionRadius) )
continue;
// calculate factor
Vector3 dir = level.Vec3Diff(Source.pos,a.Vec3Offset(0,0,a.Height/2));
double dist = dir.length();
// intersecting, randomize direction
if ( dir.length() <= double.epsilon )
{
double ang = FRandom[DoBlast](0,360);
double pt = FRandom[DoBlast](-90,90);
dir = (cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt));
}
dir /= dist;
dist = clamp(dist-FullDamageRadius,0,min(dist,ExplosionRadius));
double damagescale = 1.-clamp((dist-a.Radius)*brange,0.,1.);
double mm = MomentumTransfer*damagescale;
// no knockback if massive/unpushable
if ( (abs(mm) > 0.) && !a.bDORMANT && !a.bDONTTHRUST && (a.Mass < Actor.LARGE_MASS) )
{
Vector3 Momentum = dir*mm;
if ( (a.pos.z <= a.floorz) || !a.TestMobjZ() )
Momentum.z = max(Momentum.z,(flags&DE_EXTRAZTHRUST?.4:.1)*Momentum.length());
Momentum /= Thinker.TICRATE*max(50,a.Mass); // prevent tiny things from getting yeeted at warp speed
a.vel += Momentum;
if ( (flags&DE_BLAST) && a.bCANBLAST && !a.bDONTBLAST ) a.bBLASTED = true;
}
// hit it
nhit++;
int dmg = int(Damage*damagescale);
if ( dmg <= 0 ) continue; // no harm
int ndmg = a.DamageMobj(Source,Instigator,dmg,(DamageType=='')?Source.DamageType:DamageType,DMG_EXPLOSION,atan2(-dir.y,-dir.x));
if ( a && !(flags&DE_NOBLEED) ) a.TraceBleed((ndmg>0)?ndmg:dmg,Source);
if ( !a || (a.Health <= 0) ) nkill++;
}
return nhit, nkill;
}
static play bool InPlayerFOV( PlayerInfo p, Actor a, double maxdist = 0. )
{
double vfov = CVar.GetCVar('fov',p).GetFloat()*0.5;
double hfov = atan(Screen.GetAspectRatio()*tan(vfov));
let mo = p.camera;
Vector3 pp;
if ( !mo.CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) return false;
if ( mo is 'PlayerPawn' ) pp = mo.Vec2OffsetZ(0,0,PlayerPawn(mo).player.viewz);
else pp = mo.Vec3Offset(0,0,mo.CameraHeight);
Vector3 sc = level.SphericalCoords(pp,a.pos,(mo.angle,mo.pitch));
if ( (abs(sc.x) > hfov) && (abs(sc.y) > vfov) ) return false;
if ( (maxdist > 0.) && (sc.z < maxdist) ) return false;
return true;
}
static clearscope bool CheatsDisabled( int p = -1 )
{
if ( cl_blockcheats || ((G_SkillPropertyInt(SKILLP_DisableCheats) || netgame || deathmatch) && !sv_cheats) )
{
if ( (p != -1) && (p == consoleplayer) )
{
Console.Printf("\cxSORRY NOTHING\c-");
S_StartSound("misc/trombone",CHAN_VOICE,CHANF_UI);
}
return true;
}
return false;
}
static clearscope bool IdentifyingDog( Actor a )
{
if ( a is 'MBFHelperDog' ) return true;
// more dogs will be added as found
// because all dogs must be pet
return false;
}
static clearscope bool IdentifyingCaco( Actor a )
{
if ( a is 'DeadCacodemon' ) return false;
if ( a is 'Cacodemon' ) return true;
if ( a.Species == 'RLCacodemon' ) return true; // DRLA
if ( a.Species == 'Caco' ) return true; // CH
return false;
}
// Друг
static clearscope bool IdentifyingDrug( Actor a )
{
if ( a is 'Beast' ) return true;
return false;
}
static clearscope bool IdentifyingDoubleBoi( Actor a )
{
if ( a is 'Ettin' ) return true;
return false;
}
}
Class RadiusDebugSphere : Actor
{
Default
{
RenderStyle "AddStencil";
StencilColor "White";
Radius .1;
Height 0.;
+NOGRAVITY;
+NOINTERACTION;
}
States
{
Spawn:
XZW1 A 1 BRIGHT A_FadeOut();
Wait;
}
}
Class ShinemapDebugSphere : Actor
{
override bool Used( Actor user )
{
if ( CurState.NextState )
SetState(CurState.NextState);
else SetState(SpawnState);
return true;
}
override void Tick() {}
Default
{
RenderStyle "Add";
Radius 16;
Height 48;
}
States
{
Spawn:
XZW1 A -1 Bright NoDelay A_SetRenderStyle(1.,STYLE_Add);
XZW1 B -1 A_SetRenderStyle(1.,STYLE_Normal);
XZW1 C -1 Bright A_SetRenderStyle(1.,STYLE_Add);
XZW1 D -1 Bright A_SetRenderStyle(1.,STYLE_Normal);
XZW1 E -1 A_SetRenderStyle(1.,STYLE_Add);
XZW1 F -1 A_SetRenderStyle(1.,STYLE_Normal);
XZW1 G -1 A_SetRenderStyle(1.,STYLE_Normal);
XZW1 H -1 A_SetRenderStyle(1.,STYLE_Normal);
XZW1 I -1 A_SetRenderStyle(1.,STYLE_Normal);
XZW1 J -1 A_SetRenderStyle(1.,STYLE_Normal);
XZW1 K -1 A_SetRenderStyle(1.,STYLE_Normal);
XZW1 L -1 A_SetRenderStyle(1.,STYLE_Normal);
XZW1 M -1 A_SetRenderStyle(1.,STYLE_Normal);
XZW1 N -1 A_SetRenderStyle(1.,STYLE_Normal);
Loop;
}
}