Fix the incorrect SPAC_PCross behaviour in other spots. Redo swap weapon implementation. Should now theoretically allow for not just two, but many more swap weapons per slot. Minor fixups.
1172 lines
32 KiB
Text
1172 lines
32 KiB
Text
// various stat tracking thinkers and others
|
|
|
|
// Stats
|
|
Class WeaponUsage
|
|
{
|
|
Class<Weapon> w;
|
|
int kills;
|
|
}
|
|
|
|
Class MonsterKill
|
|
{
|
|
Class<Actor> m;
|
|
int kills;
|
|
}
|
|
|
|
Class LevelStat
|
|
{
|
|
bool hub;
|
|
String levelname, mapname;
|
|
int kcount, ktotal;
|
|
int icount, itotal;
|
|
int scount, stotal;
|
|
int time, par, suck;
|
|
}
|
|
|
|
Class SWWMStats : Thinker
|
|
{
|
|
PlayerInfo myplayer;
|
|
int lastspawn, dashcount, boostcount, stompcount, airtime, kills,
|
|
deaths, damagedealt, hdamagedealt, damagetaken, hdamagetaken,
|
|
mkill, hiscore, hhiscore, topdealt, toptaken, skill, wponch,
|
|
busts, buttslams, secrets, items;
|
|
double grounddist, airdist, swimdist, fuelusage, topspeed, teledist;
|
|
Array<WeaponUsage> wstats;
|
|
Array<MonsterKill> mstats;
|
|
Array<LevelStat> lstats;
|
|
Array<Class<Weapon> > alreadygot;
|
|
int favweapon;
|
|
// these two are used for mission updates
|
|
Array<int> clustervisit;
|
|
Array<bool> secretdone;
|
|
// [Strife] preserve previous mission logs
|
|
String oldlogtext;
|
|
Array<String> questbacklog;
|
|
// hackaround for stuff getting lost
|
|
Array<Class<SWWMCollectible> > ownedcollectibles;
|
|
|
|
bool GotWeapon( Class<Weapon> which )
|
|
{
|
|
for ( int i=0; i<alreadygot.Size(); i++ )
|
|
{
|
|
if ( alreadygot[i] == which ) return true;
|
|
}
|
|
alreadygot.Push(which);
|
|
return false;
|
|
}
|
|
|
|
void AddDamageDealt( int dmg )
|
|
{
|
|
int upper = dmg/1000000000;
|
|
int lower = dmg%1000000000;
|
|
if ( hdamagedealt+upper > 999999999 ) hdamagedealt = 999999999;
|
|
else hdamagedealt += upper;
|
|
damagedealt += lower;
|
|
if ( damagedealt > 999999999 )
|
|
{
|
|
upper = damagedealt/1000000000;
|
|
lower = damagedealt%1000000000;
|
|
if ( hdamagedealt+upper > 999999999 ) hdamagedealt = 999999999;
|
|
else hdamagedealt += upper;
|
|
damagedealt = lower;
|
|
}
|
|
}
|
|
void AddDamageTaken( int dmg )
|
|
{
|
|
int upper = dmg/1000000000;
|
|
int lower = dmg%1000000000;
|
|
if ( hdamagetaken+upper > 999999999 ) hdamagetaken = 999999999;
|
|
else hdamagetaken += upper;
|
|
damagetaken += lower;
|
|
if ( damagetaken > 999999999 )
|
|
{
|
|
upper = damagetaken/1000000000;
|
|
lower = damagetaken%1000000000;
|
|
if ( hdamagetaken+upper > 999999999 ) hdamagetaken = 999999999;
|
|
else hdamagetaken += upper;
|
|
damagetaken = lower;
|
|
}
|
|
}
|
|
|
|
void AddLevelStats()
|
|
{
|
|
let ls = new("LevelStat");
|
|
ls.hub = !!(level.clusterflags&level.CLUSTER_HUB);
|
|
ls.levelname = level.levelname;
|
|
int iof = ls.levelname.IndexOf(" - by: ");
|
|
if ( iof != -1 ) ls.levelname.Truncate(iof);
|
|
ls.mapname = level.mapname;
|
|
ls.kcount = level.killed_monsters;
|
|
ls.ktotal = level.total_monsters;
|
|
ls.icount = level.found_items;
|
|
ls.itotal = level.total_items;
|
|
ls.scount = level.found_secrets;
|
|
ls.stotal = level.total_secrets;
|
|
ls.time = level.maptime;
|
|
ls.par = level.partime;
|
|
ls.suck = level.sucktime;
|
|
lstats.Push(ls);
|
|
}
|
|
|
|
void AddWeaponKill( Actor inflictor, Actor victim, Name damagetype )
|
|
{
|
|
if ( victim )
|
|
{
|
|
bool found = false;
|
|
for ( int i=0; i<mstats.Size(); i++ )
|
|
{
|
|
if ( mstats[i].m != victim.GetClass() ) continue;
|
|
found = true;
|
|
mstats[i].kills++;
|
|
break;
|
|
}
|
|
if ( !found )
|
|
{
|
|
let ms = new("MonsterKill");
|
|
ms.m = victim.GetClass();
|
|
ms.kills = 1;
|
|
mstats.Push(ms);
|
|
}
|
|
}
|
|
Class<Weapon> which = myplayer.ReadyWeapon?myplayer.ReadyWeapon.GetClass():null;
|
|
if ( inflictor is 'Weapon' ) which = Weapon(inflictor).GetClass();
|
|
if ( which is 'DualExplodiumGun' ) which = 'ExplodiumGun'; // don't credit sister weapon
|
|
// properly credit some projectiles to their respective gun
|
|
if ( inflictor is 'AirBullet' ) which = 'DeepImpact';
|
|
else if ( inflictor is 'PusherProjectile' ) which = 'PusherWeapon';
|
|
else if ( (inflictor is 'ExplodiumMagArm') || (inflictor is 'ExplodiumMagProj') || (inflictor is 'ExplodiumBulletImpact') ) which = 'ExplodiumGun';
|
|
else if ( (inflictor is 'DragonBreathArm') || ((inflictor is 'SaltImpact') && !inflictor.Args[0]) || ((inflictor is 'SaltBeam') && !inflictor.Args[1]) || (inflictor is 'OnFire') || (inflictor is 'FlamingChunk') || ((inflictor is 'TheBall') && !inflictor.special1) || (inflictor is 'GoldenImpact') || (inflictor is 'GoldenSubImpact') || (inflictor is 'GoldenSubSubImpact') ) which = 'Spreadgun';
|
|
else if ( ((inflictor is 'SaltImpact') && inflictor.Args[0]) || ((inflictor is 'SaltBeam') && inflictor.Args[1]) || ((inflictor is 'TheBall') && inflictor.special1) ) which = 'Wallbuster';
|
|
else if ( (inflictor is 'EvisceratorChunk') || (inflictor is 'EvisceratorProj') ) which = 'Eviscerator';
|
|
else if ( (inflictor is 'HellblazerRavagerArm') || (inflictor is 'HellblazerWarheadArm') ) which = 'Hellblazer';
|
|
else if ( (inflictor is 'BigBiospark') || (inflictor is 'BiosparkBall') || (inflictor is 'BiosparkBeamImpact') || (inflictor is 'BiosparkComboImpact') || (inflictor is 'BiosparkComboImpactSub') || (inflictor is 'BiosparkBeam') || (inflictor is 'BiosparkArc') ) which = 'Sparkster';
|
|
else if ( (inflictor is 'CandyBeam') || (inflictor is 'CandyPop') || (inflictor is 'CandyMagArm') || (inflictor is 'CandyGunProj') || (inflictor is 'CandyMagProj') || (inflictor is 'CandyBulletImpact') ) which = 'CandyGun';
|
|
else if ( (inflictor is 'YnykronBeam') || (inflictor is 'YnykronImpact') || (inflictor is 'YnykronSingularity') || (inflictor is 'YnykronCloud') || (inflictor is 'YnykronVoidBeam') || (inflictor is 'YnykronLightningArc') || (inflictor is 'YnykronLightningImpact') ) which = 'Ynykron';
|
|
else if ( (inflictor is 'Demolitionist') || (inflictor is 'DemolitionistShockwave') || (inflictor is 'DemolitionistRadiusShockwave') ) which = 'SWWMWeapon'; // hack to assume Demolitionist as weapon
|
|
else if ( inflictor is 'BigPunchSplash' )
|
|
{
|
|
// guess from damagetype
|
|
if ( (damagetype == 'Jump') || (damagetype == 'Dash') || (damagetype == 'Buttslam') || (damagetype == 'GroundPound') )
|
|
which = 'SWWMWeapon';
|
|
else if ( damagetype == 'Love' )
|
|
which = 'SWWMGesture';
|
|
// others are just weapon melee, so keep the readyweapon
|
|
}
|
|
if ( damagetype == 'Falling' )
|
|
which = 'Weapon'; // the gross hacks continue
|
|
if ( !which ) return;
|
|
for ( int i=0; i<wstats.Size(); i++ )
|
|
{
|
|
if ( wstats[i].w != which ) continue;
|
|
wstats[i].kills++;
|
|
if ( (favweapon == -1) || (wstats[favweapon].kills < wstats[i].kills) ) favweapon = i;
|
|
return;
|
|
}
|
|
let ws = new("WeaponUsage");
|
|
ws.w = which;
|
|
ws.kills = 1;
|
|
wstats.Push(ws);
|
|
if ( (favweapon == -1) || (wstats[favweapon].kills < ws.kills) )
|
|
favweapon = wstats.Size()-1;
|
|
}
|
|
|
|
static clearscope SWWMStats Find( PlayerInfo p )
|
|
{
|
|
let ti = ThinkerIterator.Create("SWWMStats",STAT_STATIC);
|
|
SWWMStats t;
|
|
while ( t = SWWMStats(ti.Next()) )
|
|
{
|
|
if ( t.myplayer != p ) continue;
|
|
return t;
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Scoring
|
|
Class SWWMCredits : Thinker
|
|
{
|
|
PlayerInfo myplayer;
|
|
int credits, hcredits;
|
|
|
|
static void Give( PlayerInfo p, int amount, int hamount = 0 )
|
|
{
|
|
let c = Find(p);
|
|
if ( !c ) return;
|
|
if ( c.credits+amount < c.credits ) c.credits = int.max;
|
|
else c.credits += amount;
|
|
while ( c.credits > 999999999 )
|
|
{
|
|
c.credits -= 1000000000;
|
|
c.hcredits++;
|
|
}
|
|
if ( (c.hcredits+hamount < c.hcredits) || (c.hcredits+hamount > 999999999) ) c.hcredits = 999999999;
|
|
else c.hcredits += hamount;
|
|
let s = SWWMStats.Find(p);
|
|
if ( s && ((c.hcredits > s.hhiscore) || ((c.credits > s.hiscore) && (c.hcredits >= s.hhiscore))) )
|
|
{
|
|
s.hiscore = c.credits;
|
|
s.hhiscore = c.hcredits;
|
|
}
|
|
}
|
|
|
|
static clearscope bool CanTake( PlayerInfo p, int amount, int hamount = 0 )
|
|
{
|
|
let c = Find(p);
|
|
if ( !c ) return false;
|
|
int req = amount, hreq = hamount;
|
|
while ( req > 999999999 )
|
|
{
|
|
req -= 1000000000;
|
|
hreq++;
|
|
}
|
|
// waaaaay too much
|
|
if ( (c.hcredits-hreq < 0) || (c.hcredits-hreq > c.hcredits) ) return false;
|
|
// too much!
|
|
if ( ((c.credits-amount < 0) || (c.credits-amount > c.credits)) && (c.hcredits-hreq <= 0) ) return false;
|
|
return true;
|
|
}
|
|
|
|
static bool Take( PlayerInfo p, int amount, int hamount = 0 )
|
|
{
|
|
let c = Find(p);
|
|
if ( !c ) return false;
|
|
int req = amount, hreq = hamount;
|
|
while ( req > 999999999 )
|
|
{
|
|
req -= 1000000000;
|
|
hreq++;
|
|
}
|
|
// waaaaay too much
|
|
if ( (c.hcredits-hreq < 0) || (c.hcredits-hreq > c.hcredits) ) return false;
|
|
// too much!
|
|
if ( ((c.credits-amount < 0) || (c.credits-amount > c.credits)) && (c.hcredits-hreq <= 0) ) return false;
|
|
c.hcredits -= hreq;
|
|
c.credits -= req;
|
|
while ( c.credits < 0 )
|
|
{
|
|
c.credits += 1000000000;
|
|
c.hcredits--;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static clearscope int, int Get( PlayerInfo p )
|
|
{
|
|
let c = Find(p);
|
|
if ( !c ) return 0;
|
|
return c.credits, c.hcredits;
|
|
}
|
|
|
|
static clearscope SWWMCredits Find( PlayerInfo p )
|
|
{
|
|
let ti = ThinkerIterator.Create("SWWMCredits",STAT_STATIC);
|
|
SWWMCredits t;
|
|
while ( t = SWWMCredits(ti.Next()) )
|
|
{
|
|
if ( t.myplayer != p ) continue;
|
|
return t;
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Trading history between players
|
|
Class SWWMTrade
|
|
{
|
|
int timestamp, type, amt;
|
|
String other;
|
|
Class<Inventory> what;
|
|
}
|
|
|
|
Class SWWMTradeHistory : Thinker
|
|
{
|
|
PlayerInfo myplayer;
|
|
Array<SWWMTrade> ent;
|
|
|
|
static void RegisterSend( PlayerInfo p, PlayerInfo other, Class<Inventory> what, int amt )
|
|
{
|
|
let th = Find(p);
|
|
if ( !th ) return;
|
|
SWWMTrade t = new("SWWMTrade");
|
|
t.timestamp = level.totaltime;
|
|
t.type = 0;
|
|
t.other = other.GetUserName();
|
|
t.what = what;
|
|
t.amt = amt;
|
|
th.ent.Push(t);
|
|
}
|
|
static void RegisterReceive( PlayerInfo p, PlayerInfo other, Class<Inventory> what, int amt )
|
|
{
|
|
let th = Find(p);
|
|
if ( !th ) return;
|
|
SWWMTrade t = new("SWWMTrade");
|
|
t.timestamp = level.totaltime;
|
|
t.type = 1;
|
|
t.other = other.GetUserName();
|
|
t.what = what;
|
|
t.amt = amt;
|
|
th.ent.Push(t);
|
|
}
|
|
|
|
static clearscope SWWMTradeHistory Find( PlayerInfo p )
|
|
{
|
|
let ti = ThinkerIterator.Create("SWWMTradeHistory",STAT_STATIC);
|
|
SWWMTradeHistory th;
|
|
while ( th = SWWMTradeHistory(ti.Next()) )
|
|
{
|
|
if ( th.myplayer != p ) continue;
|
|
return th;
|
|
}
|
|
return Null;
|
|
}
|
|
}
|
|
|
|
// Lore holder
|
|
enum ELoreTab
|
|
{
|
|
LORE_ITEM,
|
|
LORE_PEOPLE,
|
|
LORE_LORE // lol
|
|
};
|
|
|
|
Class SWWMLore
|
|
{
|
|
String tag, text, assoc;
|
|
int tab;
|
|
bool read;
|
|
}
|
|
|
|
Class SWWMLoreLibrary : Thinker
|
|
{
|
|
PlayerInfo myplayer;
|
|
Array<SWWMLore> ent;
|
|
int lastaddtic;
|
|
|
|
static bool PreVerify( String ref )
|
|
{
|
|
// restrictions
|
|
if ( !(gameinfo.gametype&(GAME_Raven|GAME_Strife)) )
|
|
{
|
|
if ( ref ~== "Parthoris" ) return true;
|
|
if ( ref ~== "Sidhe" ) return true;
|
|
if ( ref ~== "SerpentRiders" ) return true;
|
|
}
|
|
if ( !(gameinfo.gametype&(GAME_Hexen|GAME_Strife)) )
|
|
{
|
|
if ( ref ~== "Cronos" ) return true;
|
|
if ( ref ~== "Kirin" ) return true; // not met
|
|
if ( ref ~== "Fabricator" ) return true; // not yet introduced
|
|
if ( ref ~== "Administrators" ) return true; // not met
|
|
}
|
|
if ( !(gameinfo.gametype&GAME_Strife) )
|
|
{
|
|
if ( ref ~== "TheOrder" ) return true;
|
|
if ( ref ~== "TheFront" ) return true;
|
|
}
|
|
// restrict dlc weaponset until that's done
|
|
if ( ref ~== "ItamexHammer" ) return true;
|
|
if ( ref ~== "PuntzerBeta" ) return true;
|
|
if ( ref ~== "PuntzerGamma" ) return true;
|
|
if ( ref ~== "HeavyMahSheenGun" ) return true;
|
|
if ( ref ~== "Quadravol" ) return true;
|
|
if ( ref ~== "BlackfireIgniter" ) return true;
|
|
if ( ref ~== "EMPCarbine" ) return true;
|
|
if ( ref ~== "RayKhom" ) return true;
|
|
if ( ref ~== "GrandLance" ) return true;
|
|
// same for white lady
|
|
if ( ref ~== "WhiteLady" ) return true;
|
|
// check if entry is for a collectible
|
|
for ( int i=0; i<AllActorClasses.Size(); i++ )
|
|
{
|
|
let c = (Class<SWWMCollectible>)(AllActorClasses[i]);
|
|
if ( !c || (c == 'SWWMCollectible') ) continue;
|
|
let def = GetDefaultByType(c);
|
|
// skip if we match and it's not for this game
|
|
if ( (c.GetClassName() == ref) && !(gameinfo.gametype&def.avail) )
|
|
return true;
|
|
}
|
|
ref = ref.MakeUpper();
|
|
String tag = String.Format("SWWM_LORETAG_%s",ref);
|
|
String tab = String.Format("SWWM_LORETAB_%s",ref);
|
|
String text = String.Format("SWWM_LORETXT_%s",ref);
|
|
// check that it's valid
|
|
if ( StringTable.Localize(tag,false) == tag ) return true;
|
|
if ( StringTable.Localize(tab,false) == tab )
|
|
{
|
|
Console.Printf("Entry \"%s\" defines no tab.",ref);
|
|
return true;
|
|
}
|
|
if ( StringTable.Localize(text,false) == text )
|
|
{
|
|
Console.Printf("Entry \"%s\" defines no text.",ref);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool DirectAdd( String ref )
|
|
{
|
|
if ( PreVerify(ref) ) return true;
|
|
return InternalAdd(ref);
|
|
}
|
|
|
|
private bool InternalAdd( String ref )
|
|
{
|
|
ref = ref.MakeUpper();
|
|
String tag = String.Format("SWWM_LORETAG_%s",ref);
|
|
String tab = String.Format("SWWM_LORETAB_%s",ref);
|
|
String text = String.Format("SWWM_LORETXT_%s",ref);
|
|
String assoc = String.Format("SWWM_LOREREL_%s",ref);
|
|
// redirects
|
|
if ( gameinfo.gametype&GAME_Strife )
|
|
{
|
|
}
|
|
if ( gameinfo.gametype&(GAME_Hexen|GAME_Strife) )
|
|
{
|
|
if ( text ~== "SWWM_LORETXT_SAYA" )
|
|
text = "SWWM_LORETXT_SAYA3"; // married kirin
|
|
else if ( text ~== "SWWM_LORETXT_ANARUKON" )
|
|
text = "SWWM_LORETXT_ANARUKON2"; // comments from miyamoto-xanai wedding
|
|
else if ( text ~== "SWWM_LORETXT_HELL" )
|
|
text = "SWWM_LORETXT_HELL3"; // met father nostros during the wedding
|
|
else if ( text ~== "SWWM_LORETXT_NANA" )
|
|
text = "SWWM_LORETXT_NANA3"; // stuff that happened at the wedding
|
|
else if ( text ~== "SWWM_LORETXT_GHOULHUNT" )
|
|
text = "SWWM_LORETXT_GHOULHUNT2"; // met anthon anderken during the wedding
|
|
else if ( text ~== "SWWM_LORETXT_RAGEKIT" )
|
|
text = "SWWM_LORETXT_RAGEKIT2"; // kirin's reactions to demo using this item
|
|
else if ( text ~== "SWWM_LORETXT_SANKAIDERIHA" )
|
|
text = "SWWM_LORETXT_SANKAIDERIHA2"; // comments about kirin
|
|
else if ( text ~== "SWWM_LORETXT_SERPENTRIDERS" )
|
|
text = "SWWM_LORETXT_SERPENTRIDERS2"; // defeated d'sparil
|
|
else if ( text ~== "SWWM_LORETXT_XANIMEN" )
|
|
text = "SWWM_LORETXT_XANIMEN2"; // footnote about nuoma
|
|
else if ( text ~== "SWWM_LORETXT_ZANAVETH2" )
|
|
text = "SWWM_LORETXT_ZANAVETH22"; // met at wedding
|
|
else if ( text ~== "SWWM_LORETXT_YNYKRON" )
|
|
text = "SWWM_LORETXT_YNYKRON2"; // confirmed to harm (but not kill) gods
|
|
else if ( text ~== "SWWM_LORETXT_AKARIPROJECT" )
|
|
text = "SWWM_LORETXT_AKARIPROJECT3"; // mentions kirin
|
|
else if ( text ~== "SWWM_LORETXT_GODS" )
|
|
text = "SWWM_LORETXT_GODS2"; // beyond gods
|
|
}
|
|
if ( gameinfo.gametype&(GAME_Raven|GAME_Strife) )
|
|
{
|
|
if ( text ~== "SWWM_LORETXT_SAYA" )
|
|
text = "SWWM_LORETXT_SAYA2"; // dating demo
|
|
else if ( text ~== "SWWM_LORETXT_AKARILABS" )
|
|
text = "SWWM_LORETXT_AKARILABS2"; // demo won, akari project announced
|
|
else if ( text ~== "SWWM_LORETXT_DEMOLITIONIST" )
|
|
text = "SWWM_LORETXT_DEMOLITIONIST2"; // demo rewarded with maidbot frame
|
|
else if ( text ~== "SWWM_LORETXT_DOOMGUY" )
|
|
text = "SWWM_LORETXT_DOOMGUY2"; // he gone
|
|
else if ( text ~== "SWWM_LORETXT_UAC" )
|
|
text = "SWWM_LORETXT_UAC2"; // uac "reformed"
|
|
else if ( text ~== "SWWM_LORETXT_HELL" )
|
|
text = "SWWM_LORETXT_HELL2"; // invasion was a thing of the past
|
|
else if ( text ~== "SWWM_LORETXT_NANA" )
|
|
text = "SWWM_LORETXT_NANA2"; // demo met nana
|
|
else if ( text ~== "SWWM_LORETXT_ZANAVETH3" )
|
|
text = "SWWM_LORETXT_ZANAVETH32"; // iagb happened
|
|
else if ( text ~== "SWWM_LORETXT_BIGSHOT" )
|
|
text = "SWWM_LORETXT_BIGSHOT2"; // predictions about crimes_m
|
|
else if ( text ~== "SWWM_LORETXT_AKARIPROJECT" )
|
|
text = "SWWM_LORETXT_AKARIPROJECT2"; // fiction becomes reality
|
|
}
|
|
// check if existing
|
|
for ( int i=0; i<ent.Size(); i++ )
|
|
{
|
|
if ( ent[i].tag != "$"..tag ) continue;
|
|
return true;
|
|
}
|
|
SWWMLore e = new("SWWMLore");
|
|
e.tag = "$"..tag;
|
|
if ( StringTable.Localize(e.tag) == "" )
|
|
{
|
|
Console.Printf("Entry \"%s\" has an empty tag.",ref);
|
|
return true;
|
|
}
|
|
String ttab = StringTable.Localize(tab,false);
|
|
if ( ttab ~== "People" ) e.tab = LORE_PEOPLE;
|
|
else if ( ttab ~== "Lore" ) e.tab = LORE_LORE;
|
|
else if ( ttab ~== "Item" ) e.tab = LORE_ITEM;
|
|
else
|
|
{
|
|
Console.Printf("Entry \"%s\" has an incorrect tab setting of \"%s\".",ref,ttab);
|
|
return true;
|
|
}
|
|
e.text = "$"..text;
|
|
if ( StringTable.Localize(e.text) == "" )
|
|
{
|
|
Console.Printf("Entry \"%s\" has empty text.",ref);
|
|
return true;
|
|
}
|
|
e.assoc = "$"..assoc;
|
|
e.read = false;
|
|
// "new lore" message
|
|
if ( (level.maptime > 0) && (gametic > lastaddtic) && (myplayer == players[consoleplayer]) && (!menuactive || (menuactive == Menu.OnNoPause)) && (myplayer.mo is 'Demolitionist') )
|
|
Console.Printf(StringTable.Localize("$SWWM_NEWLORE"));
|
|
lastaddtic = gametic;
|
|
ent.Push(e);
|
|
return true;
|
|
}
|
|
|
|
static void Add( PlayerInfo p, String ref )
|
|
{
|
|
if ( PreVerify(ref) ) return;
|
|
SWWMLoreLibrary ll = Find(p);
|
|
if ( !ll )
|
|
{
|
|
ll = new("SWWMLoreLibrary");
|
|
ll.ChangeStatNum(STAT_STATIC);
|
|
ll.myplayer = p;
|
|
}
|
|
ll.InternalAdd(ref);
|
|
}
|
|
|
|
void MarkRead( int idx )
|
|
{
|
|
if ( (idx < 0) || (idx >= ent.Size()) ) return;
|
|
if ( !ent[idx].read )
|
|
{
|
|
ent[idx].read = true;
|
|
// add associated entries
|
|
Array<String> rel;
|
|
rel.Clear();
|
|
String assocstr = StringTable.Localize(ent[idx].assoc);
|
|
assocstr.Split(rel,";",0);
|
|
for ( int i=0; i<rel.Size(); i++ )
|
|
{
|
|
if ( (rel[i] != "") && !DirectAdd(rel[i]) )
|
|
Console.Printf("Related entry \"%s\" not found, please update LANGUAGE.txt",rel[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
clearscope int FindEntry( String tag )
|
|
{
|
|
for ( int i=0; i<ent.Size(); i++ )
|
|
{
|
|
if ( ent[i].tag ~== tag )
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static clearscope SWWMLoreLibrary Find( PlayerInfo p )
|
|
{
|
|
let ti = ThinkerIterator.Create("SWWMLoreLibrary",STAT_STATIC);
|
|
SWWMLoreLibrary ll;
|
|
while ( ll = SWWMLoreLibrary(ti.Next()) )
|
|
{
|
|
if ( ll.myplayer != p ) continue;
|
|
return ll;
|
|
}
|
|
return Null;
|
|
}
|
|
}
|
|
|
|
// floating scores
|
|
Class SWWMScoreObj : Thinker
|
|
{
|
|
int xcnt;
|
|
int xtcolor[5];
|
|
int xscore[5];
|
|
String xstr[5];
|
|
int tcolor;
|
|
int score;
|
|
Vector3 pos;
|
|
int lifespan, initialspan;
|
|
int starttic, seed, seed2;
|
|
SWWMScoreObj prev, next;
|
|
bool damnum;
|
|
Actor acc;
|
|
|
|
static SWWMScoreObj Spawn( int score, Vector3 pos, int tcolor = Font.CR_GOLD, Actor acc = null )
|
|
{
|
|
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
|
if ( !hnd ) return null;
|
|
let o = new("SWWMScoreObj");
|
|
o.ChangeStatNum(STAT_USER);
|
|
o.score = score;
|
|
o.pos = pos;
|
|
o.lifespan = o.initialspan = 60;
|
|
o.tcolor = tcolor;
|
|
o.starttic = level.maptime;
|
|
o.seed = Random[ScoreBits]();
|
|
o.seed2 = Random[ScoreBits]();
|
|
o.damnum = (tcolor == Font.CR_RED) || (tcolor == Font.CR_GREEN) || (tcolor == Font.CR_BLUE);
|
|
o.xcnt = 0;
|
|
o.acc = acc;
|
|
if ( o.damnum )
|
|
{
|
|
o.next = hnd.damnums;
|
|
if ( hnd.damnums ) hnd.damnums.prev = o;
|
|
hnd.damnums = o;
|
|
hnd.damnums_cnt++;
|
|
}
|
|
else
|
|
{
|
|
o.next = hnd.scorenums;
|
|
if ( hnd.scorenums ) hnd.scorenums.prev = o;
|
|
hnd.scorenums = o;
|
|
hnd.scorenums_cnt++;
|
|
}
|
|
return o;
|
|
}
|
|
|
|
override void OnDestroy()
|
|
{
|
|
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
|
if ( hnd )
|
|
{
|
|
if ( damnum )
|
|
{
|
|
hnd.damnums_cnt--;
|
|
if ( !prev ) hnd.damnums = next;
|
|
}
|
|
else
|
|
{
|
|
hnd.scorenums_cnt--;
|
|
if ( !prev ) hnd.scorenums = next;
|
|
}
|
|
if ( !prev )
|
|
{
|
|
if ( next ) next.prev = null;
|
|
}
|
|
else
|
|
{
|
|
prev.next = next;
|
|
if ( next ) next.prev = prev;
|
|
}
|
|
}
|
|
Super.OnDestroy();
|
|
}
|
|
|
|
override void Tick()
|
|
{
|
|
lifespan--;
|
|
if ( lifespan <= 0 ) Destroy();
|
|
}
|
|
}
|
|
|
|
enum EInterestType
|
|
{
|
|
INT_Key,
|
|
INT_Exit
|
|
};
|
|
|
|
Class SWWMInterest : Thinker
|
|
{
|
|
int type;
|
|
Key trackedkey;
|
|
Line trackedline;
|
|
Vector3 pos;
|
|
SWWMInterest prev, next;
|
|
String keytag;
|
|
|
|
static SWWMInterest Spawn( Vector3 pos = (0,0,0), Key thekey = null, Line theline = null )
|
|
{
|
|
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
|
if ( !hnd ) return null;
|
|
if ( (!thekey && !theline) || (thekey && theline) ) return null;
|
|
let i = new("SWWMInterest");
|
|
i.ChangeStatNum(STAT_USER);
|
|
i.trackedkey = thekey;
|
|
i.trackedline = theline;
|
|
if ( thekey )
|
|
{
|
|
i.type = INT_Key;
|
|
i.keytag = thekey.GetTag();
|
|
}
|
|
else if ( theline ) i.type = INT_Exit;
|
|
else
|
|
{
|
|
i.Destroy();
|
|
return null;
|
|
}
|
|
i.pos = thekey?thekey.Vec3Offset(0,0,thekey.height/2):pos;
|
|
i.next = hnd.intpoints;
|
|
if ( hnd.intpoints ) hnd.intpoints.prev = i;
|
|
hnd.intpoints = i;
|
|
hnd.intpoints_cnt++;
|
|
return i;
|
|
}
|
|
|
|
override void OnDestroy()
|
|
{
|
|
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
|
if ( hnd )
|
|
{
|
|
hnd.intpoints_cnt--;
|
|
if ( !prev )
|
|
{
|
|
hnd.intpoints = next;
|
|
if ( next ) next.prev = null;
|
|
}
|
|
else
|
|
{
|
|
prev.next = next;
|
|
if ( next ) next.prev = prev;
|
|
}
|
|
}
|
|
Super.OnDestroy();
|
|
}
|
|
|
|
override void Tick()
|
|
{
|
|
// update
|
|
if ( (type == INT_Key) && (!trackedkey || trackedkey.Owner) ) Destroy();
|
|
else if ( trackedkey ) pos = trackedkey.Vec3Offset(0,0,trackedkey.height/2);
|
|
}
|
|
}
|
|
|
|
Class SWWMItemSense : Thinker
|
|
{
|
|
Inventory item;
|
|
String tag;
|
|
int updated;
|
|
Demolitionist parent;
|
|
SWWMItemSense prev, next;
|
|
Vector3 pos;
|
|
|
|
static SWWMItemSense Spawn( Demolitionist parent, Inventory item )
|
|
{
|
|
if ( !parent || !item ) return null;
|
|
// only refresh the updated time if existing
|
|
for ( SWWMItemSense s=parent.itemsense; s; s=s.next )
|
|
{
|
|
if ( s.item != item ) continue;
|
|
s.updated = level.maptime+35;
|
|
s.pos = item.Vec3Offset(0,0,item.height);
|
|
return s;
|
|
}
|
|
let i = new("SWWMItemSense");
|
|
i.ChangeStatNum(STAT_USER);
|
|
i.item = item;
|
|
i.parent = parent;
|
|
i.updated = level.maptime+35;
|
|
i.UpdateTag();
|
|
i.pos = item.Vec3Offset(0,0,item.height);
|
|
i.next = parent.itemsense;
|
|
if ( parent.itemsense ) parent.itemsense.prev = i;
|
|
parent.itemsense = i;
|
|
parent.itemsense_cnt++;
|
|
return i;
|
|
}
|
|
|
|
void UpdateTag()
|
|
{
|
|
if ( !item ) return;
|
|
// certain ammo types use the pickup message as it's amount-aware
|
|
if ( (item is 'RedShell') || (item is 'GreenShell')
|
|
|| (item is 'WhiteShell') || (item is 'BlueShell')
|
|
|| (item is 'BlackShell') || (item is 'PurpleShell')
|
|
|| (item is 'GoldShell')/* || (item is 'SMW05Ammo')
|
|
|| (item is 'SheenAmmo')*/ )
|
|
tag = item.PickupMessage();
|
|
else tag = item.GetTag();
|
|
}
|
|
|
|
override void OnDestroy()
|
|
{
|
|
if ( parent )
|
|
{
|
|
parent.itemsense_cnt--;
|
|
if ( !prev )
|
|
{
|
|
parent.itemsense = next;
|
|
if ( next ) next.prev = null;
|
|
}
|
|
else
|
|
{
|
|
prev.next = next;
|
|
if ( next ) next.prev = prev;
|
|
}
|
|
}
|
|
Super.OnDestroy();
|
|
}
|
|
|
|
override void Tick()
|
|
{
|
|
if ( !parent )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
// expire
|
|
if ( level.maptime > updated+70 ) Destroy();
|
|
}
|
|
}
|
|
|
|
// enemy combat tracker
|
|
Class SWWMCombatTracker : Thinker
|
|
{
|
|
Actor mytarget;
|
|
String mytag;
|
|
int updated, lasthealth, maxhealth;
|
|
DynamicValueInterpolator intp;
|
|
Vector3 pos, prevpos, oldpos, oldprev;
|
|
PlayerInfo myplayer;
|
|
SWWMCombatTracker prev, next;
|
|
bool legged, mutated;
|
|
int tcnt;
|
|
double height;
|
|
transient CVar funtags, maxdist, damagebars;
|
|
bool funtag;
|
|
int mxdist, dbar;
|
|
bool bBOSS, bFRIENDLY;
|
|
bool firsthit;
|
|
|
|
void UpdateTag()
|
|
{
|
|
// only the first tracker accesses the CVar, saves on perf
|
|
if ( !prev )
|
|
{
|
|
if ( !funtags ) funtags = CVar.GetCVar('swwm_funtags',players[consoleplayer]);
|
|
funtag = funtags.GetBool();
|
|
}
|
|
if ( next ) next.funtag = funtag;
|
|
if ( mytarget && (mytarget.player || mytarget.bISMONSTER || (mytarget is 'BossBrain') || (mytarget is 'SWWMHangingKeen')) )
|
|
{
|
|
String realtag = funtag?SWWMUtility.GetFunTag(mytarget,FallbackTag):mytarget.GetTag(FallbackTag);
|
|
if ( realtag == FallbackTag )
|
|
{
|
|
realtag = mytarget.GetClassName();
|
|
SWWMUtility.BeautifyClassName(realtag);
|
|
}
|
|
mytag = mytarget.player?(mytarget.player.mo!=mytarget)?String.Format(StringTable.Localize("$FN_VOODOO"),mytarget.player.GetUserName()):mytarget.player.GetUserName():realtag;
|
|
}
|
|
else mytag = "";
|
|
}
|
|
|
|
static SWWMCombatTracker Spawn( Actor target )
|
|
{
|
|
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
|
if ( !hnd ) return null;
|
|
SWWMCombatTracker t;
|
|
for ( t=hnd.trackers; t; t=t.next )
|
|
{
|
|
if ( t.mytarget != target ) continue;
|
|
return t;
|
|
}
|
|
t = new("SWWMCombatTracker");
|
|
t.ChangeStatNum(STAT_USER);
|
|
t.mytarget = target;
|
|
t.UpdateTag();
|
|
if ( target.player )
|
|
{
|
|
t.lasthealth = target.health;
|
|
t.maxhealth = target.default.health;
|
|
}
|
|
else t.lasthealth = t.maxhealth = target.health;
|
|
t.updated = int.min;
|
|
t.height = target.height;
|
|
t.pos = level.Vec3Offset(target.pos,(0,0,t.height));
|
|
t.prevpos = level.Vec3Offset(target.prev,(0,0,t.height));
|
|
t.oldpos = target.pos;
|
|
t.oldprev = target.prev;
|
|
t.intp = DynamicValueInterpolator.Create(t.lasthealth,.5,1,100);
|
|
t.myplayer = target.player;
|
|
t.next = hnd.trackers;
|
|
t.bBOSS = target.bBOSS;
|
|
t.bFRIENDLY = target.bFRIENDLY;
|
|
if ( hnd.trackers )
|
|
{
|
|
hnd.trackers.prev = t;
|
|
// propagate cvar values
|
|
t.mxdist = hnd.trackers.mxdist;
|
|
t.dbar = hnd.trackers.dbar;
|
|
}
|
|
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()
|
|
{
|
|
// only the first tracker accesses the CVars, saves on perf
|
|
if ( !prev )
|
|
{
|
|
if ( !damagebars ) damagebars = CVar.GetCVar('swwm_damagetarget',players[consoleplayer]);
|
|
if ( !maxdist ) maxdist = CVar.GetCVar('swwm_maxtargetdist',players[consoleplayer]);
|
|
dbar = damagebars.GetInt();
|
|
mxdist = maxdist.GetInt();
|
|
}
|
|
if ( next )
|
|
{
|
|
next.dbar = dbar;
|
|
next.mxdist = mxdist;
|
|
}
|
|
// is target gone or dead?
|
|
if ( !mytarget || (mytarget.Health <= 0) )
|
|
{
|
|
// we're done
|
|
if ( updated > level.maptime ) updated = level.maptime;
|
|
lasthealth = 0;
|
|
prevpos = pos; // prevent stuttering
|
|
intp.Update(lasthealth);
|
|
if ( level.maptime > updated+35 ) Destroy();
|
|
return;
|
|
}
|
|
// don't update dormant targets
|
|
if ( mytarget.bDORMANT )
|
|
return;
|
|
// in deathmatch, don't update for hostile players
|
|
if ( deathmatch && mytarget.player && (mytarget.player.mo == mytarget) )
|
|
{
|
|
if ( teamplay && (mytarget.player.GetTeam() != players[consoleplayer].GetTeam()) )
|
|
return;
|
|
else if ( !teamplay ) return;
|
|
}
|
|
// only update height/position while alive
|
|
bool heightchanged = false;
|
|
if ( height != mytarget.height ) heightchanged = true;
|
|
height = mytarget.height;
|
|
if ( heightchanged || (mytarget.pos != oldpos) || (mytarget.prev != oldprev) )
|
|
{
|
|
oldpos = mytarget.pos;
|
|
oldprev = mytarget.prev;
|
|
pos = level.Vec3Offset(mytarget.pos,(0,0,height));
|
|
prevpos = level.Vec3Offset(mytarget.prev,(0,0,height));
|
|
}
|
|
tcnt++;
|
|
if ( (tcnt == 1) && !mytarget.player )
|
|
{
|
|
// post-spawn health inflation check
|
|
if ( lasthealth > maxhealth )
|
|
{
|
|
maxhealth = lasthealth;
|
|
intp.Reset(lasthealth);
|
|
}
|
|
}
|
|
if ( (tcnt == 6) && !mytarget.player )
|
|
{
|
|
// legendoom check
|
|
for ( Inventory i=mytarget.inv; i; i=i.inv )
|
|
{
|
|
if ( i.GetClassName() != "LDLegendaryMonsterToken" ) continue;
|
|
legged = true;
|
|
// adjust for health inflation
|
|
if ( lasthealth > maxhealth )
|
|
{
|
|
maxhealth = lasthealth;
|
|
intp.Reset(lasthealth);
|
|
}
|
|
}
|
|
}
|
|
if ( legged && !mutated )
|
|
{
|
|
// check inventory regularly to mark as mutated
|
|
for ( Inventory i=mytarget.inv; i; i=i.inv )
|
|
{
|
|
if ( i.GetClassName() != "LDLegendaryMonsterTransformed" ) continue;
|
|
mutated = true;
|
|
Console.Printf(StringTable.Localize("$SWWM_LTFORM"),mytag);
|
|
}
|
|
}
|
|
bFRIENDLY = mytarget.bFRIENDLY;
|
|
if ( mytarget.Health < lasthealth ) firsthit = true;
|
|
lasthealth = mytarget.Health;
|
|
intp.Update(lasthealth);
|
|
// special update conditions
|
|
if ( dbar )
|
|
{
|
|
if ( (dbar == 2) && (lasthealth >= maxhealth) )
|
|
return;
|
|
else if ( (dbar == 1) && !firsthit )
|
|
return;
|
|
}
|
|
if ( (mytarget.bISMONSTER || mytarget.player) && !mytarget.bINVISIBLE && !mytarget.bCORPSE )
|
|
{
|
|
bool straifu = false;
|
|
if ( (gameinfo.gametype&GAME_Strife) && (!mytarget.bINCOMBAT && !mytarget.bJUSTATTACKED) || (mytarget is 'Beggar') || (mytarget is 'Peasant') )
|
|
straifu = true;
|
|
// players (but not voodoo dolls), always visible
|
|
if ( mytarget.player && (mytarget.player.mo == mytarget) ) updated = level.maptime+35;
|
|
// friendlies within a set distance
|
|
else if ( mytarget.bFRIENDLY && ((mxdist <= 0) || (mytarget.Vec3To(players[consoleplayer].Camera).length() < mxdist)) && players[consoleplayer].Camera.CheckSight(mytarget,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) updated = level.maptime+35;
|
|
// enemies within a set distance that have us as target
|
|
else if ( !straifu && mytarget.target && (mytarget.target.Health > 0) && (mytarget.target.player == players[consoleplayer]) && ((mxdist <= 0) || (mytarget.Vec3To(players[consoleplayer].Camera).length() < mxdist)) && players[consoleplayer].Camera.CheckSight(mytarget,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) updated = level.maptime+70;
|
|
// any visible enemies within one quarter of the set distance
|
|
else if ( ((mxdist <= 0) || (mytarget.Vec3To(players[consoleplayer].Camera).length() < (mxdist/4))) && players[consoleplayer].Camera.CheckSight(mytarget,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) updated = level.maptime;
|
|
}
|
|
else if ( (mytarget is 'BossBrain') || (mytarget is 'SWWMHangingKeen') )
|
|
{
|
|
// special stuff, only if visible
|
|
if ( ((mxdist <= 0) || (mytarget.Vec3To(players[consoleplayer].Camera).length() < (mxdist/4))) && players[consoleplayer].Camera.CheckSight(mytarget,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) updated = level.maptime;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
|
|
// Track last damage source to blame fall damage on
|
|
Class SWWMWhoPushedMe : Thinker
|
|
{
|
|
Actor tracked, instigator;
|
|
|
|
static void SetInstigator( Actor b, Actor whomst )
|
|
{
|
|
if ( !b || !whomst ) return;
|
|
let ti = ThinkerIterator.Create("SWWMWhoPushedMe",STAT_INFO);
|
|
SWWMWhoPushedMe ffd;
|
|
while ( ffd = SWWMWhoPushedMe(ti.Next()) )
|
|
{
|
|
if ( ffd.tracked != b ) continue;
|
|
ffd.instigator = whomst;
|
|
return;
|
|
}
|
|
ffd = new("SWWMWhoPushedMe");
|
|
ffd.ChangeStatNum(STAT_INFO);
|
|
ffd.tracked = b;
|
|
ffd.instigator = whomst;
|
|
}
|
|
|
|
static Actor RecallInstigator( Actor b )
|
|
{
|
|
if ( !b ) return null;
|
|
let ti = ThinkerIterator.Create("SWWMWhoPushedMe",STAT_INFO);
|
|
SWWMWhoPushedMe ffd;
|
|
while ( ffd = SWWMWhoPushedMe(ti.Next()) )
|
|
{
|
|
if ( ffd.tracked != b ) continue;
|
|
Actor whomst = ffd.instigator;
|
|
ffd.Destroy();
|
|
return whomst;
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
Class SWWMDamageAccumulator : Thinker
|
|
{
|
|
Actor victim, inflictor, source;
|
|
Array<Int> amounts;
|
|
int total;
|
|
Name type;
|
|
bool dontgib;
|
|
int flags;
|
|
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
// so many damn safeguards in this
|
|
if ( !victim )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
int gibhealth = victim.GetGibHealth();
|
|
// おまえはもう死んでいる
|
|
if ( (victim.health-total <= gibhealth) && !dontgib )
|
|
{
|
|
// safeguard for inflictors that have somehow ceased to exist, which apparently STILL CAN HAPPEN
|
|
if ( inflictor ) inflictor.bEXTREMEDEATH = true;
|
|
else type = 'Extreme';
|
|
}
|
|
// make sure accumulation isn't reentrant
|
|
if ( inflictor && (inflictor is 'EvisceratorChunk') ) inflictor.bAMBUSH = true;
|
|
// 何?
|
|
for ( int i=0; i<amounts.Size(); i++ )
|
|
{
|
|
if ( !victim ) break;
|
|
victim.DamageMobj(inflictor,source,amounts[i],type,DMG_THRUSTLESS|flags);
|
|
}
|
|
// clean up
|
|
if ( inflictor )
|
|
{
|
|
if ( inflictor is 'EvisceratorChunk' ) inflictor.bAMBUSH = false;
|
|
inflictor.bEXTREMEDEATH = false;
|
|
}
|
|
Destroy();
|
|
}
|
|
|
|
static void Accumulate( Actor victim, int amount, Actor inflictor, Actor source, Name type, bool dontgib = false, int flags = 0 )
|
|
{
|
|
if ( !victim ) return;
|
|
let ti = ThinkerIterator.Create("SWWMDamageAccumulator",STAT_USER);
|
|
SWWMDamageAccumulator a, match = null;
|
|
while ( a = SWWMDamageAccumulator(ti.Next()) )
|
|
{
|
|
if ( a.victim != victim ) continue;
|
|
match = a;
|
|
break;
|
|
}
|
|
if ( !match )
|
|
{
|
|
match = new("SWWMDamageAccumulator");
|
|
match.ChangeStatNum(STAT_USER);
|
|
match.victim = victim;
|
|
match.amounts.Clear();
|
|
}
|
|
match.amounts.Push(amount);
|
|
match.total += amount;
|
|
match.inflictor = inflictor;
|
|
match.source = source;
|
|
match.type = type;
|
|
match.dontgib = dontgib;
|
|
match.flags = flags;
|
|
}
|
|
|
|
static clearscope int GetAmount( Actor victim )
|
|
{
|
|
let ti = ThinkerIterator.Create("SWWMDamageAccumulator",STAT_USER);
|
|
SWWMDamageAccumulator a, match = null;
|
|
while ( a = SWWMDamageAccumulator(ti.Next()) )
|
|
{
|
|
if ( a.victim != victim ) continue;
|
|
return a.total;
|
|
}
|
|
return 0;
|
|
}
|
|
}
|