Promocionar servidor
csb-pets / addons / amxmodx / scripting / csb_pets.sma
265 lines 6.2 KiB Pawn Raw Permalink History
/*
 * CSB Pets
 * Copyright (C) 2026 counter-strike-boost.com
 *
 * A cosmetic pet model that follows the player. On spawn a non-solid model
 * entity is created and, on a movement task, glides toward a point just behind
 * and above its owner with smooth interpolation, always facing the owner. Each
 * player picks a pet from a menu; the choice is stored per SteamID in nVault and
 * restored on their next spawn. Pets are removed on death and disconnect.
 *
 * 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 <https://www.gnu.org/licenses/> for details.
 */

#include <amxmodx>
#include <fakemeta>
#include <nvault>

new const PLUGIN[] = "CSB Pets"
new const VERSION[] = "1.0.0"
new const AUTHOR[]  = "counter-strike-boost.com"

new const g_szPetName[][] = { "None", "Scientist", "Chicken", "Robo Grunt" }
new const g_szPetModel[][] =
{
    "",
    "models/scientist.mdl",
    "models/chicken.mdl",
    "models/hgrunt.mdl"
}

new g_pEnabled, g_pSpeed
new g_iVault
new g_iPetEnt[33]
new g_iPetChoice[33]

public plugin_init()
{
    register_plugin(PLUGIN, VERSION, AUTHOR)

    g_pEnabled = register_cvar("csb_pets_enabled", "1")
    g_pSpeed   = register_cvar("csb_pets_speed", "0.25")

    register_clcmd("say /pet", "cmdMenu")
    register_clcmd("say_team /pet", "cmdMenu")

    register_event("ResetHUD", "eventSpawn", "b")
    register_event("DeathMsg", "eventDeath", "a", "1>0")

    g_iVault = nvault_open("csb_pets")

    set_task(0.1, "taskFollow", 0, _, _, "b")
}

public plugin_precache()
{
    for (new i = 1; i < sizeof(g_szPetModel); i++)
    {
        if (g_szPetModel[i][0])
            engfunc(EngFunc_PrecacheModel, g_szPetModel[i])
    }
}

public client_putinserver(id)
{
    g_iPetEnt[id]    = 0
    g_iPetChoice[id] = 0

    loadChoice(id)
}

public client_disconnected(id)
{
    removePet(id)
    g_iPetChoice[id] = 0
}

loadChoice(id)
{
    new auth[35]
    get_user_authid(id, auth, charsmax(auth))

    if (!auth[0])
        return

    new data[8], ts
    if (nvault_lookup(g_iVault, auth, data, charsmax(data), ts))
        g_iPetChoice[id] = clamp(str_to_num(data), 0, sizeof(g_szPetModel) - 1)
}

saveChoice(id)
{
    new auth[35]
    get_user_authid(id, auth, charsmax(auth))

    if (!auth[0])
        return

    new data[8]
    num_to_str(g_iPetChoice[id], data, charsmax(data))
    nvault_set(g_iVault, auth, data)
}

public cmdMenu(id)
{
    if (!get_pcvar_num(g_pEnabled))
        return PLUGIN_HANDLED

    new menu = menu_create("\yCSB Pets^n\wPick your pet:", "menuHandler")

    new info[8]
    for (new i = 0; i < sizeof(g_szPetName); i++)
    {
        num_to_str(i, info, charsmax(info))
        menu_additem(menu, g_szPetName[i], info, 0)
    }

    menu_setprop(menu, MPROP_EXIT, MEXIT_ALL)
    menu_display(id, menu, 0)
    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)

    g_iPetChoice[id] = clamp(str_to_num(info), 0, sizeof(g_szPetModel) - 1)
    saveChoice(id)

    removePet(id)

    if (g_iPetChoice[id] == 0)
    {
        client_print(id, print_chat, "[CSB] Pet removed.")
    }
    else
    {
        client_print(id, print_chat, "[CSB] Pet set to %s.", g_szPetName[g_iPetChoice[id]])
        if (is_user_alive(id))
            spawnPet(id)
    }

    return PLUGIN_HANDLED
}

public eventSpawn(id)
{
    if (is_user_alive(id) && g_iPetChoice[id] > 0)
        spawnPet(id)
}

public eventDeath()
{
    new victim = read_data(2)

    if (victim >= 1 && victim <= 32)
        removePet(victim)
}

spawnPet(id)
{
    removePet(id)

    new choice = g_iPetChoice[id]

    if (choice <= 0 || !g_szPetModel[choice][0])
        return

    new ent = engfunc(EngFunc_CreateNamedEntity, engfunc(EngFunc_AllocString, "info_target"))

    if (!ent)
        return

    set_pev(ent, pev_classname, "csb_pet")
    set_pev(ent, pev_owner, id)
    set_pev(ent, pev_movetype, MOVETYPE_NOCLIP)
    set_pev(ent, pev_solid, SOLID_NOT)

    engfunc(EngFunc_SetModel, ent, g_szPetModel[choice])

    static Float:fOrigin[3]
    pev(id, pev_origin, fOrigin)
    engfunc(EngFunc_SetOrigin, ent, fOrigin)

    g_iPetEnt[id] = ent
}

removePet(id)
{
    new ent = g_iPetEnt[id]

    if (ent > 0 && pev_valid(ent))
        engfunc(EngFunc_RemoveEntity, ent)

    g_iPetEnt[id] = 0
}

public taskFollow()
{
    new players[32], num
    get_players(players, num, "a")

    new Float:speed = get_pcvar_float(g_pSpeed)

    if (speed <= 0.0)
        speed = 0.25
    else if (speed > 1.0)
        speed = 1.0

    for (new i = 0; i < num; i++)
    {
        new id = players[i]
        new ent = g_iPetEnt[id]

        if (ent <= 0 || !pev_valid(ent))
            continue

        static Float:fOwner[3], Float:fAng[3], Float:fFwd[3], Float:fTarget[3], Float:fPet[3]
        pev(id, pev_origin, fOwner)
        pev(id, pev_angles, fAng)
        fAng[0] = 0.0
        angle_vector(fAng, ANGLEVECTOR_FORWARD, fFwd)

        /* point behind and slightly above the owner */
        fTarget[0] = fOwner[0] - fFwd[0] * 60.0
        fTarget[1] = fOwner[1] - fFwd[1] * 60.0
        fTarget[2] = fOwner[2] + 20.0

        pev(ent, pev_origin, fPet)

        static Float:fNew[3]
        fNew[0] = fPet[0] + (fTarget[0] - fPet[0]) * speed
        fNew[1] = fPet[1] + (fTarget[1] - fPet[1]) * speed
        fNew[2] = fPet[2] + (fTarget[2] - fPet[2]) * speed

        engfunc(EngFunc_SetOrigin, ent, fNew)

        /* face the owner */
        static Float:fLook[3]
        fLook[0] = fOwner[0] - fNew[0]
        fLook[1] = fOwner[1] - fNew[1]
        fLook[2] = 0.0

        static Float:fYaw
        fYaw = floatatan2(fLook[1], fLook[0], degrees)

        static Float:fSet[3]
        fSet[0] = 0.0
        fSet[1] = fYaw
        fSet[2] = 0.0
        set_pev(ent, pev_angles, fSet)
    }
}