swwmgz_m/zscript/handler/swwm_handler_worldtick.zsc

368 lines
12 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);
if ( i == consoleplayer ) SWWMScoreObj.SpawnAtActorFromHandler(self,score,players[i].mo);
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;
// not unless at least one player is standing in this sector
// (used for Eviternity 2 MAP33)
if ( allclearsector )
{
bool insector = false;
for ( int i=0; i<MAXPLAYERS; i++ )
{
if ( !playeringame[i] || !players[i].mo ) continue;
if ( players[i].mo.CurSector.Index() == allclearsector )
insector = true;
}
if ( !insector ) 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);
bool altclear = swwm_altclear;
S_StartSound(altclear?"misc/yippeee":"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);
if ( altclear ) f.bSTANDSTILL = true;
else f.bAMBUSH = true;
if ( mapclearagain )
{
SWWMCredits.Give(players[i],500);
if ( i == consoleplayer ) SWWMScoreObj.SpawnAtActorFromHandler(self,500,players[i].mo);
}
else
{
SWWMCredits.Give(players[i],5000);
if ( i == consoleplayer ) SWWMScoreObj.SpawnAtActorFromHandler(self,5000,players[i].mo);
}
}
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;
// still about as expensive as using a BlockThingsIterator, but without the need to allocate one every tic
int thisgroup = players[consoleplayer].Camera.CurSector.portalgroup;
foreach ( s:level.Sectors )
{
// don't check sectors that aren't within bounds, saves some time
if ( !BoxInSectorBounds(s,players[consoleplayer].Camera.pos.xy,viewdist,players[consoleplayer].Camera.CurSector.PortalGroup) )
continue;
for ( Actor a=s.thinglist; a; a=a.snext )
{
Vector2 rv;
if ( s.portalgroup != thisgroup )
{
Vector2 relpos = players[consoleplayer].Camera.pos.xy+level.GetDisplacement(thisgroup,s.portalgroup);
rv = a.pos.xy-relpos;
}
else rv = a.pos.xy-players[consoleplayer].Camera.pos.xy;
double rad;
bool isproj = a.bMISSILE&&SWWMUtility.ValidProjectile(a);
if ( SWWMUtility.IsBeamProj(a) )
{
isproj = true;
rad = SWWMUtility.IsYBeam(a)?(a.scale.y*cos(a.pitch-90)):(a.speed*cos(a.pitch));
}
else rad = a.radius;
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') && !isproj )
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 || (a.GetClassName() == 'aas_token')) ) // autoautosave hotfix
continue;
if ( (a is 'Chancebox') && (a.CurState != a.SpawnState) )
continue;
if ( isproj && !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;
}
}
}