Couch changes

This commit is contained in:
2026-10-14 16:53:45 +01:00
parent fa8d12ddb5
commit fb971adf6d
5 changed files with 1089 additions and 98 deletions
+218 -19
View File
@@ -4,8 +4,6 @@
#pragma semicolon 1
#pragma newdecls required
#define PREFIX "\x01[SM] \x04"
// A player who keeps moving the couch the same way is reported at most once per this many seconds.
#define LOG_REPEAT_INTERVAL 2.0
@@ -15,7 +13,8 @@
// So commands fail with a message instead of an unbound-native error while the core is unloaded.
bool g_bCoreLoaded;
// Admins who asked to see couch interactions. Per slot, so it survives map changes.
// Who sees couch interactions in their console. On by default for anyone who can run sm_couchlog,
// off for everyone else. Per slot, so it survives map changes.
bool g_bLogging[MAXPLAYERS + 1];
// Per player: what was last reported, and when the same thing may be reported again.
@@ -39,8 +38,32 @@ public void OnPluginStart()
RegAdminCmd("sm_bringcouch", Command_BringCouch, ADMFLAG_CHANGEMAP, "Brings the couch to where you are aiming");
RegAdminCmd("sm_gotocouch", Command_GotoCouch, ADMFLAG_CHANGEMAP, "Teleports you to the couch");
RegAdminCmd("sm_removecouch", Command_RemoveCouch, ADMFLAG_CHANGEMAP, "Removes the couch");
RegAdminCmd("sm_couchbeacon", Command_Beacon, ADMFLAG_CHANGEMAP, "sm_couchbeacon [0|1] - beacon rings on the couch; no argument toggles");
RegAdminCmd("sm_couchpistolonly", Command_PistolOnly, ADMFLAG_CHANGEMAP, "sm_couchpistolonly [0|1] - only pistols and knives move the couch; no argument toggles");
RegAdminCmd("sm_couchlog", Command_Log, ADMFLAG_CHANGEMAP, "sm_couchlog [0|1] - show who moves the couch and how in your console; no argument toggles");
RegAdminCmd("sm_couchhp", Command_Hp, ADMFLAG_CHANGEMAP, "sm_couchhp [hp] - sets the couch's HP; no argument shows it");
RegAdminCmd("sm_couchmaxhp", Command_MaxHp, ADMFLAG_CHANGEMAP, "sm_couchmaxhp <hp> - sets the couch's maximum HP");
// Late load: admins are already in, so give them their default.
for (int client = 1; client <= MaxClients; client++)
{
if (IsClientInGame(client) && !IsFakeClient(client))
g_bLogging[client] = IsLoggingAdmin(client);
}
}
// Admin access is only known once the admin check has run, so the default is set here rather
// than on connect.
public void OnClientPostAdminCheck(int client)
{
if (!IsFakeClient(client))
g_bLogging[client] = IsLoggingAdmin(client);
}
// Follows the command's access, so an admin override on sm_couchlog changes who gets the default.
bool IsLoggingAdmin(int client)
{
return CheckCommandAccess(client, "sm_couchlog", ADMFLAG_CHANGEMAP);
}
// Game time restarts with the map.
@@ -123,6 +146,19 @@ public Action Command_RemoveCouch(int client, int args)
return Plugin_Handled;
}
public Action Command_Beacon(int client, int args)
{
if (!RequireCore(client))
return Plugin_Handled;
bool bEnable = !Couch_IsBeaconEnabled();
if (args >= 1)
bEnable = (GetCmdArgInt(1) != 0);
DoSetBeacon(client, bEnable);
return Plugin_Handled;
}
public Action Command_PistolOnly(int client, int args)
{
if (!RequireCore(client))
@@ -136,6 +172,52 @@ public Action Command_PistolOnly(int client, int args)
return Plugin_Handled;
}
public Action Command_Hp(int client, int args)
{
if (!RequireCore(client))
return Plugin_Handled;
if (Couch_GetCouch() == -1)
{
ReplyToCommand(client, PREFIX_PLAIN ... " There is no couch. Use sm_spawncouch first.");
return Plugin_Handled;
}
if (args < 1)
{
char sHp[16], sMax[16];
FormatThousands(Couch_GetHp(), sHp, sizeof(sHp));
FormatThousands(Couch_GetMaxHp(), sMax, sizeof(sMax));
ReplyToCommand(client, PREFIX_PLAIN ... " Couch HP: %s/%s (couch HP is %s).", sHp, sMax, Couch_IsHpEnabled() ? "on" : "off");
return Plugin_Handled;
}
DoSetHp(client, GetCmdArgInt(1));
return Plugin_Handled;
}
public Action Command_MaxHp(int client, int args)
{
if (!RequireCore(client))
return Plugin_Handled;
if (args < 1)
{
ReplyToCommand(client, PREFIX_PLAIN ... " Usage: sm_couchmaxhp <hp>");
return Plugin_Handled;
}
int hp = GetCmdArgInt(1);
if (hp < 1)
{
ReplyToCommand(client, PREFIX_PLAIN ... " The maximum HP must be at least 1.");
return Plugin_Handled;
}
DoSetMaxHp(client, hp);
return Plugin_Handled;
}
public Action Command_Log(int client, int args)
{
if (!RequireInGame(client))
@@ -154,7 +236,7 @@ bool RequireInGame(int client)
if (client >= 1 && IsClientInGame(client))
return true;
ReplyToCommand(client, "[SM] This command can only be used in-game.");
ReplyToCommand(client, PREFIX_PLAIN ... " This command can only be used in-game.");
return false;
}
@@ -163,7 +245,7 @@ bool RequireCore(int client)
if (g_bCoreLoaded)
return true;
ReplyToCommand(client, "[SM] The Couch core plugin is not loaded.");
ReplyToCommand(client, PREFIX_PLAIN ... " The Couch core plugin is not loaded.");
return false;
}
@@ -175,7 +257,7 @@ void DoSpawn(int client)
{
if (Couch_Spawn(client) == -1)
{
ReplyToCommand(client, "[SM] Could not create the couch.");
ReplyToCommand(client, PREFIX_PLAIN ... " Could not create the couch.");
return;
}
@@ -187,7 +269,7 @@ void DoBring(int client)
{
if (!Couch_Bring(client))
{
ReplyToCommand(client, "[SM] There is no couch to bring. Use sm_spawncouch first.");
ReplyToCommand(client, PREFIX_PLAIN ... " There is no couch to bring. Use sm_spawncouch first.");
return;
}
@@ -199,13 +281,13 @@ void DoGoto(int client)
{
if (!IsPlayerAlive(client))
{
ReplyToCommand(client, "[SM] You must be alive to go to the couch.");
ReplyToCommand(client, PREFIX_PLAIN ... " You must be alive to go to the couch.");
return;
}
if (!Couch_Goto(client))
{
ReplyToCommand(client, "[SM] There is no couch to go to. Use sm_spawncouch first.");
ReplyToCommand(client, PREFIX_PLAIN ... " There is no couch to go to. Use sm_spawncouch first.");
return;
}
@@ -217,7 +299,7 @@ void DoRemove(int client)
{
if (!Couch_Remove())
{
ReplyToCommand(client, "[SM] There is no couch to remove.");
ReplyToCommand(client, PREFIX_PLAIN ... " There is no couch to remove.");
return;
}
@@ -234,6 +316,14 @@ void DoToggleEffect(int client)
LogAction(client, -1, "\"%L\" turned the couch effect %s.", client, bEnable ? "on" : "off");
}
void DoSetBeacon(int client, bool bEnable)
{
Couch_SetBeaconEnabled(bEnable);
ShowActivity2(client, PREFIX, "\x01Turned the couch beacon \x04%s\x01.", bEnable ? "on" : "off");
LogAction(client, -1, "\"%L\" turned the couch beacon %s.", client, bEnable ? "on" : "off");
}
// Announced to everyone so players know why their rifle stopped moving the couch.
void DoSetPistolOnly(int client, bool bEnable)
{
@@ -257,6 +347,45 @@ void DoToggleUse(int client)
LogAction(client, -1, "\"%L\" %s couch use.", client, bAllow ? "allowed" : "disallowed");
}
void DoSetHpEnabled(int client, bool bEnable)
{
Couch_SetHpEnabled(bEnable);
if (bEnable)
ShowActivity2(client, PREFIX, "\x01Turned couch HP \x04on\x01, zombies can break the couch now.");
else
ShowActivity2(client, PREFIX, "\x01Turned couch HP \x04off\x01.");
LogAction(client, -1, "\"%L\" turned couch HP %s.", client, bEnable ? "on" : "off");
}
// Read back afterwards so the announcement shows the clamped value. 0 breaks the couch.
void DoSetHp(int client, int hp)
{
Couch_SetHp(hp);
hp = Couch_GetHp();
char sHp[16], sMax[16];
FormatThousands(hp, sHp, sizeof(sHp));
FormatThousands(Couch_GetMaxHp(), sMax, sizeof(sMax));
ShowActivity2(client, PREFIX, "\x01Set the couch's \x04HP\x01 to \x04%s/%s\x01.", sHp, sMax);
LogAction(client, -1, "\"%L\" set the couch's HP to %d.", client, hp);
}
void DoSetMaxHp(int client, int hp)
{
Couch_SetMaxHp(hp);
hp = Couch_GetMaxHp();
char sHp[16], sMax[16];
FormatThousands(Couch_GetHp(), sHp, sizeof(sHp));
FormatThousands(hp, sMax, sizeof(sMax));
ShowActivity2(client, PREFIX, "\x01Set the couch's \x04maximum HP\x01 to \x04%s\x01, it is now at \x04%s/%s\x01.", sMax, sHp, sMax);
LogAction(client, -1, "\"%L\" set the couch's maximum HP to %d.", client, hp);
}
// The tier cvars belong to the core plugin, so they are looked up by name. sName is "move" or "button".
void DoChangeTier(int client, const char[] sName, int iDelta)
{
@@ -308,7 +437,8 @@ void DoSetPushScale(int client, float fScale)
LogAction(client, -1, "\"%L\" set the couch push scale to %.2f.", client, fScale);
}
// Only affects what this admin sees, so it is neither announced nor logged.
// Only affects what this admin sees, so it is neither announced nor logged. The choice lasts
// until they disconnect; on their next connect the log is on again.
void DoSetLogging(int client, bool bEnable)
{
g_bLogging[client] = bEnable;
@@ -316,7 +446,7 @@ void DoSetLogging(int client, bool bEnable)
if (bEnable)
PrintToChat(client, PREFIX ... "\x01Couch log \x04on\x01, see your console for who moves the couch and how.");
else
PrintToChat(client, PREFIX ... "\x01Couch log \x04off\x01.");
PrintToChat(client, PREFIX ... "\x01Couch log \x04off\x01 until you reconnect.");
}
// ---------------------------------------------------------------------------------------------
@@ -328,7 +458,7 @@ public void Couch_OnCouchInteraction(int couch, int client, CouchMethod method,
// Grenades are rare and knock the couch far, so everyone is told. One blast is one report, so no
// rate limit is needed. The userid is there so an admin can put it straight into sm_couchban.
if (method == CouchMethod_Grenade)
PrintToChatAll("\x01[Couch] \x04%N\x01 (#%d) hit the couch with a \x04grenade", client, GetClientUserId(client));
PrintToChatAll(PREFIX ... "%N\x01 (#%d) hit the couch with a \x04grenade", client, GetClientUserId(client));
if (!AnyoneLogging())
return;
@@ -366,7 +496,7 @@ public void Couch_OnCouchInteraction(int couch, int client, CouchMethod method,
if (!g_bLogging[i] || !IsClientInGame(i))
continue;
PrintToConsole(i, "[Couch] %N (#%d) %s", client, userid, sHow);
PrintToConsole(i, PREFIX_PLAIN ... " %N (#%d) %s", client, userid, sHow);
}
}
@@ -407,18 +537,19 @@ void ShowCouchMenu(int client)
menu.AddItem("remove", "Remove couch", ITEMDRAW_DISABLED);
char sItem[64];
FormatEx(sItem, sizeof(sItem), "Effect: %s", Couch_IsEffectEnabled() ? "ON" : "OFF");
menu.AddItem("effect", sItem);
FormatEx(sItem, sizeof(sItem), "Pistol only: %s", Couch_IsPistolOnly() ? "ON" : "OFF");
menu.AddItem("pistol", sItem);
FormatEx(sItem, sizeof(sItem), "Allow USE: %s", Couch_IsUseAllowed() ? "ON" : "OFF");
menu.AddItem("use", sItem);
FormatEx(sItem, sizeof(sItem), "Couch HP: %s", Couch_IsHpEnabled() ? "ON" : "OFF");
menu.AddItem("hp", sItem);
FormatEx(sItem, sizeof(sItem), "Couch logs (for me): %s", g_bLogging[client] ? "ON" : "OFF");
menu.AddItem("log", sItem);
menu.AddItem("effects", "Effects");
menu.AddItem("tiers", "Tier requirements");
menu.AddItem("pushscale", "Push scale");
@@ -451,14 +582,19 @@ public int MenuHandler_Couch(Menu menu, MenuAction action, int param1, int param
DoGoto(client);
else if (StrEqual(sInfo, "remove"))
DoRemove(client);
else if (StrEqual(sInfo, "effect"))
DoToggleEffect(client);
else if (StrEqual(sInfo, "pistol"))
DoSetPistolOnly(client, !Couch_IsPistolOnly());
else if (StrEqual(sInfo, "use"))
DoToggleUse(client);
else if (StrEqual(sInfo, "hp"))
DoSetHpEnabled(client, !Couch_IsHpEnabled());
else if (StrEqual(sInfo, "log"))
DoSetLogging(client, !g_bLogging[client]);
else if (StrEqual(sInfo, "effects"))
{
ShowEffectsMenu(client);
return 0;
}
else if (StrEqual(sInfo, "tiers"))
{
ShowTierMenu(client);
@@ -482,6 +618,69 @@ public int MenuHandler_Couch(Menu menu, MenuAction action, int param1, int param
return 0;
}
// Rebuilt on every display so the labels always reflect the current state.
void ShowEffectsMenu(int client)
{
Menu menu = new Menu(MenuHandler_Effects);
menu.SetTitle("Couch - Effects");
char sItem[64];
FormatEx(sItem, sizeof(sItem), "Effect: %s", Couch_IsEffectEnabled() ? "ON" : "OFF");
menu.AddItem("effect", sItem);
FormatEx(sItem, sizeof(sItem), "Beacon: %s", Couch_IsBeaconEnabled() ? "ON" : "OFF");
menu.AddItem("beacon", sItem);
menu.ExitBackButton = true;
menu.ExitButton = true;
menu.Display(client, MENU_TIME_FOREVER);
}
public int MenuHandler_Effects(Menu menu, MenuAction action, int param1, int param2)
{
switch (action)
{
case MenuAction_Select:
{
int client = param1;
if (!g_bCoreLoaded)
{
RequireCore(client);
return 0;
}
char sInfo[16];
menu.GetItem(param2, sInfo, sizeof(sInfo));
if (StrEqual(sInfo, "effect"))
DoToggleEffect(client);
else if (StrEqual(sInfo, "beacon"))
DoSetBeacon(client, !Couch_IsBeaconEnabled());
ShowEffectsMenu(client);
}
case MenuAction_Cancel:
{
if (param2 == MenuCancel_ExitBack)
{
if (g_bCoreLoaded)
ShowCouchMenu(param1);
else
RequireCore(param1);
}
}
case MenuAction_End:
{
delete menu;
}
}
return 0;
}
void ShowTierMenu(int client)
{
Menu menu = new Menu(MenuHandler_Tier);