417 lines
11 KiB
SourcePawn
417 lines
11 KiB
SourcePawn
#pragma semicolon 1
|
|
#include <sourcemod>
|
|
#include <clientprefs>
|
|
#include <sdktools>
|
|
#include <sdkhooks>
|
|
#include <unloze_playtime>
|
|
|
|
#pragma newdecls required
|
|
|
|
#define MAX_SKINS 64
|
|
|
|
enum struct GrenadeSkin
|
|
{
|
|
char ModelPath[128];
|
|
char Name[128];
|
|
int VIP;
|
|
int Tier;
|
|
float Scale;
|
|
int Skin;
|
|
}
|
|
|
|
GrenadeSkin g_aGrenadeSkins[MAX_SKINS];
|
|
int g_iSkinCount = 0;
|
|
|
|
int g_iGrenadeSkin[MAXPLAYERS + 1] = { -1, ... };
|
|
bool g_bAdminChecked[MAXPLAYERS + 1];
|
|
|
|
Handle g_hCookie_GrenadeSkin;
|
|
|
|
public Plugin myinfo =
|
|
{
|
|
name = "Grenade Skins",
|
|
description = "grenade skins but source code actually provided",
|
|
author = "redone by claude ai mostly, slight human adjustments",
|
|
version = "1.0",
|
|
url = ""
|
|
};
|
|
|
|
public void OnPluginStart()
|
|
{
|
|
RegConsoleCmd("sm_grenadeskin", Cmd_GrenadeSkinSettings);
|
|
RegConsoleCmd("sm_grenadeskins", Cmd_GrenadeSkinSettings);
|
|
|
|
g_hCookie_GrenadeSkin = RegClientCookie("grenade_skin", "Selected grenade skin", CookieAccess_Protected);
|
|
|
|
SetCookieMenuItem(MenuHandler_CookieMenu, 0, "Grenade Skin");
|
|
|
|
for (int i = 1; i <= MaxClients; i++)
|
|
{
|
|
if (IsValidClient(i, true) && AreClientCookiesCached(i))
|
|
{
|
|
OnClientCookiesCached(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void OnMapStart()
|
|
{
|
|
LoadDownloads();
|
|
LoadSkinsConfig();
|
|
}
|
|
|
|
void LoadDownloads()
|
|
{
|
|
char sDownloadsFile[PLATFORM_MAX_PATH];
|
|
BuildPath(Path_SM, sDownloadsFile, sizeof(sDownloadsFile), "configs/grenade_skins/downloads.txt");
|
|
|
|
File hFile = OpenFile(sDownloadsFile, "r");
|
|
if (!hFile)
|
|
{
|
|
SetFailState("Could not open file: configs/grenade_skins/downloads.txt");
|
|
}
|
|
|
|
char sLine[1024];
|
|
while (hFile.ReadLine(sLine, sizeof(sLine)))
|
|
{
|
|
TrimString(sLine);
|
|
|
|
// Skip empty lines and comment lines (lines starting with "//" with no file extension).
|
|
if (sLine[0] == '\0')
|
|
continue;
|
|
|
|
if (StrContains(sLine, ".", false) == -1)
|
|
continue;
|
|
|
|
if (FileExists(sLine, false, "GAME"))
|
|
{
|
|
AddFileToDownloadsTable(sLine);
|
|
}
|
|
}
|
|
|
|
delete hFile;
|
|
}
|
|
|
|
void LoadSkinsConfig()
|
|
{
|
|
// Clear existing skins.
|
|
for (int i = 0; i < MAX_SKINS; i++)
|
|
{
|
|
g_aGrenadeSkins[i].ModelPath[0] = '\0';
|
|
g_aGrenadeSkins[i].Name[0] = '\0';
|
|
g_aGrenadeSkins[i].VIP = 0;
|
|
g_aGrenadeSkins[i].Tier = 0;
|
|
g_aGrenadeSkins[i].Scale = 1.0;
|
|
g_aGrenadeSkins[i].Skin = 0;
|
|
}
|
|
g_iSkinCount = 0;
|
|
|
|
char sConfigFile[PLATFORM_MAX_PATH];
|
|
BuildPath(Path_SM, sConfigFile, sizeof(sConfigFile), "configs/grenade_skins/skins.cfg");
|
|
|
|
if (!FileExists(sConfigFile, false, "GAME"))
|
|
{
|
|
SetFailState("Could not find: \"%s\"", sConfigFile);
|
|
}
|
|
|
|
KeyValues kv = new KeyValues("Skins");
|
|
if (!kv.ImportFromFile(sConfigFile))
|
|
{
|
|
delete kv;
|
|
SetFailState("ImportFromFile() failed!");
|
|
}
|
|
|
|
if (!kv.GotoFirstSubKey(true))
|
|
{
|
|
delete kv;
|
|
SetFailState("Unable to goto first sub key in: \"%s\"", sConfigFile);
|
|
}
|
|
|
|
int i = 0;
|
|
do
|
|
{
|
|
if (i >= MAX_SKINS)
|
|
{
|
|
LogError("grenade_skins: skins.cfg has more than %d entries, ignoring the rest.", MAX_SKINS);
|
|
break;
|
|
}
|
|
|
|
kv.GetString("ModelPath", g_aGrenadeSkins[i].ModelPath, sizeof(g_aGrenadeSkins[].ModelPath), "error");
|
|
if (StrEqual(g_aGrenadeSkins[i].ModelPath, "error", true))
|
|
{
|
|
SetFailState("Unable to read Path");
|
|
}
|
|
|
|
kv.GetString("Name", g_aGrenadeSkins[i].Name, sizeof(g_aGrenadeSkins[].Name), "error");
|
|
if (StrEqual(g_aGrenadeSkins[i].Name, "error", true))
|
|
{
|
|
SetFailState("Unable to read Name");
|
|
}
|
|
|
|
g_aGrenadeSkins[i].VIP = kv.GetNum("VIP", 0);
|
|
g_aGrenadeSkins[i].Tier = kv.GetNum("tier", 0);
|
|
g_aGrenadeSkins[i].Scale = kv.GetFloat("Scale", 1.0);
|
|
g_aGrenadeSkins[i].Skin = kv.GetNum("Skin", 0);
|
|
|
|
PrecacheModel(g_aGrenadeSkins[i].ModelPath, false);
|
|
|
|
i++;
|
|
}
|
|
while (kv.GotoNextKey(true));
|
|
|
|
g_iSkinCount = i;
|
|
|
|
delete kv;
|
|
}
|
|
|
|
public void OnClientCookiesCached(int client)
|
|
{
|
|
char sBuffer[256];
|
|
GetClientCookie(client, g_hCookie_GrenadeSkin, sBuffer, sizeof(sBuffer));
|
|
|
|
if (sBuffer[0])
|
|
{
|
|
g_iGrenadeSkin[client] = -1;
|
|
|
|
for (int i = 0; i < g_iSkinCount; i++)
|
|
{
|
|
if (StrEqual(g_aGrenadeSkins[i].ModelPath, sBuffer, true))
|
|
{
|
|
g_iGrenadeSkin[client] = i;
|
|
|
|
if (g_bAdminChecked[client] && !ClientHasSkinAccess(client, i))
|
|
{
|
|
g_iGrenadeSkin[client] = -1;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_iGrenadeSkin[client] = -1;
|
|
}
|
|
}
|
|
|
|
public void OnClientDisconnect(int client)
|
|
{
|
|
g_iGrenadeSkin[client] = -1;
|
|
g_bAdminChecked[client] = false;
|
|
}
|
|
|
|
public void OnClientPostAdminCheck(int client)
|
|
{
|
|
g_bAdminChecked[client] = true;
|
|
|
|
if (AreClientCookiesCached(client) && g_iGrenadeSkin[client] != -1)
|
|
{
|
|
if (!ClientHasSkinAccess(client, g_iGrenadeSkin[client]))
|
|
{
|
|
g_iGrenadeSkin[client] = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks whether a client has access to a given skin index,
|
|
* taking both the legacy VIP flag and the new tier requirement into account.
|
|
*/
|
|
bool ClientHasSkinAccess(int client, int skinIndex)
|
|
{
|
|
if (g_aGrenadeSkins[skinIndex].VIP && !CheckCommandAccess(client, "", ADMFLAG_RESERVATION, false))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (g_aGrenadeSkins[skinIndex].Tier > 0 && GetPlayerTier_native(client) < g_aGrenadeSkins[skinIndex].Tier)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public void OnEntitySpawned(int entity, const char[] sClassname)
|
|
{
|
|
if (StrContains(sClassname, "_projectile", false) == -1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int iOwner = GetEntPropEnt(entity, Prop_Send, "m_hOwnerEntity");
|
|
|
|
if (!IsValidClient(iOwner, true) || g_iGrenadeSkin[iOwner] == -1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int skinIndex = g_iGrenadeSkin[iOwner];
|
|
|
|
SetEntityRenderMode(entity, RENDER_TRANSALPHA);
|
|
|
|
float fNadeOrigin[3];
|
|
GetEntPropVector(entity, Prop_Send, "m_vecOrigin", fNadeOrigin);
|
|
|
|
int iNadeProp = CreateEntityAtOrigin("prop_dynamic_override", fNadeOrigin);
|
|
|
|
DispatchKeyValue(iNadeProp, "disableshadows", "1");
|
|
DispatchKeyValue(iNadeProp, "disablereceiveshadows", "1");
|
|
DispatchKeyValue(iNadeProp, "DisableBoneFollowers", "1");
|
|
DispatchKeyValue(iNadeProp, "solid", "0");
|
|
DispatchKeyValue(iNadeProp, "model", g_aGrenadeSkins[skinIndex].ModelPath);
|
|
|
|
char sScale[32];
|
|
FloatToString(g_aGrenadeSkins[skinIndex].Scale, sScale, sizeof(sScale));
|
|
DispatchKeyValue(iNadeProp, "modelscale", sScale);
|
|
|
|
SpawnAndActivate(iNadeProp);
|
|
ParentToEntity(iNadeProp, entity);
|
|
|
|
if (g_aGrenadeSkins[skinIndex].Skin > 0)
|
|
{
|
|
char sSkinBuf[16];
|
|
IntToString(g_aGrenadeSkins[skinIndex].Skin, sSkinBuf, sizeof(sSkinBuf));
|
|
SetVariantString(sSkinBuf);
|
|
AcceptEntityInput(iNadeProp, "Skin");
|
|
}
|
|
}
|
|
|
|
public Action Cmd_GrenadeSkinSettings(int client, int args)
|
|
{
|
|
ShowSettingsMenu(client);
|
|
return Plugin_Handled;
|
|
}
|
|
|
|
public void MenuHandler_CookieMenu(int client, CookieMenuAction action, any info, char[] buffer, int maxlen)
|
|
{
|
|
switch (action)
|
|
{
|
|
case CookieMenuAction_DisplayOption:
|
|
{
|
|
Format(buffer, maxlen, "Grenade Skin");
|
|
}
|
|
case CookieMenuAction_SelectOption:
|
|
{
|
|
ShowSettingsMenu(client);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ShowSettingsMenu(int client)
|
|
{
|
|
Menu menu = new Menu(MenuHandler_MainMenu);
|
|
menu.SetTitle("Grenade Skin");
|
|
|
|
char sBuffer[128];
|
|
Format(sBuffer, sizeof(sBuffer), "No Skin%s", (g_iGrenadeSkin[client] == -1) ? " (Selected)" : "");
|
|
menu.AddItem("-1", sBuffer, (g_iGrenadeSkin[client] == -1) ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT);
|
|
|
|
for (int i = 0; i < g_iSkinCount; i++)
|
|
{
|
|
bool bSelected = (i == g_iGrenadeSkin[client]);
|
|
bool bHasAccess = ClientHasSkinAccess(client, i);
|
|
bool bUnavailable = bSelected || !bHasAccess;
|
|
|
|
char sSuffix[32];
|
|
sSuffix[0] = '\0';
|
|
|
|
if (!bHasAccess)
|
|
{
|
|
if (g_aGrenadeSkins[i].VIP && !CheckCommandAccess(client, "", ADMFLAG_RESERVATION, false))
|
|
{
|
|
Format(sSuffix, sizeof(sSuffix), " [VIP]");
|
|
}
|
|
else if (g_aGrenadeSkins[i].Tier > 0 && GetPlayerTier_native(client) < g_aGrenadeSkins[i].Tier)
|
|
{
|
|
Format(sSuffix, sizeof(sSuffix), " [Tier %i]", g_aGrenadeSkins[i].Tier);
|
|
}
|
|
}
|
|
else if (bSelected)
|
|
{
|
|
Format(sSuffix, sizeof(sSuffix), " (Selected)");
|
|
}
|
|
|
|
Format(sBuffer, sizeof(sBuffer), "%s%s", g_aGrenadeSkins[i].Name, sSuffix);
|
|
|
|
char sInfo[8];
|
|
IntToString(i, sInfo, sizeof(sInfo));
|
|
|
|
menu.AddItem(sInfo, sBuffer, bUnavailable ? ITEMDRAW_DISABLED : ITEMDRAW_DEFAULT);
|
|
}
|
|
|
|
menu.ExitBackButton = true;
|
|
menu.Display(client, MENU_TIME_FOREVER);
|
|
}
|
|
|
|
public int MenuHandler_MainMenu(Menu menu, MenuAction action, int client, int selection)
|
|
{
|
|
switch (action)
|
|
{
|
|
case MenuAction_Select:
|
|
{
|
|
char sInfo[8];
|
|
menu.GetItem(selection, sInfo, sizeof(sInfo));
|
|
|
|
int chosen = StringToInt(sInfo);
|
|
g_iGrenadeSkin[client] = chosen;
|
|
|
|
if (chosen == -1)
|
|
{
|
|
SetClientCookie(client, g_hCookie_GrenadeSkin, "");
|
|
}
|
|
else
|
|
{
|
|
SetClientCookie(client, g_hCookie_GrenadeSkin, g_aGrenadeSkins[chosen].ModelPath);
|
|
}
|
|
|
|
ShowSettingsMenu(client);
|
|
}
|
|
case MenuAction_Cancel:
|
|
{
|
|
if (selection == MenuCancel_ExitBack)
|
|
{
|
|
// Re-open the cookie/settings menu if desired; original plugin
|
|
// routed back into the cookie menu here.
|
|
}
|
|
}
|
|
case MenuAction_End:
|
|
{
|
|
delete menu;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Helper stocks (ported from decompiled output)
|
|
// ---------------------------------------------------------------------
|
|
|
|
stock bool IsValidClient(int client, bool noBots)
|
|
{
|
|
if (client <= 0 || client > MaxClients || !IsClientConnected(client) || (noBots && IsFakeClient(client)))
|
|
{
|
|
return false;
|
|
}
|
|
return IsClientInGame(client);
|
|
}
|
|
|
|
stock int CreateEntityAtOrigin(const char[] classname, const float origin[3])
|
|
{
|
|
int entity = CreateEntityByName(classname);
|
|
TeleportEntity(entity, origin, NULL_VECTOR, NULL_VECTOR);
|
|
return entity;
|
|
}
|
|
|
|
stock void SpawnAndActivate(int entity)
|
|
{
|
|
DispatchSpawn(entity);
|
|
ActivateEntity(entity);
|
|
}
|
|
|
|
stock void ParentToEntity(int entity, int parent)
|
|
{
|
|
SetVariantString("!activator");
|
|
AcceptEntityInput(entity, "SetParent", parent, parent);
|
|
}
|