diff --git a/TODO.md b/TODO.md index 1d57e4219..48bfd6d88 100644 --- a/TODO.md +++ b/TODO.md @@ -5,7 +5,6 @@ Not so fundamental things during first beta: - Extra Demolitionist animations (swimming, additional gestures) - Dual wielding Explodium Gun - Quick grenade function (Explodium Mag) - - Allow loading partial mags from spare bullets - Parrying for hitscan weapons (currently parrying only handles vanilla LineAttack) - Fun options - Omnibusting (all weapons can bust walls) @@ -13,7 +12,7 @@ Not so fundamental things during first beta: - Confetti gibs - Keen replacement (need ideas) - Achievements - - Extra Demolitionist Menu tabs (radio, pong minigame) + - Extra Demolitionist Menu tabs (radio, minigames) - Collectables Extra things for later: diff --git a/language.def_menu b/language.def_menu index daa53783d..362b4b144 100644 --- a/language.def_menu +++ b/language.def_menu @@ -8,7 +8,7 @@ SWWM_ZOOMFIRE = "Tertiary Fire / Zoom"; SWWM_MELEE = "Melee Attack"; SWWM_DASH = "Dash"; SWWM_ITEMSENSE = "Item Sense"; -SWWM_EXTRAFIRE = "Quaternary Fire"; +SWWM_EXTRAFIRE = "Quick Grenade"; SWWM_GESTURE1 = "Wave"; SWWM_GESTURE2 = "Thumbs Up"; SWWM_GESTURE3 = "Victory"; diff --git a/language.es_menu b/language.es_menu index 67def046c..bd227e015 100644 --- a/language.es_menu +++ b/language.es_menu @@ -8,7 +8,7 @@ SWWM_ZOOMFIRE = "Fuego Terciario / Zoom"; SWWM_MELEE = "Ataque a Melé"; SWWM_DASH = "Esprintar"; SWWM_ITEMSENSE = "Sensor de Items"; -SWWM_EXTRAFIRE = "Fuego Cuaternario"; +SWWM_EXTRAFIRE = "Granada Rápida"; SWWM_GESTURE1 = "Saludar"; SWWM_GESTURE2 = "Pulgar Arriba"; SWWM_GESTURE3 = "Victoria"; diff --git a/language.version b/language.version index 1d6e4f16f..54848c09d 100644 --- a/language.version +++ b/language.version @@ -1,2 +1,2 @@ [default] -SWWM_MODVER="\chSWWM \cwGZ\c- r503 (Sat 22 Aug 15:18:03 CEST 2020)"; +SWWM_MODVER="\chSWWM \cwGZ\c- r505 (Sun 23 Aug 16:32:44 CEST 2020)"; diff --git a/zscript.txt b/zscript.txt index 06ad373c3..3c9084769 100644 --- a/zscript.txt +++ b/zscript.txt @@ -14,13 +14,18 @@ version "4.4" #include "zscript/swwm_libeye/viewport.txt" #include "zscript/swwm_coordutil.zsc" #include "zscript/swwm_quaternion.zsc" -#include "zscript/swwm_crimesdlg.zsc" // base code #include "zscript/swwm_common.zsc" +#include "zscript/swwm_utility.zsc" +#include "zscript/swwm_handler.zsc" +#include "zscript/swwm_shame.zsc" +#include "zscript/swwm_thinkers.zsc" #include "zscript/swwm_player.zsc" #include "zscript/swwm_inventory.zsc" #include "zscript/swwm_hud.zsc" +#include "zscript/swwm_hudextra.zsc" #include "zscript/swwm_menu.zsc" +#include "zscript/swwm_crimesdlg.zsc" #include "zscript/swwm_options.zsc" #include "zscript/swwm_title.zsc" #include "zscript/swwm_inter.zsc" diff --git a/zscript/swwm_ammo.zsc b/zscript/swwm_ammo.zsc index 97f0aa705..e4b8c8d2e 100644 --- a/zscript/swwm_ammo.zsc +++ b/zscript/swwm_ammo.zsc @@ -230,6 +230,11 @@ Class MagAmmo : Inventory abstract return; } if ( countdown-- > 0 ) return; + MagFill(); + } + + bool MagFill() + { bool given = false; while ( (pamo.Amount < pamo.MaxAmount) && (Amount >= ClipSize) ) { @@ -240,6 +245,7 @@ Class MagAmmo : Inventory abstract pamo.PrintPickupMessage(true,pamo.PickupMessage()); } if ( given ) pamo.PlayPickupSound(Owner); + return given; } override inventory CreateTossable( int amt ) @@ -701,6 +707,28 @@ Class EvisceratorSixPack : EvisceratorShell } } +Class EvisceratorTrioSpawn : Actor +{ + override void PostBeginPlay() + { + if ( bCOUNTSECRET ) level.total_secrets--; + for ( int i=0; i<3; i++ ) + { + let a = Spawn("EvisceratorShell",Vec3Angle(12,i*120)); + a.special = special; + a.angle = i*120; + a.FloatBobPhase = FloatBobPhase; + for ( int j=0; j<5; j++ ) a.args[j] = args[j]; + if ( bCOUNTSECRET ) + { + a.bCOUNTSECRET = true; + level.total_secrets++; + } + } + Destroy(); + } +} + // ============================================================================ // Hellblazer ammo // ============================================================================ @@ -743,6 +771,27 @@ Class HellblazerMissileMag : HellblazerMissiles Inventory.Amount 6; } } +Class HellblazerMissileTrioSpawn : Actor +{ + override void PostBeginPlay() + { + if ( bCOUNTSECRET ) level.total_secrets--; + for ( int i=0; i<3; i++ ) + { + let a = Spawn("HellblazerMissiles",Vec3Angle(8,i*120)); + a.special = special; + a.angle = i*120; + a.FloatBobPhase = FloatBobPhase; + for ( int j=0; j<5; j++ ) a.args[j] = args[j]; + if ( bCOUNTSECRET ) + { + a.bCOUNTSECRET = true; + level.total_secrets++; + } + } + Destroy(); + } +} Class HellblazerCrackshots : Ammo { @@ -876,7 +925,7 @@ Class SparkUnit : Ammo Stamina 50000; Inventory.Icon "graphics/HUD/Icons/A_Sparkster.png"; Inventory.Amount 1; - Inventory.MaxAmount 4; + Inventory.MaxAmount 8; Ammo.BackpackAmount 1; Ammo.DropAmount 1; +FLOATBOB; @@ -991,6 +1040,51 @@ Class SilverBullets2 : MagAmmo } } +Class SilverBulletsBundleSpawn : Actor +{ + override void PostBeginPlay() + { + if ( bCOUNTSECRET ) level.total_secrets--; + int bnd = Random[Bundle](2,3); + for ( int i=0; i<3; i++ ) + { + let a = Spawn("SilverBullets",Vec3Angle(6,i*(360/bnd))); + a.special = special; + a.angle = i*(360/bnd); + a.FloatBobPhase = FloatBobPhase; + for ( int j=0; j<5; j++ ) a.args[j] = args[j]; + if ( bCOUNTSECRET ) + { + a.bCOUNTSECRET = true; + level.total_secrets++; + } + } + Destroy(); + } +} +Class SilverBullets2BundleSpawn : Actor +{ + override void PostBeginPlay() + { + if ( bCOUNTSECRET ) level.total_secrets--; + int bnd = Random[Bundle](2,3); + for ( int i=0; i double.epsilon); - } - - // box intersection check, for collision detection - static bool BoxIntersect( Actor a, Actor b, Vector3 ofs = (0,0,0), int pad = 0 ) - { - Vector3 diff = level.Vec3Diff(level.Vec3Offset(a.pos,ofs),b.pos); - if ( (abs(diff.x) > (a.radius+b.radius+pad)) || (abs(diff.y) > (a.radius+b.radius+pad)) ) return false; - if ( (diff.z > a.height+pad) || (diff.z < -(b.height+pad)) ) return false; - return true; - } - - // extruded box intersection check, useful when checking things that might be hit along a path - static bool ExtrudeIntersect( Actor a, Actor b, Vector3 range, int steps, int pad = 0 ) - { - if ( steps <= 0 ) return BoxIntersect(a,b,pad:pad); - double step = 1./steps; - for ( double i=step; i<=1.; i+=step ) - { - if ( BoxIntersect(a,b,range*i,pad) ) - return true; - } - return false; - } - - // sphere intersection check, useful for proximity detection - static bool SphereIntersect( Actor a, Vector3 p, double radius ) - { - Vector3 ap = p+level.Vec3Diff(p,a.pos); // portal-relative actor position - Vector3 amin = ap+(-a.radius,-a.radius,0), - amax = ap+(a.radius,a.radius,a.height); - double distsq = 0.; - if ( p.x < amin.x ) distsq += (amin.x-p.x)**2; - if ( p.x > amax.x ) distsq += (p.x-amax.x)**2; - if ( p.y < amin.y ) distsq += (amin.y-p.y)**2; - if ( p.y > amax.y ) distsq += (p.y-amax.y)**2; - if ( p.z < amin.z ) distsq += (amin.z-p.z)**2; - if ( p.z > amax.z ) distsq += (p.z-amax.z)**2; - return (distsq <= (radius**2)); - } - - // THANKS FOR NOT GIVING US ANY OTHER WAY TO CHECK IF A LOCK NUMBER IS VALID - static bool IsValidLockNum( int l ) - { - if ( (l < 1) || (l > 255) ) return true; - Array valid; - valid.Clear(); - for ( int i=0; i lines; - lines.Clear(); - data.Split(lines,"\n"); - 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.Push(spl[1].ToInt()); - } - } - } - for ( int i=0; i= 10) && (l.special <= 13)) - || (!part && (l.special >= 20) && (l.special <= 25)) - || (!part && (l.special == 28)) - || ((l.special >= 29) && (l.special <= 30)) - || (!part && (l.special >= 35) && (l.special <= 37)) - || (part && (l.special >= 40) && (l.special <= 45)) - || (!part && (l.special == 46)) - || (part && (l.special == 47)) - || (!part && (l.special >= 60) && (l.special <= 68)) - || (part && (l.special == 69)) - || ((l.special >= 94) && (l.special <= 96)) - || (part && (l.special == 97)) - || (!part && (l.special == 99)) - || (part && (l.special == 104)) - || (part && (l.special >= 105) && (l.special <= 106)) - || (part && (l.special >= 168) && (l.special <= 169)) - || (!part && (l.special == 172)) - || (part && (l.special >= 192) && (l.special <= 199)) - || (!part && (l.special == 200)) - || (part && (l.special >= 201) && (l.special <= 202)) - || (!part && (l.special == 203)) - || (part && (l.special == 205)) - || (!part && (l.special >= 206) && (l.special <= 207)) - || (!part && (l.special == 228)) - || (!part && (l.special >= 230) && (l.special <= 231)) - || (!part && (l.special >= 238) && (l.special <= 242)) - || ((l.special >= 245) && (l.special <= 247)) - || (part && (l.special == 249)) - || (!part && (l.special >= 250) && (l.special <= 251)) - || (part && (l.special >= 251) && (l.special <= 255)) - || (!part && (l.special >= 256) && (l.special <= 261)) - || (part && (l.special >= 262) && (l.special <= 269)) - || (!part && (l.special == 275)) - || (part && (l.special == 276)) - || (!part && (l.special == 279)) - || (part && (l.special == 280)) ) - { - let si = level.CreateSectorTagIterator(l.Args[0],l); - int idx; - while ( (idx = si.Next()) != -1 ) - if ( level.Sectors[idx] == s ) - return true; - } - } - let ti = ThinkerIterator.Create("Actor"); - Actor a; - while ( a = Actor(ti.Next()) ) - { - if ( !a.special || !a.Args[0] ) continue; - if ( (part && (a.special >= 10) && (a.special <= 13)) - || (!part && (a.special >= 20) && (a.special <= 25)) - || (!part && (a.special == 28)) - || ((a.special >= 29) && (a.special <= 30)) - || (!part && (a.special >= 35) && (a.special <= 37)) - || (part && (a.special >= 40) && (a.special <= 45)) - || (!part && (a.special == 46)) - || (part && (a.special == 47)) - || (!part && (a.special >= 60) && (a.special <= 68)) - || (part && (a.special == 69)) - || ((a.special >= 94) && (a.special <= 96)) - || (part && (a.special == 97)) - || (!part && (a.special == 99)) - || (part && (a.special == 104)) - || (part && (a.special >= 105) && (a.special <= 106)) - || (part && (a.special >= 168) && (a.special <= 169)) - || (!part && (a.special == 172)) - || (part && (a.special >= 192) && (a.special <= 199)) - || (!part && (a.special == 200)) - || (part && (a.special >= 201) && (a.special <= 202)) - || (!part && (a.special == 203)) - || (part && (a.special == 205)) - || (!part && (a.special >= 206) && (a.special <= 207)) - || (!part && (a.special == 228)) - || (!part && (a.special >= 230) && (a.special <= 231)) - || (!part && (a.special >= 238) && (a.special <= 242)) - || ((a.special >= 245) && (a.special <= 247)) - || (part && (a.special == 249)) - || (!part && (a.special >= 250) && (a.special <= 251)) - || (part && (a.special >= 251) && (a.special <= 255)) - || (!part && (a.special >= 256) && (a.special <= 261)) - || (part && (a.special >= 262) && (a.special <= 269)) - || (!part && (a.special == 275)) - || (part && (a.special == 276)) - || (!part && (a.special == 279)) - || (part && (a.special == 280)) ) - { - let si = level.CreateSectorTagIterator(a.Args[0]); - int idx; - while ( (idx = si.Next()) != -1 ) - if ( level.Sectors[idx] == s ) - return true; - } - } - return false; - } - - // because GetTag() returns the localized string, we need to do things the hard way - static clearscope String GetFunTag( Actor a, String defstr = "" ) - { - if ( a is 'SWWMMonster' ) return SWWMMonster(a).GetFunTag(defstr); - int ntags = 1; - String basetag = ""; - switch ( a.GetClassName() ) - { - // Doom - case 'ZombieMan': - case 'StealthZombieMan': - basetag = "ZOMBIE"; - break; - case 'ShotgunGuy': - case 'StealthShotgunGuy': - basetag = "SHOTGUN"; - break; - case 'ChaingunGuy': - case 'StealthChaingunGuy': - basetag = "HEAVY"; - break; - case 'DoomImp': - case 'StealthDoomImp': - basetag = "IMP"; - break; - case 'Demon': - case 'StealthDemon': - basetag = "DEMON"; - break; - case 'Spectre': - basetag = "SPECTRE"; - break; - case 'LostSoul': - basetag = "LOST"; - break; - case 'Cacodemon': - case 'StealthCacodemon': - basetag = "CACO"; - break; - case 'HellKnight': - case 'StealthHellKnight': - basetag = "HELL"; - break; - case 'BaronOfHell': - case 'StealthBaron': - basetag = "BARON"; - break; - case 'Arachnotron': - case 'StealthArachnotron': - basetag = "ARACH"; - break; - case 'PainElemental': - basetag = "PAIN"; - break; - case 'Revenant': - case 'StealthRevenant': - basetag = "REVEN"; - break; - case 'Fatso': - case 'StealthFatso': - basetag = "MANCU"; - break; - case 'Archvile': - case 'StealthArchvile': - basetag = "ARCH"; - break; - case 'SpiderMastermind': - basetag = "SPIDER"; - break; - case 'Cyberdemon': - basetag = "CYBER"; - break; - case 'BossBrain': - basetag = "BOSSBRAIN"; - break; - case 'WolfensteinSS': - basetag = "WOLFSS"; - break; - case 'CommanderKeen': - basetag = "KEEN"; - break; - case 'MBFHelperDog': - basetag = "DOG"; - break; - // Heretic - case 'Chicken': - basetag = "CHICKEN"; - break; - case 'Beast': - basetag = "BEAST"; - break; - case 'Clink': - basetag = "CLINK"; - break; - case 'Sorcerer1': - case 'Sorcerer2': - basetag = "DSPARIL"; - break; - case 'HereticImp': - case 'HereticImpLeader': - basetag = "HERETICIMP"; - break; - case 'Ironlich': - basetag = "IRONLICH"; - break; - case 'Knight': - case 'KnightGhost': - basetag = "BONEKNIGHT"; - break; - case 'Minotaur': - case 'MinotaurFriend': - basetag = "MINOTAUR"; - break; - case 'Mummy': - case 'MummyGhost': - basetag = "MUMMY"; - break; - case 'MummyLeader': - case 'MummyLeaderGhost': - basetag = "MUMMYLEADER"; - break; - case 'Snake': - basetag = "SNAKE"; - break; - case 'Wizard': - basetag = "WIZARD"; - break; - // Hexen - case 'FireDemon': - basetag = "FIREDEMON"; - break; - case 'Demon1': - case 'Demon1Mash': - case 'Demon2': - case 'Demon2Mash': - basetag = "DEMON1"; - break; - case 'Ettin': - case 'EttinMash': - basetag = "ETTIN"; - break; - case 'Centaur': - case 'CentaurMash': - basetag = "CENTAUR"; - break; - case 'CentaurLeader': - basetag = "SLAUGHTAUR"; - break; - case 'Bishop': - basetag = "BISHOP"; - break; - case 'IceGuy': - basetag = "ICEGUY"; - break; - case 'Serpent': - case 'SerpentLeader': - basetag = "SERPENT"; - break; - case 'Wraith': - case 'WraithBuried': - basetag = "WRAITH"; - break; - case 'Dragon': - basetag = "DRAGON"; - break; - case 'Korax': - basetag = "KORAX"; - break; - case 'FighterBoss': - basetag = "FBOSS"; - break; - case 'MageBoss': - basetag = "MBOSS"; - break; - case 'ClericBoss': - basetag = "CBOSS"; - break; - case 'Heresiarch': - basetag = "HERESIARCH"; - break; - } - if ( basetag == "" ) return a.GetTag(defstr); - String funtag = "FN_"..basetag.."_FUN"; - String lfuntag = StringTable.Localize(funtag,false); - if ( lfuntag != funtag ) return lfuntag; - String nfuntag = "FN_"..basetag.."_FUNN"; - String lnfuntag = StringTable.Localize(nfuntag,false); - if ( lnfuntag == nfuntag ) return a.GetTag(defstr); - ntags = lnfuntag.ToInt(); - return StringTable.Localize(String.Format("$FN_%s_FUN%d",basetag,Random[FunTags](1,ntags))); - } -} - // Future planning, will be filled out with AI stuff and whatnot someday Class SWWMMonster : Actor { @@ -587,1060 +24,6 @@ Class SWWMMonster : Actor } } -// 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; -} - -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; - double grounddist, airdist, swimdist, fuelusage, topspeed; - Array wstats; - Array mstats; - Array lstats; - Array > alreadygot; - int favweapon; - - bool GotWeapon( Class which ) - { - for ( int i=0; i 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; - 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; - lstats.Push(ls); - } - - void AddWeaponKill( Actor inflictor, Actor victim ) - { - if ( victim ) - { - bool found = false; - for ( int i=0; i which = myplayer.ReadyWeapon?myplayer.ReadyWeapon.GetClass():null; - if ( inflictor is 'Weapon' ) which = Weapon(inflictor).GetClass(); - // 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') ) which = 'Ynykron'; - else if ( (inflictor is 'Demolitionist') || (inflictor is 'DemolitionistShockwave') || (inflictor is 'DemolitionistRadiusShockwave') ) which = 'SWWMWeapon'; // hack to assume Demolitionist as weapon - 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; -} - -// so apparently on restart after dying, static thinkers don't get reloaded until AFTER the player has spawned -// and the player calls GiveDefaultInventory even though it's all later discarded and they're given the saved inventory -// all I can ask is: WHY -Class SWWMExcuseMeWhatTheFuckHandler : StaticEventHandler -{ - bool reborn_hackfix; - int clear_hackfix; - - override void WorldTick() - { - if ( gamestate != GS_LEVEL ) return; - if ( clear_hackfix > 0 ) - { - clear_hackfix--; - if ( clear_hackfix <= 0 ) - reborn_hackfix = false; - } - } - - static bool WhyTheFuckIsThisAThing() - { - let hnd = SWWMExcuseMeWhatTheFuckHandler(StaticEventHandler.Find("SWWMExcuseMeWhatTheFuckHandler")); - if ( !hnd ) return false; - return hnd.reborn_hackfix; - } - - static void ThisIsSomeSeriousBullshit() - { - let hnd = SWWMExcuseMeWhatTheFuckHandler(StaticEventHandler.Find("SWWMExcuseMeWhatTheFuckHandler")); - if ( !hnd ) return; - hnd.reborn_hackfix = true; - hnd.clear_hackfix = 2; - } -} - -Class SWWMLoreLibrary : Thinker -{ - PlayerInfo myplayer; - Array ent; - int lastaddtic; - - bool DirectAdd( 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 ~== "AmmoFabricator" ) return true; // not yet introduced - } - if ( !(gameinfo.gametype&GAME_Strife) ) - { - if ( ref ~== "TheOrder" ) return true; - if ( ref ~== "TheFront" ) 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); - 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_GHOULHUNT" ) - text = "SWWM_LORETXT_GHOULHUNT2"; // met anthon anderken during the - 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 - } - 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 - } - // check that it's valid - if ( StringTable.Localize(tag,false) == tag ) return false; - if ( StringTable.Localize(tab,false) == tab ) - { - Console.Printf("Entry \"%s\" defines no tab.",ref); - return false; - } - if ( StringTable.Localize(text,false) == text ) - { - Console.Printf("Entry \"%s\" defines no text.",ref); - return false; - } - // 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; - // sorted add - String loca = StringTable.Localize(e.tag), locb; - int cpa, cpb; - for ( int i=0; i= 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 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; - int tcnt; - double height; - transient CVar funtags; - - void UpdateTag() - { - if ( !funtags ) funtags = CVar.GetCVar('swwm_funtags',players[consoleplayer]); - if ( mytarget && (mytarget.player || mytarget.bISMONSTER || (mytarget is 'BossBrain')) ) - { - String realtag = funtags.GetBool()?SWWMUtility.GetFunTag(mytarget,"AWESOME IT'S PENIS"):mytarget.GetTag("AWESOME IT'S PENIS"); - if ( realtag == "AWESOME IT'S PENIS" ) - { - 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 = t.mytarget.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; - if ( hnd.trackers ) hnd.trackers.prev = t; - hnd.trackers = t; - hnd.trackers_cnt++; - return t; - } - - override void OnDestroy() - { - let hnd = SWWMHandler(EventHandler.Find("SWWMHandler")); - if ( hnd ) - { - hnd.trackers_cnt--; - if ( !prev ) - { - hnd.trackers = next; - if ( next ) next.prev = null; - } - else - { - prev.next = next; - if ( next ) next.prev = prev; - } - } - Super.OnDestroy(); - } - - override void Tick() - { - if ( swwm_notrack ) - { - Destroy(); - return; - } - // don't update - if ( mytarget && mytarget.bDORMANT ) - return; - // update - if ( mytarget && ((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)); - } - if ( !mytarget || (mytarget.Health <= 0) ) - { - // we're done - if ( updated > level.maptime ) updated = level.maptime; - lasthealth = 0; - intp.Update(lasthealth); - if ( level.maptime > updated+35 ) Destroy(); - return; - } - // only update height while alive - height = mytarget.height; - tcnt++; - if ( (tcnt == 1) && !mytarget.player ) - { - // post-spawn health inflation check - if ( lasthealth > maxhealth ) maxhealth = 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; - } - } - lasthealth = mytarget.Health; - intp.Update(lasthealth); - if ( (mytarget.bISMONSTER || mytarget.player) && !mytarget.bINVISIBLE ) - { - bool straifu = false; - if ( (gameinfo.gametype&GAME_Strife) && (!mytarget.bINCOMBAT && !mytarget.bJUSTATTACKED) || (mytarget is 'Beggar') || (mytarget is 'Peasant') ) - straifu = true; - // enemies within 2000mu that have us as target - if ( !straifu && mytarget.target && (mytarget.target.Health > 0) && (mytarget.target.player == players[consoleplayer]) && (mytarget.Vec3To(mytarget.target).length() < 2000) && mytarget.CheckSight(mytarget.target) ) updated = level.maptime+70; - // players (but not voodoo dolls), always visible in sp/coop - if ( !deathmatch && mytarget.player && (mytarget.player.mo == mytarget) ) updated = level.maptime+35; - // any visible enemies within 600mu - if ( (mytarget.Vec3To(players[consoleplayer].mo).length() < 600) && players[consoleplayer].mo.CheckSight(mytarget) ) updated = level.maptime; - } - else if ( mytarget is 'BossBrain' ) - { - // teh romero, only if visible - if ( (mytarget.Vec3To(players[consoleplayer].mo).length() < 600) && players[consoleplayer].mo.CheckSight(mytarget) ) updated = level.maptime; - } - } -} - -// Press F to Pay Respects -Class PayRespects : HUDMessageBase -{ - Vector2 basepos; - int lifespan, initialspan, starttic; - transient Font TewiFont; - double scale; - Vector2 hs, ss; - int seed, seed2; - - static PayRespects PressF() - { - let f = new("PayRespects"); - f.basepos = (FRandom[FInTheChat](0.,1.),FRandom[FInTheChat](1.02,1.05)); - f.scale = FRandom[FInTheChat](.5,2.); - f.lifespan = f.initialspan = Random[FInTheChat](20,80); - f.starttic = level.maptime; - f.seed = Random[FInTheChat](); - f.seed2 = Random[FInTheChat](); - f.ScreenSizeChanged(); - return f; - } - - override bool Tick() - { - lifespan--; - return (lifespan<=0); - } - - override void ScreenSizeChanged() - { - hs = StatusBar.GetHUDScale()*scale; - ss = (Screen.GetWidth()/hs.x,Screen.GetHeight()/hs.y); - } - - override void Draw( int bottom, int visibility ) - { - Vector2 realpos = (basepos.x*ss.x,basepos.y*ss.y); - if ( !TewiFont ) TewiFont = Font.GetFont('TewiShaded'); - Vector2 fo = (TewiFont.StringWidth("F")/2.,-TewiFont.GetHeight()); - // F rise up - int initspd = (128-seed); - if ( (initspd >= 0) && (initspd < 32) ) initspd = 32; - if ( (initspd < 0) && (initspd > -32) ) initspd = -32; - int boostup = 32+(seed2/4); - double fractic = SWWMStatusBar(statusbar)?SWWMStatusBar(statusbar).fractic:0; - fo.x += (.15*initspd)*((initialspan-(lifespan-fractic))**.6); - fo.y += ((initialspan-(lifespan-fractic))**1.6)-boostup*sin((90./initialspan)*(level.maptime+fractic-starttic)); - double alph = clamp((lifespan+fractic)/double(initialspan),0.,1.); - Screen.DrawText(TewiFont,Font.CR_GREEN,realpos.x-fo.x,realpos.y-fo.y,"F",DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_Alpha,alph); - } -} - -// One-liners -Class SWWMOneLiner : HUDMessageBase -{ - String whichline; - int lifespan, curtime; - transient Font TewiFont, MPlusFont; - transient CVar safezone, lang, hscale; - - static SWWMOneLiner Make( String whichline, int lifespan ) - { - let l = new("SWWMOneLiner"); - l.whichline = whichline; - l.curtime = l.lifespan = lifespan; - return l; - } - - override bool Tick() - { - if ( players[consoleplayer].Health <= 0 ) curtime = int.min; - curtime--; - return (curtime<-20); - } - - override void Draw( int bottom, int visibility ) - { - if ( !safezone ) safezone = CVar.GetCVar('swwm_hudmargin',players[consoleplayer]); - if ( !lang ) lang = CVar.GetCVar('language',players[consoleplayer]); - if ( !TewiFont ) TewiFont = Font.GetFont('TewiShaded'); - if ( !MPlusFont ) MPlusFont = Font.GetFont('MPlusShaded'); - if ( !hscale ) hscale = CVar.GetCVar('swwm_hudscale',players[consoleplayer]); - int margin = safezone.GetInt(); - Vector2 hs; - if ( hscale.GetInt() <= 0 ) hs = StatusBar.GetHUDScale(); - else - { - hs.x = hscale.GetInt(); - if ( Screen.GetWidth()/hs.x < 640. ) hs.x = max(1,floor(Screen.GetWidth()/640.)); - hs.y = hs.x; - } - Vector2 ss = (Screen.GetWidth()/hs.x,Screen.GetHeight()/hs.y); - String loc = StringTable.Localize(whichline); - if ( loc.Length() <= 0 ) return; // don't draw empty strings - String locs = StringTable.Localize("$SWWM_LQUOTE")..loc..StringTable.Localize("$SWWM_RQUOTE"); - Font fnt = TewiFont; - if ( lang.GetString() ~== "jp" ) fnt = MPlusFont; - // split so it can fit - BrokenLines l = fnt.BreakLines(locs,int(ss.x*.5)); - int maxlen = 0; - for ( int i=0; i maxlen ) maxlen = len; - } - int h = fnt.GetHeight(); - int fh = h*l.Count(); - double alph = clamp((curtime/20.)+1.,0.,1.); - alph *= clamp((lifespan-curtime)/10.,0.,1.); - Screen.Dim("Black",alph*.8,int((Screen.GetWidth()-(maxlen+12)*hs.x)/2.),int(bottom-(margin+2+fh)*hs.y),int((maxlen+12)*hs.x),int((fh+4)*hs.y)); - int yy = margin+fh; - for ( int i=0; i 0 ) vel.z *= 0.98; - if ( CurState == ResolveState("Death") ) - { - deadtimer++; - if ( deadtimer > 300 ) A_FadeOut(0.05); - return; - } - } - States - { - Spawn: - XZW1 # 1 - { - angle += anglevel; - pitch += pitchvel; - roll += rollvel; - return A_JumpIf(pos.z<=floorz,"Death"); - } - Loop; - Death: - XZW1 # -1 - { - A_Stop(); - pitch = roll = 0; - } - Stop; - Dummy: - XZW1 ABCDEFGHIJ -1; - Stop; - } -} - Class SWWMNothing : Actor { States @@ -2370,7 +684,6 @@ Class SWWMBulletTrail : LineTracer } } - // Elastic recoil from DT Enum ESwingMode { @@ -2432,1887 +745,6 @@ Class Swinger : Thinker return; } } -} - -// Screen flashes from DT -Class GenericFlash : HUDMessageBase -{ - Color col; - int duration; - double alpha; - Actor cam; - transient CVar str; - GenericFlash Setup( Actor camera, Color c, int d ) - { - alpha = 1.0; - col = c; - duration = d; - cam = camera; - return self; - } - override bool Tick() - { - if ( duration > 0 ) alpha -= 1./duration; - return (alpha<=0)||(!cam); - } - override void Draw( int bottom, int visibility ) - { - if ( automapactive || (visibility != BaseStatusBar.HUDMSGLayer_UnderHUD) ) return; - if ( cam && (players[consoleplayer].camera != cam) ) return; - if ( !str ) str = CVar.GetCVar('swwm_flashstrength',players[consoleplayer]); - Screen.Dim(col,(col.a/255.)*alpha*str.GetFloat(),0,0,Screen.GetWidth(),Screen.GetHeight()); - } -} - -Class QueuedFlash -{ - Color c; - int duration; - int tic; - Actor cam; -} - -Class LastLine -{ - String type; - int lineno; -} - -// Korax instakill handler -Class UglyBoyGetsFuckedUp : Thinker -{ - bool wedone; - - override void Tick() - { - if ( wedone ) return; - if ( level.killed_monsters < level.total_monsters ) - { - // stop portal door - int sidx = level.CreateSectorTagIterator(145).Next(); - if ( sidx == -1 ) return; - Sector door = level.Sectors[sidx]; - let ti = ThinkerIterator.Create("SectorEffect"); - SectorEffect se; - while ( se = SectorEffect(ti.Next()) ) - { - if ( se.GetSector() != door ) continue; - se.Destroy(); - door.StopSoundSequence(CHAN_VOICE); - } - return; - } - wedone = true; - level.ExecuteSpecial(Door_Open,null,null,false,145,8); - Destroy(); - } -} - -// Gotta force it someway -Class ForcedFallerDamager : Thinker -{ - Actor mybody, instigator; - double lastvelz; - bool wasflying; - - static void TrackBody( Actor b ) - { - if ( !b ) return; - let ffd = new("ForcedFallerDamager"); - ffd.ChangeStatNum(STAT_USER); - ffd.mybody = b; - ffd.lastvelz = b.vel.z; - ffd.wasflying = ((b.pos.z>b.floorz)&&b.TestMobjZ()); - } - static void SetInstigator( Actor b, Actor whomst ) - { - if ( !b || !whomst ) return; - let ti = ThinkerIterator.Create("ForcedFallerDamager",STAT_USER); - ForcedFallerDamager ffd; - while ( ffd = ForcedFallerDamager(ti.Next()) ) - { - if ( ffd.mybody != b ) continue; - ffd.instigator = whomst; - break; - } - } - override void Tick() - { - if ( !swwm_doomfall || !mybody ) - { - Destroy(); - return; - } - bool isflying = ((mybody.pos.z>mybody.floorz)&&mybody.TestMobjZ())||mybody.bNOGRAVITY; - if ( wasflying && !isflying && (lastvelz < -23) && !level.monsterfallingdamage && !(mybody.floorsector.flags&Sector.SECF_NOFALLINGDAMAGE) ) - { - // big hurt - int dmg; - if ( lastvelz < -35 ) dmg = Actor.TELEFRAG_DAMAGE; - else dmg = int((-lastvelz-23)*6); - mybody.DamageMobj(instigator,instigator,dmg,'Falling'); - } - if ( !isflying && wasflying ) instigator = null; - wasflying = isflying; - lastvelz = mybody.vel.z; - // wait until body is dead - if ( mybody.Health > 0 ) return; - Destroy(); - } -} - -Class RadiusDebugSphere : Actor -{ - Default - { - RenderStyle "AddStencil"; - StencilColor "White"; - Radius .1; - Height 0.; - +NOGRAVITY; - +NOINTERACTION; - } - States - { - Spawn: - XZW1 A 1 BRIGHT A_FadeOut(); - Wait; - } -} - -// Handler responsible for item replacements and whatever else -Class SWWMHandler : EventHandler -{ - transient String oneliner, onelinersnd; - transient int onelinertic, onelinerspan, onelinerlevel; - transient int lastlock, lastcombat; - transient Array combatactors; - transient Array combattics; - transient int highesttic; - transient Array flashes; - transient Array lastlines; - transient int lastpickuptic[MAXPLAYERS]; - SWWMCombatTracker trackers; - SWWMScoreObj scorenums, damnums; - SWWMInterest intpoints; - int trackers_cnt, scorenums_cnt, damnums_cnt, intpoints_cnt; - bool tookdamage[MAXPLAYERS]; - int spreecount[MAXPLAYERS]; - int lastkill[MAXPLAYERS]; - int multilevel[MAXPLAYERS]; - int lastitemcount[MAXPLAYERS]; - bool allkills, allitems, allsecrets; - // for money cheat - transient ui int kcode, lcode; - - // heal/armor flashes need to be handled here so they don't stack - transient int hflash[MAXPLAYERS], aflash[MAXPLAYERS]; - - // for menu events - transient Array checklist; - - transient CVar mutevoice, accdamage; - transient ui CVar useshaders; - transient CVar lang; - transient String curlang; - transient CVar funtags; - transient bool curfuntags; - - // optimization - OnFire fires; - int fires_cnt; - - static void HealthFlash( int p ) - { - let hnd = SWWMHandler(EventHandler.Find("SWWMHandler")); - if ( !hnd || (p == -1) ) return; - hnd.hflash[p] = gametic+5; - } - - static void ArmorFlash( int p ) - { - let hnd = SWWMHandler(EventHandler.Find("SWWMHandler")); - if ( !hnd || (p == -1) ) return; - hnd.aflash[p] = gametic+5; - } - - static int AddOneliner( String type, int level, int delay = 5 ) - { - // only Demolitionist can play voice lines - if ( !(players[consoleplayer].mo is 'Demolitionist') ) - return 0; - let hnd = SWWMHandler(EventHandler.Find("SWWMHandler")); - if ( !hnd ) return 0; - String voicetype = CVar.GetCVar('swwm_voicetype',players[consoleplayer]).GetString(); - // suppress non-rage comments when ragekit is active, only screaming allowed - if ( players[consoleplayer].mo.FindInventory("RagekitPower") && (type != "ragekit") ) return 0; - int whichline; - String testme = String.Format("SWWM_SUBS_%s_N%s",voicetype.MakeUpper(),type.MakeUpper()); - String locme = StringTable.Localize(testme,false); - int countem; - if ( testme == locme ) countem = 0; - else countem = locme.ToInt(); - if ( countem == 0 ) - { - if ( voicetype ~== "default" ) return 0; - // retry with the default voicetype - voicetype = "default"; - testme = String.Format("SWWM_SUBS_DEFAULT_N%s",type.MakeUpper()); - locme = StringTable.Localize(testme,false); - if ( testme == locme ) countem = 0; - else countem = locme.ToInt(); - if ( countem == 0 ) return 0; - } - // check last line so we don't repeat - int last = 0, ent; - for ( int i=0; i 0 ) - { - whichline = Random[DemoLines](1,countem-1); - if ( whichline >= last ) whichline++; - hnd.lastlines[ent].lineno = whichline; - } - else - { - whichline = Random[DemoLines](1,countem); - let lst = new("LastLine"); - lst.type = type; - lst.lineno = whichline; - hnd.lastlines.Push(lst); - } - hnd.oneliner = String.Format("$SWWM_SUBS_%s_%s%d",voicetype.MakeUpper(),type.MakeUpper(),whichline); - hnd.onelinersnd = String.Format("voice/%s/%s%d",voicetype,type,whichline); - hnd.onelinertic = gametic+delay; - hnd.onelinerspan = int(S_GetLength(hnd.onelinersnd)*Thinker.TICRATE); - hnd.onelinerlevel = level; - return hnd.onelinertic+hnd.onelinerspan; - } - - override void OnRegister() - { - // oneliner RNG must be relative to consoleplayer - SetRandomSeed[DemoLines](Random[DemoLines]()+consoleplayer+MSTime()); - } - - private static Vector3 UseLinePos( Line l ) - { - Vector3 al, ah, bl, bh; - if ( !l.sidedef[1] ) - { - // just the whole line - al = (l.v1.p,l.frontsector.floorplane.ZatPoint(l.v1.p)); - ah = (l.v1.p,l.frontsector.ceilingplane.ZatPoint(l.v1.p)); - bl = (l.v2.p,l.frontsector.floorplane.ZatPoint(l.v2.p)); - bh = (l.v2.p,l.frontsector.ceilingplane.ZatPoint(l.v2.p)); - return (al+ah+bl+bh)*.25; - } - SecPlane highestfloor, lowestfloor, lowestceiling, highestceiling; - if ( (l.frontsector.floorplane.ZatPoint(l.v1.p) > l.backsector.floorplane.ZatPoint(l.v1.p)) - && (l.frontsector.floorplane.ZatPoint(l.v2.p) > l.backsector.floorplane.ZatPoint(l.v2.p)) ) - { - highestfloor = l.frontsector.floorplane; - lowestfloor = l.backsector.floorplane; - } - else - { - highestfloor = l.backsector.floorplane; - lowestfloor = l.frontsector.floorplane; - } - if ( (l.frontsector.ceilingplane.ZatPoint(l.v1.p) < l.backsector.ceilingplane.ZatPoint(l.v1.p)) - && (l.frontsector.ceilingplane.ZatPoint(l.v2.p) < l.backsector.ceilingplane.ZatPoint(l.v2.p)) ) - { - lowestceiling = l.frontsector.ceilingplane; - highestceiling = l.backsector.ceilingplane; - } - else - { - lowestceiling = l.backsector.ceilingplane; - highestceiling = l.frontsector.ceilingplane; - } - // try to guess what the part that triggers this is - if ( l.Activation&SPAC_Cross ) - { - // pick the "intersection" - al = (l.v1.p,highestfloor.ZatPoint(l.v1.p)); - ah = (l.v1.p,lowestceiling.ZatPoint(l.v1.p)); - bl = (l.v2.p,highestfloor.ZatPoint(l.v2.p)); - bh = (l.v2.p,lowestceiling.ZatPoint(l.v2.p)); - return (al+ah+bl+bh)*.25; - } - // check if lower part available - al = (l.v1.p,lowestfloor.ZatPoint(l.v1.p)); - ah = (l.v1.p,highestfloor.ZatPoint(l.v1.p)); - bl = (l.v2.p,lowestfloor.ZatPoint(l.v2.p)); - bh = (l.v2.p,highestfloor.ZatPoint(l.v2.p)); - if ( ((al-ah).length() > 0) && ((bl-bh).length() > 0) ) - return (al+ah+bl+bh)*.25; - // check if upper part available - al = (l.v1.p,lowestceiling.ZatPoint(l.v1.p)); - ah = (l.v1.p,highestceiling.ZatPoint(l.v1.p)); - bl = (l.v2.p,lowestceiling.ZatPoint(l.v2.p)); - bh = (l.v2.p,highestceiling.ZatPoint(l.v2.p)); - if ( ((al-ah).length() > 0) && ((bl-bh).length() > 0) ) - return (al+ah+bl+bh)*.25; - // check for 3d floors - bool floorfound = false; - Vector3 fal, fah, fbl, fbh; - for ( int i=0; i ah.z) && (fbh.z > bh.z) && (fal.z > al.z) && (fbl.z > bl.z) ) continue; - al = fal; - ah = fah; - bl = fbl; - bh = fbh; - floorfound = true; - } - if ( floorfound ) return (al+ah+bl+bh)*.25; - for ( int i=0; i ah.z) && (fbh.z > bh.z) && (fal.z > al.z) && (fbl.z > bl.z) ) continue; - al = fal; - ah = fah; - bl = fbl; - bh = fbh; - floorfound = true; - } - if ( floorfound ) return (al+ah+bl+bh)*.25; - // check for midtex - if ( !l.sidedef[0].GetTexture(1).IsNull() ) - { - double ofs = l.sidedef[0].GetTextureYOffset(1); - Vector2 siz = TexMan.GetScaledSize(l.sidedef[0].GetTexture(1)); - Vector2 tofs = TexMan.GetScaledOffset(l.sidedef[0].GetTexture(1)); - ofs += tofs.y; - ofs *= l.sidedef[0].GetTextureYScale(1); - siz.y *= l.sidedef[0].GetTextureYScale(1); - if ( l.flags&Line.ML_DONTPEGBOTTOM ) - { - al = (l.v1.p,highestfloor.ZAtPoint(l.v1.p)+ofs); - bl = (l.v2.p,highestfloor.ZAtPoint(l.v2.p)+ofs); - ah = al+(0,0,siz.y); - bh = bl+(0,0,siz.y); - } - else - { - ah = (l.v1.p,lowestceiling.ZAtPoint(l.v1.p)+ofs); - bh = (l.v2.p,lowestceiling.ZAtPoint(l.v2.p)+ofs); - al = ah-(0,0,siz.y); - bl = bh-(0,0,siz.y); - } - return (al+ah+bl+bh)*.25; - } - if ( !l.sidedef[1].GetTexture(1).IsNull() ) - { - double ofs = l.sidedef[1].GetTextureYOffset(1); - Vector2 siz = TexMan.GetScaledSize(l.sidedef[1].GetTexture(1)); - Vector2 tofs = TexMan.GetScaledOffset(l.sidedef[1].GetTexture(1)); - ofs += tofs.y; - ofs *= l.sidedef[1].GetTextureYScale(1); - siz.y *= l.sidedef[1].GetTextureYScale(1); - if ( l.flags&Line.ML_DONTPEGBOTTOM ) - { - al = (l.v1.p,highestfloor.ZAtPoint(l.v1.p)+ofs); - bl = (l.v2.p,highestfloor.ZAtPoint(l.v2.p)+ofs); - ah = al+(0,0,siz.y); - bh = bl+(0,0,siz.y); - } - else - { - ah = (l.v1.p,lowestceiling.ZAtPoint(l.v1.p)+ofs); - bh = (l.v2.p,lowestceiling.ZAtPoint(l.v2.p)+ofs); - al = ah-(0,0,siz.y); - bl = bh-(0,0,siz.y); - } - return (al+ah+bl+bh)*.25; - } - // just use the intersection - al = (l.v1.p,highestfloor.ZatPoint(l.v1.p)); - ah = (l.v1.p,lowestceiling.ZatPoint(l.v1.p)); - bl = (l.v2.p,highestfloor.ZatPoint(l.v2.p)); - bh = (l.v2.p,lowestceiling.ZatPoint(l.v2.p)); - return (al+ah+bl+bh)*.25; - } - - // level end stats - override void WorldUnloaded( WorldEvent e ) - { - let ti = ThinkerIterator.Create("SWWMStats",Thinker.STAT_STATIC); - SWWMStats s; - while ( s = SWWMStats(ti.Next()) ) - s.AddLevelStats(); - PlayerInfo p = players[consoleplayer]; - Shader.SetEnabled(p,"ZoomBlur",false); - Shader.SetEnabled(p,"RagekitShader",false); - Shader.SetEnabled(p,"GhostShader",false); - Shader.SetEnabled(p,"InvinciShader",false); - Shader.SetEnabled(p,"Glitch",false); - Shader.SetEnabled(p,"Grain",false); - } - - override void WorldLoaded( WorldEvent e ) - { - if ( level.levelname ~== "Modder Test Map" ) level.ReplaceTextures("-noflat-","kinstile",0); - if ( !mutevoice ) mutevoice = CVar.GetCVar('swwm_mutevoice',players[consoleplayer]); - if ( !e.IsSaveGame && !e.IsReopen && (gamestate != GS_TITLELEVEL) ) - AddOneliner("mapstart",3); - if ( !e.IsSaveGame && !e.IsReopen ) - { - // for skipping over merged exit lines (sharing vertices) - Array skipme; - skipme.Clear(); - // find exit lines, and use lines that aren't exits - for ( int i=0; i 0 ) - { - if ( onelinerlevel > mutevoice.GetInt() ) - players[consoleplayer].mo.A_StartSound(onelinersnd,CHAN_DEMOVOICE,CHANF_DEFAULT,1.,ATTN_NONE); - SendNetworkEvent("swwmremoteliner."..onelinersnd,consoleplayer,onelinerlevel); - } - onelinertic = 0; - onelinerspan = 0; - } - for ( int i=0; i= gametic ) continue; - flashes.Delete(i); - i--; - } - // countable item scoring - for ( int i=0; i lastitemcount[i] ) - { - int score = 25*(players[i].itemcount-lastitemcount[i]); - if ( (level.total_items == level.found_items) && !allitems ) - { - allitems = true; - Console.Printf(StringTable.Localize("$SWWM_LASTITEM"),players[i].GetUserName(),2500); - score += 2475; - } - SWWMCredits.Give(players[i],score); - SWWMScoreObj.Spawn(score,players[i].mo.Vec3Offset(0,0,players[i].mo.Height/2)); - lastitemcount[i] = players[i].itemcount; - } - } - // combat tracking - // 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 either actor can't see the other - if ( (a.target != players[consoleplayer].mo) - || !a.CheckSight(players[consoleplayer].mo) - || !players[consoleplayer].mo.CheckSight(a) ) continue; - // is it already in? - bool addme = true; - for ( int i=0; i highesttic+700)) ) - lastcombat = AddOneliner("fightstart",1,10); - } - - private bool HexenMap40() - { - if ( level.GetChecksum() ~== "2A6C4235B942467D25FD50D5B313E67A" ) return true; // 1.1 - if ( level.GetChecksum() ~== "1C5DE5A921DEE405E98E7E09D9829387" ) return true; // 1.0 - if ( level.GetChecksum() ~== "EFAFE59092DE5E613562ACF52B86C37F" ) return true; // beta - return false; - } - - override void WorldThingDied( WorldEvent e ) - { - if ( e.Thing.default.bBOSS && !Random[GoldDrop](0,2) ) - { - let g = Actor.Spawn("GoldShell",e.Thing.Vec3Offset(0,0,e.Thing.Height/2)); - double ang = FRandom[SpareShells](0,360); - g.vel.xy = (cos(ang),sin(ang))*FRandom[SpareShells](.4,.8); - g.vel.z = FRandom[SpareShells](2.,4.); - } - // Korax instakill - if ( (e.Thing is 'Korax') && !e.Thing.special2 && HexenMap40() ) - { - e.Thing.special2 = 1; - // terminate the monster closet scripts, open all the - // doors ourselves - level.ExecuteSpecial(ACS_Terminate,e.Thing,null,false,220); - level.ExecuteSpecial(ACS_Terminate,e.Thing,null,false,220); - level.ExecuteSpecial(ACS_Terminate,e.Thing,null,false,221); - level.ExecuteSpecial(ACS_Terminate,e.Thing,null,false,255); - level.ExecuteSpecial(Door_Open,e.Thing,null,false,10,16); - level.ExecuteSpecial(Door_Open,e.Thing,null,false,11,16); - level.ExecuteSpecial(Door_Open,e.Thing,null,false,12,16); - level.ExecuteSpecial(Door_Open,e.Thing,null,false,13,16); - level.ExecuteSpecial(Door_Open,e.Thing,null,false,14,16); - level.ExecuteSpecial(Door_Open,e.Thing,null,false,10,16); - // keep the portal closed, you can't leave unless you - // kill everyone else - let t = new("UglyBoyGetsFuckedUp"); - t.ChangeStatNum(Thinker.STAT_USER); - } - } - - override void WorldThingDamaged( WorldEvent e ) - { - if ( e.Damage > 0 ) - { - if ( !accdamage ) accdamage = CVar.GetCVar('swwm_accdamage',players[consoleplayer]); - bool spawnme = true; - if ( accdamage.GetBool() ) - { - // 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),Font.CR_RED,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 - ForcedFallerDamager.SetInstigator(e.Thing,e.DamageSource); - // stats - if ( e.Thing.player ) - { - tookdamage[e.Thing.PlayerNumber()] = true; - let s = SWWMStats.Find(e.Thing.player); - 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); - s.AddDamageDealt(e.Damage); - if ( e.Damage > s.topdealt ) s.topdealt = e.Damage; - } - } - if ( e.DamageSource && (e.DamageSource != e.Thing) ) - { - if ( (e.DamageSource.bISMONSTER || e.DamageSource.player || (e.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||e.DamageSource.bBOSSDEATH)?2:4) ) 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; - } - } - } - if ( (e.Thing.Health > 0) || e.Thing.bKilled || e.Thing.bCorpse ) return; - if ( !e.Thing.player && !e.Thing.bIsMonster && !e.Thing.bCountKill && !(e.Thing is 'ScriptedMarine') ) return; - if ( (e.DamageSource && e.DamageSource.player && (e.DamageSource != e.Thing)) ) - { - let s = SWWMStats.Find(e.DamageSource.player); - if ( s ) - { - s.kills++; - s.AddWeaponKill(e.Inflictor,e.Thing); - } - if ( e.DamageSource == players[consoleplayer].mo ) - { - highesttic = gametic; - if ( !lastcombat || (gametic > lastcombat+40) ) - { - if ( e.Thing.IsFriend(e.DamageSource) || SWWMUtility.IsCivilian(e.Thing) ) - lastcombat = AddOneliner("friendkill",1,5); - else if ( (!lastcombat || (gametic > lastcombat+100)) && !Random[DemoLines](0,(e.Thing.bBOSS||e.Thing.bBOSSDEATH)?2:5) ) - lastcombat = AddOneliner("scorekill",1,15); - } - } - // no credits unless it's a counted kill or marine (that isn't friendly) - if ( e.Thing.IsFriend(e.DamageSource) || (!e.Thing.default.bCountKill && !(e.Thing is 'ScriptedMarine')) ) - return; - int pnum = e.DamageSource.PlayerNumber(); - if ( level.maptime < (lastkill[pnum]+5*Thinker.TICRATE) ) - multilevel[pnum]++; - else multilevel[pnum] = 0; - if ( s && (multilevel[pnum]+1 > s.mkill) ) - s.mkill = multilevel[pnum]+1; - lastkill[pnum] = level.maptime; - // scoring - int score = min(1000,int(ceil(e.Thing.SpawnHealth()*.25)*10)); - SWWMScoreObj scr = null; - if ( e.DamageSource.player == players[consoleplayer] ) - scr = SWWMScoreObj.Spawn(score,e.Thing.Vec3Offset(0,0,e.Thing.Height/2)); - int ofs = 0; - if ( e.DamageType == 'Push' ) - { - score += 1000; - if ( scr ) - { - scr.xscore[ofs] = 0; - scr.xtcolor[ofs] = Font.CR_FIRE; - scr.xstr[ofs] = StringTable.Localize("$SWWM_SHAMEFUL"); - scr.xcnt = ++ofs; - } - } - if ( ((e.Thing.Health <= e.Thing.GetGibHealth()) || (e.DamageSource.bEXTREMEDEATH) || (e.Inflictor && e.Inflictor.bEXTREMEDEATH) || (e.DamageType == 'Extreme')) && !e.DamageSource.bNOEXTREMEDEATH && (!e.Inflictor || !e.Inflictor.bNOEXTREMEDEATH) ) - { - score = int(score*1.25); - if ( scr ) - { - scr.xscore[ofs] = 0; - scr.xtcolor[ofs] = Font.CR_FIRE; - 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.xtcolor[ofs] = Font.CR_FIRE; - scr.xstr[ofs] = StringTable.Localize("$SWWM_MULTIKILL"); - scr.xcnt = ++ofs; - } - } - 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]+1; - scr.xtcolor[ofs] = Font.CR_FIRE; - scr.xstr[ofs] = StringTable.Localize("$SWWM_SPREEKILL"); - scr.xcnt = ++ofs; - } - } - if ( e.Thing.bBOSS ) - { - score += 5000; - if ( scr ) - { - scr.xscore[ofs] = 0; - scr.xtcolor[ofs] = Font.CR_FIRE; - scr.xstr[ofs] = StringTable.Localize("$SWWM_BOSSKILL"); - scr.xcnt = ++ofs; - } - } - SWWMCredits.Give(e.DamageSource.player,score); - if ( scr ) scr.score = score; // update final score - if ( (level.killed_monsters+1 == level.total_monsters) && !allkills ) - { - allkills = true; - SWWMCredits.Give(e.DamageSource.player,2000); - Console.Printf(StringTable.Localize("$SWWM_LASTMONSTER"),e.DamageSource.player.GetUserName(),2000); - SWWMScoreObj.Spawn(2000,e.DamageSource.Vec3Offset(0,0,e.DamageSource.Height/2)); - } - spreecount[pnum]++; - if ( s && (spreecount[pnum] > s.skill) && !tookdamage[pnum] ) - s.skill = spreecount[pnum]; - } - } - - private void DoKeyTagFix( Actor a ) - { - if ( a is 'RedCard' ) a.SetTag("$T_REDCARD"); - else if ( a is 'BlueCard' ) a.SetTag("$T_BLUECARD"); - else if ( a is 'YellowCard' ) a.SetTag("$T_YELLOWCARD"); - else if ( a is 'RedSkull' ) a.SetTag("$T_REDSKULL"); - else if ( a is 'BlueSkull' ) a.SetTag("$T_BLUESKULL"); - else if ( a is 'YellowSkull' ) a.SetTag("$T_YELLOWSKULL"); - else if ( a is 'KeyYellow' ) a.SetTag("$T_YELLOWKEY"); - else if ( a is 'KeyGreen' ) a.SetTag("$T_GREENKEY"); - else if ( a is 'KeyBlue' ) a.SetTag("$T_BLUEKEY"); - else if ( a.GetClassName() == 'KeyRed' ) a.SetTag("$T_REDKEY"); - else if ( a is 'KeySteel' ) a.SetTag("$T_KEYSTEEL"); - else if ( a is 'KeyCave' ) a.SetTag("$T_KEYCAVE"); - else if ( a is 'KeyAxe' ) a.SetTag("$T_KEYAXE"); - else if ( a is 'KeyFire' ) a.SetTag("$T_KEYFIRE"); - else if ( a is 'KeyEmerald' ) a.SetTag("$T_KEYEMERALD"); - else if ( a is 'KeyDungeon' ) a.SetTag("$T_KEYDUNGEON"); - else if ( a is 'KeySilver' ) a.SetTag("$T_KEYSILVER"); - else if ( a is 'KeyRusted' ) a.SetTag("$T_KEYRUSTED"); - else if ( a is 'KeyHorn' ) a.SetTag("$T_KEYHORN"); - else if ( a is 'KeySwamp' ) a.SetTag("$T_KEYSWAMP"); - else if ( a is 'KeyCastle' ) a.SetTag("$T_KEYCASTLE"); - } - - // tempfix keys have no tags - static void KeyTagFix( Actor a ) - { - let hnd = SWWMHandler(Find("SWWMHandler")); - if ( hnd ) hnd.DoKeyTagFix(a); - } - - // 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; - break; - } - } - - override void WorldThingSpawned( WorldEvent e ) - { - if ( e.Thing is 'Inventory' ) - CopyFloatBob(e.Thing); - if ( swwm_doomfall && e.Thing.bISMONSTER && !level.monsterfallingdamage ) - ForcedFallerDamager.TrackBody(e.Thing); - if ( e.Thing is 'Key' ) - { - DoKeyTagFix(e.Thing); - SWWMInterest.Spawn(thekey:Key(e.Thing)); - } - else if ( e.Thing is 'BossBrain' ) - { - e.Thing.SetTag("$FN_BOSSBRAIN"); - e.Thing.A_SetSize(20,70); - } - else if ( e.Thing is 'CommanderKeen' ) e.Thing.SetTag("$FN_KEEN"); - if ( !swwm_notrack && (e.Thing.bSHOOTABLE || e.Thing.bISMONSTER) && !(e.Thing is 'LampMoth') && !(e.Thing is 'CompanionLamp') ) SWWMCombatTracker.Spawn(e.Thing); - } - - override void PostUiTick() - { - if ( (gametic == onelinertic) && (oneliner != "") && (players[consoleplayer].health > 0) ) - { - int mute; - if ( mutevoice ) mute = mutevoice.GetInt(); - else mute = CVar.GetCVar('swwm_mutevoice',players[consoleplayer]).GetInt(); // we can't assign the variable here since it's play scope - if ( onelinerlevel > mute ) - { - let l = SWWMOneLiner.Make(oneliner,onelinerspan); - StatusBar.AttachMessage(l,-3473); - } - SendNetworkEvent("swwmremotelinertxt."..oneliner,consoleplayer,onelinerlevel); - } - for ( int i=0; i= 11 ) - { - SendNetworkEvent("swwmmoneycheat",consoleplayer); - kcode = 0; - } - } - else kcode = 0; - static const int deep[] = {32,18,18,25,38,24,19,18}; - // the deepest lore - if ( e.KeyScan == deep[lcode] ) - { - lcode++; - if ( lcode >= 8 ) - { - SendNetworkEvent("swwmlorecheat",consoleplayer); - lcode = 0; - } - } - else lcode = 0; - if ( e.KeyScan == 33 ) // assuming that's the F key on all keyboards (hopefully) - { - let demo = Demolitionist(players[consoleplayer].mo); - let gone = PlayerGone(players[consoleplayer].mo); - if ( (demo && (demo.Health <= 0) && (demo.deadtimer > 40)) - || (gone && (gone.Health <= 0) && (gone.deadtimer > 40)) ) - { - // pay respects - int numf = Random[FInTheChat](1,6); - for ( int i=0; i 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++; - } - - override void CheckReplacee( ReplacedEvent e ) - { - if ( e.Replacement is 'DSparilHax' ) - e.Replacee = 'Sorcerer2'; - } - - override void CheckReplacement( ReplaceEvent e ) - { - // respect final replacements - if ( e.IsFinal ) return; - // shell types (sorted by rarity - static const Class redpool[] = {"RedShell","RedShell2","RedShell4","RedShell8","RedShell12","RedShell16"}; - static const Class greenpool[] = {"GreenShell","GreenShell2","GreenShell4","GreenShell8","GreenShell12"}; - static const Class whitepool[] = {"WhiteShell","WhiteShell2","WhiteShell4","WhiteShell8"}; - static const Class purplepool[] = {"PurpleShell","PurpleShell2","PurpleShell4"}; - static const Class bluepool[] = {"BlueShell","BlueShell2","BlueShell4","BlueShell8"}; - static const Class blackpool[] = {"BlackShell","BlackShell2","BlackShell4"}; - if ( e.Replacee is 'ItemFog' ) e.Replacement = 'SWWMItemFog'; - if ( e.Replacee is 'TeleportFog' ) e.Replacement = 'SWWMTeleportFog'; - else if ( (e.Replacee is 'Chainsaw') || (e.Replacee is 'Gauntlets') || (e.Replacee is 'FWeapAxe') ) e.Replacement = 'PusherWeapon'; - else if ( (e.Replacee is 'Fist') || (e.Replacee is 'Staff') ) e.Replacement = 'DeepImpact'; - else if ( (e.Replacee is 'Pistol') || (e.Replacee is 'GoldWand') || (e.Replacee is 'FWeapFist') || (e.Replacee is 'CWeapMace') || (e.Replacee is 'MWeapWand') ) e.Replacement = 'ExplodiumGun'; - else if ( (e.Replacee is 'Shotgun') || (e.Replacee is 'CWeapStaff') ) e.Replacement = 'Spreadgun'; - else if ( (e.Replacee is 'SuperShotgun') || (e.Replacee is 'MWeapFrost') ) e.Replacement = 'Wallbuster'; - else if ( e.Replacee is 'Crossbow' ) - { - if ( Random[Replacements](0,2) ) e.Replacement = 'Spreadgun'; - else e.Replacement = 'Wallbuster'; - } - else if ( (e.Replacee is 'Chaingun') || (e.Replacee is 'Blaster') || (e.Replacee is 'FWeapHammer') ) e.Replacement = 'Eviscerator'; - else if ( (e.Replacee is 'RocketLauncher') || (e.Replacee is 'PhoenixRod') || (e.Replacee is 'FWeaponPiece3') ) e.Replacement = 'Hellblazer'; - else if ( (e.Replacee is 'PlasmaRifle') || (e.Replacee is 'SkullRod') ) - { - if ( Random[Replacements](0,2) ) e.Replacement = 'Sparkster'; - else e.Replacement = 'SilverBullet'; - } - else if ( e.Replacee is 'CWeapFlame' ) e.Replacement = 'Sparkster'; - else if ( e.Replacee is 'MWeapLightning' ) e.Replacement = 'SilverBullet'; - else if ( (e.Replacee is 'BFG9000') || (e.Replacee is 'Mace') ) - { - if ( !Random[Replacements](0,2) ) e.Replacement = 'Ynykron'; - else e.Replacement = 'CandyGun'; - } - else if ( e.Replacee is 'CWeaponPiece2' ) e.Replacement = 'CandyGun'; - else if ( e.Replacee is 'MWeaponPiece1' ) e.Replacement = 'Ynykron'; - else if ( (e.Replacee == 'Clip') || (e.Replacee == 'GoldWandAmmo') || (e.Replacee == 'GoldWandHefty') || (e.Replacee is 'ArtiPoisonBag') ) - { - switch( Random[Replacement](0,14) ) - { - case 0: - case 1: - case 2: - e.Replacement = redpool[Random[Replacement](0,1)]; - break; - case 4: - case 5: - case 6: - e.Replacement = greenpool[Random[Replacement](0,1)]; - break; - case 7: - case 8: - e.Replacement = purplepool[0]; - break; - default: - e.Replacement = 'SWWMNothing'; - break; - } - } - else if ( (e.Replacee == 'Shell') || (e.Replacee is 'CrossbowAmmo') ) - { - switch( Random[Replacement](0,13) ) - { - case 0: - case 1: - case 2: - e.Replacement = redpool[Random[Replacement](0,1)]; - break; - case 3: - case 4: - case 5: - e.Replacement = greenpool[Random[Replacement](0,1)]; - break; - case 6: - case 7: - e.Replacement = whitepool[0]; - break; - case 8: - case 9: - case 10: - e.Replacement = purplepool[0]; - break; - case 11: - case 12: - e.Replacement = bluepool[Random[Replacement](0,1)]; - break; - case 13: - e.Replacement = blackpool[0]; - break; - } - } - else if ( (e.Replacee == 'ShellBox') || (e.Replacee is 'CrossbowHefty') ) - { - switch( Random[Replacement](0,14) ) - { - case 0: - case 1: - case 2: - e.Replacement = redpool[Random[Replacement](0,5)]; - break; - case 3: - case 4: - case 5: - e.Replacement = greenpool[Random[Replacement](0,4)]; - break; - case 6: - case 7: - case 8: - e.Replacement = whitepool[Random[Replacement](0,3)]; - break; - case 9: - case 10: - case 11: - e.Replacement = purplepool[Random[Replacement](0,2)]; - break; - case 12: - case 13: - e.Replacement = bluepool[Random[Replacement](0,3)]; - break; - case 14: - e.Replacement = blackpool[Random[Replacement](0,2)]; - break; - } - } - else if ( e.Replacee == 'ClipBox' ) - { - if ( Random[Replacements](0,3) ) e.Replacement = 'EvisceratorShell'; - else e.Replacement = 'EvisceratorSixPack'; - } - else if ( e.Replacee == 'BlasterAmmo' ) e.Replacement = 'EvisceratorShell'; - else if ( e.Replacee == 'BlasterHefty' ) e.Replacement = 'EvisceratorSixPack'; - else if ( (e.Replacee == 'RocketAmmo') || (e.Replacee == 'PhoenixRodAmmo') || (e.Replacee == 'MaceAmmo') ) - { - switch ( Random[Replacements](0,6) ) - { - case 0: - case 1: - case 2: - case 3: - e.Replacement = 'HellblazerMissiles'; - break; - case 4: - case 5: - case 6: - e.Replacement = 'HellblazerCrackshots'; - break; - } - } - else if ( (e.Replacee == 'RocketBox') || (e.Replacee == 'PhoenixRodHefty') || (e.Replacee == 'MaceHefty') ) - { - switch ( Random[Replacements](0,11) ) - { - case 0: - case 1: - case 2: - case 3: - case 4: - if ( Random[Replacements](0,2) ) e.Replacement = 'HellblazerMissiles'; - else e.Replacement = 'HellblazerMissileMag'; - break; - case 5: - case 6: - case 7: - case 8: - if ( Random[Replacements](0,3) ) e.Replacement = 'HellblazerCrackshots'; - else e.Replacement = 'HellblazerCrackshotMag'; - break; - case 9: - case 10: - if ( Random[Replacements](0,8) ) e.Replacement = 'HellblazerRavagers'; - else e.Replacement = 'HellblazerRavagerMag'; - break; - case 11: - if ( Random[Replacements](0,10) ) e.Replacement = 'HellblazerWarheads'; - else e.Replacement = 'HellblazerWarheadMag'; - break; - } - } - else if ( (e.Replacee == 'Cell') || (e.Replacee == 'SkullRodAmmo') ) - { - if ( Random[Replacements](0,3) ) e.Replacement = 'SparkUnit'; - else if ( !Random[Replacements](0,2) ) e.Replacement = 'CandyGunBullets'; - else e.Replacement = Random[Replacements](0,2)?'SilverBullets':'SilverBullets2'; - } - else if ( (e.Replacee == 'ArtiTeleport') || (e.Replacee == 'ArtiTeleportOther') ) - { - if ( Random[Replacements](0,3) ) e.Replacement = 'SWWMNothing'; - else e.Replacement = 'GoldShell'; - } - else if ( (e.Replacee == 'CellPack') || (e.Replacee == 'SkullRodHefty') ) - { - if ( !Random[Replacements](0,2) ) e.Replacement = Random[Replacements](0,2)?'SilverBulletAmmo':'SilverBulletAmmo2'; - else e.Replacement = 'CandyGunAmmo'; - } - else if ( e.Replacee == 'Mana1' ) e.Replacement = 'FabricatorTier1'; - else if ( e.Replacee == 'Mana2' ) e.Replacement = 'FabricatorTier2'; - else if ( e.Replacee == 'Mana3' ) e.Replacement = 'FabricatorTier3'; - else if ( e.Replacee == 'ArtiBoostMana' ) e.Replacement = 'FabricatorTier4'; - else if ( (e.Replacee == 'Backpack') || (e.Replacee == 'BagOfHolding') || (e.Replacee == 'ArtiPork') || (e.Replacee == 'ArtiBoostArmor') ) e.Replacement = 'HammerspaceEmbiggener'; - else if ( (e.Replacee == 'FWeaponPiece1') || (e.Replacee == 'FWeaponPiece2') - || (e.Replacee == 'CWeaponPiece1') || (e.Replacee == 'CWeaponPiece3') - || (e.Replacee == 'MWeaponPiece2') || (e.Replacee == 'MWeaponPiece3') ) - { - if ( Random[Replacements](0,1) ) e.Replacement = 'SWWMNothing'; - else if ( Random[Replacements](0,5) ) e.Replacement = 'HammerspaceEmbiggener'; - else e.Replacement = 'GoldShell'; - } - else if ( (e.Replacee == 'ArmorBonus') || (e.Replacee == 'ArtiTimeBomb') || (e.Replacee == 'ArtiBlastRadius') ) e.Replacement = 'ArmorNuggetItem'; - else if ( (e.Replacee == 'HealthBonus') || (e.Replacee == 'CrystalVial') ) e.Replacement = 'HealthNuggetItem'; - else if ( e.Replacee == 'Stimpack' ) e.Replacement = 'TetraHealthItem'; - else if ( e.Replacee == 'Medikit' ) e.Replacement = 'CubeHealthItem'; - else if ( e.Replacee == 'ArtiHealth' ) - { - if ( gameinfo.gametype&GAME_Heretic && Random[Replacements](0,1) ) e.Replacement = 'CubeHealthItem'; - else e.Replacement = 'TetraHealthItem'; - } - else if ( (e.Replacee == 'Soulsphere') || (e.Replacee == 'ArtiSuperHealth') ) - { - if ( gameinfo.gametype&GAME_Hexen ) e.Replacement = 'CubeHealthItem'; - else e.Replacement = 'RefresherItem'; - } - else if ( e.Replacee == 'ArtiHealingRadius' ) e.Replacement = 'RefresherItem'; - else if ( (e.Replacee == 'Megasphere') || (e.Replacee == 'ArtiEgg') || (e.Replacee == 'PlatinumHelm') ) e.Replacement = 'GrilledCheeseSandwich'; - else if ( (e.Replacee == 'Blursphere') || (e.Replacee == 'ArtiInvisibility') || (e.Replacee == 'AmuletOfWarding') ) e.Replacement = 'GhostArtifact'; - else if ( e.Replacee == 'Radsuit' ) e.Replacement = 'EBarrier'; - else if ( (e.Replacee == 'ArtiFly') ) e.Replacement = 'GravitySuppressor'; - else if ( (e.Replacee == 'InvulnerabilitySphere') || (e.Replacee == 'ArtiInvulnerability') || (e.Replacee == 'ArtiInvulnerability2') ) e.Replacement = 'FuckingInvinciball'; - else if ( (e.Replacee == 'Berserk') || (e.Replacee == 'ArtiTomeOfPower') || (e.Replacee == 'ArtiDarkServant') || (e.Replacee == 'ArtiSpeedBoots') ) e.Replacement = 'Ragekit'; - else if ( (e.Replacee == 'AllMap') || (e.Replacee == 'SuperMap') ) e.Replacement = 'Omnisight'; - else if ( (e.Replacee == 'Infrared') || (e.Replacee == 'ArtiTorch') ) e.Replacement = 'SWWMLamp'; - else if ( (e.Replacee == 'GreenArmor') || (e.Replacee == 'SilverShield') || (e.Replacee == 'MeshArmor') ) e.Replacement = 'BlastSuitItem'; - else if ( (e.Replacee == 'BlueArmor') || (e.Replacee == 'FalconShield') || (e.Replacee == 'EnchantedShield') ) e.Replacement = 'WarArmorItem'; - } - - override void NetworkProcess( ConsoleEvent e ) - { - static const Class cbttypes[] = {"RedShell","GreenShell","BlueShell","PurpleShell"}; - if ( e.Name ~== "swwmgesture" ) - { - if ( (e.player == -1) || !playeringame[e.player] || !players[e.player].mo ) return; - let mo = players[e.player].mo; - if ( (mo.Health <= 0) || !(mo is 'Demolitionist') ) return; - switch ( e.Args[0] ) - { - case 1: - SWWMGesture.SetGesture(mo,1); - break; - case 2: - SWWMGesture.SetGesture(mo,2); - break; - default: - SWWMGesture.SetGesture(mo,0); - break; - } - } - if ( e.IsManual ) return; - if ( e.Name.Left(14) ~== "swwmstoregive." ) - { - Class item = e.Name.Mid(14); - if ( !item ) return; - if ( SWWMCredits.Take(players[e.Args[0]],e.Args[1]) ) - { - players[e.Args[0]].mo.GiveInventory(item,e.Args[2],true); - if ( item is 'Weapon' ) players[e.Args[0]].mo.A_SelectWeapon((Class)(item)); - } - } - else if ( e.Name.Left(10) ~== "swwmtrade." ) - { - Class item = e.Name.Mid(10); - if ( !item ) return; - let def = GetDefaultByType(item); - int amt = def.Amount; - // if it's an ammo, check the largest unit givable - if ( item is 'Ammo' ) - { - for ( int i=0; i)(AllActorClasses[i]); - if ( !a || (a.GetParentClass() != item) || (GetDefaultByType(a).Amount < amt) ) continue; - amt = GetDefaultByType(a).Amount; - } - } - Inventory ritm = players[e.Args[1]].mo.FindInventory(item); - if ( ritm ) - { - int maxgive = ritm.MaxAmount-ritm.Amount; - if ( amt > maxgive ) amt = maxgive; - } - else if ( amt > def.MaxAmount ) amt = def.MaxAmount; - bool rslt = false; - if ( (amt > 0) && players[e.Args[1]].mo.GiveInventory(item,amt,true) ) - { - players[e.Args[0]].mo.TakeInventory(item,amt); - // add to history - SWWMTradeHistory.RegisterSend(players[e.Args[0]],players[e.Args[1]],item,amt); - SWWMTradeHistory.RegisterReceive(players[e.Args[1]],players[e.Args[0]],item,amt); - // add messages - if ( e.Args[0] == consoleplayer ) Console.Printf(StringTable.Localize("$SWWM_MSGSENT"),amt,def.GetTag(),players[e.Args[1]].GetUserName()); - if ( e.Args[1] == consoleplayer ) Console.Printf(StringTable.Localize("$SWWM_MSGRECV"),players[e.Args[0]].GetUserName(),amt,def.GetTag()); - rslt = true; - } - if ( e.Args[0] == consoleplayer ) - { - let t = new("MenuTransaction"); - t.uid = e.Args[2]; - t.type = MenuTransaction.TT_ITEMSEND; - t.result = rslt; - t.used = item; - t.usedup = (players[e.Args[1]].mo.CountInv(item)<=0); - checklist.Push(t); - } - } - else if ( e.Name.Left(17) ~== "swwmmarkloreread." ) - { - let l = SWWMLoreLibrary.Find(players[e.Args[0]]); - let idx = l.FindEntry(e.Name.Mid(17)); - l.MarkRead(idx); - } - else if ( e.Name.Left(12) ~== "swwmuseitem." ) - { - Class item = e.Name.Mid(12); - if ( !item ) return; - let i = players[e.Args[0]].mo.FindInventory(item); - if ( !i ) return; - bool rslt = players[e.Args[0]].mo.UseInventory(i); - if ( e.Args[0] == consoleplayer ) - { - let t = new("MenuTransaction"); - t.uid = e.Args[1]; - t.type = MenuTransaction.TT_ITEMUSE; - let w = (Class)(item); - if ( w ) t.result = (players[e.Args[0]].PendingWeapon==Weapon(i)); - else t.result = rslt; - t.used = item; - t.usedup = (!i||(i.Amount<=0)); - checklist.Push(t); - } - } - else if ( e.Name.Left(13) ~== "swwmdropitem." ) - { - Class item = e.Name.Mid(13); - if ( !item ) return; - let i = players[e.Args[0]].mo.FindInventory(item); - if ( !i ) return; - int amt = i.default.Amount; - // if it's an ammo, check the largest unit givable - if ( i is 'Ammo' ) - { - for ( int i=0; i)(AllActorClasses[i]); - if ( !a || (a.GetParentClass() != item) || (GetDefaultByType(a).Amount < amt) ) continue; - amt = GetDefaultByType(a).Amount; - } - } - if ( amt > i.Amount ) amt = i.Amount; - let drop = players[e.Args[0]].mo.DropInventory(i,amt); - // add some random velocity so multiple drops don't get bunched together - if ( drop ) drop.vel += (Actor.RotateVector((FRandom[Junk](-1.5,.5),FRandom[Junk](-2.5,2.5)),players[e.Args[0]].mo.angle),FRandom[Junk](2.,5.)); - if ( e.Args[0] == consoleplayer ) - { - let t = new("MenuTransaction"); - t.uid = e.Args[1]; - t.type = MenuTransaction.TT_ITEMDROP; - t.used = item; - t.result = drop; - t.usedup = (!i||(i.Amount<=0)); - checklist.Push(t); - } - } - else if ( e.Name ~== "swwmkoraxline" ) - { - if ( consoleplayer != e.Args[1] ) return; - switch ( e.Args[0] ) - { - case 0: - AddOneliner("koraxgreet",3,60); - break; - case 1: - AddOneliner("koraxblood",3,150); - break; - case 2: - AddOneliner("koraxgame",3,120); - break; - case 3: - AddOneliner("koraxworship",3,80); - break; - case 4: - AddOneliner("koraxmasters",3,90); - break; - } - } - else if ( e.Name.Left(16) ~== "swwmremoteliner." ) - { - if ( consoleplayer == e.Args[0] ) return; - if ( !CVar.GetCVar('swwm_othervoice',players[consoleplayer]).GetBool() ) return; - if ( CVar.GetCVar('swwm_mutevoice',players[consoleplayer]).GetInt() >= e.Args[1] ) return; - players[e.Args[0]].mo.A_StartSound(e.Name.Mid(16),CHAN_DEMOVOICE,attenuation:.5); - } - else if ( e.Name.Left(19) ~== "swwmremotelinertxt." ) - { - if ( consoleplayer == e.Args[0] ) return; - if ( !CVar.GetCVar('swwm_othervoice',players[consoleplayer]).GetBool() ) return; - if ( CVar.GetCVar('swwm_mutevoice',players[consoleplayer]).GetInt() >= e.Args[1] ) return; - double dist = players[consoleplayer].Camera.Distance3D(players[e.Args[0]].mo); - if ( dist < 2000 ) - Console.Printf("\cx%s\cx: %s\c-",players[e.Args[0]].GetUserName(),StringTable.Localize(e.Name.Mid(19))); - } - else if ( e.Name ~== "swwmmoneycheat" ) - { - if ( consoleplayer == e.Args[0] ) - { - Console.Printf("\cfLOADSAMONEY!\c-"); - S_StartSound("menu/buyinv",CHAN_ITEM,CHANF_UI); - S_StartSound("misc/emone",CHAN_VOICE,CHANF_UI); - } - SWWMCredits.Give(players[e.Args[0]],1000000000); - SWWMScoreObj.Spawn(1000000000,players[e.Args[0]].mo.Vec3Offset(0,0,players[e.Args[0]].mo.Height/2)); - } - else if ( e.Name ~== "swwmlorecheat" ) - { - // look up all lore files - for ( int l=0; l qs; - qs.Clear(); - String rite = e.Name.Mid(8); - rite.Split(qs,",",TOK_SKIPEMPTY); - for ( int i=0; i 3) ) continue; - cbt.reloadqueue.Push(cbttypes[qi]); - } - cbt.waitreload = false; - } - else if ( e.Name ~== "swwmcleartransaction" ) - { - if ( e.Args[1] != consoleplayer ) return; - for ( int i=0; i0)?demo.lastdamage:Random[Flicker](60,80); - int lastdmgtic = (demo.Health>0)?demo.lastdamagetic:(gametic+Random[Flicker](30,20)); - double noiz = min(lastdmg*.09*max(0,(lastdmgtic-(gametic+e.Fractic))/35.),.5); - Shader.SetUniform1f(p,"Grain","ni",noiz); - noiz = min(lastdmg*.08*max(0,(lastdmgtic-(gametic+e.Fractic))/35.),.8); - Shader.SetUniform1f(p,"Glitch","str1",noiz); - noiz = min(lastdmg*.03*max(0,(lastdmgtic-(gametic+e.Fractic))/35.),3.5); - Shader.SetUniform1f(p,"Glitch","str2",noiz); - Shader.SetEnabled(p,"ZoomBlur",true); - if ( !demo.InStateSequence(demo.CurState,demo.FindState("Dash")) ) - { - Shader.SetEnabled(p,"ZoomBlur",false); - return; - } - Vector3 vel = demo.vel+demo.dashdir*demo.dashboost; - double baumpu = max(0.,(demo.bumptic-(gametic+e.Fractic))/35.); - vel += demo.dashdir*baumpu; - double spd = vel.length(); - Vector3 worlddir = vel/spd; - Shader.SetUniform1f(p,"ZoomBlur","Fade",clamp((spd-20.)/60.,0.,.75)); - double str = min(spd/50.,10.); - Vector3 x, y, z; - [x, y, z] = swwm_CoordUtil.GetAxes(e.ViewPitch,e.ViewAngle,e.ViewRoll); - Vector3 reldir = (worlddir dot y, worlddir dot z, worlddir dot x); - Vector2 centerspot = (.5+reldir.x*.5,.5+reldir.y*.5); - if ( reldir.z < 0 ) - { - centerspot.x = 1.-centerspot.x; - centerspot.y = 1.-centerspot.y; - str *= -1; - } - Shader.SetUniform1f(p,"ZoomBlur","Str",str); - Shader.SetUniform2f(p,"ZoomBlur","CenterSpot",centerspot); - } - else - { - Shader.SetEnabled(p,"Glitch",false); - Shader.SetEnabled(p,"Grain",false); - Shader.SetEnabled(p,"ZoomBlur",false); - } - } - - static void DoFlash( Actor camera, Color c, int duration ) - { - // don't flash when paused - if ( menuactive && (menuactive != Menu.OnNoPause) ) return; - QueuedFlash qf = new("QueuedFlash"); - qf.duration = duration; - qf.c = c; - qf.tic = gametic; - qf.cam = camera; - let hnd = SWWMHandler(EventHandler.Find("SWWMHandler")); - if ( !hnd ) return; // not supposed to happen - hnd.flashes.push(qf); - } - - // Doom's explosions aren't fully 3D in their knockback - static void DoBlast( Actor Source, double ExplosionRadius, double MomentumTransfer, Actor ignoreme = null, bool forceblast = false ) - { - BlockThingsIterator bi = BlockThingsIterator.Create(Source,ExplosionRadius); - while ( bi.Next() ) - { - Actor a = bi.Thing; - // early checks for self and ignored actor (usually the instigator) - if ( !a || (a == ignoreme) || (a == Source) ) - continue; - // can't be affected - if ( !a.bSHOOTABLE && !a.bVULNERABLE ) - continue; - // no blasting if no radius dmg (unless forced) - if ( a.bNORADIUSDMG && !Source.bFORCERADIUSDMG && !forceblast ) - continue; - // massive, no knockback - if ( a.bDONTTHRUST || (a.Mass >= Actor.LARGE_MASS) ) - continue; - // check the DONTHARMCLASS/DONTHARMSPECIES flags - if ( !a.player && ((Source.bDONTHARMCLASS && (a.GetClass() == Source.GetClass())) || (Source.bDONTHARMSPECIES && (a.GetSpecies() == Source.GetSpecies()))) ) - continue; - // can we see it - if ( !Source.CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) - continue; - // make use of GetRadiusDamage to see if it's in range - if ( Source.GetRadiusDamage(a,int(MomentumTransfer),int(ExplosionRadius),oldradiusdmg:Source.bOLDRADIUSDMG) <= 0 ) - continue; - // perform our own blasting code, based partially on Unreal's HurtRadius - Vector3 midpoint = a.Vec3Offset(0,0,a.height*0.5); - Vector3 dir = Level.Vec3Diff(Source.pos,midpoint); - double dist = max(1,dir.length()); - double damagescale = 1-clamp((dist-a.radius)/ExplosionRadius,0.,1.); - // intersecting, randomize direction - if ( dir.length() <= double.epsilon ) - { - double ang = FRandom[DoBlast](0,360); - double pt = FRandom[DoBlast](-90,90); - dir = (cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt)); - } - dir = dir/dist; - DoKnockback(a,dir,MomentumTransfer*damagescale); - } - } - - // Same for this - static void DoKnockback( Actor Victim, Vector3 HitDirection, double MomentumTransfer ) - { - if ( !Victim ) - return; - if ( !Victim.bSHOOTABLE && !Victim.bVULNERABLE ) - return; - if ( Victim.bDONTTHRUST || (Victim.Mass >= Actor.LARGE_MASS) ) - return; - Vector3 Momentum = HitDirection*MomentumTransfer; - if ( (Victim.pos.z <= Victim.floorz) || !Victim.TestMobjZ() ) - Momentum.z = max(Momentum.z,.1*Momentum.length()); - Momentum /= Thinker.TICRATE*max(50,Victim.Mass); - Victim.vel += Momentum; - } - - // complete spherical and more accurate replacement of A_Explode - // 100% free of the buggery GZDoom's own splash damage has - static void DoExplosion( Actor Source, double Damage, double MomentumTransfer, double ExplosionRadius, double FullDamageRadius = 0., int flags = 0, Name DamageType = '', Actor ignoreme = null ) - { - // debug, display radius sphere - if ( swwm_explosiondebug ) - { - let s = Actor.Spawn("RadiusDebugSphere",Source.pos); - s.Scale *= ExplosionRadius; - s.SetShade((Damage>0)?"Green":"Blue"); - if ( FullDamageRadius > 0. ) - { - let s = Actor.Spawn("RadiusDebugSphere",Source.pos); - s.Scale *= FullDamageRadius; - s.SetShade("Red"); - } - } - if ( !(flags&DE_NOSPLASH) ) Source.CheckSplash(ExplosionRadius); - double brange = 1./(ExplosionRadius-FullDamageRadius); - Actor Instigator = (flags&DE_NOTMISSILE)?Source:Source.target; - BlockThingsIterator bi = BlockThingsIterator.Create(Source,ExplosionRadius*2); // test with doubled radius, just to be sure - while ( bi.Next() ) - { - Actor a = bi.Thing; - // early checks for self and ignored actor (usually the instigator) - if ( !a || (a == ignoreme) || (a == Source) ) - continue; - // can't be affected - if ( !a.bSHOOTABLE && !a.bVULNERABLE ) - continue; - // no blasting if no radius dmg (unless forced) - if ( a.bNORADIUSDMG && !Source.bFORCERADIUSDMG ) - continue; - // check the DONTHARMCLASS/DONTHARMSPECIES flags - if ( !a.player && ((Source.bDONTHARMCLASS && (a.GetClass() == Source.GetClass())) || (Source.bDONTHARMSPECIES && (a.GetSpecies() == Source.GetSpecies()))) ) - continue; - // can we see it - if ( !(flags&DE_THRUWALLS) && !Source.CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) - continue; - // intersecting? - if ( !SWWMUtility.SphereIntersect(a,Source.pos,ExplosionRadius) ) - continue; - // calculate factor - Vector3 dir = level.Vec3Diff(Source.pos,a.Vec3Offset(0,0,a.Height/2)); - double dist = dir.length(); - // intersecting, randomize direction - if ( dir.length() <= double.epsilon ) - { - double ang = FRandom[DoBlast](0,360); - double pt = FRandom[DoBlast](-90,90); - dir = (cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt)); - } - dir /= dist; - dist = clamp(dist-FullDamageRadius,0,min(dist,ExplosionRadius)); - double damagescale = 1.-clamp((dist-a.Radius)*brange,0.,1.); - double mm = MomentumTransfer*damagescale; - // no knockback if massive/unpushable - if ( (abs(mm) > 0.) && !a.bDORMANT && !a.bDONTTHRUST && (a.Mass < Actor.LARGE_MASS) ) - { - Vector3 Momentum = dir*mm; - if ( (a.pos.z <= a.floorz) || !a.TestMobjZ() ) - Momentum.z = max(Momentum.z,(flags&DE_EXTRAZTHRUST?.4:.1)*Momentum.length()); - Momentum /= Thinker.TICRATE*max(50,a.Mass); // prevent tiny things from getting yeeted at warp speed - a.vel += Momentum; - if ( (flags&DE_BLAST) && a.bCANBLAST && !a.bDONTBLAST ) a.bBLASTED = true; - } - // hit it - int dmg = int(Damage*damagescale); - if ( dmg <= 0 ) continue; // no harm - int ndmg = a.DamageMobj(Instigator,Source,dmg,(DamageType!='')?Source.DamageType:DamageType,DMG_EXPLOSION,atan2(-dir.y,-dir.x)); - if ( !(flags&DE_NOBLEED) ) a.TraceBleed((ndmg>0)?ndmg:dmg,Source); - } - // TODO destructible geometry support - } static void DoSwing( Actor target, Vector2 dir, double initial, double inc, int steps, int mode = 0, int delay = 0, double rmul = 1.0 ) { @@ -4331,189 +763,3 @@ Class SWWMHandler : EventHandler s.tstr = initial; } } - -// preload fonts and stuff -Class SWWMPreloader : StaticEventHandler -{ - override void OnRegister() - { - Font.GetFont('k6x8'); - Font.GetFont('k6x8Shaded'); - Font.GetFont('k6x8ShadedInverse'); - Font.GetFont('Miniwi'); - Font.GetFont('MiniwiShaded'); - Font.GetFont('MiniwiShadedInverse'); - Font.GetFont('MPlus'); - Font.GetFont('MPlusShaded'); - Font.GetFont('MPlusShadedInverse'); - Font.GetFont('Tewi'); - Font.GetFont('TewiShaded'); - Font.GetFont('TewiShadedInverse'); - } -} - -// Fancy crash effect -Class SWWMCrashHandler : StaticEventHandler -{ - ui bool wasinmap; - ui int timer; - - override void UiTick() - { - if ( (gamestate == GS_LEVEL) || (gamestate == GS_TITLELEVEL) ) - { - wasinmap = true; - timer = 0; - } - else if ( (gamestate == GS_FULLCONSOLE) && ((wasinmap && !players[consoleplayer].viewheight) || (timer > 0)) ) - { - wasinmap = false; - if ( timer == 1 ) - { - Console.Printf(TEXTCOLOR_GOLD.."Oopsie Woopsie!"..TEXTCOLOR_NORMAL); - let hnd = SWWMBrutalHandler(StaticEventHandler.Find("SWWMBrutalHandler")); - if ( hnd && hnd.detected ) - { - S_StartSound("crash/glass",CHAN_YOUDONEFUCKEDUP,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE); - S_StartSound("crash/glass",CHAN_YOUDONEFUCKEDUP,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE); - } - else S_StartSound("crash/crash",CHAN_YOUDONEFUCKEDUP,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE); - } - else if ( timer == 140 ) - { - Console.Printf(TEXTCOLOR_GOLD.."Looks like GZDoom made a fucky wucky! owo"..TEXTCOLOR_NORMAL); - S_StartSound("crash/curb",CHAN_YOUDONEFUCKEDUP,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE); - } - else if ( timer == 350 ) - { - let hnd = SWWMBrutalHandler(StaticEventHandler.Find("SWWMBrutalHandler")); - if ( hnd && hnd.detected ) Console.Printf(TEXTCOLOR_GOLD.."Don't blame me. Shouldn't have tried running this with Brutal Doom."..TEXTCOLOR_NORMAL); - else Console.Printf(TEXTCOLOR_GOLD.."If you didn't trigger it manually, it's best if you take a screenshot and show it to Marisa."..TEXTCOLOR_NORMAL); - Console.Printf(TEXTCOLOR_GOLD.."Version Information: %s"..TEXTCOLOR_NORMAL,StringTable.Localize("SWWM_MODVER")); - } - timer++; - } - } -} - -// >loading brutal doom with this -Class SWWMBrutalHandler : StaticEventHandler -{ - ui int timer; - ui TextureID scr; - bool detected; - - override void OnRegister() - { - for ( int i=0; iBrutal Doom"); - timer++; - } - else timer = 0; - } - - override void WorldTick() - { - if ( !detected ) return; - for ( int i=0; i vsize.x) || (tsize.y > vsize.y) ) - { - double sar = tsize.x/tsize.y; - if ( sar > ar ) vsize = (tsize.x,tsize.x/ar); - else if ( sar < ar ) vsize = (tsize.y*ar,tsize.y); - else vsize = tsize; - } - Screen.DrawTexture(scr,false,(vsize.x-tsize.x)/2.+FRandom[bdscreen](-1,1)*max(timer-40,0)**3*.000003,(vsize.y-tsize.y)/2.+FRandom[bdscreen](-1,1)*max(timer-40,0)**3*.000003,DTA_VirtualWidthF,vsize.x,DTA_VirtualHeightF,vsize.y,DTA_KeepRatio,true,DTA_Alpha,min(1.,timer/50.)); - Screen.Dim("Red",(timer/70.)-3.5,0,0,Screen.GetWidth(),Screen.GetHeight()); - } -} - -// HORNY -Class SWWMHDoomHandler : StaticEventHandler -{ - ui int timer; - ui TextureID scr; - bool detected; - - override void OnRegister() - { - for ( int i=0; i vsize.x) || (tsize.y > vsize.y) ) - { - double sar = tsize.x/tsize.y; - if ( sar > ar ) vsize = (tsize.x,tsize.x/ar); - else if ( sar < ar ) vsize = (tsize.y*ar,tsize.y); - else vsize = tsize; - } - Screen.DrawTexture(scr,false,(vsize.x-tsize.x)/2.,(vsize.y-tsize.y)/2.,DTA_VirtualWidthF,vsize.x,DTA_VirtualHeightF,vsize.y,DTA_KeepRatio,true); - } -} diff --git a/zscript/swwm_danmaku.zsc b/zscript/swwm_danmaku.zsc index b6d30c597..fa8006a53 100644 --- a/zscript/swwm_danmaku.zsc +++ b/zscript/swwm_danmaku.zsc @@ -84,7 +84,7 @@ Class EvisceratorChunk : Actor Radius 2; Height 2; Speed 50; - DamageFunction int(clamp((vel.length()-6)*.2+max(0,1.-lifetime)*5.,0,12)); + DamageFunction int(clamp((vel.length()-6)*.15+max(0,1.-lifetime)*3.,0,10)); DamageType 'shot'; BounceFactor 1.0; WallBounceFactor 1.0; @@ -237,7 +237,7 @@ Class EvisceratorChunk : Actor lasthit = victim; // don't knock back if already dead int oldamt = SWWMDamageAccumulator.GetAmount(victim); - if ( victim.health-oldamt > 0 ) SWWMHandler.DoKnockback(victim,vel.unit(),12000); + if ( victim.health-oldamt > 0 ) SWWMUtility.DoKnockback(victim,vel.unit(),12000); // gather damage SWWMDamageAccumulator.Accumulate(victim,GetMissileDamage(0,0),self,target,damagetype); int amt = SWWMDamageAccumulator.GetAmount(victim); @@ -359,7 +359,7 @@ Class EvisceratorProj : Actor A_SprayDecal("BigScorch",50); A_NoGravity(); A_SetScale(3.5); - SWWMHandler.DoExplosion(self,150,120000,200,80); + SWWMUtility.DoExplosion(self,150,120000,200,80); A_QuakeEx(6,6,6,20,0,1200,"",QF_RELATIVE|QF_SCALEDOWN,falloff:300,rollIntensity:.7); A_StartSound("eviscerator/shell",CHAN_WEAPON,attenuation:.5); A_StartSound("eviscerator/shell",CHAN_VOICE,attenuation:.3); @@ -612,7 +612,7 @@ Class Eviscerator : SWWMWeapon Vector3 x, y, z, x2, y2, z2, dir, origin; double a, s; [x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll); - SWWMHandler.DoKnockback(self,-x,25000.); + SWWMUtility.DoKnockback(self,-x,25000.); [x2, y2, z2] = swwm_CoordUtil.GetAxes(BulletSlope(),angle,roll); origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),10*x+3*y-5*z); for ( int i=0; i<40; i++ ) @@ -669,7 +669,7 @@ Class Eviscerator : SWWMWeapon Vector3 x, y, z, x2, y2, z2, dir, origin; double a, s; [x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll); - SWWMHandler.DoKnockback(self,-x,32000.); + SWWMUtility.DoKnockback(self,-x,32000.); [x2, y2, z2] = swwm_CoordUtil.GetAxes(BulletSlope(),angle,roll); origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),10*x+3*y-5*z); a = FRandom[Eviscerator](0,360); diff --git a/zscript/swwm_deepdarkimpact.zsc b/zscript/swwm_deepdarkimpact.zsc index c70502aba..237925354 100644 --- a/zscript/swwm_deepdarkimpact.zsc +++ b/zscript/swwm_deepdarkimpact.zsc @@ -31,7 +31,7 @@ Class AirBullet : FastProjectile if ( target == lasthit ) return 0; lasthit = target; } - SWWMHandler.DoKnockback(target,vel.unit(),10000); + SWWMUtility.DoKnockback(target,vel.unit(),10000); return damage; } override void Effect() @@ -54,7 +54,7 @@ Class AirBullet : FastProjectile s.alpha *= .2; } bAMBUSH = true; - SWWMHandler.DoExplosion(self,GetMissileDamage(0,0),0,50,ignoreme:target); + SWWMUtility.DoExplosion(self,GetMissileDamage(0,0),0,50,ignoreme:target); bAMBUSH = false; tcnt++; if ( tcnt < 2 ) return; @@ -72,9 +72,9 @@ Class AirBullet : FastProjectile dir /= dist; double mm = 8000000./max(20.,dist); if ( (target.pos.z > target.floorz) && target.TestMobjZ() ) mm *= 1.6; - SWWMHandler.DoKnockback(target,dir,mm); + SWWMUtility.DoKnockback(target,dir,mm); } - SWWMHandler.DoExplosion(self,0,100000,150,ignoreme:target); + SWWMUtility.DoExplosion(self,0,100000,150,ignoreme:target); A_QuakeEx(6,6,6,20,0,250,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:1.); A_StartSound("deepimpact/bullethit",CHAN_VOICE,CHANF_DEFAULT,1.,.3); A_SprayDecal("ImpactMark"); @@ -282,7 +282,7 @@ Class DeepImpact : SWWMWeapon invoker.clipcount = max(0,invoker.clipcount-3); Vector3 x, y, z, dir; [x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll); - SWWMHandler.DoKnockback(self,-x,2000.); + SWWMUtility.DoKnockback(self,-x,2000.); Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),10*x+2*y-3*z); DeepTracer t = new("DeepTracer"); t.ignoreme = self; @@ -348,7 +348,7 @@ Class DeepImpact : SWWMWeapon { wnorm /= wallhits; if ( (pos.z > floorz) && TestMobjZ() ) wnorm *= 2.4; - SWWMHandler.DoKnockback(self,wnorm.unit(),wnorm.length()*5000000.); + SWWMUtility.DoKnockback(self,wnorm.unit(),wnorm.length()*5000000.); } for ( int i=0; i 0 ) vel.z *= 0.98; + if ( CurState == ResolveState("Death") ) + { + deadtimer++; + if ( deadtimer > 300 ) A_FadeOut(0.05); + return; + } + } + States + { + Spawn: + XZW1 # 1 + { + angle += anglevel; + pitch += pitchvel; + roll += rollvel; + return A_JumpIf(pos.z<=floorz,"Death"); + } + Loop; + Death: + XZW1 # -1 + { + A_Stop(); + pitch = roll = 0; + } + Stop; + Dummy: + XZW1 ABCDEFGHIJ -1; + Stop; + } +} + // Collectable box (recycling of discarded "chance box" item) Class Chancebox : Actor { diff --git a/zscript/swwm_handler.zsc b/zscript/swwm_handler.zsc new file mode 100644 index 000000000..d20653637 --- /dev/null +++ b/zscript/swwm_handler.zsc @@ -0,0 +1,1713 @@ +// event handlers and whatnot + +// so apparently on restart after dying, static thinkers don't get reloaded until AFTER the player has spawned +// and the player calls GiveDefaultInventory even though it's all later discarded and they're given the saved inventory +// all I can ask is: WHY +Class SWWMExcuseMeWhatTheFuckHandler : StaticEventHandler +{ + bool reborn_hackfix; + int clear_hackfix; + + override void WorldTick() + { + if ( gamestate != GS_LEVEL ) return; + if ( clear_hackfix > 0 ) + { + clear_hackfix--; + if ( clear_hackfix <= 0 ) + reborn_hackfix = false; + } + } + + static bool WhyTheFuckIsThisAThing() + { + let hnd = SWWMExcuseMeWhatTheFuckHandler(StaticEventHandler.Find("SWWMExcuseMeWhatTheFuckHandler")); + if ( !hnd ) return false; + return hnd.reborn_hackfix; + } + + static void ThisIsSomeSeriousBullshit() + { + let hnd = SWWMExcuseMeWhatTheFuckHandler(StaticEventHandler.Find("SWWMExcuseMeWhatTheFuckHandler")); + if ( !hnd ) return; + hnd.reborn_hackfix = true; + hnd.clear_hackfix = 2; + } +} + +// preload fonts and stuff +Class SWWMPreloader : StaticEventHandler +{ + override void OnRegister() + { + Font.GetFont('k6x8'); + Font.GetFont('k6x8Shaded'); + Font.GetFont('k6x8ShadedInverse'); + Font.GetFont('Miniwi'); + Font.GetFont('MiniwiShaded'); + Font.GetFont('MiniwiShadedInverse'); + Font.GetFont('MPlus'); + Font.GetFont('MPlusShaded'); + Font.GetFont('MPlusShadedInverse'); + Font.GetFont('Tewi'); + Font.GetFont('TewiShaded'); + Font.GetFont('TewiShadedInverse'); + } +} + +// Fancy crash effect +Class SWWMCrashHandler : StaticEventHandler +{ + ui bool wasinmap; + ui int timer; + + override void UiTick() + { + if ( (gamestate == GS_LEVEL) || (gamestate == GS_TITLELEVEL) ) + { + wasinmap = true; + timer = 0; + } + else if ( (gamestate == GS_FULLCONSOLE) && ((wasinmap && !players[consoleplayer].viewheight) || (timer > 0)) ) + { + wasinmap = false; + if ( timer == 1 ) + { + Console.Printf(TEXTCOLOR_GOLD.."Oopsie Woopsie!"..TEXTCOLOR_NORMAL); + let hnd = SWWMBrutalHandler(StaticEventHandler.Find("SWWMBrutalHandler")); + if ( hnd && hnd.detected ) + { + S_StartSound("crash/glass",CHAN_YOUDONEFUCKEDUP,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE); + S_StartSound("crash/glass",CHAN_YOUDONEFUCKEDUP,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE); + } + else S_StartSound("crash/crash",CHAN_YOUDONEFUCKEDUP,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE); + } + else if ( timer == 140 ) + { + Console.Printf(TEXTCOLOR_GOLD.."Looks like GZDoom made a fucky wucky! owo"..TEXTCOLOR_NORMAL); + S_StartSound("crash/curb",CHAN_YOUDONEFUCKEDUP,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE); + } + else if ( timer == 350 ) + { + let hnd = SWWMBrutalHandler(StaticEventHandler.Find("SWWMBrutalHandler")); + if ( hnd && hnd.detected ) Console.Printf(TEXTCOLOR_GOLD.."Don't blame me. Shouldn't have tried running this with Brutal Doom."..TEXTCOLOR_NORMAL); + else Console.Printf(TEXTCOLOR_GOLD.."If you didn't trigger it manually, it's best if you take a screenshot and show it to Marisa."..TEXTCOLOR_NORMAL); + Console.Printf(TEXTCOLOR_GOLD.."Version Information: %s"..TEXTCOLOR_NORMAL,StringTable.Localize("SWWM_MODVER")); + } + timer++; + } + } +} + +// Handler responsible for item replacements and whatever else +Class SWWMHandler : EventHandler +{ + transient String oneliner, onelinersnd; + transient int onelinertic, onelinerspan, onelinerlevel; + transient int lastlock, lastcombat; + transient Array combatactors; + transient Array combattics; + transient int highesttic; + transient Array flashes; + transient Array lastlines; + transient int lastpickuptic[MAXPLAYERS]; + SWWMCombatTracker trackers; + SWWMScoreObj scorenums, damnums; + SWWMInterest intpoints; + int trackers_cnt, scorenums_cnt, damnums_cnt, intpoints_cnt; + bool tookdamage[MAXPLAYERS]; + int spreecount[MAXPLAYERS]; + int lastkill[MAXPLAYERS]; + int multilevel[MAXPLAYERS]; + int lastitemcount[MAXPLAYERS]; + bool allkills, allitems, allsecrets; + // for money cheat + transient ui int kcode, lcode; + + // heal/armor flashes need to be handled here so they don't stack + transient int hflash[MAXPLAYERS], aflash[MAXPLAYERS]; + + // for menu events + transient Array checklist; + + transient CVar mutevoice, accdamage; + transient ui CVar useshaders; + transient CVar lang; + transient String curlang; + transient CVar funtags; + transient bool curfuntags; + + // optimization + OnFire fires; + int fires_cnt; + + static void HealthFlash( int p ) + { + let hnd = SWWMHandler(EventHandler.Find("SWWMHandler")); + if ( !hnd || (p == -1) ) return; + hnd.hflash[p] = gametic+5; + } + + static void ArmorFlash( int p ) + { + let hnd = SWWMHandler(EventHandler.Find("SWWMHandler")); + if ( !hnd || (p == -1) ) return; + hnd.aflash[p] = gametic+5; + } + + static int AddOneliner( String type, int level, int delay = 5 ) + { + // only Demolitionist can play voice lines + if ( !(players[consoleplayer].mo is 'Demolitionist') ) + return 0; + let hnd = SWWMHandler(EventHandler.Find("SWWMHandler")); + if ( !hnd ) return 0; + String voicetype = CVar.GetCVar('swwm_voicetype',players[consoleplayer]).GetString(); + // suppress non-rage comments when ragekit is active, only screaming allowed + if ( players[consoleplayer].mo.FindInventory("RagekitPower") && (type != "ragekit") ) return 0; + int whichline; + String testme = String.Format("SWWM_SUBS_%s_N%s",voicetype.MakeUpper(),type.MakeUpper()); + String locme = StringTable.Localize(testme,false); + int countem; + if ( testme == locme ) countem = 0; + else countem = locme.ToInt(); + if ( countem == 0 ) + { + if ( voicetype ~== "default" ) return 0; + // retry with the default voicetype + voicetype = "default"; + testme = String.Format("SWWM_SUBS_DEFAULT_N%s",type.MakeUpper()); + locme = StringTable.Localize(testme,false); + if ( testme == locme ) countem = 0; + else countem = locme.ToInt(); + if ( countem == 0 ) return 0; + } + // check last line so we don't repeat + int last = 0, ent; + for ( int i=0; i 0 ) + { + whichline = Random[DemoLines](1,countem-1); + if ( whichline >= last ) whichline++; + hnd.lastlines[ent].lineno = whichline; + } + else + { + whichline = Random[DemoLines](1,countem); + let lst = new("LastLine"); + lst.type = type; + lst.lineno = whichline; + hnd.lastlines.Push(lst); + } + hnd.oneliner = String.Format("$SWWM_SUBS_%s_%s%d",voicetype.MakeUpper(),type.MakeUpper(),whichline); + hnd.onelinersnd = String.Format("voice/%s/%s%d",voicetype,type,whichline); + hnd.onelinertic = gametic+delay; + hnd.onelinerspan = int(S_GetLength(hnd.onelinersnd)*Thinker.TICRATE); + hnd.onelinerlevel = level; + return hnd.onelinertic+hnd.onelinerspan; + } + + override void OnRegister() + { + // oneliner RNG must be relative to consoleplayer + SetRandomSeed[DemoLines](Random[DemoLines]()+consoleplayer+MSTime()); + } + + private static Vector3 UseLinePos( Line l ) + { + Vector3 al, ah, bl, bh; + if ( !l.sidedef[1] ) + { + // just the whole line + al = (l.v1.p,l.frontsector.floorplane.ZatPoint(l.v1.p)); + ah = (l.v1.p,l.frontsector.ceilingplane.ZatPoint(l.v1.p)); + bl = (l.v2.p,l.frontsector.floorplane.ZatPoint(l.v2.p)); + bh = (l.v2.p,l.frontsector.ceilingplane.ZatPoint(l.v2.p)); + return (al+ah+bl+bh)*.25; + } + SecPlane highestfloor, lowestfloor, lowestceiling, highestceiling; + if ( (l.frontsector.floorplane.ZatPoint(l.v1.p) > l.backsector.floorplane.ZatPoint(l.v1.p)) + && (l.frontsector.floorplane.ZatPoint(l.v2.p) > l.backsector.floorplane.ZatPoint(l.v2.p)) ) + { + highestfloor = l.frontsector.floorplane; + lowestfloor = l.backsector.floorplane; + } + else + { + highestfloor = l.backsector.floorplane; + lowestfloor = l.frontsector.floorplane; + } + if ( (l.frontsector.ceilingplane.ZatPoint(l.v1.p) < l.backsector.ceilingplane.ZatPoint(l.v1.p)) + && (l.frontsector.ceilingplane.ZatPoint(l.v2.p) < l.backsector.ceilingplane.ZatPoint(l.v2.p)) ) + { + lowestceiling = l.frontsector.ceilingplane; + highestceiling = l.backsector.ceilingplane; + } + else + { + lowestceiling = l.backsector.ceilingplane; + highestceiling = l.frontsector.ceilingplane; + } + // try to guess what the part that triggers this is + if ( l.Activation&SPAC_Cross ) + { + // pick the "intersection" + al = (l.v1.p,highestfloor.ZatPoint(l.v1.p)); + ah = (l.v1.p,lowestceiling.ZatPoint(l.v1.p)); + bl = (l.v2.p,highestfloor.ZatPoint(l.v2.p)); + bh = (l.v2.p,lowestceiling.ZatPoint(l.v2.p)); + return (al+ah+bl+bh)*.25; + } + // check if lower part available + al = (l.v1.p,lowestfloor.ZatPoint(l.v1.p)); + ah = (l.v1.p,highestfloor.ZatPoint(l.v1.p)); + bl = (l.v2.p,lowestfloor.ZatPoint(l.v2.p)); + bh = (l.v2.p,highestfloor.ZatPoint(l.v2.p)); + if ( ((al-ah).length() > 0) && ((bl-bh).length() > 0) ) + return (al+ah+bl+bh)*.25; + // check if upper part available + al = (l.v1.p,lowestceiling.ZatPoint(l.v1.p)); + ah = (l.v1.p,highestceiling.ZatPoint(l.v1.p)); + bl = (l.v2.p,lowestceiling.ZatPoint(l.v2.p)); + bh = (l.v2.p,highestceiling.ZatPoint(l.v2.p)); + if ( ((al-ah).length() > 0) && ((bl-bh).length() > 0) ) + return (al+ah+bl+bh)*.25; + // check for 3d floors + bool floorfound = false; + Vector3 fal, fah, fbl, fbh; + for ( int i=0; i ah.z) && (fbh.z > bh.z) && (fal.z > al.z) && (fbl.z > bl.z) ) continue; + al = fal; + ah = fah; + bl = fbl; + bh = fbh; + floorfound = true; + } + if ( floorfound ) return (al+ah+bl+bh)*.25; + for ( int i=0; i ah.z) && (fbh.z > bh.z) && (fal.z > al.z) && (fbl.z > bl.z) ) continue; + al = fal; + ah = fah; + bl = fbl; + bh = fbh; + floorfound = true; + } + if ( floorfound ) return (al+ah+bl+bh)*.25; + // check for midtex + if ( !l.sidedef[0].GetTexture(1).IsNull() ) + { + double ofs = l.sidedef[0].GetTextureYOffset(1); + Vector2 siz = TexMan.GetScaledSize(l.sidedef[0].GetTexture(1)); + Vector2 tofs = TexMan.GetScaledOffset(l.sidedef[0].GetTexture(1)); + ofs += tofs.y; + ofs *= l.sidedef[0].GetTextureYScale(1); + siz.y *= l.sidedef[0].GetTextureYScale(1); + if ( l.flags&Line.ML_DONTPEGBOTTOM ) + { + al = (l.v1.p,highestfloor.ZAtPoint(l.v1.p)+ofs); + bl = (l.v2.p,highestfloor.ZAtPoint(l.v2.p)+ofs); + ah = al+(0,0,siz.y); + bh = bl+(0,0,siz.y); + } + else + { + ah = (l.v1.p,lowestceiling.ZAtPoint(l.v1.p)+ofs); + bh = (l.v2.p,lowestceiling.ZAtPoint(l.v2.p)+ofs); + al = ah-(0,0,siz.y); + bl = bh-(0,0,siz.y); + } + return (al+ah+bl+bh)*.25; + } + if ( !l.sidedef[1].GetTexture(1).IsNull() ) + { + double ofs = l.sidedef[1].GetTextureYOffset(1); + Vector2 siz = TexMan.GetScaledSize(l.sidedef[1].GetTexture(1)); + Vector2 tofs = TexMan.GetScaledOffset(l.sidedef[1].GetTexture(1)); + ofs += tofs.y; + ofs *= l.sidedef[1].GetTextureYScale(1); + siz.y *= l.sidedef[1].GetTextureYScale(1); + if ( l.flags&Line.ML_DONTPEGBOTTOM ) + { + al = (l.v1.p,highestfloor.ZAtPoint(l.v1.p)+ofs); + bl = (l.v2.p,highestfloor.ZAtPoint(l.v2.p)+ofs); + ah = al+(0,0,siz.y); + bh = bl+(0,0,siz.y); + } + else + { + ah = (l.v1.p,lowestceiling.ZAtPoint(l.v1.p)+ofs); + bh = (l.v2.p,lowestceiling.ZAtPoint(l.v2.p)+ofs); + al = ah-(0,0,siz.y); + bl = bh-(0,0,siz.y); + } + return (al+ah+bl+bh)*.25; + } + // just use the intersection + al = (l.v1.p,highestfloor.ZatPoint(l.v1.p)); + ah = (l.v1.p,lowestceiling.ZatPoint(l.v1.p)); + bl = (l.v2.p,highestfloor.ZatPoint(l.v2.p)); + bh = (l.v2.p,lowestceiling.ZatPoint(l.v2.p)); + return (al+ah+bl+bh)*.25; + } + + // level end stats + override void WorldUnloaded( WorldEvent e ) + { + let ti = ThinkerIterator.Create("SWWMStats",Thinker.STAT_STATIC); + SWWMStats s; + while ( s = SWWMStats(ti.Next()) ) + s.AddLevelStats(); + PlayerInfo p = players[consoleplayer]; + Shader.SetEnabled(p,"ZoomBlur",false); + Shader.SetEnabled(p,"RagekitShader",false); + Shader.SetEnabled(p,"GhostShader",false); + Shader.SetEnabled(p,"InvinciShader",false); + Shader.SetEnabled(p,"Glitch",false); + Shader.SetEnabled(p,"Grain",false); + } + + override void WorldLoaded( WorldEvent e ) + { + if ( level.levelname ~== "Modder Test Map" ) level.ReplaceTextures("-noflat-","kinstile",0); + if ( !mutevoice ) mutevoice = CVar.GetCVar('swwm_mutevoice',players[consoleplayer]); + if ( !e.IsSaveGame && !e.IsReopen && (gamestate != GS_TITLELEVEL) ) + AddOneliner("mapstart",3); + if ( !e.IsSaveGame && !e.IsReopen ) + { + // for skipping over merged exit lines (sharing vertices) + Array skipme; + skipme.Clear(); + // find exit lines, and use lines that aren't exits + for ( int i=0; i 0 ) + { + if ( onelinerlevel > mutevoice.GetInt() ) + players[consoleplayer].mo.A_StartSound(onelinersnd,CHAN_DEMOVOICE,CHANF_DEFAULT,1.,ATTN_NONE); + SendNetworkEvent("swwmremoteliner."..onelinersnd,consoleplayer,onelinerlevel); + } + onelinertic = 0; + onelinerspan = 0; + } + for ( int i=0; i= gametic ) continue; + flashes.Delete(i); + i--; + } + // countable item scoring + for ( int i=0; i lastitemcount[i] ) + { + int score = 25*(players[i].itemcount-lastitemcount[i]); + if ( (level.total_items == level.found_items) && !allitems ) + { + allitems = true; + Console.Printf(StringTable.Localize("$SWWM_LASTITEM"),players[i].GetUserName(),2500); + score += 2475; + } + SWWMCredits.Give(players[i],score); + SWWMScoreObj.Spawn(score,players[i].mo.Vec3Offset(0,0,players[i].mo.Height/2)); + lastitemcount[i] = players[i].itemcount; + } + } + // combat tracking + // 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 either actor can't see the other + if ( (a.target != players[consoleplayer].mo) + || !a.CheckSight(players[consoleplayer].mo) + || !players[consoleplayer].mo.CheckSight(a) ) continue; + // is it already in? + bool addme = true; + for ( int i=0; i highesttic+700)) ) + lastcombat = AddOneliner("fightstart",1,10); + } + + private bool HexenMap40() + { + if ( level.GetChecksum() ~== "2A6C4235B942467D25FD50D5B313E67A" ) return true; // 1.1 + if ( level.GetChecksum() ~== "1C5DE5A921DEE405E98E7E09D9829387" ) return true; // 1.0 + if ( level.GetChecksum() ~== "EFAFE59092DE5E613562ACF52B86C37F" ) return true; // beta + return false; + } + + override void WorldThingDied( WorldEvent e ) + { + if ( e.Thing.default.bBOSS && !Random[GoldDrop](0,2) ) + { + let g = Actor.Spawn("GoldShell",e.Thing.Vec3Offset(0,0,e.Thing.Height/2)); + double ang = FRandom[SpareShells](0,360); + g.vel.xy = (cos(ang),sin(ang))*FRandom[SpareShells](.4,.8); + g.vel.z = FRandom[SpareShells](2.,4.); + } + // Korax instakill + if ( (e.Thing is 'Korax') && !e.Thing.special2 && HexenMap40() ) + { + e.Thing.special2 = 1; + // terminate the monster closet scripts, open all the + // doors ourselves + level.ExecuteSpecial(ACS_Terminate,e.Thing,null,false,220); + level.ExecuteSpecial(ACS_Terminate,e.Thing,null,false,220); + level.ExecuteSpecial(ACS_Terminate,e.Thing,null,false,221); + level.ExecuteSpecial(ACS_Terminate,e.Thing,null,false,255); + level.ExecuteSpecial(Door_Open,e.Thing,null,false,10,16); + level.ExecuteSpecial(Door_Open,e.Thing,null,false,11,16); + level.ExecuteSpecial(Door_Open,e.Thing,null,false,12,16); + level.ExecuteSpecial(Door_Open,e.Thing,null,false,13,16); + level.ExecuteSpecial(Door_Open,e.Thing,null,false,14,16); + level.ExecuteSpecial(Door_Open,e.Thing,null,false,10,16); + // keep the portal closed, you can't leave unless you + // kill everyone else + let t = new("UglyBoyGetsFuckedUp"); + t.ChangeStatNum(Thinker.STAT_USER); + } + } + + override void WorldThingDamaged( WorldEvent e ) + { + if ( e.Damage > 0 ) + { + if ( !accdamage ) accdamage = CVar.GetCVar('swwm_accdamage',players[consoleplayer]); + bool spawnme = true; + if ( accdamage.GetBool() ) + { + // 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),Font.CR_RED,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 + ForcedFallerDamager.SetInstigator(e.Thing,e.DamageSource); + // stats + if ( e.Thing.player ) + { + tookdamage[e.Thing.PlayerNumber()] = true; + let s = SWWMStats.Find(e.Thing.player); + 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); + s.AddDamageDealt(e.Damage); + if ( e.Damage > s.topdealt ) s.topdealt = e.Damage; + } + } + if ( e.DamageSource && (e.DamageSource != e.Thing) ) + { + if ( (e.DamageSource.bISMONSTER || e.DamageSource.player || (e.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||e.DamageSource.bBOSSDEATH)?2:4) ) 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; + } + } + } + if ( (e.Thing.Health > 0) || e.Thing.bKilled || e.Thing.bCorpse ) return; + if ( !e.Thing.player && !e.Thing.bIsMonster && !e.Thing.bCountKill && !(e.Thing is 'ScriptedMarine') ) return; + if ( (e.DamageSource && e.DamageSource.player && (e.DamageSource != e.Thing)) ) + { + let s = SWWMStats.Find(e.DamageSource.player); + if ( s ) + { + s.kills++; + s.AddWeaponKill(e.Inflictor,e.Thing); + } + if ( e.DamageSource == players[consoleplayer].mo ) + { + highesttic = gametic; + if ( !lastcombat || (gametic > lastcombat+40) ) + { + if ( e.Thing.IsFriend(e.DamageSource) || SWWMUtility.IsCivilian(e.Thing) ) + lastcombat = AddOneliner("friendkill",1,5); + else if ( (!lastcombat || (gametic > lastcombat+100)) && !Random[DemoLines](0,(e.Thing.bBOSS||e.Thing.bBOSSDEATH)?2:5) ) + lastcombat = AddOneliner("scorekill",1,15); + } + } + // no credits unless it's a counted kill or marine (that isn't friendly) + if ( e.Thing.IsFriend(e.DamageSource) || (!e.Thing.default.bCountKill && !(e.Thing is 'ScriptedMarine')) ) + return; + int pnum = e.DamageSource.PlayerNumber(); + if ( level.maptime < (lastkill[pnum]+5*Thinker.TICRATE) ) + multilevel[pnum]++; + else multilevel[pnum] = 0; + if ( s && (multilevel[pnum]+1 > s.mkill) ) + s.mkill = multilevel[pnum]+1; + lastkill[pnum] = level.maptime; + // scoring + int score = min(1000,int(ceil(e.Thing.SpawnHealth()*.25)*10)); + SWWMScoreObj scr = null; + if ( e.DamageSource.player == players[consoleplayer] ) + scr = SWWMScoreObj.Spawn(score,e.Thing.Vec3Offset(0,0,e.Thing.Height/2)); + int ofs = 0; + if ( e.DamageType == 'Push' ) + { + score += 1000; + if ( scr ) + { + scr.xscore[ofs] = 0; + scr.xtcolor[ofs] = Font.CR_FIRE; + scr.xstr[ofs] = StringTable.Localize("$SWWM_SHAMEFUL"); + scr.xcnt = ++ofs; + } + } + if ( ((e.Thing.Health <= e.Thing.GetGibHealth()) || (e.DamageSource.bEXTREMEDEATH) || (e.Inflictor && e.Inflictor.bEXTREMEDEATH) || (e.DamageType == 'Extreme')) && !e.DamageSource.bNOEXTREMEDEATH && (!e.Inflictor || !e.Inflictor.bNOEXTREMEDEATH) ) + { + score = int(score*1.25); + if ( scr ) + { + scr.xscore[ofs] = 0; + scr.xtcolor[ofs] = Font.CR_FIRE; + 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.xtcolor[ofs] = Font.CR_FIRE; + scr.xstr[ofs] = StringTable.Localize("$SWWM_MULTIKILL"); + scr.xcnt = ++ofs; + } + } + 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]+1; + scr.xtcolor[ofs] = Font.CR_FIRE; + scr.xstr[ofs] = StringTable.Localize("$SWWM_SPREEKILL"); + scr.xcnt = ++ofs; + } + } + if ( e.Thing.bBOSS ) + { + score += 5000; + if ( scr ) + { + scr.xscore[ofs] = 0; + scr.xtcolor[ofs] = Font.CR_FIRE; + scr.xstr[ofs] = StringTable.Localize("$SWWM_BOSSKILL"); + scr.xcnt = ++ofs; + } + } + SWWMCredits.Give(e.DamageSource.player,score); + if ( scr ) scr.score = score; // update final score + if ( (level.killed_monsters+1 == level.total_monsters) && !allkills ) + { + allkills = true; + SWWMCredits.Give(e.DamageSource.player,2000); + Console.Printf(StringTable.Localize("$SWWM_LASTMONSTER"),e.DamageSource.player.GetUserName(),2000); + SWWMScoreObj.Spawn(2000,e.DamageSource.Vec3Offset(0,0,e.DamageSource.Height/2)); + } + spreecount[pnum]++; + if ( s && (spreecount[pnum] > s.skill) && !tookdamage[pnum] ) + s.skill = spreecount[pnum]; + } + } + + private void DoKeyTagFix( Actor a ) + { + if ( a is 'RedCard' ) a.SetTag("$T_REDCARD"); + else if ( a is 'BlueCard' ) a.SetTag("$T_BLUECARD"); + else if ( a is 'YellowCard' ) a.SetTag("$T_YELLOWCARD"); + else if ( a is 'RedSkull' ) a.SetTag("$T_REDSKULL"); + else if ( a is 'BlueSkull' ) a.SetTag("$T_BLUESKULL"); + else if ( a is 'YellowSkull' ) a.SetTag("$T_YELLOWSKULL"); + else if ( a is 'KeyYellow' ) a.SetTag("$T_YELLOWKEY"); + else if ( a is 'KeyGreen' ) a.SetTag("$T_GREENKEY"); + else if ( a is 'KeyBlue' ) a.SetTag("$T_BLUEKEY"); + else if ( a.GetClassName() == 'KeyRed' ) a.SetTag("$T_REDKEY"); + else if ( a is 'KeySteel' ) a.SetTag("$T_KEYSTEEL"); + else if ( a is 'KeyCave' ) a.SetTag("$T_KEYCAVE"); + else if ( a is 'KeyAxe' ) a.SetTag("$T_KEYAXE"); + else if ( a is 'KeyFire' ) a.SetTag("$T_KEYFIRE"); + else if ( a is 'KeyEmerald' ) a.SetTag("$T_KEYEMERALD"); + else if ( a is 'KeyDungeon' ) a.SetTag("$T_KEYDUNGEON"); + else if ( a is 'KeySilver' ) a.SetTag("$T_KEYSILVER"); + else if ( a is 'KeyRusted' ) a.SetTag("$T_KEYRUSTED"); + else if ( a is 'KeyHorn' ) a.SetTag("$T_KEYHORN"); + else if ( a is 'KeySwamp' ) a.SetTag("$T_KEYSWAMP"); + else if ( a is 'KeyCastle' ) a.SetTag("$T_KEYCASTLE"); + } + + // tempfix keys have no tags + static void KeyTagFix( Actor a ) + { + let hnd = SWWMHandler(Find("SWWMHandler")); + if ( hnd ) hnd.DoKeyTagFix(a); + } + + // 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; + break; + } + } + + override void WorldThingSpawned( WorldEvent e ) + { + if ( e.Thing is 'Inventory' ) + CopyFloatBob(e.Thing); + if ( swwm_doomfall && e.Thing.bISMONSTER && !level.monsterfallingdamage ) + ForcedFallerDamager.TrackBody(e.Thing); + if ( e.Thing is 'Key' ) + { + DoKeyTagFix(e.Thing); + SWWMInterest.Spawn(thekey:Key(e.Thing)); + } + else if ( e.Thing is 'BossBrain' ) + { + e.Thing.SetTag("$FN_BOSSBRAIN"); + e.Thing.A_SetSize(20,70); + } + else if ( e.Thing is 'CommanderKeen' ) e.Thing.SetTag("$FN_KEEN"); + if ( !swwm_notrack && (e.Thing.bSHOOTABLE || e.Thing.bISMONSTER) && !(e.Thing is 'LampMoth') && !(e.Thing is 'CompanionLamp') ) SWWMCombatTracker.Spawn(e.Thing); + } + + override void PostUiTick() + { + if ( (gametic == onelinertic) && (oneliner != "") && (players[consoleplayer].health > 0) ) + { + int mute; + if ( mutevoice ) mute = mutevoice.GetInt(); + else mute = CVar.GetCVar('swwm_mutevoice',players[consoleplayer]).GetInt(); // we can't assign the variable here since it's play scope + if ( onelinerlevel > mute ) + { + let l = SWWMOneLiner.Make(oneliner,onelinerspan); + StatusBar.AttachMessage(l,-3473); + } + SendNetworkEvent("swwmremotelinertxt."..oneliner,consoleplayer,onelinerlevel); + } + for ( int i=0; i= 11 ) + { + SendNetworkEvent("swwmmoneycheat",consoleplayer); + kcode = 0; + } + } + else kcode = 0; + static const int deep[] = {32,18,18,25,38,24,19,18}; + // the deepest lore + if ( e.KeyScan == deep[lcode] ) + { + lcode++; + if ( lcode >= 8 ) + { + SendNetworkEvent("swwmlorecheat",consoleplayer); + lcode = 0; + } + } + else lcode = 0; + if ( e.KeyScan == 33 ) // assuming that's the F key on all keyboards (hopefully) + { + let demo = Demolitionist(players[consoleplayer].mo); + let gone = PlayerGone(players[consoleplayer].mo); + if ( (demo && (demo.Health <= 0) && (demo.deadtimer > 40)) + || (gone && (gone.Health <= 0) && (gone.deadtimer > 40)) ) + { + // pay respects + int numf = Random[FInTheChat](1,6); + for ( int i=0; i 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++; + } + + override void CheckReplacee( ReplacedEvent e ) + { + if ( e.Replacement is 'DSparilHax' ) + e.Replacee = 'Sorcerer2'; + } + + override void CheckReplacement( ReplaceEvent e ) + { + // respect final replacements + if ( e.IsFinal ) return; + // shell types (sorted by rarity + static const Class redpool[] = {"RedShell","RedShell2","RedShell4","RedShell8"}; + static const Class greenpool[] = {"GreenShell","GreenShell2","GreenShell4"}; + static const Class whitepool[] = {"WhiteShell","WhiteShell2"}; + static const Class purplepool[] = {"PurpleShell","PurpleShell2","PurpleShell4"}; + static const Class bluepool[] = {"BlueShell","BlueShell2","BlueShell4"}; + static const Class blackpool[] = {"BlackShell","BlackShell2"}; + if ( e.Replacee is 'ItemFog' ) e.Replacement = 'SWWMItemFog'; + if ( e.Replacee is 'TeleportFog' ) e.Replacement = 'SWWMTeleportFog'; + else if ( (e.Replacee is 'Chainsaw') || (e.Replacee is 'Gauntlets') || (e.Replacee is 'FWeapAxe') ) e.Replacement = 'PusherWeapon'; + else if ( (e.Replacee is 'Fist') || (e.Replacee is 'Staff') ) e.Replacement = 'DeepImpact'; + else if ( (e.Replacee is 'Pistol') || (e.Replacee is 'GoldWand') || (e.Replacee is 'FWeapFist') || (e.Replacee is 'CWeapMace') || (e.Replacee is 'MWeapWand') ) e.Replacement = 'ExplodiumGun'; + else if ( (e.Replacee is 'Shotgun') || (e.Replacee is 'CWeapStaff') ) e.Replacement = 'Spreadgun'; + else if ( (e.Replacee is 'SuperShotgun') || (e.Replacee is 'MWeapFrost') ) e.Replacement = 'Wallbuster'; + else if ( e.Replacee is 'Crossbow' ) + { + if ( Random[Replacements](0,2) ) e.Replacement = 'Spreadgun'; + else e.Replacement = 'Wallbuster'; + } + else if ( (e.Replacee is 'Chaingun') || (e.Replacee is 'Blaster') || (e.Replacee is 'FWeapHammer') ) e.Replacement = 'Eviscerator'; + else if ( (e.Replacee is 'RocketLauncher') || (e.Replacee is 'PhoenixRod') || (e.Replacee is 'FWeaponPiece3') ) e.Replacement = 'Hellblazer'; + else if ( (e.Replacee is 'PlasmaRifle') || (e.Replacee is 'SkullRod') ) + { + if ( Random[Replacements](0,2) ) e.Replacement = 'Sparkster'; + else e.Replacement = 'SilverBullet'; + } + else if ( e.Replacee is 'CWeapFlame' ) e.Replacement = 'Sparkster'; + else if ( e.Replacee is 'MWeapLightning' ) e.Replacement = 'SilverBullet'; + else if ( (e.Replacee is 'BFG9000') || (e.Replacee is 'Mace') ) + { + if ( Random[Replacements](0,2) ) e.Replacement = 'CandyGun'; + else e.Replacement = 'Ynykron'; + } + else if ( e.Replacee is 'CWeaponPiece2' ) e.Replacement = 'CandyGun'; + else if ( e.Replacee is 'MWeaponPiece1' ) e.Replacement = 'Ynykron'; + else if ( (e.Replacee == 'Clip') || (e.Replacee == 'GoldWandAmmo') || (e.Replacee == 'GoldWandHefty') || (e.Replacee is 'ArtiPoisonBag') ) + { + switch( Random[Replacement](0,14) ) + { + case 0: + case 1: + case 2: + e.Replacement = redpool[Random[Replacement](0,1)]; + break; + case 4: + case 5: + case 6: + e.Replacement = greenpool[Random[Replacement](0,1)]; + break; + case 7: + case 8: + e.Replacement = purplepool[0]; + break; + default: + e.Replacement = 'SWWMNothing'; + break; + } + } + else if ( (e.Replacee == 'Shell') || (e.Replacee is 'CrossbowAmmo') ) + { + switch( Random[Replacement](0,13) ) + { + case 0: + case 1: + case 2: + e.Replacement = redpool[Random[Replacement](0,1)]; + break; + case 3: + case 4: + case 5: + e.Replacement = greenpool[Random[Replacement](0,1)]; + break; + case 6: + case 7: + e.Replacement = whitepool[0]; + break; + case 8: + case 9: + case 10: + e.Replacement = purplepool[0]; + break; + case 11: + case 12: + e.Replacement = bluepool[Random[Replacement](0,1)]; + break; + case 13: + e.Replacement = blackpool[0]; + break; + } + } + else if ( (e.Replacee == 'ShellBox') || (e.Replacee is 'CrossbowHefty') ) + { + switch( Random[Replacement](0,14) ) + { + case 0: + case 1: + case 2: + e.Replacement = redpool[Random[Replacement](1,3)]; + break; + case 3: + case 4: + case 5: + e.Replacement = greenpool[Random[Replacement](1,2)]; + break; + case 6: + case 7: + case 8: + e.Replacement = whitepool[Random[Replacement](0,1)]; + break; + case 9: + case 10: + case 11: + e.Replacement = purplepool[Random[Replacement](0,2)]; + break; + case 12: + case 13: + e.Replacement = bluepool[Random[Replacement](0,2)]; + break; + case 14: + e.Replacement = blackpool[Random[Replacement](0,1)]; + break; + } + } + else if ( e.Replacee == 'ClipBox' ) + { + if ( Random[Replacements](0,2) ) e.Replacement = 'EvisceratorShell'; + else if ( Random[Replacements](0,4) ) e.Replacement = 'EvisceratorTrioSpawn'; + else e.Replacement = 'EvisceratorSixPack'; + } + else if ( e.Replacee == 'BlasterAmmo' ) e.Replacement = 'EvisceratorShell'; + else if ( e.Replacee == 'BlasterHefty' ) + { + if ( Random[Replacements](0,3) ) e.Replacement = 'EvisceratorTrioSpawn'; + else e.Replacement = 'EvisceratorSixPack'; + } + else if ( (e.Replacee == 'RocketAmmo') || (e.Replacee == 'PhoenixRodAmmo') || (e.Replacee == 'MaceAmmo') ) + { + switch ( Random[Replacements](0,6) ) + { + case 0: + case 1: + case 2: + case 3: + e.Replacement = 'HellblazerMissiles'; + break; + case 4: + case 5: + case 6: + e.Replacement = 'HellblazerCrackshots'; + break; + } + } + else if ( (e.Replacee == 'RocketBox') || (e.Replacee == 'PhoenixRodHefty') || (e.Replacee == 'MaceHefty') ) + { + switch ( Random[Replacements](0,11) ) + { + case 0: + case 1: + case 2: + case 3: + case 4: + if ( Random[Replacements](0,2) ) e.Replacement = 'HellblazerMissiles'; + else if ( Random[Replacements](0,2) ) e.Replacement = 'HellblazerMissileTrioSpawn'; + else e.Replacement = 'HellblazerMissileMag'; + break; + case 5: + case 6: + case 7: + case 8: + if ( Random[Replacements](0,3) ) e.Replacement = 'HellblazerCrackshots'; + else e.Replacement = 'HellblazerCrackshotMag'; + break; + case 9: + case 10: + if ( Random[Replacements](0,8) ) e.Replacement = 'HellblazerRavagers'; + else e.Replacement = 'HellblazerRavagerMag'; + break; + case 11: + if ( Random[Replacements](0,10) ) e.Replacement = 'HellblazerWarheads'; + else e.Replacement = 'HellblazerWarheadMag'; + break; + } + } + else if ( (e.Replacee == 'Cell') || (e.Replacee == 'SkullRodAmmo') ) + { + if ( Random[Replacements](0,3) ) e.Replacement = 'SparkUnit'; + else if ( !Random[Replacements](0,2) ) e.Replacement = 'CandyGunBullets'; + else e.Replacement = Random[Replacements](0,2)?'SilverBullets':'SilverBullets2'; + } + else if ( (e.Replacee == 'ArtiTeleport') || (e.Replacee == 'ArtiTeleportOther') ) + { + if ( Random[Replacements](0,3) ) e.Replacement = 'SWWMNothing'; + else e.Replacement = 'GoldShell'; + } + else if ( (e.Replacee == 'CellPack') || (e.Replacee == 'SkullRodHefty') ) + { + if ( !Random[Replacements](0,2) ) + { + if ( Random[Replacements](0,2) ) e.Replacement = Random[Replacements](0,2)?'SilverBulletsBundleSpawn':'SilverBullets2BundleSpawn'; + else e.Replacement = Random[Replacements](0,2)?'SilverBulletAmmo':'SilverBulletAmmo2'; + } + else if ( Random[Replacements](0,2) ) e.Replacement = 'CandyGunBulletsBundleSpawn'; + else e.Replacement = 'CandyGunAmmo'; + } + else if ( e.Replacee == 'Mana1' ) e.Replacement = 'FabricatorTier1'; + else if ( e.Replacee == 'Mana2' ) e.Replacement = 'FabricatorTier2'; + else if ( e.Replacee == 'Mana3' ) e.Replacement = 'FabricatorTier3'; + else if ( e.Replacee == 'ArtiBoostMana' ) e.Replacement = 'FabricatorTier4'; + else if ( (e.Replacee == 'Backpack') || (e.Replacee == 'BagOfHolding') || (e.Replacee == 'ArtiPork') || (e.Replacee == 'ArtiBoostArmor') ) e.Replacement = 'HammerspaceEmbiggener'; + else if ( (e.Replacee == 'FWeaponPiece1') || (e.Replacee == 'FWeaponPiece2') + || (e.Replacee == 'CWeaponPiece1') || (e.Replacee == 'CWeaponPiece3') + || (e.Replacee == 'MWeaponPiece2') || (e.Replacee == 'MWeaponPiece3') ) + { + if ( Random[Replacements](0,1) ) e.Replacement = 'SWWMNothing'; + else if ( Random[Replacements](0,5) ) e.Replacement = 'HammerspaceEmbiggener'; + else e.Replacement = 'GoldShell'; + } + else if ( (e.Replacee == 'ArmorBonus') || (e.Replacee == 'ArtiTimeBomb') || (e.Replacee == 'ArtiBlastRadius') ) e.Replacement = 'ArmorNuggetItem'; + else if ( (e.Replacee == 'HealthBonus') || (e.Replacee == 'CrystalVial') ) e.Replacement = 'HealthNuggetItem'; + else if ( e.Replacee == 'Stimpack' ) e.Replacement = 'TetraHealthItem'; + else if ( e.Replacee == 'Medikit' ) e.Replacement = 'CubeHealthItem'; + else if ( e.Replacee == 'ArtiHealth' ) + { + if ( gameinfo.gametype&GAME_Heretic && Random[Replacements](0,1) ) e.Replacement = 'CubeHealthItem'; + else e.Replacement = 'TetraHealthItem'; + } + else if ( (e.Replacee == 'Soulsphere') || (e.Replacee == 'ArtiSuperHealth') ) + { + if ( gameinfo.gametype&GAME_Hexen ) e.Replacement = 'CubeHealthItem'; + else e.Replacement = 'RefresherItem'; + } + else if ( e.Replacee == 'ArtiHealingRadius' ) e.Replacement = 'RefresherItem'; + else if ( (e.Replacee == 'Megasphere') || (e.Replacee == 'ArtiEgg') || (e.Replacee == 'PlatinumHelm') ) e.Replacement = 'GrilledCheeseSandwich'; + else if ( (e.Replacee == 'Blursphere') || (e.Replacee == 'ArtiInvisibility') || (e.Replacee == 'AmuletOfWarding') ) e.Replacement = 'GhostArtifact'; + else if ( e.Replacee == 'Radsuit' ) e.Replacement = 'EBarrier'; + else if ( (e.Replacee == 'ArtiFly') ) e.Replacement = 'GravitySuppressor'; + else if ( (e.Replacee == 'InvulnerabilitySphere') || (e.Replacee == 'ArtiInvulnerability') || (e.Replacee == 'ArtiInvulnerability2') ) e.Replacement = 'FuckingInvinciball'; + else if ( (e.Replacee == 'Berserk') || (e.Replacee == 'ArtiTomeOfPower') || (e.Replacee == 'ArtiDarkServant') || (e.Replacee == 'ArtiSpeedBoots') ) e.Replacement = 'Ragekit'; + else if ( (e.Replacee == 'AllMap') || (e.Replacee == 'SuperMap') ) e.Replacement = 'Omnisight'; + else if ( (e.Replacee == 'Infrared') || (e.Replacee == 'ArtiTorch') ) e.Replacement = 'SWWMLamp'; + else if ( (e.Replacee == 'GreenArmor') || (e.Replacee == 'SilverShield') || (e.Replacee == 'MeshArmor') ) e.Replacement = 'BlastSuitItem'; + else if ( (e.Replacee == 'BlueArmor') || (e.Replacee == 'FalconShield') || (e.Replacee == 'EnchantedShield') ) e.Replacement = 'WarArmorItem'; + } + + override void NetworkProcess( ConsoleEvent e ) + { + static const Class cbttypes[] = {"RedShell","GreenShell","BlueShell","PurpleShell"}; + if ( e.Name ~== "swwmgesture" ) + { + if ( (e.player == -1) || !playeringame[e.player] || !players[e.player].mo ) return; + let mo = players[e.player].mo; + if ( (mo.Health <= 0) || !(mo is 'Demolitionist') ) return; + switch ( e.Args[0] ) + { + case 1: + SWWMGesture.SetGesture(mo,1); + break; + case 2: + SWWMGesture.SetGesture(mo,2); + break; + default: + SWWMGesture.SetGesture(mo,0); + break; + } + } + if ( e.IsManual ) return; + if ( e.Name.Left(14) ~== "swwmstoregive." ) + { + Class item = e.Name.Mid(14); + if ( !item ) return; + if ( SWWMCredits.Take(players[e.Args[0]],e.Args[1]) ) + { + players[e.Args[0]].mo.GiveInventory(item,e.Args[2],true); + if ( item is 'Weapon' ) players[e.Args[0]].mo.A_SelectWeapon((Class)(item)); + } + } + else if ( e.Name.Left(10) ~== "swwmtrade." ) + { + Class item = e.Name.Mid(10); + if ( !item ) return; + let def = GetDefaultByType(item); + int amt = def.Amount; + // if it's an ammo, check the largest unit givable + if ( item is 'Ammo' ) + { + for ( int i=0; i)(AllActorClasses[i]); + if ( !a || (a.GetParentClass() != item) || (GetDefaultByType(a).Amount < amt) ) continue; + amt = GetDefaultByType(a).Amount; + } + } + Inventory ritm = players[e.Args[1]].mo.FindInventory(item); + if ( ritm ) + { + int maxgive = ritm.MaxAmount-ritm.Amount; + if ( amt > maxgive ) amt = maxgive; + } + else if ( amt > def.MaxAmount ) amt = def.MaxAmount; + bool rslt = false; + if ( (amt > 0) && players[e.Args[1]].mo.GiveInventory(item,amt,true) ) + { + players[e.Args[0]].mo.TakeInventory(item,amt); + // add to history + SWWMTradeHistory.RegisterSend(players[e.Args[0]],players[e.Args[1]],item,amt); + SWWMTradeHistory.RegisterReceive(players[e.Args[1]],players[e.Args[0]],item,amt); + // add messages + if ( e.Args[0] == consoleplayer ) Console.Printf(StringTable.Localize("$SWWM_MSGSENT"),amt,def.GetTag(),players[e.Args[1]].GetUserName()); + if ( e.Args[1] == consoleplayer ) Console.Printf(StringTable.Localize("$SWWM_MSGRECV"),players[e.Args[0]].GetUserName(),amt,def.GetTag()); + rslt = true; + } + if ( e.Args[0] == consoleplayer ) + { + let t = new("MenuTransaction"); + t.uid = e.Args[2]; + t.type = MenuTransaction.TT_ITEMSEND; + t.result = rslt; + t.used = item; + t.usedup = (players[e.Args[1]].mo.CountInv(item)<=0); + checklist.Push(t); + } + } + else if ( e.Name.Left(17) ~== "swwmmarkloreread." ) + { + let l = SWWMLoreLibrary.Find(players[e.Args[0]]); + let idx = l.FindEntry(e.Name.Mid(17)); + l.MarkRead(idx); + } + else if ( e.Name.Left(12) ~== "swwmuseitem." ) + { + Class item = e.Name.Mid(12); + if ( !item ) return; + let i = players[e.Args[0]].mo.FindInventory(item); + if ( !i ) return; + bool rslt = players[e.Args[0]].mo.UseInventory(i); + if ( e.Args[0] == consoleplayer ) + { + let t = new("MenuTransaction"); + t.uid = e.Args[1]; + t.type = MenuTransaction.TT_ITEMUSE; + let w = (Class)(item); + if ( w ) t.result = (players[e.Args[0]].PendingWeapon==Weapon(i)); + else t.result = rslt; + t.used = item; + t.usedup = (!i||(i.Amount<=0)); + checklist.Push(t); + } + } + else if ( e.Name.Left(13) ~== "swwmdropitem." ) + { + Class item = e.Name.Mid(13); + if ( !item ) return; + let i = players[e.Args[0]].mo.FindInventory(item); + if ( !i ) return; + int amt = i.default.Amount; + // if it's an ammo, check the largest unit givable + if ( i is 'Ammo' ) + { + for ( int i=0; i)(AllActorClasses[i]); + if ( !a || (a.GetParentClass() != item) || (GetDefaultByType(a).Amount < amt) ) continue; + amt = GetDefaultByType(a).Amount; + } + } + if ( amt > i.Amount ) amt = i.Amount; + let drop = players[e.Args[0]].mo.DropInventory(i,amt); + // add some random velocity so multiple drops don't get bunched together + if ( drop ) drop.vel += (Actor.RotateVector((FRandom[Junk](-1.5,.5),FRandom[Junk](-2.5,2.5)),players[e.Args[0]].mo.angle),FRandom[Junk](2.,5.)); + if ( e.Args[0] == consoleplayer ) + { + let t = new("MenuTransaction"); + t.uid = e.Args[1]; + t.type = MenuTransaction.TT_ITEMDROP; + t.used = item; + t.result = drop; + t.usedup = (!i||(i.Amount<=0)); + checklist.Push(t); + } + } + else if ( e.Name ~== "swwmkoraxline" ) + { + if ( consoleplayer != e.Args[1] ) return; + switch ( e.Args[0] ) + { + case 0: + AddOneliner("koraxgreet",3,60); + break; + case 1: + AddOneliner("koraxblood",3,150); + break; + case 2: + AddOneliner("koraxgame",3,120); + break; + case 3: + AddOneliner("koraxworship",3,80); + break; + case 4: + AddOneliner("koraxmasters",3,90); + break; + } + } + else if ( e.Name.Left(16) ~== "swwmremoteliner." ) + { + if ( consoleplayer == e.Args[0] ) return; + if ( !CVar.GetCVar('swwm_othervoice',players[consoleplayer]).GetBool() ) return; + if ( CVar.GetCVar('swwm_mutevoice',players[consoleplayer]).GetInt() >= e.Args[1] ) return; + players[e.Args[0]].mo.A_StartSound(e.Name.Mid(16),CHAN_DEMOVOICE,attenuation:.5); + } + else if ( e.Name.Left(19) ~== "swwmremotelinertxt." ) + { + if ( consoleplayer == e.Args[0] ) return; + if ( !CVar.GetCVar('swwm_othervoice',players[consoleplayer]).GetBool() ) return; + if ( CVar.GetCVar('swwm_mutevoice',players[consoleplayer]).GetInt() >= e.Args[1] ) return; + double dist = players[consoleplayer].Camera.Distance3D(players[e.Args[0]].mo); + if ( dist < 2000 ) + Console.Printf("\cx%s\cx: %s\c-",players[e.Args[0]].GetUserName(),StringTable.Localize(e.Name.Mid(19))); + } + else if ( e.Name ~== "swwmmoneycheat" ) + { + if ( consoleplayer == e.Args[0] ) + { + Console.Printf("\cfLOADSAMONEY!\c-"); + S_StartSound("menu/buyinv",CHAN_ITEM,CHANF_UI); + S_StartSound("misc/emone",CHAN_VOICE,CHANF_UI); + } + SWWMCredits.Give(players[e.Args[0]],1000000000); + SWWMScoreObj.Spawn(1000000000,players[e.Args[0]].mo.Vec3Offset(0,0,players[e.Args[0]].mo.Height/2)); + } + else if ( e.Name ~== "swwmlorecheat" ) + { + // look up all lore files + for ( int l=0; l qs; + qs.Clear(); + String rite = e.Name.Mid(8); + rite.Split(qs,",",TOK_SKIPEMPTY); + for ( int i=0; i 3) ) continue; + cbt.reloadqueue.Push(cbttypes[qi]); + } + cbt.waitreload = false; + } + else if ( e.Name ~== "swwmcleartransaction" ) + { + if ( e.Args[1] != consoleplayer ) return; + for ( int i=0; i0)?demo.lastdamage:Random[Flicker](60,80); + int lastdmgtic = (demo.Health>0)?demo.lastdamagetic:(gametic+Random[Flicker](30,20)); + double noiz = min(lastdmg*.09*max(0,(lastdmgtic-(gametic+e.Fractic))/35.),.5); + Shader.SetEnabled(p,"Glitch",noiz>0); + Shader.SetEnabled(p,"Grain",noiz>0); + if ( noiz > 0 ) + { + Shader.SetUniform1f(p,"Glitch","Timer",(gametic+e.FracTic)/Thinker.TICRATE); + Shader.SetUniform1f(p,"Grain","Timer",(gametic+e.FracTic)/Thinker.TICRATE); + Shader.SetUniform1f(p,"Grain","ni",noiz); + noiz = min(lastdmg*.08*max(0,(lastdmgtic-(gametic+e.Fractic))/35.),.8); + Shader.SetUniform1f(p,"Glitch","str1",noiz); + noiz = min(lastdmg*.03*max(0,(lastdmgtic-(gametic+e.Fractic))/35.),3.5); + Shader.SetUniform1f(p,"Glitch","str2",noiz); + } + if ( !demo.InStateSequence(demo.CurState,demo.FindState("Dash")) ) + { + Shader.SetEnabled(p,"ZoomBlur",false); + return; + } + Shader.SetEnabled(p,"ZoomBlur",true); + Vector3 vel = demo.vel+demo.dashdir*demo.dashboost; + double baumpu = max(0.,(demo.bumptic-(gametic+e.Fractic))/35.); + vel += demo.dashdir*baumpu; + double spd = vel.length(); + Vector3 worlddir = vel/spd; + Shader.SetUniform1f(p,"ZoomBlur","Fade",clamp((spd-20.)/60.,0.,.75)); + double str = min(spd/50.,10.); + Vector3 x, y, z; + [x, y, z] = swwm_CoordUtil.GetAxes(e.ViewPitch,e.ViewAngle,e.ViewRoll); + Vector3 reldir = (worlddir dot y, worlddir dot z, worlddir dot x); + Vector2 centerspot = (.5+reldir.x*.5,.5+reldir.y*.5); + if ( reldir.z < 0 ) + { + centerspot.x = 1.-centerspot.x; + centerspot.y = 1.-centerspot.y; + str *= -1; + } + Shader.SetUniform1f(p,"ZoomBlur","Str",str); + Shader.SetUniform2f(p,"ZoomBlur","CenterSpot",centerspot); + } + else + { + Shader.SetEnabled(p,"Glitch",false); + Shader.SetEnabled(p,"Grain",false); + Shader.SetEnabled(p,"ZoomBlur",false); + } + } + + static void DoFlash( Actor camera, Color c, int duration ) + { + // don't flash when paused + if ( menuactive && (menuactive != Menu.OnNoPause) ) return; + QueuedFlash qf = new("QueuedFlash"); + qf.duration = duration; + qf.c = c; + qf.tic = gametic; + qf.cam = camera; + let hnd = SWWMHandler(EventHandler.Find("SWWMHandler")); + if ( !hnd ) return; // not supposed to happen + hnd.flashes.push(qf); + } +} diff --git a/zscript/swwm_hudextra.zsc b/zscript/swwm_hudextra.zsc new file mode 100644 index 000000000..9fe1f47fe --- /dev/null +++ b/zscript/swwm_hudextra.zsc @@ -0,0 +1,166 @@ +// additional hud things + +// Press F to Pay Respects +Class PayRespects : HUDMessageBase +{ + Vector2 basepos; + int lifespan, initialspan, starttic; + transient Font TewiFont; + double scale; + Vector2 hs, ss; + int seed, seed2; + + static PayRespects PressF() + { + let f = new("PayRespects"); + f.basepos = (FRandom[FInTheChat](0.,1.),FRandom[FInTheChat](1.02,1.05)); + f.scale = FRandom[FInTheChat](.5,2.); + f.lifespan = f.initialspan = Random[FInTheChat](20,80); + f.starttic = level.maptime; + f.seed = Random[FInTheChat](); + f.seed2 = Random[FInTheChat](); + f.ScreenSizeChanged(); + return f; + } + + override bool Tick() + { + lifespan--; + return (lifespan<=0); + } + + override void ScreenSizeChanged() + { + hs = StatusBar.GetHUDScale()*scale; + ss = (Screen.GetWidth()/hs.x,Screen.GetHeight()/hs.y); + } + + override void Draw( int bottom, int visibility ) + { + Vector2 realpos = (basepos.x*ss.x,basepos.y*ss.y); + if ( !TewiFont ) TewiFont = Font.GetFont('TewiShaded'); + Vector2 fo = (TewiFont.StringWidth("F")/2.,-TewiFont.GetHeight()); + // F rise up + int initspd = (128-seed); + if ( (initspd >= 0) && (initspd < 32) ) initspd = 32; + if ( (initspd < 0) && (initspd > -32) ) initspd = -32; + int boostup = 32+(seed2/4); + double fractic = SWWMStatusBar(statusbar)?SWWMStatusBar(statusbar).fractic:0; + fo.x += (.15*initspd)*((initialspan-(lifespan-fractic))**.6); + fo.y += ((initialspan-(lifespan-fractic))**1.6)-boostup*sin((90./initialspan)*(level.maptime+fractic-starttic)); + double alph = clamp((lifespan+fractic)/double(initialspan),0.,1.); + Screen.DrawText(TewiFont,Font.CR_GREEN,realpos.x-fo.x,realpos.y-fo.y,"F",DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_Alpha,alph); + } +} + +// One-liners +Class SWWMOneLiner : HUDMessageBase +{ + String whichline; + int lifespan, curtime; + transient Font TewiFont, MPlusFont; + transient CVar safezone, lang, hscale; + + static SWWMOneLiner Make( String whichline, int lifespan ) + { + let l = new("SWWMOneLiner"); + l.whichline = whichline; + l.curtime = l.lifespan = lifespan; + return l; + } + + override bool Tick() + { + if ( players[consoleplayer].Health <= 0 ) curtime = int.min; + curtime--; + return (curtime<-20); + } + + override void Draw( int bottom, int visibility ) + { + if ( !safezone ) safezone = CVar.GetCVar('swwm_hudmargin',players[consoleplayer]); + if ( !lang ) lang = CVar.GetCVar('language',players[consoleplayer]); + if ( !TewiFont ) TewiFont = Font.GetFont('TewiShaded'); + if ( !MPlusFont ) MPlusFont = Font.GetFont('MPlusShaded'); + if ( !hscale ) hscale = CVar.GetCVar('swwm_hudscale',players[consoleplayer]); + int margin = safezone.GetInt(); + Vector2 hs; + if ( hscale.GetInt() <= 0 ) hs = StatusBar.GetHUDScale(); + else + { + hs.x = hscale.GetInt(); + if ( Screen.GetWidth()/hs.x < 640. ) hs.x = max(1,floor(Screen.GetWidth()/640.)); + hs.y = hs.x; + } + Vector2 ss = (Screen.GetWidth()/hs.x,Screen.GetHeight()/hs.y); + String loc = StringTable.Localize(whichline); + if ( loc.Length() <= 0 ) return; // don't draw empty strings + String locs = StringTable.Localize("$SWWM_LQUOTE")..loc..StringTable.Localize("$SWWM_RQUOTE"); + Font fnt = TewiFont; + if ( lang.GetString() ~== "jp" ) fnt = MPlusFont; + // split so it can fit + BrokenLines l = fnt.BreakLines(locs,int(ss.x*.5)); + int maxlen = 0; + for ( int i=0; i maxlen ) maxlen = len; + } + int h = fnt.GetHeight(); + int fh = h*l.Count(); + double alph = clamp((curtime/20.)+1.,0.,1.); + alph *= clamp((lifespan-curtime)/10.,0.,1.); + Screen.Dim("Black",alph*.8,int((Screen.GetWidth()-(maxlen+12)*hs.x)/2.),int(bottom-(margin+2+fh)*hs.y),int((maxlen+12)*hs.x),int((fh+4)*hs.y)); + int yy = margin+fh; + for ( int i=0; i 0 ) alpha -= 1./duration; + return (alpha<=0)||(!cam); + } + override void Draw( int bottom, int visibility ) + { + if ( automapactive || (visibility != BaseStatusBar.HUDMSGLayer_UnderHUD) ) return; + if ( cam && (players[consoleplayer].camera != cam) ) return; + if ( !str ) str = CVar.GetCVar('swwm_flashstrength',players[consoleplayer]); + Screen.Dim(col,(col.a/255.)*alpha*str.GetFloat(),0,0,Screen.GetWidth(),Screen.GetHeight()); + } +} + +Class QueuedFlash +{ + Color c; + int duration; + int tic; + Actor cam; +} diff --git a/zscript/swwm_inventory.zsc b/zscript/swwm_inventory.zsc index 91585ec6b..ef3cbd84a 100644 --- a/zscript/swwm_inventory.zsc +++ b/zscript/swwm_inventory.zsc @@ -572,7 +572,7 @@ Class BigPunchSplash : Actor override void PostBeginPlay() { Super.PostBeginPlay(); - SWWMHandler.DoExplosion(self,special1,40000,120,60,ignoreme:target); + SWWMUtility.DoExplosion(self,special1,40000,120,60,ignoreme:target); Destroy(); } } @@ -1149,7 +1149,7 @@ Class SWWMWeapon : Weapon abstract bool bloodless = true; double diff = deltaangle(self.angle,AngleTo(d.HitActor)); self.angle += clamp(diff,-5.,5.); - SWWMHandler.DoKnockback(d.HitActor,d.HitDir+(0,0,.2),dmg*2000); + SWWMUtility.DoKnockback(d.HitActor,d.HitDir+(0,0,.2),dmg*2000); if ( raging ) { invoker.bEXTREMEDEATH = true; diff --git a/zscript/swwm_jackhammer.zsc b/zscript/swwm_jackhammer.zsc index 8d9dc4a36..dcb026e0a 100644 --- a/zscript/swwm_jackhammer.zsc +++ b/zscript/swwm_jackhammer.zsc @@ -290,7 +290,7 @@ Class PusherProjectile : Actor if ( target.bNOBLOOD || target.bINVULNERABLE ) A_StartSound("pusher/althit",CHAN_WEAPON,CHANF_OVERLAP); else A_StartSound("pusher/altmeat",CHAN_WEAPON,CHANF_OVERLAP); target.A_QuakeEx(6,6,6,10,0,200,"",QF_RELATIVE|QF_SCALEDOWN,falloff:100,rollIntensity:.7); - SWWMHandler.DoKnockback(target,vel.unit(),85000); + SWWMUtility.DoKnockback(target,vel.unit(),85000); return damage; } override void Touch( Actor toucher ) @@ -381,7 +381,7 @@ Class PusherWeapon : SWWMWeapon { double diff = deltaangle(self.angle,AngleTo(d.HitActor)); self.angle += clamp(diff,-5.,5.); - SWWMHandler.DoKnockback(d.HitActor,d.HitDir,8500); + SWWMUtility.DoKnockback(d.HitActor,d.HitDir,8500); d.HitActor.A_QuakeEx(4,4,4,10,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:1.1); dmg = d.HitActor.DamageMobj(invoker,self,dmg,'Tenderize',DMG_THRUSTLESS); if ( d.HitActor.bNOBLOOD || d.HitActor.bINVULNERABLE ) @@ -466,7 +466,7 @@ Class PusherWeapon : SWWMWeapon { double diff = deltaangle(self.angle,AngleTo(d.HitActor)); self.angle += clamp(diff,-5.,5.); - SWWMHandler.DoKnockback(d.HitActor,d.HitDir,85000); + SWWMUtility.DoKnockback(d.HitActor,d.HitDir,85000); d.HitActor.A_QuakeEx(9,9,9,15,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:1.1); dmg = d.HitActor.DamageMobj(invoker,self,dmg,'Tenderize',DMG_THRUSTLESS); if ( d.HitActor.bNOBLOOD || d.HitActor.bINVULNERABLE ) @@ -517,7 +517,7 @@ Class PusherWeapon : SWWMWeapon p.pitch = pitch; p.vel = x*p.speed*invoker.chargelevel; p.target = self; - SWWMHandler.DoKnockback(self,x,85000.); + SWWMUtility.DoKnockback(self,x,85000.); } if ( !gone ) { diff --git a/zscript/swwm_player.zsc b/zscript/swwm_player.zsc index b99ee7df2..4152155b4 100644 --- a/zscript/swwm_player.zsc +++ b/zscript/swwm_player.zsc @@ -1016,7 +1016,7 @@ Class Demolitionist : PlayerPawn } if ( jumpactor && jumpactor.bSHOOTABLE ) { - SWWMHandler.DoKnockback(jumpactor,(-walldir,0),12000); + SWWMUtility.DoKnockback(jumpactor,(-walldir,0),12000); int dmg = jumpactor.DamageMobj(self,self,10,'Jump'); if ( FindInventory("RagekitPower") ) { @@ -2006,7 +2006,7 @@ Class DemolitionistShockwave : Actor A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,attenuation:.2,pitch:.7); A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,attenuation:.1,pitch:.4); } - SWWMHandler.DoExplosion(self,40+min(special1,120),100000+min(special1*2000,150000),100+min(special1*2,130),80,DE_BLAST|DE_EXTRAZTHRUST,'GroundPound',target); + SWWMUtility.DoExplosion(self,40+min(special1,120),100000+min(special1*2000,150000),100+min(special1*2,130),80,DE_BLAST|DE_EXTRAZTHRUST,'GroundPound',target); for ( int i=0; i<360; i+=5 ) { Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](1,3); diff --git a/zscript/swwm_shame.zsc b/zscript/swwm_shame.zsc new file mode 100644 index 000000000..535d235a2 --- /dev/null +++ b/zscript/swwm_shame.zsc @@ -0,0 +1,123 @@ +// SHAMEFUL DISPLAY + +// >loading brutal doom with this +Class SWWMBrutalHandler : StaticEventHandler +{ + ui int timer; + ui TextureID scr; + bool detected; + + override void OnRegister() + { + for ( int i=0; iBrutal Doom"); + timer++; + } + else timer = 0; + } + + override void WorldTick() + { + if ( !detected ) return; + for ( int i=0; i vsize.x) || (tsize.y > vsize.y) ) + { + double sar = tsize.x/tsize.y; + if ( sar > ar ) vsize = (tsize.x,tsize.x/ar); + else if ( sar < ar ) vsize = (tsize.y*ar,tsize.y); + else vsize = tsize; + } + Screen.DrawTexture(scr,false,(vsize.x-tsize.x)/2.+FRandom[bdscreen](-1,1)*max(timer-40,0)**3*.000003,(vsize.y-tsize.y)/2.+FRandom[bdscreen](-1,1)*max(timer-40,0)**3*.000003,DTA_VirtualWidthF,vsize.x,DTA_VirtualHeightF,vsize.y,DTA_KeepRatio,true,DTA_Alpha,min(1.,timer/50.)); + Screen.Dim("Red",(timer/70.)-3.5,0,0,Screen.GetWidth(),Screen.GetHeight()); + } +} + +// HORNY +Class SWWMHDoomHandler : StaticEventHandler +{ + ui int timer; + ui TextureID scr; + bool detected; + + override void OnRegister() + { + for ( int i=0; i vsize.x) || (tsize.y > vsize.y) ) + { + double sar = tsize.x/tsize.y; + if ( sar > ar ) vsize = (tsize.x,tsize.x/ar); + else if ( sar < ar ) vsize = (tsize.y*ar,tsize.y); + else vsize = tsize; + } + Screen.DrawTexture(scr,false,(vsize.x-tsize.x)/2.,(vsize.y-tsize.y)/2.,DTA_VirtualWidthF,vsize.x,DTA_VirtualHeightF,vsize.y,DTA_KeepRatio,true); + } +} diff --git a/zscript/swwm_shot.zsc b/zscript/swwm_shot.zsc index 24b364b9c..c6cd3748b 100644 --- a/zscript/swwm_shot.zsc +++ b/zscript/swwm_shot.zsc @@ -26,89 +26,6 @@ Class GoldShellCasing : RedShellCasing } } -Class SWWMDamageAccumulator : Thinker -{ - Actor victim, inflictor, source; - Array 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 girth) && is_schutt ) { @@ -1656,7 +1573,7 @@ Class GoldenImpact : Actor { Super.PostBeginPlay(); A_AlertMonsters(40000); - SWWMHandler.DoExplosion(self,7777,40000,600,500,DE_EXTRAZTHRUST); + SWWMUtility.DoExplosion(self,7777,40000,600,500,DE_EXTRAZTHRUST); A_QuakeEx(9,9,9,40,0,5000,"",QF_RELATIVE|QF_SCALEDOWN,falloff:500,rollintensity:1.5); A_StartSound("spreadgun/goldexpl",CHAN_VOICE,attenuation:.3); A_StartSound("spreadgun/goldexpl",CHAN_WEAPON,attenuation:.15); @@ -1783,7 +1700,7 @@ Class GoldenSubImpact : Actor override void PostBeginPlay() { Super.PostBeginPlay(); - SWWMHandler.DoExplosion(self,777,30000,400,300,DE_EXTRAZTHRUST); + SWWMUtility.DoExplosion(self,777,30000,400,300,DE_EXTRAZTHRUST); A_QuakeEx(7,7,7,20,0,2000,"",QF_RELATIVE|QF_SCALEDOWN,falloff:200,rollintensity:.8); A_SprayDecal("BigScorch",-172); Scale *= FRandom[ExploS](0.8,1.1); @@ -1908,7 +1825,7 @@ Class GoldenSubSubImpact : Actor override void PostBeginPlay() { Super.PostBeginPlay(); - SWWMHandler.DoExplosion(self,77,20000,200,100,DE_EXTRAZTHRUST); + SWWMUtility.DoExplosion(self,77,20000,200,100,DE_EXTRAZTHRUST); A_QuakeEx(4,4,4,15,0,1000,"",QF_RELATIVE|QF_SCALEDOWN,falloff:100,rollintensity:.4); A_SprayDecal("Scorch",-172); Scale *= FRandom[ExploS](0.8,1.1); @@ -2128,7 +2045,7 @@ Class Spreadgun : SWWMWeapon { int realdmg = dmg?dmg:t.HitList[i].HitDamage; SWWMDamageAccumulator.Accumulate(t.HitList[i].HitActor,realdmg,invoker,self,'shot',!large); - SWWMHandler.DoKnockback(t.HitList[i].HitActor,t.HitList[i].x+(0,0,0.025),mm*FRandom[Spreadgun](0.4,1.2)); + SWWMUtility.DoKnockback(t.HitList[i].HitActor,t.HitList[i].x+(0,0,0.025),mm*FRandom[Spreadgun](0.4,1.2)); if ( t.HitList[i].HitActor.bNOBLOOD || t.HitList[i].HitActor.bINVULNERABLE ) { let p = Spawn(impact,t.HitList[i].HitLocation); @@ -2233,10 +2150,10 @@ Class Spreadgun : SWWMWeapon s.alpha *= .4; s.vel += vel*.5+x*FRandom[Spreadgun](4.,8.)+y*FRandom[Spreadgun](-1,1)+z*FRandom[Spreadgun](-1,1); } - SWWMHandler.DoKnockback(self,-x,25000.); + SWWMUtility.DoKnockback(self,-x,25000.); break; case 2: - for ( int j=0; j<9; j++ ) + for ( int j=0; j<5; j++ ) { a = FRandom[Spreadgun](0,360); s = FRandom[Spreadgun](0,.2); @@ -2270,7 +2187,7 @@ Class Spreadgun : SWWMWeapon s.alpha *= .4; s.vel += vel*.5+x*FRandom[Spreadgun](4.,8.)+y*FRandom[Spreadgun](-2,2)+z*FRandom[Spreadgun](-2,2); } - SWWMHandler.DoKnockback(self,-x,13000.); + SWWMUtility.DoKnockback(self,-x,13000.); break; case 3: for ( int j=0; j<8; j++ ) @@ -2306,7 +2223,7 @@ Class Spreadgun : SWWMWeapon s.alpha *= .4; s.vel += vel*.5+x*FRandom[Spreadgun](4.,8.)+y*FRandom[Spreadgun](-2,2)+z*FRandom[Spreadgun](-2,2); } - SWWMHandler.DoKnockback(self,-x,23000.); + SWWMUtility.DoKnockback(self,-x,23000.); break; case 4: for ( int j=0; j<10; j++ ) @@ -2344,7 +2261,7 @@ Class Spreadgun : SWWMWeapon s.alpha *= .4; s.vel += vel*.5+x*FRandom[Spreadgun](4.,12.)+y*FRandom[Spreadgun](-2,2)+z*FRandom[Spreadgun](-2,2); } - SWWMHandler.DoKnockback(self,-x,14000.); + SWWMUtility.DoKnockback(self,-x,14000.); break; case 5: a = FRandom[Spreadgun](0,360); @@ -2378,7 +2295,7 @@ Class Spreadgun : SWWMWeapon s.alpha *= .4; s.vel += vel*.5+x*FRandom[Spreadgun](4.,8.)+y*FRandom[Spreadgun](-1,1)+z*FRandom[Spreadgun](-1,1); } - SWWMHandler.DoKnockback(self,-x,9500.); + SWWMUtility.DoKnockback(self,-x,9500.); break; case 6: a = FRandom[Spreadgun](0,360); @@ -2443,7 +2360,7 @@ Class Spreadgun : SWWMWeapon s.bAMBUSH = true; s.vel += vel*.5+x*FRandom[Spreadgun](1.,20.)+y*FRandom[Spreadgun](-2,2)+z*FRandom[Spreadgun](-2,2); } - SWWMHandler.DoKnockback(self,-x,30000.); + SWWMUtility.DoKnockback(self,-x,30000.); break; default: st = new("SpreadgunTracer"); @@ -2483,7 +2400,7 @@ Class Spreadgun : SWWMWeapon s.alpha *= .4; s.vel += vel*.5+x*FRandom[Spreadgun](4.,8.)+y*FRandom[Spreadgun](-2,2)+z*FRandom[Spreadgun](-2,2); } - SWWMHandler.DoKnockback(self,-x,20000.); + SWWMUtility.DoKnockback(self,-x,20000.); break; } break; diff --git a/zscript/swwm_sparkyboi.zsc b/zscript/swwm_sparkyboi.zsc index 3bf3fc4f1..f06a381d2 100644 --- a/zscript/swwm_sparkyboi.zsc +++ b/zscript/swwm_sparkyboi.zsc @@ -265,7 +265,7 @@ Class BigBiospark : Actor Vector3 dirto = level.Vec3Diff(pos,t.Vec3Offset(0,0,t.height/2)); double dist = dirto.length(); dirto /= dist; - if ( dist > 4. ) SWWMHandler.DoKnockback(t,-dirto,clamp(120.-dist,0.,120.)*100); + if ( dist > 4. ) SWWMUtility.DoKnockback(t,-dirto,clamp(120.-dist,0.,120.)*100); } } Health -= 2; @@ -276,7 +276,7 @@ Class BigBiospark : Actor { A_StopSound(CHAN_VOICE); A_AlertMonsters(15000); - SWWMHandler.DoExplosion(self,450,200000,300,100); + SWWMUtility.DoExplosion(self,450,200000,300,100); A_QuakeEx(9,9,9,30,0,1400,"",QF_RELATIVE|QF_SCALEDOWN,falloff:500,rollIntensity:1.5); A_StartSound("biospark/bighit",CHAN_ITEM,attenuation:.4); A_StartSound("biospark/bighit",CHAN_WEAPON,attenuation:.3); @@ -330,8 +330,8 @@ Class BigBiospark : Actor if ( special1 > 40 ) return; double factor = (40-special1)/40.; double invfct = 1.-factor; - SWWMHandler.DoExplosion(self,150*factor,0.,250*invfct); - SWWMHandler.DoExplosion(self,0,-8000*factor,500*invfct); + SWWMUtility.DoExplosion(self,150*factor,0.,250*invfct); + SWWMUtility.DoExplosion(self,0,-8000*factor,500*invfct); int numpt = int(Random[ExploS](16,32)*factor); for ( int i=0; i 30 ) return; double factor = (30-special1)/30.; double invfct = 1.-factor; - SWWMHandler.DoExplosion(self,30*factor,0.,150*invfct); - SWWMHandler.DoExplosion(self,0,-5000*factor,300*invfct); + SWWMUtility.DoExplosion(self,30*factor,0.,150*invfct); + SWWMUtility.DoExplosion(self,0,-5000*factor,300*invfct); int numpt = int(Random[ExploS](16,32)*factor); for ( int i=0; i 30 ) return; double factor = (30-special1)/30.; double invfct = 1.-factor; - SWWMHandler.DoExplosion(self,10*factor,0.,50*invfct); - SWWMHandler.DoExplosion(self,0.,-5000*factor,100*invfct); + SWWMUtility.DoExplosion(self,10*factor,0.,50*invfct); + SWWMUtility.DoExplosion(self,0.,-5000*factor,100*invfct); int numpt = int(Random[ExploS](8,16)*factor); for ( int i=0; i mx ) return; double factor = (mx-special1)/mx; double invfct = 1.-factor; - SWWMHandler.DoExplosion(self,(bAMBUSH?(500+Args[0]*50):200)*factor,0.,(bAMBUSH?900.:300.)*invfct); - SWWMHandler.DoExplosion(self,0.,-12000*factor,(bAMBUSH?1500:500)*invfct); + SWWMUtility.DoExplosion(self,(bAMBUSH?(500+Args[0]*50):200)*factor,0.,(bAMBUSH?900.:300.)*invfct); + SWWMUtility.DoExplosion(self,0.,-12000*factor,(bAMBUSH?1500:500)*invfct); int numpt = int(Random[ExploS](8,16)*factor); for ( int i=0; i 30 ) return; double factor = (30-special2)/30.; double invfct = 1.-factor; - SWWMHandler.DoExplosion(self,0.,-5000*factor,300*invfct); - SWWMHandler.DoExplosion(self,30*factor,0.,150*invfct); + SWWMUtility.DoExplosion(self,0.,-5000*factor,300*invfct); + SWWMUtility.DoExplosion(self,30*factor,0.,150*invfct); int numpt = int(Random[ExploS](16,32)*factor); for ( int i=0; i9)?48:45; - Screen.DrawText(TewiFont,Font.CR_FIRE,bx-cx,by-18,String.Format("%d",Ammo1.Amount),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true); + int sb = Owner.CountInv("CandyGunBullets"); + if ( sb > 0 ) + { + int cbx = (sb>9)?50:47; + Screen.DrawText(TewiFont,Font.CR_FIRE,bx-cbx,by-21,String.Format("⁺%s",SWWMUtility.SuperscriptNum(sb)),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_Spacing,-1); + Screen.DrawText(TewiFont,Font.CR_FIRE,bx-cx,by-14,String.Format("%d",Ammo1.Amount),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true); + } + else Screen.DrawText(TewiFont,Font.CR_FIRE,bx-cx,by-18,String.Format("%d",Ammo1.Amount),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true); Screen.DrawText(TewiFont,Font.CR_FIRE,bx-37,by-40,String.Format("%d",Ammo2.Amount),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true); } @@ -920,7 +927,7 @@ Class CandyGun : SWWMWeapon A_PlayerFire(); Vector3 x, y, z, x2, y2, z2; [x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll); - SWWMHandler.DoKnockback(self,-x,18000.); + SWWMUtility.DoKnockback(self,-x,18000.); Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),10*x+3*y-2*z); double a = FRandom[Spread](0,360), s = FRandom[Spread](0,.005); [x2, y2, z2] = swwm_CoordUtil.GetAxes(BulletSlope(),angle,roll); @@ -931,7 +938,7 @@ Class CandyGun : SWWMWeapon if ( d.HitType == TRACE_HitActor ) { int dmg = 900; - SWWMHandler.DoKnockback(d.HitActor,d.HitDir,72000); + SWWMUtility.DoKnockback(d.HitActor,d.HitDir,72000); dmg = d.HitActor.DamageMobj(invoker,self,dmg,'Explodium',DMG_USEANGLE|DMG_THRUSTLESS|DMG_FOILINVUL,atan2(d.HitDir.y,d.HitDir.x)); if ( d.HitActor.bNOBLOOD ) { @@ -1054,8 +1061,9 @@ Class CandyGun : SWWMWeapon int dropamt = invoker.clipcount-maxgiveamt; if ( dropamt > 0 ) ma.CreateTossable(dropamt); ma.Amount = min(ma.MaxAmount,ma.Amount+invoker.clipcount); - if ( CheckLocalView() ) for ( int i=0; i<(invoker.clipcount-dropamt); i++ ) ma.PrintPickupMessage(true,ma.PickupMessage()); - if ( (invoker.clipcount-dropamt) > 0 ) ma.PlayPickupSound(self); + ma.MagFill(); + if ( CheckLocalView() ) for ( int i=0; i 0 ) ma.PlayPickupSound(self); invoker.clipcount = 0; } @@ -1075,14 +1083,14 @@ Class CandyGun : SWWMWeapon override bool CheckAmmo( int fireMode, bool autoSwitch, bool requireAmmo, int ammocount ) { if ( sv_infiniteammo || Owner.FindInventory('PowerInfiniteAmmo',true) ) return true; - if ( fireMode == PrimaryFire ) return (chambered || (clipcount > 0) || (Ammo1.Amount > 0)); - if ( fireMode == AltFire ) return (Ammo1.Amount > 0); + if ( fireMode == PrimaryFire ) return (chambered || (clipcount > 0) || (Ammo1.Amount > 0) || (Owner.CountInv("CandyGunBullets") > 0)); + if ( fireMode == AltFire ) return ((Ammo1.Amount > 0) || (Owner.CountInv("CandyGunBullets") > 0)); return Super.CheckAmmo(firemode,autoswitch,requireammo,ammocount); } override bool ReportHUDAmmo() { - if ( chambered || (clipcount > 0) ) return true; + if ( chambered || (clipcount > 0) || (Owner.CountInv("CandyGunBullets") > 0) ) return true; if ( Ammo1.Amount <= 0 ) return false; return Super.ReportHUDAmmo(); } @@ -1119,13 +1127,13 @@ Class CandyGun : SWWMWeapon Ready: XZW2 A 1 { - if ( (invoker.clipcount <= 0) && !invoker.chambered && (invoker.Ammo1.Amount > 0) ) player.SetPSprite(PSP_WEAPON,ResolveState("Reload")); + if ( (invoker.clipcount <= 0) && !invoker.chambered && ((invoker.Ammo1.Amount > 0) || (CountInv("CandyGunBullets") > 0)) ) player.SetPSprite(PSP_WEAPON,ResolveState("Reload")); else if ( (invoker.clipcount > 0) && !invoker.chambered ) player.SetPSprite(PSP_WEAPON,ResolveState("Slide")); else { int flg = WRF_ALLOWZOOM|WRF_ALLOWUSER1; if ( sv_infiniteammo || FindInventory('PowerInfiniteAmmo',true) || (invoker.Ammo1.Amount > 0) || invoker.chambered ) flg |= WRF_ALLOWRELOAD; - if ( (invoker.Ammo1.Amount <= 0) && !sv_infiniteammo && !FindInventory('PowerInfiniteAmmo',true) ) flg |= WRF_NOSECONDARY; + if ( (invoker.Ammo1.Amount <= 0) && (CountInv("CandyGunBullets") <= 0) && !sv_infiniteammo && !FindInventory('PowerInfiniteAmmo',true) ) flg |= WRF_NOSECONDARY; A_WeaponReady(flg); if ( player.cmd.buttons&(BT_ATTACK|BT_ALTATTACK) ) invoker.CheckAmmo(EitherFire,true); @@ -1148,7 +1156,7 @@ Class CandyGun : SWWMWeapon XZW5 Q 1; XZW5 R 1 { - if ( player.cmd.buttons&BT_ATTACK && (((invoker.Ammo1.Amount > 0) && (invoker.Ammo2.Amount > 0)) || sv_infiniteammo || FindInventory('PowerInfiniteAmmo',true)) ) + if ( player.cmd.buttons&BT_ATTACK && ((((invoker.Ammo1.Amount > 0) || (CountInv("CandyGunBullets") > 0)) && (invoker.Ammo2.Amount > 0)) || sv_infiniteammo || FindInventory('PowerInfiniteAmmo',true)) ) player.SetPSprite(PSP_WEAPON,ResolveState("SpecialFire")); } XZW5 STUVWXYZ 1; @@ -1187,15 +1195,26 @@ Class CandyGun : SWWMWeapon XZW1 B 0 { invoker.PlayUpSound(self); - if ( !sv_infiniteammo && !FindInventory('PowerInfiniteAmmo',true) ) + if ( sv_infiniteammo || FindInventory('PowerInfiniteAmmo',true) ) + invoker.clipcount = invoker.default.clipcount; + else if ( invoker.Ammo1.Amount <= 0 ) + { + MagAmmo sb = MagAmmo(FindInventory("CandyGunBullets")); + int takeamt = min(sb.Amount,sb.ClipSize); + invoker.clipcount = takeamt; + sb.Amount -= takeamt; + } + else + { invoker.Ammo1.Amount = max(0,invoker.Ammo1.Amount-1); - invoker.clipcount = invoker.default.clipcount; + invoker.clipcount = invoker.default.clipcount; + } } Goto Select; Reload: XZW2 A 1 { - if ( (invoker.Ammo1.Amount <= 0) || (invoker.clipcount >= invoker.default.clipcount) ) return ResolveState("CheckBullet"); + if ( ((invoker.Ammo1.Amount <= 0) && (CountInv("CandyGunBullets") <= 0)) || (invoker.clipcount >= invoker.default.clipcount) ) return ResolveState("CheckBullet"); A_PlayerReload(); if ( invoker.clipcount <= 0 ) return ResolveState("ReloadEmpty"); return ResolveState(null); @@ -1225,9 +1244,20 @@ Class CandyGun : SWWMWeapon XZW4 R 1 { A_StartSound("explodium/jamitin",CHAN_WEAPON,CHANF_OVERLAP); - invoker.clipcount = invoker.default.clipcount; - if ( !sv_infiniteammo && !FindInventory('PowerInfiniteAmmo',true) ) + if ( sv_infiniteammo || FindInventory('PowerInfiniteAmmo',true) ) + invoker.clipcount = invoker.default.clipcount; + else if ( invoker.Ammo1.Amount <= 0 ) + { + MagAmmo sb = MagAmmo(FindInventory("CandyGunBullets")); + int takeamt = min(sb.Amount,sb.ClipSize); + invoker.clipcount = takeamt; + sb.Amount -= takeamt; + } + else + { invoker.Ammo1.Amount = max(0,invoker.Ammo1.Amount-1); + invoker.clipcount = invoker.default.clipcount; + } } XZW4 STUV 1; XZW2 A 1 A_JumpIf(!invoker.chambered,"Slide"); diff --git a/zscript/swwm_thiccboolet.zsc b/zscript/swwm_thiccboolet.zsc index 9482b2d51..ff59d9345 100644 --- a/zscript/swwm_thiccboolet.zsc +++ b/zscript/swwm_thiccboolet.zsc @@ -56,7 +56,7 @@ Class SilverAirRip : Actor override void PostBeginPlay() { Super.PostBeginPlay(); - SWWMHandler.DoExplosion(self,40,2000,40,ignoreme:target); + SWWMUtility.DoExplosion(self,40,2000,40,ignoreme:target); Destroy(); } } @@ -78,7 +78,7 @@ Class SilverAirRip2 : Actor override void PostBeginPlay() { Super.PostBeginPlay(); - SWWMHandler.DoExplosion(self,20,2000,30,ignoreme:target); + SWWMUtility.DoExplosion(self,20,2000,30,ignoreme:target); Destroy(); } } @@ -101,7 +101,7 @@ Class SilverImpact : Actor override void PostBeginPlay() { Super.PostBeginPlay(); - SWWMHandler.DoExplosion(self,100,8000,100,20); + SWWMUtility.DoExplosion(self,100,8000,100,20); A_AlertMonsters(2500); A_QuakeEx(4,4,4,20,0,400,"",QF_RELATIVE|QF_SCALEDOWN,falloff:100,rollIntensity:.9); if ( special1 ) @@ -394,7 +394,7 @@ Class FatChodeImpact : Actor override void PostBeginPlay() { Super.PostBeginPlay(); - SWWMHandler.DoExplosion(self,1500,40000,250,120,DE_THRUWALLS|DE_EXTRAZTHRUST); + SWWMUtility.DoExplosion(self,1500,40000,250,120,DE_THRUWALLS|DE_EXTRAZTHRUST); A_AlertMonsters(8000); A_QuakeEx(7,7,7,50,0,2000,"",QF_RELATIVE|QF_SCALEDOWN,falloff:800,rollIntensity:1.); A_StartSound("silverbullet/chode",CHAN_VOICE,CHANF_DEFAULT,1.,.35); @@ -528,7 +528,7 @@ Class FatChodeExplosionArm : Actor TNT1 A 1 { A_CountDown(); - SWWMHandler.DoExplosion(self,20+reactiontime,8000+1500*reactiontime,80+5*reactiontime,50,DE_THRUWALLS); + SWWMUtility.DoExplosion(self,20+reactiontime,8000+1500*reactiontime,80+5*reactiontime,50,DE_THRUWALLS); if ( level.IsPointInLevel(pos) ) { A_SprayDecal("HugeScorch",-32); @@ -596,9 +596,23 @@ Class SilverBullet : SWWMWeapon Screen.DrawTexture(AmmoIcon[0],false,bx-18,by-19,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_ColorOverlay,fcbselected?Color(128,0,0,0):Color(0,0,0,0)); Screen.DrawTexture(AmmoIcon[1],false,bx-18,by-41,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_ColorOverlay,fcbselected?Color(0,0,0,0):Color(128,0,0,0)); int cx = (Ammo1.Amount>9)?32:29; - Screen.DrawText(TewiFont,Font.CR_FIRE,bx-cx,by-18,String.Format("%d",Ammo1.Amount),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_ColorOverlay,fcbselected?Color(128,0,0,0):Color(0,0,0,0)); + int sb = Owner.CountInv("SilverBullets"); + if ( sb > 0 ) + { + int cbx = (sb>9)?34:30; + Screen.DrawText(TewiFont,Font.CR_FIRE,bx-cbx,by-21,String.Format("⁺%s",SWWMUtility.SuperscriptNum(sb)),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_Spacing,-1,DTA_ColorOverlay,fcbselected?Color(128,0,0,0):Color(0,0,0,0)); + Screen.DrawText(TewiFont,Font.CR_FIRE,bx-cx,by-14,String.Format("%d",Ammo1.Amount),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_ColorOverlay,fcbselected?Color(128,0,0,0):Color(0,0,0,0)); + } + else Screen.DrawText(TewiFont,Font.CR_FIRE,bx-cx,by-18,String.Format("%d",Ammo1.Amount),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_ColorOverlay,fcbselected?Color(128,0,0,0):Color(0,0,0,0)); cx = (Ammo2.Amount>9)?32:29; - Screen.DrawText(TewiFont,Font.CR_FIRE,bx-cx,by-40,String.Format("%d",Ammo2.Amount),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_ColorOverlay,fcbselected?Color(0,0,0,0):Color(128,0,0,0)); + sb = Owner.CountInv("SilverBullets2"); + if ( sb > 0 ) + { + int cbx = (sb>9)?34:30; + Screen.DrawText(TewiFont,Font.CR_FIRE,bx-cbx,by-43,String.Format("⁺%s",SWWMUtility.SuperscriptNum(sb)),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_Spacing,-1,DTA_ColorOverlay,fcbselected?Color(0,0,0,0):Color(128,0,0,0)); + Screen.DrawText(TewiFont,Font.CR_FIRE,bx-cx,by-36,String.Format("%d",Ammo2.Amount),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_ColorOverlay,fcbselected?Color(0,0,0,0):Color(128,0,0,0)); + } + else Screen.DrawText(TewiFont,Font.CR_FIRE,bx-cx,by-40,String.Format("%d",Ammo2.Amount),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_ColorOverlay,fcbselected?Color(0,0,0,0):Color(128,0,0,0)); } override void HudTick() { @@ -609,7 +623,7 @@ Class SilverBullet : SWWMWeapon override bool ReportHUDAmmo() { if ( (chambered && !fired) || (clipcount > 0) ) return true; - if ( (Ammo1.Amount <= 0) && (Ammo2.Amount <= 0) ) return false; + if ( (Ammo1.Amount <= 0) && (Ammo2.Amount <= 0) && (CountInv("SilverBullets") <= 0) && (CountInv("SilverBullets2") <= 0) ) return false; return Super.ReportHUDAmmo(); } override bool CheckAmmo( int firemode, bool autoswitch, bool requireammo, int ammocount ) @@ -620,7 +634,7 @@ Class SilverBullet : SWWMWeapon // allow player to still use the zoom even if there's no ammo left // (should work fine, assuming I've correctly interpreted the execution chain of all this stuff) if ( autoswitch && (fireMode == AltFire) ) return true; - return ((chambered && !fired) || (clipcount > 0) || (Ammo1.Amount > 0) || (Ammo2.Amount > 0)); + return ((chambered && !fired) || (clipcount > 0) || (Ammo1.Amount > 0) || (Ammo2.Amount > 0) || (CountInv("SilverBullets") > 0) || (CountInv("SilverBullets2") > 0)); } return Super.CheckAmmo(firemode,autoswitch,requireammo,ammocount); } @@ -692,7 +706,7 @@ Class SilverBullet : SWWMWeapon { int realdmg = t.HitList[i].HitDamage; SWWMDamageAccumulator.Accumulate(t.HitList[i].HitActor,realdmg,invoker,self,'shot',false,DMG_FOILINVUL); - SWWMHandler.DoKnockback(t.HitList[i].HitActor,t.HitList[i].x+(0,0,0.025),realdmg*20.*FRandom[SilverBullet](.8,1.2)); + SWWMUtility.DoKnockback(t.HitList[i].HitActor,t.HitList[i].x+(0,0,0.025),realdmg*20.*FRandom[SilverBullet](.8,1.2)); if ( t.HitList[i].HitActor.bNOBLOOD ) { let p = Spawn("SilverImpact",t.HitList[i].HitLocation); @@ -797,7 +811,7 @@ Class SilverBullet : SWWMWeapon if ( t.Results.HitType == TRACE_HitActor ) { int dmg = t.Results.HitActor.DamageMobj(invoker,self,1000,'shot',DMG_FOILINVUL|DMG_USEANGLE|DMG_THRUSTLESS,atan2(t.Results.HitVector.y,t.Results.HitVector.x)); - SWWMHandler.DoKnockback(t.Results.HitActor,t.Results.HitVector+(0,0,.025),dmg*20.*FRandom[SilverBullet](.8,1.2)); + SWWMUtility.DoKnockback(t.Results.HitActor,t.Results.HitVector+(0,0,.025),dmg*20.*FRandom[SilverBullet](.8,1.2)); if ( !t.Results.HitActor.bNOBLOOD ) { t.Results.HitActor.TraceBleed(dmg,self); @@ -899,7 +913,7 @@ Class SilverBullet : SWWMWeapon } double fact = invoker.fcbchambered?270000.:320000.; if ( player.mo.CanCrouch() && (player.crouchfactor != 1) ) fact /= 10.; - SWWMHandler.DoKnockback(self,-x,fact); + SWWMUtility.DoKnockback(self,-x,fact); } action void A_DropCasing( bool fcb = false ) { @@ -945,8 +959,9 @@ Class SilverBullet : SWWMWeapon int dropamt = invoker.clipcount-maxgiveamt; if ( dropamt > 0 ) ma.CreateTossable(dropamt); ma.Amount = min(ma.MaxAmount,ma.Amount+invoker.clipcount); - if ( CheckLocalView() ) for ( int i=0; i<(invoker.clipcount-dropamt); i++ ) ma.PrintPickupMessage(true,ma.PickupMessage()); - if ( (invoker.clipcount-dropamt) > 0 ) ma.PlayPickupSound(self); + ma.MagFill(); + if ( CheckLocalView() ) for ( int i=0; i 0 ) ma.PlayPickupSound(self); } invoker.ClipCount = 0; Vector3 x, y, z; @@ -983,8 +998,10 @@ Class SilverBullet : SWWMWeapon action void A_SwitchAmmoType( bool ifempty = false ) { bool oldsel = invoker.fcbselected; - if ( invoker.fcbselected && (invoker.Ammo1.Amount > 0) ) invoker.fcbselected = (ifempty&&(invoker.Ammo2.Amount>0))?true:false; - else if ( !invoker.fcbselected && (invoker.Ammo2.Amount > 0) ) invoker.fcbselected = (ifempty&&(invoker.Ammo1.Amount>0))?false:true; + if ( invoker.fcbselected && ((invoker.Ammo1.Amount > 0) || (CountInv("SilverBullets") > 0)) ) + invoker.fcbselected = (ifempty&&((invoker.Ammo2.Amount>0)||(CountInv("SilverBullets2")>0)))?true:false; + else if ( !invoker.fcbselected && ((invoker.Ammo2.Amount > 0) || (CountInv("SilverBullets2") > 0)) ) + invoker.fcbselected = (ifempty&&((invoker.Ammo1.Amount>0)||(CountInv("SilverBullets")>0)))?false:true; if ( oldsel != invoker.fcbselected ) A_StartSound("misc/invchange",CHAN_WEAPONEXTRA,CHANF_UI|CHANF_LOCAL); A_WeaponReady(WRF_NOFIRE); } @@ -1067,7 +1084,7 @@ Class SilverBullet : SWWMWeapon { if ( !invoker.chambered || invoker.fired ) { - if ( !invoker.fired && (invoker.clipcount <= 0) && (sv_infiniteammo || FindInventory('PowerInfiniteAmmo',true) || (invoker.Ammo1.Amount > 0) || (invoker.Ammo2.Amount > 0)) ) + if ( !invoker.fired && (invoker.clipcount <= 0) && (sv_infiniteammo || FindInventory('PowerInfiniteAmmo',true) || (invoker.Ammo1.Amount > 0) || (invoker.Ammo2.Amount > 0) || (CountInv("SilverBullets") > 0) || (CountInv("SilverBullets2") > 0)) ) { A_SwitchAmmoType(true); return ResolveState("Reload"); @@ -1275,7 +1292,7 @@ Class SilverBullet : SWWMWeapon Reload: XZW2 A 0 { - if ( ((invoker.clipcount >= invoker.default.clipcount) && (invoker.fcbselected == invoker.fcbloaded)) || (!sv_infiniteammo && !FindInventory('PowerInfiniteAmmo',true) && (invoker.Ammo1.Amount <= 0) && (invoker.Ammo2.Amount <= 0)) ) + if ( ((invoker.clipcount >= invoker.default.clipcount) && (invoker.fcbselected == invoker.fcbloaded)) || (!sv_infiniteammo && !FindInventory('PowerInfiniteAmmo',true) && (invoker.Ammo1.Amount <= 0) && (invoker.Ammo2.Amount <= 0) && (CountInv("SilverBullets") <= 0) && (CountInv("SilverBullets2") <= 0)) ) return ResolveState("Idle"); if ( invoker.zoomed ) { @@ -1328,9 +1345,20 @@ Class SilverBullet : SWWMWeapon XZW5 D 2 { invoker.fcbloaded = false; - invoker.clipcount = invoker.default.clipcount; - if ( !sv_infiniteammo && !FindInventory('PowerInfiniteAmmo',true) ) + if ( sv_infiniteammo || FindInventory('PowerInfiniteAmmo',true) ) + invoker.clipcount = invoker.default.clipcount; + else if ( invoker.Ammo1.Amount <= 0 ) + { + MagAmmo sb = MagAmmo(FindInventory("SilverBullets")); + int takeamt = min(sb.Amount,sb.ClipSize); + invoker.clipcount = takeamt; + sb.Amount -= takeamt; + } + else + { invoker.Ammo1.Amount = max(0,invoker.Ammo1.Amount-1); + invoker.clipcount = invoker.default.clipcount; + } A_SwitchAmmoType(true); A_StartSound("silverbullet/magin",CHAN_WEAPON,CHANF_OVERLAP); } @@ -1343,9 +1371,20 @@ Class SilverBullet : SWWMWeapon XZWB D 2 { invoker.fcbloaded = true; - invoker.clipcount = invoker.default.clipcount; - if ( !sv_infiniteammo && !FindInventory('PowerInfiniteAmmo',true) ) + if ( sv_infiniteammo || FindInventory('PowerInfiniteAmmo',true) ) + invoker.clipcount = invoker.default.clipcount; + else if ( invoker.Ammo2.Amount <= 0 ) + { + MagAmmo sb = MagAmmo(FindInventory("SilverBullets2")); + int takeamt = min(sb.Amount,sb.ClipSize); + invoker.clipcount = takeamt; + sb.Amount -= takeamt; + } + else + { invoker.Ammo2.Amount = max(0,invoker.Ammo2.Amount-1); + invoker.clipcount = invoker.default.clipcount; + } A_SwitchAmmoType(true); A_StartSound("silverbullet/magin",CHAN_WEAPON,CHANF_OVERLAP); } diff --git a/zscript/swwm_thinkers.zsc b/zscript/swwm_thinkers.zsc new file mode 100644 index 000000000..244e7f3d4 --- /dev/null +++ b/zscript/swwm_thinkers.zsc @@ -0,0 +1,1066 @@ +// various stat tracking thinkers and others + + +// 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; +} + +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; + double grounddist, airdist, swimdist, fuelusage, topspeed; + Array wstats; + Array mstats; + Array lstats; + Array > alreadygot; + int favweapon; + + bool GotWeapon( Class which ) + { + for ( int i=0; i 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; + 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; + lstats.Push(ls); + } + + void AddWeaponKill( Actor inflictor, Actor victim ) + { + if ( victim ) + { + bool found = false; + for ( int i=0; i which = myplayer.ReadyWeapon?myplayer.ReadyWeapon.GetClass():null; + if ( inflictor is 'Weapon' ) which = Weapon(inflictor).GetClass(); + // 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') ) which = 'Ynykron'; + else if ( (inflictor is 'Demolitionist') || (inflictor is 'DemolitionistShockwave') || (inflictor is 'DemolitionistRadiusShockwave') ) which = 'SWWMWeapon'; // hack to assume Demolitionist as weapon + 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; + + bool DirectAdd( 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 ~== "AmmoFabricator" ) return true; // not yet introduced + } + if ( !(gameinfo.gametype&GAME_Strife) ) + { + if ( ref ~== "TheOrder" ) return true; + if ( ref ~== "TheFront" ) 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); + 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_GHOULHUNT" ) + text = "SWWM_LORETXT_GHOULHUNT2"; // met anthon anderken during the + 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 + } + 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 + } + // check that it's valid + if ( StringTable.Localize(tag,false) == tag ) return false; + if ( StringTable.Localize(tab,false) == tab ) + { + Console.Printf("Entry \"%s\" defines no tab.",ref); + return false; + } + if ( StringTable.Localize(text,false) == text ) + { + Console.Printf("Entry \"%s\" defines no text.",ref); + return false; + } + // 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; + // sorted add + String loca = StringTable.Localize(e.tag), locb; + int cpa, cpb; + for ( int i=0; i= 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 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; + int tcnt; + double height; + transient CVar funtags; + + void UpdateTag() + { + if ( !funtags ) funtags = CVar.GetCVar('swwm_funtags',players[consoleplayer]); + if ( mytarget && (mytarget.player || mytarget.bISMONSTER || (mytarget is 'BossBrain')) ) + { + String realtag = funtags.GetBool()?SWWMUtility.GetFunTag(mytarget,"AWESOME IT'S PENIS"):mytarget.GetTag("AWESOME IT'S PENIS"); + if ( realtag == "AWESOME IT'S PENIS" ) + { + 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 = t.mytarget.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; + if ( hnd.trackers ) hnd.trackers.prev = t; + hnd.trackers = t; + hnd.trackers_cnt++; + return t; + } + + override void OnDestroy() + { + let hnd = SWWMHandler(EventHandler.Find("SWWMHandler")); + if ( hnd ) + { + hnd.trackers_cnt--; + if ( !prev ) + { + hnd.trackers = next; + if ( next ) next.prev = null; + } + else + { + prev.next = next; + if ( next ) next.prev = prev; + } + } + Super.OnDestroy(); + } + + override void Tick() + { + if ( swwm_notrack ) + { + Destroy(); + return; + } + // don't update + if ( mytarget && mytarget.bDORMANT ) + return; + // update + if ( mytarget && ((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)); + } + if ( !mytarget || (mytarget.Health <= 0) ) + { + // we're done + if ( updated > level.maptime ) updated = level.maptime; + lasthealth = 0; + intp.Update(lasthealth); + if ( level.maptime > updated+35 ) Destroy(); + return; + } + // only update height while alive + height = mytarget.height; + tcnt++; + if ( (tcnt == 1) && !mytarget.player ) + { + // post-spawn health inflation check + if ( lasthealth > maxhealth ) maxhealth = 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; + } + } + lasthealth = mytarget.Health; + intp.Update(lasthealth); + if ( (mytarget.bISMONSTER || mytarget.player) && !mytarget.bINVISIBLE ) + { + bool straifu = false; + if ( (gameinfo.gametype&GAME_Strife) && (!mytarget.bINCOMBAT && !mytarget.bJUSTATTACKED) || (mytarget is 'Beggar') || (mytarget is 'Peasant') ) + straifu = true; + // enemies within 2000mu that have us as target + if ( !straifu && mytarget.target && (mytarget.target.Health > 0) && (mytarget.target.player == players[consoleplayer]) && (mytarget.Vec3To(mytarget.target).length() < 2000) && mytarget.CheckSight(mytarget.target) ) updated = level.maptime+70; + // players (but not voodoo dolls), always visible in sp/coop + if ( !deathmatch && mytarget.player && (mytarget.player.mo == mytarget) ) updated = level.maptime+35; + // any visible enemies within 600mu + if ( (mytarget.Vec3To(players[consoleplayer].mo).length() < 600) && players[consoleplayer].mo.CheckSight(mytarget) ) updated = level.maptime; + } + else if ( mytarget is 'BossBrain' ) + { + // teh romero, only if visible + if ( (mytarget.Vec3To(players[consoleplayer].mo).length() < 600) && players[consoleplayer].mo.CheckSight(mytarget) ) 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(); + } +} + +// Gotta force it someway +Class ForcedFallerDamager : Thinker +{ + Actor mybody, instigator; + double lastvelz; + bool wasflying; + + static void TrackBody( Actor b ) + { + if ( !b ) return; + let ffd = new("ForcedFallerDamager"); + ffd.ChangeStatNum(STAT_USER); + ffd.mybody = b; + ffd.lastvelz = b.vel.z; + ffd.wasflying = ((b.pos.z>b.floorz)&&b.TestMobjZ()); + } + static void SetInstigator( Actor b, Actor whomst ) + { + if ( !b || !whomst ) return; + let ti = ThinkerIterator.Create("ForcedFallerDamager",STAT_USER); + ForcedFallerDamager ffd; + while ( ffd = ForcedFallerDamager(ti.Next()) ) + { + if ( ffd.mybody != b ) continue; + ffd.instigator = whomst; + break; + } + } + override void Tick() + { + if ( !swwm_doomfall || !mybody ) + { + Destroy(); + return; + } + bool isflying = ((mybody.pos.z>mybody.floorz)&&mybody.TestMobjZ())||mybody.bNOGRAVITY; + if ( wasflying && !isflying && (lastvelz < -23) && !level.monsterfallingdamage && !(mybody.floorsector.flags&Sector.SECF_NOFALLINGDAMAGE) ) + { + // big hurt + int dmg; + if ( lastvelz < -35 ) dmg = Actor.TELEFRAG_DAMAGE; + else dmg = int((-lastvelz-23)*6); + mybody.DamageMobj(instigator,instigator,dmg,'Falling'); + } + if ( !isflying && wasflying ) instigator = null; + wasflying = isflying; + lastvelz = mybody.vel.z; + // wait until body is dead + if ( mybody.Health > 0 ) return; + Destroy(); + } +} + +Class SWWMDamageAccumulator : Thinker +{ + Actor victim, inflictor, source; + Array 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=0; i-- ) + { + int d = int(val/(10**i))%10; + str.AppendCharacter(digs[d]); + } + return str; + } + + static clearscope void BeautifyClassName( out String str ) + { + String workstr = str; + str.Truncate(0); + workstr.Replace("_"," "); + int len = workstr.CodePointCount(); + for ( int i=0; i double.epsilon); + } + + // box intersection check, for collision detection + static clearscope bool BoxIntersect( Actor a, Actor b, Vector3 ofs = (0,0,0), int pad = 0 ) + { + Vector3 diff = level.Vec3Diff(level.Vec3Offset(a.pos,ofs),b.pos); + if ( (abs(diff.x) > (a.radius+b.radius+pad)) || (abs(diff.y) > (a.radius+b.radius+pad)) ) return false; + if ( (diff.z > a.height+pad) || (diff.z < -(b.height+pad)) ) return false; + return true; + } + + // extruded box intersection check, useful when checking things that might be hit along a path + static clearscope bool ExtrudeIntersect( Actor a, Actor b, Vector3 range, int steps, int pad = 0 ) + { + if ( steps <= 0 ) return BoxIntersect(a,b,pad:pad); + double step = 1./steps; + for ( double i=step; i<=1.; i+=step ) + { + if ( BoxIntersect(a,b,range*i,pad) ) + return true; + } + return false; + } + + // sphere intersection check, useful for proximity detection + static clearscope bool SphereIntersect( Actor a, Vector3 p, double radius ) + { + Vector3 ap = p+level.Vec3Diff(p,a.pos); // portal-relative actor position + Vector3 amin = ap+(-a.radius,-a.radius,0), + amax = ap+(a.radius,a.radius,a.height); + double distsq = 0.; + if ( p.x < amin.x ) distsq += (amin.x-p.x)**2; + if ( p.x > amax.x ) distsq += (p.x-amax.x)**2; + if ( p.y < amin.y ) distsq += (amin.y-p.y)**2; + if ( p.y > amax.y ) distsq += (p.y-amax.y)**2; + if ( p.z < amin.z ) distsq += (amin.z-p.z)**2; + if ( p.z > amax.z ) distsq += (p.z-amax.z)**2; + return (distsq <= (radius**2)); + } + + // THANKS FOR NOT GIVING US ANY OTHER WAY TO CHECK IF A LOCK NUMBER IS VALID + static clearscope bool IsValidLockNum( int l ) + { + if ( (l < 1) || (l > 255) ) return true; + Array valid; + valid.Clear(); + for ( int i=0; i lines; + lines.Clear(); + data.Split(lines,"\n"); + 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.Push(spl[1].ToInt()); + } + } + } + for ( int i=0; i= 10) && (l.special <= 13)) + || (!part && (l.special >= 20) && (l.special <= 25)) + || (!part && (l.special == 28)) + || ((l.special >= 29) && (l.special <= 30)) + || (!part && (l.special >= 35) && (l.special <= 37)) + || (part && (l.special >= 40) && (l.special <= 45)) + || (!part && (l.special == 46)) + || (part && (l.special == 47)) + || (!part && (l.special >= 60) && (l.special <= 68)) + || (part && (l.special == 69)) + || ((l.special >= 94) && (l.special <= 96)) + || (part && (l.special == 97)) + || (!part && (l.special == 99)) + || (part && (l.special == 104)) + || (part && (l.special >= 105) && (l.special <= 106)) + || (part && (l.special >= 168) && (l.special <= 169)) + || (!part && (l.special == 172)) + || (part && (l.special >= 192) && (l.special <= 199)) + || (!part && (l.special == 200)) + || (part && (l.special >= 201) && (l.special <= 202)) + || (!part && (l.special == 203)) + || (part && (l.special == 205)) + || (!part && (l.special >= 206) && (l.special <= 207)) + || (!part && (l.special == 228)) + || (!part && (l.special >= 230) && (l.special <= 231)) + || (!part && (l.special >= 238) && (l.special <= 242)) + || ((l.special >= 245) && (l.special <= 247)) + || (part && (l.special == 249)) + || (!part && (l.special >= 250) && (l.special <= 251)) + || (part && (l.special >= 251) && (l.special <= 255)) + || (!part && (l.special >= 256) && (l.special <= 261)) + || (part && (l.special >= 262) && (l.special <= 269)) + || (!part && (l.special == 275)) + || (part && (l.special == 276)) + || (!part && (l.special == 279)) + || (part && (l.special == 280)) ) + { + let si = level.CreateSectorTagIterator(l.Args[0],l); + int idx; + while ( (idx = si.Next()) != -1 ) + if ( level.Sectors[idx] == s ) + return true; + } + } + let ti = ThinkerIterator.Create("Actor"); + Actor a; + while ( a = Actor(ti.Next()) ) + { + if ( !a.special || !a.Args[0] ) continue; + if ( (part && (a.special >= 10) && (a.special <= 13)) + || (!part && (a.special >= 20) && (a.special <= 25)) + || (!part && (a.special == 28)) + || ((a.special >= 29) && (a.special <= 30)) + || (!part && (a.special >= 35) && (a.special <= 37)) + || (part && (a.special >= 40) && (a.special <= 45)) + || (!part && (a.special == 46)) + || (part && (a.special == 47)) + || (!part && (a.special >= 60) && (a.special <= 68)) + || (part && (a.special == 69)) + || ((a.special >= 94) && (a.special <= 96)) + || (part && (a.special == 97)) + || (!part && (a.special == 99)) + || (part && (a.special == 104)) + || (part && (a.special >= 105) && (a.special <= 106)) + || (part && (a.special >= 168) && (a.special <= 169)) + || (!part && (a.special == 172)) + || (part && (a.special >= 192) && (a.special <= 199)) + || (!part && (a.special == 200)) + || (part && (a.special >= 201) && (a.special <= 202)) + || (!part && (a.special == 203)) + || (part && (a.special == 205)) + || (!part && (a.special >= 206) && (a.special <= 207)) + || (!part && (a.special == 228)) + || (!part && (a.special >= 230) && (a.special <= 231)) + || (!part && (a.special >= 238) && (a.special <= 242)) + || ((a.special >= 245) && (a.special <= 247)) + || (part && (a.special == 249)) + || (!part && (a.special >= 250) && (a.special <= 251)) + || (part && (a.special >= 251) && (a.special <= 255)) + || (!part && (a.special >= 256) && (a.special <= 261)) + || (part && (a.special >= 262) && (a.special <= 269)) + || (!part && (a.special == 275)) + || (part && (a.special == 276)) + || (!part && (a.special == 279)) + || (part && (a.special == 280)) ) + { + let si = level.CreateSectorTagIterator(a.Args[0]); + int idx; + while ( (idx = si.Next()) != -1 ) + if ( level.Sectors[idx] == s ) + return true; + } + } + return false; + } + + // because GetTag() returns the localized string, we need to do things the hard way + static clearscope String GetFunTag( Actor a, String defstr = "" ) + { + if ( a is 'SWWMMonster' ) return SWWMMonster(a).GetFunTag(defstr); + int ntags = 1; + String basetag = ""; + switch ( a.GetClassName() ) + { + // Doom + case 'ZombieMan': + case 'StealthZombieMan': + basetag = "ZOMBIE"; + break; + case 'ShotgunGuy': + case 'StealthShotgunGuy': + basetag = "SHOTGUN"; + break; + case 'ChaingunGuy': + case 'StealthChaingunGuy': + basetag = "HEAVY"; + break; + case 'DoomImp': + case 'StealthDoomImp': + basetag = "IMP"; + break; + case 'Demon': + case 'StealthDemon': + basetag = "DEMON"; + break; + case 'Spectre': + basetag = "SPECTRE"; + break; + case 'LostSoul': + basetag = "LOST"; + break; + case 'Cacodemon': + case 'StealthCacodemon': + basetag = "CACO"; + break; + case 'HellKnight': + case 'StealthHellKnight': + basetag = "HELL"; + break; + case 'BaronOfHell': + case 'StealthBaron': + basetag = "BARON"; + break; + case 'Arachnotron': + case 'StealthArachnotron': + basetag = "ARACH"; + break; + case 'PainElemental': + basetag = "PAIN"; + break; + case 'Revenant': + case 'StealthRevenant': + basetag = "REVEN"; + break; + case 'Fatso': + case 'StealthFatso': + basetag = "MANCU"; + break; + case 'Archvile': + case 'StealthArchvile': + basetag = "ARCH"; + break; + case 'SpiderMastermind': + basetag = "SPIDER"; + break; + case 'Cyberdemon': + basetag = "CYBER"; + break; + case 'BossBrain': + basetag = "BOSSBRAIN"; + break; + case 'WolfensteinSS': + basetag = "WOLFSS"; + break; + case 'CommanderKeen': + basetag = "KEEN"; + break; + case 'MBFHelperDog': + basetag = "DOG"; + break; + // Heretic + case 'Chicken': + basetag = "CHICKEN"; + break; + case 'Beast': + basetag = "BEAST"; + break; + case 'Clink': + basetag = "CLINK"; + break; + case 'Sorcerer1': + case 'Sorcerer2': + basetag = "DSPARIL"; + break; + case 'HereticImp': + case 'HereticImpLeader': + basetag = "HERETICIMP"; + break; + case 'Ironlich': + basetag = "IRONLICH"; + break; + case 'Knight': + case 'KnightGhost': + basetag = "BONEKNIGHT"; + break; + case 'Minotaur': + case 'MinotaurFriend': + basetag = "MINOTAUR"; + break; + case 'Mummy': + case 'MummyGhost': + basetag = "MUMMY"; + break; + case 'MummyLeader': + case 'MummyLeaderGhost': + basetag = "MUMMYLEADER"; + break; + case 'Snake': + basetag = "SNAKE"; + break; + case 'Wizard': + basetag = "WIZARD"; + break; + // Hexen + case 'FireDemon': + basetag = "FIREDEMON"; + break; + case 'Demon1': + case 'Demon1Mash': + case 'Demon2': + case 'Demon2Mash': + basetag = "DEMON1"; + break; + case 'Ettin': + case 'EttinMash': + basetag = "ETTIN"; + break; + case 'Centaur': + case 'CentaurMash': + basetag = "CENTAUR"; + break; + case 'CentaurLeader': + basetag = "SLAUGHTAUR"; + break; + case 'Bishop': + basetag = "BISHOP"; + break; + case 'IceGuy': + basetag = "ICEGUY"; + break; + case 'Serpent': + case 'SerpentLeader': + basetag = "SERPENT"; + break; + case 'Wraith': + case 'WraithBuried': + basetag = "WRAITH"; + break; + case 'Dragon': + basetag = "DRAGON"; + break; + case 'Korax': + basetag = "KORAX"; + break; + case 'FighterBoss': + basetag = "FBOSS"; + break; + case 'MageBoss': + basetag = "MBOSS"; + break; + case 'ClericBoss': + basetag = "CBOSS"; + break; + case 'Heresiarch': + basetag = "HERESIARCH"; + break; + } + if ( basetag == "" ) return a.GetTag(defstr); + String funtag = "FN_"..basetag.."_FUN"; + String lfuntag = StringTable.Localize(funtag,false); + if ( lfuntag != funtag ) return lfuntag; + String nfuntag = "FN_"..basetag.."_FUNN"; + String lnfuntag = StringTable.Localize(nfuntag,false); + if ( lnfuntag == nfuntag ) return a.GetTag(defstr); + ntags = lnfuntag.ToInt(); + return StringTable.Localize(String.Format("$FN_%s_FUN%d",basetag,Random[FunTags](1,ntags))); + } + + // Apply full 3D knockback in a specific direction, useful for hitscan + static play void DoKnockback( Actor Victim, Vector3 HitDirection, double MomentumTransfer ) + { + if ( !Victim ) + return; + if ( !Victim.bSHOOTABLE && !Victim.bVULNERABLE ) + return; + if ( Victim.bDONTTHRUST || (Victim.Mass >= Actor.LARGE_MASS) ) + return; + Vector3 Momentum = HitDirection*MomentumTransfer; + if ( (Victim.pos.z <= Victim.floorz) || !Victim.TestMobjZ() ) + Momentum.z = max(Momentum.z,.1*Momentum.length()); + Momentum /= Thinker.TICRATE*max(50,Victim.Mass); + Victim.vel += Momentum; + } + + // complete spherical and more accurate replacement of A_Explode + // 100% free of the buggery GZDoom's own splash damage has + static play void DoExplosion( Actor Source, double Damage, double MomentumTransfer, double ExplosionRadius, double FullDamageRadius = 0., int flags = 0, Name DamageType = '', Actor ignoreme = null ) + { + // debug, display radius sphere + if ( swwm_explosiondebug ) + { + let s = Actor.Spawn("RadiusDebugSphere",Source.pos); + s.Scale *= ExplosionRadius; + s.SetShade((Damage>0)?"Green":"Blue"); + if ( FullDamageRadius > 0. ) + { + let s = Actor.Spawn("RadiusDebugSphere",Source.pos); + s.Scale *= FullDamageRadius; + s.SetShade("Red"); + } + } + if ( !(flags&DE_NOSPLASH) ) Source.CheckSplash(ExplosionRadius); + double brange = 1./(ExplosionRadius-FullDamageRadius); + Actor Instigator = (flags&DE_NOTMISSILE)?Source:Source.target; + BlockThingsIterator bi = BlockThingsIterator.Create(Source,ExplosionRadius*2); // test with doubled radius, just to be sure + while ( bi.Next() ) + { + Actor a = bi.Thing; + // early checks for self and ignored actor (usually the instigator) + if ( !a || (a == ignoreme) || (a == Source) ) + continue; + // can't be affected + if ( !a.bSHOOTABLE && !a.bVULNERABLE ) + continue; + // no blasting if no radius dmg (unless forced) + if ( a.bNORADIUSDMG && !Source.bFORCERADIUSDMG ) + continue; + // check the DONTHARMCLASS/DONTHARMSPECIES flags + if ( !a.player && ((Source.bDONTHARMCLASS && (a.GetClass() == Source.GetClass())) || (Source.bDONTHARMSPECIES && (a.GetSpecies() == Source.GetSpecies()))) ) + continue; + // can we see it + if ( !(flags&DE_THRUWALLS) && !Source.CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) + continue; + // intersecting? + if ( !SWWMUtility.SphereIntersect(a,Source.pos,ExplosionRadius) ) + continue; + // calculate factor + Vector3 dir = level.Vec3Diff(Source.pos,a.Vec3Offset(0,0,a.Height/2)); + double dist = dir.length(); + // intersecting, randomize direction + if ( dir.length() <= double.epsilon ) + { + double ang = FRandom[DoBlast](0,360); + double pt = FRandom[DoBlast](-90,90); + dir = (cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt)); + } + dir /= dist; + dist = clamp(dist-FullDamageRadius,0,min(dist,ExplosionRadius)); + double damagescale = 1.-clamp((dist-a.Radius)*brange,0.,1.); + double mm = MomentumTransfer*damagescale; + // no knockback if massive/unpushable + if ( (abs(mm) > 0.) && !a.bDORMANT && !a.bDONTTHRUST && (a.Mass < Actor.LARGE_MASS) ) + { + Vector3 Momentum = dir*mm; + if ( (a.pos.z <= a.floorz) || !a.TestMobjZ() ) + Momentum.z = max(Momentum.z,(flags&DE_EXTRAZTHRUST?.4:.1)*Momentum.length()); + Momentum /= Thinker.TICRATE*max(50,a.Mass); // prevent tiny things from getting yeeted at warp speed + a.vel += Momentum; + if ( (flags&DE_BLAST) && a.bCANBLAST && !a.bDONTBLAST ) a.bBLASTED = true; + } + // hit it + int dmg = int(Damage*damagescale); + if ( dmg <= 0 ) continue; // no harm + int ndmg = a.DamageMobj(Instigator,Source,dmg,(DamageType!='')?Source.DamageType:DamageType,DMG_EXPLOSION,atan2(-dir.y,-dir.x)); + if ( !(flags&DE_NOBLEED) ) a.TraceBleed((ndmg>0)?ndmg:dmg,Source); + } + // TODO destructible geometry support + } +} + +Class RadiusDebugSphere : Actor +{ + Default + { + RenderStyle "AddStencil"; + StencilColor "White"; + Radius .1; + Height 0.; + +NOGRAVITY; + +NOINTERACTION; + } + States + { + Spawn: + XZW1 A 1 BRIGHT A_FadeOut(); + Wait; + } +}