// player-specific thinkers Class SWWMStaticThinker : Thinker abstract { // shell class to iterate easier on sanity checks // all subclasses should be guaranteed to have a statnum of STAT_STATIC // if they don't, something is very wrong } // "Full History" contains all messages since session start, nothing is flushed // this can be accessed from a section of the knowledge base Class SWWMFullHistory : SWWMStaticThinker { String lastmap; Array msg; static clearscope SWWMFullHistory Get() { let fh = SWWMFullHistory(ThinkerIterator.Create("SWWMFullHistory",STAT_STATIC).Next()); return fh; } static play void PushMessage( String str, int tic, int type ) { let fh = SWWMFullHistory(ThinkerIterator.Create("SWWMFullHistory",STAT_STATIC).Next()); if ( !fh ) { fh = new("SWWMFullHistory"); fh.ChangeStatNum(STAT_STATIC); } MsgLine m; if ( level.mapname != fh.lastmap ) { // push a map change label m = new("MsgLine"); m.str = level.levelname; m.type = -1; fh.lastmap = level.mapname; fh.msg.Push(m); } m = new("MsgLine"); m.str = str; m.tic = tic; m.type = type; fh.msg.Push(m); } } // Dedicated mission log (for custom maps) Class SWWMMissionLog : SWWMStaticThinker { Array entries; bool clockset; int year, month, day, hour, minute; String tz; static clearscope SWWMMissionLog Get() { let ml = SWWMMissionLog(ThinkerIterator.Create("SWWMMissionLog",STAT_STATIC).Next()); return ml; } static play void AddLog( String str ) { let ml = SWWMMissionLog(ThinkerIterator.Create("SWWMMissionLog",STAT_STATIC).Next()); if ( !ml ) { ml = new("SWWMMissionLog"); ml.ChangeStatNum(STAT_STATIC); } if ( ml.entries.Find(str) < ml.entries.Size() ) return; ml.entries.Push(str); Console.Printf(StringTable.Localize("$SWWM_NEWMISSION")); } static play void SetClock( int year, int month, int day, int hour, int minute, String tz = "JST" ) { let ml = SWWMMissionLog(ThinkerIterator.Create("SWWMMissionLog",STAT_STATIC).Next()); if ( !ml ) { ml = new("SWWMMissionLog"); ml.ChangeStatNum(STAT_STATIC); } ml.clockset = true; ml.year = year; ml.month = month; ml.day = day; ml.hour = hour; ml.minute = minute; ml.tz = tz; } } // 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 : SWWMStaticThinker { PlayerInfo myplayer; int lastspawn, dashcount, boostcount, stompcount, airtime, kills, deaths, damagedealt, hdamagedealt, damagetaken, hdamagetaken, mkill, hiscore, 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; int plushuses; // for pistol start info (to avoid it within hubs) int lastcluster; // for trash removal achievement int nazicleanup; // stored for hexen int puzzlecnt, realpuzzlecnt; // easter eggs int silveregg, quadegg; bool oldcheat; bool gotyorick; // caching SWWMHandler hnd; 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 'SWWMPuff' ) inflictor = inflictor.master; // special puffs transfer real inflictor through master pointer if ( inflictor is 'Weapon' ) which = Weapon(inflictor).GetClass(); if ( which is 'DualExplodiumGun' ) which = 'ExplodiumGun'; // don't credit sister weapon if ( inflictor && 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 'HammerShockwave') || (inflictor is 'HammerRadiusShockwave') ) which = 'ItamexHammer'; else if ( (inflictor is 'ExplodiumMagArm') || (inflictor is 'ExplodiumMagProj') || (inflictor is 'ExplodiumBulletImpact') ) which = 'ExplodiumGun'; else if ( (inflictor is 'GoldenSubImpact') || (inflictor is 'GoldenSubSubImpact') ) which = 'Spreadgun'; else if ( (inflictor is 'EvisceratorChunk') || (inflictor is 'EvisceratorProj') ) which = 'Eviscerator'; else if ( inflictor is 'SheenTrail' ) which = 'HeavyMahSheenGun'; else if ( (inflictor is 'HellblazerMissile') || (inflictor is 'HellblazerArm') ) which = 'Hellblazer'; else if ( (inflictor is 'QuadProj') || (inflictor is 'QuadExplArm') || (inflictor is 'OnFire') ) which = 'Quadravol'; 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 'SilverImpact') ) 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 'MisterBulletImpact') || (inflictor is 'MisterPop') || (inflictor is 'MisterFuzzy') || (inflictor is 'MisterGrenade') || (inflictor is 'MisterRailBeam') ) which = 'MisterRifle'; 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 ) { if ( !hnd ) hnd = SWWMHandler(EventHandler.Find("SWWMHandler")); Class mvictim = SWWMUtility.MergeMonster(hnd,victim.GetClass()); bool found = false; for ( int i=0; i which = WeaponFromInflictor(inflictor,damagetype); if ( !which ) return; for ( int i=0; i s.hiscore) ) s.hiscore = c.credits; SWWMLoreLibrary.Add(p,"ScoreSystem"); } static clearscope bool CanTake( PlayerInfo p, int amount ) { let c = Find(p); if ( !c ) return false; // safeguard if ( amount < 0 ) ThrowAbortException("SWWMCredits.CanTake() called with negative amount"); // too much! if ( (amount > 999999999) || (c.credits-amount < 0) || (c.credits-amount > c.credits) ) return false; return true; } static bool Take( PlayerInfo p, int amount, int hamount = 0 ) { let c = Find(p); if ( !c ) return false; // safeguard if ( amount < 0 ) ThrowAbortException("SWWMCredits.Take() called with negative amount"); // too much! if ( (amount > 999999999) || (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; } } // Lore holder enum ELoreTab { LORE_ITEM, LORE_ENEMY, LORE_PEOPLE, LORE_LORE // lol }; Class SWWMLore { String tag, text, assoc; TextureID img; int tab; bool read; } Class SWWMLoreLibrary : SWWMStaticThinker { PlayerInfo myplayer; Array ent; int lastaddtic; static bool PreVerify( String ref ) { // restrictions let mlog = SWWMMissionLog.Get(); if ( !(gameinfo.gametype&GAME_Raven) && (!mlog || ((mlog.year < 2171) && (mlog.month < 3))) ) { if ( ref ~== "Parthoris" ) return true; if ( ref ~== "Sidhe" ) return true; if ( ref ~== "SerpentRiders" ) return true; } if ( !(gameinfo.gametype&GAME_Hexen) && (!mlog || ((mlog.year < 2171) && (mlog.month < 4))) ) { if ( ref ~== "Cronos" ) return true; if ( ref ~== "Kirin" ) return true; // not met if ( ref ~== "Alakir" ) return true; // likewise 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 let mlog = SWWMMissionLog.Get(); if ( (gameinfo.gametype&GAME_Hexen) || (mlog && (mlog.year >= 2171) && (mlog.month >= 4)) ) { 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_BRCALBUM" ) text = "SWWM_LORETXT_BRCALBUM2"; // comment about kirin's song else if ( text ~== "SWWM_LORETXT_DEMOLITIONIST" ) text = "SWWM_LORETXT_DEMOLITIONIST3"; // married to kirin else if ( text ~== "SWWM_LORETXT_DEMONINVASION" ) text = "SWWM_LORETXT_DEMONINVASION4"; // meeting with azkhan 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_IBUKIMILK" ) text = "SWWM_LORETXT_IBUKIMILK3"; // tasted by kirin else if ( text ~== "SWWM_LORETXT_MAIDBOT" ) text = "SWWM_LORETXT_MAIDBOT2"; // married to kirin else if ( text ~== "SWWM_LORETXT_NANA" ) text = "SWWM_LORETXT_NANA3"; // stuff that happened at the wedding else if ( text ~== "SWWM_LORETXT_NOVOSKHANA" ) text = "SWWM_LORETXT_NOVOSKHANA2"; // met up with the empress 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_SAFETYTETHER" ) text = "SWWM_LORETXT_SAFETYTETHER2"; // we're in cronos now 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 else if ( text ~== "SWWM_LORETXT_MADCAT" ) text = "SWWM_LORETXT_MADCAT3"; // demolition quest released else if ( text ~== "SWWM_LORETXT_MARISA" ) text = "SWWM_LORETXT_MARISA3"; // post-wedding update } if ( (gameinfo.gametype&GAME_Raven) || (mlog && (mlog.year >= 2171) && (mlog.month >= 3)) ) { if ( text ~== "SWWM_LORETXT_AKARIPROJECT" ) text = "SWWM_LORETXT_AKARIPROJECT2"; // fiction becomes reality else if ( text ~== "SWWM_LORETXT_ARCHDEMONS" ) text = "SWWM_LORETXT_ARCHDEMONS3"; // archdemon generals assassinated else if ( text ~== "SWWM_LORETXT_DECADEMECH" ) text = "SWWM_LORETXT_DECADEMECH2"; // extra info else if ( text ~== "SWWM_LORETXT_DEMONINVASION" ) text = "SWWM_LORETXT_DEMONINVASION3"; // events of doom 64 and such 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 else if ( text ~== "SWWM_LORETXT_MADCAT" ) text = "SWWM_LORETXT_MADCAT2"; // interstellar demolitionist released else if ( text ~== "SWWM_LORETXT_MARISA" ) text = "SWWM_LORETXT_MARISA2"; // post-invasion update } if ( (gameinfo.gametype&GAME_Raven) || SWWMUtility.IsEviternity() || (mlog && (mlog.year >= 2150) && (mlog.month >= 5)) ) { if ( text ~== "SWWM_LORETXT_AKARILABS" ) text = "SWWM_LORETXT_AKARILABS2"; // demo won, akari project announced else if ( text ~== "SWWM_LORETXT_ARCHDEMONS" ) text = "SWWM_LORETXT_ARCHDEMONS2"; // demon invasion repelled 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_DEMONINVASION" ) text = "SWWM_LORETXT_DEMONINVASION2"; // victory against invasion 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_IBUKIMILK" ) text = "SWWM_LORETXT_IBUKIMILK2"; // tasted by demo 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_VOICEBOX" ) text = "SWWM_LORETXT_VOICEBOX2"; // remarks on demo's voice as a maidbot 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 ( deathmatch || 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