swwmgz_m/zscript/swwm_common.zsc
Marisa Kirisame 163fdce33b Intermission displays for Coop and DM are now added.
Tweaks/fixes for dash slam and ground pound.
Score numbers will pop up in the world for count items, secrets, and others.
Fonts are now preloaded at launch, to reduce freezes. This obviously increases load times.
Berserk punches now have additional splash damage.
2020-04-26 18:26:36 +02:00

2886 lines
79 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_AMBEXTRA = 63208 // player ambience when submerged
};
// 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 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);
}
// not portal aware, will have to fix that later
static bool SphereIntersect( Actor a, Vector3 p, double radius )
{
Vector3 amin = a.pos+(-a.radius,-a.radius,0),
amax = a.pos+(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));
}
}
// Stats
Class WeaponUsage
{
Class<Weapon> w;
int kills;
}
Class SWWMStats : Thinker
{
PlayerInfo myplayer;
int lastspawn, dashcount, boostcount, stompcount, airtime, kills,
deaths, damagedealt, damagetaken, mkill, hiscore, topdealt,
toptaken, skill;
double grounddist, airdist, fuelusage, topspeed;
Array<WeaponUsage> wstats;
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 AddWeaponKill( Class<Weapon> which )
{
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;
static void Give( PlayerInfo p, int amount )
{
let c = Find(p);
if ( !c ) return;
if ( (c.credits+amount < c.credits) || (c.credits+amount > 999999999) ) c.credits = 999999999;
else c.credits += amount;
let s = SWWMStats.Find(p);
if ( s && (c.credits > s.hiscore) ) s.hiscore = c.credits;
}
static clearscope bool CanTake( PlayerInfo p, int amount )
{
let c = Find(p);
if ( !c ) return false;
// too much!
if ( (c.credits-amount < 0) || (c.credits-amount > c.credits) ) return false;
return true;
}
static bool Take( PlayerInfo p, int amount )
{
let c = Find(p);
if ( !c ) return false;
// too much!
if ( (c.credits-amount < 0) || (c.credits-amount > c.credits) ) return false;
c.credits -= amount;
return true;
}
static clearscope int Get( PlayerInfo p )
{
let c = Find(p);
if ( !c ) return 0;
return c.credits;
}
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;
}
Class SWWMLoreLibrary : Thinker
{
PlayerInfo myplayer;
Array<SWWMLore> ent;
int lastaddtic;
bool DirectAdd( String ref )
{
// restrictions
if ( !(gameinfo.gametype&GAME_Hexen) )
{
if ( ref ~== "Kirin" ) return true; // not met
}
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_Raven )
{
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
}
if ( gameinfo.gametype&GAME_Hexen )
{
if ( text ~== "SWWM_LORETXT_SAYA" )
text = "SWWM_LORETXT_SAYA2"; // married kirin
else if ( text ~== "SWWM_LORETXT_ANARUKON" )
text = "SWWM_LORETXT_ANARUKON2"; // comments from miyamoto-xanai wedding
}
// check that it's valid
if ( StringTable.Localize(tag,false) == tag ) return false;
if ( StringTable.Localize(tab,false) == tab ) return false;
if ( StringTable.Localize(text,false) == text ) 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)) )
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 )
{
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 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;
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
if ( hnd )
{
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;
static SWWMInterest Spawn( Vector3 pos = (0,0,0), Key thekey = null, Line theline = 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;
else if ( theline ) i.type = INT_Exit;
else
{
i.Destroy();
return null;
}
i.pos = thekey?thekey.Vec3Offset(0,0,thekey.height/2):pos;
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
if ( hnd )
{
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);
}
}
// enemy combat tracker
Class SWWMCombatTracker : Thinker
{
Actor mytarget;
String mytag;
int updated, lasthealth, maxhealth;
DynamicValueInterpolator intp;
Vector3 pos, prevpos;
PlayerInfo myplayer;
SWWMCombatTracker prev, next;
bool legged;
int tcnt;
double height;
static SWWMCombatTracker Spawn( Actor target )
{
let ti = ThinkerIterator.Create("SWWMCombatTracker",STAT_USER);
SWWMCombatTracker t;
while ( t = SWWMCombatTracker(ti.Next()) )
{
if ( t.mytarget != target ) continue;
return t;
}
t = new("SWWMCombatTracker");
t.ChangeStatNum(STAT_USER);
t.mytarget = target;
if ( target.player || target.bISMONSTER )
t.mytag = target.player?target.player.GetUserName():target.GetTag();
else t.mytag = "";
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.intp = DynamicValueInterpolator.Create(t.lasthealth,.5,1,100);
t.myplayer = target.player;
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
if ( hnd )
{
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()
{
// update
if ( mytarget )
{
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 )
{
// post-spawn health inflation check
if ( lasthealth > maxhealth ) maxhealth = lasthealth;
}
if ( tcnt == 6 )
{
// 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;
}
}
// VERY expensive, do not use
/*if ( mytarget.player || mytarget.bISMONSTER )
mytag = mytarget.player?mytarget.player.GetUserName():mytarget.GetTag();
else mytag = "";*/
int newhealth = mytarget.Health;
if ( (mytarget.bISMONSTER || mytarget.player) && !mytarget.bINVISIBLE )
{
// enemies within 2000mu that have us as target
if ( mytarget.target && (mytarget.target.Health > 0) && (mytarget.target.player == players[consoleplayer]) && mytarget.CheckSight(mytarget.target) && (mytarget.Vec3To(mytarget.target).length() < 2000) ) 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;
// enemies we're directly aiming at within 600mu
// the order of these checks allows for a minimal performance hit
// as AimTarget() is incredibly expensive to call too often
if ( (mytarget.Vec3To(players[consoleplayer].mo).length() < 600) && players[consoleplayer].mo.CheckSight(mytarget) && (players[consoleplayer].mo.AimTarget() == mytarget) ) updated = level.maptime;
}
lasthealth = newhealth;
intp.Update(lasthealth);
}
}
// 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;
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');
int margin = safezone.GetInt();
Vector2 hs = StatusBar.GetHUDScale();
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,(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;
}
}
Class SWWMHalfSmoke : SWWMSmoke
{
States
{
Spawn:
XSMK ACEGIKMOQS 1 A_SetTics(1+special1);
Stop;
}
}
Class SWWMSmallSmoke : SWWMSmoke
{
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);
}
}
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;
+MOVEWITHSECTOR;
+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;
+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 = 0;
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 ( InStateSequence(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 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;
}
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
{
+NOGRAVITY;
+NOBLOCKMAP;
+DONTSPLASH;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
SetOrigin(Vec3Offset(0,0,28),false);
A_StartSound("misc/teleport",CHAN_VOICE);
Spawn("TeleLight",pos);
}
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;
}
}
// Bullet trails from DT
Class WaterHit
{
Sector sect;
Vector3 hitpos;
}
Class InvisibleSplasher : Actor
{
Default
{
Mass 100;
VSpeed -2;
}
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);
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();
}
}
// Hack
Class SWWMFontPreloader : StaticEventHandler
{
override void OnRegister()
{
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');
}
}
// 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;
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;
// heal/armor flashes need to be handled here so they don't stack
transient int hflash[MAXPLAYERS], aflash[MAXPLAYERS];
transient CVar mutevoice, accdamage;
transient ui CVar useshaders;
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 )
{
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;
// TODO check for any 3d floors
for ( int i=0; i<l.frontsector.Get3DFloorCount(); i++ )
{
}
for ( int i=0; i<l.backsector.Get3DFloorCount(); i++ )
{
}
// TODO check for midtex
// 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;
}
override void WorldLoaded( WorldEvent e )
{
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<int> 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.Index()) < 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;
skipme.Push(l2.Index());
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;
skipme.Push(l2.Index());
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,"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 )
{
l.DirectAdd("Parthoris");
l.DirectAdd("SerpentRiders");
l.DirectAdd("Sidhe");
}
if ( gameinfo.gametype&GAME_Hexen )
l.DirectAdd("Cronos");
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;
}
// reset some vars
multilevel[e.playernumber] = 0;
spreecount[e.playernumber] = 0;
tookdamage[e.playernumber] = false;
lastkill[e.playernumber] = int.min;
// reset combat tracker
SWWMCombatTracker.Spawn(players[e.playernumber].mo);
// initialize some player vars
if ( p.mo is 'Demolitionist' )
{
Demolitionist(p.mo).dashfuel = 100.;
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 ( (e.Thing.bSHOOTABLE || e.Thing.bISMONSTER) && !(e.Thing is 'LampMoth') && !(e.Thing is 'CompanionLamp') )
SWWMCombatTracker.Spawn(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 = 100.;
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 ( !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 && !a.bCountKill ) continue;
// ignore the dead
if ( (a.Health <= 0) || a.bKILLED || a.bCORPSE ) 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+100)) )
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)) || (e.Thing.default.bBOSSDEATH && !Random[GoldDrop](0,6)) )
{
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;
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
for ( SWWMCombatTracker t=trackers; t; t=t.next )
{
if ( t.mytarget != e.Thing ) continue;
t.updated = level.maptime+35;
break;
}
}
if ( e.Thing.player )
{
tookdamage[e.Thing.PlayerNumber()] = true;
let s = SWWMStats.Find(e.Thing.player);
s.damagetaken += 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.damagedealt += 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.Thing == players[consoleplayer].mo) && (e.Thing.Health > 0) )
{
if ( !lastcombat || (gametic > lastcombat+20) )
lastcombat = AddOneliner(e.Thing.IsFriend(e.DamageSource)?"friendhit":"gethit",1,15);
highesttic = gametic;
}
if ( (e.DamageSource == players[consoleplayer].mo) && (e.Thing.bISMONSTER || e.Thing.player) )
{
// 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) && !(e.Thing is 'LampMoth') )
{
if ( !lastcombat || (gametic > lastcombat+20) )
lastcombat = AddOneliner("hitfriend",1,15);
highesttic = gametic;
}
}
}
if ( (e.Thing.Health > 0) || e.Thing.bKilled || e.Thing.bCorpse ) return;
if ( !e.Thing.player && !e.Thing.bIsMonster && !e.Thing.bCountKill ) return;
if ( (e.DamageSource && e.DamageSource.player && (e.DamageSource != e.Thing)) )
{
let s = SWWMStats.Find(e.DamageSource.player);
if ( s )
{
s.kills++;
if ( e.DamageSource.player.ReadyWeapon )
s.AddWeaponKill(e.DamageSource.player.ReadyWeapon.GetClass());
}
if ( e.DamageSource == players[consoleplayer].mo )
{
highesttic = gametic;
if ( !lastcombat || (gametic > lastcombat+20) )
lastcombat = AddOneliner("scorekill",1,15);
}
if ( !e.Thing.default.bCountKill ) // no credits
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.Thing.Health <= e.Thing.GetGibHealth()) || (e.DamageSource.bEXTREMEDEATH) || (e.Inflictor && e.Inflictor.bEXTREMEDEATH)) && !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] = "$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] = "$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] = "$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] = "$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 )
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);
}
override void WorldThingSpawned( WorldEvent e )
{
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");
else if ( e.Thing is 'CommanderKeen' ) e.Thing.SetTag("$FN_KEEN");
if ( (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) )
{
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 ) // assuming that's the F key on all keyboards (hopefully)
{
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;
if ( e.KeyScan == 33 )
{
let demo = Demolitionist(players[consoleplayer].mo);
if ( demo && (demo.Health <= 0) && (demo.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 ) return;
if ( e.Thing.CheckLocalView() && !e.Thing.CheckKeys(locknum,false,true) )
{
if ( !lastlock || (gametic > lastlock+20) )
lastlock = AddOneliner("locked",2);
}
}
override void CheckReplacement( ReplaceEvent e )
{
// 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 'CWeapFlame') ) 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 'MWeapLightning' ) e.Replacement = 'Sparkster';
else if ( e.Replacee is 'FWeaponPiece3' ) 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,2)];
break;
case 3:
case 4:
case 5:
e.Replacement = greenpool[Random[Replacement](0,2)];
break;
case 6:
case 7:
e.Replacement = whitepool[Random[Replacement](0,1)];
break;
case 8:
case 9:
case 10:
e.Replacement = purplepool[Random[Replacement](0,1)];
break;
case 11:
case 12:
e.Replacement = bluepool[Random[Replacement](0,2)];
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](1,5)];
break;
case 3:
case 4:
case 5:
e.Replacement = greenpool[Random[Replacement](1,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](1,2)];
break;
case 12:
case 13:
e.Replacement = bluepool[Random[Replacement](1,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,9) )
{
case 0:
case 1:
case 2:
case 3:
e.Replacement = 'HellblazerMissiles';
break;
case 4:
case 5:
case 6:
e.Replacement = 'HellblazerCrackshots';
break;
case 7:
case 8:
e.Replacement = 'HellblazerRavagers';
break;
case 9:
e.Replacement = 'HellblazerWarheads';
break;
}
}
else if ( (e.Replacee == 'RocketBox') || (e.Replacee == 'PhoenixRodHefty') || (e.Replacee == 'MaceHefty') )
{
switch ( Random[Replacements](0,9) )
{
case 0:
case 1:
case 2:
case 3:
if ( Random[Replacements](0,1) ) e.Replacement = 'HellblazerMissiles';
else e.Replacement = 'HellblazerMissileMag';
break;
case 4:
case 5:
case 6:
if ( Random[Replacements](0,2) ) e.Replacement = 'HellblazerCrackshots';
else e.Replacement = 'HellblazerCrackshotMag';
break;
case 7:
case 8:
if ( Random[Replacements](0,3) ) e.Replacement = 'HellblazerRavagers';
else e.Replacement = 'HellblazerRavagerMag';
break;
case 9:
if ( Random[Replacements](0,4) ) 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 e.Replacement = 'SilverBulletAmmo';
}
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 = 'SilverBulletAmmo';
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.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,7) ) e.Replacement = 'SWWMNothing';
else if ( Random[Replacements](0,3) ) 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.Replacee == 'ArtiFly') || (e.Replacee == 'ArtiSpeedBoots') ) 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 == 'ArtiBoostArmor') ) e.Replacement = 'Ragekit';
else if ( (e.Replacee == 'AllMap') || (e.Replacee == 'SuperMap') ) e.Replacement = 'Omnisight';
else if ( (e.Replacee == 'Infrared') || (e.Replacee == 'ArtiTorch') || (e.Replacee == 'ArtiDarkServant') || (e.Replacee == 'ArtiBoostArmor') ) 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 )
{
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;
// TODO redo these as fake weapons
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]);
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;
if ( (amt > 0) && players[e.Args[1]].mo.GiveInventory(item,amt) )
{
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());
}
}
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;
players[e.Args[0]].mo.UseInventory(i);
}
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.));
}
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]],999999999);
SWWMScoreObj.Spawn(999999999,players[e.Args[0]].mo.Vec3Offset(0,0,players[e.Args[0]].mo.Height/2));
}
}
// 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());
}
}
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);
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);
}
else
{
Shader.SetEnabled(p,"Glitch",false);
Shader.SetEnabled(p,"Grain",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
static void DoBlast( Actor Source, double ExplosionRadius, double MomentumTransfer, Actor ignoreme = null )
{
BlockThingsIterator bi = BlockThingsIterator.Create(Source,ExplosionRadius);
while ( bi.Next() )
{
Actor a = bi.Thing;
if ( !a || (a == ignoreme) || !a.bSHOOTABLE || !Source.CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) || (a == Source) || (Source.Distance3D(a) > ExplosionRadius) || a.bCANNOTPUSH || (a.Mass >= 10000000) )
continue;
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-max(0,(dist-a.radius)/ExplosionRadius);
dir = dir/dist;
a.vel += dir*damagescale*(MomentumTransfer/(Thinker.TICRATE*max(50,a.mass)));
}
}
// Same for this
static void DoKnockback( Actor Victim, Vector3 HitDirection, double MomentumTransfer )
{
if ( !Victim || !Victim.bSHOOTABLE || Victim.bCANNOTPUSH || (Victim.Mass >= 10000000) ) return;
Victim.vel += HitDirection*(MomentumTransfer/(Thinker.TICRATE*max(50,Victim.Mass)));
}
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;
}
}
// 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 || (timer > 0)) )
{
wasinmap = false;
if ( timer == 1 )
{
Console.Printf(TEXTCOLOR_GOLD.."Oopsie Woopsie!"..TEXTCOLOR_NORMAL);
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 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);
}
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;
}
}
override void UiTick()
{
if ( !detected ) return;
if ( (gamestate == GS_LEVEL) || (gamestate == GS_TITLELEVEL) )
{
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.IsNull() ) scr = TexMan.CheckForTexture("graphics/bdscreen.png",TexMan.Type_Any);
Screen.Dim("Red",(timer/350.)-.2,0,0,Screen.GetWidth(),Screen.GetHeight());
Screen.DrawTexture(scr,false,FRandom[bdscreen](-1,1)*max(timer-40,0)**3*.000003,FRandom[bdscreen](-1,1)*max(timer-40,0)**3*.000003,DTA_VirtualWidth,1280,DTA_VirtualHeight,960,DTA_Alpha,min(1.,timer/50.));
Screen.Dim("Red",(timer/70.)-3.5,0,0,Screen.GetWidth(),Screen.GetHeight());
}
}