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.
This commit is contained in:
Mari the Deer 2025-08-20 15:50:07 +02:00
commit 2aa0ea4680
16 changed files with 414 additions and 43 deletions

View file

@ -139,6 +139,11 @@ Class SWWMLevelCompatibility : LevelPostProcessor
level.nextsecretmap = level.nextmap; // so the handler can detect this
if ( LevelInfo.MapExists("MAP01") ) level.nextmap = "MAP01";
break;
// Legacy of Rust E1M7
case '7F00D2FAA5F0B10A6028BE2FC5530EC9':
level.nextsecretmap = level.nextmap; // so the handler can detect this
level.nextmap = "MAP08";
break;
// Heretic E1M8
case '27639D04F8090D57A47D354992435893':
level.nextsecretmap = level.nextmap; // so the handler can detect this
@ -539,6 +544,35 @@ Class SWWMLevelCompatibility : LevelPostProcessor
}
switch ( checksum )
{
// ALL of Legacy of Rust
case '4F9E705F55E45C1047FABF8BDF2B2399':
case '5FB5010F988FFAE2679A9BDD57460473':
case '7CABD8B043B69996D9777F7070C8BCCE':
case '7F00D2FAA5F0B10A6028BE2FC5530EC9':
case '8A2C0869EAA69FB7B441CD2B648978D0':
case '95B94864754AC50446A456A88DA3E052':
case '867B6AD67389A077CE3C9E3CC896F484':
case '1283C3288A2F51B7455D817C5B7FCFAD':
case '1699E255B8C0DB86EBB00E5B3C44B4AA':
case 'A3F7A58FC08C369F1360741A99F1497C':
case 'B6447217725A2A709D6D021CDE15FE10':
case 'BF34C34C5DFC8BB47228CC304F9A6748':
case 'C745F8D0D8824A1910F9DC8B7AB16AA2':
case 'E2D2886FD22DC4354939E6E51690C34B':
case 'F5AED83945C8BDE642E55E72FE0D92AA':
// we need to replace the dehacked things with the
// ones defined in zscript internally, for the sake of
// script compatibility and whatnot
for ( uint i=0; i<GetThingCount(); i++ )
{
int ednum = GetThingEdNum(i);
if ( ((ednum >= 3007) && (ednum <= 3014))
|| ((ednum >= 3100) && (ednum <= 3142)) )
SetThingEdNum(i,ednum+4204000);
}
break;
case 'F206766043C4D9BA2C36F76106F96279':
case 'FCF009C63BBA5F8CEE71ED5EC0B02CDA':
// ALL of Equinox
case '9705315427A2F951A538B23C39199236':
case '54E9953A3C1A88641E00AA353BAF46E9':

View file

@ -77,6 +77,28 @@ Class ROM3R0Death : Inventory
return;
}
}
Class TyrantWake : Inventory
{
override void DoEffect()
{
if ( Owner.InStateSequence(Owner.CurState,Owner.SeeState) )
{
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
if ( hnd && (hnd.bosstag != "$BT_TYRANT2") )
{
hnd.bossactors.Clear();
hnd.initialized = false;
let ti = ThinkerIterator.Create('Deh_Actor_157');
Actor a;
while ( a = Actor(ti.Next()) )
hnd.bossactors.Push(a);
hnd.bosstag = "$BT_TYRANT2";
}
DepleteOrDestroy();
return;
}
}
}
extend Class SWWMHandler
{
@ -124,7 +146,9 @@ extend Class SWWMHandler
MAP_HMAP60,
MAP_EVMAP30, // eviternity
MAP_EVIIMAP30, // eviternity 2
MAP_DE1M8B // "tech gone bad"
MAP_DE1M8B, // "tech gone bad"
MAP_ID24MAP13, // TODO Soul Silo cybies
MAP_ID24MAP14 // TODO Brink tyrants
};
static play void AddBoss( int tid, String tag, bool endgame = false )
@ -237,6 +261,10 @@ extend Class SWWMHandler
return MAP_EVMAP30;
if ( mapsum ~== "CF2B3E2589CA6FBB6EE3E3A09F19BA18" )
return MAP_EVIIMAP30;
if ( mapsum ~== "7CABD8B043B69996D9777F7070C8BCCE" )
return MAP_ID24MAP13;
if ( mapsum ~== "A3F7A58FC08C369F1360741A99F1497C" )
return MAP_ID24MAP14;
return MAP_NONE;
}
private void VanillaBossSpawn( WorldEvent e )
@ -320,6 +348,42 @@ extend Class SWWMHandler
bosstag = "$BT_CYBIE2";
}
}
else if ( bossmap == MAP_ID24MAP13 )
{
if ( e.Thing is 'Cyberdemon' )
{
bossactors.Push(e.Thing);
e.Thing.StartHealth = e.Thing.Health *= 4;
e.Thing.GiveInventory('BossMarker',1);
bosstag = "$BT_CYBIE3";
}
}
else if ( bossmap == MAP_ID24MAP14 )
{
if ( e.Thing is 'Deh_Actor_156' )
{
bossactors.Push(e.Thing);
e.Thing.StartHealth = e.Thing.Health *= 4;
e.Thing.GiveInventory('BossMarker',1);
bosstag = "$BT_TYRANT";
}
else if ( e.Thing is 'Deh_Actor_155' )
{
e.Thing.StartHealth = e.Thing.Health *= 4;
e.Thing.GiveInventory('BossMarker',1);
}
else if ( e.Thing is 'Deh_Actor_157' )
{
e.Thing.StartHealth = e.Thing.Health *= 4;
e.Thing.GiveInventory('BossMarker',1);
e.Thing.GiveInventory('TyrantWake',1);
}
else if ( e.Thing is 'Cyberdemon' )
{
e.Thing.StartHealth = e.Thing.Health *= 4;
e.Thing.GiveInventory('BossMarker',1);
}
}
else if ( bossmap == MAP_HE1M8_HE4M8 )
{
if ( e.Thing is 'IronLich' )

View file

@ -80,7 +80,8 @@ extend Class SWWMHandler
else if ( SWWMUtility.IsEviternity() )
{
// we have to do some heavy lifting here because episodes don't match clusters
if ( level.levelnum <= 5 ) clust = 1;
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;
@ -93,6 +94,15 @@ extend Class SWWMHandler
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)) )

