swwmgz_m/zscript/handler/swwm_handler_worldtick.zsc

441 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;
int strackers_cnt;
bool mnotify;
bool allkills, allitems, allsecrets;
bool mapclear;
int mapclearagain, restartmus, startmus;
double musvol;
String lastmus;
int lastorder;
bool lastloop;
transient ThinkerIterator cti;
private void LangRefresh()
{
if ( (language != curlang) || (swwm_funtags != curfuntags) )
{
// manually refresh some tags if language has changed
for ( SWWMCombatTracker t=trackers; t; t=t.next )
t.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
if ( !cti ) cti = ThinkerIterator.Create("Actor");
else cti.Reinit();
Actor a, keyactor = null;
bool bossfound = false;
while ( a = Actor(cti.Next()) )
{
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;
// [HDoom] ignore cute girls
if ( SWWMHDoomHandler.IsCuteGirl(a.target) ) 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;
let ti = ThinkerIterator.Create("SWWMStats",Thinker.STAT_STATIC);
SWWMStats s;
while ( s = SWWMStats(ti.Next()) )
{
if ( s.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();
scorenums_cnt--;
}
else soprev = so;
so = sonext;
}
so = damnums;
soprev = null;
while ( so )
{
sonext = so.next;
if ( so.Tick() )
{
if ( soprev ) soprev.next = sonext;
else damnums = sonext;
so.Destroy();
damnums_cnt--;
}
else soprev = so;
so = sonext;
}
// 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();
intpoints_cnt--;
}
else ipprev = ip;
ip = ipnext;
}
// combat trackers
SWWMCombatTracker trk = trackers;
SWWMCombatTracker trkprev = null, trknext;
int dbar = swwm_damagetarget;
while ( trk )
{
trknext = trk.next;
trk.dbar = dbar;
if ( trk.Tick() )
{
if ( trkprev ) trkprev.next = trknext;
else trackers = trknext;
trk.Destroy();
trackers_cnt--;
}
else trkprev = trk;
trk = trknext;
}
}
// "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;
}
strackers_cnt = 0;
return;
}
// update trackers for anything around the player
double viewdist = SWWMStatusBar.MAPVIEWDIST*swwm_mm_zoom;
BlockThingsIterator bt = BlockThingsIterator.Create(players[consoleplayer].Camera,viewdist);
while ( bt.Next() )
{
let a = bt.Thing;
if ( !a ) 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 ( 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))) && !players[consoleplayer].Camera.CheckSight(a,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
for ( int i=0; i<suckableactors.Size(); i++ )
{
let a = suckableactors[i];
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)) && !players[consoleplayer].Camera.CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) )
continue;
SWWMSimpleTracker.Track(self,a);
}
for ( int i=0; i<beams.Size(); i++ )
{
let a = beams[i];
if ( !a ) continue;
Vector2 rv = a.pos.xy-players[consoleplayer].Camera.pos.xy;
double rad = 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)) && !players[consoleplayer].Camera.CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) )
continue;
SWWMSimpleTracker.Track(self,a);
}
bt.Destroy();
if ( psectors.Size() > 1 )
{
// oh boy here we go
int thisgroup = players[consoleplayer].Camera.CurSector.portalgroup;
for ( int i=0; i<psectors.Size(); i++ )
{
if ( i == thisgroup ) continue;
Vector2 relpos = players[consoleplayer].Camera.pos.xy+SWWMUtility.PortalDisplacement(level.Sectors[psectors[thisgroup]],level.Sectors[psectors[i]]);
if ( bt ) bt.Destroy();
bt = BlockThingsIterator.CreateFromPos(relpos.x,relpos.y,players[consoleplayer].Camera.pos.z,players[consoleplayer].Camera.pos.z+players[consoleplayer].Camera.height,viewdist,false);
while ( bt.Next() )
{
let a = bt.Thing;
if ( !a ) continue;
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) && !players[consoleplayer].Camera.CheckSight(a,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
for ( int i=0; i<suckableactors.Size(); i++ )
{
let a = suckableactors[i];
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)) && !players[consoleplayer].Camera.CheckSight(a,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) )
continue;
SWWMSimpleTracker.Track(self,a);
}
for ( int i=0; i<beams.Size(); i++ )
{
let a = beams[i];
if ( !a ) continue;
Vector2 rv = a.pos.xy-relpos;
double rad = 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)) && !players[consoleplayer].Camera.CheckSight(a,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();
strackers_cnt--;
}
else prev = trk;
trk = next;
}
}
}