swwmgz_m/zscript/handler/swwm_handler_worldload.zsc
Marisa the Magician 2aa0ea4680 More work towards Legacy of Rust support (with caveats).
As of this commit, do not consider the experience when playing that new
expansion to be complete. I've only partially written some of the mission texts
and rudimentarily enhanced some boss fights.

Currently there is one major limitation in that the intermission texts cannot
be replaced, as they're hardcoded inside the UMAPINFO. I don't know if I can
work around that.
2025-08-20 16:47:24 +02:00

547 lines
18 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;
bool nogroundanchor;
int allclearsector;
// 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.IsEviternityTwo() )
{
// clusters have to be remapped here
let clus = level.cluster;
if ( clus == 5 ) clust = 1;
else if ( (clus == 6) || (clus == 13) ) clust = 2;
else if ( (clus == 7) || (clus == 14) ) clust = 3;
else if ( (clus == 8) || (clus == 15) ) clust = 4;
else if ( (clus == 9) || (clus == 16) ) clust = 5;
else if ( (clus == 10) || (clus == 17) ) clust = 6;
else if ( (clus == 11) || (clus == 12) || (clus == 18) || (clus == 19) ) clust = 7;
}
else if ( SWWMUtility.IsEviternity() )
{
// we have to do some heavy lifting here because episodes don't match clusters
if ( level.levelnum <= 0 ) {}
else 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 ( SWWMUtility.IsLegacyOfRust() )
{
// clusters must be manually assigned
if ( level.levelnum <= 0 ) {}
if ( (level.levelnum <= 7) || (level.levelnum == 15) ) clust = 28;
else if ( (level.levelnum <= 14) || (level.levelnum == 16) ) clust = 29;
if ( (level.levelnum == 15) || (level.levelnum == 16) )
secret = true;
}
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);
}
// re-enable retries after Eviternity 2 MAP33
if ( level.GetChecksum() ~== "442504BA06E5EFB6C7EBD452E159522D" )
gdat.disablerevive = false;
// 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;
// Death exit counter breaks Eviternity's episode transitions
// so we need this little patch-up work here
if ( (players[i].playerstate == PST_DEAD) && nextlv && (nextlv.flags2&LEVEL2_RESETINVENTORY) )
demo.invwipe |= Demolitionist.WIPE_EPISODE;
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:
case MAP_DE1M8B:
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:
if ( FindClass('Robot_BossBrain','Actor') )
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_EVIIMAP30:
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.EV2K");
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");
// SIGIL 2 E6M8
if ( csum ~== "5BA3D00F6B64F6268E11C6851D47ECBF" )
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.SIGIL2");
// 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");
// Eviternity 2
// MAP01
else if ( csum ~== "8EB38D5289C47BB68D64F2832EFA096D" )
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.EV2A");
// MAP05
else if ( csum ~== "457CAF066596B6AF59F7273C8D5461B7" )
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.EV2E");
// MAP10
else if ( csum ~== "066653E60ACC99D7B8EB5EBBFEF4F11A" )
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.EV2F");
// MAP15
else if ( csum ~== "8FB3513B313002B1287610F545F0FDFF" )
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.EV2G");
// MAP20
else if ( csum ~== "641A394145EF638B972E87C4CDFB34EF" )
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.EV2H");
// MAP25
else if ( csum ~== "67A80E78AEBA38AB0A0DD0616040F4F2" )
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.EV2I");
// MAP33
else if ( csum ~== "442504BA06E5EFB6C7EBD452E159522D" )
SendInterfaceEvent(consoleplayer,"swwmsetdialogue.EV2P");
// 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)
if ( FindClass('KdikdizdCompatEventHandler','EventHandler') )
{
ti = ThinkerIterator.Create('Thinker');
foreach ( t:ti )
{
if ( t.GetClassName() != 'VoodooPusher' )
continue;
t.Destroy();
}
}
// Eviternity II MAP33 fix. Player movement physics need to
// have ground anchoring disabled, as it will make some
// segments impossible due to the player's feet immediately
// touching the instant-kill lava
if ( level.GetChecksum() ~== "442504BA06E5EFB6C7EBD452E159522D" )
{
nogroundanchor = true;
allclearsector = 18414; // only check all-clear if the player is standing in this sector
gdat.disablerevive = true; // ONE TRY
}
Array<Line> exits;
Array<int> exittypes;
exits.Clear();
exittypes.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;
exits.Push(l);
exittypes.Push(exittype);
}
// for skipping over merged exit lines (sharing vertices)
Array<Line> skipme;
skipme.Clear();
for ( int i=0; i<exits.Size(); i++ )
{
let l = exits[i];
if ( skipme.Find(l) < skipme.Size() ) continue;
skipme.Push(l);
// look for connected lines
// only stop once we cannot find more
Array<Line> con;
con.Clear();
con.Push(l);
int found;
do
{
found = 0;
for ( int j=0; j<exits.Size(); j++ )
{
let l2 = exits[j];
if ( (l2 == l) || !SWWMUtility.SameSpecial(l,l2) || (skipme.Find(l2) < skipme.Size()) || (con.Find(l2) < con.size()) ) continue;
// needs to have at least one point in common with this one or any of the added lines
bool nomatches = true;
foreach ( c:con )
{
if ( (l2.v1.p != c.v1.p) && (l2.v2.p != c.v2.p) && (l2.v1.p != c.v2.p) && (l2.v2.p != c.v1.p) )
continue;
nomatches = false;
break;
}
if ( nomatches ) continue;
found++;
skipme.Push(l2);
con.Push(l2);
}
}
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:exittypes[i]);
}
// 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;
}
}
}