swwmgz_m/zscript/handler/swwm_handler_worldthings.zsc

440 lines
16 KiB
Text

// WorldThing* events (excluding damage)
extend Class SWWMHandler
{
// prevents revived monsters from spawning in more golden shells
Array<Actor> alreadygold;
// attempt to optimize Ynykron singularity suction
Array<Actor> suckableactors;
// for displaying beam-type projectiles
Array<Actor> beams;
override void WorldThingRevived( WorldEvent e )
{
if ( profiling ) curms = MSTime();
// reattach combat tracker
if ( !swwm_notrack && (e.Thing.bSHOOTABLE || e.Thing.bISMONSTER) && !(e.Thing is 'LampMoth') && !(e.Thing is 'CompanionLamp') )
SWWMCombatTracker.Spawn(e.Thing);
// reattach headpats
if ( SWWMUtility.IdentifyingDog(e.Thing) || SWWMUtility.IdentifyingCaco(e.Thing)
|| SWWMUtility.IdentifyingDrug(e.Thing) || SWWMUtility.IdentifyingDoubleBoi(e.Thing) )
{
// you can pet the dog, and you can also pet the caco (and friends)
let hp = Actor.Spawn("HeadpatTracker",e.Thing.pos);
hp.target = e.Thing;
}
if ( !(e.Thing is 'PlayerPawn') )
{
if ( profiling ) worldthingrevived_ms += MSTime()-curms;
return;
}
// reset some vars
if ( e.Thing.playernumber() != -1 )
{
multilevel[e.Thing.playernumber()] = 0;
spreecount[e.Thing.playernumber()] = 0;
tookdamage[e.Thing.playernumber()] = false;
lastkill[e.Thing.playernumber()] = int.min;
}
// reset uptime since player had just died
SWWMStats s = SWWMStats.Find(e.Thing.player);
if ( s ) s.lastspawn = level.totaltime;
if ( profiling ) worldthingrevived_ms += MSTime()-curms;
}
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;
}
static bool ShouldSpawnGold()
{
int totalneeded = 0;
// check "free space" in player inventories
for ( int i=0; i<MAXPLAYERS; i++ )
{
if ( !playeringame[i] || !players[i].mo ) continue;
let cg = players[i].mo.FindInventory("GoldShell");
if ( cg ) totalneeded += cg.MaxAmount-cg.Amount;
else totalneeded = GetDefaultByType("GoldShell").MaxAmount;
}
// subtract any shells already in the world
let ti = ThinkerIterator.Create("GoldShell");
GoldShell g;
while ( g = GoldShell(ti.Next()) )
{
if ( g.Owner ) continue;
totalneeded -= g.Amount;
}
return (totalneeded > 0);
}
override void WorldThingDied( WorldEvent e )
{
if ( profiling ) curms = MSTime();
if ( e.Thing.default.bISMONSTER && ((e.Thing.default.bBOSS) || (e.Thing.GetSpawnHealth() >= 1000) || e.Thing.FindInventory("BossMarker")) && (alreadygold.Find(e.Thing) == alreadygold.Size()) )
{
// make sure we can't farm drops from revivable enemies
// (or cause some things to spam-spawn gold shells)
// (*cough* Touhou Doom *cough*)
alreadygold.Push(e.Thing);
// weight drop chance based on total count of this monster type
// guarantees that maps that get a bit slaughtery won't become easy farms for drops
int dropweight = 0;
let ti = ThinkerIterator.Create(e.Thing.GetClass());
while ( ti.Next() ) dropweight++;
int minchance = max(1,6-(e.Thing.GetSpawnHealth()/1000));
dropweight = max(minchance,dropweight/4);
// make sure the gold shell is "worth spawning", too
if ( !Random[GoldDrop](0,dropweight) && ShouldSpawnGold() )
{
let g = Actor.Spawn("GoldShell",e.Thing.Vec3Offset(0,0,e.Thing.Height/2));
double ang = FRandom[SpareShells](0,360);
g.vel.xy = (cos(ang),sin(ang))*FRandom[SpareShells](.4,.8);
g.vel.z = FRandom[SpareShells](2.,4.);
}
}
// Korax instakill
if ( (e.Thing is 'Korax') && !e.Thing.special2 && HexenMap40() )
{
e.Thing.special2 = 1;
// terminate the monster closet scripts, open all the
// doors ourselves
level.ExecuteSpecial(ACS_Terminate,e.Thing,null,false,220);
level.ExecuteSpecial(ACS_Terminate,e.Thing,null,false,220);
level.ExecuteSpecial(ACS_Terminate,e.Thing,null,false,221);
level.ExecuteSpecial(ACS_Terminate,e.Thing,null,false,255);
level.ExecuteSpecial(Door_Open,e.Thing,null,false,10,16);
level.ExecuteSpecial(Door_Open,e.Thing,null,false,11,16);
level.ExecuteSpecial(Door_Open,e.Thing,null,false,12,16);
level.ExecuteSpecial(Door_Open,e.Thing,null,false,13,16);
level.ExecuteSpecial(Door_Open,e.Thing,null,false,14,16);
level.ExecuteSpecial(Door_Open,e.Thing,null,false,10,16);
// keep the portal closed, you can't leave unless you
// kill everyone else
let t = new("UglyBoyGetsFuckedUp");
t.ChangeStatNum(Thinker.STAT_USER);
}
// Archangelus death
if ( e.Thing.GetClassName() == "ArchangelusB" )
{
// kill all other monsters
let ti = ThinkerIterator.Create("Actor");
Actor a;
while ( a = Actor(ti.Next()) )
{
if ( (a != e.Thing) && (a.Health > 0) && (a.bBossSpawned || a.bCOUNTKILL) )
a.DamageMobj(e.Thing,e.Thing,a.Health,'EndMii',DMG_FORCED|DMG_THRUSTLESS);
}
}
if ( swwm_partytime )
{
let pt = Actor.Spawn("PartyTime",e.Thing.pos);
pt.target = e.Thing;
}
// force insert gib animations on some vanilla Doom monsters
int gibhealth = e.Thing.GetGibHealth();
bool gotgibbed = (!e.Thing.bDONTGIB && ((e.Inflictor && e.Inflictor.bEXTREMEDEATH) || (e.DamageSource && e.DamageSource.bEXTREMEDEATH) || (e.DamageType == 'Extreme') || (e.Thing.Health < gibhealth)) && (!e.Inflictor || !e.Inflictor.bNOEXTREMEDEATH) && (!e.DamageSource || !e.DamageSource.bNOEXTREMEDEATH));
if ( !gotgibbed )
{
if ( profiling ) worldthingdied_ms += MSTime()-curms;
return;
}
if ( (e.Thing.GetClass() == "Demon") || (e.Thing.GetClass() == "Spectre") )
ExtraGibDeaths.GibThis(e.Thing,"DemonXDeath");
else if ( e.Thing.GetClass() == "HellKnight" )
ExtraGibDeaths.GibThis(e.Thing,"KnightXDeath");
else if ( e.Thing.GetClass() == "BaronOfHell" )
ExtraGibDeaths.GibThis(e.Thing,"BaronXDeath");
else if ( e.Thing.GetClass() == "Cacodemon" )
ExtraGibDeaths.GibThis(e.Thing,"CacoXDeath");
else if ( e.Thing.GetClass() == "Revenant" )
ExtraGibDeaths.GibThis(e.Thing,"BonerXDeath");
else if ( e.Thing.GetClass() == "Archvile" )
ExtraGibDeaths.GibThis(e.Thing,"VileXDeath");
else if ( e.Thing.GetClass() == "Arachnotron" )
ExtraGibDeaths.GibThis(e.Thing,"ArachXDeath");
else if ( e.Thing.GetClass() == "Fatso" )
ExtraGibDeaths.GibThis(e.Thing,"FatsoXDeath");
if ( profiling ) worldthingdied_ms += MSTime()-curms;
}
private void DoKeyTagFix( Actor a )
{
if ( a is 'SWWMKey' ) return; // mod's custom keys are fine
if ( a is 'RedCard' ) a.SetTag("$T_REDCARD");
else if ( a is 'BlueCard' ) a.SetTag("$T_BLUECARD");
else if ( a is 'YellowCard' ) a.SetTag("$T_YELLOWCARD");
else if ( a is 'RedSkull' ) a.SetTag("$T_REDSKULL");
else if ( a is 'BlueSkull' ) a.SetTag("$T_BLUESKULL");
else if ( a is 'YellowSkull' ) a.SetTag("$T_YELLOWSKULL");
else if ( a is 'KeyYellow' ) a.SetTag("$T_YELLOWKEY");
else if ( a is 'KeyGreen' ) a.SetTag("$T_GREENKEY");
else if ( a is 'KeyBlue' ) a.SetTag("$T_BLUEKEY");
else if ( a.GetClassName() == 'KeyRed' ) a.SetTag("$T_REDKEY");
else if ( a is 'KeySteel' ) a.SetTag("$T_KEYSTEEL");
else if ( a is 'KeyCave' ) a.SetTag("$T_KEYCAVE");
else if ( a is 'KeyAxe' ) a.SetTag("$T_KEYAXE");
else if ( a is 'KeyFire' ) a.SetTag("$T_KEYFIRE");
else if ( a is 'KeyEmerald' ) a.SetTag("$T_KEYEMERALD");
else if ( a is 'KeyDungeon' ) a.SetTag("$T_KEYDUNGEON");
else if ( a is 'KeySilver' ) a.SetTag("$T_KEYSILVER");
else if ( a is 'KeyRusted' ) a.SetTag("$T_KEYRUSTED");
else if ( a is 'KeyHorn' ) a.SetTag("$T_KEYHORN");
else if ( a is 'KeySwamp' ) a.SetTag("$T_KEYSWAMP");
else if ( a is 'KeyCastle' ) a.SetTag("$T_KEYCASTLE");
}
// tempfix keys have no tags
static void KeyTagFix( Actor a )
{
let hnd = SWWMHandler(Find("SWWMHandler"));
if ( hnd ) hnd.DoKeyTagFix(a);
}
// copies the floatbob of overlapping identical items, so it doesn't look weird
private void CopyFloatBob( Actor a )
{
let bt = BlockThingsIterator.Create(a,16);
while ( bt.Next() )
{
let t = bt.Thing;
if ( !t || (t == a) || !(t is 'Inventory') || !(t.spawnpoint ~== a.spawnpoint) ) continue;
a.floatbobphase = t.floatbobphase;
a.angle = t.angle; // also copy angle
break;
}
}
private bool SuppressMultiItem( WorldEvent e )
{
// quick checks
if ( !G_SkillPropertyInt(SKILLP_SpawnMulti) || multiplayer || sv_alwaysspawnmulti )
return false;
// is it coop inventory? suppress it
if ( (e.Thing.spawnflags&(MTF_COOPERATIVE|MTF_DEATHMATCH)) && !(e.Thing.spawnflags&MTF_SINGLE) && ((e.Thing is 'Inventory') || (e.Thing is 'MaceSpawner')) )
{
e.Thing.ClearCounters();
e.Thing.Destroy();
return true;
}
return false;
}
override void WorldThingDestroyed( WorldEvent e )
{
if ( profiling ) curms = MSTime();
if ( !e.Thing.default.bSHOOTABLE && !e.Thing.default.bMISSILE && !(e.Thing is 'Inventory') && !SWWMUtility.IsBeamProj(e.Thing) )
{
if ( profiling ) worldthingdestroyed_ms += MSTime()-curms;
return;
}
// remove from suckables
int pos = suckableactors.Find(e.Thing);
if ( pos < suckableactors.Size() )
suckableactors.Delete(pos);
// remove from beams
pos = beams.Find(e.Thing);
if ( pos < beams.Size() )
beams.Delete(pos);
if ( profiling ) worldthingdestroyed_ms += MSTime()-curms;
}
override void WorldThingSpawned( WorldEvent e )
{
if ( profiling ) curms = MSTime();
if ( !e.Thing || SuppressMultiItem(e) )
{
if ( profiling ) worldthingspawned_ms += MSTime()-curms;
return;
}
IWantDieSpawn(e);
if ( (e.Thing is 'TeleportDest') || (e.Thing is 'BossTarget') )
{
let d = Actor.Spawn("SWWMTeleportDest",e.Thing.pos);
d.bNOGRAVITY = e.Thing.bNOGRAVITY;
}
if ( e.Thing is 'Inventory' )
{
CopyFloatBob(e.Thing);
if ( (Inventory(e.Thing).PickupFlash is 'SWWMPickupFlash') && swwm_itemglows )
{
let p = Actor.Spawn(Inventory(e.Thing).PickupFlash,e.Thing.Vec3Offset(0,0,16));
p.target = e.Thing;
p.SetStateLabel("Pickup");
}
}
if ( swwm_doomfall && e.Thing.bISMONSTER )
e.Thing.bFALLDAMAGE = true;
if ( e.Thing is 'Key' )
{
DoKeyTagFix(e.Thing);
SWWMInterest.Spawn(thekey:Key(e.Thing));
}
if ( indoomvacation == -1 ) indoomvacation = SWWMUtility.InDoomVacation();
if ( inultdoom2 == -1 ) inultdoom2 = SWWMUtility.IsUltDoom2();
if ( e.Thing.GetClass() == 'Pig' )
e.Thing.SetTag("$FN_PIG"); // missing in gzdoom
// eviternity stuff
else if ( (e.Thing.GetClassName() == "Archangelus")
|| (e.Thing.GetClassName() == "ArchangelusA")
|| (e.Thing.GetClassName() == "ArchangelusB") )
e.Thing.SetTag("$FN_ANGEL");
else if ( e.Thing.GetClassName() == "AstralCaco" )
e.Thing.SetTag("$FN_ASTRAL");
else if ( e.Thing.GetClassName() == "Annihilator" )
{
e.Thing.SetTag("$FN_ANNIHIL");
// OH BOY, THESE AREN'T CHANGEABLE
//e.Thing.Obituary = "$OB_ANNIHIL";
}
else if ( e.Thing.GetClassName() == "FormerCaptain" )
{
e.Thing.SetTag("$FN_FCAPTAIN");
//e.Thing.Obituary = "$OB_FCAPTAIN";
}
else if ( e.Thing.GetClassName() == "NightmareDemon" )
{
e.Thing.SetTag("$FN_NDEMON");
//e.Thing.Obituary = "$OB_NDEMON";
}
// doom vacation stuff
else if ( indoomvacation )
{
if ( e.Thing.GetClassName() == "Babe" )
{
e.Thing.bSHOOTABLE = false; // no hurt
let hp = Actor.Spawn("HeadpatTracker",e.Thing.pos);
hp.target = e.Thing;
HeadpatTracker(hp).hdoomheightfix = .2;
}
else if ( e.Thing.GetClassName() == "CommanderKeen" )
{
let hp = Actor.Spawn("HeadpatTracker",e.Thing.pos);
hp.target = e.Thing;
HeadpatTracker(hp).hdoomheightfix = .4;
HeadpatTracker(hp).hdoomangfix = 5;
}
else if ( e.Thing.GetClassName() == "BBChair" )
{
e.Thing.bUSESPECIAL = false;
let hp = Actor.Spawn("HeadpatTracker",e.Thing.pos);
hp.target = e.Thing;
HeadpatTracker(hp).hdoomheightfix = .2;
HeadpatTracker(hp).hdoomangfix = 15;
HeadpatTracker(hp).patstate = e.Thing.MeleeState;
}
else if ( e.Thing.GetClassName() == "EvilEye" )
{
let hp = Actor.Spawn("HeadpatTracker",e.Thing.pos);
hp.target = e.Thing;
HeadpatTracker(hp).hdoomheightfix = .1;
}
else if ( e.Thing.GetClassName() == "HeadCandles" )
{
let hp = Actor.Spawn("HeadpatTracker",e.Thing.pos);
hp.target = e.Thing;
HeadpatTracker(hp).hdoomangfix = 20;
}
else if ( e.Thing.GetClassName() == "HeartColumn" )
{
let hp = Actor.Spawn("HeadpatTracker",e.Thing.pos);
hp.target = e.Thing;
HeadpatTracker(hp).hdoomheightfix = -.3;
}
else if ( e.Thing.GetClassName() == "Meat2" )
{
let hp = Actor.Spawn("HeadpatTracker",e.Thing.pos);
hp.target = e.Thing;
HeadpatTracker(hp).hdoomheightfix = .6;
HeadpatTracker(hp).hdoomangfix = -15;
HeadpatTracker(hp).dvacationarghack = true;
}
else if ( e.Thing.GetClassName() == "Meat3" )
{
let hp = Actor.Spawn("HeadpatTracker",e.Thing.pos);
hp.target = e.Thing;
HeadpatTracker(hp).hdoomheightfix = .6;
HeadpatTracker(hp).hdoomangfix = 20;
HeadpatTracker(hp).dvacationarghack = true;
}
else if ( e.Thing.GetClassName() == "LegsBabe" )
{
let hp = Actor.Spawn("HeadpatTracker",e.Thing.pos);
hp.target = e.Thing;
HeadpatTracker(hp).hdoomheightfix = -1.5;
HeadpatTracker(hp).hdoomangfix = 20;
HeadpatTracker(hp).dvacationarghack = true;
}
else if ( e.Thing.GetClassName() == "Meat4" )
{
let hp = Actor.Spawn("HeadpatTracker",e.Thing.pos);
hp.target = e.Thing;
HeadpatTracker(hp).hdoomheightfix = .6;
HeadpatTracker(hp).hdoomangfix = 15;
HeadpatTracker(hp).dvacationarghack = true;
}
}
else if ( inultdoom2 && (e.Thing.GetClassName() == "WolfensteinSS") )
{
e.Thing.SetTag("$FN_ELITEZOMBIE");
//e.Thing.Obituary = "$OB_ELITEZOMBIE";
e.Thing.SeeSound = "grunt/sight";
e.Thing.AttackSound = "grunt/attack";
e.Thing.PainSound = "grunt/pain";
e.Thing.DeathSound = "grunt/death";
e.Thing.ActiveSound = "grunt/active";
}
if ( SWWMUtility.IdentifyingDog(e.Thing) || SWWMUtility.IdentifyingCaco(e.Thing)
|| SWWMUtility.IdentifyingDrug(e.Thing) || SWWMUtility.IdentifyingDoubleBoi(e.Thing) )
{
// you can pet the dog, and you can also pet the caco (and friends)
let hp = Actor.Spawn("HeadpatTracker",e.Thing.pos);
hp.target = e.Thing;
}
SWWMCombatTracker trk;
if ( !swwm_notrack && (e.Thing.bSHOOTABLE || e.Thing.bISMONSTER) && !(e.Thing is 'LampMoth') && !(e.Thing is 'CompanionLamp') )
trk = SWWMCombatTracker.Spawn(e.Thing);
if ( swwm_shadows && !(e.Thing is 'LampMoth') && (e.Thing.bSHOOTABLE || e.Thing.bISMONSTER || (e.Thing is 'Inventory') || (e.Thing is 'CompanionLamp')) && ((e.Thing is 'Demolitionist') || (e.Thing.SpawnState.sprite == e.Thing.GetSpriteIndex('XZW1'))) )
SWWMShadow.Track(e.Thing);
// Ynykron vortex optimization (faster than a thinker iterator)
if ( e.Thing.bSHOOTABLE || SWWMUtility.ValidProjectile(e.Thing) || (e.Thing is 'Inventory') )
SuckableActors.Push(e.Thing);
else if ( SWWMUtility.IsBeamProj(e.Thing) )
Beams.Push(e.Thing);
// vanilla blood color changes
if ( (e.Thing.GetClass() == "BaronOfHell") || (e.Thing.GetClass() == "HellKnight") || (e.Thing.GetClass() == "Bishop") || (e.Thing.GetClass() == "Korax") )
{
let gb = Actor.Spawn("GreenBloodReference");
e.Thing.CopyBloodColor(gb);
gb.Destroy();
}
else if ( e.Thing.GetClass() == "Cacodemon" )
{
let bb = Actor.Spawn("BlueBloodReference");
e.Thing.CopyBloodColor(bb);
bb.Destroy();
}
else if ( (e.Thing.GetClass() == "Wizard") || (e.Thing.GetClass() == "Heresiarch") || (e.Thing.GetClass() == "Sorcerer2") )
{
let pb = Actor.Spawn("PurpleBloodReference");
e.Thing.CopyBloodColor(pb);
pb.Destroy();
}
else if ( e.Thing.GetClass() == "LostSoul" )
e.Thing.bNOBLOOD = true;
VanillaBossSpawn(e,trk);
// inflation check
if ( trk )
{
trk.maxhealth = trk.lasthealth = e.Thing.Health;
trk.intp.Reset(trk.lasthealth);
}
if ( profiling ) worldthingspawned_ms += MSTime()-curms;
}
}