/* * CSB Knife Skins * Copyright (C) 2026 counter-strike-boost.com * * A /knife menu of custom knife models. Some entries can be locked behind an * admin/VIP flag. The chosen v_/p_ model is applied on CurWeapon and the choice * is saved per SteamID in nVault, so it survives a reconnect. Each skin can have * its own deploy and hit sounds. * * Inspired by the classic "Knife Models Menu" plugins for AMX Mod X. This is an * independent GPL re-implementation; no original code is reused. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later * version. It is distributed in the hope that it will be useful, but WITHOUT ANY * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE. See for details. */ #include #include #include #include new const PLUGIN[] = "CSB Knife Skins" new const VERSION[] = "1.0.0" new const AUTHOR[] = "counter-strike-boost.com" #define NO_VAULT (-1) /* Skin 0 is the default knife (no custom model). Edit these paths to your own * models; only entries whose files actually exist are precached. */ new const g_szName[][] = { "Default Knife", "Karambit", "Butterfly", "Bayonet (VIP)" } new const g_szViewModel[][] = { "", "models/csb_knives/v_karambit.mdl", "models/csb_knives/v_butterfly.mdl", "models/csb_knives/v_bayonet.mdl" } new const g_szWorldModel[][] = { "", "models/csb_knives/p_karambit.mdl", "models/csb_knives/p_butterfly.mdl", "models/csb_knives/p_bayonet.mdl" } new const g_szDeploySnd[][] = { "", "csb_knives/karambit_deploy.wav", "csb_knives/butterfly_deploy.wav", "csb_knives/bayonet_deploy.wav" } new const g_szHitSnd[][] = { "", "csb_knives/karambit_hit.wav", "csb_knives/butterfly_hit.wav", "csb_knives/bayonet_hit.wav" } /* 0 = free for everyone; otherwise the admin flag required */ new const g_iFlag[] = { 0, 0, 0, ADMIN_LEVEL_H } new bool:g_bViewOk[sizeof(g_szName)] new bool:g_bWorldOk[sizeof(g_szName)] new bool:g_bDeployOk[sizeof(g_szName)] new bool:g_bHitOk[sizeof(g_szName)] new g_iChoice[33] new g_szAuth[33][35] new g_iVault = NO_VAULT new g_pEnabled public plugin_init() { register_plugin(PLUGIN, VERSION, AUTHOR) g_pEnabled = register_cvar("csb_knife_enabled", "1") register_clcmd("say /knife", "cmdMenu") register_clcmd("say_team /knife", "cmdMenu") register_clcmd("say /knives", "cmdMenu") register_event("CurWeapon", "fwCurWeapon", "be", "1=1", "2=29") RegisterHam(Ham_TakeDamage, "player", "fwTakeDamage") } public plugin_precache() { for (new i = 1; i < sizeof(g_szName); i++) { if (g_szViewModel[i][0] && file_exists(g_szViewModel[i])) { precache_model(g_szViewModel[i]) g_bViewOk[i] = true } if (g_szWorldModel[i][0] && file_exists(g_szWorldModel[i])) { precache_model(g_szWorldModel[i]) g_bWorldOk[i] = true } new snd[128] formatex(snd, charsmax(snd), "sound/%s", g_szDeploySnd[i]) if (g_szDeploySnd[i][0] && file_exists(snd)) { precache_sound(g_szDeploySnd[i]) g_bDeployOk[i] = true } formatex(snd, charsmax(snd), "sound/%s", g_szHitSnd[i]) if (g_szHitSnd[i][0] && file_exists(snd)) { precache_sound(g_szHitSnd[i]) g_bHitOk[i] = true } } } public plugin_cfg() { g_iVault = nvault_open("csb_knife_skins") if (g_iVault == NO_VAULT) log_amx("[CSB Knife Skins] could not open nVault - choices will not persist.") } public plugin_end() { if (g_iVault != NO_VAULT) nvault_close(g_iVault) } public client_authorized(id) { g_iChoice[id] = 0 if (is_user_bot(id) || is_user_hltv(id)) return get_user_authid(id, g_szAuth[id], charsmax(g_szAuth[])) if (g_iVault == NO_VAULT) return new value[8] if (nvault_get(g_iVault, g_szAuth[id], value, charsmax(value))) { new idx = str_to_num(value) if (idx > 0 && idx < sizeof(g_szName)) g_iChoice[id] = idx } } public cmdMenu(id) { if (!get_pcvar_num(g_pEnabled)) return PLUGIN_HANDLED new title[64] formatex(title, charsmax(title), "\yCSB Knife Skins^n\wCurrent: \r%s", g_szName[g_iChoice[id]]) new menu = menu_create(title, "menuHandler") new info[8], line[64] new flags = get_user_flags(id) for (new i = 0; i < sizeof(g_szName); i++) { num_to_str(i, info, charsmax(info)) if (g_iFlag[i] != 0 && !(flags & g_iFlag[i])) formatex(line, charsmax(line), "%s \r[locked]", g_szName[i]) else if (i == g_iChoice[id]) formatex(line, charsmax(line), "%s \y[selected]", g_szName[i]) else copy(line, charsmax(line), g_szName[i]) menu_additem(menu, line, info) } menu_setprop(menu, MPROP_EXITNAME, "Close") menu_display(id, menu) return PLUGIN_HANDLED } public menuHandler(id, menu, item) { if (item == MENU_EXIT) { menu_destroy(menu) return PLUGIN_HANDLED } new info[8], name[32], access, callback menu_item_getinfo(menu, item, access, info, charsmax(info), name, charsmax(name), callback) menu_destroy(menu) new idx = str_to_num(info) if (idx < 0 || idx >= sizeof(g_szName)) return PLUGIN_HANDLED if (g_iFlag[idx] != 0 && !(get_user_flags(id) & g_iFlag[idx])) { client_print(id, print_chat, "[CSB] '%s' is locked - VIP only.", g_szName[idx]) return PLUGIN_HANDLED } g_iChoice[id] = idx if (g_iVault != NO_VAULT && g_szAuth[id][0]) { new value[8] num_to_str(idx, value, charsmax(value)) nvault_set(g_iVault, g_szAuth[id], value) } client_print(id, print_chat, "[CSB] Knife skin set to '%s'.", g_szName[idx]) return PLUGIN_HANDLED } public fwCurWeapon(id) { if (!get_pcvar_num(g_pEnabled) || !is_user_alive(id)) return new idx = g_iChoice[id] if (idx <= 0) return if (g_bViewOk[idx]) set_pev(id, pev_viewmodel2, engfunc(EngFunc_AllocString, g_szViewModel[idx])) if (g_bWorldOk[idx]) set_pev(id, pev_weaponmodel2, engfunc(EngFunc_AllocString, g_szWorldModel[idx])) if (g_bDeployOk[idx]) engfunc(EngFunc_EmitSound, id, 1, g_szDeploySnd[idx], 1.0, 0.8, 0, 100) } public fwTakeDamage(victim, inflictor, attacker, Float:damage, damagebits) { if (attacker < 1 || attacker > 32 || !is_user_connected(attacker)) return HAM_IGNORED new idx = g_iChoice[attacker] if (idx <= 0 || !g_bHitOk[idx]) return HAM_IGNORED if (get_user_weapon(attacker) != CSW_KNIFE) return HAM_IGNORED engfunc(EngFunc_EmitSound, attacker, 1, g_szHitSnd[idx], 1.0, 0.8, 0, 100) return HAM_IGNORED }