Server bewerben
csb-zombie-classes / addons / amxmodx / scripting / csb_zombie_classes.sma
221 lines 6 KiB Pawn Raw Permalink History
/*
 * 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: <https://www.gnu.org/licenses/>.
 */

#include <amxmodx>
#include <fakemeta>
#include <hamsandwich>
#include <cstrike>
#include <fun>
#include <nvault>

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