View file

@ -69,6 +69,17 @@ extend Class DemolitionistMenu
c_minute = 9;
c_tz = "+09";
}
else if ( SWWMUtility.IsLegacyOfRust() )
{
// August 8th 2150, 02:31 EDT
// (August 8th 2150, 11:31 JST)
c_year = 2150;
c_month = 7;
c_day = 7;
c_hour = 2;
c_minute = 31;
c_tz = "EDT";
}
else // Doom
{
// June 6th 2148, 18:37 EDT

View file

@ -23,6 +23,7 @@ Class DemolitionistMissionTab : DemolitionistMenuTab
}
// saves time
bool nrftl = false;
bool lor = false;
bool eviternity = false;
bool eviternitwo = false;
bool hexdd = false;
@ -32,6 +33,7 @@ Class DemolitionistMissionTab : DemolitionistMenuTab
{
int clus = level.cluster;
if ( clus == 11 ) nrftl = true;
lor = SWWMUtility.IsLegacyOfRust();
eviternity = SWWMUtility.IsEviternity();
eviternitwo = SWWMUtility.IsEviternityTwo();
if ( eviternitwo )
@ -59,6 +61,14 @@ Class DemolitionistMissionTab : DemolitionistMenuTab
else if ( level.levelnum <= 32 ) clus = 8;
missionstr = String.Format("$SWWM_MISSION_EVITERNITY%d",clus);
}
else if ( lor )
{
// legacy of rust has its quirks, as with umapinfo there are
// technically no clusters
if ( (level.levelnum <= 7) || (level.levelnum == 15) ) clus = 28;
else if ( (level.levelnum <= 14) || (level.levelnum == 16) ) clus = 29;
missionstr = String.Format("$SWWM_MISSION_DOOM%d",clus);
}
// naive method to guess if this is sigil
else if ( (clus == 5) && (level.mapname.Left(2) == "E5") )
missionstr = String.Format("$SWWM_MISSION_SIGIL");

View file

