Added hitscan parrying (not that it's very useful). Various fixes to some timing stuff. Added "F to pay respects" when in death screen. Added money cheat, figure out the key combination yourself. Changed score interpolator so it goes faster with huge score gains.
2711 lines
75 KiB
Text
2711 lines
75 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;
|
|
}
|
|
}
|
|
|
|
// 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_Raven) )
|
|
{
|
|
if ( ref ~== "Doomslayer" ) return true; // not witnessed
|
|
}
|
|
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 ded
|
|
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
|
|
}
|
|
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
|
|
else if ( text ~== "SWWM_LORETXT_HELL2" )
|
|
text = "SWWM_LORETXT_HELL3"; // mention the return of manakei
|
|
}
|
|
// 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 tcolor;
|
|
int score;
|
|
String str;
|
|
Vector3 pos;
|
|
int lifespan, initialspan;
|
|
int starttic, seed, seed2;
|
|
int ofs;
|
|
SWWMScoreObj prev, next;
|
|
bool damnum;
|
|
|
|
static SWWMScoreObj Spawn( int score, Vector3 pos, int tcolor = Font.CR_GOLD, String str = "", int ofs = 0 )
|
|
{
|
|
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.str = str;
|
|
o.seed = Random[ScoreBits]();
|
|
o.seed2 = Random[ScoreBits]();
|
|
o.ofs = ofs;
|
|
o.damnum = (tcolor == Font.CR_RED) || (tcolor == Font.CR_GREEN);
|
|
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;
|
|
|
|
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.pos = level.Vec3Offset(target.pos,(0,0,target.default.height));
|
|
t.prevpos = level.Vec3Offset(target.prev,(0,0,target.default.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,mytarget.default.height));
|
|
prevpos = level.Vec3Offset(mytarget.prev,(0,0,mytarget.default.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;
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
if ( mytarget.player || mytarget.bISMONSTER )
|
|
mytag = mytarget.player?mytarget.player.GetUserName():mytarget.GetTag();
|
|
else mytag = "";
|
|
int newhealth = mytarget.Health;
|
|
//if ( newhealth != lasthealth ) updated = level.maptime+35;
|
|
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
|
|
if ( players[consoleplayer].mo.CheckSight(mytarget) && ((mytarget.Vec3To(players[consoleplayer].mo).length() < 600) || (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 )
|
|
{
|
|
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 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 ) 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 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](16,32);
|
|
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("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 *= .2;
|
|
}
|
|
Spawn("PoofLight",pos);
|
|
}
|
|
BLPF A 1 Bright A_FadeOut(.3);
|
|
Wait;
|
|
}
|
|
}
|
|
|
|
Class TeleLight : PaletteLight
|
|
{
|
|
Default
|
|
{
|
|
Tag "ImpactWav";
|
|
ReactionTime 10;
|
|
Args 0,0,0,150;
|
|
}
|
|
}
|
|
|
|
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](16,32)*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;
|
|
}
|
|
A_FadeOut();
|
|
}
|
|
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();
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
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 )
|
|
{
|
|
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);
|
|
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 )
|
|
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);
|
|
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(2000,int(ceil(e.Thing.SpawnHealth()*.5)*10));
|
|
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 ( e.DamageSource.player == players[consoleplayer] )
|
|
SWWMScoreObj.Spawn(0,e.Thing.Vec3Offset(0,0,e.Thing.Height/2),Font.CR_FIRE,"$SWWM_OVERKILL",++ofs);
|
|
}
|
|
score = int(score*(1.+.5*min(multilevel[pnum],16)));
|
|
if ( (multilevel[pnum] > 0) && (e.DamageSource.player == players[consoleplayer]) )
|
|
SWWMScoreObj.Spawn((multilevel[pnum]>=16)?int.max:(multilevel[pnum]+1),e.Thing.Vec3Offset(0,0,e.Thing.Height/2),Font.CR_FIRE,"$SWWM_MULTIKILL",++ofs);
|
|
if ( !tookdamage[pnum] )
|
|
{
|
|
score += 100+50*spreecount[pnum];
|
|
if ( (spreecount[pnum] > 0) && (e.DamageSource.player == players[consoleplayer]) )
|
|
SWWMScoreObj.Spawn(spreecount[pnum]+1,e.Thing.Vec3Offset(0,0,e.Thing.Height/2),Font.CR_FIRE,"$SWWM_SPREEKILL",++ofs);
|
|
}
|
|
if ( e.Thing.bBOSS )
|
|
{
|
|
score += 10000;
|
|
if ( e.DamageSource.player == players[consoleplayer] )
|
|
SWWMScoreObj.Spawn(0,e.Thing.Vec3Offset(0,0,e.Thing.Height/2),Font.CR_FIRE,"$SWWM_BOSSKILL",++ofs);
|
|
}
|
|
SWWMCredits.Give(e.DamageSource.player,score);
|
|
if ( e.DamageSource.player == players[consoleplayer] )
|
|
SWWMScoreObj.Spawn(score,e.Thing.Vec3Offset(0,0,e.Thing.Height/2));
|
|
if ( (level.killed_monsters+1 == level.total_monsters) && !allkills )
|
|
{
|
|
allkills = true;
|
|
SWWMCredits.Give(e.DamageSource.player,5000);
|
|
Console.Printf(StringTable.Localize("$SWWM_LASTMONSTER"),e.DamageSource.player.GetUserName(),5000);
|
|
}
|
|
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));
|
|
}
|
|
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:
|
|
e.Replacement = purplepool[0];
|
|
break;
|
|
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](1,2)];
|
|
break;
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
e.Replacement = greenpool[Random[Replacement](1,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](2,5)];
|
|
break;
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
e.Replacement = greenpool[Random[Replacement](2,4)];
|
|
break;
|
|
case 6:
|
|
case 7:
|
|
case 8:
|
|
e.Replacement = whitepool[Random[Replacement](1,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](2,3)];
|
|
break;
|
|
case 14:
|
|
e.Replacement = blackpool[Random[Replacement](0,2)];
|
|
break;
|
|
}
|
|
}
|
|
else if ( e.Replacee == 'ClipBox' )
|
|
{
|
|
if ( Random[Replacements](0,2) ) 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:
|
|
e.Replacement = 'HellblazerMissileMag';
|
|
break;
|
|
case 4:
|
|
case 5:
|
|
case 6:
|
|
e.Replacement = 'HellblazerCrackshotMag';
|
|
break;
|
|
case 7:
|
|
case 8:
|
|
e.Replacement = 'HellblazerRavagerMag';
|
|
break;
|
|
case 9:
|
|
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,2) ) 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') ) e.Replacement = 'SWWMNothing';
|
|
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;
|
|
switch ( e.Args[0] )
|
|
{
|
|
case 1:
|
|
if ( mo.FindState("Approve") ) mo.SetStateLabel("Approve");
|
|
break;
|
|
case 2:
|
|
if ( mo.FindState("Victory") ) mo.SetStateLabel("Victory");
|
|
break;
|
|
default:
|
|
if ( mo.FindState("Taunt") ) mo.SetStateLabel("Taunt");
|
|
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);
|
|
}
|
|
}
|
|
|
|
// 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,0xf) || (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());
|
|
}
|
|
}
|