// 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 xtcolor; Array xscore; Array 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 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; } }