/*
* 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
}