WIP: Gib models currently in progress. WIP: IK re-rigging of Demolitionist in progress.
538 lines
14 KiB
Text
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;
|
|
}
|
|
}
|