@ -223,42 +223,63 @@ Class SWWMStats : SWWMStaticThinker
return null;
}
// we doin' that thing again, yup
int ClusterRemap( int clus, int levelnum )
{
if ( SWWMUtility.IsEviternityTwo() )
{
// clusters in eviternity 2 have to be remapped
if ( clus == 5 ) return 1;
if ( (clus == 6) || (clus == 13) ) return 2;
if ( (clus == 7) || (clus == 14) ) return 3;
if ( (clus == 8) || (clus == 15) ) return 4;
if ( (clus == 9) || (clus == 16) ) return 5;
if ( (clus == 10) || (clus == 17) ) return 6;
if ( (clus == 11) || (clus == 12) || (clus == 18) || (clus == 19) ) return 7;
}
else if ( SWWMUtility.IsEviternity() )
{
// we have to do some heavy lifting here because episodes don't match clusters
if ( levelnum <= 0 ) return clus;
if ( levelnum <= 5 ) return 1;
if ( levelnum <= 10 ) return 2;
if ( levelnum <= 15 ) return 3;
if ( levelnum <= 20 ) return 4;
if ( levelnum <= 25 ) return 5;
if ( levelnum <= 30 ) return 6;
if ( levelnum <= 31 ) return 7;
if ( levelnum <= 32 ) return 8;
}
else if ( SWWMUtility.IsLegacyOfRust() )
{
// legacy of rust has its quirks, as with umapinfo there are
// technically no clusters
if ( levelnum <= 0 ) return clus;
if ( (levelnum <= 7) || (levelnum == 15) ) return 28;
else if ( (levelnum <= 14) || (levelnum == 16) ) return 29;
}
return clus;
}
void PreloadLevelStats()
{
// pre-adds all unvisited levels from the current cluster
int nlevels = LevelInfo.GetLevelInfoCount();
int ourcluster = ClusterRemap(level.cluster,level.levelnum);
for ( int i=0; i<nlevels; i++ )
{
let li = LevelInfo.GetLevelInfo(i);
if ( !li.isValid() || !LevelInfo.MapExists(li.mapname)
|| (li.cluster != level.cluster)
|| FindLevelStats(li.mapname) )
continue;
// wadfusion hack for E1M4B and E1M8B
bool wf_hack = false;
if ( (li.mapname == "E1M4") && LevelInfo.MapExists("E1M4B") )
{
let cv = CVar.GetCVar('wf_blackroomswap_e1m4b');
if ( cv && cv.GetBool() )
{
wf_hack = true;
li = LevelInfo.FindLevelInfo("E1M4B");
}
}
else if ( (li.mapname == "E1M8") && LevelInfo.MapExists("E1M8B") )
{
let cv = CVar.GetCVar('wf_blackroomswap_e1m8b');
if ( cv && cv.GetBool() )
{
wf_hack = true;
li = LevelInfo.FindLevelInfo("E1M8B");
}
}
int theircluster = ClusterRemap(li.cluster,li.levelnum);
if ( theircluster != ourcluster )
continue;
let ls = new('LevelStat');
// we can automatically assume that all levels from the same cluster will have this flag as well
ls.hub = !!(level.clusterflags&level.CLUSTER_HUB);
ls.visited = false;
ls.cluster = wf_hack?1:li.cluster; // force cluster to be 1 for Romero's extra maps
ls.cluster = theircluster;
ls.levelname = li.LookupLevelName();
// level name may contain trailing whitespace due to DEHACKED nonsense, so strip it
ls.levelname.StripRight();
@ -614,7 +635,7 @@ Class SWWMLoreLibrary : SWWMStaticThinker
else if ( text ~== "SWWM_LORETXT_MARISA" )
text = "SWWM_LORETXT_MARISA2"; // post-invasion update
}
if ( (gameinfo.gametype&GAME_Raven) || SWWMUtility.IsEviternity() || SWWMUtility.IsEviternityTwo() || (mlog && (mlog.year >= 2150) && (mlog.month >= 5)) )
if ( (gameinfo.gametype&GAME_Raven) || SWWMUtility.IsEviternity() || SWWMUtility.IsEviternityTwo() || SWWMUtility.IsLegacyOfRust() || (mlog && (mlog.year >= 2150) && (mlog.month >= 5)) )
{
if ( text ~== "SWWM_LORETXT_AKARILABS" )
text = "SWWM_LORETXT_AKARILABS2"; // demo won, akari project announced

View file

@ -52,10 +52,16 @@ extend Class SWWMUtility
if ( IsEviternity() ) return true;
if ( IsEviternityTwo() ) return true;
if ( IsUltDoom2() ) return true;
if ( IsLegacyOfRust() ) return true;
}
return false;
}
static bool IsLegacyOfRust()
{
return CheckMD5List("id1.lst");
}
// detect ultimate doom 2
static bool IsUltDoom2()
{

View file

@ -84,6 +84,26 @@ extend Class SWWMUtility
case 'CyberdemonMAP24':
basetag = "CYBER";
break;
case 'ID24Banshee':
basetag = "ID24BANSHEE";
break;
case 'ID24Ghoul':
basetag = "ID24GHOUL";
break;
case 'ID24Mindweaver':
basetag = "ID24MINDWEAVER";
break;
case 'ID24PlasmaGuy':
basetag = "ID24SHOCKTROOPER";
break;
case 'ID24Vassago':
basetag = "ID24VASSAGO";
break;
case 'ID24Tyrant':
case 'ID24TyrantBoss1':
case 'ID24TyrantBoss2':
basetag = "ID24TYRANT";
break;
case 'SWWMBossBrain':
basetag = "BOSSBRAIN";
break;
@ -303,6 +323,8 @@ extend Class SWWMUtility
Class<Actor> rescls = res;
return rescls;
}
// special boss tyrants in LoR final map
if ( (a == 'ID24TyrantBoss1') || (a == 'ID24TyrantBoss2') ) return 'ID24Tyrant';
// stealth monsters, the worst thing ever invented
if ( a == 'StealthArachnotron' ) return 'Arachnotron';
if ( a == 'StealthArchvile' ) return 'Archvile';