4519 lines
130 KiB
Text
4519 lines
130 KiB
Text
// common code goes here
|
|
enum ESWWMGZChannels
|
|
{
|
|
CHAN_YOUDONEFUCKEDUP = 63200, // exception handler
|
|
CHAN_DEMOVOICE = 63201, // demolitionist voices
|
|
CHAN_FOOTSTEP = 63202, // footstep sounds and others
|
|
CHAN_WEAPONEXTRA = 63203, // additional weapon sounds (usually loops)
|
|
CHAN_POWERUP = 63204, // powerup sounds
|
|
CHAN_POWERUPEXTRA = 63205, // additional powerup sounds
|
|
CHAN_JETPACK = 63206, // jetpack sound
|
|
CHAN_ITEMEXTRA = 63207, // additional item sounds
|
|
CHAN_WEAPONEXTRA2 = 63208, // additional weapon sound slot
|
|
CHAN_WEAPONEXTRA3 = 63209, // additional weapon sound slot (again)
|
|
CHAN_DAMAGE = 63210, // used for impact/hit sounds
|
|
CHAN_AMBEXTRA = 63211 // player ambience when submerged
|
|
};
|
|
|
|
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
|
|
};
|
|
|
|
// Misc. Utility code
|
|
Class SWWMUtility
|
|
{
|
|
static void StripColor( out String str )
|
|
{
|
|
int len = str.CodePointCount();
|
|
for ( int i=0; i<len; i++ )
|
|
{
|
|
int remlen = 0;
|
|
if ( str.GetNextCodePoint(i) != 0x1C )
|
|
continue;
|
|
remlen++;
|
|
if ( str.GetNextCodePoint(i+remlen) == 0x5B )
|
|
while ( str.GetNextCodePoint(i+remlen) != 0x5D )
|
|
remlen++;
|
|
remlen++;
|
|
str.Remove(i,remlen);
|
|
len -= remlen;
|
|
}
|
|
}
|
|
|
|
static void BeautifyClassName( out String str )
|
|
{
|
|
String workstr = str;
|
|
str.Truncate(0);
|
|
workstr.Replace("_"," ");
|
|
int len = workstr.CodePointCount();
|
|
for ( int i=0; i<len; i++ )
|
|
{
|
|
int cp1 = workstr.GetNextCodePoint(i);
|
|
str.AppendCharacter(cp1);
|
|
if ( i < len-1 )
|
|
{
|
|
int cp2 = workstr.GetNextCodePoint(i+1);
|
|
if ( (String.CharLower(cp1) == cp1) && (String.CharUpper(cp2) == cp2) )
|
|
str.AppendCharacter(0x20);
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static 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 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);
|
|
}
|
|
|
|
// box intersection check, for collision detection
|
|
static 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 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 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 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 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 )
|
|
{
|
|
vector3 fnormal = a.CurSector.floorplane.Normal;
|
|
// find closest 3d floor for its normal
|
|
F3DFloor ff;
|
|
for ( int i=0; i<a.CurSector.Get3DFloorCount(); i++ )
|
|
{
|
|
if ( !(a.CurSector.Get3DFloor(i).top.ZAtPoint(a.pos.xy) ~== a.floorz) ) continue;
|
|
ff = a.CurSector.Get3DFloor(i);
|
|
break;
|
|
}
|
|
if ( ff ) 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.);
|
|
}
|
|
|
|
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':
|
|
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)));
|
|
}
|
|
}
|
|
|
|
// Future planning, will be filled out with AI stuff and whatnot someday
|
|
Class SWWMMonster : Actor
|
|
{
|
|
virtual clearscope String GetFunTag( String defstr = "" )
|
|
{
|
|
return GetTag(defstr);
|
|
}
|
|
}
|
|
|
|
// Stats
|
|
Class WeaponUsage
|
|
{
|
|
Class<Weapon> w;
|
|
int kills;
|
|
}
|
|
|
|
Class MonsterKill
|
|
{
|
|
Class<Actor> m;
|
|
int kills;
|
|
}
|
|
|
|
Class LevelStat
|
|
{
|
|
bool hub;
|
|
String levelname, mapname;
|
|
int kcount, ktotal;
|
|
int icount, itotal;
|
|
int scount, stotal;
|
|
int time, par;
|
|
}
|
|
|
|
Class SWWMStats : Thinker
|
|
{
|
|
PlayerInfo myplayer;
|
|
int lastspawn, dashcount, boostcount, stompcount, airtime, kills,
|
|
deaths, damagedealt, hdamagedealt, damagetaken, hdamagetaken,
|
|
mkill, hiscore, hhiscore, topdealt, toptaken, skill, wponch,
|
|
busts;
|
|
double grounddist, airdist, swimdist, fuelusage, topspeed;
|
|
Array<WeaponUsage> wstats;
|
|
Array<MonsterKill> mstats;
|
|
Array<LevelStat> lstats;
|
|
Array<Class<Weapon> > alreadygot;
|
|
int favweapon;
|
|
|
|
bool GotWeapon( Class<Weapon> which )
|
|
{
|
|
for ( int i=0; i<alreadygot.Size(); i++ )
|
|
{
|
|
if ( alreadygot[i] == which ) return true;
|
|
}
|
|
alreadygot.Push(which);
|
|
return false;
|
|
}
|
|
|
|
void AddDamageDealt( int dmg )
|
|
{
|
|
int upper = dmg/1000000000;
|
|
int lower = dmg%1000000000;
|
|
if ( hdamagedealt+upper > 999999999 ) hdamagedealt = 999999999;
|
|
else hdamagedealt += upper;
|
|
damagedealt += lower;
|
|
if ( damagedealt > 999999999 )
|
|
{
|
|
upper = damagedealt/1000000000;
|
|
lower = damagedealt%1000000000;
|
|
if ( hdamagedealt+upper > 999999999 ) hdamagedealt = 999999999;
|
|
else hdamagedealt += upper;
|
|
damagedealt = lower;
|
|
}
|
|
}
|
|
void AddDamageTaken( int dmg )
|
|
{
|
|
int upper = dmg/1000000000;
|
|
int lower = dmg%1000000000;
|
|
if ( hdamagetaken+upper > 999999999 ) hdamagetaken = 999999999;
|
|
else hdamagetaken += upper;
|
|
damagetaken += lower;
|
|
if ( damagetaken > 999999999 )
|
|
{
|
|
upper = damagetaken/1000000000;
|
|
lower = damagetaken%1000000000;
|
|
if ( hdamagetaken+upper > 999999999 ) hdamagetaken = 999999999;
|
|
else hdamagetaken += upper;
|
|
damagetaken = lower;
|
|
}
|
|
}
|
|
|
|
void AddLevelStats()
|
|
{
|
|
let ls = new("LevelStat");
|
|
ls.hub = !!(level.clusterflags&level.CLUSTER_HUB);
|
|
ls.levelname = level.levelname;
|
|
ls.mapname = level.mapname;
|
|
ls.kcount = level.killed_monsters;
|
|
ls.ktotal = level.total_monsters;
|
|
ls.icount = level.found_items;
|
|
ls.itotal = level.total_items;
|
|
ls.scount = level.found_secrets;
|
|
ls.stotal = level.total_secrets;
|
|
ls.time = level.maptime;
|
|
ls.par = level.partime;
|
|
lstats.Push(ls);
|
|
}
|
|
|
|
void AddWeaponKill( Actor inflictor, Actor victim )
|
|
{
|
|
if ( victim )
|
|
{
|
|
bool found = false;
|
|
for ( int i=0; i<mstats.Size(); i++ )
|
|
{
|
|
if ( mstats[i].m != victim.GetClass() ) continue;
|
|
found = true;
|
|
mstats[i].kills++;
|
|
break;
|
|
}
|
|
if ( !found )
|
|
{
|
|
let ms = new("MonsterKill");
|
|
ms.m = victim.GetClass();
|
|
ms.kills = 1;
|
|
mstats.Push(ms);
|
|
}
|
|
}
|
|
Class<Weapon> which = myplayer.ReadyWeapon?myplayer.ReadyWeapon.GetClass():null;
|
|
if ( inflictor is 'Weapon' ) which = Weapon(inflictor).GetClass();
|
|
// properly credit some projectiles to their respective gun
|
|
if ( inflictor is 'AirBullet' ) which = 'DeepImpact';
|
|
else if ( inflictor is 'PusherProjectile' ) which = 'PusherWeapon';
|
|
else if ( (inflictor is 'ExplodiumMagArm') || (inflictor is 'ExplodiumMagProj') || (inflictor is 'ExplodiumBulletImpact') ) which = 'ExplodiumGun';
|
|
else if ( (inflictor is 'DragonBreathArm') || ((inflictor is 'SaltImpact') && !inflictor.Args[0]) || ((inflictor is 'SaltBeam') && !inflictor.Args[1]) || (inflictor is 'OnFire') || (inflictor is 'FlamingChunk') || ((inflictor is 'TheBall') && !inflictor.special1) || (inflictor is 'GoldenImpact') || (inflictor is 'GoldenSubImpact') || (inflictor is 'GoldenSubSubImpact') ) which = 'Spreadgun';
|
|
else if ( ((inflictor is 'SaltImpact') && inflictor.Args[0]) || ((inflictor is 'SaltBeam') && inflictor.Args[1]) || ((inflictor is 'TheBall') && inflictor.special1) ) which = 'Wallbuster';
|
|
else if ( (inflictor is 'EvisceratorChunk') || (inflictor is 'EvisceratorProj') ) which = 'Eviscerator';
|
|
else if ( (inflictor is 'HellblazerRavagerArm') || (inflictor is 'HellblazerWarheadArm') ) which = 'Hellblazer';
|
|
else if ( (inflictor is 'BigBiospark') || (inflictor is 'BiosparkBall') || (inflictor is 'BiosparkBeamImpact') || (inflictor is 'BiosparkComboImpact') || (inflictor is 'BiosparkComboImpactSub') || (inflictor is 'BiosparkBeam') || (inflictor is 'BiosparkArc') ) which = 'Sparkster';
|
|
else if ( (inflictor is 'CandyBeam') || (inflictor is 'CandyPop') || (inflictor is 'CandyMagArm') || (inflictor is 'CandyGunProj') || (inflictor is 'CandyMagProj') || (inflictor is 'CandyBulletImpact') ) which = 'CandyGun';
|
|
else if ( (inflictor is 'YnykronBeam') || (inflictor is 'YnykronImpact') ) which = 'Ynykron';
|
|
else if ( (inflictor is 'Demolitionist') || (inflictor is 'DemolitionistShockwave') || (inflictor is 'DemolitionistRadiusShockwave') ) which = 'SWWMWeapon'; // hack to assume Demolitionist as weapon
|
|
if ( !which ) return;
|
|
for ( int i=0; i<wstats.Size(); i++ )
|
|
{
|
|
if ( wstats[i].w != which ) continue;
|
|
wstats[i].kills++;
|
|
if ( (favweapon == -1) || (wstats[favweapon].kills < wstats[i].kills) ) favweapon = i;
|
|
return;
|
|
}
|
|
let ws = new("WeaponUsage");
|
|
ws.w = which;
|
|
ws.kills = 1;
|
|
wstats.Push(ws);
|
|
if ( (favweapon == -1) || (wstats[favweapon].kills < ws.kills) )
|
|
favweapon = wstats.Size()-1;
|
|
}
|
|
|
|
static clearscope SWWMStats Find( PlayerInfo p )
|
|
{
|
|
let ti = ThinkerIterator.Create("SWWMStats",STAT_STATIC);
|
|
SWWMStats t;
|
|
while ( t = SWWMStats(ti.Next()) )
|
|
{
|
|
if ( t.myplayer != p ) continue;
|
|
return t;
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Scoring
|
|
Class SWWMCredits : Thinker
|
|
{
|
|
PlayerInfo myplayer;
|
|
int credits, hcredits;
|
|
|
|
static void Give( PlayerInfo p, int amount, int hamount = 0 )
|
|
{
|
|
let c = Find(p);
|
|
if ( !c ) return;
|
|
if ( c.credits+amount < c.credits ) c.credits = int.max;
|
|
else c.credits += amount;
|
|
while ( c.credits > 999999999 )
|
|
{
|
|
c.credits -= 1000000000;
|
|
c.hcredits++;
|
|
}
|
|
if ( (c.hcredits+hamount < c.hcredits) || (c.hcredits+hamount > 999999999) ) c.hcredits = 999999999;
|
|
else c.hcredits += hamount;
|
|
let s = SWWMStats.Find(p);
|
|
if ( s && ((c.hcredits > s.hhiscore) || ((c.credits > s.hiscore) && (c.hcredits >= s.hhiscore))) )
|
|
{
|
|
s.hiscore = c.credits;
|
|
s.hhiscore = c.hcredits;
|
|
}
|
|
}
|
|
|
|
static clearscope bool CanTake( PlayerInfo p, int amount, int hamount = 0 )
|
|
{
|
|
let c = Find(p);
|
|
if ( !c ) return false;
|
|
int req = amount, hreq = hamount;
|
|
while ( req > 999999999 )
|
|
{
|
|
req -= 1000000000;
|
|
hreq++;
|
|
}
|
|
// waaaaay too much
|
|
if ( (c.hcredits-hreq < 0) || (c.hcredits-hreq > c.hcredits) ) return false;
|
|
// too much!
|
|
if ( ((c.credits-amount < 0) || (c.credits-amount > c.credits)) && (c.hcredits-hreq <= 0) ) return false;
|
|
return true;
|
|
}
|
|
|
|
static bool Take( PlayerInfo p, int amount, int hamount = 0 )
|
|
{
|
|
let c = Find(p);
|
|
if ( !c ) return false;
|
|
int req = amount, hreq = hamount;
|
|
while ( req > 999999999 )
|
|
{
|
|
req -= 1000000000;
|
|
hreq++;
|
|
}
|
|
// waaaaay too much
|
|
if ( (c.hcredits-hreq < 0) || (c.hcredits-hreq > c.hcredits) ) return false;
|
|
// too much!
|
|
if ( ((c.credits-amount < 0) || (c.credits-amount > c.credits)) && (c.hcredits-hreq <= 0) ) return false;
|
|
c.hcredits -= hreq;
|
|
c.credits -= req;
|
|
while ( c.credits < 0 )
|
|
{
|
|
c.credits += 1000000000;
|
|
c.hcredits--;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static clearscope int, int Get( PlayerInfo p )
|
|
{
|
|
let c = Find(p);
|
|
if ( !c ) return 0;
|
|
return c.credits, c.hcredits;
|
|
}
|
|
|
|
static clearscope SWWMCredits Find( PlayerInfo p )
|
|
{
|
|
let ti = ThinkerIterator.Create("SWWMCredits",STAT_STATIC);
|
|
SWWMCredits t;
|
|
while ( t = SWWMCredits(ti.Next()) )
|
|
{
|
|
if ( t.myplayer != p ) continue;
|
|
return t;
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Trading history between players
|
|
Class SWWMTrade
|
|
{
|
|
int timestamp, type, amt;
|
|
String other;
|
|
Class<Inventory> what;
|
|
}
|
|
|
|
Class SWWMTradeHistory : Thinker
|
|
{
|
|
PlayerInfo myplayer;
|
|
Array<SWWMTrade> ent;
|
|
|
|
static void RegisterSend( PlayerInfo p, PlayerInfo other, Class<Inventory> what, int amt )
|
|
{
|
|
let th = Find(p);
|
|
if ( !th ) return;
|
|
SWWMTrade t = new("SWWMTrade");
|
|
t.timestamp = level.totaltime;
|
|
t.type = 0;
|
|
t.other = other.GetUserName();
|
|
t.what = what;
|
|
t.amt = amt;
|
|
th.ent.Push(t);
|
|
}
|
|
static void RegisterReceive( PlayerInfo p, PlayerInfo other, Class<Inventory> what, int amt )
|
|
{
|
|
let th = Find(p);
|
|
if ( !th ) return;
|
|
SWWMTrade t = new("SWWMTrade");
|
|
t.timestamp = level.totaltime;
|
|
t.type = 1;
|
|
t.other = other.GetUserName();
|
|
t.what = what;
|
|
t.amt = amt;
|
|
th.ent.Push(t);
|
|
}
|
|
|
|
static clearscope SWWMTradeHistory Find( PlayerInfo p )
|
|
{
|
|
let ti = ThinkerIterator.Create("SWWMTradeHistory",STAT_STATIC);
|
|
SWWMTradeHistory th;
|
|
while ( th = SWWMTradeHistory(ti.Next()) )
|
|
{
|
|
if ( th.myplayer != p ) continue;
|
|
return th;
|
|
}
|
|
return Null;
|
|
}
|
|
}
|
|
|
|
// Lore holder
|
|
enum ELoreTab
|
|
{
|
|
LORE_ITEM,
|
|
LORE_PEOPLE,
|
|
LORE_LORE // lol
|
|
};
|
|
|
|
Class SWWMLore
|
|
{
|
|
String tag, text, assoc;
|
|
int tab;
|
|
bool read;
|
|
}
|
|
|
|
// so apparently on restart after dying, static thinkers don't get reloaded until AFTER the player has spawned
|
|
// and the player calls GiveDefaultInventory even though it's all later discarded and they're given the saved inventory
|
|
// all I can ask is: WHY
|
|
Class SWWMExcuseMeWhatTheFuckHandler : StaticEventHandler
|
|
{
|
|
bool reborn_hackfix;
|
|
int clear_hackfix;
|
|
|
|
override void WorldTick()
|
|
{
|
|
if ( gamestate != GS_LEVEL ) return;
|
|
if ( clear_hackfix > 0 )
|
|
{
|
|
clear_hackfix--;
|
|
if ( clear_hackfix <= 0 )
|
|
reborn_hackfix = false;
|
|
}
|
|
}
|
|
|
|
static bool WhyTheFuckIsThisAThing()
|
|
{
|
|
let hnd = SWWMExcuseMeWhatTheFuckHandler(StaticEventHandler.Find("SWWMExcuseMeWhatTheFuckHandler"));
|
|
if ( !hnd ) return false;
|
|
return hnd.reborn_hackfix;
|
|
}
|
|
|
|
static void ThisIsSomeSeriousBullshit()
|
|
{
|
|
let hnd = SWWMExcuseMeWhatTheFuckHandler(StaticEventHandler.Find("SWWMExcuseMeWhatTheFuckHandler"));
|
|
if ( !hnd ) return;
|
|
hnd.reborn_hackfix = true;
|
|
hnd.clear_hackfix = 2;
|
|
}
|
|
}
|
|
|
|
Class SWWMLoreLibrary : Thinker
|
|
{
|
|
PlayerInfo myplayer;
|
|
Array<SWWMLore> ent;
|
|
int lastaddtic;
|
|
|
|
bool DirectAdd( String ref )
|
|
{
|
|
// restrictions
|
|
if ( !(gameinfo.gametype&(GAME_Raven|GAME_Strife)) )
|
|
{
|
|
if ( ref ~== "Parthoris" ) return true;
|
|
if ( ref ~== "Sidhe" ) return true;
|
|
if ( ref ~== "SerpentRiders" ) return true;
|
|
}
|
|
if ( !(gameinfo.gametype&(GAME_Hexen|GAME_Strife)) )
|
|
{
|
|
if ( ref ~== "Cronos" ) return true;
|
|
if ( ref ~== "Kirin" ) return true; // not met
|
|
if ( ref ~== "AmmoFabricator" ) return true; // not yet introduced
|
|
}
|
|
if ( !(gameinfo.gametype&GAME_Strife) )
|
|
{
|
|
if ( ref ~== "TheOrder" ) return true;
|
|
if ( ref ~== "TheFront" ) return true;
|
|
}
|
|
ref = ref.MakeUpper();
|
|
String tag = String.Format("SWWM_LORETAG_%s",ref);
|
|
String tab = String.Format("SWWM_LORETAB_%s",ref);
|
|
String text = String.Format("SWWM_LORETXT_%s",ref);
|
|
String assoc = String.Format("SWWM_LOREREL_%s",ref);
|
|
// redirects
|
|
if ( gameinfo.gametype&GAME_Strife )
|
|
{
|
|
}
|
|
if ( gameinfo.gametype&(GAME_Hexen|GAME_Strife) )
|
|
{
|
|
if ( text ~== "SWWM_LORETXT_SAYA" )
|
|
text = "SWWM_LORETXT_SAYA3"; // married kirin
|
|
else if ( text ~== "SWWM_LORETXT_ANARUKON" )
|
|
text = "SWWM_LORETXT_ANARUKON2"; // comments from miyamoto-xanai wedding
|
|
else if ( text ~== "SWWM_LORETXT_HELL" )
|
|
text = "SWWM_LORETXT_HELL3"; // met father nostros during the wedding
|
|
else if ( text ~== "SWWM_LORETXT_GHOULHUNT" )
|
|
text = "SWWM_LORETXT_GHOULHUNT2"; // met anthon anderken during the
|
|
else if ( text ~== "SWWM_LORETXT_RAGEKIT" )
|
|
text = "SWWM_LORETXT_RAGEKIT2"; // kirin's reactions to demo using this item
|
|
else if ( text ~== "SWWM_LORETXT_SANKAIDERIHA" )
|
|
text = "SWWM_LORETXT_SANKAIDERIHA2"; // comments about kirin
|
|
else if ( text ~== "SWWM_LORETXT_SERPENTRIDERS" )
|
|
text = "SWWM_LORETXT_SERPENTRIDERS2"; // defeated d'sparil
|
|
else if ( text ~== "SWWM_LORETXT_XANIMEN" )
|
|
text = "SWWM_LORETXT_XANIMEN2"; // footnote about nuoma
|
|
else if ( text ~== "SWWM_LORETXT_ZANAVETH2" )
|
|
text = "SWWM_LORETXT_ZANAVETH22"; // met at wedding
|
|
else if ( text ~== "SWWM_LORETXT_YNYKRON" )
|
|
text = "SWWM_LORETXT_YNYKRON2"; // confirmed to harm (but not kill) gods
|
|
}
|
|
if ( gameinfo.gametype&(GAME_Raven|GAME_Strife) )
|
|
{
|
|
if ( text ~== "SWWM_LORETXT_SAYA" )
|
|
text = "SWWM_LORETXT_SAYA2"; // dating demo
|
|
else if ( text ~== "SWWM_LORETXT_AKARILABS" )
|
|
text = "SWWM_LORETXT_AKARILABS2"; // demo won, akari project announced
|
|
else if ( text ~== "SWWM_LORETXT_DEMOLITIONIST" )
|
|
text = "SWWM_LORETXT_DEMOLITIONIST2"; // demo rewarded with maidbot frame
|
|
else if ( text ~== "SWWM_LORETXT_DOOMGUY" )
|
|
text = "SWWM_LORETXT_DOOMGUY2"; // he gone
|
|
else if ( text ~== "SWWM_LORETXT_UAC" )
|
|
text = "SWWM_LORETXT_UAC2"; // uac "reformed"
|
|
else if ( text ~== "SWWM_LORETXT_HELL" )
|
|
text = "SWWM_LORETXT_HELL2"; // invasion was a thing of the past
|
|
else if ( text ~== "SWWM_LORETXT_NANA" )
|
|
text = "SWWM_LORETXT_NANA2"; // demo met nana
|
|
else if ( text ~== "SWWM_LORETXT_ZANAVETH3" )
|
|
text = "SWWM_LORETXT_ZANAVETH32"; // iagb happened
|
|
else if ( text ~== "SWWM_LORETXT_BIGSHOT" )
|
|
text = "SWWM_LORETXT_BIGSHOT2"; // predictions about crimes_m
|
|
}
|
|
// check that it's valid
|
|
if ( StringTable.Localize(tag,false) == tag ) return false;
|
|
if ( StringTable.Localize(tab,false) == tab )
|
|
{
|
|
Console.Printf("Entry \"%s\" defines no tab.",ref);
|
|
return false;
|
|
}
|
|
if ( StringTable.Localize(text,false) == text )
|
|
{
|
|
Console.Printf("Entry \"%s\" defines no text.",ref);
|
|
return false;
|
|
}
|
|
// check if existing
|
|
for ( int i=0; i<ent.Size(); i++ )
|
|
{
|
|
if ( ent[i].tag != "$"..tag ) continue;
|
|
return true;
|
|
}
|
|
SWWMLore e = new("SWWMLore");
|
|
e.tag = "$"..tag;
|
|
if ( StringTable.Localize(e.tag) == "" )
|
|
{
|
|
Console.Printf("Entry \"%s\" has an empty tag.",ref);
|
|
return true;
|
|
}
|
|
String ttab = StringTable.Localize(tab,false);
|
|
if ( ttab ~== "People" ) e.tab = LORE_PEOPLE;
|
|
else if ( ttab ~== "Lore" ) e.tab = LORE_LORE;
|
|
else if ( ttab ~== "Item" ) e.tab = LORE_ITEM;
|
|
else
|
|
{
|
|
Console.Printf("Entry \"%s\" has an incorrect tab setting of \"%s\".",ref,ttab);
|
|
return true;
|
|
}
|
|
e.text = "$"..text;
|
|
if ( StringTable.Localize(e.text) == "" )
|
|
{
|
|
Console.Printf("Entry \"%s\" has empty text.",ref);
|
|
return true;
|
|
}
|
|
e.assoc = "$"..assoc;
|
|
e.read = false;
|
|
// "new lore" message
|
|
if ( (level.maptime > 0) && (gametic > lastaddtic) && (myplayer == players[consoleplayer]) && (!menuactive || (menuactive == Menu.OnNoPause)) && (myplayer.mo is 'Demolitionist') )
|
|
Console.Printf(StringTable.Localize("$SWWM_NEWLORE"));
|
|
lastaddtic = gametic;
|
|
// sorted add
|
|
String loca = StringTable.Localize(e.tag), locb;
|
|
int cpa, cpb;
|
|
for ( int i=0; i<ent.Size(); i++ )
|
|
{
|
|
locb = StringTable.Localize(ent[i].tag);
|
|
if ( locb < loca ) continue;
|
|
ent.Insert(i,e);
|
|
return true;
|
|
}
|
|
// append otherwise
|
|
ent.Push(e);
|
|
return true;
|
|
}
|
|
|
|
static void Add( PlayerInfo p, String ref )
|
|
{
|
|
// ignore this call on player rebirth, otherwise a new thinker will be created when the player calls GiveDefaultInventory
|
|
if ( SWWMExcuseMeWhatTheFuckHandler.WhyTheFuckIsThisAThing() ) return;
|
|
SWWMLoreLibrary ll = Find(p);
|
|
if ( !ll )
|
|
{
|
|
ll = new("SWWMLoreLibrary");
|
|
ll.ChangeStatNum(STAT_STATIC);
|
|
ll.myplayer = p;
|
|
}
|
|
ll.DirectAdd(ref);
|
|
}
|
|
|
|
void MarkRead( int idx )
|
|
{
|
|
if ( (idx < 0) || (idx >= ent.Size()) ) return;
|
|
if ( !ent[idx].read )
|
|
{
|
|
ent[idx].read = true;
|
|
// add associated entries
|
|
Array<String> rel;
|
|
rel.Clear();
|
|
String assocstr = StringTable.Localize(ent[idx].assoc);
|
|
assocstr.Split(rel,";",0);
|
|
for ( int i=0; i<rel.Size(); i++ )
|
|
{
|
|
if ( (rel[i] != "") && !DirectAdd(rel[i]) )
|
|
Console.Printf("Related entry \"%s\" not found, please update LANGUAGE.txt",rel[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
clearscope int FindEntry( String tag )
|
|
{
|
|
for ( int i=0; i<ent.Size(); i++ )
|
|
{
|
|
if ( ent[i].tag ~== tag )
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static clearscope SWWMLoreLibrary Find( PlayerInfo p )
|
|
{
|
|
let ti = ThinkerIterator.Create("SWWMLoreLibrary",STAT_STATIC);
|
|
SWWMLoreLibrary ll;
|
|
while ( ll = SWWMLoreLibrary(ti.Next()) )
|
|
{
|
|
if ( ll.myplayer != p ) continue;
|
|
return ll;
|
|
}
|
|
return Null;
|
|
}
|
|
}
|
|
|
|
// floating scores
|
|
Class SWWMScoreObj : Thinker
|
|
{
|
|
int xcnt;
|
|
int xtcolor[5];
|
|
int xscore[5];
|
|
String xstr[5];
|
|
int tcolor;
|
|
int score;
|
|
Vector3 pos;
|
|
int lifespan, initialspan;
|
|
int starttic, seed, seed2;
|
|
SWWMScoreObj prev, next;
|
|
bool damnum;
|
|
Actor acc;
|
|
|
|
static SWWMScoreObj Spawn( int score, Vector3 pos, int tcolor = Font.CR_GOLD, Actor acc = null )
|
|
{
|
|
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
|
if ( !hnd ) return null;
|
|
let o = new("SWWMScoreObj");
|
|
o.ChangeStatNum(STAT_USER);
|
|
o.score = score;
|
|
o.pos = pos;
|
|
o.lifespan = o.initialspan = 60;
|
|
o.tcolor = tcolor;
|
|
o.starttic = level.maptime;
|
|
o.seed = Random[ScoreBits]();
|
|
o.seed2 = Random[ScoreBits]();
|
|
o.damnum = (tcolor == Font.CR_RED) || (tcolor == Font.CR_GREEN) || (tcolor == Font.CR_BLUE);
|
|
o.xcnt = 0;
|
|
o.acc = acc;
|
|
if ( o.damnum )
|
|
{
|
|
o.next = hnd.damnums;
|
|
if ( hnd.damnums ) hnd.damnums.prev = o;
|
|
hnd.damnums = o;
|
|
hnd.damnums_cnt++;
|
|
}
|
|
else
|
|
{
|
|
o.next = hnd.scorenums;
|
|
if ( hnd.scorenums ) hnd.scorenums.prev = o;
|
|
hnd.scorenums = o;
|
|
hnd.scorenums_cnt++;
|
|
}
|
|
return o;
|
|
}
|
|
|
|
override void OnDestroy()
|
|
{
|
|
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
|
if ( hnd )
|
|
{
|
|
if ( damnum )
|
|
{
|
|
hnd.damnums_cnt--;
|
|
if ( !prev ) hnd.damnums = next;
|
|
}
|
|
else
|
|
{
|
|
hnd.scorenums_cnt--;
|
|
if ( !prev ) hnd.scorenums = next;
|
|
}
|
|
if ( !prev )
|
|
{
|
|
if ( next ) next.prev = null;
|
|
}
|
|
else
|
|
{
|
|
prev.next = next;
|
|
if ( next ) next.prev = prev;
|
|
}
|
|
}
|
|
Super.OnDestroy();
|
|
}
|
|
|
|
override void Tick()
|
|
{
|
|
lifespan--;
|
|
if ( lifespan <= 0 ) Destroy();
|
|
}
|
|
}
|
|
|
|
enum EInterestType
|
|
{
|
|
INT_Key,
|
|
INT_Exit
|
|
};
|
|
|
|
Class SWWMInterest : Thinker
|
|
{
|
|
int type;
|
|
Key trackedkey;
|
|
Line trackedline;
|
|
Vector3 pos;
|
|
SWWMInterest prev, next;
|
|
String keytag;
|
|
|
|
static SWWMInterest Spawn( Vector3 pos = (0,0,0), Key thekey = null, Line theline = null )
|
|
{
|
|
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
|
if ( !hnd ) return null;
|
|
if ( (!thekey && !theline) || (thekey && theline) ) return null;
|
|
let i = new("SWWMInterest");
|
|
i.ChangeStatNum(STAT_USER);
|
|
i.trackedkey = thekey;
|
|
i.trackedline = theline;
|
|
if ( thekey )
|
|
{
|
|
i.type = INT_Key;
|
|
i.keytag = thekey.GetTag();
|
|
}
|
|
else if ( theline ) i.type = INT_Exit;
|
|
else
|
|
{
|
|
i.Destroy();
|
|
return null;
|
|
}
|
|
i.pos = thekey?thekey.Vec3Offset(0,0,thekey.height/2):pos;
|
|
i.next = hnd.intpoints;
|
|
if ( hnd.intpoints ) hnd.intpoints.prev = i;
|
|
hnd.intpoints = i;
|
|
hnd.intpoints_cnt++;
|
|
return i;
|
|
}
|
|
|
|
override void OnDestroy()
|
|
{
|
|
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
|
if ( hnd )
|
|
{
|
|
hnd.intpoints_cnt--;
|
|
if ( !prev )
|
|
{
|
|
hnd.intpoints = next;
|
|
if ( next ) next.prev = null;
|
|
}
|
|
else
|
|
{
|
|
prev.next = next;
|
|
if ( next ) next.prev = prev;
|
|
}
|
|
}
|
|
Super.OnDestroy();
|
|
}
|
|
|
|
override void Tick()
|
|
{
|
|
// update
|
|
if ( (type == INT_Key) && (!trackedkey || trackedkey.Owner) ) Destroy();
|
|
else if ( trackedkey ) pos = trackedkey.Vec3Offset(0,0,trackedkey.height/2);
|
|
}
|
|
}
|
|
|
|
Class SWWMItemSense : Thinker
|
|
{
|
|
Inventory item;
|
|
String tag;
|
|
int updated;
|
|
Demolitionist parent;
|
|
SWWMItemSense prev, next;
|
|
Vector3 pos;
|
|
|
|
static SWWMItemSense Spawn( Demolitionist parent, Inventory item )
|
|
{
|
|
if ( !parent || !item ) return null;
|
|
// only refresh the updated time if existing
|
|
for ( SWWMItemSense s=parent.itemsense; s; s=s.next )
|
|
{
|
|
if ( s.item != item ) continue;
|
|
s.updated = level.maptime+35;
|
|
s.pos = item.Vec3Offset(0,0,item.height);
|
|
return s;
|
|
}
|
|
let i = new("SWWMItemSense");
|
|
i.ChangeStatNum(STAT_USER);
|
|
i.item = item;
|
|
i.parent = parent;
|
|
i.updated = level.maptime+35;
|
|
i.UpdateTag();
|
|
i.pos = item.Vec3Offset(0,0,item.height);
|
|
i.next = parent.itemsense;
|
|
if ( parent.itemsense ) parent.itemsense.prev = i;
|
|
parent.itemsense = i;
|
|
parent.itemsense_cnt++;
|
|
return i;
|
|
}
|
|
|
|
void UpdateTag()
|
|
{
|
|
if ( !item ) return;
|
|
// certain ammo types use the pickup message as it's amount-aware
|
|
if ( (item is 'RedShell') || (item is 'GreenShell')
|
|
|| (item is 'WhiteShell') || (item is 'BlueShell')
|
|
|| (item is 'BlackShell') || (item is 'PurpleShell')
|
|
|| (item is 'GoldShell') || (item is 'EvisceratorShell')
|
|
|| (item is 'HellblazerMissiles')|| (item is 'HellblazerCrackshots')
|
|
|| (item is 'HellblazerRavagers')|| (item is 'HellblazerWarheads') )
|
|
tag = item.PickupMessage();
|
|
else tag = item.GetTag();
|
|
}
|
|
|
|
override void OnDestroy()
|
|
{
|
|
if ( parent )
|
|
{
|
|
parent.itemsense_cnt--;
|
|
if ( !prev )
|
|
{
|
|
parent.itemsense = next;
|
|
if ( next ) next.prev = null;
|
|
}
|
|
else
|
|
{
|
|
prev.next = next;
|
|
if ( next ) next.prev = prev;
|
|
}
|
|
}
|
|
Super.OnDestroy();
|
|
}
|
|
|
|
override void Tick()
|
|
{
|
|
if ( !parent )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
// expire
|
|
if ( level.maptime > updated+70 ) Destroy();
|
|
}
|
|
}
|
|
|
|
// enemy combat tracker
|
|
Class SWWMCombatTracker : Thinker
|
|
{
|
|
Actor mytarget;
|
|
String mytag;
|
|
int updated, lasthealth, maxhealth;
|
|
DynamicValueInterpolator intp;
|
|
Vector3 pos, prevpos, oldpos, oldprev;
|
|
PlayerInfo myplayer;
|
|
SWWMCombatTracker prev, next;
|
|
bool legged;
|
|
int tcnt;
|
|
double height;
|
|
transient CVar funtags;
|
|
|
|
void UpdateTag()
|
|
{
|
|
if ( !funtags ) funtags = CVar.GetCVar('swwm_funtags',players[consoleplayer]);
|
|
if ( mytarget && (mytarget.player || mytarget.bISMONSTER || (mytarget is 'BossBrain')) )
|
|
{
|
|
String realtag = funtags.GetBool()?SWWMUtility.GetFunTag(mytarget,"AWESOME IT'S PENIS"):mytarget.GetTag("AWESOME IT'S PENIS");
|
|
if ( realtag == "AWESOME IT'S PENIS" )
|
|
{
|
|
realtag = mytarget.GetClassName();
|
|
SWWMUtility.BeautifyClassName(realtag);
|
|
}
|
|
mytag = mytarget.player?(mytarget.player.mo!=mytarget)?String.Format(StringTable.Localize("$FN_VOODOO"),mytarget.player.GetUserName()):mytarget.player.GetUserName():realtag;
|
|
}
|
|
else mytag = "";
|
|
}
|
|
|
|
static SWWMCombatTracker Spawn( Actor target )
|
|
{
|
|
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
|
if ( !hnd ) return null;
|
|
SWWMCombatTracker t;
|
|
for ( t=hnd.trackers; t; t=t.next )
|
|
{
|
|
if ( t.mytarget != target ) continue;
|
|
return t;
|
|
}
|
|
t = new("SWWMCombatTracker");
|
|
t.ChangeStatNum(STAT_USER);
|
|
t.mytarget = target;
|
|
t.UpdateTag();
|
|
if ( target.player )
|
|
{
|
|
t.lasthealth = target.health;
|
|
t.maxhealth = target.default.health;
|
|
}
|
|
else t.lasthealth = t.maxhealth = target.health;
|
|
t.updated = int.min;
|
|
t.height = t.mytarget.height;
|
|
t.pos = level.Vec3Offset(target.pos,(0,0,t.height));
|
|
t.prevpos = level.Vec3Offset(target.prev,(0,0,t.height));
|
|
t.oldpos = target.pos;
|
|
t.oldprev = target.prev;
|
|
t.intp = DynamicValueInterpolator.Create(t.lasthealth,.5,1,100);
|
|
t.myplayer = target.player;
|
|
t.next = hnd.trackers;
|
|
if ( hnd.trackers ) hnd.trackers.prev = t;
|
|
hnd.trackers = t;
|
|
hnd.trackers_cnt++;
|
|
return t;
|
|
}
|
|
|
|
override void OnDestroy()
|
|
{
|
|
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
|
if ( hnd )
|
|
{
|
|
hnd.trackers_cnt--;
|
|
if ( !prev )
|
|
{
|
|
hnd.trackers = next;
|
|
if ( next ) next.prev = null;
|
|
}
|
|
else
|
|
{
|
|
prev.next = next;
|
|
if ( next ) next.prev = prev;
|
|
}
|
|
}
|
|
Super.OnDestroy();
|
|
}
|
|
|
|
override void Tick()
|
|
{
|
|
if ( swwm_notrack )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
// don't update
|
|
if ( mytarget && mytarget.bDORMANT )
|
|
return;
|
|
// update
|
|
if ( mytarget && ((mytarget.pos != oldpos) || (mytarget.prev != oldprev)) )
|
|
{
|
|
oldpos = mytarget.pos;
|
|
oldprev = mytarget.prev;
|
|
pos = level.Vec3Offset(mytarget.pos,(0,0,height));
|
|
prevpos = level.Vec3Offset(mytarget.prev,(0,0,height));
|
|
}
|
|
if ( !mytarget || (mytarget.Health <= 0) )
|
|
{
|
|
// we're done
|
|
if ( updated > level.maptime ) updated = level.maptime;
|
|
lasthealth = 0;
|
|
intp.Update(lasthealth);
|
|
if ( level.maptime > updated+35 ) Destroy();
|
|
return;
|
|
}
|
|
// only update height while alive
|
|
height = mytarget.height;
|
|
tcnt++;
|
|
if ( (tcnt == 1) && !mytarget.player )
|
|
{
|
|
// post-spawn health inflation check
|
|
if ( lasthealth > maxhealth ) maxhealth = lasthealth;
|
|
}
|
|
if ( (tcnt == 6) && !mytarget.player )
|
|
{
|
|
// legendoom check
|
|
for ( Inventory i=mytarget.inv; i; i=i.inv )
|
|
{
|
|
if ( i.GetClassName() != "LDLegendaryMonsterToken" ) continue;
|
|
legged = true;
|
|
// adjust for health inflation
|
|
if ( lasthealth > maxhealth ) maxhealth = lasthealth;
|
|
}
|
|
}
|
|
lasthealth = mytarget.Health;
|
|
intp.Update(lasthealth);
|
|
if ( (mytarget.bISMONSTER || mytarget.player) && !mytarget.bINVISIBLE )
|
|
{
|
|
bool straifu = false;
|
|
if ( (gameinfo.gametype&GAME_Strife) && (!mytarget.bINCOMBAT && !mytarget.bJUSTATTACKED) || (mytarget is 'Beggar') || (mytarget is 'Peasant') )
|
|
straifu = true;
|
|
// enemies within 2000mu that have us as target
|
|
if ( !straifu && mytarget.target && (mytarget.target.Health > 0) && (mytarget.target.player == players[consoleplayer]) && (mytarget.Vec3To(mytarget.target).length() < 2000) && mytarget.CheckSight(mytarget.target) ) updated = level.maptime+70;
|
|
// players (but not voodoo dolls), always visible in sp/coop
|
|
if ( !deathmatch && mytarget.player && (mytarget.player.mo == mytarget) ) updated = level.maptime+35;
|
|
// any visible enemies within 600mu
|
|
if ( (mytarget.Vec3To(players[consoleplayer].mo).length() < 600) && players[consoleplayer].mo.CheckSight(mytarget) ) updated = level.maptime;
|
|
}
|
|
else if ( mytarget is 'BossBrain' )
|
|
{
|
|
// teh romero, only if visible
|
|
if ( (mytarget.Vec3To(players[consoleplayer].mo).length() < 600) && players[consoleplayer].mo.CheckSight(mytarget) ) updated = level.maptime;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Press F to Pay Respects
|
|
Class PayRespects : HUDMessageBase
|
|
{
|
|
Vector2 basepos;
|
|
int lifespan, initialspan, starttic;
|
|
transient Font TewiFont;
|
|
double scale;
|
|
Vector2 hs, ss;
|
|
int seed, seed2;
|
|
|
|
static PayRespects PressF()
|
|
{
|
|
let f = new("PayRespects");
|
|
f.basepos = (FRandom[FInTheChat](0.,1.),FRandom[FInTheChat](1.02,1.05));
|
|
f.scale = FRandom[FInTheChat](.5,2.);
|
|
f.lifespan = f.initialspan = Random[FInTheChat](20,80);
|
|
f.starttic = level.maptime;
|
|
f.seed = Random[FInTheChat]();
|
|
f.seed2 = Random[FInTheChat]();
|
|
f.ScreenSizeChanged();
|
|
return f;
|
|
}
|
|
|
|
override bool Tick()
|
|
{
|
|
lifespan--;
|
|
return (lifespan<=0);
|
|
}
|
|
|
|
override void ScreenSizeChanged()
|
|
{
|
|
hs = StatusBar.GetHUDScale()*scale;
|
|
ss = (Screen.GetWidth()/hs.x,Screen.GetHeight()/hs.y);
|
|
}
|
|
|
|
override void Draw( int bottom, int visibility )
|
|
{
|
|
Vector2 realpos = (basepos.x*ss.x,basepos.y*ss.y);
|
|
if ( !TewiFont ) TewiFont = Font.GetFont('TewiShaded');
|
|
Vector2 fo = (TewiFont.StringWidth("F")/2.,-TewiFont.GetHeight());
|
|
// F rise up
|
|
int initspd = (128-seed);
|
|
if ( (initspd >= 0) && (initspd < 32) ) initspd = 32;
|
|
if ( (initspd < 0) && (initspd > -32) ) initspd = -32;
|
|
int boostup = 32+(seed2/4);
|
|
double fractic = SWWMStatusBar(statusbar)?SWWMStatusBar(statusbar).fractic:0;
|
|
fo.x += (.15*initspd)*((initialspan-(lifespan-fractic))**.6);
|
|
fo.y += ((initialspan-(lifespan-fractic))**1.6)-boostup*sin((90./initialspan)*(level.maptime+fractic-starttic));
|
|
double alph = clamp((lifespan+fractic)/double(initialspan),0.,1.);
|
|
Screen.DrawText(TewiFont,Font.CR_GREEN,realpos.x-fo.x,realpos.y-fo.y,"F",DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_Alpha,alph);
|
|
}
|
|
}
|
|
|
|
// One-liners
|
|
Class SWWMOneLiner : HUDMessageBase
|
|
{
|
|
String whichline;
|
|
int lifespan, curtime;
|
|
transient Font TewiFont, MPlusFont;
|
|
transient CVar safezone, lang, hscale;
|
|
|
|
static SWWMOneLiner Make( String whichline, int lifespan )
|
|
{
|
|
let l = new("SWWMOneLiner");
|
|
l.whichline = whichline;
|
|
l.curtime = l.lifespan = lifespan;
|
|
return l;
|
|
}
|
|
|
|
override bool Tick()
|
|
{
|
|
if ( players[consoleplayer].Health <= 0 ) curtime = int.min;
|
|
curtime--;
|
|
return (curtime<-20);
|
|
}
|
|
|
|
override void Draw( int bottom, int visibility )
|
|
{
|
|
if ( !safezone ) safezone = CVar.GetCVar('swwm_hudmargin',players[consoleplayer]);
|
|
if ( !lang ) lang = CVar.GetCVar('language',players[consoleplayer]);
|
|
if ( !TewiFont ) TewiFont = Font.GetFont('TewiShaded');
|
|
if ( !MPlusFont ) MPlusFont = Font.GetFont('MPlusShaded');
|
|
if ( !hscale ) hscale = CVar.GetCVar('swwm_hudscale',players[consoleplayer]);
|
|
int margin = safezone.GetInt();
|
|
Vector2 hs;
|
|
if ( hscale.GetInt() <= 0 ) hs = StatusBar.GetHUDScale();
|
|
else
|
|
{
|
|
hs.x = hscale.GetInt();
|
|
if ( Screen.GetWidth()/hs.x < 640. ) hs.x = max(1,floor(Screen.GetWidth()/640.));
|
|
hs.y = hs.x;
|
|
}
|
|
Vector2 ss = (Screen.GetWidth()/hs.x,Screen.GetHeight()/hs.y);
|
|
String loc = StringTable.Localize(whichline);
|
|
if ( loc.Length() <= 0 ) return; // don't draw empty strings
|
|
String locs = StringTable.Localize("$SWWM_LQUOTE")..loc..StringTable.Localize("$SWWM_RQUOTE");
|
|
Font fnt = TewiFont;
|
|
if ( lang.GetString() ~== "jp" ) fnt = MPlusFont;
|
|
// split so it can fit
|
|
BrokenLines l = fnt.BreakLines(locs,int(ss.x*.5));
|
|
int maxlen = 0;
|
|
for ( int i=0; i<l.Count(); i++ )
|
|
{
|
|
int len = fnt.StringWidth(l.StringAt(i));
|
|
if ( len > maxlen ) maxlen = len;
|
|
}
|
|
int h = fnt.GetHeight();
|
|
int fh = h*l.Count();
|
|
double alph = clamp((curtime/20.)+1.,0.,1.);
|
|
alph *= clamp((lifespan-curtime)/10.,0.,1.);
|
|
Screen.Dim("Black",alph*.8,int((Screen.GetWidth()-(maxlen+12)*hs.x)/2.),int(bottom-(margin+2+fh)*hs.y),int((maxlen+12)*hs.x),int((fh+4)*hs.y));
|
|
int yy = margin+fh;
|
|
for ( int i=0; i<l.Count(); i++ )
|
|
{
|
|
int len = fnt.StringWidth(l.StringAt(i));
|
|
Screen.DrawText(fnt,Font.CR_FIRE,int((ss.x-len)/2.),(bottom/hs.y)-yy,l.StringAt(i),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_Alpha,alph);
|
|
yy -= h;
|
|
}
|
|
}
|
|
}
|
|
|
|
// imitates UE1 light type LT_TexturePaletteOnce/LT_TexturePaletteLoop
|
|
Class PaletteLight : DynamicLight
|
|
{
|
|
Color pal[256];
|
|
bool IsLooping;
|
|
|
|
Default
|
|
{
|
|
Tag "Explosion";
|
|
DynamicLight.Type "Point";
|
|
Args 0,0,0,80;
|
|
ReactionTime 15;
|
|
}
|
|
private void UpdateLight()
|
|
{
|
|
int index = 255-((255*ReactionTime)/abs(default.ReactionTime));
|
|
args[LIGHT_RED] = pal[index].r;
|
|
args[LIGHT_GREEN] = pal[index].g;
|
|
args[LIGHT_BLUE] = pal[index].b;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
int lump = Wads.CheckNumForFullname(String.Format("palettes/%s.pal",GetTag()));
|
|
String paldat = Wads.ReadLump(lump);
|
|
for ( int i=0; i<256; i++ )
|
|
{
|
|
pal[i].r = paldat.ByteAt(i*3);
|
|
pal[i].g = paldat.ByteAt(i*3+1);
|
|
pal[i].b = paldat.ByteAt(i*3+2);
|
|
}
|
|
if ( ReactionTime < 0 )
|
|
{
|
|
ReactionTime = -ReactionTime;
|
|
IsLooping = true;
|
|
}
|
|
UpdateLight();
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( isFrozen() ) return;
|
|
ReactionTime--;
|
|
if ( ReactionTime < 0 )
|
|
{
|
|
if ( !IsLooping )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
else ReactionTime = abs(default.ReactionTime);
|
|
}
|
|
if ( target ) SetOrigin(target.pos,true);
|
|
UpdateLight();
|
|
}
|
|
}
|
|
|
|
// Generic particles
|
|
Class SWWMSmoke : Actor
|
|
{
|
|
Default
|
|
{
|
|
RenderStyle "Shaded";
|
|
StencilColor "FFFFFF";
|
|
Radius 2;
|
|
Height 2;
|
|
+NOBLOCKMAP;
|
|
+NOGRAVITY;
|
|
+DONTSPLASH;
|
|
+FORCEXYBILLBOARD;
|
|
+ROLLSPRITE;
|
|
+ROLLCENTER;
|
|
+THRUACTORS;
|
|
+NOTELEPORT;
|
|
+CANBOUNCEWATER;
|
|
-BOUNCEAUTOOFF;
|
|
BounceType "Hexen";
|
|
BounceFactor 1.0;
|
|
WallBounceFactor 1.0;
|
|
Scale 0.3;
|
|
}
|
|
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
double ang, pt;
|
|
scale *= FRandom[Puff](0.5,1.5);
|
|
alpha *= FRandom[Puff](0.5,1.5);
|
|
ang = FRandom[Puff](0,360);
|
|
pt = FRandom[Puff](-90,90);
|
|
vel += (cos(pt)*cos(ang),cos(pt)*sin(ang),-sin(pt))*FRandom[Puff](0.2,0.8);
|
|
roll = Frandom[Puff](0,360);
|
|
scale.x *= RandomPick[Puff](-1,1);
|
|
scale.y *= RandomPick[Puff](-1,1);
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( isFrozen() ) return;
|
|
vel *= 0.96;
|
|
vel.z += 0.01;
|
|
if ( (waterlevel > 0) && !bAMBUSH )
|
|
{
|
|
let b = Spawn("SWWMBubble",pos);
|
|
b.scale *= abs(scale.x);
|
|
b.vel = vel;
|
|
Destroy();
|
|
}
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
XSMK ABCDEFGHIJKLMNOPQRST 1 A_SetTics(1+special1);
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
// ultra-lightweight non-interacting smoke, used by fire trails
|
|
Class SWWMHalfSmoke : Actor
|
|
{
|
|
Default
|
|
{
|
|
RenderStyle "Shaded";
|
|
StencilColor "FFFFFF";
|
|
Radius 0.1;
|
|
Height 0;
|
|
+NOBLOCKMAP;
|
|
+NOGRAVITY;
|
|
+DONTSPLASH;
|
|
+FORCEXYBILLBOARD;
|
|
+ROLLSPRITE;
|
|
+ROLLCENTER;
|
|
+NOTELEPORT;
|
|
+NOINTERACTION;
|
|
Scale 0.3;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
double ang, pt;
|
|
scale *= FRandom[Puff](0.5,1.5);
|
|
alpha *= FRandom[Puff](0.5,1.5);
|
|
ang = FRandom[Puff](0,360);
|
|
pt = FRandom[Puff](-90,90);
|
|
vel += (cos(pt)*cos(ang),cos(pt)*sin(ang),-sin(pt))*FRandom[Puff](0.2,0.8);
|
|
roll = Frandom[Puff](0,360);
|
|
scale.x *= RandomPick[Puff](-1,1);
|
|
scale.y *= RandomPick[Puff](-1,1);
|
|
}
|
|
override void Tick()
|
|
{
|
|
if ( isFrozen() ) return;
|
|
vel *= 0.96;
|
|
vel.z += 0.01;
|
|
SetOrigin(level.Vec3Offset(pos,vel),true);
|
|
UpdateWaterLevel();
|
|
if ( (waterlevel > 0) && !bAMBUSH )
|
|
{
|
|
let b = Spawn("SWWMBubble",pos);
|
|
b.scale *= abs(scale.x);
|
|
b.vel = vel;
|
|
Destroy();
|
|
return;
|
|
}
|
|
if ( tics > 0 ) tics--;
|
|
while ( !tics )
|
|
{
|
|
if ( !SetState(CurState.NextState) )
|
|
return;
|
|
}
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
XSMK ABCDEFGHIJKLMNOPQRST 1 A_SetTics(1+special1);
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class SWWMSmallSmoke : SWWMHalfSmoke
|
|
{
|
|
override void PostBeginPlay()
|
|
{
|
|
Actor.PostBeginPlay();
|
|
double ang, pt;
|
|
scale *= FRandom[Puff](0.1,0.3);
|
|
alpha *= FRandom[Puff](0.5,1.5);
|
|
ang = FRandom[Puff](0,360);
|
|
pt = FRandom[Puff](-90,90);
|
|
vel += (cos(pt)*cos(ang),cos(pt)*sin(ang),-sin(pt))*FRandom[Puff](0.04,0.16);
|
|
roll = Frandom[Puff](0,360);
|
|
scale.x *= RandomPick[Puff](-1,1);
|
|
scale.y *= RandomPick[Puff](-1,1);
|
|
}
|
|
|
|
States
|
|
{
|
|
Spawn:
|
|
QSM6 ABCDEFGHIJKLMNOPQR 1 A_SetTics(1+special1);
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class SWWMViewSmoke : SWWMSmoke
|
|
{
|
|
Vector3 ofs, vvel;
|
|
|
|
override void PostBeginPlay()
|
|
{
|
|
Actor.PostBeginPlay();
|
|
double ang, pt;
|
|
scale *= FRandom[Puff](0.1,0.3);
|
|
alpha *= FRandom[Puff](0.5,1.5);
|
|
ang = FRandom[Puff](0,360);
|
|
pt = FRandom[Puff](-90,90);
|
|
vvel += (cos(pt)*cos(ang),cos(pt)*sin(ang),-sin(pt))*FRandom[Puff](0.04,0.16);
|
|
}
|
|
|
|
override void Tick()
|
|
{
|
|
Actor.Tick();
|
|
if ( !target || !target.player )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
Vector3 x, y, z;
|
|
[x, y, z] = swwm_CoordUtil.GetAxes(target.pitch,target.angle,target.roll);
|
|
Vector3 origin = level.Vec3Offset(target.Vec2OffsetZ(0,0,target.player.viewz),x*ofs.x+y*ofs.y+z*ofs.z);
|
|
SetOrigin(origin,true);
|
|
bInvisible = (players[consoleplayer].camera != target);
|
|
if ( isFrozen() ) return;
|
|
ofs += vvel;
|
|
vvel *= 0.96;
|
|
vvel.z += 0.01;
|
|
if ( (waterlevel > 0) && !bAMBUSH ) Destroy();
|
|
}
|
|
}
|
|
|
|
Class SWWMBubble : Actor
|
|
{
|
|
Default
|
|
{
|
|
RenderStyle "Add";
|
|
Radius 2;
|
|
Height 2;
|
|
+NOBLOCKMAP;
|
|
+NOGRAVITY;
|
|
+DONTSPLASH;
|
|
+FORCEXYBILLBOARD;
|
|
+NOTELEPORT;
|
|
Scale 0.5;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
double ang, pt;
|
|
scale *= FRandom[Puff](0.5,1.5);
|
|
ang = FRandom[Puff](0,360);
|
|
pt = FRandom[Puff](-90,90);
|
|
vel += (cos(pt)*cos(ang),cos(pt)*sin(ang),-sin(pt))*FRandom[Puff](0.2,0.8);
|
|
if ( waterlevel <= 0 ) Destroy();
|
|
SetState(ResolveState("Spawn")+Random[Puff](0,19));
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( isFrozen() ) return;
|
|
vel *= 0.96;
|
|
vel.z += 0.05;
|
|
if ( (waterlevel <= 0) || !Random[Puff](0,100) ) Destroy();
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XBUB ABCDEFGHIJKLMNOPQRST 1;
|
|
Loop;
|
|
}
|
|
}
|
|
|
|
Class SWWMSpark : Actor
|
|
{
|
|
Default
|
|
{
|
|
RenderStyle "Add";
|
|
Radius 2;
|
|
Height 2;
|
|
+NOBLOCKMAP;
|
|
+FORCEXYBILLBOARD;
|
|
+MISSILE;
|
|
+DROPOFF;
|
|
+THRUACTORS;
|
|
+NOTELEPORT;
|
|
+DONTSPLASH;
|
|
BounceType "Doom";
|
|
BounceFactor 0.4;
|
|
Gravity 0.2;
|
|
Scale 0.05;
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( waterlevel > 0 )
|
|
{
|
|
let b = Spawn("SWWMBubble",pos);
|
|
b.vel = vel;
|
|
b.scale *= 0.3;
|
|
Destroy();
|
|
}
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
BLPF A 1 Bright A_FadeOut(0.01);
|
|
Wait;
|
|
Death:
|
|
BLPF A 1 Bright A_FadeOut(0.05);
|
|
Wait;
|
|
}
|
|
}
|
|
|
|
Class SWWMChip : Actor
|
|
{
|
|
int deadtimer;
|
|
double rollvel;
|
|
|
|
Default
|
|
{
|
|
Radius 2;
|
|
Height 2;
|
|
+NOBLOCKMAP;
|
|
+MISSILE;
|
|
+DROPOFF;
|
|
+MOVEWITHSECTOR;
|
|
+THRUACTORS;
|
|
+NOTELEPORT;
|
|
+DONTSPLASH;
|
|
+INTERPOLATEANGLES;
|
|
+ROLLSPRITE;
|
|
+ROLLCENTER;
|
|
+FORCEXYBILLBOARD;
|
|
BounceType "Doom";
|
|
BounceFactor 0.3;
|
|
Gravity 0.35;
|
|
Scale 0.2;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
deadtimer = Random[Junk](-30,30);
|
|
rollvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
|
|
frame = Random[Junk](0,5);
|
|
scale *= Frandom[Junk](0.8,1.2);
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( isFrozen() ) return;
|
|
if ( CurState == ResolveState("Death") )
|
|
{
|
|
deadtimer++;
|
|
if ( deadtimer > 300 ) A_FadeOut(0.05);
|
|
return;
|
|
}
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
JUNK # 1
|
|
{
|
|
roll += rollvel;
|
|
}
|
|
Loop;
|
|
Bounce:
|
|
JUNK # 0
|
|
{
|
|
rollvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
|
|
}
|
|
Goto Spawn;
|
|
Death:
|
|
JUNK # -1;
|
|
Stop;
|
|
Dummy:
|
|
JUNK ABCDEF -1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class FancyConfetti : Actor
|
|
{
|
|
int deadtimer;
|
|
double anglevel, pitchvel, rollvel;
|
|
|
|
Default
|
|
{
|
|
Radius 2;
|
|
Height 2;
|
|
+NOBLOCKMAP;
|
|
+DROPOFF;
|
|
+MOVEWITHSECTOR;
|
|
+THRUACTORS;
|
|
+NOTELEPORT;
|
|
+DONTSPLASH;
|
|
+INTERPOLATEANGLES;
|
|
+ROLLSPRITE;
|
|
+ROLLCENTER;
|
|
+SLIDESONWALLS;
|
|
Gravity 0.05;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
deadtimer = Random[Junk](-30,30);
|
|
anglevel = FRandom[Junk](3,12)*RandomPick[Junk](-1,1);
|
|
pitchvel = FRandom[Junk](3,12)*RandomPick[Junk](-1,1);
|
|
rollvel = FRandom[Junk](3,12)*RandomPick[Junk](-1,1);
|
|
if ( bAMBUSH ) frame = 0;
|
|
else frame = Random[Junk](0,9);
|
|
scale *= Frandom[Junk](0.8,1.2);
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( isFrozen() ) return;
|
|
vel.xy *= 0.98;
|
|
if ( vel.z > 0 ) vel.z *= 0.98;
|
|
if ( CurState == ResolveState("Death") )
|
|
{
|
|
deadtimer++;
|
|
if ( deadtimer > 300 ) A_FadeOut(0.05);
|
|
return;
|
|
}
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 # 1
|
|
{
|
|
angle += anglevel;
|
|
pitch += pitchvel;
|
|
roll += rollvel;
|
|
return A_JumpIf(pos.z<=floorz,"Death");
|
|
}
|
|
Loop;
|
|
Death:
|
|
XZW1 # -1
|
|
{
|
|
A_Stop();
|
|
pitch = roll = 0;
|
|
}
|
|
Stop;
|
|
Dummy:
|
|
XZW1 ABCDEFGHIJ -1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class SWWMNothing : Actor
|
|
{
|
|
States
|
|
{
|
|
Spawn:
|
|
TNT1 A 1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class PoofLight : PaletteLight
|
|
{
|
|
Default
|
|
{
|
|
Tag "Yellow";
|
|
ReactionTime 5;
|
|
Args 0,0,0,60;
|
|
}
|
|
}
|
|
Class PoofLight2 : PaletteLight
|
|
{
|
|
Default
|
|
{
|
|
Tag "Yellow";
|
|
ReactionTime 20;
|
|
Args 0,0,0,90;
|
|
}
|
|
}
|
|
|
|
Class SWWMItemFog : Actor
|
|
{
|
|
Default
|
|
{
|
|
RenderStyle "Add";
|
|
+NOGRAVITY;
|
|
+NOBLOCKMAP;
|
|
+DONTSPLASH;
|
|
+ROLLSPRITE;
|
|
+ROLLCENTER;
|
|
+NOINTERACTION;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
BLPF A 2 Bright NoDelay
|
|
{
|
|
// offset up
|
|
SetOrigin(Vec3Offset(0,0,16),false);
|
|
roll = FRandom[ExploS](0,360);
|
|
scale *= FRandom[ExploS](0.9,1.1);
|
|
scale.x *= RandomPick[ExploS](-1,1);
|
|
scale.y *= RandomPick[ExploS](-1,1);
|
|
int numpt = Random[ExploS](8,12);
|
|
if ( bAMBUSH ) numpt *= 2;
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](.3,8);
|
|
let s = Spawn(bAMBUSH?"SWWMSmoke":"SWWMSmallSmoke",pos);
|
|
s.vel = pvel;
|
|
s.SetShade(Color(3,2,1)*Random[ExploS](64,85));
|
|
s.A_SetRenderStyle(s.alpha,STYLE_AddShaded);
|
|
s.scale *= 3.;
|
|
s.alpha *= bAMBUSH?.4:.2;
|
|
}
|
|
Spawn(bAMBUSH?"PoofLight2":"PoofLight",pos);
|
|
}
|
|
BLPF A 1 Bright A_FadeOut(.3);
|
|
Wait;
|
|
}
|
|
}
|
|
|
|
Class TeleLight : PaletteLight
|
|
{
|
|
Default
|
|
{
|
|
Tag "ImpactWav";
|
|
ReactionTime 10;
|
|
Args 0,0,0,150;
|
|
}
|
|
}
|
|
|
|
Class SWWMTeleportSparkle : Actor
|
|
{
|
|
Default
|
|
{
|
|
RenderStyle "Add";
|
|
Scale 0.3;
|
|
Radius 0.1;
|
|
Height 0;
|
|
+NOGRAVITY;
|
|
+NOBLOCKMAP;
|
|
+DONTSPLASH;
|
|
+ROLLSPRITE;
|
|
+ROLLCENTER;
|
|
+FORCEXYBILLBOARD;
|
|
+NOINTERACTION;
|
|
}
|
|
override void Tick()
|
|
{
|
|
if ( isFrozen() ) return;
|
|
A_SetScale(scale.x*specialf1);
|
|
A_FadeOut(specialf2);
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
BLPF C -1 Bright;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class SWWMTeleportFog : Actor
|
|
{
|
|
Default
|
|
{
|
|
RenderStyle "Add";
|
|
+NOGRAVITY;
|
|
+NOBLOCKMAP;
|
|
+DONTSPLASH;
|
|
+NOINTERACTION;
|
|
+FORCEXYBILLBOARD;
|
|
Radius .1;
|
|
Height 0.;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
SetOrigin(Vec3Offset(0,0,28),false);
|
|
A_StartSound("misc/teleport",CHAN_VOICE);
|
|
Spawn("TeleLight",pos);
|
|
if ( swwm_simplefog ) SetStateLabel("Simple");
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
TNT1 A 1
|
|
{
|
|
int numpt = int(Random[ExploS](6,12)*alpha);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](.3,8)*alpha;
|
|
let s = Spawn("SWWMSmallSmoke",pos);
|
|
s.vel = pvel;
|
|
s.SetShade(Color(1,2,3)*int(Random[ExploS](64,85)*alpha));
|
|
s.A_SetRenderStyle(s.alpha,STYLE_AddShaded);
|
|
s.scale *= 3.*alpha;
|
|
s.alpha *= alpha;
|
|
}
|
|
numpt = int(Random[ExploS](4,8));
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
double ang = FRandom[ExploS](0,360);
|
|
double pt = FRandom[ExploS](-90,90);
|
|
double dist = (FRandom[ExploS](5,10)+60*(1.-alpha));
|
|
if ( LineTrace(ang,dist,pt,TRF_THRUACTORS|TRF_THRUHITSCAN) ) continue;
|
|
Vector3 ofs = (cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt))*dist;
|
|
Vector3 spos = level.Vec3Offset(pos,ofs);
|
|
let s = Spawn("SWWMTeleportSparkle",spos);
|
|
s.scale *= FRandom[ExploS](.8,1.2);
|
|
s.specialf1 = FRandom[ExploS](.93,.97);
|
|
s.specialf2 = FRandom[ExploS](.02,.04);
|
|
s.roll = FRandom[ExploS](0,360);
|
|
}
|
|
A_FadeOut(.07);
|
|
}
|
|
Wait;
|
|
Simple:
|
|
SPEX ABCDEFGHIJKLMNOPQRSTUVWXYZ[\] 1 Bright;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
// Bullet trails from DT
|
|
Class WaterHit
|
|
{
|
|
Sector sect;
|
|
Vector3 hitpos;
|
|
}
|
|
|
|
Class InvisibleSplasher : Actor
|
|
{
|
|
Default
|
|
{
|
|
Mass 100;
|
|
VSpeed -2;
|
|
Radius 2;
|
|
Radius 2;
|
|
+NOBLOCKMAP; // needed to prevent infinite loops with some 3D floor water (yes, you read that right)
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
TNT1 A 2;
|
|
Stop;
|
|
}
|
|
}
|
|
Class SmolInvisibleSplasher : InvisibleSplasher
|
|
{
|
|
Default
|
|
{
|
|
Mass 25;
|
|
}
|
|
}
|
|
|
|
Class SWWMBulletTrail : LineTracer
|
|
{
|
|
Array<WaterHit> WaterHitList;
|
|
Array<Line> ShootThroughList;
|
|
Actor ignoreme;
|
|
|
|
static play void DoTrail( Actor target, Vector3 pos, Vector3 dir, int dist, int bubblechance, bool smoky = false )
|
|
{
|
|
let t = new("SWWMBulletTrail");
|
|
t.ignoreme = target;
|
|
t.WaterHitList.Clear();
|
|
t.ShootThroughList.Clear();
|
|
t.Trace(pos,level.PointInSector(pos.xy),dir,dist,0);
|
|
for ( int i=0; i<t.ShootThroughList.Size(); i++ )
|
|
{
|
|
t.ShootThroughList[i].Activate(target,0,SPAC_PCross);
|
|
if ( t.ShootThroughList[i].special == GlassBreak ) // fuck glass
|
|
t.ShootThroughList[i].Activate(target,0,SPAC_Impact);
|
|
}
|
|
for ( int i=0; i<t.WaterHitList.Size(); i++ )
|
|
{
|
|
let b = Actor.Spawn("InvisibleSplasher",t.WaterHitList[i].hitpos);
|
|
b.A_CheckTerrain();
|
|
}
|
|
for ( int i=5; i<t.Results.Distance; i+=10 )
|
|
{
|
|
if ( !Random[Boolet](0,bubblechance) ) continue;
|
|
let b = Actor.Spawn(smoky?"SWWMSmallSmoke":"SWWMBubble",level.Vec3Offset(pos,dir*i));
|
|
b.Scale *= FRandom[Boolet](.4,.6);
|
|
}
|
|
t.Destroy();
|
|
}
|
|
|
|
override ETraceStatus TraceCallback()
|
|
{
|
|
// liquid splashes
|
|
if ( Results.CrossedWater )
|
|
{
|
|
let hl = new("WaterHit");
|
|
hl.sect = Results.CrossedWater;
|
|
hl.hitpos = Results.CrossedWaterPos;
|
|
WaterHitList.Push(hl);
|
|
}
|
|
else if ( Results.Crossed3DWater )
|
|
{
|
|
let hl = new("WaterHit");
|
|
hl.sect = Results.Crossed3DWater;
|
|
hl.hitpos = Results.Crossed3DWaterPos;
|
|
WaterHitList.Push(hl);
|
|
}
|
|
if ( Results.HitType == TRACE_HitActor )
|
|
{
|
|
if ( Results.HitActor == ignoreme ) return TRACE_Skip;
|
|
if ( Results.HitActor.bSHOOTABLE ) return TRACE_Stop;
|
|
return TRACE_Skip;
|
|
}
|
|
else if ( (Results.HitType == TRACE_HitWall) && (Results.Tier == TIER_Middle) )
|
|
{
|
|
if ( !Results.HitLine.sidedef[1] || (Results.HitLine.Flags&(Line.ML_BlockHitscan|Line.ML_BlockEverything)) )
|
|
return TRACE_Stop;
|
|
ShootThroughList.Push(Results.HitLine);
|
|
return TRACE_Skip;
|
|
}
|
|
return TRACE_Stop;
|
|
}
|
|
}
|
|
|
|
|
|
// Elastic recoil from DT
|
|
Enum ESwingMode
|
|
{
|
|
SWING_Straight, // constant increment
|
|
SWING_Spring, // bounces back after a delay
|
|
};
|
|
|
|
Class Swinger : Thinker
|
|
{
|
|
Actor target;
|
|
Vector2 dir;
|
|
double inc, rmul;
|
|
int steps, mode, delay;
|
|
double str, tstr;
|
|
int cnt, cstate;
|
|
|
|
Enum ESwingerState
|
|
{
|
|
STATE_Initial,
|
|
STATE_Wait,
|
|
STATE_Return,
|
|
};
|
|
|
|
override void Tick()
|
|
{
|
|
if ( !target ) cstate = -1;
|
|
switch ( cstate )
|
|
{
|
|
case STATE_Initial:
|
|
target.A_SetAngle(target.angle+dir.x*str,SPF_INTERPOLATE);
|
|
target.A_SetPitch(target.pitch+dir.y*str,SPF_INTERPOLATE);
|
|
str += inc;
|
|
if ( ++cnt >= steps )
|
|
{
|
|
cnt = 0;
|
|
str = tstr/steps;
|
|
cstate = (mode==SWING_Straight)?(-1):(delay>0)?STATE_Wait:STATE_Return;
|
|
}
|
|
else tstr += str;
|
|
break;
|
|
case STATE_Wait:
|
|
if ( ++cnt >= delay )
|
|
{
|
|
cnt = 0;
|
|
cstate = STATE_Return;
|
|
}
|
|
break;
|
|
case STATE_Return:
|
|
target.A_SetAngle(target.angle-dir.x*(str/rmul),SPF_INTERPOLATE);
|
|
target.A_SetPitch(target.pitch-dir.y*(str/rmul),SPF_INTERPOLATE);
|
|
if ( ++cnt >= steps*rmul )
|
|
{
|
|
cnt = 0;
|
|
cstate = -1;
|
|
}
|
|
break;
|
|
default:
|
|
Destroy();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Screen flashes from DT
|
|
Class GenericFlash : HUDMessageBase
|
|
{
|
|
Color col;
|
|
int duration;
|
|
double alpha;
|
|
Actor cam;
|
|
transient CVar str;
|
|
GenericFlash Setup( Actor camera, Color c, int d )
|
|
{
|
|
alpha = 1.0;
|
|
col = c;
|
|
duration = d;
|
|
cam = camera;
|
|
return self;
|
|
}
|
|
override bool Tick()
|
|
{
|
|
if ( duration > 0 ) alpha -= 1./duration;
|
|
return (alpha<=0)||(!cam);
|
|
}
|
|
override void Draw( int bottom, int visibility )
|
|
{
|
|
if ( automapactive || (visibility != BaseStatusBar.HUDMSGLayer_UnderHUD) ) return;
|
|
if ( cam && (players[consoleplayer].camera != cam) ) return;
|
|
if ( !str ) str = CVar.GetCVar('swwm_flashstrength',players[consoleplayer]);
|
|
Screen.Dim(col,(col.a/255.)*alpha*str.GetFloat(),0,0,Screen.GetWidth(),Screen.GetHeight());
|
|
}
|
|
}
|
|
|
|
Class QueuedFlash
|
|
{
|
|
Color c;
|
|
int duration;
|
|
int tic;
|
|
Actor cam;
|
|
}
|
|
|
|
Class LastLine
|
|
{
|
|
String type;
|
|
int lineno;
|
|
}
|
|
|
|
// Korax instakill handler
|
|
Class UglyBoyGetsFuckedUp : Thinker
|
|
{
|
|
bool wedone;
|
|
|
|
override void Tick()
|
|
{
|
|
if ( wedone ) return;
|
|
if ( level.killed_monsters < level.total_monsters )
|
|
{
|
|
// stop portal door
|
|
int sidx = level.CreateSectorTagIterator(145).Next();
|
|
if ( sidx == -1 ) return;
|
|
Sector door = level.Sectors[sidx];
|
|
let ti = ThinkerIterator.Create("SectorEffect");
|
|
SectorEffect se;
|
|
while ( se = SectorEffect(ti.Next()) )
|
|
{
|
|
if ( se.GetSector() != door ) continue;
|
|
se.Destroy();
|
|
door.StopSoundSequence(CHAN_VOICE);
|
|
}
|
|
return;
|
|
}
|
|
wedone = true;
|
|
level.ExecuteSpecial(Door_Open,null,null,false,145,8);
|
|
Destroy();
|
|
}
|
|
}
|
|
|
|
// Gotta force it someway
|
|
Class ForcedFallerDamager : Thinker
|
|
{
|
|
Actor mybody, instigator;
|
|
double lastvelz;
|
|
bool wasflying;
|
|
|
|
static void TrackBody( Actor b )
|
|
{
|
|
if ( !b ) return;
|
|
let ffd = new("ForcedFallerDamager");
|
|
ffd.ChangeStatNum(STAT_USER);
|
|
ffd.mybody = b;
|
|
ffd.lastvelz = b.vel.z;
|
|
ffd.wasflying = ((b.pos.z>b.floorz)&&b.TestMobjZ());
|
|
}
|
|
static void SetInstigator( Actor b, Actor whomst )
|
|
{
|
|
if ( !b || !whomst ) return;
|
|
let ti = ThinkerIterator.Create("ForcedFallerDamager",STAT_USER);
|
|
ForcedFallerDamager ffd;
|
|
while ( ffd = ForcedFallerDamager(ti.Next()) )
|
|
{
|
|
if ( ffd.mybody != b ) continue;
|
|
ffd.instigator = whomst;
|
|
break;
|
|
}
|
|
}
|
|
override void Tick()
|
|
{
|
|
if ( !swwm_doomfall || !mybody )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
bool isflying = ((mybody.pos.z>mybody.floorz)&&mybody.TestMobjZ())||mybody.bNOGRAVITY;
|
|
if ( wasflying && !isflying && (lastvelz < -23) && !level.monsterfallingdamage && !(mybody.floorsector.flags&Sector.SECF_NOFALLINGDAMAGE) )
|
|
{
|
|
// big hurt
|
|
int dmg;
|
|
if ( lastvelz < -35 ) dmg = Actor.TELEFRAG_DAMAGE;
|
|
else dmg = int((-lastvelz-23)*6);
|
|
mybody.DamageMobj(instigator,instigator,dmg,'Falling');
|
|
}
|
|
if ( !isflying && wasflying ) instigator = null;
|
|
wasflying = isflying;
|
|
lastvelz = mybody.vel.z;
|
|
// wait until body is dead
|
|
if ( mybody.Health > 0 ) return;
|
|
Destroy();
|
|
}
|
|
}
|
|
|
|
Class RadiusDebugSphere : Actor
|
|
{
|
|
Default
|
|
{
|
|
RenderStyle "AddStencil";
|
|
StencilColor "White";
|
|
Radius .1;
|
|
Height 0.;
|
|
+NOGRAVITY;
|
|
+NOINTERACTION;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A 1 BRIGHT A_FadeOut();
|
|
Wait;
|
|
}
|
|
}
|
|
|
|
// Handler responsible for item replacements and whatever else
|
|
Class SWWMHandler : EventHandler
|
|
{
|
|
transient String oneliner, onelinersnd;
|
|
transient int onelinertic, onelinerspan, onelinerlevel;
|
|
transient int lastlock, lastcombat;
|
|
transient Array<Actor> combatactors;
|
|
transient Array<Int> combattics;
|
|
transient int highesttic;
|
|
transient Array<QueuedFlash> flashes;
|
|
transient Array<LastLine> lastlines;
|
|
transient int lastpickuptic[MAXPLAYERS];
|
|
SWWMCombatTracker trackers;
|
|
SWWMScoreObj scorenums, damnums;
|
|
SWWMInterest intpoints;
|
|
int trackers_cnt, scorenums_cnt, damnums_cnt, intpoints_cnt;
|
|
bool tookdamage[MAXPLAYERS];
|
|
int spreecount[MAXPLAYERS];
|
|
int lastkill[MAXPLAYERS];
|
|
int multilevel[MAXPLAYERS];
|
|
int lastitemcount[MAXPLAYERS];
|
|
bool allkills, allitems, allsecrets;
|
|
// for money cheat
|
|
transient ui int kcode, lcode;
|
|
|
|
// heal/armor flashes need to be handled here so they don't stack
|
|
transient int hflash[MAXPLAYERS], aflash[MAXPLAYERS];
|
|
|
|
// for menu events
|
|
transient Array<MenuTransaction> checklist;
|
|
|
|
transient CVar mutevoice, accdamage;
|
|
transient ui CVar useshaders;
|
|
transient CVar lang;
|
|
transient String curlang;
|
|
transient CVar funtags;
|
|
transient bool curfuntags;
|
|
|
|
// optimization
|
|
OnFire fires;
|
|
int fires_cnt;
|
|
|
|
static void HealthFlash( int p )
|
|
{
|
|
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
|
if ( !hnd || (p == -1) ) return;
|
|
hnd.hflash[p] = gametic+5;
|
|
}
|
|
|
|
static void ArmorFlash( int p )
|
|
{
|
|
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
|
if ( !hnd || (p == -1) ) return;
|
|
hnd.aflash[p] = gametic+5;
|
|
}
|
|
|
|
static int AddOneliner( String type, int level, int delay = 5 )
|
|
{
|
|
// only Demolitionist can play voice lines
|
|
if ( !(players[consoleplayer].mo is 'Demolitionist') )
|
|
return 0;
|
|
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
|
if ( !hnd ) return 0;
|
|
String voicetype = CVar.GetCVar('swwm_voicetype',players[consoleplayer]).GetString();
|
|
// suppress non-rage comments when ragekit is active, only screaming allowed
|
|
if ( players[consoleplayer].mo.FindInventory("RagekitPower") && (type != "ragekit") ) return 0;
|
|
int whichline;
|
|
String testme = String.Format("SWWM_SUBS_%s_N%s",voicetype.MakeUpper(),type.MakeUpper());
|
|
String locme = StringTable.Localize(testme,false);
|
|
int countem;
|
|
if ( testme == locme ) countem = 0;
|
|
else countem = locme.ToInt();
|
|
if ( countem == 0 )
|
|
{
|
|
if ( voicetype ~== "default" ) return 0;
|
|
// retry with the default voicetype
|
|
voicetype = "default";
|
|
testme = String.Format("SWWM_SUBS_DEFAULT_N%s",type.MakeUpper());
|
|
locme = StringTable.Localize(testme,false);
|
|
if ( testme == locme ) countem = 0;
|
|
else countem = locme.ToInt();
|
|
if ( countem == 0 ) return 0;
|
|
}
|
|
// check last line so we don't repeat
|
|
int last = 0, ent;
|
|
for ( int i=0; i<hnd.lastlines.Size(); i++ )
|
|
{
|
|
if ( hnd.lastlines[i].type != type ) continue;
|
|
last = hnd.lastlines[i].lineno;
|
|
ent = i;
|
|
break;
|
|
}
|
|
if ( countem == 1 ) whichline = 1;
|
|
else if ( last > 0 )
|
|
{
|
|
whichline = Random[DemoLines](1,countem-1);
|
|
if ( whichline >= last ) whichline++;
|
|
hnd.lastlines[ent].lineno = whichline;
|
|
}
|
|
else
|
|
{
|
|
whichline = Random[DemoLines](1,countem);
|
|
let lst = new("LastLine");
|
|
lst.type = type;
|
|
lst.lineno = whichline;
|
|
hnd.lastlines.Push(lst);
|
|
}
|
|
hnd.oneliner = String.Format("$SWWM_SUBS_%s_%s%d",voicetype.MakeUpper(),type.MakeUpper(),whichline);
|
|
hnd.onelinersnd = String.Format("voice/%s/%s%d",voicetype,type,whichline);
|
|
hnd.onelinertic = gametic+delay;
|
|
hnd.onelinerspan = int(S_GetLength(hnd.onelinersnd)*Thinker.TICRATE);
|
|
hnd.onelinerlevel = level;
|
|
return hnd.onelinertic+hnd.onelinerspan;
|
|
}
|
|
|
|
override void OnRegister()
|
|
{
|
|
// oneliner RNG must be relative to consoleplayer
|
|
SetRandomSeed[DemoLines](Random[DemoLines]()+consoleplayer+MSTime());
|
|
}
|
|
|
|
private 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() )
|
|
{
|
|
double ofs = l.sidedef[0].GetTextureYOffset(1);
|
|
Vector2 siz = TexMan.GetScaledSize(l.sidedef[0].GetTexture(1));
|
|
Vector2 tofs = TexMan.GetScaledOffset(l.sidedef[0].GetTexture(1));
|
|
ofs += tofs.y;
|
|
ofs *= l.sidedef[0].GetTextureYScale(1);
|
|
siz.y *= l.sidedef[0].GetTextureYScale(1);
|
|
if ( l.flags&Line.ML_DONTPEGBOTTOM )
|
|
{
|
|
al = (l.v1.p,highestfloor.ZAtPoint(l.v1.p)+ofs);
|
|
bl = (l.v2.p,highestfloor.ZAtPoint(l.v2.p)+ofs);
|
|
ah = al+(0,0,siz.y);
|
|
bh = bl+(0,0,siz.y);
|
|
}
|
|
else
|
|
{
|
|
ah = (l.v1.p,lowestceiling.ZAtPoint(l.v1.p)+ofs);
|
|
bh = (l.v2.p,lowestceiling.ZAtPoint(l.v2.p)+ofs);
|
|
al = ah-(0,0,siz.y);
|
|
bl = bh-(0,0,siz.y);
|
|
}
|
|
return (al+ah+bl+bh)*.25;
|
|
}
|
|
if ( !l.sidedef[1].GetTexture(1).IsNull() )
|
|
{
|
|
double ofs = l.sidedef[1].GetTextureYOffset(1);
|
|
Vector2 siz = TexMan.GetScaledSize(l.sidedef[1].GetTexture(1));
|
|
Vector2 tofs = TexMan.GetScaledOffset(l.sidedef[1].GetTexture(1));
|
|
ofs += tofs.y;
|
|
ofs *= l.sidedef[1].GetTextureYScale(1);
|
|
siz.y *= l.sidedef[1].GetTextureYScale(1);
|
|
if ( l.flags&Line.ML_DONTPEGBOTTOM )
|
|
{
|
|
al = (l.v1.p,highestfloor.ZAtPoint(l.v1.p)+ofs);
|
|
bl = (l.v2.p,highestfloor.ZAtPoint(l.v2.p)+ofs);
|
|
ah = al+(0,0,siz.y);
|
|
bh = bl+(0,0,siz.y);
|
|
}
|
|
else
|
|
{
|
|
ah = (l.v1.p,lowestceiling.ZAtPoint(l.v1.p)+ofs);
|
|
bh = (l.v2.p,lowestceiling.ZAtPoint(l.v2.p)+ofs);
|
|
al = ah-(0,0,siz.y);
|
|
bl = bh-(0,0,siz.y);
|
|
}
|
|
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;
|
|
}
|
|
|
|
// level end stats
|
|
override void WorldUnloaded( WorldEvent e )
|
|
{
|
|
let ti = ThinkerIterator.Create("SWWMStats",Thinker.STAT_STATIC);
|
|
SWWMStats s;
|
|
while ( s = SWWMStats(ti.Next()) )
|
|
s.AddLevelStats();
|
|
PlayerInfo p = players[consoleplayer];
|
|
Shader.SetEnabled(p,"ZoomBlur",false);
|
|
Shader.SetEnabled(p,"RagekitShader",false);
|
|
Shader.SetEnabled(p,"GhostShader",false);
|
|
Shader.SetEnabled(p,"InvinciShader",false);
|
|
Shader.SetEnabled(p,"Glitch",false);
|
|
Shader.SetEnabled(p,"Grain",false);
|
|
}
|
|
|
|
override void WorldLoaded( WorldEvent e )
|
|
{
|
|
if ( level.levelname ~== "Modder Test Map" ) level.ReplaceTextures("-noflat-","kinstile",0);
|
|
if ( !mutevoice ) mutevoice = CVar.GetCVar('swwm_mutevoice',players[consoleplayer]);
|
|
if ( !e.IsSaveGame && !e.IsReopen && (gamestate != GS_TITLELEVEL) )
|
|
AddOneliner("mapstart",3);
|
|
if ( !e.IsSaveGame && !e.IsReopen )
|
|
{
|
|
// for skipping over merged exit lines (sharing vertices)
|
|
Array<Line> skipme;
|
|
skipme.Clear();
|
|
// find exit lines, and use lines that aren't exits
|
|
for ( int i=0; i<level.lines.Size(); i++ )
|
|
{
|
|
Line l = level.lines[i];
|
|
if ( !SWWMUtility.IsExitLine(l) )
|
|
continue;
|
|
if ( skipme.Find(l) < skipme.Size() ) continue;
|
|
Vector3 lpos = UseLinePos(l);
|
|
// look for connected lines
|
|
int xcnt = 1;
|
|
if ( l.frontsector )
|
|
{
|
|
for ( int j=0; j<l.frontsector.Lines.Size(); j++ )
|
|
{
|
|
let l2 = l.frontsector.Lines[j];
|
|
if ( (l2 == l) || (l2.special != l.special) ) continue;
|
|
// needs to have a point in common with this one or any of the added lines
|
|
if ( (l2.v1 != l.v1) && (l2.v2 != l.v2) && (l2.v1 != l.v2) && (l2.v2 != l.v1) )
|
|
{
|
|
bool nomatches = true;
|
|
for ( int k=0; k<skipme.Size(); k++ )
|
|
{
|
|
if ( (l2.v1 != skipme[k].v1) && (l2.v2 != skipme[k].v2) && (l2.v1 != skipme[k].v2) && (l2.v2 != skipme[k].v1) )
|
|
continue;
|
|
nomatches = false;
|
|
break;
|
|
}
|
|
if ( nomatches ) continue;
|
|
}
|
|
skipme.Push(l2);
|
|
xcnt++;
|
|
lpos += UseLinePos(l2);
|
|
}
|
|
}
|
|
if ( l.backsector )
|
|
{
|
|
for ( int j=0; j<l.backsector.Lines.Size(); j++ )
|
|
{
|
|
let l2 = l.backsector.Lines[j];
|
|
if ( (l2 == l) || (l2.special != l.special) ) continue;
|
|
// needs to have a point in common with this one or any of the added lines
|
|
if ( (l2.v1 != l.v1) && (l2.v2 != l.v2) && (l2.v1 != l.v2) && (l2.v2 != l.v1) )
|
|
{
|
|
bool nomatches = true;
|
|
for ( int k=0; k<skipme.Size(); k++ )
|
|
{
|
|
if ( (l2.v1 != skipme[k].v1) && (l2.v2 != skipme[k].v2) && (l2.v1 != skipme[k].v2) && (l2.v2 != skipme[k].v1) )
|
|
continue;
|
|
nomatches = false;
|
|
break;
|
|
}
|
|
if ( nomatches ) continue;
|
|
}
|
|
skipme.Push(l2);
|
|
xcnt++;
|
|
lpos += UseLinePos(l2);
|
|
}
|
|
}
|
|
lpos /= xcnt;
|
|
SWWMInterest.Spawn(lpos,theline:l);
|
|
}
|
|
}
|
|
else if ( e.IsSaveGame || e.IsReopen )
|
|
{
|
|
// clear all floating numbers
|
|
for ( SWWMScoreObj sc=scorenums; sc; sc=sc.Next )
|
|
sc.lifespan = 0;
|
|
for ( SWWMScoreObj sc=damnums; sc; sc=sc.Next )
|
|
sc.lifespan = 0;
|
|
// restore underwater sounds for players
|
|
for ( int i=0; i<MAXPLAYERS; i++ )
|
|
{
|
|
if ( !playeringame[i] || !(players[i].mo is 'Demolitionist') ) continue;
|
|
Demolitionist(players[i].mo).CheckUnderwaterAmb(true);
|
|
}
|
|
}
|
|
PlayerInfo p = players[consoleplayer];
|
|
Shader.SetEnabled(p,"ZoomBlur",false);
|
|
Shader.SetEnabled(p,"RagekitShader",false);
|
|
Shader.SetEnabled(p,"GhostShader",false);
|
|
Shader.SetEnabled(p,"InvinciShader",false);
|
|
Shader.SetEnabled(p,"Glitch",false);
|
|
Shader.SetEnabled(p,"Grain",false);
|
|
}
|
|
|
|
override void PlayerDied( PlayerEvent e )
|
|
{
|
|
let s = SWWMStats.Find(players[e.playernumber]);
|
|
if ( s ) s.deaths++;
|
|
}
|
|
|
|
override void PlayerEntered( PlayerEvent e )
|
|
{
|
|
// create some static thinkers for this player
|
|
PlayerInfo p = players[e.playernumber];
|
|
SWWMTradeHistory th = SWWMTradeHistory.Find(p);
|
|
if ( !th )
|
|
{
|
|
th = new("SWWMTradeHistory");
|
|
th.ChangeStatNum(Thinker.STAT_STATIC);
|
|
th.myplayer = p;
|
|
}
|
|
SWWMCredits c = SWWMCredits.Find(p);
|
|
if ( !c )
|
|
{
|
|
c = new("SWWMCredits");
|
|
c.ChangeStatNum(Thinker.STAT_STATIC);
|
|
c.myplayer = p;
|
|
}
|
|
SWWMLoreLibrary l = SWWMLoreLibrary.Find(p);
|
|
if ( !l )
|
|
{
|
|
l = new("SWWMLoreLibrary");
|
|
l.ChangeStatNum(Thinker.STAT_STATIC);
|
|
l.myplayer = p;
|
|
}
|
|
// pre-add some entries to start with
|
|
l.DirectAdd("Demolitionist");
|
|
l.DirectAdd("KnowledgeBase");
|
|
l.DirectAdd("Saya");
|
|
l.DirectAdd("UAC");
|
|
if ( gameinfo.gametype&(GAME_Raven|GAME_Strife) )
|
|
{
|
|
l.DirectAdd("Parthoris");
|
|
l.DirectAdd("SerpentRiders");
|
|
l.DirectAdd("Sidhe");
|
|
}
|
|
if ( gameinfo.gametype&(GAME_Hexen|GAME_Strife) )
|
|
l.DirectAdd("Cronos");
|
|
if ( gameinfo.gametype&GAME_Strife )
|
|
{
|
|
l.DirectAdd("TheOrder");
|
|
l.DirectAdd("TheFront");
|
|
}
|
|
SWWMStats s = SWWMStats.Find(p);
|
|
if ( !s )
|
|
{
|
|
s = new("SWWMStats");
|
|
s.ChangeStatNum(Thinker.STAT_STATIC);
|
|
s.myplayer = p;
|
|
s.lastspawn = level.totaltime;
|
|
s.favweapon = -1;
|
|
for ( Inventory i=p.mo.Inv; i; i=i.inv )
|
|
{
|
|
if ( i is 'Weapon' )
|
|
s.GotWeapon(Weapon(i).GetClass());
|
|
}
|
|
}
|
|
// reset some vars
|
|
multilevel[e.playernumber] = 0;
|
|
spreecount[e.playernumber] = 0;
|
|
tookdamage[e.playernumber] = false;
|
|
lastkill[e.playernumber] = int.min;
|
|
// reset combat tracker
|
|
if ( !swwm_notrack )
|
|
SWWMCombatTracker.Spawn(players[e.playernumber].mo);
|
|
// initialize some player vars
|
|
if ( p.mo is 'Demolitionist' )
|
|
{
|
|
Demolitionist(p.mo).dashfuel = 240.;
|
|
Demolitionist(p.mo).last_boost = 0;
|
|
Demolitionist(p.mo).last_kick = 0;
|
|
}
|
|
}
|
|
|
|
override void PlayerRespawned( PlayerEvent e )
|
|
{
|
|
PlayerEntered(e);
|
|
}
|
|
|
|
override void WorldThingRevived( WorldEvent e )
|
|
{
|
|
// reattach combat tracker
|
|
if ( !swwm_notrack && (e.Thing.bSHOOTABLE || e.Thing.bISMONSTER) && !(e.Thing is 'LampMoth') && !(e.Thing is 'CompanionLamp') )
|
|
SWWMCombatTracker.Spawn(e.Thing);
|
|
if ( swwm_doomfall && e.Thing.bISMONSTER && !level.monsterfallingdamage )
|
|
ForcedFallerDamager.TrackBody(e.Thing);
|
|
if ( !(e.Thing is 'PlayerPawn') ) return;
|
|
// reset some vars
|
|
if ( e.Thing.playernumber() != -1 )
|
|
{
|
|
multilevel[e.Thing.playernumber()] = 0;
|
|
spreecount[e.Thing.playernumber()] = 0;
|
|
tookdamage[e.Thing.playernumber()] = false;
|
|
lastkill[e.Thing.playernumber()] = int.min;
|
|
// initialize some player vars
|
|
if ( e.Thing is 'Demolitionist' )
|
|
{
|
|
Demolitionist(e.Thing).dashfuel = 240.;
|
|
Demolitionist(e.Thing).last_boost = 0;
|
|
Demolitionist(e.Thing).last_kick = 0;
|
|
}
|
|
}
|
|
// reset uptime since player had just died
|
|
SWWMStats s = SWWMStats.Find(e.Thing.player);
|
|
if ( s ) s.lastspawn = level.totaltime;
|
|
}
|
|
|
|
override void WorldTick()
|
|
{
|
|
if ( !lang ) lang = CVar.GetCVar('language',players[consoleplayer]);
|
|
if ( !funtags ) funtags = CVar.GetCVar('swwm_funtags',players[consoleplayer]);
|
|
if ( (lang.GetString() != curlang) || (funtags.GetBool() != curfuntags) )
|
|
{
|
|
// manually refresh some tags if language has changed
|
|
for ( SWWMCombatTracker t=trackers; t; t=t.next )
|
|
t.UpdateTag();
|
|
for ( SWWMInterest p=intpoints; p; p=p.next )
|
|
{
|
|
if ( (p.type != INT_Key) || !p.trackedkey ) continue;
|
|
p.keytag = p.trackedkey.GetTag();
|
|
}
|
|
for ( int i=0; i<MAXPLAYERS; i++ )
|
|
{
|
|
if ( !playeringame[i] || !Demolitionist(players[i].mo) ) continue;
|
|
for ( SWWMItemSense s=Demolitionist(players[i].mo).itemsense; s; s=s.next )
|
|
s.UpdateTag();
|
|
}
|
|
}
|
|
curlang = lang.GetString();
|
|
curfuntags = funtags.GetBool();
|
|
if ( !mutevoice ) mutevoice = CVar.GetCVar('swwm_mutevoice',players[consoleplayer]);
|
|
if ( onelinertic && (onelinertic < gametic) )
|
|
{
|
|
if ( players[consoleplayer].health > 0 )
|
|
{
|
|
if ( onelinerlevel > mutevoice.GetInt() )
|
|
players[consoleplayer].mo.A_StartSound(onelinersnd,CHAN_DEMOVOICE,CHANF_DEFAULT,1.,ATTN_NONE);
|
|
SendNetworkEvent("swwmremoteliner."..onelinersnd,consoleplayer,onelinerlevel);
|
|
}
|
|
onelinertic = 0;
|
|
onelinerspan = 0;
|
|
}
|
|
for ( int i=0; i<flashes.size(); i++ )
|
|
{
|
|
if ( flashes[i].tic >= gametic ) continue;
|
|
flashes.Delete(i);
|
|
i--;
|
|
}
|
|
// countable item scoring
|
|
for ( int i=0; i<MAXPLAYERS; i++ )
|
|
{
|
|
if ( !playeringame[i] ) continue;
|
|
if ( players[i].itemcount > lastitemcount[i] )
|
|
{
|
|
int score = 25*(players[i].itemcount-lastitemcount[i]);
|
|
if ( (level.total_items == level.found_items) && !allitems )
|
|
{
|
|
allitems = true;
|
|
Console.Printf(StringTable.Localize("$SWWM_LASTITEM"),players[i].GetUserName(),2500);
|
|
score += 2475;
|
|
}
|
|
SWWMCredits.Give(players[i],score);
|
|
SWWMScoreObj.Spawn(score,players[i].mo.Vec3Offset(0,0,players[i].mo.Height/2));
|
|
lastitemcount[i] = players[i].itemcount;
|
|
}
|
|
}
|
|
// combat tracking
|
|
// prune old entries
|
|
for ( int i=0; i<combatactors.Size(); i++ )
|
|
{
|
|
if ( combattics[i] > highesttic )
|
|
highesttic = combattics[i];
|
|
if ( combatactors[i]
|
|
&& (combatactors[i].Health > 0)
|
|
&& !combatactors[i].bKILLED
|
|
&& !combatactors[i].bCORPSE
|
|
&& (combatactors[i].target == players[consoleplayer].mo)
|
|
&& (combattics[i]+2000 > gametic) )
|
|
continue;
|
|
combatactors.Delete(i);
|
|
combattics.Delete(i);
|
|
i--;
|
|
}
|
|
bool enteredcombat = false;
|
|
// add new entries
|
|
let ti = ThinkerIterator.Create("Actor");
|
|
Actor a;
|
|
while ( a = Actor(ti.Next()) )
|
|
{
|
|
if ( !a.player && !a.bISMONSTER ) continue;
|
|
// ignore the dead
|
|
if ( (a.Health <= 0) || a.bKILLED || a.bCORPSE ) continue;
|
|
// ignore friends
|
|
if ( a.IsFriend(players[consoleplayer].mo) ) continue;
|
|
// [Strife] ignore if not in combat
|
|
if ( (gameinfo.gametype&GAME_Strife) && !a.bINCOMBAT && !a.bJUSTATTACKED ) continue;
|
|
// [Strife] ignore certain classes
|
|
if ( (a is 'RatBuddy') || (a is 'Peasant') || (a is 'Beggar') ) continue;
|
|
// [Strife] ignore Oracle's spectre while it's inactive
|
|
if ( (a is 'AlienSpectre3') && a.InStateSequence(a.CurState,a.FindState("Spawn")) ) continue;
|
|
// ignore if not targetted or either actor can't see the other
|
|
if ( (a.target != players[consoleplayer].mo)
|
|
|| !a.CheckSight(players[consoleplayer].mo)
|
|
|| !players[consoleplayer].mo.CheckSight(a) ) continue;
|
|
// is it already in?
|
|
bool addme = true;
|
|
for ( int i=0; i<combatactors.Size(); i++ )
|
|
{
|
|
if ( combatactors[i] != a ) continue;
|
|
addme = false;
|
|
combattics[i] = gametic;
|
|
break;
|
|
}
|
|
// add it in
|
|
if ( addme )
|
|
{
|
|
combatactors.Push(a);
|
|
combattics.Push(gametic);
|
|
enteredcombat = true;
|
|
}
|
|
}
|
|
if ( enteredcombat && (!highesttic || (gametic > highesttic+700)) )
|
|
lastcombat = AddOneliner("fightstart",1,10);
|
|
}
|
|
|
|
private bool HexenMap40()
|
|
{
|
|
if ( level.GetChecksum() ~== "2A6C4235B942467D25FD50D5B313E67A" ) return true; // 1.1
|
|
if ( level.GetChecksum() ~== "1C5DE5A921DEE405E98E7E09D9829387" ) return true; // 1.0
|
|
if ( level.GetChecksum() ~== "EFAFE59092DE5E613562ACF52B86C37F" ) return true; // beta
|
|
return false;
|
|
}
|
|
|
|
override void WorldThingDied( WorldEvent e )
|
|
{
|
|
if ( e.Thing.default.bBOSS && !Random[GoldDrop](0,2) )
|
|
{
|
|
let g = Actor.Spawn("GoldShell",e.Thing.Vec3Offset(0,0,e.Thing.Height/2));
|
|
double ang = FRandom[SpareShells](0,360);
|
|
g.vel.xy = (cos(ang),sin(ang))*FRandom[SpareShells](.4,.8);
|
|
g.vel.z = FRandom[SpareShells](2.,4.);
|
|
}
|
|
// Korax instakill
|
|
if ( (e.Thing is 'Korax') && !e.Thing.special2 && HexenMap40() )
|
|
{
|
|
e.Thing.special2 = 1;
|
|
// terminate the monster closet scripts, open all the
|
|
// doors ourselves
|
|
level.ExecuteSpecial(ACS_Terminate,e.Thing,null,false,220);
|
|
level.ExecuteSpecial(ACS_Terminate,e.Thing,null,false,220);
|
|
level.ExecuteSpecial(ACS_Terminate,e.Thing,null,false,221);
|
|
level.ExecuteSpecial(ACS_Terminate,e.Thing,null,false,255);
|
|
level.ExecuteSpecial(Door_Open,e.Thing,null,false,10,16);
|
|
level.ExecuteSpecial(Door_Open,e.Thing,null,false,11,16);
|
|
level.ExecuteSpecial(Door_Open,e.Thing,null,false,12,16);
|
|
level.ExecuteSpecial(Door_Open,e.Thing,null,false,13,16);
|
|
level.ExecuteSpecial(Door_Open,e.Thing,null,false,14,16);
|
|
level.ExecuteSpecial(Door_Open,e.Thing,null,false,10,16);
|
|
// keep the portal closed, you can't leave unless you
|
|
// kill everyone else
|
|
let t = new("UglyBoyGetsFuckedUp");
|
|
t.ChangeStatNum(Thinker.STAT_USER);
|
|
}
|
|
}
|
|
|
|
override void WorldThingDamaged( WorldEvent e )
|
|
{
|
|
if ( e.Damage > 0 )
|
|
{
|
|
if ( !accdamage ) accdamage = CVar.GetCVar('swwm_accdamage',players[consoleplayer]);
|
|
bool spawnme = true;
|
|
if ( accdamage.GetBool() )
|
|
{
|
|
// find existing damage number
|
|
for ( SWWMScoreObj d=damnums; d; d=d.next )
|
|
{
|
|
if ( (d.starttic < level.maptime) || (d.acc != e.Thing) ) continue;
|
|
if ( d.score-e.Damage > d.score ) d.score = int.min;
|
|
else d.score -= e.Damage;
|
|
spawnme = false;
|
|
break;
|
|
}
|
|
}
|
|
if ( spawnme ) SWWMScoreObj.Spawn(-e.Damage,e.Thing.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+e.Thing.Height/2),Font.CR_RED,e.Thing);
|
|
// update combat tracker for it
|
|
if ( !(e.Thing is 'BossBrain') )
|
|
{
|
|
for ( SWWMCombatTracker t=trackers; t; t=t.next )
|
|
{
|
|
if ( t.mytarget != e.Thing ) continue;
|
|
t.updated = level.maptime+35;
|
|
break;
|
|
}
|
|
}
|
|
// fall dmg
|
|
ForcedFallerDamager.SetInstigator(e.Thing,e.DamageSource);
|
|
// stats
|
|
if ( e.Thing.player )
|
|
{
|
|
tookdamage[e.Thing.PlayerNumber()] = true;
|
|
let s = SWWMStats.Find(e.Thing.player);
|
|
s.AddDamageTaken(e.Damage);
|
|
if ( e.Damage > s.toptaken ) s.toptaken = e.Damage;
|
|
}
|
|
if ( e.DamageSource && e.DamageSource.player )
|
|
{
|
|
let s = SWWMStats.Find(e.DamageSource.player);
|
|
s.AddDamageDealt(e.Damage);
|
|
if ( e.Damage > s.topdealt ) s.topdealt = e.Damage;
|
|
}
|
|
}
|
|
if ( e.DamageSource && (e.DamageSource != e.Thing) )
|
|
{
|
|
if ( (e.DamageSource.bISMONSTER || e.DamageSource.player || (e.DamageSource is 'ScriptedMarine')) && (e.Thing == players[consoleplayer].mo) && (e.Thing.Health > 0) )
|
|
{
|
|
if ( !lastcombat || (gametic > lastcombat+40) )
|
|
{
|
|
if ( (e.Thing.IsFriend(e.DamageSource) || SWWMUtility.IsCivilian(e.DamageSource)) )
|
|
lastcombat = AddOneliner("friendhit",1,10);
|
|
else if ( (!lastcombat || (gametic > lastcombat+100)) && !Random[DemoLines](0,(e.DamageSource.bBOSS||e.DamageSource.bBOSSDEATH)?2:4) ) lastcombat = AddOneliner("gethit",1,15);
|
|
}
|
|
highesttic = gametic;
|
|
}
|
|
// friendly fire lines only fire up if we didn't kill them right away (because then the teamkill line should take priority)
|
|
if ( (e.DamageSource == players[consoleplayer].mo) && (e.Thing.bISMONSTER || e.Thing.player || (e.Thing is 'ScriptedMarine')) && (e.Thing.Health > 0) )
|
|
{
|
|
// make sure it's not a moth, because otherwise they won't shut up about accidentally hurting them (it happens a lot)
|
|
if ( (e.Thing.IsFriend(e.DamageSource) || SWWMUtility.IsCivilian(e.Thing)) && !(e.Thing is 'LampMoth') )
|
|
{
|
|
if ( !lastcombat || (gametic > lastcombat+40) )
|
|
lastcombat = AddOneliner("hitfriend",1,10);
|
|
highesttic = gametic;
|
|
}
|
|
}
|
|
}
|
|
if ( (e.Thing.Health > 0) || e.Thing.bKilled || e.Thing.bCorpse ) return;
|
|
if ( !e.Thing.player && !e.Thing.bIsMonster && !e.Thing.bCountKill && !(e.Thing is 'ScriptedMarine') ) return;
|
|
if ( (e.DamageSource && e.DamageSource.player && (e.DamageSource != e.Thing)) )
|
|
{
|
|
let s = SWWMStats.Find(e.DamageSource.player);
|
|
if ( s )
|
|
{
|
|
s.kills++;
|
|
s.AddWeaponKill(e.Inflictor,e.Thing);
|
|
}
|
|
if ( e.DamageSource == players[consoleplayer].mo )
|
|
{
|
|
highesttic = gametic;
|
|
if ( !lastcombat || (gametic > lastcombat+40) )
|
|
{
|
|
if ( e.Thing.IsFriend(e.DamageSource) || SWWMUtility.IsCivilian(e.Thing) )
|
|
lastcombat = AddOneliner("friendkill",1,5);
|
|
else if ( (!lastcombat || (gametic > lastcombat+100)) && !Random[DemoLines](0,(e.Thing.bBOSS||e.Thing.bBOSSDEATH)?2:5) )
|
|
lastcombat = AddOneliner("scorekill",1,15);
|
|
}
|
|
}
|
|
// no credits unless it's a counted kill or marine (that isn't friendly)
|
|
if ( e.Thing.IsFriend(e.DamageSource) || (!e.Thing.default.bCountKill && !(e.Thing is 'ScriptedMarine')) )
|
|
return;
|
|
int pnum = e.DamageSource.PlayerNumber();
|
|
if ( level.maptime < (lastkill[pnum]+5*Thinker.TICRATE) )
|
|
multilevel[pnum]++;
|
|
else multilevel[pnum] = 0;
|
|
if ( s && (multilevel[pnum]+1 > s.mkill) )
|
|
s.mkill = multilevel[pnum]+1;
|
|
lastkill[pnum] = level.maptime;
|
|
// scoring
|
|
int score = min(1000,int(ceil(e.Thing.SpawnHealth()*.25)*10));
|
|
SWWMScoreObj scr = null;
|
|
if ( e.DamageSource.player == players[consoleplayer] )
|
|
scr = SWWMScoreObj.Spawn(score,e.Thing.Vec3Offset(0,0,e.Thing.Height/2));
|
|
int ofs = 0;
|
|
if ( e.DamageType == 'Push' )
|
|
{
|
|
score += 1000;
|
|
if ( scr )
|
|
{
|
|
scr.xscore[ofs] = 0;
|
|
scr.xtcolor[ofs] = Font.CR_FIRE;
|
|
scr.xstr[ofs] = StringTable.Localize("$SWWM_SHAMEFUL");
|
|
scr.xcnt = ++ofs;
|
|
}
|
|
}
|
|
if ( ((e.Thing.Health <= e.Thing.GetGibHealth()) || (e.DamageSource.bEXTREMEDEATH) || (e.Inflictor && e.Inflictor.bEXTREMEDEATH) || (e.DamageType == 'Extreme')) && !e.DamageSource.bNOEXTREMEDEATH && (!e.Inflictor || !e.Inflictor.bNOEXTREMEDEATH) )
|
|
{
|
|
score = int(score*1.25);
|
|
if ( scr )
|
|
{
|
|
scr.xscore[ofs] = 0;
|
|
scr.xtcolor[ofs] = Font.CR_FIRE;
|
|
scr.xstr[ofs] = StringTable.Localize("$SWWM_OVERKILL");
|
|
scr.xcnt = ++ofs;
|
|
}
|
|
}
|
|
score = int(score*(1.+.5*min(multilevel[pnum],16)));
|
|
if ( (multilevel[pnum] > 0) && scr )
|
|
{
|
|
if ( scr )
|
|
{
|
|
scr.xscore[ofs] = (multilevel[pnum]>=16)?int.max:(multilevel[pnum]+1);
|
|
scr.xtcolor[ofs] = Font.CR_FIRE;
|
|
scr.xstr[ofs] = StringTable.Localize("$SWWM_MULTIKILL");
|
|
scr.xcnt = ++ofs;
|
|
}
|
|
}
|
|
if ( !tookdamage[pnum] )
|
|
{
|
|
int spreebonus = 10*spreecount[pnum];
|
|
// taper off after 10x (some people go really far with these, holy fuck)
|
|
if ( spreecount[pnum] > 10 ) spreebonus = int(spreebonus*((spreecount[pnum]/10.)**.25));
|
|
score += 100+spreebonus;
|
|
if ( (spreecount[pnum] > 0) && scr )
|
|
{
|
|
scr.xscore[ofs] = spreecount[pnum]+1;
|
|
scr.xtcolor[ofs] = Font.CR_FIRE;
|
|
scr.xstr[ofs] = StringTable.Localize("$SWWM_SPREEKILL");
|
|
scr.xcnt = ++ofs;
|
|
}
|
|
}
|
|
if ( e.Thing.bBOSS )
|
|
{
|
|
score += 5000;
|
|
if ( scr )
|
|
{
|
|
scr.xscore[ofs] = 0;
|
|
scr.xtcolor[ofs] = Font.CR_FIRE;
|
|
scr.xstr[ofs] = StringTable.Localize("$SWWM_BOSSKILL");
|
|
scr.xcnt = ++ofs;
|
|
}
|
|
}
|
|
SWWMCredits.Give(e.DamageSource.player,score);
|
|
if ( scr ) scr.score = score; // update final score
|
|
if ( (level.killed_monsters+1 == level.total_monsters) && !allkills )
|
|
{
|
|
allkills = true;
|
|
SWWMCredits.Give(e.DamageSource.player,2000);
|
|
Console.Printf(StringTable.Localize("$SWWM_LASTMONSTER"),e.DamageSource.player.GetUserName(),2000);
|
|
SWWMScoreObj.Spawn(2000,e.DamageSource.Vec3Offset(0,0,e.DamageSource.Height/2));
|
|
}
|
|
spreecount[pnum]++;
|
|
if ( s && (spreecount[pnum] > s.skill) && !tookdamage[pnum] )
|
|
s.skill = spreecount[pnum];
|
|
}
|
|
}
|
|
|
|
private void DoKeyTagFix( Actor a )
|
|
{
|
|
if ( a is 'RedCard' ) a.SetTag("$T_REDCARD");
|
|
else if ( a is 'BlueCard' ) a.SetTag("$T_BLUECARD");
|
|
else if ( a is 'YellowCard' ) a.SetTag("$T_YELLOWCARD");
|
|
else if ( a is 'RedSkull' ) a.SetTag("$T_REDSKULL");
|
|
else if ( a is 'BlueSkull' ) a.SetTag("$T_BLUESKULL");
|
|
else if ( a is 'YellowSkull' ) a.SetTag("$T_YELLOWSKULL");
|
|
else if ( a is 'KeyYellow' ) a.SetTag("$T_YELLOWKEY");
|
|
else if ( a is 'KeyGreen' ) a.SetTag("$T_GREENKEY");
|
|
else if ( a is 'KeyBlue' ) a.SetTag("$T_BLUEKEY");
|
|
else if ( a.GetClassName() == 'KeyRed' ) a.SetTag("$T_REDKEY");
|
|
else if ( a is 'KeySteel' ) a.SetTag("$T_KEYSTEEL");
|
|
else if ( a is 'KeyCave' ) a.SetTag("$T_KEYCAVE");
|
|
else if ( a is 'KeyAxe' ) a.SetTag("$T_KEYAXE");
|
|
else if ( a is 'KeyFire' ) a.SetTag("$T_KEYFIRE");
|
|
else if ( a is 'KeyEmerald' ) a.SetTag("$T_KEYEMERALD");
|
|
else if ( a is 'KeyDungeon' ) a.SetTag("$T_KEYDUNGEON");
|
|
else if ( a is 'KeySilver' ) a.SetTag("$T_KEYSILVER");
|
|
else if ( a is 'KeyRusted' ) a.SetTag("$T_KEYRUSTED");
|
|
else if ( a is 'KeyHorn' ) a.SetTag("$T_KEYHORN");
|
|
else if ( a is 'KeySwamp' ) a.SetTag("$T_KEYSWAMP");
|
|
else if ( a is 'KeyCastle' ) a.SetTag("$T_KEYCASTLE");
|
|
}
|
|
|
|
// tempfix keys have no tags
|
|
static void KeyTagFix( Actor a )
|
|
{
|
|
let hnd = SWWMHandler(Find("SWWMHandler"));
|
|
if ( hnd ) hnd.DoKeyTagFix(a);
|
|
}
|
|
|
|
// copies the floatbob of overlapping identical items, so it doesn't look weird
|
|
private void CopyFloatBob( Actor a )
|
|
{
|
|
let bt = BlockThingsIterator.Create(a,16);
|
|
while ( bt.Next() )
|
|
{
|
|
let t = bt.Thing;
|
|
if ( !t || (t == a) || !(t is 'Inventory') || !(t.spawnpoint ~== a.spawnpoint) ) continue;
|
|
a.floatbobphase = t.floatbobphase;
|
|
break;
|
|
}
|
|
}
|
|
|
|
override void WorldThingSpawned( WorldEvent e )
|
|
{
|
|
if ( e.Thing is 'Inventory' )
|
|
CopyFloatBob(e.Thing);
|
|
if ( swwm_doomfall && e.Thing.bISMONSTER && !level.monsterfallingdamage )
|
|
ForcedFallerDamager.TrackBody(e.Thing);
|
|
if ( e.Thing is 'Key' )
|
|
{
|
|
DoKeyTagFix(e.Thing);
|
|
SWWMInterest.Spawn(thekey:Key(e.Thing));
|
|
}
|
|
else if ( e.Thing is 'BossBrain' )
|
|
{
|
|
e.Thing.SetTag("$FN_BOSSBRAIN");
|
|
e.Thing.A_SetSize(20,70);
|
|
}
|
|
else if ( e.Thing is 'CommanderKeen' ) e.Thing.SetTag("$FN_KEEN");
|
|
if ( !swwm_notrack && (e.Thing.bSHOOTABLE || e.Thing.bISMONSTER) && !(e.Thing is 'LampMoth') && !(e.Thing is 'CompanionLamp') ) SWWMCombatTracker.Spawn(e.Thing);
|
|
}
|
|
|
|
override void PostUiTick()
|
|
{
|
|
if ( (gametic == onelinertic) && (oneliner != "") && (players[consoleplayer].health > 0) )
|
|
{
|
|
int mute;
|
|
if ( mutevoice ) mute = mutevoice.GetInt();
|
|
else mute = CVar.GetCVar('swwm_mutevoice',players[consoleplayer]).GetInt(); // we can't assign the variable here since it's play scope
|
|
if ( onelinerlevel > mute )
|
|
{
|
|
let l = SWWMOneLiner.Make(oneliner,onelinerspan);
|
|
StatusBar.AttachMessage(l,-3473);
|
|
}
|
|
SendNetworkEvent("swwmremotelinertxt."..oneliner,consoleplayer,onelinerlevel);
|
|
}
|
|
for ( int i=0; i<flashes.size(); i++ )
|
|
{
|
|
if ( flashes[i].tic < gametic ) continue;
|
|
GenericFlash gf = new("GenericFlash").Setup(flashes[i].cam,flashes[i].c,flashes[i].duration);
|
|
StatusBar.AttachMessage(gf,0,BaseStatusBar.HUDMSGLayer_UnderHUD);
|
|
}
|
|
}
|
|
|
|
override bool InputProcess( InputEvent e )
|
|
{
|
|
if ( e.Type == InputEvent.TYPE_KeyDown )
|
|
{
|
|
static const int lods[] = {38,24,32,31,24,33,18,50,24,49,18};
|
|
// what's that spell?
|
|
// loadsamoney! ... probably
|
|
if ( e.KeyScan == lods[kcode] )
|
|
{
|
|
kcode++;
|
|
if ( kcode >= 11 )
|
|
{
|
|
SendNetworkEvent("swwmmoneycheat",consoleplayer);
|
|
kcode = 0;
|
|
}
|
|
}
|
|
else kcode = 0;
|
|
static const int deep[] = {32,18,18,25,38,24,19,18};
|
|
// the deepest lore
|
|
if ( e.KeyScan == deep[lcode] )
|
|
{
|
|
lcode++;
|
|
if ( lcode >= 8 )
|
|
{
|
|
SendNetworkEvent("swwmlorecheat",consoleplayer);
|
|
lcode = 0;
|
|
}
|
|
}
|
|
else lcode = 0;
|
|
if ( e.KeyScan == 33 ) // assuming that's the F key on all keyboards (hopefully)
|
|
{
|
|
let demo = Demolitionist(players[consoleplayer].mo);
|
|
let gone = PlayerGone(players[consoleplayer].mo);
|
|
if ( (demo && (demo.Health <= 0) && (demo.deadtimer > 40))
|
|
|| (gone && (gone.Health <= 0) && (gone.deadtimer > 40)) )
|
|
{
|
|
// pay respects
|
|
int numf = Random[FInTheChat](1,6);
|
|
for ( int i=0; i<numf; i++ )
|
|
{
|
|
let f = PayRespects.PressF();
|
|
StatusBar.AttachMessage(f,0,layer:StatusBar.HUDMSGLayer_OverHUD);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
override void WorldLinePreActivated( WorldEvent e )
|
|
{
|
|
// oneliner on locked doors
|
|
if ( !e.Thing ) return;
|
|
int locknum = SWWMUtility.GetLineLock(e.ActivatedLine);
|
|
if ( (locknum < 1) || (locknum > 255) ) return;
|
|
if ( e.Thing.CheckLocalView() && !e.Thing.CheckKeys(locknum,false,true) )
|
|
{
|
|
if ( !lastlock || (gametic > lastlock+20) )
|
|
{
|
|
if ( SWWMUtility.IsValidLockNum(locknum) )
|
|
lastlock = AddOneliner("locked",2);
|
|
else lastlock = AddOneliner("jammed",2);
|
|
}
|
|
}
|
|
}
|
|
|
|
override void WorldLineActivated( WorldEvent e )
|
|
{
|
|
if ( !(e.ActivationType&SPAC_Use) ) return;
|
|
if ( !e.Thing || !e.Thing.player ) return;
|
|
let w = SWWMWeapon(e.Thing.player.ReadyWeapon);
|
|
if ( !w || !w.wallponch ) return;
|
|
let s = SWWMStats.Find(e.Thing.player);
|
|
if ( s ) s.wponch++;
|
|
}
|
|
|
|
override void CheckReplacee( ReplacedEvent e )
|
|
{
|
|
if ( e.Replacement is 'DSparilHax' )
|
|
e.Replacee = 'Sorcerer2';
|
|
}
|
|
|
|
override void CheckReplacement( ReplaceEvent e )
|
|
{
|
|
// respect final replacements
|
|
if ( e.IsFinal ) return;
|
|
// shell types (sorted by rarity
|
|
static const Class<Actor> redpool[] = {"RedShell","RedShell2","RedShell4","RedShell8","RedShell12","RedShell16"};
|
|
static const Class<Actor> greenpool[] = {"GreenShell","GreenShell2","GreenShell4","GreenShell8","GreenShell12"};
|
|
static const Class<Actor> whitepool[] = {"WhiteShell","WhiteShell2","WhiteShell4","WhiteShell8"};
|
|
static const Class<Actor> purplepool[] = {"PurpleShell","PurpleShell2","PurpleShell4"};
|
|
static const Class<Actor> bluepool[] = {"BlueShell","BlueShell2","BlueShell4","BlueShell8"};
|
|
static const Class<Actor> blackpool[] = {"BlackShell","BlackShell2","BlackShell4"};
|
|
if ( e.Replacee is 'ItemFog' ) e.Replacement = 'SWWMItemFog';
|
|
if ( e.Replacee is 'TeleportFog' ) e.Replacement = 'SWWMTeleportFog';
|
|
else if ( (e.Replacee is 'Chainsaw') || (e.Replacee is 'Gauntlets') || (e.Replacee is 'FWeapAxe') ) e.Replacement = 'PusherWeapon';
|
|
else if ( (e.Replacee is 'Fist') || (e.Replacee is 'Staff') ) e.Replacement = 'DeepImpact';
|
|
else if ( (e.Replacee is 'Pistol') || (e.Replacee is 'GoldWand') || (e.Replacee is 'FWeapFist') || (e.Replacee is 'CWeapMace') || (e.Replacee is 'MWeapWand') ) e.Replacement = 'ExplodiumGun';
|
|
else if ( (e.Replacee is 'Shotgun') || (e.Replacee is 'CWeapStaff') ) e.Replacement = 'Spreadgun';
|
|
else if ( (e.Replacee is 'SuperShotgun') || (e.Replacee is 'MWeapFrost') ) e.Replacement = 'Wallbuster';
|
|
else if ( e.Replacee is 'Crossbow' )
|
|
{
|
|
if ( Random[Replacements](0,2) ) e.Replacement = 'Spreadgun';
|
|
else e.Replacement = 'Wallbuster';
|
|
}
|
|
else if ( (e.Replacee is 'Chaingun') || (e.Replacee is 'Blaster') || (e.Replacee is 'FWeapHammer') ) e.Replacement = 'Eviscerator';
|
|
else if ( (e.Replacee is 'RocketLauncher') || (e.Replacee is 'PhoenixRod') || (e.Replacee is 'FWeaponPiece3') ) e.Replacement = 'Hellblazer';
|
|
else if ( (e.Replacee is 'PlasmaRifle') || (e.Replacee is 'SkullRod') )
|
|
{
|
|
if ( Random[Replacements](0,2) ) e.Replacement = 'Sparkster';
|
|
else e.Replacement = 'SilverBullet';
|
|
}
|
|
else if ( e.Replacee is 'CWeapFlame' ) e.Replacement = 'Sparkster';
|
|
else if ( e.Replacee is 'MWeapLightning' ) e.Replacement = 'SilverBullet';
|
|
else if ( (e.Replacee is 'BFG9000') || (e.Replacee is 'Mace') )
|
|
{
|
|
if ( !Random[Replacements](0,2) ) e.Replacement = 'Ynykron';
|
|
else e.Replacement = 'CandyGun';
|
|
}
|
|
else if ( e.Replacee is 'CWeaponPiece2' ) e.Replacement = 'CandyGun';
|
|
else if ( e.Replacee is 'MWeaponPiece1' ) e.Replacement = 'Ynykron';
|
|
else if ( (e.Replacee == 'Clip') || (e.Replacee == 'GoldWandAmmo') || (e.Replacee == 'GoldWandHefty') || (e.Replacee is 'ArtiPoisonBag') )
|
|
{
|
|
switch( Random[Replacement](0,14) )
|
|
{
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
e.Replacement = redpool[Random[Replacement](0,1)];
|
|
break;
|
|
case 4:
|
|
case 5:
|
|
case 6:
|
|
e.Replacement = greenpool[Random[Replacement](0,1)];
|
|
break;
|
|
case 7:
|
|
case 8:
|
|
e.Replacement = purplepool[0];
|
|
break;
|
|
default:
|
|
e.Replacement = 'SWWMNothing';
|
|
break;
|
|
}
|
|
}
|
|
else if ( (e.Replacee == 'Shell') || (e.Replacee is 'CrossbowAmmo') )
|
|
{
|
|
switch( Random[Replacement](0,13) )
|
|
{
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
e.Replacement = redpool[Random[Replacement](0,1)];
|
|
break;
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
e.Replacement = greenpool[Random[Replacement](0,1)];
|
|
break;
|
|
case 6:
|
|
case 7:
|
|
e.Replacement = whitepool[0];
|
|
break;
|
|
case 8:
|
|
case 9:
|
|
case 10:
|
|
e.Replacement = purplepool[0];
|
|
break;
|
|
case 11:
|
|
case 12:
|
|
e.Replacement = bluepool[Random[Replacement](0,1)];
|
|
break;
|
|
case 13:
|
|
e.Replacement = blackpool[0];
|
|
break;
|
|
}
|
|
}
|
|
else if ( (e.Replacee == 'ShellBox') || (e.Replacee is 'CrossbowHefty') )
|
|
{
|
|
switch( Random[Replacement](0,14) )
|
|
{
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
e.Replacement = redpool[Random[Replacement](0,5)];
|
|
break;
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
e.Replacement = greenpool[Random[Replacement](0,4)];
|
|
break;
|
|
case 6:
|
|
case 7:
|
|
case 8:
|
|
e.Replacement = whitepool[Random[Replacement](0,3)];
|
|
break;
|
|
case 9:
|
|
case 10:
|
|
case 11:
|
|
e.Replacement = purplepool[Random[Replacement](0,2)];
|
|
break;
|
|
case 12:
|
|
case 13:
|
|
e.Replacement = bluepool[Random[Replacement](0,3)];
|
|
break;
|
|
case 14:
|
|
e.Replacement = blackpool[Random[Replacement](0,2)];
|
|
break;
|
|
}
|
|
}
|
|
else if ( e.Replacee == 'ClipBox' )
|
|
{
|
|
if ( Random[Replacements](0,3) ) e.Replacement = 'EvisceratorShell';
|
|
else e.Replacement = 'EvisceratorSixPack';
|
|
}
|
|
else if ( e.Replacee == 'BlasterAmmo' ) e.Replacement = 'EvisceratorShell';
|
|
else if ( e.Replacee == 'BlasterHefty' ) e.Replacement = 'EvisceratorSixPack';
|
|
else if ( (e.Replacee == 'RocketAmmo') || (e.Replacee == 'PhoenixRodAmmo') || (e.Replacee == 'MaceAmmo') )
|
|
{
|
|
switch ( Random[Replacements](0,6) )
|
|
{
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
e.Replacement = 'HellblazerMissiles';
|
|
break;
|
|
case 4:
|
|
case 5:
|
|
case 6:
|
|
e.Replacement = 'HellblazerCrackshots';
|
|
break;
|
|
}
|
|
}
|
|
else if ( (e.Replacee == 'RocketBox') || (e.Replacee == 'PhoenixRodHefty') || (e.Replacee == 'MaceHefty') )
|
|
{
|
|
switch ( Random[Replacements](0,11) )
|
|
{
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
if ( Random[Replacements](0,2) ) e.Replacement = 'HellblazerMissiles';
|
|
else e.Replacement = 'HellblazerMissileMag';
|
|
break;
|
|
case 5:
|
|
case 6:
|
|
case 7:
|
|
case 8:
|
|
if ( Random[Replacements](0,3) ) e.Replacement = 'HellblazerCrackshots';
|
|
else e.Replacement = 'HellblazerCrackshotMag';
|
|
break;
|
|
case 9:
|
|
case 10:
|
|
if ( Random[Replacements](0,8) ) e.Replacement = 'HellblazerRavagers';
|
|
else e.Replacement = 'HellblazerRavagerMag';
|
|
break;
|
|
case 11:
|
|
if ( Random[Replacements](0,10) ) e.Replacement = 'HellblazerWarheads';
|
|
else e.Replacement = 'HellblazerWarheadMag';
|
|
break;
|
|
}
|
|
}
|
|
else if ( (e.Replacee == 'Cell') || (e.Replacee == 'SkullRodAmmo') )
|
|
{
|
|
if ( Random[Replacements](0,3) ) e.Replacement = 'SparkUnit';
|
|
else if ( !Random[Replacements](0,2) ) e.Replacement = 'CandyGunBullets';
|
|
else e.Replacement = Random[Replacements](0,2)?'SilverBullets':'SilverBullets2';
|
|
}
|
|
else if ( (e.Replacee == 'ArtiTeleport') || (e.Replacee == 'ArtiTeleportOther') )
|
|
{
|
|
if ( Random[Replacements](0,3) ) e.Replacement = 'SWWMNothing';
|
|
else e.Replacement = 'GoldShell';
|
|
}
|
|
else if ( (e.Replacee == 'CellPack') || (e.Replacee == 'SkullRodHefty') )
|
|
{
|
|
if ( !Random[Replacements](0,2) ) e.Replacement = Random[Replacements](0,2)?'SilverBulletAmmo':'SilverBulletAmmo2';
|
|
else e.Replacement = 'CandyGunAmmo';
|
|
}
|
|
else if ( e.Replacee == 'Mana1' ) e.Replacement = 'FabricatorTier1';
|
|
else if ( e.Replacee == 'Mana2' ) e.Replacement = 'FabricatorTier2';
|
|
else if ( e.Replacee == 'Mana3' ) e.Replacement = 'FabricatorTier3';
|
|
else if ( e.Replacee == 'ArtiBoostMana' ) e.Replacement = 'FabricatorTier4';
|
|
else if ( (e.Replacee == 'Backpack') || (e.Replacee == 'BagOfHolding') || (e.Replacee == 'ArtiPork') || (e.Replacee == 'ArtiBoostArmor') ) e.Replacement = 'HammerspaceEmbiggener';
|
|
else if ( (e.Replacee == 'FWeaponPiece1') || (e.Replacee == 'FWeaponPiece2')
|
|
|| (e.Replacee == 'CWeaponPiece1') || (e.Replacee == 'CWeaponPiece3')
|
|
|| (e.Replacee == 'MWeaponPiece2') || (e.Replacee == 'MWeaponPiece3') )
|
|
{
|
|
if ( Random[Replacements](0,1) ) e.Replacement = 'SWWMNothing';
|
|
else if ( Random[Replacements](0,5) ) e.Replacement = 'HammerspaceEmbiggener';
|
|
else e.Replacement = 'GoldShell';
|
|
}
|
|
else if ( (e.Replacee == 'ArmorBonus') || (e.Replacee == 'ArtiTimeBomb') || (e.Replacee == 'ArtiBlastRadius') ) e.Replacement = 'ArmorNuggetItem';
|
|
else if ( (e.Replacee == 'HealthBonus') || (e.Replacee == 'CrystalVial') ) e.Replacement = 'HealthNuggetItem';
|
|
else if ( e.Replacee == 'Stimpack' ) e.Replacement = 'TetraHealthItem';
|
|
else if ( e.Replacee == 'Medikit' ) e.Replacement = 'CubeHealthItem';
|
|
else if ( e.Replacee == 'ArtiHealth' )
|
|
{
|
|
if ( gameinfo.gametype&GAME_Heretic && Random[Replacements](0,1) ) e.Replacement = 'CubeHealthItem';
|
|
else e.Replacement = 'TetraHealthItem';
|
|
}
|
|
else if ( (e.Replacee == 'Soulsphere') || (e.Replacee == 'ArtiSuperHealth') )
|
|
{
|
|
if ( gameinfo.gametype&GAME_Hexen ) e.Replacement = 'CubeHealthItem';
|
|
else e.Replacement = 'RefresherItem';
|
|
}
|
|
else if ( e.Replacee == 'ArtiHealingRadius' ) e.Replacement = 'RefresherItem';
|
|
else if ( (e.Replacee == 'Megasphere') || (e.Replacee == 'ArtiEgg') || (e.Replacee == 'PlatinumHelm') ) e.Replacement = 'GrilledCheeseSandwich';
|
|
else if ( (e.Replacee == 'Blursphere') || (e.Replacee == 'ArtiInvisibility') || (e.Replacee == 'AmuletOfWarding') ) e.Replacement = 'GhostArtifact';
|
|
else if ( e.Replacee == 'Radsuit' ) e.Replacement = 'EBarrier';
|
|
else if ( (e.Replacee == 'ArtiFly') ) e.Replacement = 'GravitySuppressor';
|
|
else if ( (e.Replacee == 'InvulnerabilitySphere') || (e.Replacee == 'ArtiInvulnerability') || (e.Replacee == 'ArtiInvulnerability2') ) e.Replacement = 'FuckingInvinciball';
|
|
else if ( (e.Replacee == 'Berserk') || (e.Replacee == 'ArtiTomeOfPower') || (e.Replacee == 'ArtiDarkServant') || (e.Replacee == 'ArtiSpeedBoots') ) e.Replacement = 'Ragekit';
|
|
else if ( (e.Replacee == 'AllMap') || (e.Replacee == 'SuperMap') ) e.Replacement = 'Omnisight';
|
|
else if ( (e.Replacee == 'Infrared') || (e.Replacee == 'ArtiTorch') ) e.Replacement = 'SWWMLamp';
|
|
else if ( (e.Replacee == 'GreenArmor') || (e.Replacee == 'SilverShield') || (e.Replacee == 'MeshArmor') ) e.Replacement = 'BlastSuitItem';
|
|
else if ( (e.Replacee == 'BlueArmor') || (e.Replacee == 'FalconShield') || (e.Replacee == 'EnchantedShield') ) e.Replacement = 'WarArmorItem';
|
|
}
|
|
|
|
override void NetworkProcess( ConsoleEvent e )
|
|
{
|
|
static const Class<Ammo> cbttypes[] = {"RedShell","GreenShell","BlueShell","PurpleShell"};
|
|
if ( e.Name ~== "swwmgesture" )
|
|
{
|
|
if ( (e.player == -1) || !playeringame[e.player] || !players[e.player].mo ) return;
|
|
let mo = players[e.player].mo;
|
|
if ( (mo.Health <= 0) || !(mo is 'Demolitionist') ) return;
|
|
switch ( e.Args[0] )
|
|
{
|
|
case 1:
|
|
SWWMGesture.SetGesture(mo,1);
|
|
break;
|
|
case 2:
|
|
SWWMGesture.SetGesture(mo,2);
|
|
break;
|
|
default:
|
|
SWWMGesture.SetGesture(mo,0);
|
|
break;
|
|
}
|
|
}
|
|
if ( e.IsManual ) return;
|
|
if ( e.Name.Left(14) ~== "swwmstoregive." )
|
|
{
|
|
Class<Inventory> item = e.Name.Mid(14);
|
|
if ( !item ) return;
|
|
if ( SWWMCredits.Take(players[e.Args[0]],e.Args[1]) )
|
|
{
|
|
players[e.Args[0]].mo.GiveInventory(item,e.Args[2],true);
|
|
if ( item is 'Weapon' ) players[e.Args[0]].mo.A_SelectWeapon((Class<Weapon>)(item));
|
|
}
|
|
}
|
|
else if ( e.Name.Left(10) ~== "swwmtrade." )
|
|
{
|
|
Class<Inventory> item = e.Name.Mid(10);
|
|
if ( !item ) return;
|
|
let def = GetDefaultByType(item);
|
|
int amt = def.Amount;
|
|
// if it's an ammo, check the largest unit givable
|
|
if ( item is 'Ammo' )
|
|
{
|
|
for ( int i=0; i<AllActorClasses.Size(); i++ )
|
|
{
|
|
let a = (Class<Ammo>)(AllActorClasses[i]);
|
|
if ( !a || (a.GetParentClass() != item) || (GetDefaultByType(a).Amount < amt) ) continue;
|
|
amt = GetDefaultByType(a).Amount;
|
|
}
|
|
}
|
|
Inventory ritm = players[e.Args[1]].mo.FindInventory(item);
|
|
if ( ritm )
|
|
{
|
|
int maxgive = ritm.MaxAmount-ritm.Amount;
|
|
if ( amt > maxgive ) amt = maxgive;
|
|
}
|
|
else if ( amt > def.MaxAmount ) amt = def.MaxAmount;
|
|
bool rslt = false;
|
|
if ( (amt > 0) && players[e.Args[1]].mo.GiveInventory(item,amt,true) )
|
|
{
|
|
players[e.Args[0]].mo.TakeInventory(item,amt);
|
|
// add to history
|
|
SWWMTradeHistory.RegisterSend(players[e.Args[0]],players[e.Args[1]],item,amt);
|
|
SWWMTradeHistory.RegisterReceive(players[e.Args[1]],players[e.Args[0]],item,amt);
|
|
// add messages
|
|
if ( e.Args[0] == consoleplayer ) Console.Printf(StringTable.Localize("$SWWM_MSGSENT"),amt,def.GetTag(),players[e.Args[1]].GetUserName());
|
|
if ( e.Args[1] == consoleplayer ) Console.Printf(StringTable.Localize("$SWWM_MSGRECV"),players[e.Args[0]].GetUserName(),amt,def.GetTag());
|
|
rslt = true;
|
|
}
|
|
if ( e.Args[0] == consoleplayer )
|
|
{
|
|
let t = new("MenuTransaction");
|
|
t.uid = e.Args[2];
|
|
t.type = MenuTransaction.TT_ITEMSEND;
|
|
t.result = rslt;
|
|
t.used = item;
|
|
t.usedup = (players[e.Args[1]].mo.CountInv(item)<=0);
|
|
checklist.Push(t);
|
|
}
|
|
}
|
|
else if ( e.Name.Left(17) ~== "swwmmarkloreread." )
|
|
{
|
|
let l = SWWMLoreLibrary.Find(players[e.Args[0]]);
|
|
let idx = l.FindEntry(e.Name.Mid(17));
|
|
l.MarkRead(idx);
|
|
}
|
|
else if ( e.Name.Left(12) ~== "swwmuseitem." )
|
|
{
|
|
Class<Inventory> item = e.Name.Mid(12);
|
|
if ( !item ) return;
|
|
let i = players[e.Args[0]].mo.FindInventory(item);
|
|
if ( !i ) return;
|
|
bool rslt = players[e.Args[0]].mo.UseInventory(i);
|
|
if ( e.Args[0] == consoleplayer )
|
|
{
|
|
let t = new("MenuTransaction");
|
|
t.uid = e.Args[1];
|
|
t.type = MenuTransaction.TT_ITEMUSE;
|
|
let w = (Class<Weapon>)(item);
|
|
if ( w ) t.result = (players[e.Args[0]].PendingWeapon==Weapon(i));
|
|
else t.result = rslt;
|
|
t.used = item;
|
|
t.usedup = (!i||(i.Amount<=0));
|
|
checklist.Push(t);
|
|
}
|
|
}
|
|
else if ( e.Name.Left(13) ~== "swwmdropitem." )
|
|
{
|
|
Class<Inventory> item = e.Name.Mid(13);
|
|
if ( !item ) return;
|
|
let i = players[e.Args[0]].mo.FindInventory(item);
|
|
if ( !i ) return;
|
|
int amt = i.default.Amount;
|
|
// if it's an ammo, check the largest unit givable
|
|
if ( i is 'Ammo' )
|
|
{
|
|
for ( int i=0; i<AllActorClasses.Size(); i++ )
|
|
{
|
|
let a = (Class<Ammo>)(AllActorClasses[i]);
|
|
if ( !a || (a.GetParentClass() != item) || (GetDefaultByType(a).Amount < amt) ) continue;
|
|
amt = GetDefaultByType(a).Amount;
|
|
}
|
|
}
|
|
if ( amt > i.Amount ) amt = i.Amount;
|
|
let drop = players[e.Args[0]].mo.DropInventory(i,amt);
|
|
// add some random velocity so multiple drops don't get bunched together
|
|
if ( drop ) drop.vel += (Actor.RotateVector((FRandom[Junk](-1.5,.5),FRandom[Junk](-2.5,2.5)),players[e.Args[0]].mo.angle),FRandom[Junk](2.,5.));
|
|
if ( e.Args[0] == consoleplayer )
|
|
{
|
|
let t = new("MenuTransaction");
|
|
t.uid = e.Args[1];
|
|
t.type = MenuTransaction.TT_ITEMDROP;
|
|
t.used = item;
|
|
t.result = drop;
|
|
t.usedup = (!i||(i.Amount<=0));
|
|
checklist.Push(t);
|
|
}
|
|
}
|
|
else if ( e.Name ~== "swwmkoraxline" )
|
|
{
|
|
if ( consoleplayer != e.Args[1] ) return;
|
|
switch ( e.Args[0] )
|
|
{
|
|
case 0:
|
|
AddOneliner("koraxgreet",3,60);
|
|
break;
|
|
case 1:
|
|
AddOneliner("koraxblood",3,150);
|
|
break;
|
|
case 2:
|
|
AddOneliner("koraxgame",3,120);
|
|
break;
|
|
case 3:
|
|
AddOneliner("koraxworship",3,80);
|
|
break;
|
|
case 4:
|
|
AddOneliner("koraxmasters",3,90);
|
|
break;
|
|
}
|
|
}
|
|
else if ( e.Name.Left(16) ~== "swwmremoteliner." )
|
|
{
|
|
if ( consoleplayer == e.Args[0] ) return;
|
|
if ( !CVar.GetCVar('swwm_othervoice',players[consoleplayer]).GetBool() ) return;
|
|
if ( CVar.GetCVar('swwm_mutevoice',players[consoleplayer]).GetInt() >= e.Args[1] ) return;
|
|
players[e.Args[0]].mo.A_StartSound(e.Name.Mid(16),CHAN_DEMOVOICE,attenuation:.5);
|
|
}
|
|
else if ( e.Name.Left(19) ~== "swwmremotelinertxt." )
|
|
{
|
|
if ( consoleplayer == e.Args[0] ) return;
|
|
if ( !CVar.GetCVar('swwm_othervoice',players[consoleplayer]).GetBool() ) return;
|
|
if ( CVar.GetCVar('swwm_mutevoice',players[consoleplayer]).GetInt() >= e.Args[1] ) return;
|
|
double dist = players[consoleplayer].Camera.Distance3D(players[e.Args[0]].mo);
|
|
if ( dist < 2000 )
|
|
Console.Printf("\cx%s\cx: %s\c-",players[e.Args[0]].GetUserName(),StringTable.Localize(e.Name.Mid(19)));
|
|
}
|
|
else if ( e.Name ~== "swwmmoneycheat" )
|
|
{
|
|
if ( consoleplayer == e.Args[0] )
|
|
{
|
|
Console.Printf("\cfLOADSAMONEY!\c-");
|
|
S_StartSound("menu/buyinv",CHAN_ITEM,CHANF_UI);
|
|
S_StartSound("misc/emone",CHAN_VOICE,CHANF_UI);
|
|
}
|
|
SWWMCredits.Give(players[e.Args[0]],1000000000);
|
|
SWWMScoreObj.Spawn(1000000000,players[e.Args[0]].mo.Vec3Offset(0,0,players[e.Args[0]].mo.Height/2));
|
|
}
|
|
else if ( e.Name ~== "swwmlorecheat" )
|
|
{
|
|
// look up all lore files
|
|
for ( int l=0; l<Wads.GetNumLumps(); l++ )
|
|
{
|
|
String fn = Wads.GetLumpFullName(l);
|
|
if ( fn.Left(13) != "lore/default/" ) continue;
|
|
int ext = fn.IndexOf(".txt");
|
|
if ( ext != fn.Length()-4 ) continue;
|
|
SWWMLoreLibrary.Add(players[e.Args[0]],fn.Mid(13,ext-13));
|
|
}
|
|
}
|
|
else if ( e.Name.Left(8) ~== "swwmcbt." )
|
|
{
|
|
// from wikipedia, the free encyclopedia
|
|
if ( !playeringame[e.Args[0]] || !players[e.Args[0]].mo ) return;
|
|
let cbt = Wallbuster(players[e.Args[0]].mo.FindInventory("Wallbuster"));
|
|
if ( !cbt ) return;
|
|
cbt.reloadqueue.Clear();
|
|
Array<String> qs;
|
|
qs.Clear();
|
|
String rite = e.Name.Mid(8);
|
|
rite.Split(qs,",",TOK_SKIPEMPTY);
|
|
for ( int i=0; i<qs.Size(); i++ )
|
|
{
|
|
int qi = qs[i].ToInt();
|
|
if ( (qi < 0) || (qi > 3) ) continue;
|
|
cbt.reloadqueue.Push(cbttypes[qi]);
|
|
}
|
|
cbt.waitreload = false;
|
|
}
|
|
else if ( e.Name ~== "swwmcleartransaction" )
|
|
{
|
|
if ( e.Args[1] != consoleplayer ) return;
|
|
for ( int i=0; i<checklist.Size(); i++ )
|
|
{
|
|
if ( checklist[i].uid != e.Args[0] ) continue;
|
|
checklist.Delete(i);
|
|
i--;
|
|
}
|
|
}
|
|
else if ( e.Name ~== "swwmclearalltransactions" )
|
|
{
|
|
if ( e.Args[0] != consoleplayer ) return;
|
|
checklist.Clear();
|
|
}
|
|
}
|
|
|
|
// stuff for hud
|
|
override void RenderUnderlay( RenderEvent e )
|
|
{
|
|
// armor/health flashes
|
|
int camplayer = players[consoleplayer].Camera.PlayerNumber();
|
|
if ( camplayer != -1 )
|
|
{
|
|
if ( gametic < hflash[camplayer] )
|
|
{
|
|
double fstr = (hflash[camplayer]-(gametic+e.FracTic))/5.;
|
|
Screen.Dim(Color(64,128,255),.1875*fstr,0,0,Screen.GetWidth(),Screen.GetHeight());
|
|
}
|
|
if ( gametic < aflash[camplayer] )
|
|
{
|
|
double fstr = (aflash[camplayer]-(gametic+e.FracTic))/5.;
|
|
Screen.Dim(Color(96,255,64),.1875*fstr,0,0,Screen.GetWidth(),Screen.GetHeight());
|
|
}
|
|
}
|
|
// weapon underlays
|
|
if ( players[consoleplayer].ReadyWeapon is 'SWWMWeapon' )
|
|
SWWMWeapon(players[consoleplayer].ReadyWeapon).RenderUnderlay(e);
|
|
if ( !statusbar || !(statusbar is 'SWWMStatusBar') ) return;
|
|
SWWMStatusBar(statusbar).viewpos = e.viewpos;
|
|
SWWMStatusBar(statusbar).viewrot = (e.viewangle,e.viewpitch,e.viewroll);
|
|
}
|
|
|
|
// various shaders
|
|
override void RenderOverlay( RenderEvent e )
|
|
{
|
|
PlayerInfo p = players[consoleplayer];
|
|
if ( !useshaders ) useshaders = CVar.GetCVar('swwm_shaders',p);
|
|
let mo = p.mo;
|
|
if ( !mo ) return;
|
|
bool pc = (p.camera == mo);
|
|
let rage = RagekitPower(mo.FindInventory("RagekitPower"));
|
|
if ( pc && rage && useshaders.GetBool() )
|
|
{
|
|
Shader.SetEnabled(p,"RagekitShader",true);
|
|
Shader.SetUniform1f(p,"RagekitShader","timer",(gametic+e.FracTic)/Thinker.TICRATE);
|
|
double xstrastr = 1.+max(0,rage.lastpulse-(gametic+e.Fractic))/35.;
|
|
Shader.SetUniform1f(p,"RagekitShader","xtrastr",xstrastr**2.);
|
|
}
|
|
else Shader.SetEnabled(p,"RagekitShader",false);
|
|
let ghost = GhostPower(mo.FindInventory("GhostPower"));
|
|
if ( pc && ghost && useshaders.GetBool() ) Shader.SetEnabled(p,"GhostShader",true);
|
|
else Shader.SetEnabled(p,"GhostShader",false);
|
|
let sunny = InvinciballPower(mo.FindInventory("InvinciballPower"));
|
|
if ( pc && sunny && useshaders.GetBool() )
|
|
{
|
|
Shader.SetEnabled(p,"InvinciShader",true);
|
|
double str = max(0,sunny.lastpulse-(gametic+e.Fractic))/35.;
|
|
Shader.SetUniform1f(p,"InvinciShader","str",str);
|
|
}
|
|
else Shader.SetEnabled(p,"InvinciShader",false);
|
|
let coat = BarrierPower(mo.FindInventory("BarrierPower"));
|
|
if ( pc && coat && useshaders.GetBool() )
|
|
{
|
|
Shader.SetEnabled(p,"BarrierShader",true);
|
|
Shader.SetUniform1f(p,"BarrierShader","timer",(gametic+e.FracTic)/Thinker.TICRATE);
|
|
}
|
|
else Shader.SetEnabled(p,"BarrierShader",false);
|
|
if ( pc && (mo is 'Demolitionist') )
|
|
{
|
|
let demo = Demolitionist(mo);
|
|
Shader.SetEnabled(p,"Glitch",true);
|
|
Shader.SetEnabled(p,"Grain",true);
|
|
Shader.SetUniform1f(p,"Glitch","Timer",(gametic+e.FracTic)/Thinker.TICRATE);
|
|
Shader.SetUniform1f(p,"Grain","Timer",(gametic+e.FracTic)/Thinker.TICRATE);
|
|
int lastdmg = (demo.Health>0)?demo.lastdamage:Random[Flicker](60,80);
|
|
int lastdmgtic = (demo.Health>0)?demo.lastdamagetic:(gametic+Random[Flicker](30,20));
|
|
double noiz = min(lastdmg*.09*max(0,(lastdmgtic-(gametic+e.Fractic))/35.),.5);
|
|
Shader.SetUniform1f(p,"Grain","ni",noiz);
|
|
noiz = min(lastdmg*.08*max(0,(lastdmgtic-(gametic+e.Fractic))/35.),.8);
|
|
Shader.SetUniform1f(p,"Glitch","str1",noiz);
|
|
noiz = min(lastdmg*.03*max(0,(lastdmgtic-(gametic+e.Fractic))/35.),3.5);
|
|
Shader.SetUniform1f(p,"Glitch","str2",noiz);
|
|
Shader.SetEnabled(p,"ZoomBlur",true);
|
|
if ( !demo.InStateSequence(demo.CurState,demo.FindState("Dash")) )
|
|
{
|
|
Shader.SetEnabled(p,"ZoomBlur",false);
|
|
return;
|
|
}
|
|
Vector3 vel = demo.vel+demo.dashdir*demo.dashboost;
|
|
double baumpu = max(0.,(demo.bumptic-(gametic+e.Fractic))/35.);
|
|
vel += demo.dashdir*baumpu;
|
|
double spd = vel.length();
|
|
Vector3 worlddir = vel/spd;
|
|
Shader.SetUniform1f(p,"ZoomBlur","Fade",clamp((spd-20.)/60.,0.,.75));
|
|
double str = min(spd/50.,10.);
|
|
Vector3 x, y, z;
|
|
[x, y, z] = swwm_CoordUtil.GetAxes(e.ViewPitch,e.ViewAngle,e.ViewRoll);
|
|
Vector3 reldir = (worlddir dot y, worlddir dot z, worlddir dot x);
|
|
Vector2 centerspot = (.5+reldir.x*.5,.5+reldir.y*.5);
|
|
if ( reldir.z < 0 )
|
|
{
|
|
centerspot.x = 1.-centerspot.x;
|
|
centerspot.y = 1.-centerspot.y;
|
|
str *= -1;
|
|
}
|
|
Shader.SetUniform1f(p,"ZoomBlur","Str",str);
|
|
Shader.SetUniform2f(p,"ZoomBlur","CenterSpot",centerspot);
|
|
}
|
|
else
|
|
{
|
|
Shader.SetEnabled(p,"Glitch",false);
|
|
Shader.SetEnabled(p,"Grain",false);
|
|
Shader.SetEnabled(p,"ZoomBlur",false);
|
|
}
|
|
}
|
|
|
|
static void DoFlash( Actor camera, Color c, int duration )
|
|
{
|
|
// don't flash when paused
|
|
if ( menuactive && (menuactive != Menu.OnNoPause) ) return;
|
|
QueuedFlash qf = new("QueuedFlash");
|
|
qf.duration = duration;
|
|
qf.c = c;
|
|
qf.tic = gametic;
|
|
qf.cam = camera;
|
|
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
|
if ( !hnd ) return; // not supposed to happen
|
|
hnd.flashes.push(qf);
|
|
}
|
|
|
|
// Doom's explosions aren't fully 3D in their knockback
|
|
static void DoBlast( Actor Source, double ExplosionRadius, double MomentumTransfer, Actor ignoreme = null, bool forceblast = false )
|
|
{
|
|
BlockThingsIterator bi = BlockThingsIterator.Create(Source,ExplosionRadius);
|
|
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 && !forceblast )
|
|
continue;
|
|
// massive, no knockback
|
|
if ( a.bDONTTHRUST || (a.Mass >= Actor.LARGE_MASS) )
|
|
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 ( !Source.CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) )
|
|
continue;
|
|
// make use of GetRadiusDamage to see if it's in range
|
|
if ( Source.GetRadiusDamage(a,int(MomentumTransfer),int(ExplosionRadius),oldradiusdmg:Source.bOLDRADIUSDMG) <= 0 )
|
|
continue;
|
|
// perform our own blasting code, based partially on Unreal's HurtRadius
|
|
Vector3 midpoint = a.Vec3Offset(0,0,a.height*0.5);
|
|
Vector3 dir = Level.Vec3Diff(Source.pos,midpoint);
|
|
double dist = max(1,dir.length());
|
|
double damagescale = 1-clamp((dist-a.radius)/ExplosionRadius,0.,1.);
|
|
// 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 = dir/dist;
|
|
DoKnockback(a,dir,MomentumTransfer*damagescale);
|
|
}
|
|
}
|
|
|
|
// Same for this
|
|
static void DoKnockback( Actor Victim, Vector3 HitDirection, double MomentumTransfer )
|
|
{
|
|
if ( !Victim )
|
|
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
|
|
static void 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_explosiondebug )
|
|
{
|
|
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
|
|
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
|
|
int dmg = int(Damage*damagescale);
|
|
if ( dmg <= 0 ) continue; // no harm
|
|
int ndmg = a.DamageMobj(Instigator,Source,dmg,(DamageType!='')?Source.DamageType:DamageType,DMG_EXPLOSION,atan2(-dir.y,-dir.x));
|
|
if ( !(flags&DE_NOBLEED) ) a.TraceBleed((ndmg>0)?ndmg:dmg,Source);
|
|
}
|
|
// TODO destructible geometry support
|
|
}
|
|
|
|
static void DoSwing( Actor target, Vector2 dir, double initial, double inc, int steps, int mode = 0, int delay = 0, double rmul = 1.0 )
|
|
{
|
|
let s = new("Swinger");
|
|
s.ChangeStatNum(Thinker.STAT_USER);
|
|
s.target = target;
|
|
s.dir = dir;
|
|
s.inc = inc;
|
|
s.rmul = rmul;
|
|
s.steps = steps;
|
|
s.mode = mode;
|
|
s.delay = delay;
|
|
s.cnt = 0;
|
|
s.cstate = 0;
|
|
s.str = initial;
|
|
s.tstr = initial;
|
|
}
|
|
}
|
|
|
|
// preload fonts and stuff
|
|
Class SWWMPreloader : StaticEventHandler
|
|
{
|
|
override void OnRegister()
|
|
{
|
|
Font.GetFont('k6x8');
|
|
Font.GetFont('k6x8Shaded');
|
|
Font.GetFont('k6x8ShadedInverse');
|
|
Font.GetFont('Miniwi');
|
|
Font.GetFont('MiniwiShaded');
|
|
Font.GetFont('MiniwiShadedInverse');
|
|
Font.GetFont('MPlus');
|
|
Font.GetFont('MPlusShaded');
|
|
Font.GetFont('MPlusShadedInverse');
|
|
Font.GetFont('Tewi');
|
|
Font.GetFont('TewiShaded');
|
|
Font.GetFont('TewiShadedInverse');
|
|
}
|
|
}
|
|
|
|
// Fancy crash effect
|
|
Class SWWMCrashHandler : StaticEventHandler
|
|
{
|
|
ui bool wasinmap;
|
|
ui int timer;
|
|
|
|
override void UiTick()
|
|
{
|
|
if ( (gamestate == GS_LEVEL) || (gamestate == GS_TITLELEVEL) )
|
|
{
|
|
wasinmap = true;
|
|
timer = 0;
|
|
}
|
|
else if ( (gamestate == GS_FULLCONSOLE) && ((wasinmap && !players[consoleplayer].viewheight) || (timer > 0)) )
|
|
{
|
|
wasinmap = false;
|
|
if ( timer == 1 )
|
|
{
|
|
Console.Printf(TEXTCOLOR_GOLD.."Oopsie Woopsie!"..TEXTCOLOR_NORMAL);
|
|
let hnd = SWWMBrutalHandler(StaticEventHandler.Find("SWWMBrutalHandler"));
|
|
if ( hnd && hnd.detected )
|
|
{
|
|
S_StartSound("crash/glass",CHAN_YOUDONEFUCKEDUP,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE);
|
|
S_StartSound("crash/glass",CHAN_YOUDONEFUCKEDUP,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE);
|
|
}
|
|
else S_StartSound("crash/crash",CHAN_YOUDONEFUCKEDUP,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE);
|
|
}
|
|
else if ( timer == 140 )
|
|
{
|
|
Console.Printf(TEXTCOLOR_GOLD.."Looks like GZDoom made a fucky wucky! owo"..TEXTCOLOR_NORMAL);
|
|
S_StartSound("crash/curb",CHAN_YOUDONEFUCKEDUP,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE);
|
|
}
|
|
else if ( timer == 350 )
|
|
{
|
|
let hnd = SWWMBrutalHandler(StaticEventHandler.Find("SWWMBrutalHandler"));
|
|
if ( hnd && hnd.detected ) Console.Printf(TEXTCOLOR_GOLD.."Don't blame me. Shouldn't have tried running this with Brutal Doom."..TEXTCOLOR_NORMAL);
|
|
else Console.Printf(TEXTCOLOR_GOLD.."If you didn't trigger it manually, it's best if you take a screenshot and show it to Marisa."..TEXTCOLOR_NORMAL);
|
|
Console.Printf(TEXTCOLOR_GOLD.."Version Information: %s"..TEXTCOLOR_NORMAL,StringTable.Localize("SWWM_MODVER"));
|
|
}
|
|
timer++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// >loading brutal doom with this
|
|
Class SWWMBrutalHandler : StaticEventHandler
|
|
{
|
|
ui int timer;
|
|
ui TextureID scr;
|
|
bool detected;
|
|
|
|
override void OnRegister()
|
|
{
|
|
for ( int i=0; i<AllActorClasses.size(); i++ )
|
|
{
|
|
if ( (AllActorClasses[i].GetClassName() != "BrutalWeapon")
|
|
&& (AllActorClasses[i].GetClassName() != "BrutalDoomer") ) continue;
|
|
detected = true;
|
|
break;
|
|
}
|
|
SetRandomSeed[bdscreen](Random[bdscreen]()+consoleplayer+MSTime());
|
|
if ( detected )
|
|
Console.Printf("\cg========\n\cgIf you have BD on your autoload, you really shouldn't.\n\cgIf you manually loaded it with this mod, why would you?\n\cgThey're not compatible and never will be.\n\cg========");
|
|
}
|
|
|
|
override void UiTick()
|
|
{
|
|
if ( !detected ) return;
|
|
if ( gamestate == GS_LEVEL )
|
|
{
|
|
if ( timer == 1 )
|
|
{
|
|
S_StartSound("brutal/ezmodo",CHAN_YOUDONEFUCKEDUP,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE);
|
|
S_StartSound("brutal/ezmodo",CHAN_YOUDONEFUCKEDUP,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE);
|
|
}
|
|
else if ( timer == 350 ) ThrowAbortException(">Brutal Doom");
|
|
timer++;
|
|
}
|
|
else timer = 0;
|
|
}
|
|
|
|
override void WorldTick()
|
|
{
|
|
if ( !detected ) return;
|
|
for ( int i=0; i<MAXPLAYERS; i++ ) if ( playeringame[i] ) players[i].cheats |= CF_TOTALLYFROZEN;
|
|
}
|
|
|
|
override void RenderOverlay( RenderEvent e )
|
|
{
|
|
if ( !detected ) return;
|
|
if ( !scr ) scr = TexMan.CheckForTexture("graphics/bdscreen.png",TexMan.Type_Any);
|
|
Screen.Dim("Red",(timer/350.)-.2,0,0,Screen.GetWidth(),Screen.GetHeight());
|
|
double ar = Screen.GetAspectRatio();
|
|
Vector2 tsize = TexMan.GetScaledSize(scr);
|
|
Vector2 vsize = (Screen.GetWidth(),Screen.GetHeight());
|
|
if ( (tsize.x > vsize.x) || (tsize.y > vsize.y) )
|
|
{
|
|
double sar = tsize.x/tsize.y;
|
|
if ( sar > ar ) vsize = (tsize.x,tsize.x/ar);
|
|
else if ( sar < ar ) vsize = (tsize.y*ar,tsize.y);
|
|
else vsize = tsize;
|
|
}
|
|
Screen.DrawTexture(scr,false,(vsize.x-tsize.x)/2.+FRandom[bdscreen](-1,1)*max(timer-40,0)**3*.000003,(vsize.y-tsize.y)/2.+FRandom[bdscreen](-1,1)*max(timer-40,0)**3*.000003,DTA_VirtualWidthF,vsize.x,DTA_VirtualHeightF,vsize.y,DTA_KeepRatio,true,DTA_Alpha,min(1.,timer/50.));
|
|
Screen.Dim("Red",(timer/70.)-3.5,0,0,Screen.GetWidth(),Screen.GetHeight());
|
|
}
|
|
}
|
|
|
|
// HORNY
|
|
Class SWWMHDoomHandler : StaticEventHandler
|
|
{
|
|
ui int timer;
|
|
ui TextureID scr;
|
|
bool detected;
|
|
|
|
override void OnRegister()
|
|
{
|
|
for ( int i=0; i<AllActorClasses.size(); i++ )
|
|
{
|
|
if ( AllActorClasses[i].GetClassName() != "HDoomPlayer" ) continue;
|
|
detected = true;
|
|
break;
|
|
}
|
|
SetRandomSeed[hdscreen](Random[hdscreen]()+consoleplayer+MSTime());
|
|
if ( detected )
|
|
Console.Printf("\cg========\n\cgHa ha very funny.\n\cgIf you want the Demolitionist to fuck some hot demon girls go commission a porn artist.\n\cg========");
|
|
}
|
|
|
|
override void UiTick()
|
|
{
|
|
if ( !detected ) return;
|
|
if ( gamestate == GS_LEVEL )
|
|
{
|
|
if ( timer == 50 )
|
|
{
|
|
S_StartSound("bestsound",CHAN_YOUDONEFUCKEDUP,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE);
|
|
S_StartSound("bestsound",CHAN_YOUDONEFUCKEDUP,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE);
|
|
}
|
|
else if ( timer == 70 )
|
|
{
|
|
int ngiggl = Random[hdscreen](1,14);
|
|
S_StartSound(String.Format("saya/giggle%d",ngiggl),CHAN_YOUDONEFUCKEDUP,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE);
|
|
S_StartSound(String.Format("saya/giggle%d",ngiggl),CHAN_YOUDONEFUCKEDUP,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE);
|
|
}
|
|
timer++;
|
|
}
|
|
else timer = 0;
|
|
}
|
|
|
|
override void RenderOverlay( RenderEvent e )
|
|
{
|
|
if ( !detected || (timer < 50) ) return;
|
|
if ( !scr ) scr = TexMan.CheckForTexture("graphics/hdscreen.png",TexMan.Type_Any);
|
|
double ar = Screen.GetAspectRatio();
|
|
Vector2 tsize = TexMan.GetScaledSize(scr);
|
|
Vector2 vsize = (Screen.GetWidth(),Screen.GetHeight());
|
|
if ( (tsize.x > vsize.x) || (tsize.y > vsize.y) )
|
|
{
|
|
double sar = tsize.x/tsize.y;
|
|
if ( sar > ar ) vsize = (tsize.x,tsize.x/ar);
|
|
else if ( sar < ar ) vsize = (tsize.y*ar,tsize.y);
|
|
else vsize = tsize;
|
|
}
|
|
Screen.DrawTexture(scr,false,(vsize.x-tsize.x)/2.,(vsize.y-tsize.y)/2.,DTA_VirtualWidthF,vsize.x,DTA_VirtualHeightF,vsize.y,DTA_KeepRatio,true);
|
|
}
|
|
}
|