// The SWWM GZ HUD is mostly built on top of what I had already done for SWWM Z, also a bit of Dark Souls Class MsgLine { String str; int tic, type, rep; } Class SWWMStatusBar : BaseStatusBar { TextureID StatusTex, WeaponTex, ScoreTex, InventoryTex, ChatTex[6], HealthTex[5], FuelTex, DashTex; HUDFont mTewiFont; // "Full History" contains all messages since session start, nothing is flushed // this can be accessed from a section of the knowledge base Array MainQueue, PickupQueue, FullHistory; Actor targetactors[40], scoreactors[60], keyactors[10]; Vector3 exitpoints[10], uselines[10]; // client cvars transient CVar safezone, maxchat[2], maxpick, chatduration, msgduration, pickduration, chatcol, teamcol, obitcol, critcol, pickcol; // shared stuff Vector2 ss, hs; int margin; double FracTic; int chatopen; bool justselected; DynamicValueInterpolator HealthInter, ScoreInter, FuelInter, DashInter; override void FlushNotify() { // flush non-chat messages for ( int i=0; i= PRINT_CHAT ) continue; MainQueue.Delete(i); i--; } } override bool ProcessMidPrint( Font fnt, String msg, bool bold ) { // check for Korax lines if ( msg == StringTable.Localize("$TXT_ACS_MAP02_11_AREYO") ) EventHandler.SendNetworkEvent("swwmkoraxline",0,consoleplayer); else if ( msg == StringTable.Localize("$TXT_ACS_MAP13_11_MYSER") ) EventHandler.SendNetworkEvent("swwmkoraxline",1,consoleplayer); else if ( msg == StringTable.Localize("$TXT_ACS_MAP22_29_ITHIN") ) EventHandler.SendNetworkEvent("swwmkoraxline",2,consoleplayer); else if ( msg == StringTable.Localize("$TXT_ACS_MAP27_10_THENA") ) EventHandler.SendNetworkEvent("swwmkoraxline",3,consoleplayer); else if ( msg == StringTable.Localize("$TXT_ACS_MAP35_14_TOFAC") ) EventHandler.SendNetworkEvent("swwmkoraxline",4,consoleplayer); return false; } override bool ProcessNotify( EPrintLevel printlevel, String outline ) { let m = new("MsgLine"); m.str = outline.Left(outline.Length()-1); // strip newline m.type = printlevel; m.tic = gametic; m.rep = 1; // append chat messages to full history if ( (printlevel == PRINT_CHAT) || (printlevel == PRINT_TEAMCHAT) ) FullHistory.Push(m); // ignore during intermission if ( gamestate != GS_LEVEL ) return false; if ( (printlevel < PRINT_LOW) || (printlevel > PRINT_TEAMCHAT) ) return true; // we couldn't care less about these if ( printlevel == PRINT_LOW ) { // check if repeated for ( int i=0; i= (5*Thinker.TICRATE)-1 ) { if ( CPlayer.mo.InvSel ) S_StartSound("menu/demoscroll",CHAN_BODY,CHANF_UI|CHANF_MAYBE_LOCAL); } if ( CPlayer.inventorytics > 0 ) justselected = false; else { if ( !justselected && CPlayer.mo.InvSel ) S_StartSound("menu/democlose",CHAN_BODY,CHANF_UI|CHANF_MAYBE_LOCAL); justselected = true; } if ( !chatduration ) chatduration = CVar.GetCVar('swwm_chatduration',players[consoleplayer]); if ( !msgduration ) msgduration = CVar.GetCVar('swwm_msgduration',players[consoleplayer]); if ( !pickduration ) pickduration = CVar.GetCVar('swwm_pickduration',players[consoleplayer]); // prune old messages for ( int i=0; i PRINT_HIGH) && (gametic < (MainQueue[i].tic+35*chatduration.GetInt())) ) continue; MainQueue.Delete(i); i--; } // update target actors // update floating scores // update interpolators HealthInter.Update(CPlayer.health); let hnd = SWWMHandler(EventHandler.Find("SWWMHandler")); ScoreInter.Update(SWWMCredits.Get(CPlayer)); let d = Demolitionist(CPlayer.mo); if ( d ) { FuelInter.Update(int(d.dashfuel)); DashInter.Update(int((40-d.dashcooldown)*3.)); } else { FuelInter.Update(0); DashInter.Update(0); } } override void Init() { Super.Init(); SetSize(0,640,360); StatusTex = TexMan.CheckForTexture("graphics/HUD/StatusBox.png",TexMan.Type_Any); DashTex = TexMan.CheckForTexture("graphics/HUD/DashBar.png",TexMan.Type_Any); FuelTex = TexMan.CheckForTexture("graphics/HUD/FuelBar.png",TexMan.Type_Any); HealthTex[0] = TexMan.CheckForTexture("graphics/HUD/HealthBar0.png",TexMan.Type_Any); HealthTex[1] = TexMan.CheckForTexture("graphics/HUD/HealthBar1.png",TexMan.Type_Any); HealthTex[2] = TexMan.CheckForTexture("graphics/HUD/HealthBar2.png",TexMan.Type_Any); HealthTex[3] = TexMan.CheckForTexture("graphics/HUD/HealthBar3.png",TexMan.Type_Any); HealthTex[4] = TexMan.CheckForTexture("graphics/HUD/HealthBarS.png",TexMan.Type_Any); ScoreTex = TexMan.CheckForTexture("graphics/HUD/ScoreBox.png",TexMan.Type_Any); WeaponTex = TexMan.CheckForTexture("graphics/HUD/WeaponBox.png",TexMan.Type_Any); ChatTex[0] = TexMan.CheckForTexture("graphics/HUD/ChatBoxTop.png",TexMan.Type_Any); ChatTex[1] = TexMan.CheckForTexture("graphics/HUD/ChatBoxLine.png",TexMan.Type_Any); ChatTex[2] = TexMan.CheckForTexture("graphics/HUD/ChatBoxBottom.png",TexMan.Type_Any); ChatTex[3] = TexMan.CheckForTexture("graphics/HUD/ChatBoxTop_Smol.png",TexMan.Type_Any); ChatTex[4] = TexMan.CheckForTexture("graphics/HUD/ChatBoxLine_Smol.png",TexMan.Type_Any); ChatTex[5] = TexMan.CheckForTexture("graphics/HUD/ChatBoxBottom_Smol.png",TexMan.Type_Any); InventoryTex = TexMan.CheckForTexture("graphics/HUD/InventoryBox.png",TexMan.Type_Any); mTewiFont = HUDFont.Create("TewiShaded"); HealthInter = DynamicValueInterpolator.Create(100,.1,1,100); ScoreInter = DynamicValueInterpolator.Create(0,.1,1,1000); FuelInter = DynamicValueInterpolator.Create(120,.5,1,100); DashInter = DynamicValueInterpolator.Create(120,.5,1,40); } private void DrawTarget() { // omnisight: usable highlights // omnisight: key locations // targetting array // floating kill scores } private void DrawScore() { Screen.DrawTexture(ScoreTex,false,ss.x-(margin+73),margin,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true); Screen.DrawText(mTewiFont.mFont,Font.CR_FIRE,ss.x-(margin+58),margin+1,String.Format("%09d",clamp(ScoreInter.GetValue(),0,999999999)),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true); } private void DrawInvIcon( Inventory i, double xx, double yy, double alpha = 1., bool forceamt = false ) { if ( !i ) return; Vector2 scl = TexMan.GetScaledSize(i.Icon); double mscl = 30./max(scl.x,scl.y); double dw = (ss.x/mscl), dh = (ss.y/mscl); double dx = (xx+(30-scl.x*mscl)/2)/mscl, dy = (yy+(30-scl.y*mscl)/2)/mscl; if ( i is 'Powerup' ) { Screen.DrawTexture(i.Icon,false,dx,dy,DTA_VirtualWidthF,dw,DTA_VirtualHeightF,dh,DTA_KeepRatio,true,DTA_Alpha,Powerup(i).IsBlinking()?alpha*.5:alpha,DTA_TopOffset,0,DTA_LeftOffset,0); String nstr = String.Format("%ds",Powerup(i).EffectTics/Thinker.TICRATE); int len = mTewiFont.mFont.StringWidth(nstr); Screen.DrawText(mTewiFont.mFont,Font.CR_FIRE,(xx+30)-len,(yy+30)-11,nstr,DTA_VirtualWidthF,dw,DTA_VirtualHeightF,dh,DTA_KeepRatio,true,DTA_Alpha,Powerup(i).IsBlinking()?alpha*.5:alpha); return; } Screen.DrawTexture(i.Icon,false,dx,dy,DTA_VirtualWidthF,dw,DTA_VirtualHeightF,dh,DTA_KeepRatio,true,DTA_Alpha,alpha,DTA_TopOffset,0,DTA_LeftOffset,0); if ( (i.Amount > 1) || forceamt ) { String nstr; if ( (i.Amount > 99999) && !forceamt ) nstr = "99999"; else nstr = String.Format("%d",i.Amount); int len = mTewiFont.mFont.StringWidth(nstr); Screen.DrawText(mTewiFont.mFont,Font.CR_FIRE,(xx+30)-len,(yy+30)-11,nstr,DTA_VirtualWidthF,dw,DTA_VirtualHeightF,dh,DTA_KeepRatio,true,DTA_Alpha,alpha); } } private void DrawInventory() { // active items (armor / powerups) double xx = margin; double yy = ss.y-(margin+60); if ( CPlayer.mo.InvSel && !isInventoryBarVisible() ) yy -= 34; bool drewarmor = false; for ( Inventory i=CPlayer.mo.Inv; i; i=i.Inv ) { if ( (i.Amount <= 0) || (!(i is 'SWWMArmor') && !(i is 'BasicArmor')) ) continue; DrawInvIcon(i,xx,yy,forceamt:true); yy -= 34; drewarmor = true; } yy = ss.y-(margin+60); if ( drewarmor ) xx += 40; else if ( CPlayer.mo.InvSel && !isInventoryBarVisible() ) yy -= 34; for ( Inventory i=CPlayer.mo.Inv; i; i=i.Inv ) { if ( !(i is 'Powerup') || (Powerup(i).EffectTics <= 0) || !(Powerup(i).Icon) ) continue; DrawInvIcon(i,xx,yy); yy -= 34; } // inventory box / bar if ( !CPlayer.mo.InvSel ) return; if ( isInventoryBarVisible() ) { Array bar; bar.Clear(); for ( Inventory i=CPlayer.mo.FirstInv(); i; i=i.NextInv() ) bar.Push(i); int ps = bar.Find(CPlayer.mo.InvSel); Inventory prev[2], next[2]; if ( bar.Size() > 1 ) { if ( ps+1 >= bar.Size() ) next[0] = bar[0]; else next[0] = bar[ps+1]; if ( ps-1 < 0 ) prev[0] = bar[bar.Size()-1]; else prev[0] = bar[ps-1]; } if ( bar.Size() > 2 ) { if ( ps+2 >= bar.Size() ) next[1] = bar[(ps+2)-bar.Size()]; else next[1] = bar[ps+2]; if ( ps-2 < 0 ) prev[1] = bar[bar.Size()+(ps-2)]; else prev[1] = bar[ps-2]; } xx = (ss.x-34)/2; yy = (ss.y+64)/2; Screen.DrawTexture(InventoryTex,false,xx,yy,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true); DrawInvIcon(CPlayer.mo.InvSel,xx+2,yy+2); DrawInvIcon(prev[0],xx-32,yy+2,2./3.); DrawInvIcon(prev[1],xx-66,yy+2,1./3.); DrawInvIcon(next[0],xx+36,yy+2,2./3.); DrawInvIcon(next[1],xx+70,yy+2,1./3.); return; } Screen.DrawTexture(InventoryTex,false,margin,ss.y-(margin+61),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true); DrawInvIcon(CPlayer.mo.InvSel,margin+2,ss.y-(margin+59)); } private void DrawWeapon() { if ( CPlayer.ReadyWeapon is 'SWWMWeapon' ) SWWMWeapon(CPlayer.ReadyWeapon).DrawWeapon(FracTic,ss.x-margin,ss.y-(margin+28),hs,ss); else { // TODO generic display } Screen.DrawTexture(WeaponTex,false,ss.x-(margin+61),ss.y-(margin+29),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true); double xx = ss.x-(margin+58), yy = ss.y-(margin+29); for ( int i=1; i<=10; i++ ) { int ncolor = Font.CR_WHITE; if ( !CPlayer.HasWeaponsInSlot(i%10) ) ncolor = Font.CR_DARKGRAY; else if ( CPlayer.PendingWeapon && (CPlayer.PendingWeapon != WP_NOCHANGE) && (CPlayer.PendingWeapon.SlotNumber == (i%10)) ) ncolor = Font.CR_FIRE; else if ( (!CPlayer.PendingWeapon || (CPlayer.PendingWeapon == WP_NOCHANGE)) && CPlayer.ReadyWeapon && (CPlayer.ReadyWeapon.SlotNumber == (i%10)) ) ncolor = Font.CR_FIRE; else { bool hasammo = false; for ( Inventory inv=CPlayer.mo.Inv; inv; inv=inv.Inv ) { bool dummy; int slot; if ( inv is 'Weapon' ) [dummy, slot] = CPlayer.weapons.LocateWeapon(Weapon(inv).GetClass()); if ( slot != (i%10) ) continue; // CheckAmmo can't be called from ui, so we have to improvise // for SWWM weapons I made a function for this at least if ( (inv is 'SWWMWeapon') && SWWMWeapon(inv).ReportHUDAmmo() ) hasammo = true; else if ( !(inv is 'SWWMWeapon') && ((!Weapon(inv).Ammo1 || (Weapon(inv).Ammo1.Amount > 0)) || (Weapon(inv).Ammo2 && (Weapon(inv).Ammo2.Amount > 0))) ) hasammo = true; } if ( !hasammo ) ncolor = Font.CR_RED; } Screen.DrawText(mTewiFont.mFont,ncolor,xx,yy,String.Format("%d",(i%10)),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true); xx += 12; if ( i == 5 ) { xx = ss.x-(margin+58); yy += 14; } } } private void DrawStatus() { Screen.DrawTexture(StatusTex,false,margin,ss.y-(margin+27),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true); let d = Demolitionist(CPlayer.mo); int dw = DashInter.GetValue(); double alph = .6; if ( !d || (d.dashfuel > 20) || ((gametic%10) < 5) ) alph = 1.; Screen.DrawTexture(DashTex,false,margin+2,ss.y-(margin+21),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_WindowRight,dw,DTA_Alpha,alph); int fw = FuelInter.GetValue(); Screen.DrawTexture(FuelTex,false,margin+2,ss.y-(margin+25),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_WindowRight,fw); if ( isInvulnerable() ) { Screen.DrawTexture(HealthTex[4],false,margin+2,ss.y-(margin+15),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true); Screen.DrawText(mTewiFont.mFont,Font.CR_WHITE,margin+108,ss.y-(margin+16),String.Format("%3d",Random[HudStuff](0,999)),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true); return; } int ht = clamp(HealthInter.GetValue(),0,10000); int hw = min(ht,100); Screen.DrawTexture(HealthTex[0],false,margin+2,ss.y-(margin+15),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_WindowRight,hw); if ( ht > 100 ) { hw = min(ht-100,100); Screen.DrawTexture(HealthTex[1],false,margin+2,ss.y-(margin+15),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_WindowRight,hw); } if ( ht > 200 ) { hw = int(min(ht-100,400)*0.25); Screen.DrawTexture(HealthTex[2],false,margin+2,ss.y-(margin+15),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_WindowRight,hw); } if ( ht > 500 ) { hw = int(min(ht-500,500)*0.2); Screen.DrawTexture(HealthTex[3],false,margin+2,ss.y-(margin+15),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_WindowRight,hw); } int hcolor = Font.CR_RED; if ( ht > 500 ) hcolor = Font.CR_GOLD; else if ( ht > 200 ) hcolor = Font.CR_PURPLE; else if ( ht > 100 ) hcolor = Font.CR_CYAN; Screen.DrawText(mTewiFont.mFont,hcolor,margin+108,ss.y-(margin+16),String.Format("%3d",clamp(ht,0,999)),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true); } private void DrawMessages() { if ( !chatduration ) chatduration = CVar.GetCVar('swwm_chatduration',players[consoleplayer]); if ( !msgduration ) msgduration = CVar.GetCVar('swwm_msgduration',players[consoleplayer]); if ( !pickduration ) pickduration = CVar.GetCVar('swwm_pickduration',players[consoleplayer]); if ( !pickcol ) pickcol = CVar.GetCVar('msg0color',players[consoleplayer]); if ( !obitcol ) obitcol = CVar.GetCVar('msg1color',players[consoleplayer]); if ( !critcol ) critcol = CVar.GetCVar('msg2color',players[consoleplayer]); if ( !chatcol ) chatcol = CVar.GetCVar('msg3color',players[consoleplayer]); if ( !teamcol ) teamcol = CVar.GetCVar('msg4color',players[consoleplayer]); // common message area if ( MainQueue.Size() > 0 ) { int mstart = max(0,MainQueue.Size()-(1+maxchat[chatopen>=gametic].GetInt())); double xx = margin, yy = margin; bool smol = (ss.x<640); Screen.DrawTexture(ChatTex[smol?3:0],false,xx,yy,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true); yy++; for ( int i=mstart; i 1 ) cstr.AppendFormat(" (x%d)",MainQueue[i].rep); int curtime = MainQueue[i].tic-gametic; if ( MainQueue[i].type < PRINT_CHAT ) curtime += 35*msgduration.GetInt(); else curtime += 35*chatduration.GetInt(); double alph = clamp(curtime/20.,0.,1.); BrokenLines l = mTewiFont.mFont.BreakLines(cstr,smol?211:361); for ( int j=0; j 0 ) { // reverse order since they're drawn bottom to top int mend = max(0,PickupQueue.Size()-(1+maxpick.GetInt())); double yy = ss.y-(margin+50); for ( int i=PickupQueue.Size()-1; i>=mend; i-- ) { String cstr = PickupQueue[i].str; if ( PickupQueue[i].rep > 1 ) cstr.AppendFormat(" (x%d)",PickupQueue[i].rep); int curtime = (PickupQueue[i].tic+35*pickduration.GetInt())-gametic; double alph = clamp(curtime/20.,0.,1.); BrokenLines l = mTewiFont.mFont.BreakLines(cstr,int(ss.x*.75)); int maxlen = 0; for ( int j=0; j maxlen ) maxlen = len; } int h = mTewiFont.mFont.GetHeight(); double xx = (ss.x-maxlen)/2.; Screen.Dim("Black",.8*alph,int((xx-6)*hs.x),int((yy-h*(l.Count()-1))*hs.y),int((maxlen+12)*hs.x),int((h*l.Count()+4)*hs.y)); for ( int j=l.Count()-1; j>=0; j-- ) { int len = mTewiFont.mFont.StringWidth(l.StringAt(j)); xx = (ss.x-len)/2.; Screen.DrawText(mTewiFont.mFont,pickcol.GetInt(),xx,yy+2,l.StringAt(j),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_Alpha,alph); yy -= h; } yy -= 6; } } } override bool DrawChat( String txt ) { // ignore during intermission if ( gamestate != GS_LEVEL ) return false; chatopen = gametic+1; // have to add 1 because DrawChat is called after everything else double xx = 2; double yy = ss.y-14; Screen.Dim("Black",.8,0,Screen.GetHeight()-int(15*hs.y),Screen.GetWidth(),int(15*hs.y)); String pname = players[consoleplayer].GetUserName(); // strip colors SWWMUtility.StripColor(pname); String fullstr = String.Format("\cq%s\cd@\cqdemolitionist%d\cn ~ \c-wall %s%s",pname,consoleplayer+1,txt,mTewiFont.mFont.GetCursor()); // cut out to fit int w = mTewiFont.mFont.StringWidth(fullstr); if ( w > ss.x-4 ) { // draw trailing dots Screen.DrawText(mTewiFont.mFont,Font.CR_WHITE,xx,yy,"...",DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true); // shift back xx -= w-(ss.x-4); // draw trimmed Screen.DrawText(mTewiFont.mFont,Font.CR_WHITE,xx,yy,fullstr,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_ClipLeft,int(26*hs.x)); } else Screen.DrawText(mTewiFont.mFont,Font.CR_WHITE,xx,yy,fullstr,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true); return true; } override void DrawPowerups() { // don't do anything } override void Draw( int state, double TicFrac ) { Super.Draw(state,TicFrac); if ( (state != HUD_StatusBar) && (state != HUD_Fullscreen) ) return; if ( !safezone ) safezone = CVar.GetCVar('swwm_hudmargin',players[consoleplayer]); if ( !maxchat[0] ) maxchat[0] = CVar.GetCVar('swwm_maxshown',players[consoleplayer]); if ( !maxchat[1] ) maxchat[1] = CVar.GetCVar('swwm_maxshownbig',players[consoleplayer]); if ( !maxpick ) maxpick = CVar.GetCVar('swwm_maxpickup',players[consoleplayer]); BeginHUD(); hs = GetHUDScale(); ss = (Screen.GetWidth()/hs.x,Screen.GetHeight()/hs.y); margin = clamp(safezone.GetInt(),0,(ss.x<640)?10:40); FracTic = TicFrac; DrawTarget(); DrawScore(); DrawInventory(); DrawStatus(); DrawWeapon(); DrawMessages(); } }