While in the latter case this may result in longer loops, it also reduces GC thrashing by not needing to allocate an iterator every time. This also simplifies the DoBlast code as there is no longer a need to manually traverse portals vertically.
421 lines
14 KiB
Text
421 lines
14 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;
|
|
|
|
// 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 )
|
|
{
|
|
bool breakout = false;
|
|
foreach ( s:level.Sectors )
|
|
{
|
|
for ( Actor t=s.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
|
|
breakout = true;
|
|
break;
|
|
}
|
|
if ( breakout ) 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 ( e.Thing.default.bSHOOTABLE || (e.Thing is 'Inventory') || SWWMUtility.ValidProjectile(e.Thing) )
|
|
{
|
|
// remove from suckables
|
|
int pos = suckableactors.Find(e.Thing);
|
|
if ( pos < suckableactors.Size() )
|
|
suckableactors.Delete(pos);
|
|
}
|
|
else if ( SWWMUtility.IsBeamProj(e.Thing) )
|
|
{
|
|
// remove from beams
|
|
int pos = beams.Find(e.Thing);
|
|
if ( pos < beams.Size() )
|
|
beams.Delete(pos);
|
|
}
|
|
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",SWWMDLG_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;
|
|
}
|
|
// Ynykron vortex optimization (faster than a thinker iterator)
|
|
if ( e.Thing.bSHOOTABLE || (e.Thing is 'Inventory') || SWWMUtility.ValidProjectile(e.Thing) )
|
|
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);
|
|
if ( profiling ) ProfileTock(PT_WORLDTHINGSPAWNED);
|
|
}
|
|
}
|