#pragma semicolon 1 #include #include #include #include #include #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); }