/* * CSB Zombie Classes * Copyright (C) 2026 counter-strike-boost.com * * A class system that sits on top of CSB Zombie Core. Players pick a zombie * class from a menu (Classic, Runner, Tank, Leaper, Hunter); each class has its * own health, speed, gravity, knockback and model. The choice is saved per * player in nVault and applied automatically the moment they are infected, * through the core's csb_zombie_infected forward. Bullet knockback is scaled * by the class's knockback multiplier. * * Requires CSB Zombie Core (provides csb_is_zombie and the infection forward). * Inspired by Zombie Plague Classes by MeRcyLeZZ. Independent GPL re-write. * * 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. Distributed WITHOUT ANY WARRANTY. See the GNU General * Public License for more details: . */ #include #include #include #include #include #include // provided by CSB Zombie Core at runtime native csb_is_zombie(id) new const PLUGIN[] = "CSB Zombie Classes" new const VERSION[] = "1.0.0" new const AUTHOR[] = "counter-strike-boost.com" // Class table: name, health, max speed, gravity, knockback, model. new const g_szClassName[][] = { "Classic", "Runner", "Tank", "Leaper", "Hunter" } new const g_szClassDesc[][] = { "Balanced all-rounder", "Fast but fragile", "Slow wall of health", "Low gravity, big jumps", "High speed, high knockback taken" } new const g_iClassHp[] = { 2000, 1400, 4000, 1600, 1800 } new const Float:g_fClassSpeed[] = { 250.0, 320.0, 220.0, 260.0, 300.0 } new const Float:g_fClassGravity[] = { 0.85, 0.80, 1.00, 0.55, 0.80 } new const Float:g_fClassKnock[] = { 1.0, 1.4, 0.6, 1.2, 1.6 } new const g_szClassModel[][] = { "zombie_source", "zombie_source", "zombie_source", "zombie_source", "zombie_source" } new g_pEnabled new g_iClass[33] new g_iVault new g_iMaxPlayers public plugin_init() { register_plugin(PLUGIN, VERSION, AUTHOR) g_pEnabled = register_cvar("csb_zmclass_enabled", "1") register_clcmd("say /zclass", "cmdClassMenu") register_clcmd("say /class", "cmdClassMenu") RegisterHam(Ham_TakeDamage, "player", "fwTakeDamage", 0) g_iMaxPlayers = get_maxplayers() g_iVault = nvault_open("csb_zm_classes") } public plugin_end() { if (g_iVault != INVALID_HANDLE) nvault_close(g_iVault) } public client_putinserver(id) { g_iClass[id] = loadClass(id) } // Called by CSB Zombie Core when a player becomes a zombie. public csb_zombie_infected(id, infector) { if (!get_pcvar_num(g_pEnabled) || !is_user_alive(id)) return applyClass(id) } applyClass(id) { new c = g_iClass[id] if (c < 0 || c >= sizeof(g_szClassName)) c = 0 set_user_health(id, g_iClassHp[c]) set_user_maxspeed(id, g_fClassSpeed[c]) set_user_gravity(id, g_fClassGravity[c]) if (g_szClassModel[c][0]) cs_set_user_model(id, g_szClassModel[c]) client_print(id, print_center, "Zombie class: %s", g_szClassName[c]) } public cmdClassMenu(id) { if (!get_pcvar_num(g_pEnabled)) return PLUGIN_HANDLED new menu = menu_create("\yChoose your zombie class", "classHandler") new line[96], info[8] for (new i = 0; i < sizeof(g_szClassName); i++) { formatex(info, charsmax(info), "%d", i) formatex(line, charsmax(line), "%s \r[HP %d]^n\w%s", g_szClassName[i], g_iClassHp[i], g_szClassDesc[i]) menu_additem(menu, line, info, 0) } menu_setprop(menu, MPROP_EXITNAME, "Close") menu_display(id, menu, 0) return PLUGIN_HANDLED } public classHandler(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 c = str_to_num(info) if (c < 0 || c >= sizeof(g_szClassName)) return PLUGIN_HANDLED g_iClass[id] = c saveClass(id, c) client_print(id, print_chat, "[Zombie] Class set to %s. It applies the next time you are infected.", g_szClassName[c]) // If already a zombie this round, apply immediately. if (is_user_alive(id) && csb_is_zombie(id)) applyClass(id) return PLUGIN_HANDLED } public fwTakeDamage(victim, inflictor, attacker, Float:damage, damagebits) { if (!get_pcvar_num(g_pEnabled)) return HAM_IGNORED if (attacker < 1 || attacker > g_iMaxPlayers || attacker == victim) return HAM_IGNORED // only human-on-zombie bullet damage produces knockback if (!csb_is_zombie(victim) || csb_is_zombie(attacker)) return HAM_IGNORED if (!is_user_alive(victim)) return HAM_IGNORED new c = g_iClass[victim] if (c < 0 || c >= sizeof(g_szClassName)) c = 0 new Float:kb = g_fClassKnock[c] if (kb <= 0.0) return HAM_IGNORED new Float:oa[3], Float:ov[3], Float:dir[3] pev(attacker, pev_origin, oa) pev(victim, pev_origin, ov) dir[0] = ov[0] - oa[0] dir[1] = ov[1] - oa[1] new Float:len = floatsqroot(dir[0] * dir[0] + dir[1] * dir[1]) if (len <= 0.0) return HAM_IGNORED new Float:power = damage * kb new Float:vel[3] pev(victim, pev_velocity, vel) vel[0] += dir[0] / len * power vel[1] += dir[1] / len * power set_pev(victim, pev_velocity, vel) return HAM_IGNORED } saveClass(id, c) { new authid[35], data[8] get_user_authid(id, authid, charsmax(authid)) formatex(data, charsmax(data), "%d", c) nvault_set(g_iVault, authid, data) } loadClass(id) { new authid[35], data[8], timestamp get_user_authid(id, authid, charsmax(authid)) if (nvault_lookup(g_iVault, authid, data, charsmax(data), timestamp)) return clamp(str_to_num(data), 0, sizeof(g_szClassName) - 1) return 0 }