525 lines
14 KiB
SourcePawn
525 lines
14 KiB
SourcePawn
#include <sourcemod>
|
|
#include <Couch>
|
|
|
|
#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
|
|
|
|
// 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.
|
|
bool g_bLogging[MAXPLAYERS + 1];
|
|
|
|
// Per player: what was last reported, and when the same thing may be reported again.
|
|
CouchMethod g_iLastMethod[MAXPLAYERS + 1];
|
|
char g_sLastWeapon[MAXPLAYERS + 1][64];
|
|
float g_fNextReport[MAXPLAYERS + 1];
|
|
|
|
public Plugin myinfo =
|
|
{
|
|
name = "Couch Admin",
|
|
author = "marisakirisame.net",
|
|
description = "Admin commands and menu for the ZE couch",
|
|
version = "2.0",
|
|
url = "https://marisakirisame.net"
|
|
};
|
|
|
|
public void OnPluginStart()
|
|
{
|
|
RegAdminCmd("sm_couch", Command_Couch, ADMFLAG_CHANGEMAP, "Opens the couch menu");
|
|
RegAdminCmd("sm_spawncouch", Command_SpawnCouch, ADMFLAG_CHANGEMAP, "Spawns the couch where you are aiming");
|
|
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_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, grenades in chat too; no argument toggles");
|
|
}
|
|
|
|
// Game time restarts with the map.
|
|
public void OnMapStart()
|
|
{
|
|
for (int i = 1; i <= MAXPLAYERS; i++)
|
|
g_fNextReport[i] = 0.0;
|
|
}
|
|
|
|
public void OnClientDisconnect(int client)
|
|
{
|
|
g_bLogging[client] = false;
|
|
g_fNextReport[client] = 0.0;
|
|
}
|
|
|
|
public void OnAllPluginsLoaded()
|
|
{
|
|
g_bCoreLoaded = LibraryExists("Couch");
|
|
}
|
|
|
|
public void OnLibraryAdded(const char[] name)
|
|
{
|
|
if (StrEqual(name, "Couch"))
|
|
g_bCoreLoaded = true;
|
|
}
|
|
|
|
public void OnLibraryRemoved(const char[] name)
|
|
{
|
|
if (StrEqual(name, "Couch"))
|
|
g_bCoreLoaded = false;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------------
|
|
// Commands
|
|
// ---------------------------------------------------------------------------------------------
|
|
|
|
public Action Command_Couch(int client, int args)
|
|
{
|
|
if (!RequireInGame(client) || !RequireCore(client))
|
|
return Plugin_Handled;
|
|
|
|
ShowCouchMenu(client);
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
public Action Command_SpawnCouch(int client, int args)
|
|
{
|
|
if (!RequireInGame(client) || !RequireCore(client))
|
|
return Plugin_Handled;
|
|
|
|
DoSpawn(client);
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
public Action Command_BringCouch(int client, int args)
|
|
{
|
|
if (!RequireInGame(client) || !RequireCore(client))
|
|
return Plugin_Handled;
|
|
|
|
DoBring(client);
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
public Action Command_GotoCouch(int client, int args)
|
|
{
|
|
if (!RequireInGame(client) || !RequireCore(client))
|
|
return Plugin_Handled;
|
|
|
|
DoGoto(client);
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
// Needs no position, so it also works from the server console.
|
|
public Action Command_RemoveCouch(int client, int args)
|
|
{
|
|
if (!RequireCore(client))
|
|
return Plugin_Handled;
|
|
|
|
DoRemove(client);
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
public Action Command_PistolOnly(int client, int args)
|
|
{
|
|
if (!RequireCore(client))
|
|
return Plugin_Handled;
|
|
|
|
bool bEnable = !Couch_IsPistolOnly();
|
|
if (args >= 1)
|
|
bEnable = (GetCmdArgInt(1) != 0);
|
|
|
|
DoSetPistolOnly(client, bEnable);
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
public Action Command_Log(int client, int args)
|
|
{
|
|
if (!RequireInGame(client))
|
|
return Plugin_Handled;
|
|
|
|
bool bEnable = !g_bLogging[client];
|
|
if (args >= 1)
|
|
bEnable = (GetCmdArgInt(1) != 0);
|
|
|
|
DoSetLogging(client, bEnable);
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
bool RequireInGame(int client)
|
|
{
|
|
if (client >= 1 && IsClientInGame(client))
|
|
return true;
|
|
|
|
ReplyToCommand(client, "[SM] This command can only be used in-game.");
|
|
return false;
|
|
}
|
|
|
|
bool RequireCore(int client)
|
|
{
|
|
if (g_bCoreLoaded)
|
|
return true;
|
|
|
|
ReplyToCommand(client, "[SM] The Couch core plugin is not loaded.");
|
|
return false;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------------
|
|
// Actions
|
|
// ---------------------------------------------------------------------------------------------
|
|
|
|
void DoSpawn(int client)
|
|
{
|
|
if (Couch_Spawn(client) == -1)
|
|
{
|
|
ReplyToCommand(client, "[SM] Could not create the couch.");
|
|
return;
|
|
}
|
|
|
|
ShowActivity2(client, PREFIX, "\x01Spawned the \x04couch\x01, it will respawn here each round until removed.");
|
|
LogAction(client, -1, "\"%L\" spawned the couch.", client);
|
|
}
|
|
|
|
void DoBring(int client)
|
|
{
|
|
if (!Couch_Bring(client))
|
|
{
|
|
ReplyToCommand(client, "[SM] There is no couch to bring. Use sm_spawncouch first.");
|
|
return;
|
|
}
|
|
|
|
ShowActivity2(client, PREFIX, "\x01Brought the \x04couch\x01.");
|
|
LogAction(client, -1, "\"%L\" brought the couch.", client);
|
|
}
|
|
|
|
void DoGoto(int client)
|
|
{
|
|
if (!IsPlayerAlive(client))
|
|
{
|
|
ReplyToCommand(client, "[SM] 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.");
|
|
return;
|
|
}
|
|
|
|
ShowActivity2(client, PREFIX, "\x01Teleported to the \x04couch\x01.");
|
|
LogAction(client, -1, "\"%L\" teleported to the couch.", client);
|
|
}
|
|
|
|
void DoRemove(int client)
|
|
{
|
|
if (!Couch_Remove())
|
|
{
|
|
ReplyToCommand(client, "[SM] There is no couch to remove.");
|
|
return;
|
|
}
|
|
|
|
ShowActivity2(client, PREFIX, "\x01Removed the \x04couch\x01.");
|
|
LogAction(client, -1, "\"%L\" removed the couch.", client);
|
|
}
|
|
|
|
void DoToggleEffect(int client)
|
|
{
|
|
bool bEnable = !Couch_IsEffectEnabled();
|
|
Couch_SetEffectEnabled(bEnable);
|
|
|
|
ShowActivity2(client, PREFIX, "\x01Turned the couch effect \x04%s\x01.", bEnable ? "on" : "off");
|
|
LogAction(client, -1, "\"%L\" turned the couch effect %s.", client, bEnable ? "on" : "off");
|
|
}
|
|
|
|
// Announced to everyone so players know why their rifle stopped moving the couch.
|
|
void DoSetPistolOnly(int client, bool bEnable)
|
|
{
|
|
Couch_SetPistolOnly(bEnable);
|
|
|
|
ShowActivity2(client, PREFIX, "\x01Turned couch pistol-only mode \x04%s\x01.", bEnable ? "on" : "off");
|
|
LogAction(client, -1, "\"%L\" turned couch pistol-only mode %s.", client, bEnable ? "on" : "off");
|
|
}
|
|
|
|
// Announced to everyone so players know to shoot the couch instead.
|
|
void DoToggleUse(int client)
|
|
{
|
|
bool bAllow = !Couch_IsUseAllowed();
|
|
Couch_SetUseAllowed(bAllow);
|
|
|
|
if (bAllow)
|
|
ShowActivity2(client, PREFIX, "\x01Allowed pushing the \x04couch\x01 with USE.");
|
|
else
|
|
ShowActivity2(client, PREFIX, "\x01Disabled pushing the \x04couch\x01 with USE, it must be shot to move.");
|
|
|
|
LogAction(client, -1, "\"%L\" %s couch use.", client, bAllow ? "allowed" : "disallowed");
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
ConVar cvar = FindTierConVar(sName);
|
|
if (cvar == null)
|
|
return;
|
|
|
|
int iTier = cvar.IntValue + iDelta;
|
|
if (iTier < 0)
|
|
iTier = 0;
|
|
|
|
cvar.IntValue = iTier;
|
|
|
|
if (iTier == 0)
|
|
{
|
|
ShowActivity2(client, PREFIX, "\x01Turned off the couch \x04%s\x01 tier requirement.", sName);
|
|
LogAction(client, -1, "\"%L\" turned off the couch %s tier requirement.", client, sName);
|
|
}
|
|
else
|
|
{
|
|
ShowActivity2(client, PREFIX, "\x01Set the couch \x04%s\x01 tier requirement to \x04%d\x01.", sName, iTier);
|
|
LogAction(client, -1, "\"%L\" set the couch %s tier requirement to %d.", client, sName, iTier);
|
|
}
|
|
}
|
|
|
|
ConVar FindTierConVar(const char[] sName)
|
|
{
|
|
char sCvar[32];
|
|
FormatEx(sCvar, sizeof(sCvar), "sm_couch_%s_tier", sName);
|
|
return FindConVar(sCvar);
|
|
}
|
|
|
|
// Only affects what this admin sees, so it is neither announced nor logged.
|
|
void DoSetLogging(int client, bool bEnable)
|
|
{
|
|
g_bLogging[client] = bEnable;
|
|
|
|
if (bEnable)
|
|
PrintToChat(client, PREFIX ... "\x01Couch log \x04on\x01, see your console for who moves the couch and how. Grenades also show here.");
|
|
else
|
|
PrintToChat(client, PREFIX ... "\x01Couch log \x04off\x01.");
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------------
|
|
// Couch log
|
|
// ---------------------------------------------------------------------------------------------
|
|
|
|
public void Couch_OnCouchInteraction(int couch, int client, CouchMethod method, const char[] weapon)
|
|
{
|
|
if (!AnyoneLogging())
|
|
return;
|
|
|
|
// Held fire and shotgun pellets would flood the console; a change of weapon or method is always shown.
|
|
float fNow = GetGameTime();
|
|
if (fNow < g_fNextReport[client] && method == g_iLastMethod[client] && StrEqual(weapon, g_sLastWeapon[client]))
|
|
return;
|
|
|
|
g_iLastMethod[client] = method;
|
|
strcopy(g_sLastWeapon[client], sizeof(g_sLastWeapon[]), weapon);
|
|
g_fNextReport[client] = fNow + LOG_REPEAT_INTERVAL;
|
|
|
|
// Plain text: the console doesn't render chat colour codes.
|
|
char sHow[96];
|
|
switch (method)
|
|
{
|
|
case CouchMethod_Bullet:
|
|
{
|
|
if (weapon[0] != '\0')
|
|
FormatEx(sHow, sizeof(sHow), "shot the couch with %s", weapon);
|
|
else
|
|
strcopy(sHow, sizeof(sHow), "shot the couch");
|
|
}
|
|
case CouchMethod_Knife: strcopy(sHow, sizeof(sHow), "knifed the couch");
|
|
case CouchMethod_Grenade: strcopy(sHow, sizeof(sHow), "hit the couch with a grenade");
|
|
case CouchMethod_Use: strcopy(sHow, sizeof(sHow), "pushed the couch with USE");
|
|
default: FormatEx(sHow, sizeof(sHow), "moved the couch with %s", weapon[0] != '\0' ? weapon : "something");
|
|
}
|
|
|
|
// Everything goes to the console; grenades are rare and knock the couch far, so they go to chat too.
|
|
// The userid is there so it can go straight into sm_couchban.
|
|
int userid = GetClientUserId(client);
|
|
for (int i = 1; i <= MaxClients; i++)
|
|
{
|
|
if (!g_bLogging[i] || !IsClientInGame(i))
|
|
continue;
|
|
|
|
PrintToConsole(i, "[Couch] %N (#%d) %s", client, userid, sHow);
|
|
|
|
if (method == CouchMethod_Grenade)
|
|
PrintToChat(i, "\x01[Couch] \x04%N\x01 (#%d) hit the couch with a \x04grenade", client, userid);
|
|
}
|
|
}
|
|
|
|
bool AnyoneLogging()
|
|
{
|
|
for (int i = 1; i <= MaxClients; i++)
|
|
{
|
|
if (g_bLogging[i])
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------------
|
|
// Menu
|
|
// ---------------------------------------------------------------------------------------------
|
|
|
|
// Rebuilt on every display so the labels always reflect the current state.
|
|
void ShowCouchMenu(int client)
|
|
{
|
|
bool bHasCouch = (Couch_GetCouch() != -1);
|
|
bool bPlaced = Couch_IsPlaced();
|
|
|
|
Menu menu = new Menu(MenuHandler_Couch);
|
|
menu.SetTitle("Couch");
|
|
|
|
// With a couch out, bring is the way to move it; without one there is nothing to bring.
|
|
menu.AddItem("spawn", "Spawn couch at crosshair", bHasCouch ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT);
|
|
menu.AddItem("bring", "Bring couch to crosshair", bHasCouch ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED);
|
|
menu.AddItem("goto", "Teleport to couch", bHasCouch ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED);
|
|
|
|
if (bHasCouch)
|
|
menu.AddItem("remove", "Remove couch");
|
|
else if (bPlaced)
|
|
menu.AddItem("remove", "Remove couch (respawns next round)");
|
|
else
|
|
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), "Log to my console: %s", g_bLogging[client] ? "ON" : "OFF");
|
|
menu.AddItem("log", sItem);
|
|
|
|
menu.AddItem("tiers", "Tier requirements");
|
|
|
|
menu.ExitButton = true;
|
|
menu.Display(client, MENU_TIME_FOREVER);
|
|
}
|
|
|
|
public int MenuHandler_Couch(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, "spawn"))
|
|
DoSpawn(client);
|
|
else if (StrEqual(sInfo, "bring"))
|
|
DoBring(client);
|
|
else if (StrEqual(sInfo, "goto"))
|
|
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, "log"))
|
|
DoSetLogging(client, !g_bLogging[client]);
|
|
else if (StrEqual(sInfo, "tiers"))
|
|
{
|
|
ShowTierMenu(client);
|
|
return 0;
|
|
}
|
|
|
|
ShowCouchMenu(client);
|
|
}
|
|
|
|
case MenuAction_End:
|
|
{
|
|
delete menu;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void ShowTierMenu(int client)
|
|
{
|
|
Menu menu = new Menu(MenuHandler_Tier);
|
|
menu.SetTitle("Couch - Tier requirements");
|
|
|
|
AddTierItems(menu, "move", "Move");
|
|
AddTierItems(menu, "button", "Button");
|
|
|
|
menu.ExitBackButton = true;
|
|
menu.ExitButton = true;
|
|
menu.Display(client, MENU_TIME_FOREVER);
|
|
}
|
|
|
|
// Adds "<label> tier: <value>", increase and decrease. Info strings are "+move", "-move" and so on.
|
|
void AddTierItems(Menu menu, const char[] sName, const char[] sLabel)
|
|
{
|
|
ConVar cvar = FindTierConVar(sName);
|
|
int iTier = (cvar == null) ? 0 : cvar.IntValue;
|
|
|
|
char sItem[64], sInfo[16];
|
|
if (iTier > 0)
|
|
FormatEx(sItem, sizeof(sItem), "%s tier: %d", sLabel, iTier);
|
|
else
|
|
FormatEx(sItem, sizeof(sItem), "%s tier: OFF", sLabel);
|
|
menu.AddItem("", sItem, ITEMDRAW_DISABLED);
|
|
|
|
FormatEx(sInfo, sizeof(sInfo), "+%s", sName);
|
|
FormatEx(sItem, sizeof(sItem), "Increase %s tier", sName);
|
|
menu.AddItem(sInfo, sItem, (cvar == null) ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT);
|
|
|
|
FormatEx(sInfo, sizeof(sInfo), "-%s", sName);
|
|
FormatEx(sItem, sizeof(sItem), "Decrease %s tier", sName);
|
|
menu.AddItem(sInfo, sItem, (iTier > 0) ? ITEMDRAW_DEFAULT : ITEMDRAW_DISABLED);
|
|
}
|
|
|
|
public int MenuHandler_Tier(Menu menu, MenuAction action, int param1, int param2)
|
|
{
|
|
switch (action)
|
|
{
|
|
case MenuAction_Select:
|
|
{
|
|
int client = param1;
|
|
|
|
char sInfo[16];
|
|
menu.GetItem(param2, sInfo, sizeof(sInfo));
|
|
|
|
DoChangeTier(client, sInfo[1], (sInfo[0] == '+') ? 1 : -1);
|
|
ShowTierMenu(client);
|
|
}
|
|
|
|
case MenuAction_Cancel:
|
|
{
|
|
if (param2 == MenuCancel_ExitBack)
|
|
{
|
|
if (g_bCoreLoaded)
|
|
ShowCouchMenu(param1);
|
|
else
|
|
RequireCore(param1);
|
|
}
|
|
}
|
|
|
|
case MenuAction_End:
|
|
{
|
|
delete menu;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|