swwmgz_m/zscript/handler/swwm_handler_worldthings.zsc
Marisa the Magician e5e6ce619c Fix severe performance issues in large maps.
So... Remember that one decision I made about avoiding BlockThingsIterator as
much as possible? Turns out that was a stupid idea. There ARE situations where
it's better to iterate sector thinglists, yes, especially for things that are
NOT part of the blockmap, but in other cases, the excess allocations of new
iterators are a reasonable price to pay for the lower perf impact in extreme
cases, such as maps that have a gazillion sectors with gazillions of things in
them (I'm looking at you, UDMF mappers).
As a compromise, at least, in situations where the thinglists are needed, I did
add a sort of micro-optimization by implementing code to check if a bounding
box is inside a sector (would be nice if this was part of GZDoom itself, tho).
2023-12-19 11:46:29 +01:00

390 lines
14 KiB
Text

// WorldThing* events (excluding damage)
extend Class SWWMHandler
{
// prevents revived monsters from spawning in more golden shells
Array<Actor> alreadygold;
// legendary monster markers (for the "has mutated" message)
Array<Inventory> legtrack;
override void WorldThingRevived( WorldEvent e )
{
if ( profiling ) ProfileTick();
// 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 ) ProfileTock(PT_WORLDTHINGREVIVED);
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 ) ProfileTock(PT_WORLDTHINGREVIVED);
}
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 int 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 max(0,totalneeded);
}
override void WorldThingDied( WorldEvent e )
{
if ( profiling ) ProfileTick();
if ( e.Thing.default.bISMONSTER && e.Thing.default.bCOUNTKILL && ((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/2);
// make sure the gold shell is "worth spawning", too
// (also, chance should be reduced as you acquire more shells)
int gchance = int(ceil(ShouldSpawnGold()/2.));
if ( !Random[GoldDrop](0,dropweight) && Random[GoldDrop](0,gchance) )
{
let g = Actor.Spawn("GoldShell",e.Thing.Vec3Offset(0,0,e.Thing.Height/2.));
double ang = FRandom[SpareShells](0.,360.);
g.vel.xy = Actor.AngleToVector(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("KoraxYeeted");
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;
}
if ( profiling ) ProfileTock(PT_WORLDTHINGDIED);
}
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_KEYYELLOW");
else if ( a is 'KeyGreen' ) a.SetTag("$T_KEYGREEN");
else if ( a is 'KeyBlue' ) a.SetTag("$T_KEYBLUE");
else if ( a.GetClassName() == 'KeyRed' ) a.SetTag("$T_KEYRED");
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 )
{
for ( Actor t=a.CurSector.thinglist; t; t=t.snext )
{
if ( (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 ) ProfileTick();
// for gibber throttling
if ( e.Thing is 'mkBloodDrop' ) blods_realcnt--;
else if ( e.Thing is 'mkFlyingGib' ) meats_realcnt--;
if ( profiling ) ProfileTock(PT_WORLDTHINGDESTROYED);
}
override void WorldThingSpawned( WorldEvent e )
{
if ( profiling ) ProfileTick();
// for gibber throttling
if ( e.Thing is 'mkBloodDrop' ) blods_realcnt++;
else if ( e.Thing is 'mkFlyingGib' ) meats_realcnt++;
if ( !e.Thing || SuppressMultiItem(e) )
{
if ( profiling ) ProfileTock(PT_WORLDTHINGSPAWNED);
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' )
{
let p = Actor.Spawn(Inventory(e.Thing).PickupFlash,e.Thing.pos);
p.target = e.Thing;
p.SetStateLabel("Pickup");
}
// for notification
if ( e.Thing.GetClassName() == "LDLegendaryMonsterTransformed" )
legtrack.Push(Inventory(e.Thing));
}
if ( swwm_doomfall && e.Thing.bISMONSTER && !e.Thing.bBOSS )
e.Thing.bFALLDAMAGE = true;
if ( e.Thing is 'Key' )
{
DoKeyTagFix(e.Thing);
SWWMInterest.Spawn(self,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
// nashgore compat
if ( (e.Thing.GetClassName() == "NashGoreFootprint")
|| (e.Thing.GetClassName() == "NashGoreFootprintLeft")
|| (e.Thing.GetClassName() == "NashGoreFootprintRight") )
e.Thing.A_ChangeModel("",0,"","",0,"models/nashgore/Footprint","DemoFootprint.png");
// 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");
else if ( e.Thing.GetClassName() == "FormerCaptain" )
e.Thing.SetTag("$FN_FCAPTAIN");
else if ( e.Thing.GetClassName() == "NightmareDemon" )
e.Thing.SetTag("$FN_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).heightfix = .2;
}
else if ( e.Thing.GetClassName() == "CommanderKeen" )
{
let hp = Actor.Spawn("HeadpatTracker",e.Thing.pos);
hp.target = e.Thing;
HeadpatTracker(hp).heightfix = .4;
HeadpatTracker(hp).angfix = 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).heightfix = .2;
HeadpatTracker(hp).angfix = 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).heightfix = .1;
}
else if ( e.Thing.GetClassName() == "HeadCandles" )
{
let hp = Actor.Spawn("HeadpatTracker",e.Thing.pos);
hp.target = e.Thing;
HeadpatTracker(hp).angfix = 20;
}
else if ( e.Thing.GetClassName() == "HeartColumn" )
{
let hp = Actor.Spawn("HeadpatTracker",e.Thing.pos);
hp.target = e.Thing;
HeadpatTracker(hp).heightfix = -.3;
}
else if ( e.Thing.GetClassName() == "Meat2" )
{
let hp = Actor.Spawn("HeadpatTracker",e.Thing.pos);
hp.target = e.Thing;
HeadpatTracker(hp).heightfix = .6;
HeadpatTracker(hp).angfix = -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).heightfix = .6;
HeadpatTracker(hp).angfix = 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).heightfix = -1.5;
HeadpatTracker(hp).angfix = 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).heightfix = .6;
HeadpatTracker(hp).angfix = 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";
}
else if ( ccloaded && (e.Thing.GetClassName() == "CCards_Token_Glitched") )
{
if ( !gdat.cclilithonce ) SendInterfaceEvent(consoleplayer,"swwmsetdialogue.LILITH");
gdat.cclilithonce = true;
}
if ( SWWMUtility.IdentifyingDog(e.Thing) || SWWMUtility.IdentifyingCaco(e.Thing)
|| SWWMUtility.IdentifyingDrug(e.Thing) || SWWMUtility.IdentifyingDoubleBoi(e.Thing) )
{
// you can pet the dog, and you can also pet the caco (and friends)
let hp = Actor.Spawn("HeadpatTracker",e.Thing.pos);
hp.target = e.Thing;
}
// 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);
if ( profiling ) ProfileTock(PT_WORLDTHINGSPAWNED);
}
}