swwmgz_m/zscript/handler/swwm_handler_worldload.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

501 lines
16 KiB
Text

// WorldLoaded/WorldUnloaded events
Class RampancyLogonDummy : Actor
{
States
{
Spawn:
TNT1 A 7;
TNT1 AAAAAAAAAAAAAAAA 1
{
for ( int i=0; i<16; i++ )
A_Log("Remote login failed.");
}
TNT1 A 7;
TNT1 A 1 A_Log("\cgWARNING:\cj 256 failed remote login attempts have been reported in the last second.\c-");
Stop;
}
}
// this is used to speed up iteration through sector thinglists within a specific area
Class SectorBounds
{
Vector4 bounds;
int portalgroup;
clearscope bool PointInSectorBounds( Vector2 p, int pg = -1 ) const
{
if ( (pg >= 0) && (level.GetPortalGroupCount() > 0) && (pg != portalgroup) )
p += level.GetDisplacement(pg,portalgroup);
if ( p.x < bounds.x ) return false;
if ( p.y < bounds.y ) return false;
if ( p.x > bounds.z ) return false;
if ( p.y > bounds.w ) return false;
return true;
}
clearscope bool BoxInSectorBounds( Vector2 p, double r, int pg = -1 ) const
{
if ( (pg >= 0) && (level.GetPortalGroupCount() > 0) && (pg != portalgroup) )
p += level.GetDisplacement(pg,portalgroup);
if ( p.x+r < bounds.x ) return false;
if ( p.y+r < bounds.y ) return false;
if ( p.x-r > bounds.z ) return false;
if ( p.y-r > bounds.w ) return false;
return true;
}
}
extend Class SWWMHandler
{
bool maphaskeys;
// weird optimization
Array<SectorBounds> sbounds;
// level end stats
override void WorldUnloaded( WorldEvent e )
{
let ti = ThinkerIterator.Create("SWWMStats",Thinker.STAT_STATIC);
SWWMStats s;
while ( s = SWWMStats(ti.Next()) )
{
int clust = 0;
bool secret = false;
if ( SWWMUtility.IsEviternity() )
{
// we have to do some heavy lifting here because episodes don't match clusters
if ( level.levelnum <= 5 ) clust = 1;
else if ( level.levelnum <= 10 ) clust = 2;
else if ( level.levelnum <= 15 ) clust = 3;
else if ( level.levelnum <= 20 ) clust = 4;
else if ( level.levelnum <= 25 ) clust = 5;
else if ( level.levelnum <= 30 ) clust = 6;
else if ( level.levelnum <= 32 )
{
secret = true;
if ( level.levelnum <= 31 ) clust = 7;
else clust = 8;
}
}
else
{
if ( (gameinfo.gametype&GAME_DOOM) && ((level.cluster == 9) || (level.cluster == 10)) )
secret = true;
clust = level.cluster;
}
int csiz = s.clustervisit.Size();
if ( csiz == 0 )
{
s.clustervisit.Push(clust);
s.secretdone.Push(secret);
}
else if ( s.clustervisit[csiz-1] != clust )
{
s.clustervisit.Push(clust);
s.secretdone.Push(secret|s.secretdone[csiz-1]);
}
s.AddLevelStats();
s.lastcluster = level.cluster;
// nazi cleanup
let ti = ThinkerIterator.Create("Actor");
Actor a;
bool hasnazis = false;
bool livenazis = false;
while ( a = Actor(ti.Next()) )
{
// yes, the dogs don't count (they're just dogs)
if ( !(a is 'SWWMGuard') && !(a is 'SWWMSS') && !(a is 'SWWMHans') )
continue;
hasnazis = true;
if ( a.Health > 0 ) livenazis = true;
}
if ( hasnazis && !livenazis )
{
if ( level.levelnum == 31 ) s.nazicleanup |= 1;
else if ( level.levelnum == 32 ) s.nazicleanup |= 2;
}
if ( s.nazicleanup == 3 )
SWWMUtility.MarkAchievement("trash",s.myplayer);
}
// reset score on dead players (death exit™)
for ( int i=0; i<MAXPLAYERS; i++ )
{
if ( !playeringame[i] || (players[i].playerstate != PST_DEAD) ) continue;
let c = SWWMCredits.Find(players[i]);
if ( c ) c.credits = 0;
}
// end of episode resets and enforced pistol starts
LevelInfo nextlv = LevelInfo.FindLevelInfo(e.NextMap);
for ( int i=0; i<MAXPLAYERS; i++ )
{
if ( !playeringame[i] || !players[i].mo ) continue;
let demo = Demolitionist(players[i].mo);
if ( !demo ) continue;
if ( level.nextsecretmap.Left(6) == "enDSeQ" ) demo.invwipe |= Demolitionist.WIPE_EPISODE;
if ( nextlv && (level.cluster!=nextlv.cluster) ) demo.invwipe |= (Demolitionist.WIPE_CLUSTER|Demolitionist.WIPE_MAP);
if ( !(level.clusterflags&LevelLocals.CLUSTER_HUB) ) demo.invwipe |= Demolitionist.WIPE_MAP;
// the playerpawn will know what to do with this in its PreTravelled()
}
// did we complete this map without collecting any of its keys? (doesn't work for hubs)
if ( maphaskeys && !(level.clusterflags&LevelLocals.CLUSTER_HUB) )
{
bool collected = false;
for ( int i=0; i<MAXPLAYERS; i++ )
{
if ( !playeringame[i] || !players[i].mo ) continue;
for ( Inventory inv=players[i].mo.inv; inv; inv=inv.inv )
{
if ( !(inv is 'Key') ) continue;
collected = true;
break;
}
}
if ( !collected ) SWWMUtility.MarkAchievement("cliffyb",players[consoleplayer]);
}
// these can't be done on hexen
if ( gameinfo.GameType&GAME_Hexen ) return;
// beat the par time?
if ( level.partime && (Thinker.Tics2Seconds(level.maptime) <= level.partime) )
SWWMUtility.AchievementProgressInc("par",1,players[consoleplayer]);
// blaze it?
if ( Thinker.Tics2Seconds(level.maptime) == 260 )
SWWMUtility.MarkAchievement("blaze",players[consoleplayer]);
// one standing?
if ( (level.total_monsters-level.killed_monsters) == 1 )
SWWMUtility.MarkAchievement("onestanding",players[consoleplayer]);
// nice?
if ( players[consoleplayer].Health == 69 )
SWWMUtility.MarkAchievement("nice",players[consoleplayer]);
// peaceful/untouchable?
if ( !dealtdamage[consoleplayer] )
SWWMUtility.MarkAchievement("peace",players[consoleplayer]);
if ( !reallytookdamage[consoleplayer] )
SWWMUtility.MarkAchievement("untouchable",players[consoleplayer]);
// in a hurry?
if ( ((level.total_monsters > 0) || (level.total_items > 0) || (level.total_secrets > 0))
&& (level.killed_monsters == 0) && (level.found_items == 0) && (level.found_secrets == 0) )
SWWMUtility.MarkAchievement("hurry",players[consoleplayer]);
}
clearscope bool PointInSectorBounds( Sector s, Vector2 p, int pg = -1 ) const
{
if ( sbounds.Size() <= 0 ) return false;
int i = s.Index();
return sbounds[i].PointInSectorBounds(p,pg);
}
clearscope bool BoxInSectorBounds( Sector s, Vector2 p, double r, int pg = -1 ) const
{
if ( sbounds.Size() <= 0 ) return false;
int i = s.Index();
return sbounds[i].BoxInSectorBounds(p,r,pg);
}
void PrecalculateSectorBounds()
{
sbounds.Resize(level.Sectors.Size());
foreach ( s:level.Sectors )
{
let sb = new("SectorBounds");
sb.portalgroup = s.portalgroup;
sb.bounds = ( 32767, 32767, -32768, -32768 );
foreach ( l:s.Lines )
{
if ( l.v1.p.x < sb.bounds.x )
sb.bounds.x = l.v1.p.x;
if ( l.v2.p.x < sb.bounds.x )
sb.bounds.x = l.v2.p.x;
if ( l.v1.p.y < sb.bounds.y )
sb.bounds.y = l.v1.p.y;
if ( l.v2.p.y < sb.bounds.y )
sb.bounds.y = l.v2.p.y;
if ( l.v1.p.x > sb.bounds.z )
sb.bounds.z = l.v1.p.x;
if ( l.v2.p.x > sb.bounds.z )
sb.bounds.z = l.v2.p.x;
if ( l.v1.p.y > sb.bounds.w )
sb.bounds.w = l.v1.p.y;
if ( l.v2.p.y > sb.bounds.w )
sb.bounds.w = l.v2.p.y;
}
sbounds[s.Index()] = sb;
}
}
private void MapStartDialogues()
{
int whichboss = WhichVanillaBossMap();
switch ( whichboss )
{
case MAP_DE1M8:
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.PHOBOS");
break;
case MAP_DE2M8:
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.DEIMOS");
break;
case MAP_DE3M8:
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.DIS");
break;
case MAP_DE4M8:
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.THY");
break;
case MAP_DMAP07:
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.DIMPLE");
break;
case MAP_DMAP30:
bool rampancy = false;
foreach ( cls:AllActorClasses )
{
if ( cls.GetClassName() != "Robot_BossBrain" ) continue;
rampancy = true;
break;
}
if ( rampancy ) SendInterfaceEvent(consoleplayer,"swwmsetdialogue.RAMPANCY");
else SendInterfaceEvent(consoleplayer,"swwmsetdialogue.IOS");
break;
case MAP_DLVL08:
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.NERVE");
break;
case MAP_EVMAP30:
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.EVIA");
break;
case MAP_HE1M8_HE4M8:
if ( level.mapname ~== "E1M8" ) SendInterfaceEvent(consoleplayer,"swwmsetdialogue.MAW");
else SendInterfaceEvent(consoleplayer,"swwmsetdialogue.HEADS");
break;
case MAP_HE2M8_HE5M8:
if ( level.mapname ~== "E2M8" ) SendInterfaceEvent(consoleplayer,"swwmsetdialogue.PORTALS");
else SendInterfaceEvent(consoleplayer,"swwmsetdialogue.BULLS");
break;
case MAP_HE3M8:
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.DSPARIL");
break;
case MAP_HMAP38:
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.CLERIC");
break;
case MAP_HMAP36:
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.FIGHTER");
break;
case MAP_HMAP37:
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.MAGE");
break;
case MAP_HMAP12:
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.HYPO");
break;
case MAP_HMAP40:
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.KORAX");
break;
case MAP_HMAP23_HMAP27_HMAP48_HMAP55:
if ( level.mapname ~== "MAP48" ) SendInterfaceEvent(consoleplayer,"swwmsetdialogue.CONSTABLE");
break;
case MAP_HMAP60:
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.DEATHKINGS");
break;
case MAP_NONE:
String csum = level.GetChecksum();
// SIGIL E5M8
if ( (csum ~== "3D72FD17F36D2D43FD9A21E6E57EE357")
|| (csum ~== "09B30C9DA9D73D3D5A709502FBB947AA")
|| (csum ~== "6EAD80DA1F30B4B3546FA294EEF9F87C") )
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.SIGIL");
// Doom 2 MAP11
else if ( (csum ~== "73D9E03CEE7BF1A97EFD2EAD86688EF8")
|| (csum ~== "F4F2A769609988837458772AAE99008C")
|| (csum ~== "DF6A001A6C42DB5CCA599EE5883B294A") )
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.CIRCLE");
// Doom 2 MAP20
else if ( (csum ~== "8898F5EC9CBDCD98019A1BC1BF892A8A")
|| (csum ~== "CC53CFFCB30E873669AA2F09DA0D3566") )
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.GOTCHA");
// Eviternity
// MAP05
else if ( csum ~== "33B8501B10CE5E2555C03725F765A914" )
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.DMN");
// MAP10
else if ( csum ~== "9E83602D325677B8D7C3BC44BEF9B03F" )
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.CRE");
// MAP15
else if ( csum ~== "CA40E6DDAB6B5C924CDC36B1F851421E" )
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.CRY");
// MAP20
else if ( csum ~== "F34B3FD4D13AC763469A8E0D7379B9D0" )
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.CON");
// MAP25
else if ( csum ~== "196BC735473C593F924A59B238574C35" )
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.SLA");
// Deathkings
// Blight
else if ( csum ~== "E3EFB0156A20ADF2DF00915A0EA85DF5" )
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.BLIGHT");
// Nave
else if ( csum ~== "E2B5D1400279335811C1C1C0B437D9C8" )
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.NAVE");
break;
}
}
override void WorldLoaded( WorldEvent e )
{
// session globals must be loaded here
// (if we do it in OnRegister the existing thinker might not have been deserialized yet)
gdat = SWWMGlobals.Get();
if ( e.IsReopen ) return;
PrecalculateSectorBounds();
MapStartDialogues();
if ( gamestate != GS_TITLELEVEL )
{
if ( (level.GetChecksum() ~== "D0E5ECD94BD38DF33F25515C00148693")
|| (level.GetChecksum() ~== "D20297AE8447232F6DBE4851E3104668")
|| (level.GetChecksum() ~== "EBBB8663AD4AB22294A8A1D211A026CD") )
{
if ( !AddOneliner("nutstart",3) )
AddOneliner("mapstart",3);
}
else AddOneliner("mapstart",3);
}
if ( level.levelname ~== "Modder Test Map" )
{
level.ReplaceTextures("-noflat-","kinstile",0);
S_ChangeMusic("music/CARDISH1.XM");
AddBoss(6666,"$BT_DOOMTEST"); // for testing boss healthbars
}
// doom vacation map01 hackaround for OPEN script not letting us
// change certain line specials in levelpostprocessor because
// ACS is just mindbogglingly weird like that, seriously
if ( (level.GetChecksum() ~== "F286BABF0D152259CD6B996E8920CA70")
|| (level.GetChecksum() ~== "A52BD2038CF814101AAB7D9C78F9ACE2") )
level.ExecuteSpecial(ACS_Execute,null,null,false,-Int('DVACATION_UNSCREW'));
// rampancy boss brain fix (repeatedly triggering "map clear")
let ti = ThinkerIterator.Create("Actor");
Actor a, brain;
bool haseye = false;
while ( a = Actor(ti.Next()) )
{
if ( a.GetClassName() == "Robot_BossEye" )
haseye = true;
if ( a.GetClassName() == "Robot_BossBrain" )
brain = a;
}
if ( haseye && brain )
{
brain.bCOUNTKILL = true;
level.total_monsters++;
// while we're at it
Actor.Spawn("RampancyLogonDummy");
}
// KDiKDiZD abort fix due to voodoo doll post-spawn replacement
// (we have our own mikoportal compatibility, anyway)
//
// note that the mods are mutually incompatible nonetheless
// since KDiKDiZD requires software rendering, while this is a
// hardware-only mod... but hey, they can sort-of-work together
// (with broken visual effects, but still... somewhat working)
foreach ( cls:AllClasses )
{
if ( cls.GetClassName() != 'KdikdizdCompatEventHandler' )
continue;
ti = ThinkerIterator.Create("Thinker");
Thinker t;
while ( t = ti.Next() )
{
if ( t.GetClassName() != 'VoodooPusher' )
continue;
t.Destroy();
}
break;
}
// for skipping over merged exit lines (sharing vertices)
Array<Line> skipme;
skipme.Clear();
// find exit lines, and use lines that aren't exits
foreach ( l:level.Lines )
{
// all lines are immediately visible in DM
if ( deathmatch && !(l.flags&Line.ML_DONTDRAW) )
l.flags |= Line.ML_MAPPED;
// while we're at it, add teleporter sparks
if ( SWWMUtility.IsTeleportLine(l) )
{
let a = SWWMTeleportLine(Actor.Spawn("SWWMTeleportLine"));
a.tline = l;
}
let [isexit, exittype] = SWWMUtility.IsExitLine(l);
if ( !isexit ) continue;
if ( skipme.Find(l) < skipme.Size() ) continue;
skipme.Push(l);
// look for connected lines
Array<Line> con;
con.Clear();
con.Push(l);
int found;
if ( l.frontsector )
{
do
{
found = 0;
foreach ( l2:l.frontsector.Lines )
{
if ( (l2.special != l.special) || (con.Find(l2) < con.Size()) ) continue;
// needs to have a point in common with this one or any of the added lines
bool nomatches = true;
foreach ( c:con )
{
if ( (l2.v1 != c.v1) && (l2.v2 != c.v2) && (l2.v1 != c.v2) && (l2.v2 != c.v1) )
continue;
nomatches = false;
break;
}
if ( nomatches ) continue;
skipme.Push(l2);
con.Push(l2);
found++;
}
}
while ( found > 0 );
}
if ( l.backsector )
{
do
{
found = 0;
foreach ( l2:l.backsector.Lines )
{
if ( (l2.special != l.special) || (con.Find(l2) < con.Size()) ) continue;
// needs to have a point in common with this one or any of the added lines
bool nomatches = true;
foreach ( s:skipme )
{
if ( (l2.v1 != s.v1) && (l2.v2 != s.v2) && (l2.v1 != s.v2) && (l2.v2 != s.v1) )
continue;
nomatches = false;
break;
}
if ( nomatches ) continue;
skipme.Push(l2);
con.Push(l2);
found++;
}
}
while ( found > 0 );
}
Vector3 lpos = (0,0,0);
foreach ( c:con )
lpos += SWWMUtility.UseLinePos(c);
lpos /= con.Size();
SWWMInterest.Spawn(self,lpos,theline:l,theexit:exittype);
}
// spawn loot
if ( !deathmatch ) Chancebox.SpawnChanceboxes();
// list map keys
maphaskeys = false;
ti = ThinkerIterator.Create("Key");
Key k;
while ( k = Key(ti.Next()) )
{
if ( k.Owner ) continue;
maphaskeys = true;
break;
}
}
}