swwmgz_m/zscript/handler/swwm_handler_worldtick.zsc
Marisa the Magician 2bd1cb0657 Replace usage of ThinkerIterator and BlockThingsIterator in various places where we can instead loop through sector thing lists.
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.
2023-07-29 13:15:34 +02:00

403 lines
14 KiB
Text

// WorldTick functions
extend Class SWWMHandler
{
transient Array<Actor> combatactors;
transient Array<Int> combattics;
transient int highesttic, lastcombat;
int lastitemcount[MAXPLAYERS];
transient String curlang;
transient bool curfuntags;
SWWMSimpleTracker strackers;
bool mnotify;
bool allkills, allitems, allsecrets;
bool mapclear;
int mapclearagain, restartmus, startmus;
double musvol;
String lastmus;
int lastorder;
bool lastloop;
transient ThinkerIterator cti, qti;
private void LangRefresh()
{
if ( (language != curlang) || (swwm_funtags != curfuntags) )
{
// manually refresh some tags if language has changed
if ( !qti ) qti = ThinkerIterator.Create("SWWMQuickCombatTracker",Thinker.STAT_INVENTORY);
else qti.Reinit();
SWWMQuickCombatTracker qt;
while ( qt=SWWMQuickCombatTracker(qti.Next()) )
qt.UpdateTag(self);
for ( SWWMInterest p=intpoints; p; p=p.next )
{
if ( (p.type != INT_Key) || !p.trackedkey ) continue;
p.keytag = p.trackedkey.GetTag();
}
for ( int i=0; i<MAXPLAYERS; i++ )
{
if ( !playeringame[i] || !Demolitionist(players[i].mo) ) continue;
for ( SWWMItemSense s=Demolitionist(players[i].mo).itemsense; s; s=s.next )
s.UpdateTag();
}
}
curlang = language;
curfuntags = swwm_funtags;
}
// countable item scoring
private void ItemCountTrack()
{
for ( int i=0; i<MAXPLAYERS; i++ )
{
if ( !playeringame[i] ) continue;
if ( players[i].itemcount > lastitemcount[i] )
{
int score = 10*(players[i].itemcount-lastitemcount[i]);
if ( !deathmatch && !(gameinfo.gametype&GAME_Hexen) && (level.total_items == level.found_items) && !allitems )
{
allitems = true;
if ( i == consoleplayer ) Console.Printf(StringTable.Localize("$SWWM_LASTITEM"),500);
else Console.Printf(StringTable.Localize("$SWWM_LASTITEMREM"),players[i].GetUserName(),500);
score += 490;
SWWMUtility.AchievementProgressInc("allitems",1,players[i]);
}
SWWMCredits.Give(players[i],score);
SWWMScoreObj.SpawnFromHandler(self,score,players[i].mo.Vec3Offset(0,0,players[i].mo.Height/2));
lastitemcount[i] = players[i].itemcount;
let s = SWWMStats.Find(players[i]);
s.items++;
}
}
}
// combat tracking
private void CombatTrack()
{
// if players are above 1HP, reset the "one hp kill" counter
for ( int i=0; i<MAXPLAYERS; i++ )
{
if ( !playeringame[i] ) continue;
if ( players[i].Health != 1 ) onehpspree[i] = 0;
}
// prune old entries
for ( int i=0; i<combatactors.Size(); i++ )
{
if ( combattics[i] > highesttic )
highesttic = combattics[i];
if ( combatactors[i]
&& (combatactors[i].Health > 0)
&& !combatactors[i].bKILLED
&& !combatactors[i].bCORPSE
&& (combatactors[i].target == players[consoleplayer].mo)
&& (combattics[i]+2000 > gametic) )
continue;
combatactors.Delete(i);
combattics.Delete(i);
i--;
}
bool enteredcombat = false;
// add new entries
bool bossfound = false;
// we can use this instead of a thinker iterator as only actors that EXIST physically could count as combatants
foreach ( s:level.Sectors ) for ( Actor a=s.thinglist; a; a=a.snext )
{
if ( !a.player && !a.bISMONSTER ) continue;
// ignore the dead
if ( (a.Health <= 0) || a.bKILLED || a.bCORPSE ) continue;
// ignore if not targetted
if ( a.target != players[consoleplayer].mo ) continue;
// ignore friends
if ( a.IsFriend(players[consoleplayer].mo) ) continue;
// ignore if player can't see it
if ( !SWWMUtility.InPlayerFOV(players[consoleplayer],a) ) continue;
// is it already in?
bool addme = true;
for ( int i=0; i<combatactors.Size(); i++ )
{
if ( combatactors[i] != a ) continue;
addme = false;
combattics[i] = gametic;
break;
}
// add it in
if ( addme )
{
combatactors.Push(a);
combattics.Push(gametic);
enteredcombat = true;
if ( a.bBOSS || a.FindInventory("BossMarker") )
bossfound = true;
}
}
// be smart, demo-chan, don't shout if you're invisible, or you'll make it worse
if ( enteredcombat && ((bossfound && (!lastcombat || (gametic > lastcombat+240))) || (!bossfound && (!highesttic || (gametic > highesttic+700)))) && !players[consoleplayer].mo.FindInventory("GhostPower") )
lastcombat = AddOneliner("fightstart",1,10);
}
private void OneHundredPercentCheck()
{
// not in DM
if ( deathmatch ) return;
// not in Hexen, due to its fully hub-based nature
if ( gameinfo.gametype&GAME_Hexen ) return;
if ( !mapclear && (restartmus > 0) )
{
restartmus--;
if ( restartmus == 0 ) S_ChangeMusic(lastmus,lastorder,lastloop,true);
return;
}
// ignore levels that have NOTHING
if ( (level.total_secrets <= 0) && (level.total_items <= 0) && (level.total_monsters <= 0) ) return;
if ( mapclear )
{
if ( (musplaying.name != "music/nomusic.ogg") && (musplaying.name != "music/solitary.ogg") )
{
lastmus = musplaying.name;
lastorder = musplaying.baseorder;
lastloop = musplaying.loop;
S_ChangeMusic((startmus>0)?"music/nomusic.ogg":"music/solitary.ogg",force:true);
}
if ( startmus > 0 ) startmus--;
else if ( startmus == 0 )
{
startmus = -1;
S_ChangeMusic("music/solitary.ogg",force:true);
}
if ( (level.found_secrets < level.total_secrets) || (level.found_items < level.total_items) || (level.killed_monsters < level.total_monsters) )
{
restartmus = 25;
S_ChangeMusic("music/nomusic.ogg",force:true);
S_StartSound("recordscratch",CHAN_VOICE,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE);
mapclear = false;
if ( mapclearagain > 1 ) Console.Printf(StringTable.Localize("$SWWM_NOTCLEARAGAIN"));
else Console.Printf(StringTable.Localize("$SWWM_NOTCLEAR"));
}
return;
}
if ( (level.found_secrets < level.total_secrets) || (level.found_items < level.total_items) || (level.killed_monsters < level.total_monsters) ) return;
restartmus = 0;
mapclear = true;
if ( mapclearagain ) Console.Printf(StringTable.Localize("$SWWM_ALLCLEARAGAIN"),500);
else Console.Printf(StringTable.Localize("$SWWM_ALLCLEAR"),5000);
S_StartSound("misc/wow",CHAN_VOICE,CHANF_UI|CHANF_NOPAUSE|CHANF_OVERLAP,1,ATTN_NONE);
lastmus = musplaying.name;
lastorder = musplaying.baseorder;
lastloop = musplaying.loop;
S_ChangeMusic("music/nomusic.ogg",force:true);
startmus = 1050;
for ( int i=0; i<MAXPLAYERS; i++ )
{
if ( !playeringame[i] || !players[i].mo ) continue;
let f = Actor.Spawn("PartyTime",players[i].mo.pos);
f.bAMBUSH = true;
if ( mapclearagain )
{
SWWMCredits.Give(players[i],500);
SWWMScoreObj.SpawnFromHandler(self,500,players[i].mo.Vec3Offset(0,0,players[i].mo.Height/2));
}
else
{
SWWMCredits.Give(players[i],5000);
SWWMScoreObj.SpawnFromHandler(self,5000,players[i].mo.Vec3Offset(0,0,players[i].mo.Height/2));
}
}
mapclearagain++;
if ( !iwantdie ) return;
for ( int i=0; i<MAXPLAYERS; i++ )
{
if ( !playeringame[i] ) continue;
let demo = Demolitionist(players[i].mo);
if ( !demo || !demo.mystats ) continue;
if ( demo.mystats.deaths > 0 ) return;
}
SWWMUtility.MarkAchievement("wantdie",players[consoleplayer]);
}
private void UpdateHUDObjects()
{
// score objects
SWWMScoreObj so = scorenums;
SWWMScoreObj soprev = null, sonext;
while ( so )
{
sonext = so.next;
if ( so.Tick() )
{
if ( soprev ) soprev.next = sonext;
else scorenums = sonext;
so.Destroy();
}
else soprev = so;
so = sonext;
}
SWWMDamNum dn = damnums;
SWWMDamNum dnprev = null, dnnext;
while ( dn )
{
dnnext = dn.next;
if ( dn.Tick() )
{
if ( dnprev ) dnprev.next = dnnext;
else damnums = dnnext;
dn.Destroy();
}
else dnprev = dn;
dn = dnnext;
}
// interest markers
SWWMInterest ip = intpoints;
SWWMInterest ipprev = null, ipnext;
while ( ip )
{
ipnext = ip.next;
if ( ip.Tick() )
{
if ( ipprev ) ipprev.next = ipnext;
else intpoints = ipnext;
ip.Destroy();
}
else ipprev = ip;
ip = ipnext;
}
}
// "simple" tracking (used by the minimap)
private void SimpleTracking()
{
if ( (gamestate != GS_LEVEL) || !swwm_mm_enable )
{
while ( strackers )
{
SWWMSimpleTracker next = strackers.next;
strackers.Destroy();
strackers = next;
}
return;
}
// update trackers for anything around the player
double viewdist = SWWMStatusBar.MAPVIEWDIST*swwm_mm_zoom;
// still about as expensive as using a BlockThingsIterator, but without the need to allocate one every tic
foreach ( s:level.Sectors ) for ( Actor a=s.thinglist; a; a=a.snext )
{
Vector2 rv = a.pos.xy-players[consoleplayer].Camera.pos.xy;
if ( max(abs(rv.x)-a.radius,abs(rv.y)-a.radius) > viewdist )
continue;
if ( a == players[consoleplayer].Camera )
continue;
if ( a is 'GhostTarget' )
continue;
if ( !a.player && !a.bSOLID && !a.bSHOOTABLE && !a.bISMONSTER && !a.bFRIENDLY && !(a is 'Inventory') && !(a is 'Chancebox') )
continue;
if ( !level.allmap && !(deathmatch && (a is 'Inventory') && !a.bDROPPED) && !(a.IsFriend(players[consoleplayer].mo) && !(a.player && (a.player.mo != a))) && !a.CheckSight(players[consoleplayer].Camera,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) )
continue;
if ( a.bKILLED || (a.Health <= 0) || a.bUnmorphed )
continue;
if ( (a is 'Inventory') && (!a.bSPECIAL || Inventory(a).Owner) )
continue;
if ( (a is 'Chancebox') && (a.CurState != a.SpawnState) )
continue;
SWWMSimpleTracker.Track(self,a);
}
// we need to refer to the suckables array to find missiles
foreach ( a:suckableactors )
{
if ( !a || !a.bMISSILE ) continue;
Vector2 rv = a.pos.xy-players[consoleplayer].Camera.pos.xy;
if ( max(abs(rv.x)-a.radius,abs(rv.y)-a.radius) > viewdist )
continue;
if ( !level.allmap && !(a.target && a.target.IsFriend(players[consoleplayer].mo)) && !a.CheckSight(players[consoleplayer].Camera,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) )
continue;
SWWMSimpleTracker.Track(self,a);
}
foreach ( a:beams )
{
if ( !a ) continue;
Vector2 rv = a.pos.xy-players[consoleplayer].Camera.pos.xy;
double rad = SWWMUtility.IsYBeam(a)?(a.scale.y*cos(a.pitch-90)):(a.speed*cos(a.pitch));
if ( max(abs(rv.x)-rad,abs(rv.y)-rad) > viewdist )
continue;
if ( !level.allmap && !(a.target && a.target.IsFriend(players[consoleplayer].mo)) && !a.CheckSight(players[consoleplayer].Camera,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) )
continue;
SWWMSimpleTracker.Track(self,a);
}
// oh boy here we go
int thisgroup = players[consoleplayer].Camera.CurSector.portalgroup;
for ( int i=0; i<level.GetPortalGroupCount(); i++ )
{
if ( i == thisgroup ) continue;
Vector2 relpos = players[consoleplayer].Camera.pos.xy+level.GetDisplacement(thisgroup,i);
// still about as expensive as using a BlockThingsIterator, but without the need to allocate one every tic
foreach ( s:level.Sectors ) for ( Actor a=s.thinglist; a; a=a.snext )
{
Vector2 rv = a.pos.xy-relpos;
if ( max(abs(rv.x)-a.radius,abs(rv.y)-a.radius) > viewdist )
continue;
if ( a == players[consoleplayer].Camera )
continue;
if ( a is 'GhostTarget' )
continue;
if ( !a.player && !a.bSOLID && !a.bSHOOTABLE && !a.bISMONSTER && !a.bFRIENDLY && !(a is 'Inventory') && !(a is 'Chancebox') )
continue;
if ( !level.allmap && !(deathmatch && (a is 'Inventory') && !a.bDROPPED) && !a.IsFriend(players[consoleplayer].mo) && !a.CheckSight(players[consoleplayer].Camera,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) )
continue;
if ( a.bKILLED || (a.Health <= 0) || a.bUnmorphed )
continue;
if ( (a is 'Inventory') && (!a.bSPECIAL || Inventory(a).Owner) )
continue;
if ( (a is 'Chancebox') && (a.CurState != a.SpawnState) )
continue;
SWWMSimpleTracker.Track(self,a);
}
// we need to refer to the suckables array to find missiles
foreach ( a:suckableactors )
{
if ( !a || !a.bMISSILE ) continue;
Vector2 rv = a.pos.xy-relpos;
if ( max(abs(rv.x)-a.radius,abs(rv.y)-a.radius) > viewdist )
continue;
if ( !level.allmap && !(a.target && a.target.IsFriend(players[consoleplayer].mo)) && !a.CheckSight(players[consoleplayer].Camera,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) )
continue;
SWWMSimpleTracker.Track(self,a);
}
foreach ( a:beams )
{
if ( !a ) continue;
Vector2 rv = a.pos.xy-relpos;
double rad = SWWMUtility.IsYBeam(a)?(a.scale.y*cos(a.pitch-90)):(a.speed*cos(a.pitch));
if ( max(abs(rv.x)-rad,abs(rv.y)-rad) > viewdist )
continue;
if ( !level.allmap && !(a.target && a.target.IsFriend(players[consoleplayer].mo)) && !a.CheckSight(players[consoleplayer].Camera,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) )
continue;
SWWMSimpleTracker.Track(self,a);
}
}
SWWMSimpleTracker trk = strackers;
SWWMSimpleTracker prev = null, next;
while ( trk )
{
next = trk.next;
// minimize lifespan of destroyed targets
if ( !trk.target ) trk.lastupdate = min(trk.lastupdate,level.maptime);
else if ( !trk.expired )
{
// "last breath" update
if ( (trk.target.bKILLED || (trk.target.Health <= 0))
|| ((trk.target is 'Inventory') && (!trk.target.bSPECIAL || Inventory(trk.target).Owner))
|| ((trk.target is 'Chancebox') && (trk.target.CurState != trk.target.SpawnState))
|| (trk.target.default.bMISSILE && !trk.target.bMISSILE)
|| trk.target.bUnmorphed )
trk.Update();
}
// prune expired trackers
if ( trk.lastupdate+140 < level.maptime )
{
if ( !prev ) strackers = trk.next;
else prev.next = trk.next;
trk.Destroy();
}
else prev = trk;
trk = next;
}
}
}