diff --git a/README.md b/README.md index 50740af7b..97dd1a0c5 100644 --- a/README.md +++ b/README.md @@ -463,9 +463,12 @@ be configurable. ### Top left corner -Message display, with separate queues for chat, critical messages and -obituaries so you won't miss anything important. Max lines of each type are -configurable, along with their lifespan. +Message display. Can be configured to show different numbers of lines depending +on whether the chat prompt is open. Chat messages take much longer to expire +than others, so there's less of a chance to miss them, as they might pop back +up when the less important ones expire. A full message history can also be read +at any time in the Knowledge Base. Repeated messages are compressed with a +multiplier suffix. ### Top right corner @@ -495,8 +498,8 @@ Your health and armor, along with an inventory bar. ### Bottom border -Pickup messages. Can configure how many to display and how long they stay, plus -an additional option to merge repeated messages (given a multiplier suffix). +Pickup messages. Repeated pickups will have a multiplier suffix added. Total +lines shown are also configurable. ### Bottom right corner diff --git a/cvarinfo.txt b/cvarinfo.txt index 87c145db1..0c3ec9043 100644 --- a/cvarinfo.txt +++ b/cvarinfo.txt @@ -11,4 +11,7 @@ user int swwm_mutevoice = 0; // mute demolitionist voice // 1 - combat comments // 2 - item/secret comments // 3 - map start comment - // 4 - pain/death and grunts \ No newline at end of file + // 4 - pain/death and grunts +user int swwm_chatduration = 900; // lifespan of chat messages +user int swwm_msgduration = 240; // lifespan of other messages +user int swwm_pickduration = 120; // lifespan of pickup messages diff --git a/language.txt b/language.txt index 92ed7619f..9d39d2878 100644 --- a/language.txt +++ b/language.txt @@ -106,7 +106,11 @@ D_RAGEKIT = "The Ragekit has ragequit."; D_REFRESHER = "The Refresher boost has ended."; D_WARARMOR = "The War Armor is no more."; // messages -SWWM_FINDSECRET = "%s found a secret. +%d"; +SWWM_FINDSECRET = "\cf%s\cf found a secret. +%d\c-"; +SWWM_FINDKEY = "\cf%s\cf got the %s. +%d\c-"; +SWWM_LASTSECRET = "\cf%s\cf found the last secret. +%d\c-"; +SWWM_LASTITEM = "\cf%s\cf got the last item. +%d\c-"; +SWWM_LASTMONSTER = "\cf%s\cf killed the last monster. +%d\c-"; /* SUBTITLES */ // new weapon received diff --git a/zscript/swwm_common.zsc b/zscript/swwm_common.zsc index 8dddd3853..2f41107c8 100644 --- a/zscript/swwm_common.zsc +++ b/zscript/swwm_common.zsc @@ -10,6 +10,50 @@ enum ESWWMGZChannels CHAN_JETPACK = 63206 }; +// Scoreing +Class SWWMCredits : Thinker +{ + PlayerInfo myplayer; + int credits; + + static void GiveCredits( PlayerInfo p, int amount ) + { + let c = FindCredits(p); + if ( (c.credits+amount < c.credits) || (c.credits+amount > 999999999) ) c.credits = 999999999; + else c.credits += amount; + } + + static bool TakeCredits( PlayerInfo p, int amount ) + { + let c = FindCredits(p); + // too much! + if ( (c.credits-amount < 0) || (c.credits-amount > c.credits) ) return false; + c.credits -= amount; + return true; + } + + static int GetCredits( PlayerInfo p ) + { + let c = FindCredits(p); + return c.credits; + } + + private static SWWMCredits FindCredits( PlayerInfo p ) + { + let ti = ThinkerIterator.Create("SWWMCredits",STAT_STATIC); + SWWMCredits t; + while ( t = SWWMCredits(ti.Next()) ) + { + if ( t.myplayer != p ) continue; + return t; + } + t = new("SWWMCredits"); + t.ChangeStatNum(STAT_STATIC); + t.myplayer = p; + return t; + } +} + // One-liners Class SWWMOneLiner : HUDMessageBase { @@ -579,6 +623,8 @@ Class SWWMHandler : EventHandler int spreecount[MAXPLAYERS]; int lastkill[MAXPLAYERS]; int multilevel[MAXPLAYERS]; + int lastitemcount[MAXPLAYERS]; + int creditsbridge[MAXPLAYERS]; // curse the gap between play/ui transient CVar mutevoice; @@ -642,6 +688,24 @@ Class SWWMHandler : EventHandler flashes.Delete(i); i--; } + // countable item scoring + for ( int i=0; i lastitemcount[i] ) + { + int score = 25; + if ( level.total_items == level.found_items ) + { + score = 2500; + Console.Printf(StringTable.Localize("$SWWM_LASTITEM"),players[i].GetUserName(),score); + } + SWWMCredits.GiveCredits(players[i],score); + lastitemcount[i] = players[i].itemcount; + } + } // combat tracking // prune old entries for ( int i=0; i lastcombat+20)) && (mutevoice.GetInt() < 1) ) + lastcombat = AddOneliner("scorekill",15); + } + if ( !e.Thing.default.bCountKill ) // no credits + return; int pnum = e.DamageSource.PlayerNumber(); if ( level.maptime < (lastkill[pnum]+5*Thinker.TICRATE) ) multilevel[pnum]++; @@ -726,14 +798,14 @@ Class SWWMHandler : EventHandler score = int(score*(1.+.5*min(multilevel[pnum],16))); if ( !tookdamage[pnum] ) score += 100+50*spreecount[pnum]; if ( e.Thing.bBOSS ) score += 10000; - if ( level.killed_monsters == level.total_monsters ) score += 5000; - e.DamageSource.GiveInventory("SWWMCredits",score); + if ( level.killed_monsters == level.total_monsters ) + { + score += 5000; + Console.Printf(StringTable.Localize("$SWWM_LASTMONSTER",e.DamageSource.player.GetUserName()),5000); + } + SWWMCredits.GiveCredits(e.DamageSource.player,score); // TODO floating score for HUD spreecount[pnum]++; - if ( e.DamageSource != players[consoleplayer].mo ) return; - highesttic = gametic; - if ( (!lastcombat || (gametic > lastcombat+20)) && (mutevoice.GetInt() < 1) ) - lastcombat = AddOneliner("scorekill",15); } } diff --git a/zscript/swwm_hud.zsc b/zscript/swwm_hud.zsc index 505d9cfd8..149558835 100644 --- a/zscript/swwm_hud.zsc +++ b/zscript/swwm_hud.zsc @@ -12,37 +12,105 @@ Class SWWMStatusBar : BaseStatusBar HealthTex[4], FuelTex, DashTex; HUDFont mTewiFont; - Array ChatMsgs, DeathMsgs, MiscMsgs, PickMsgs; + // "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; + 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; DynamicValueInterpolator HealthInter, ScoreInter, FuelInter, DashInter; override void FlushNotify() { - ChatMsgs.Clear(); - DeathMsgs.Clear(); - MiscMsgs.Clear(); - PickMsgs.Clear(); + // flush non-chat messages + for ( int i=0; i= PRINT_CHAT ) continue; + MainQueue.Delete(i); + i--; + } + } + + override bool ProcessNotify( EPrintLevel printlevel, String outline ) + { + 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]); + let m = new("MsgLine"); + m.str = outline.Left(outline.Length()-1); // strip newline + m.type = printlevel; + m.tic = gametic; + // set lifespan for each type + if ( printlevel == PRINT_LOW ) m.tic += pickduration.GetInt(); + else if ( printlevel <= PRINT_HIGH ) m.tic += msgduration.GetInt(); + else m.tic += chatduration.GetInt(); + m.rep = 1; + // append to full history + 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 0 ) + { + int mstart = max(0,MainQueue.Size()-(1+maxchat[chatopen>=gametic].GetInt())); + double xx = margin, yy = margin; + Screen.DrawTexture(ChatTex[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); + BrokenLines l = mTewiFont.mFont.BreakLines(cstr,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+40); + 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); + BrokenLines l = mTewiFont.mFont.BreakLines(cstr,int(ss.x*.75)); + for ( int j=l.Count()-1; j>=0; j-- ) + { + int len = mTewiFont.mFont.StringWidth(l.StringAt(j)); + double xx = (ss.x-len)/2.; + Screen.Dim("Black",.8,int((xx-6)*hs.x),int(yy*hs.y),int((len+12)*hs.x),int(15*hs.y)); + Screen.DrawText(mTewiFont.mFont,pickcol.GetInt(),xx,yy+1,l.StringAt(j),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true); + yy -= 15; + } + } + } + } + + 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 + for ( int i=0; i 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 Draw( int state, double TicFrac ) diff --git a/zscript/swwm_inventory.zsc b/zscript/swwm_inventory.zsc index d91e8a613..582b45368 100644 --- a/zscript/swwm_inventory.zsc +++ b/zscript/swwm_inventory.zsc @@ -1,17 +1,3 @@ -// Scoring -Class SWWMCredits : Inventory -{ - Default - { - +INVENTORY.UNTOSSABLE; - +INVENTORY.UNDROPPABLE; - +INVENTORY.UNCLEARABLE; - +INVENTORY.ALWAYSPICKUP; - Inventory.MaxAmount 999999999; - Inventory.InterHubAmount 999999999; - } -} - // Base class for all SWWM Armors Class SWWMArmor : Armor { diff --git a/zscript/swwm_player.zsc b/zscript/swwm_player.zsc index 7a9610b6c..cad78c3fe 100644 --- a/zscript/swwm_player.zsc +++ b/zscript/swwm_player.zsc @@ -6,6 +6,7 @@ Class Demolitionist : PlayerPawn double dashfuel, dashboost; int dashcooldown, boostcooldown, fuelcooldown; bool dashsnd; + bool key_reentrant; int lastdamage; bool lastground; @@ -109,6 +110,8 @@ Class Demolitionist : PlayerPawn override void Tick() { Super.Tick(); + if ( !myvoice ) myvoice = CVar.GetCVar('swwm_voicetype',player); + if ( !mute ) mute = CVar.GetCVar('swwm_mutevoice',player); if ( player.onground && !bNoGravity && !lastground && (waterlevel < 2) && (health > 0) ) { if ( lastvelz < -30 ) @@ -127,6 +130,8 @@ Class Demolitionist : PlayerPawn } } if ( lastvelz < -10 ) A_StartSound("demolitionist/runstop",CHAN_FOOTSTEP,CHANF_OVERLAP); + if ( (lastvelz < -gruntspeed) && (mute.GetInt() < 4) ) + A_StartSound(String.Format("voice/%s/grunt",myvoice.GetString()),CHAN_DEMOVOICE,CHANF_OVERLAP); A_Footstep(0,true,clamp(-lastvelz*0.05,0.01,1.0)); } lastground = player.onground; @@ -454,11 +459,15 @@ Class Demolitionist : PlayerPawn if ( !mute ) mute = CVar.GetCVar('swwm_mutevoice',player); int score = 500; // last secret (this is called before counting it up, so have to subtract) - if ( level.found_secrets == level.total_secrets-1 ) score = 5000; - Console.Printf(StringTable.Localize("$SWWM_FINDSECRET"),player.GetUserName(),score); + if ( level.found_secrets == level.total_secrets-1 ) + { + score = 5000; + Console.Printf(StringTable.Localize("$SWWM_LASTSECRET"),player.GetUserName(),score); + } + else Console.Printf(StringTable.Localize("$SWWM_FINDSECRET"),player.GetUserName(),score); if ( CheckLocalView() && (mute.GetInt() < 2) ) SWWMHandler.AddOneliner("findsecret",40); - GiveInventory("SWWMCredits",score); - return true; + SWWMCredits.GiveCredits(player,score); + return false; } override void AddInventory( Inventory item ) { @@ -466,14 +475,20 @@ Class Demolitionist : PlayerPawn Super.AddInventory(item); if ( (item is 'Weapon') && CheckLocalView() && (mute.GetInt() < 2) ) SWWMHandler.AddOneliner("getweapon"); - if ( multiplayer && (item is 'Key') ) + if ( (item is 'Key') && !key_reentrant ) { + // score + int score = 250; + Console.Printf(StringTable.Localize("$SWWM_FINDKEY"),player.GetUserName(),item.GetTag(),score); + SWWMCredits.GiveCredits(player,score); // share all keys in mp for ( int i=0; i