// player-specific thinkers // Stats Class WeaponUsage { Class w; int kills; } Class MonsterKill { Class 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, parries, pparries, pats, befriend, smooch; double grounddist, airdist, swimdist, fuelusage, topspeed, teledist; Array wstats; Array mstats; Array lstats; Array > alreadygot; int favweapon; // these two are used for mission updates Array clustervisit; Array secretdone; // hackaround for stuff getting lost Array > ownedcollectibles; // for pistol start info (to avoid it within hubs) int lastcluster; bool GotWeapon( Class which ) { for ( int i=0; i WeaponFromInflictor( Actor inflictor, Name damagetype ) { Class 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 if ( inflictor.FindInventory("ParriedBuff") ) which = 'DoomWeapon'; // gross hack // properly credit some projectiles to their respective gun else 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 'CorrodeDebuff') || (inflictor is 'CorrosiveFlechette') || ((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 'HellblazerMissile') || (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') || (inflictor is 'BiosparkCore') ) which = 'Sparkster'; else if ( (inflictor is 'SilverAirRip') || (inflictor is 'SilverAirRip2') || (inflictor is 'SilverImpact') || (inflictor is 'FatChodeImpact') || (inflictor is 'FatChodeExplosionArm') ) which = 'SilverBullet'; 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') || (inflictor is 'SWWMGesture') || (inflictor is 'SWWMItemGesture') ) 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 } else if ( inflictor is 'FroggyChair' ) which = 'SWWMItemGesture'; // more gross hacks if ( damagetype == 'Falling' ) which = 'Weapon'; // the gross hacks continue return which; } 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 which = WeaponFromInflictor(inflictor,damagetype); if ( !which ) return; for ( int i=0; i 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 what; } Class SWWMTradeHistory : Thinker { PlayerInfo myplayer; Array ent; static void RegisterSend( PlayerInfo p, PlayerInfo other, Class 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 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 ent; int lastaddtic; static bool PreVerify( String ref ) { // restrictions if ( !(gameinfo.gametype&GAME_Raven) ) { if ( ref ~== "Parthoris" ) return true; if ( ref ~== "Sidhe" ) return true; if ( ref ~== "SerpentRiders" ) return true; } if ( !(gameinfo.gametype&GAME_Hexen) ) { 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 } // check if entry is for a collectible for ( int i=0; i)(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) && !def.ValidGame() ) 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_Hexen ) { if ( text ~== "SWWM_LORETXT_AKARIPROJECT" ) text = "SWWM_LORETXT_AKARIPROJECT3"; // mentions kirin else if ( text ~== "SWWM_LORETXT_ANARUKON" ) text = "SWWM_LORETXT_ANARUKON2"; // comments from miyamoto-xanai wedding else if ( text ~== "SWWM_LORETXT_GHOULHUNT" ) text = "SWWM_LORETXT_GHOULHUNT2"; // met anthon anderken during the wedding else if ( text ~== "SWWM_LORETXT_GODS" ) text = "SWWM_LORETXT_GODS2"; // beyond gods else if ( text ~== "SWWM_LORETXT_HELL" ) text = "SWWM_LORETXT_HELL4"; // 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_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_SAYA" ) text = "SWWM_LORETXT_SAYA3"; // married 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_YNYKRON" ) text = "SWWM_LORETXT_YNYKRON2"; // confirmed to harm (but not kill) gods else if ( text ~== "SWWM_LORETXT_ZANAVETH2" ) text = "SWWM_LORETXT_ZANAVETH22"; // met at wedding } if ( gameinfo.gametype&GAME_Raven ) { if ( text ~== "SWWM_LORETXT_AKARIPROJECT" ) text = "SWWM_LORETXT_AKARIPROJECT2"; // fiction becomes reality else if ( text ~== "SWWM_LORETXT_DOOMGUY" ) text = "SWWM_LORETXT_DOOMGUY3"; // he gone else if ( text ~== "SWWM_LORETXT_HELL" ) text = "SWWM_LORETXT_HELL3"; // invasion was a thing of the past else if ( text ~== "SWWM_LORETXT_UAC" ) text = "SWWM_LORETXT_UAC3"; // events of doom 64 and more } if ( (gameinfo.gametype&GAME_Raven) || SWWMUtility.IsEviternity() ) { if ( text ~== "SWWM_LORETXT_AKARILABS" ) text = "SWWM_LORETXT_AKARILABS2"; // demo won, akari project announced else if ( text ~== "SWWM_LORETXT_BIGSHOT" ) text = "SWWM_LORETXT_BIGSHOT2"; // predictions about crimes_m 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"; // decommissioned else if ( text ~== "SWWM_LORETXT_GENERICCUBE" ) text = "SWWM_LORETXT_GENERICCUBE2"; // info from mykka else if ( text ~== "SWWM_LORETXT_HELL" ) text = "SWWM_LORETXT_HELL2"; // events of tnt/plutonia else if ( text ~== "SWWM_LORETXT_NANA" ) text = "SWWM_LORETXT_NANA2"; // demo met nana else if ( text ~== "SWWM_LORETXT_SAYA" ) text = "SWWM_LORETXT_SAYA2"; // dating demo else if ( text ~== "SWWM_LORETXT_UAC" ) text = "SWWM_LORETXT_UAC2"; // uac "reformed" else if ( text ~== "SWWM_LORETXT_ZANAVETH3" ) text = "SWWM_LORETXT_ZANAVETH32"; // iagb happened } // check if existing for ( int i=0; i 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 rel; rel.Clear(); String assocstr = StringTable.Localize(ent[idx].assoc); assocstr.Split(rel,";",0); for ( int i=0; i