swwmgz_m/zscript/hud/swwm_hudobjects.zsc
Marisa Kirisame a7eda2702a Migrate various thinkers to plain objects.
WIP: Gib models currently in progress.
WIP: IK re-rigging of Demolitionist in progress.
2022-02-28 23:45:18 +01:00

538 lines
14 KiB
Text

// hud-related storage objects
// (used to be thinkers, but this might be lighter on performance)
Enum EScoreObjType
{
ST_Score,
ST_Damage,
ST_Health,
ST_Armor
};
// floating scores
Class SWWMScoreObj play
{
Array<int> xtcolor;
Array<int> xscore;
Array<String> xstr;
int tcolor;
int score;
Vector3 pos;
int lifespan, initialspan;
int starttic, seed, seed2;
SWWMScoreObj next;
bool damnum;
Actor acc;
static SWWMScoreObj Spawn( int score, Vector3 pos, int type = ST_Score, Actor acc = null, int tcolor = -1 )
{
// early checks
if ( (type == ST_SCORE) && !swwm_scorenums ) return null;
else if ( (type > ST_SCORE) && !swwm_healthnums ) return null;
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
if ( !hnd ) return null;
let o = new("SWWMScoreObj");
o.score = score;
o.pos = pos;
o.lifespan = o.initialspan = 60;
if ( tcolor != -1 ) o.tcolor = tcolor;
else switch ( type )
{
case ST_Score:
o.tcolor = swwm_numcolor_scr;
break;
case ST_Damage:
o.tcolor = swwm_numcolor_dmg;
break;
case ST_Health:
o.tcolor = swwm_numcolor_hp;
break;
case ST_Armor:
o.tcolor = swwm_numcolor_ap;
break;
}
o.starttic = level.maptime;
o.seed = Random[ScoreBits]();
o.seed2 = Random[ScoreBits]();
o.damnum = (type > ST_Score);
o.acc = acc;
if ( o.damnum )
{
o.next = hnd.damnums;
hnd.damnums = o;
hnd.damnums_cnt++;
}
else
{
o.next = hnd.scorenums;
hnd.scorenums = o;
hnd.scorenums_cnt++;
}
return o;
}
void AppendXString( String xstr, int xscore = 0, int xtcolor = int.min )
{
self.xscore.Push(xscore);
self.xstr.Push(xstr);
self.xtcolor.Push((xtcolor==int.min)?swwm_numcolor_bonus:xtcolor);
}
bool Tick()
{
lifespan--;
return (lifespan <= 0);
}
}
enum EInterestType
{
INT_Key,
INT_Exit
};
Class SWWMInterestMarker : MapMarker
{
Default
{
Scale 2.;
Args 0, 0, 1;
+DORMANT;
}
States
{
Spawn:
EIXT ABCD -1;
Stop;
}
}
Class SWWMInterest play
{
int type, exittype;
Key trackedkey;
Line trackedline;
Actor marker;
Vector3 pos;
SWWMInterest next;
String keytag;
static SWWMInterest Spawn( Vector3 pos = (0,0,0), Key thekey = null, Line theline = null, int theexit = 0 )
{
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
if ( !hnd ) return null;
if ( (!thekey && !theline) || (thekey && theline) ) return null;
let i = new("SWWMInterest");
i.trackedkey = thekey;
i.trackedline = theline;
if ( thekey )
{
i.type = INT_Key;
i.keytag = thekey.GetTag();
i.marker = Actor.Spawn("SWWMInterestMarker",thekey.pos);
if ( thekey is 'SWWMKey' )
{
Class<Key> k = thekey.species;
let def = GetDefaultByType(k);
i.marker.picnum = def.SpawnState.GetSpriteTexture(0);
}
else i.marker.picnum = thekey.SpawnState.GetSpriteTexture(0);
i.marker.target = thekey;
i.marker.scale = thekey.scale;
i.marker.bDORMANT = !level.allmap;
}
else if ( theline )
{
i.type = INT_Exit;
i.exittype = theexit;
i.marker = Actor.Spawn("SWWMInterestMarker",pos);
i.marker.SetState(i.marker.SpawnState+theexit);
i.marker.bDORMANT = !level.allmap;
}
else
{
i.Destroy();
return null;
}
i.pos = thekey?thekey.Vec3Offset(0,0,thekey.height/2):pos;
i.next = hnd.intpoints;
hnd.intpoints = i;
hnd.intpoints_cnt++;
return i;
}
bool Tick()
{
// update
if ( (type == INT_Key) && (!trackedkey || trackedkey.Owner) )
{
marker.Destroy();
return true;
}
if ( trackedkey )
{
pos = trackedkey.Vec3Offset(0,0,trackedkey.height/2);
marker.SetOrigin(pos,true);
}
return false;
}
}
Class SWWMItemSense play
{
Actor item;
String tag;
int updated;
bool scoreitem, vipitem;
Demolitionist parent;
SWWMItemSense next;
Vector3 pos;
static SWWMItemSense Spawn( Demolitionist parent, Actor item )
{
if ( !parent || !item ) return null;
// only refresh the updated time if existing
for ( SWWMItemSense s=parent.itemsense; s; s=s.next )
{
if ( s.item != item ) continue;
s.updated = level.maptime+35;
s.pos = item.Vec3Offset(0,0,item.height);
return s;
}
let i = new("SWWMItemSense");
i.item = item;
if ( item is 'SWWMRespawnTimer' )
{
i.scoreitem = SWWMUtility.IsScoreItem(item.tracer);
i.vipitem = SWWMUtility.IsVipItem(item.tracer);
}
else
{
i.scoreitem = SWWMUtility.IsScoreItem(item);
i.vipitem = SWWMUtility.IsVipItem(item);
}
i.parent = parent;
i.updated = level.maptime+35;
i.UpdateTag();
i.pos = item.Vec3Offset(0,0,item.height);
i.next = parent.itemsense;
parent.itemsense = i;
parent.itemsense_cnt++;
return i;
}
void UpdateTag()
{
let i = item;
if ( i is 'SWWMRespawnTimer' ) i = i.tracer;
if ( !i ) return;
// our ammo types use the pickup message, as it's amount-aware
if ( (i is 'SWWMAmmo') || (i is 'MagAmmo') )
tag = Inventory(i).PickupMessage();
else tag = i.GetTag();
}
bool Tick()
{
// expire
if ( !parent || (level.maptime > updated+70) ) return true;
return false;
}
}
// enemy combat tracker
Class SWWMCombatTracker play
{
Actor mytarget;
String mytag;
int updated, lasthealth, maxhealth;
DynamicValueInterpolator intp;
Vector3 pos, prevpos, oldpos, oldprev;
PlayerInfo myplayer;
SWWMCombatTracker next;
bool legged, mutated;
int tcnt;
double height;
int mxdist, dbar;
bool bBOSS, bFRIENDLY;
bool firsthit;
bool bUpdateMorph;
String unmorphedtag;
void UpdateTag()
{
if ( mytarget && (mytarget.player || mytarget.bISMONSTER || (mytarget is 'BossBrain') || (mytarget is 'SWWMHangingKeen') || (mytarget is 'Demolitionist')) )
{
String realtag = swwm_funtags?SWWMUtility.GetFunTag(mytarget,FallbackTag):mytarget.GetTag(FallbackTag);
if ( realtag == FallbackTag )
{
realtag = mytarget.GetClassName();
SWWMUtility.BeautifyClassName(realtag);
}
mytag = mytarget.player?(mytarget.player.mo!=mytarget)?String.Format(StringTable.Localize("$FN_VOODOO"),mytarget.player.GetUserName()):mytarget.player.GetUserName():((mytarget is 'PlayerPawn')&&(!mytarget.player))?StringTable.Localize("$FN_VOODOO_NP"):realtag;
}
else mytag = "";
}
static SWWMCombatTracker Spawn( Actor target, bool update = false )
{
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
if ( !hnd ) return null;
// NOTE: These are only ever called once a thing spawns, so we don't need to "check" if entries already exist
// this check will only be performed in "update mode", i.e. when called from the swwmupdatetrackers netevent,
// or when a monster is revived
SWWMCombatTracker t;
if ( update ) for ( t=hnd.trackers; t; t=t.next )
{
if ( t.mytarget != target ) continue;
return t;
}
t = new("SWWMCombatTracker");
t.mytarget = target;
t.UpdateTag();
if ( target.player )
{
t.lasthealth = target.health;
t.maxhealth = target.default.health;
}
else t.lasthealth = t.maxhealth = target.health;
t.updated = int.min;
t.height = target.height;
t.pos = level.Vec3Offset(target.pos,(0,0,t.height));
t.prevpos = level.Vec3Offset(target.prev,(0,0,t.height));
t.oldpos = target.pos;
t.oldprev = target.prev;
t.intp = DynamicValueInterpolator.Create(t.lasthealth,.5,1,100);
t.myplayer = target.player;
t.next = hnd.trackers;
t.bBOSS = target.bBOSS;
t.bFRIENDLY = target.IsFriend(players[consoleplayer].mo);
hnd.trackers = t;
hnd.trackers_cnt++;
return t;
}
bool Tick()
{
// is target gone or dead?
if ( !mytarget || (mytarget.Health <= 0) )
{
// we're done
if ( updated > level.maptime ) updated = level.maptime;
lasthealth = 0;
prevpos = pos; // prevent stuttering
intp.Update(lasthealth);
if ( level.maptime > updated+35 ) return true;
return false;
}
// don't update dormant targets
if ( mytarget.bDORMANT )
return false;
// only update height/position while alive
bool heightchanged = false;
if ( height != mytarget.height ) heightchanged = true;
height = mytarget.height;
if ( heightchanged || (mytarget.pos != oldpos) || (mytarget.prev != oldprev) )
{
oldpos = mytarget.pos;
oldprev = mytarget.prev;
pos = level.Vec3Offset(mytarget.pos,(0,0,height));
prevpos = level.Vec3Offset(mytarget.prev,(0,0,height));
}
if ( bUpdateMorph && !(mytarget is 'MorphedMonster') )
{
// reset our tag
mytag = unmorphedtag;
bUpdateMorph = false;
}
tcnt++;
if ( (tcnt == 1) && !mytarget.player )
{
// post-spawn health inflation check
if ( lasthealth > maxhealth )
{
maxhealth = lasthealth;
intp.Reset(lasthealth);
}
// post-spawn morph check
if ( (mytarget is 'MorphedMonster') && MorphedMonster(mytarget).UnmorphedMe )
{
// look for a previous tracker that has the same target as us
for ( SWWMCombatTracker t=next; t; t=t.next )
{
if ( t.mytarget != mytarget ) continue;
// change its tag and destroy ourselves (such is life)
t.bUpdateMorph = true;
t.unmorphedtag = t.mytag;
t.mytag = String.Format("%s (%s)",mytag,t.unmorphedtag);
return true;
}
}
}
if ( (tcnt == 6) && !mytarget.player )
{
// legendoom check
for ( Inventory i=mytarget.inv; i; i=i.inv )
{
if ( i.GetClassName() != "LDLegendaryMonsterToken" ) continue;
legged = true;
// adjust for health inflation
if ( lasthealth > maxhealth )
{
maxhealth = lasthealth;
intp.Reset(lasthealth);
}
}
}
if ( legged && !mutated )
{
// check inventory regularly to mark as mutated
for ( Inventory i=mytarget.inv; i; i=i.inv )
{
if ( i.GetClassName() != "LDLegendaryMonsterTransformed" ) continue;
mutated = true;
Console.Printf(StringTable.Localize("$SWWM_LTFORM"),mytag);
}
}// voodoo dolls don't show as friendly
bFRIENDLY = mytarget.IsFriend(players[consoleplayer].mo);
if ( mytarget.Health < lasthealth ) firsthit = true;
lasthealth = mytarget.Health;
intp.Update(lasthealth);
// special update conditions
if ( dbar && !mytarget.player )
{
if ( (dbar == 2) && (lasthealth >= maxhealth) )
return false;
else if ( (dbar == 1) && !firsthit )
return false;
}
if ( (mytarget.bISMONSTER || mytarget.player) && !mytarget.bINVISIBLE && !mytarget.bCORPSE )
{
// players (but not voodoo dolls), always if friendly, otherwise only update if visible
if ( mytarget.player && (mytarget.player.mo == mytarget) )
{
if ( mytarget.IsFriend(players[consoleplayer].mo) ) updated = level.maptime+35;
else if ( ((mxdist <= 0) || (mytarget.Vec3To(players[consoleplayer].Camera).length() < mxdist)) && players[consoleplayer].Camera.CheckSight(mytarget,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) )
updated = level.maptime;
}
// friendlies within a set distance
else if ( mytarget.bFRIENDLY && ((mxdist <= 0) || (mytarget.Vec3To(players[consoleplayer].Camera).length() < mxdist)) && players[consoleplayer].Camera.CheckSight(mytarget,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) updated = level.maptime+35;
// enemies within a set distance that have us as target
else if ( mytarget.target && (mytarget.target.Health > 0) && (mytarget.target.player == players[consoleplayer]) && ((mxdist <= 0) || (mytarget.Vec3To(players[consoleplayer].Camera).length() < mxdist)) && players[consoleplayer].Camera.CheckSight(mytarget,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) updated = level.maptime+70;
// any visible enemies within one quarter of the set distance
else if ( ((mxdist <= 0) || (mytarget.Vec3To(players[consoleplayer].Camera).length() < (mxdist/4))) && players[consoleplayer].Camera.CheckSight(mytarget,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) updated = level.maptime;
}
else if ( (mytarget is 'BossBrain') || (mytarget is 'SWWMHangingKeen') )
{
// special stuff, only if visible
if ( ((mxdist <= 0) || (mytarget.Vec3To(players[consoleplayer].Camera).length() < (mxdist/4))) && players[consoleplayer].Camera.CheckSight(mytarget,SF_IGNOREVISIBILITY|SF_IGNOREWATERBOUNDARY) ) updated = level.maptime;
}
return false;
}
}
// ultralight trackers for certain things
Class SWWMSimpleTracker play
{
Actor target;
double radius;
double angle;
Vector3 pos;
bool isplayer;
Color playercol;
bool ismonster;
bool friendly;
bool countkill;
bool shootable;
bool isitem;
bool countitem;
bool vipitem;
bool expired;
bool ismissile;
bool isbeam;
int lastupdate;
ui double smoothalpha; // smoothened alpha, for ui
SWWMSimpleTracker next;
void Update()
{
if ( !target ) return;
isbeam = SWWMUtility.IsBeamProj(target);
radius = isbeam?(target.speed*cos(target.pitch)):target.radius;
angle = target.angle;
pos = target.pos;
isplayer = target.player;
if ( isplayer ) playercol = target.player.GetColor();
ismonster = target.bISMONSTER;
friendly = target.IsFriend(players[consoleplayer].mo);
countkill = target.bCOUNTKILL;
shootable = target.default.bSHOOTABLE;
ismissile = isbeam||target.default.bMISSILE;
isitem = (target is 'Inventory');
countitem = SWWMUtility.IsScoreItem(target);
vipitem = SWWMUtility.IsVipItem(target);
lastupdate = level.maptime;
if ( isitem )
{
if ( !target.bSPECIAL || Inventory(target).Owner )
expired = true;
else
{
expired = false;
lastupdate += 35;
if ( countitem ) lastupdate += 35;
if ( vipitem ) lastupdate += 70;
}
}
else if ( vipitem )
{
if ( (target is 'Chancebox') && (target.CurState != target.SpawnState) )
expired = true;
else
{
expired = false;
lastupdate += 70;
}
}
else if ( friendly )
{
expired = target.bKILLED;
if ( expired ) lastupdate += 35;
else lastupdate += 140;
}
else if ( ismonster )
{
expired = target.bKILLED||target.bUnmorphed;
if ( !expired )
{
lastupdate += 35;
if ( target.target == players[consoleplayer].mo )
lastupdate += 70;
}
}
else if ( ismissile && !isbeam )
expired = !target.bMISSILE;
else if ( target.default.bSHOOTABLE )
expired = (target.Health<=0);
}
static SWWMSimpleTracker Track( Actor target )
{
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
if ( !hnd ) return null;
SWWMSimpleTracker t;
for ( t=hnd.strackers; t; t=t.next )
{
if ( t.target != target ) continue;
t.Update();
return t;
}
t = new("SWWMSimpleTracker");
t.target = target;
t.Update();
t.next = hnd.strackers;
hnd.strackers = t;
hnd.strackers_cnt++;
return t;
}
}