Add extra couch features

This commit is contained in:
2026-09-29 17:36:33 +01:00
parent 598dc20ae3
commit 63c218a0e9
4 changed files with 516 additions and 45 deletions
+51 -11
View File
@@ -11,6 +11,9 @@
// Longer names are cut so one can't eat the whole message.
#define HUD_NAME_BYTES 20
// Put in front of the name of anyone whose grenade hit the couch recently.
#define HUD_GRENADE_TAG "<G!> "
// A user message holds 255 bytes; HudMsg's fixed fields take 34 and the string its terminator.
// Overflowing drops the whole message.
#define HUD_MSG_BYTES 255
@@ -19,14 +22,17 @@
ConVar g_cvEnabled;
ConVar g_cvTime;
ConVar g_cvGrenadeTime;
Handle g_hHudSync;
Handle g_hExpireTimer;
float g_fExpire[MAXPLAYERS + 1]; // GetGameTime() at which this client drops off the list.
float g_fLastHit[MAXPLAYERS + 1]; // Ordering key: the most recent shooter is listed first.
float g_fExpire[MAXPLAYERS + 1]; // GetGameTime() at which this client drops off the list.
float g_fLastHit[MAXPLAYERS + 1]; // Ordering key: the most recent shooter is listed first.
float g_fGrenadeUntil[MAXPLAYERS + 1]; // GetGameTime() at which this client loses the grenade tag.
float g_fNextExpiry; // Soonest expiry among the names currently on screen.
float g_fNextExpiry; // Soonest expiry or tag drop among the names currently listed.
float g_fLastExpiry; // Latest expiry among them; the text is held until then.
bool g_bFlushQueued; // A send is already scheduled for the next frame.
bool g_bHudShown; // Clients currently have text up, so an empty list must clear it.
@@ -47,6 +53,7 @@ public void OnPluginStart()
g_cvEnabled = CreateConVar("sm_couch_hud_enabled", "1", "Show the list of players shooting the couch.", FCVAR_NOTIFY, true, 0.0, true, 1.0);
g_cvTime = CreateConVar("sm_couch_hud_time", "0.5", "How long a player stays on the list after their last hit, in seconds.", FCVAR_NOTIFY, true, 0.5, true, 30.0);
g_cvGrenadeTime = CreateConVar("sm_couch_hud_time_grenade", "4.0", "How long a player stays on the list, tagged " ... HUD_GRENADE_TAG ... ", after their grenade hits the couch, in seconds.", FCVAR_NOTIFY, true, 0.5, true, 30.0);
g_cvEnabled.AddChangeHook(OnEnabledChanged);
@@ -60,11 +67,13 @@ public void OnMapStart()
g_bFlushQueued = false;
g_bHudShown = false;
g_fNextExpiry = 0.0;
g_fLastExpiry = 0.0;
for (int i = 1; i <= MAXPLAYERS; i++)
{
g_fExpire[i] = 0.0;
g_fLastHit[i] = 0.0;
g_fGrenadeUntil[i] = 0.0;
}
}
@@ -72,6 +81,7 @@ public void OnClientPutInServer(int client)
{
g_fExpire[client] = 0.0;
g_fLastHit[client] = 0.0;
g_fGrenadeUntil[client] = 0.0;
}
public void OnClientDisconnect(int client)
@@ -80,6 +90,7 @@ public void OnClientDisconnect(int client)
g_fExpire[client] = 0.0;
g_fLastHit[client] = 0.0;
g_fGrenadeUntil[client] = 0.0;
if (bListed)
QueueFlush();
@@ -106,13 +117,31 @@ public void Couch_OnCouchDamaged(int couch, int attacker, float damage)
if (!g_cvEnabled.BoolValue)
return;
// Extended, never shortened, so gunfire right after a grenade doesn't cut its time short.
float fNow = GetGameTime();
g_fLastHit[attacker] = fNow;
g_fExpire[attacker] = fNow + g_cvTime.FloatValue;
g_fExpire[attacker] = MaxFloat(g_fExpire[attacker], fNow + g_cvTime.FloatValue);
QueueFlush();
}
// Fires just after Couch_OnCouchDamaged for the same hit, so the ordering is already updated.
public void Couch_OnCouchInteraction(int couch, int client, CouchMethod method, const char[] weapon)
{
if (!g_cvEnabled.BoolValue || method != CouchMethod_Grenade)
return;
g_fGrenadeUntil[client] = GetGameTime() + g_cvGrenadeTime.FloatValue;
g_fExpire[client] = MaxFloat(g_fExpire[client], g_fGrenadeUntil[client]);
QueueFlush();
}
float MaxFloat(float a, float b)
{
return (a > b) ? a : b;
}
public void Couch_OnCouchRemoved()
{
ResetAll();
@@ -158,8 +187,9 @@ void Flush()
void Send()
{
// Hold time matches the listing time, so the text leaves on its own if no update ever comes.
SetHudTextParams(0.8, 0.2, g_cvTime.FloatValue, 255, 255, 255, 255, 0, 0.0, 0.0, 0.0);
// Held until the last name lapses, so the text leaves on its own if no update ever comes.
// Names lapsing sooner are dropped by a resend from the expiry timer.
SetHudTextParams(0.8, 0.2, g_fLastExpiry - GetGameTime(), 255, 255, 255, 255, 0, 0.0, 0.0, 0.0);
for (int i = 1; i <= MaxClients; i++)
{
@@ -184,9 +214,11 @@ int BuildHudText(char[] text, int maxlen)
int total = 0;
g_fNextExpiry = 0.0;
g_fLastExpiry = 0.0;
// Selection sort, most recent hit first. The soonest expiry is tracked over everyone listed,
// since a hidden player lapsing still changes the "+ N" count.
// since a hidden player lapsing still changes the "+ N" count. A grenade tag dropping off a
// name that stays listed changes the text too, so it counts as an expiry.
for (;;)
{
int best = 0;
@@ -206,8 +238,15 @@ int BuildHudText(char[] text, int maxlen)
bTaken[best] = true;
order[total++] = best;
if (g_fNextExpiry == 0.0 || g_fExpire[best] < g_fNextExpiry)
g_fNextExpiry = g_fExpire[best];
float fChange = g_fExpire[best];
if (g_fGrenadeUntil[best] > fNow && g_fGrenadeUntil[best] < fChange)
fChange = g_fGrenadeUntil[best];
if (g_fNextExpiry == 0.0 || fChange < g_fNextExpiry)
g_fNextExpiry = fChange;
if (g_fExpire[best] > g_fLastExpiry)
g_fLastExpiry = g_fExpire[best];
}
if (total == 0)
@@ -228,8 +267,8 @@ int BuildHudText(char[] text, int maxlen)
GetClientName(order[rows], sName, sizeof(sName));
TruncateUtf8(sName, HUD_NAME_BYTES);
char sLine[HUD_NAME_BYTES + 2];
FormatEx(sLine, sizeof(sLine), "\n%s", sName);
char sLine[HUD_NAME_BYTES + sizeof(HUD_GRENADE_TAG) + 1];
FormatEx(sLine, sizeof(sLine), "\n%s%s", (g_fGrenadeUntil[order[rows]] > fNow) ? HUD_GRENADE_TAG : "", sName);
int len = strlen(text);
if (len + strlen(sLine) >= maxlen)
@@ -310,6 +349,7 @@ void ResetAll()
{
g_fExpire[i] = 0.0;
g_fLastHit[i] = 0.0;
g_fGrenadeUntil[i] = 0.0;
}
ClearAll();