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).
501 lines
16 KiB
Text
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;
|
|
}
|
|
}
|
|
}
|