// Handler responsible for item replacements and whatever else // Note: There's still a lot of code here that could be split to other files // I'll get that done eventually, it's a complicated process Class SWWMHandler : EventHandler { transient int lastlock, lastcombat; transient Array combatactors; transient Array combattics; transient int highesttic; transient int lastpickuptic[MAXPLAYERS]; SWWMCombatTracker trackers; SWWMScoreObj scorenums, damnums; SWWMInterest intpoints; SWWMSimpleTracker strackers; int trackers_cnt, scorenums_cnt, damnums_cnt, intpoints_cnt, strackers_cnt; bool tookdamage[MAXPLAYERS]; int spreecount[MAXPLAYERS]; int lastkill[MAXPLAYERS]; int multilevel[MAXPLAYERS]; int lastitemcount[MAXPLAYERS]; bool mnotify; bool allkills, allitems, allsecrets; bool mapclear; int mapclearagain, restartmus; String lastmus; int lastorder; bool lastloop; transient String curlang; transient bool curfuntags; transient int slotstrictwarn; // optimization OnFire fires; int fires_cnt; // prevents revived monsters from spawning in more golden shells Array alreadygold; // attempt to optimize Ynykron singularity suction Array suckableactors; // list contains a sector that belongs to each portal group // used to ease some portal-aware functions Array psectors; // stuff to reduce worldthingspawned overhead int bossmap; int iwantdie; int indoomvacation; // for checkreplacement bool hasdrlamonsters; // for minimap Array ffsectors; override void OnRegister() { // oneliner RNG must be relative to consoleplayer SetRandomSeed[DemoLines](Random[DemoLines]()+consoleplayer+MSTime()); // "uninitialize" some vars iwantdie = -1; bossmap = -1; indoomvacation = -1; // this one can be initialized here easily for ( int i=0; i 0) ) continue; let c = SWWMCredits.Find(players[i]); if ( c ) c.credits = c.hcredits = 0; } // end of episode resets if ( level.nextsecretmap.Left(6) == "enDSeQ" ) { for ( int i=0; i lines; lines.Clear(); data.Split(lines,"\n"); bool valid = false; for ( int j=0; j spl; spl.Clear(); lines[j].Split(spl," ",TOK_SKIPEMPTY); // check game string (if any) if ( spl.Size() > 2 ) { if ( (spl[2] ~== "DOOM") && !(gameinfo.gametype&GAME_Doom) ) continue; else if ( (spl[2] ~== "HERETIC") && !(gameinfo.gametype&GAME_Heretic) ) continue; else if ( (spl[2] ~== "HEXEN") && !(gameinfo.gametype&GAME_Hexen) ) continue; else if ( (spl[2] ~== "STRIFE") && !(gameinfo.gametype&GAME_Strife) ) continue; else if ( (spl[2] ~== "CHEX") && !(gameinfo.gametype&GAME_Chex) ) continue; } // valid lock, prepare it let li = new("LIEntry"); li.locknumber = spl[1].ToInt(); li.hascolor = false; // see if there's a Mapcolor defined int k = j+1; for ( int k=j+2; k skipme; skipme.Clear(); // find exit lines, and use lines that aren't exits for ( int i=0; i lastitemcount[i] ) { int score = 10*(players[i].itemcount-lastitemcount[i]); if ( (level.total_items == level.found_items) && !allitems ) { allitems = true; Console.Printf(StringTable.Localize("$SWWM_LASTITEM"),players[i].GetUserName(),500); score += 490; } SWWMCredits.Give(players[i],score); SWWMScoreObj.Spawn(score,players[i].mo.Vec3Offset(0,0,players[i].mo.Height/2)); lastitemcount[i] = players[i].itemcount; let s = SWWMStats.Find(players[i]); s.items++; } } } // combat tracking private void CombatTrack() { // prune old entries for ( int i=0; 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 ) continue; // ignore the dead if ( (a.Health <= 0) || a.bKILLED || a.bCORPSE ) continue; // ignore friends if ( a.IsFriend(players[consoleplayer].mo) ) continue; // [Strife] ignore if not in combat if ( (gameinfo.gametype&GAME_Strife) && !a.bINCOMBAT && !a.bJUSTATTACKED ) continue; // [Strife] ignore certain classes if ( (a is 'RatBuddy') || (a is 'Peasant') || (a is 'Beggar') ) continue; // [Strife] ignore Oracle's spectre while it's inactive if ( (a is 'AlienSpectre3') && a.InStateSequence(a.CurState,a.FindState("Spawn")) ) continue; // ignore if not targetted or player can't see it if ( (a.target != players[consoleplayer].mo) || !SWWMUtility.InPlayerFOV(players[consoleplayer],a) ) continue; // [HDoom] ignore cute girls if ( SWWMHDoomHandler.IsCuteGirl(a.target) ) continue; // is it already in? bool addme = true; for ( int i=0; i highesttic+700)) ) lastcombat = AddOneliner("fightstart",1,10); } private void OneHundredPercentCheck() { if ( !mapclear && (restartmus > 0) ) { restartmus--; if ( restartmus == 0 ) S_ChangeMusic(lastmus,lastorder,lastloop,true); return; } // ignore levels that have NOTHING if ( (level.total_secrets <= 0) && (level.total_items <= 0) && (level.total_monsters <= 0) ) return; if ( mapclear ) { if ( (swwm_silencemap == 1) && (musplaying.name != "") ) { lastmus = musplaying.name; lastorder = musplaying.baseorder; lastloop = musplaying.loop; S_ChangeMusic("",force:true); } else if ( (swwm_silencemap > 1) && (musplaying.name != "music/olg.ogg") ) { lastmus = musplaying.name; lastorder = musplaying.baseorder; lastloop = musplaying.loop; S_ChangeMusic("music/olg.ogg",force:true); } if ( (level.found_secrets < level.total_secrets) || (level.found_items < level.total_items) || (level.killed_monsters < level.total_monsters) ) { if ( swwm_silencemap > 0 ) { restartmus = 25; S_ChangeMusic("",force:true); } S_StartSound("recordscratch",CHAN_VOICE,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE); mapclear = false; if ( mapclearagain > 1 ) Console.Printf(StringTable.Localize("$SWWM_NOTCLEARAGAIN")); else Console.Printf(StringTable.Localize("$SWWM_NOTCLEAR")); } return; } if ( (level.found_secrets < level.total_secrets) || (level.found_items < level.total_items) || (level.killed_monsters < level.total_monsters) ) return; restartmus = 0; mapclear = true; if ( mapclearagain ) Console.Printf(StringTable.Localize("$SWWM_ALLCLEARAGAIN"),500); else Console.Printf(StringTable.Localize("$SWWM_ALLCLEAR"),5000); S_StartSound("misc/wow",CHAN_VOICE,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE); lastmus = musplaying.name; lastorder = musplaying.baseorder; lastloop = musplaying.loop; if ( swwm_silencemap == 1 ) S_ChangeMusic("",force:true); else if ( swwm_silencemap > 1 ) S_ChangeMusic("music/olg.ogg",force:true); for ( int i=0; i viewdist ) continue; if ( a == players[consoleplayer].Camera ) continue; if ( !a.player && !a.bSOLID && !a.bSHOOTABLE && !a.bISMONSTER && !a.bFRIENDLY && !(a is 'Inventory') && !(a is 'Chancebox') ) continue; if ( !thesight && !a.IsFriend(players[consoleplayer].mo) && !players[consoleplayer].Camera.CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue; if ( a.bKILLED || (a.Health <= 0) ) continue; if ( (a is 'Inventory') && (!a.bSPECIAL || Inventory(a).Owner) ) continue; if ( (a is 'Chancebox') && (a.CurState != a.SpawnState) ) continue; SWWMSimpleTracker.Track(a); } if ( swwm_mm_portaloverlay && (psectors.Size() > 1) ) { // oh boy here we go int thisgroup = players[consoleplayer].Camera.CurSector.portalgroup; for ( int i=0; i viewdist ) continue; if ( a == players[consoleplayer].Camera ) continue; if ( !a.player && !a.bSOLID && !a.bSHOOTABLE && !a.bISMONSTER && !a.bFRIENDLY && !(a is 'Inventory') && !(a is 'Chancebox') ) continue; if ( !thesight && !a.IsFriend(players[consoleplayer].mo) && !players[consoleplayer].Camera.CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) continue; if ( a.bKILLED || (a.Health <= 0) ) continue; if ( (a is 'Inventory') && (!a.bSPECIAL || Inventory(a).Owner) ) continue; if ( (a is 'Chancebox') && (a.CurState != a.SpawnState) ) continue; SWWMSimpleTracker.Track(a); } } } SWWMSimpleTracker trk = strackers; while ( trk ) { SWWMSimpleTracker next = trk.next; // minimize lifespan of destroyed targets if ( !trk.target ) trk.lastupdate = min(trk.lastupdate,level.maptime); else if ( !trk.expired ) { // "last breath" update if ( (trk.target.bKILLED || (trk.target.Health <= 0)) || ((trk.target is 'Inventory') && (!trk.target.bSPECIAL || Inventory(trk.target).Owner)) || ((trk.target is 'Chancebox') && (trk.target.CurState != trk.target.SpawnState)) ) trk.Update(); } // prune expired trackers if ( trk.lastupdate+140 < level.maptime ) { if ( !trk.prev ) strackers = trk.next; else trk.prev.next = trk.next; if ( trk.next ) trk.next.prev = trk.prev; trk.Destroy(); strackers_cnt--; } trk = next; } } override void WorldTick() { LangRefresh(); QueueMaintenance(); if ( !(gameinfo.gametype&GAME_STRIFE) && !mnotify && (level.maptime >= 5) ) { mnotify = true; let ti = ThinkerIterator.Create("SWWMStats",Thinker.STAT_STATIC); SWWMStats s; while ( s = SWWMStats(ti.Next()) ) { if ( !SWWMUtility.IsKnownMap() ) break; if ( s.myplayer != players[consoleplayer] ) continue; int clust = level.cluster; if ( SWWMUtility.IsEviternity() ) { // we have to do some heavy lifting here because episodes don't match clusters if ( level.levelnum <= 5 ) clust = 1; else if ( level.levelnum <= 10 ) clust = 2; else if ( level.levelnum <= 15 ) clust = 3; else if ( level.levelnum <= 20 ) clust = 4; else if ( level.levelnum <= 25 ) clust = 5; else if ( level.levelnum <= 30 ) clust = 6; else if ( level.levelnum <= 31 ) clust = 7; else if ( level.levelnum <= 32 ) clust = 8; } int csiz = s.clustervisit.Size(); if ( (csiz > 0) && (s.clustervisit[csiz-1] != clust) ) Console.Printf(StringTable.Localize("$SWWM_NEWMISSION")); } } OnelinerTick(); FlashTick(); ItemCountTrack(); CombatTrack(); OneHundredPercentCheck(); SimpleTracking(); VanillaBossTick(); } 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; } private static bool ShouldSpawnGold() { int totalneeded = 0; // check "free space" in player inventories for ( int i=0; i 0); } override void WorldThingDied( WorldEvent e ) { if ( e.Thing.default.bISMONSTER && ((e.Thing.default.bBOSS) || (e.Thing.GetSpawnHealth() >= 1000)) && (alreadygold.Find(e.Thing) == alreadygold.Size()) ) { // make sure we can't farm drops from revivable enemies // (or cause some things to spam-spawn gold shells) // (*cough* Touhou Doom *cough*) alreadygold.Push(e.Thing); // weight drop chance based on total count of this monster type // guarantees that maps that get a bit slaughtery won't become easy farms for drops int dropweight = 0; let ti = ThinkerIterator.Create(e.Thing.GetClass()); while ( ti.Next() ) dropweight++; int minchance = max(1,6-(e.Thing.GetSpawnHealth()/1000)); dropweight = max(minchance,dropweight/4); // make sure the gold shell is "worth spawning", too if ( !Random[GoldDrop](0,dropweight) && ShouldSpawnGold() ) { 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); } if ( swwm_partytime ) { let pt = Actor.Spawn("PartyTime",e.Thing.pos); pt.target = e.Thing; } // force insert gib animations on some vanilla Doom monsters int gibhealth = e.Thing.GetGibHealth(); bool gotgibbed = (!e.Thing.bDONTGIB && ((e.Inflictor && e.Inflictor.bEXTREMEDEATH) || (e.DamageSource && e.DamageSource.bEXTREMEDEATH) || (e.DamageType == 'Extreme') || (e.Thing.Health < gibhealth)) && (!e.Inflictor || !e.Inflictor.bNOEXTREMEDEATH) && (!e.DamageSource || !e.DamageSource.bNOEXTREMEDEATH)); if ( !gotgibbed ) return; if ( (e.Thing.GetClass() == "Demon") || (e.Thing.GetClass() == "Spectre") ) ExtraGibDeaths.GibThis(e.Thing,"DemonXDeath"); else if ( e.Thing.GetClass() == "HellKnight" ) ExtraGibDeaths.GibThis(e.Thing,"KnightXDeath"); else if ( e.Thing.GetClass() == "BaronOfHell" ) ExtraGibDeaths.GibThis(e.Thing,"BaronXDeath"); else if ( e.Thing.GetClass() == "Cacodemon" ) ExtraGibDeaths.GibThis(e.Thing,"CacoXDeath"); else if ( e.Thing.GetClass() == "Revenant" ) ExtraGibDeaths.GibThis(e.Thing,"BonerXDeath"); else if ( e.Thing.GetClass() == "Archvile" ) ExtraGibDeaths.GibThis(e.Thing,"VileXDeath"); } // gibbing private void DoGibThing( WorldEvent e ) { // no gib if it was erased or used the kill monsters cheat if ( (e.DamageType == 'Ynykron') || (e.DamageType == 'Massacre') ) return; int gibhealth = e.Thing.GetGibHealth(); bool gotgibbed = (!e.Thing.bDONTGIB && ((e.Inflictor && e.Inflictor.bEXTREMEDEATH) || (e.DamageSource && e.DamageSource.bEXTREMEDEATH) || (e.DamageType == 'Extreme') || (e.Thing.Health < gibhealth)) && (!e.Inflictor || !e.Inflictor.bNOEXTREMEDEATH) && (!e.DamageSource || !e.DamageSource.bNOEXTREMEDEATH)); bool forcegibbed = false; // force gib availability for some vanilla Doom monsters if ( gotgibbed && ((e.Thing.GetClass() == "Demon") || (e.Thing.GetClass() == "Spectre") || (e.Thing.GetClass() == "HellKnight") || (e.Thing.GetClass() == "BaronOfHell") || (e.Thing.GetClass() == "Cacodemon") || (e.Thing.GetClass() == "Revenant") || (e.Thing.GetClass() == "Archvile")) ) forcegibbed = true; if ( !e.Thing.FindState("XDeath",true) && !e.Thing.FindState("Death.Extreme",true) && !forcegibbed ) gotgibbed = false; // only do special handling if they use our blood if ( (e.Thing.GetBloodType(0) != "mkBlood") || e.Thing.bNOBLOOD ) return; CorpseFallTracker.TrackBody(e.Thing); bool b; Actor a; // special handling of some monsters if ( e.Thing.GetClass() == "Cyberdemon" ) { [b,a] = e.Thing.A_SpawnItemEx("mkGibber",flags:SXF_USEBLOODCOLOR); if ( !b ) return; mkGibber(a).gibbed = e.Thing; mkGibber(a).delay = 40; a.A_SetSize(e.Thing.default.radius,e.Thing.default.height); return; } else if ( e.Thing.GetClass() == "SpiderMastermind" ) { [b,a] = e.Thing.A_SpawnItemEx("mkGibber",flags:SXF_USEBLOODCOLOR); if ( !b ) return; mkGibber(a).gibbed = e.Thing; mkGibber(a).delay = 60; a.A_SetSize(e.Thing.default.radius,e.Thing.default.height); } else if ( gotgibbed ) { [b,a] = e.Thing.A_SpawnItemEx("mkGibber",flags:SXF_USEBLOODCOLOR); if ( !b ) return; mkGibber(a).gibbed = e.Thing; a.A_SetSize(e.Thing.default.radius,e.Thing.default.height); } } // damage numbers, combat tracking, etc. private void DoDamageHandling( WorldEvent e ) { bool spawnme = true; if ( swwm_accdamage ) { // find existing damage number for ( SWWMScoreObj d=damnums; d; d=d.next ) { if ( (d.starttic < level.maptime) || (d.acc != e.Thing) ) continue; if ( d.score-e.Damage > d.score ) d.score = int.min; else d.score -= e.Damage; spawnme = false; break; } } if ( spawnme ) SWWMScoreObj.Spawn(-e.Damage,e.Thing.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+e.Thing.Height/2),ST_Damage,e.Thing); // update combat tracker for it if ( !(e.Thing is 'BossBrain') ) { for ( SWWMCombatTracker t=trackers; t; t=t.next ) { if ( t.mytarget != e.Thing ) continue; t.updated = level.maptime+35; break; } } // fall dmg SWWMWhoPushedMe.SetInstigator(e.Thing,e.DamageSource); // stats if ( e.Thing.player ) { tookdamage[e.Thing.PlayerNumber()] = true; let s = SWWMStats.Find(e.Thing.player); if ( s ) // deathmatch telefrag-on-spawn may cause this to be null { s.AddDamageTaken(e.Damage); if ( e.Damage > s.toptaken ) s.toptaken = e.Damage; } } if ( e.DamageSource && e.DamageSource.player ) { let s = SWWMStats.Find(e.DamageSource.player); if ( s ) // deathmatch telefrag-on-spawn may cause this to be null { s.AddDamageDealt(e.Damage); if ( e.Damage > s.topdealt ) s.topdealt = e.Damage; } } } // combat hit chatter private void DoCombatHit( WorldEvent e ) { if ( (e.DamageSource.bISMONSTER || e.DamageSource.player || (e.DamageSource is 'ScriptedMarine')) && (e.Thing == players[consoleplayer].mo) && (e.Thing.Health > 0) ) { if ( !lastcombat || (gametic > lastcombat+40) ) { if ( (e.Thing.IsFriend(e.DamageSource) || SWWMUtility.IsCivilian(e.DamageSource)) ) lastcombat = AddOneliner("friendhit",1,10); else if ( (!lastcombat || (gametic > lastcombat+100)) && !Random[DemoLines](0,e.DamageSource.bBOSS?2:4) && !SWWMHDoomHandler.IsCuteGirl(e.DamageSource) ) // [HDoom] don't shout at the girls lastcombat = AddOneliner("gethit",1,15); } highesttic = gametic; } // friendly fire lines only fire up if we didn't kill them right away (because then the teamkill line should take priority) if ( (e.DamageSource == players[consoleplayer].mo) && (e.Thing.bISMONSTER || e.Thing.player || (e.Thing is 'ScriptedMarine')) && (e.Thing.Health > 0) ) { // 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) || SWWMUtility.IsCivilian(e.Thing)) && !(e.Thing is 'LampMoth') ) { if ( !lastcombat || (gametic > lastcombat+40) ) lastcombat = AddOneliner("hitfriend",1,10); highesttic = gametic; } } } // kill scoring private void DoKillScoring( WorldEvent e ) { // fall damage tracking hack let src = e.DamageSource; if ( (e.DamageType == 'Falling') && !e.DamageSource ) src = SWWMWhoPushedMe.RecallInstigator(e.Thing); if ( (!src || !src.player || (src == e.Thing)) ) return; let s = SWWMStats.Find(src.player); if ( s ) { s.kills++; s.AddWeaponKill(e.Inflictor,e.Thing,e.DamageType); } if ( src == players[consoleplayer].mo ) { highesttic = gametic; if ( !lastcombat || (gametic > lastcombat+40) ) { if ( e.Thing.IsFriend(src) || SWWMUtility.IsCivilian(e.Thing) ) lastcombat = AddOneliner("friendkill",1,5); else if ( (!lastcombat || (gametic > lastcombat+100)) && !Random[DemoLines](0,e.Thing.bBOSS?2:5) && !SWWMHDoomHandler.IsCuteGirl(e.Thing) ) // [HDoom] don't shout at the girls lastcombat = AddOneliner("scorekill",1,15); } } // no credits unless it's a counted kill or marine (that isn't friendly) if ( e.Thing.IsFriend(src) || (!e.Thing.default.bCountKill && !(e.Thing is 'ScriptedMarine')) ) return; int pnum = src.PlayerNumber(); if ( level.maptime < (lastkill[pnum]+5*GameTicRate) ) multilevel[pnum]++; else multilevel[pnum] = 0; if ( s && (multilevel[pnum]+1 > s.mkill) ) s.mkill = multilevel[pnum]+1; lastkill[pnum] = level.maptime; // scoring int score = min(1000,int(ceil(e.Thing.GetSpawnHealth()*.05)*10)); SWWMScoreObj scr = null; if ( src.player == players[consoleplayer] ) scr = SWWMScoreObj.Spawn(score,e.Thing.Vec3Offset(0,0,e.Thing.Height/2)); int ofs = 0; if ( e.DamageType == 'Push' ) { score += 500; if ( scr ) { scr.xscore[ofs] = 0; scr.xstr[ofs] = StringTable.Localize("$SWWM_SHAMEFUL"); scr.xcnt = ++ofs; } } else if ( e.DamageType == 'Buttslam' ) { score += 300; if ( scr ) { scr.xscore[ofs] = 0; scr.xstr[ofs] = StringTable.Localize("$SWWM_BUTTSLAM"); scr.xcnt = ++ofs; } } else if ( e.DamageType == 'Love' ) { score += 600; if ( scr ) { scr.xscore[ofs] = 0; scr.xtcolor[ofs] = Font.FindFontColor('BlushPink'); scr.xstr[ofs] = StringTable.Localize((e.Thing is 'WolfensteinSS')?"$SWWM_LOVED_ALT":"$SWWM_LOVED"); scr.xcnt = ++ofs; } } else if ( e.Inflictor is 'FroggyChair' ) { score += 1440; if ( scr ) { scr.xscore[ofs] = 0; scr.xtcolor[ofs] = Font.CR_GREEN; scr.xstr[ofs] = StringTable.Localize("$SWWM_FROGGED"); scr.xcnt = ++ofs; } } Inventory pb; if ( e.Inflictor && (pb = e.Inflictor.FindInventory('ParriedBuff')) ) { score += 200; if ( pb.special1&1 ) score += 200; if ( scr ) { scr.xscore[ofs] = 0; if ( pb.special1&1 ) scr.xstr[ofs] = StringTable.Localize("$SWWM_PPARRY"); else scr.xstr[ofs] = StringTable.Localize("$SWWM_PARRY"); scr.xcnt = ++ofs; } } if ( (e.Damage >= e.Thing.GetSpawnHealth()*2) || (((e.Thing.Health <= e.Thing.GetGibHealth()) || (src.bEXTREMEDEATH) || (e.Inflictor && e.Inflictor.bEXTREMEDEATH) || (e.DamageType == 'Extreme')) && !src.bNOEXTREMEDEATH && (!e.Inflictor || !e.Inflictor.bNOEXTREMEDEATH)) ) { score *= 2; if ( scr ) { scr.xscore[ofs] = 0; scr.xstr[ofs] = StringTable.Localize("$SWWM_OVERKILL"); scr.xcnt = ++ofs; } } score = int(score*(1.+.5*min(multilevel[pnum],16))); if ( (multilevel[pnum] > 0) && scr ) { if ( scr ) { scr.xscore[ofs] = (multilevel[pnum]>=16)?int.max:(multilevel[pnum]+1); scr.xstr[ofs] = StringTable.Localize("$SWWM_MULTIKILL"); scr.xcnt = ++ofs; } } spreecount[pnum]++; if ( s && (spreecount[pnum] > s.skill) && !tookdamage[pnum] ) s.skill = spreecount[pnum]; if ( !tookdamage[pnum] ) { int spreebonus = 10*(spreecount[pnum]); // taper off after 10x (some people go really far with these, holy fuck) if ( spreecount[pnum] > 10 ) spreebonus = int(spreebonus*((spreecount[pnum]/10.)**.25)); score += 100+spreebonus; if ( (spreecount[pnum] > 0) && scr ) { scr.xscore[ofs] = spreecount[pnum]; scr.xstr[ofs] = StringTable.Localize("$SWWM_SPREEKILL"); scr.xcnt = ++ofs; } } if ( e.Thing.bBOSS ) { score += 2000; if ( scr ) { scr.xscore[ofs] = 0; scr.xstr[ofs] = StringTable.Localize("$SWWM_BOSSKILL"); scr.xcnt = ++ofs; } } SWWMCredits.Give(src.player,score); if ( scr ) scr.score = score; // update final score if ( (level.killed_monsters+1 == level.total_monsters) && !allkills ) { allkills = true; SWWMCredits.Give(src.player,1000); Console.Printf(StringTable.Localize("$SWWM_LASTMONSTER"),src.player.GetUserName(),1000); SWWMScoreObj.Spawn(1000,src.Vec3Offset(0,0,src.Height/2)); } } override void WorldThingDamaged( WorldEvent e ) { if ( e.Damage > 0 ) DoDamageHandling(e); if ( e.DamageSource && (e.DamageSource != e.Thing) ) DoCombatHit(e); if ( (e.Thing.Health > 0) || e.Thing.bKilled || e.Thing.bCorpse ) return; DoGibThing(e); // romero hax if ( (e.Thing is 'BossBrain') && (e.DamageType == 'Telefrag') ) e.DamageSource.DamageMobj(null,null,Actor.TELEFRAG_DAMAGE,'EndLevel'); // voodoo doll telefragging barrel hax (eviternity death exits) if ( (e.Thing is 'ExplosiveBarrel') && (e.DamageType == 'Telefrag') && e.DamageSource.player && (e.DamageSource.player.mo != e.DamageSource) ) e.DamageSource.DamageMobj(null,null,Actor.TELEFRAG_DAMAGE,'EndLevel'); if ( !e.Thing.player && !e.Thing.bIsMonster && !e.Thing.bCountKill && !(e.Thing is 'ScriptedMarine') ) return; DoKillScoring(e); } private void DoKeyTagFix( Actor a ) { if ( a is 'SWWMKey' ) return; // mod's custom keys are fine 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); } // copies the floatbob of overlapping identical items, so it doesn't look weird private void CopyFloatBob( Actor a ) { let bt = BlockThingsIterator.Create(a,16); while ( bt.Next() ) { let t = bt.Thing; if ( !t || (t == a) || !(t is 'Inventory') || !(t.spawnpoint ~== a.spawnpoint) ) continue; a.floatbobphase = t.floatbobphase; a.angle = t.angle; // also copy angle break; } } override void WorldThingDestroyed( WorldEvent e ) { if ( !e.Thing.default.bSHOOTABLE || !e.Thing.default.bMISSILE ) return; // remove from suckables int pos = suckableactors.Find(e.Thing); if ( pos >= suckableactors.Size() ) return; suckableactors.Delete(pos); } override void WorldThingSpawned( WorldEvent e ) { IWantDieSpawn(e); if ( (e.Thing is 'TeleportDest') || (e.Thing is 'BossTarget') ) { let d = Actor.Spawn("SWWMTeleportDest",e.Thing.pos); d.bNOGRAVITY = e.Thing.bNOGRAVITY; } if ( e.Thing is 'Inventory' ) CopyFloatBob(e.Thing); if ( swwm_doomfall && e.Thing.bISMONSTER ) e.Thing.bFALLDAMAGE = true; if ( e.Thing is 'Key' ) { DoKeyTagFix(e.Thing); SWWMInterest.Spawn(thekey:Key(e.Thing)); } if ( indoomvacation == -1 ) indoomvacation = SWWMUtility.InDoomVacation(); if ( e.Thing.GetClass() == 'Pig' ) e.Thing.SetTag("$FN_PIG"); // missing in gzdoom // eviternity stuff else if ( (e.Thing.GetClassName() == "Archangelus") || (e.Thing.GetClassName() == "ArchangelusA") || (e.Thing.GetClassName() == "ArchangelusB") ) e.Thing.SetTag("$FN_ANGEL"); else if ( e.Thing.GetClassName() == "AstralCaco" ) e.Thing.SetTag("$FN_ASTRAL"); else if ( e.Thing.GetClassName() == "Annihilator" ) { e.Thing.SetTag("$FN_ANNIHIL"); // OH BOY, THESE AREN'T CHANGEABLE //e.Thing.Obituary = "$OB_ANNIHIL"; } else if ( e.Thing.GetClassName() == "FormerCaptain" ) { e.Thing.SetTag("$FN_FCAPTAIN"); //e.Thing.Obituary = "$OB_FCAPTAIN"; } else if ( e.Thing.GetClassName() == "NightmareDemon" ) { e.Thing.SetTag("$FN_NDEMON"); //e.Thing.Obituary = "$OB_NDEMON"; } // doom vacation stuff else if ( indoomvacation ) { if ( e.Thing.GetClassName() == "Babe" ) { e.Thing.bSHOOTABLE = false; // no hurt let hp = Actor.Spawn("HeadpatTracker",e.Thing.pos); hp.target = e.Thing; HeadpatTracker(hp).hdoomheightfix = .2; } else if ( e.Thing.GetClassName() == "CommanderKeen" ) { let hp = Actor.Spawn("HeadpatTracker",e.Thing.pos); hp.target = e.Thing; HeadpatTracker(hp).hdoomheightfix = .4; HeadpatTracker(hp).hdoomangfix = 5; } else if ( e.Thing.GetClassName() == "BBChair" ) { e.Thing.bUSESPECIAL = false; let hp = Actor.Spawn("HeadpatTracker",e.Thing.pos); hp.target = e.Thing; HeadpatTracker(hp).hdoomheightfix = .2; HeadpatTracker(hp).hdoomangfix = 15; HeadpatTracker(hp).patstate = e.Thing.MeleeState; } else if ( e.Thing.GetClassName() == "EvilEye" ) { let hp = Actor.Spawn("HeadpatTracker",e.Thing.pos); hp.target = e.Thing; HeadpatTracker(hp).hdoomheightfix = .1; } else if ( e.Thing.GetClassName() == "HeadCandles" ) { let hp = Actor.Spawn("HeadpatTracker",e.Thing.pos); hp.target = e.Thing; HeadpatTracker(hp).hdoomangfix = 20; } else if ( e.Thing.GetClassName() == "HeartColumn" ) { let hp = Actor.Spawn("HeadpatTracker",e.Thing.pos); hp.target = e.Thing; HeadpatTracker(hp).hdoomheightfix = -.3; } else if ( e.Thing.GetClassName() == "Meat2" ) { let hp = Actor.Spawn("HeadpatTracker",e.Thing.pos); hp.target = e.Thing; HeadpatTracker(hp).hdoomheightfix = .6; HeadpatTracker(hp).hdoomangfix = -15; HeadpatTracker(hp).dvacationarghack = true; } else if ( e.Thing.GetClassName() == "Meat3" ) { let hp = Actor.Spawn("HeadpatTracker",e.Thing.pos); hp.target = e.Thing; HeadpatTracker(hp).hdoomheightfix = .6; HeadpatTracker(hp).hdoomangfix = 20; HeadpatTracker(hp).dvacationarghack = true; } else if ( e.Thing.GetClassName() == "LegsBabe" ) { let hp = Actor.Spawn("HeadpatTracker",e.Thing.pos); hp.target = e.Thing; HeadpatTracker(hp).hdoomheightfix = -1.5; HeadpatTracker(hp).hdoomangfix = 20; HeadpatTracker(hp).dvacationarghack = true; } else if ( e.Thing.GetClassName() == "Meat4" ) { let hp = Actor.Spawn("HeadpatTracker",e.Thing.pos); hp.target = e.Thing; HeadpatTracker(hp).hdoomheightfix = .6; HeadpatTracker(hp).hdoomangfix = 15; HeadpatTracker(hp).dvacationarghack = true; } } if ( SWWMUtility.IdentifyingDog(e.Thing) || SWWMUtility.IdentifyingCaco(e.Thing) || SWWMUtility.IdentifyingDrug(e.Thing) || SWWMUtility.IdentifyingDoubleBoi(e.Thing) ) { // you can pet the dog, and you can also pet the caco (and friends) let hp = Actor.Spawn("HeadpatTracker",e.Thing.pos); hp.target = e.Thing; } SWWMCombatTracker trk; if ( !swwm_notrack && (e.Thing.bSHOOTABLE || e.Thing.bISMONSTER) && !(e.Thing is 'LampMoth') && !(e.Thing is 'CompanionLamp') ) trk = SWWMCombatTracker.Spawn(e.Thing); if ( !(e.Thing is 'LampMoth') && (e.Thing.bSHOOTABLE || e.Thing.bISMONSTER || e.Thing.bCORPSE || (e.Thing is 'Inventory') || (e.Thing is 'CompanionLamp')) ) { if ( (swwm_shadows == 2) || ((swwm_shadows == 1) && ((e.Thing is 'Demolitionist') || (e.Thing.SpawnState.sprite == e.Thing.GetSpriteIndex('XZW1')))) ) SWWMShadow.Track(e.Thing); } // Ynykron vortex optimization (faster than a thinker iterator) if ( e.Thing.bSHOOTABLE || e.Thing.bMISSILE ) SuckableActors.Push(e.Thing); // vanilla blood color changes if ( (e.Thing.GetClass() == "BaronOfHell") || (e.Thing.GetClass() == "HellKnight") || (e.Thing.GetClass() == "Bishop") || (e.Thing.GetClass() == "Korax") ) { let gb = Actor.Spawn("GreenBloodReference"); e.Thing.CopyBloodColor(gb); gb.Destroy(); } else if ( e.Thing.GetClass() == "Cacodemon" ) { let bb = Actor.Spawn("BlueBloodReference"); e.Thing.CopyBloodColor(bb); bb.Destroy(); } else if ( (e.Thing.GetClass() == "Wizard") || (e.Thing.GetClass() == "Heresiarch") || (e.Thing.GetClass() == "Sorcerer2") ) { let pb = Actor.Spawn("PurpleBloodReference"); e.Thing.CopyBloodColor(pb); pb.Destroy(); } else if ( e.Thing.GetClass() == "LostSoul" ) e.Thing.bNOBLOOD = true; VanillaBossSpawn(e,trk); // inflation check if ( trk ) { trk.maxhealth = trk.lasthealth = e.Thing.Health; trk.intp.Reset(trk.lasthealth); } } override void PostUiTick() { OnelinerUITick(); FlashUITick(); VanillaBossUITick(); } override void WorldLinePreActivated( WorldEvent e ) { // oneliner on locked doors if ( !e.Thing ) return; int locknum = SWWMUtility.GetLineLock(e.ActivatedLine); if ( (locknum < 1) || (locknum > 255) ) return; if ( e.Thing.CheckLocalView() && !e.Thing.CheckKeys(locknum,false,true) ) { if ( !lastlock || (gametic > lastlock+20) ) { if ( SWWMUtility.IsValidLockNum(locknum) ) lastlock = AddOneliner("locked",2); else lastlock = AddOneliner("jammed",2); } } } override void WorldLineActivated( WorldEvent e ) { if ( !(e.ActivationType&SPAC_Use) ) return; if ( !e.Thing || !e.Thing.player ) return; let w = SWWMWeapon(e.Thing.player.ReadyWeapon); if ( !w || !w.wallponch ) return; let s = SWWMStats.Find(e.Thing.player); if ( s ) s.wponch++; } // stuff for hud override void RenderUnderlay( RenderEvent e ) { // armor/health flashes FlashRender(e); if ( slotstrictwarn && (gametic < slotstrictwarn) ) { String str = StringTable.Localize("$SWWM_SETSLOTSTRICT"); double t = (slotstrictwarn-(gametic+e.FracTic))/20.; double alph = clamp(t,0.,1.); BrokenLines l = newsmallfont.BreakLines(str,300); double yy = (200-l.Count()*newsmallfont.GetHeight())/2; for ( int i=0; i