Server-Side_SmokeNade

amxx Server-Side_SmokeNade 1.0.0-beta.2

Нет прав для скачивания
Для CS 1.6
Автор
SergeyShorokhov
Исходный код
C#:
/**
* Server-Side SmokeNade by Sergey Shorokhov
*   06.10.2023
*   https://github.com/wopox1337/
*
*
* Description:
*   Is an AMXModX plugin that enhances the smoke grenade effects in Counter-Strike 1.6 (and Counter-Strike Condition Zero).
*
*   This plugin recreates smoke grenade effects (puffs of smoke) on the server side,
*   provides a more realistic look to smoke grenades in the game
*   (as it was changed in later iterations of the game such as CS:S, CS:GO, CS2).
*
* Why is it needed:
*   The default smoke sprite (`sprites/gas_puff_01.spr`) is too transparent
*   and doesn't allow you to take advantage of the smoke grenade in gameplay.
*
* How it works:
*   The plugin blocks the sending of the event to the game client
*   and recreates smoke clubs using server-side entities,
*   exactly repeating the shape and animation of the standard smoke.
*
* Important note:
*   If the smoke effect is fully recreated (`amx_smokegren_replacemode` == 3), the load is increased 2x.
*
*   Using full smoke grenade recreation (3) is not recommended for servers
*   that have more than 10 players online (e.g. servers with 32\32 players online).
*
*   Also be careful with setting the parameter `amx_smokegren_pieces`,
*   it directly affects the shape and quality of the smoke grenade,
*   affecting the server load when using smoke grenades.
*
*   In special cases it may be necessary to set a larger number of allocated edicts
*   using the startup parameter `-num_edicts` (at least 2000).
*
* Calculations.
*   With standard parameters:
*     - amx_smokegren_replacemode 1
*     - amx_smokegren_pieces 8
*   One smoke cloud will create 17 entities.
*
*   At maximum parameters:
*     - amx_smokegren_replacemode 3
*     - amx_smokegren_pieces 10
*   One smoke cloud will create 41 entities.
*
* Advantages over standard game smoke:
*   - Improved transparency of the smoke cloud;
*   - Fixed poor smoke density in 16-bit video game mode;
*   - Smoke doesn't disappear on HLTV;
*   - Smoke can't be abused by reconnecting to the server;
*   - Smoke is always created (even if the client has a congested network channel);
*   - Ability to change the visual look of the smoke cloud.
*
* Advantages to other similar plugins:
*   - Ability to set any custom sprite to display smoke;
*   - Client FPS doesn't drop much;
*   - Server FPS doesn't drop much;
*   - The server doesn't flood the client's network channel to display the smoke cloud;
*   - Doesn't break compatibility with the game:
*       - Bots understand where smoke is located;
*       - Cannot see nickname through smoke (mp_playerid);
*       - Hostages can react to smoke grenade;
*       - Overview map can show smokes.
*   - Easy to adjust the color, duration and performance of the smoke cloud;
*   - No render bugs when positioning the smoke cloud on water;
*   - Smoke cloud has a very close to the original visual appearance (authenticity);
*   - Smoke cloud doesn't stay in a new round;
*   - Cannot be abused with the client command `fastsprites`;
*   - Smoke doesn't disappear before its lifetime expires;
*   - Smoke doesn't disappear if the player's internet connection is poor;
*   - Smoke doesn't disappear if you move away from it.
*   - Smoke doesn't flicker.
*
* Acknowledgements:
*   - ReGameDLL authors;
*   - To everyone who has previously tried custom smoke and their code examples;
*   - To the Counter-Strike developers (for their bugs, including);
*   - Community https://Dev-CS.ru/ (the most friendly and experienced team of developers).
*
* TODO:
*   - Correct the remaining visual inaccuracies.
*   - Implement API;
*   - Improve integration with GameDLL;
*   - Optimize sprite;
*
* Known bugs:
*   - If the smoke duration changes, the grenade entity may not match the smoke duration.
*
*/

#include <amxmodx>
#include <amxmisc>
#include <hamsandwich>
#include <fakemeta>
#include <xs>


public stock const PluginName[]        = "Server-Side SmokeNade"
public stock const PluginVersion[]     = "1.0.0-beta.1"
public stock const PluginAuthor[]      = "Sergey Shorokhov"
public stock const PluginURL[]         = "https://github.com/wopox1337/ServerSide_SmokeNade"
public stock const PluginDescription[] = "Replacing client smoke with Server-Side SmokeNade."


static const g_sprFile[]        = "sprites/gas_puff_02.spr"
// https://github.com/s1lentq/ReGameDLL_CS/blob/1e49d947927e7f6f2fd70d8398e4a9519a34450a/regamedll/dlls/subs.h#L31
static const g_baseClassname[]  = "info_null"
static const g_className[]      = "particle_smokegren"

enum _: CustomPEV {
    _pev_spawnInside    = pev_iuser1,
    _pev_popCreated     = pev_iuser2,

    _pev_scaleSpeed     = pev_fuser2,
    _pev_dieTime        = pev_fuser3,
    _pev_timeCreated    = pev_fuser4,
}

static        amx_smokegren_replacemode
static        amx_smokegren_fix_waterrender
static Float: amx_smokegren_pieces
static Float: amx_smokegren_color_r
static Float: amx_smokegren_color_g
static Float: amx_smokegren_color_b
static Float: amx_smokegren_color_a
static Float: amx_smokegren_lifetime

public plugin_precache() {
    #if (!defined PluginDescription)
        register_plugin(PluginName, PluginVersion, PluginAuthor)
    #endif

    StartupCheck()

    precache_model(g_sprFile)
}

public plugin_init() {
    register_event("HLTV", "CSGameRules_RestartRound", "a", "1=0", "2=0")

    register_forward(FM_PlaybackEvent, "EV_Playback", ._post = false)
    RegisterHam(Ham_Think, g_baseClassname, "CNullEntity_Think", .Post = false)

    Create_ConVars(.createConfigFile = true)

    if (amx_smokegren_fix_waterrender)
        ChangeRenderMode("func_water", kRenderNormal)
}

public EV_Playback(flags, invoker, eventIndex, Float: delay, Float: origin[3],
                    Float: angles[3], Float: fparam1, Float: fparam2,
                    iparam1, iparam2, bparam1, bparam2) {

    // https://github.com/s1lentq/ReGameDLL_CS/blob/1e49d947927e7f6f2fd70d8398e4a9519a34450a/regamedll/dlls/wpn_shared/wpn_smokegrenade.cpp#L34
    static m_usCreateSmoke
    if (!m_usCreateSmoke)
        m_usCreateSmoke = engfunc(EngFunc_PrecacheEvent, 1, "events/createsmoke.sc")

    if (eventIndex != m_usCreateSmoke)
        return FMRES_IGNORED

    new bool: isFirstSmoke = (iparam2 == 1)
    if (!isFirstSmoke)
        return FMRES_IGNORED

    new bool: isSmokeReplaced = EV_CreateSmoke(origin)
    return isSmokeReplaced ? FMRES_SUPERCEDE : FMRES_IGNORED
}

static bool: EV_CreateSmoke(const Float: origin[3]) {
    // https://github.com/s1lentq/ReGameDLL_CS/blob/1e49d947927e7f6f2fd70d8398e4a9519a34450a/regamedll/dlls/ggrenade.cpp#L603
    if (amx_smokegren_replacemode == 0)
        return false

    CreateGasInside(origin, GetColorArray(), amx_smokegren_color_a)

    if (amx_smokegren_replacemode == 3)
        CreateSmokePop(origin, GetColorArray(), amx_smokegren_color_a)

    if (amx_smokegren_replacemode >= 2)
        return true

    return false
}

public CNullEntity_Think(const entity) {
    static classname[32]
    pev(entity, pev_classname, classname, charsmax(classname))

    if (strcmp(classname, g_className) != 0)
        return

    CPartSmokeGrenade_Think(entity)
}

public CSGameRules_RestartRound() {
    new entity = MaxClients
    while ((entity = engfunc(EngFunc_FindEntityByString, entity, "classname", g_className))) {
        set_pev(entity, pev_flags, pev(entity, pev_flags) | FL_KILLME)

        #if (defined DEBUG)
            entityCount(-1)
        #endif
    }
}

static CPartSmokeGrenade_Create(const Float: origin[3], const Float: velocity[3],
                        const model[], const Float: scale,
                        const Float: color[3] = { 175.0, 175.0, 175.0 }, const Float: brightness = 190.0) {

    new entity = engfunc(EngFunc_CreateNamedEntity, engfunc(EngFunc_AllocString, g_baseClassname))
    set_pev(entity, pev_classname, g_className)

    engfunc(EngFunc_SetModel, entity, model)
    engfunc(EngFunc_SetOrigin, entity, origin)
    set_pev(entity, pev_velocity, velocity)
    set_pev(entity, pev_movetype, MOVETYPE_NOCLIP)
    set_pev(entity, pev_gravity, 0.0)
    set_pev(entity, pev_scale, scale)

    set_pev(entity, pev_rendermode, kRenderTransAlpha)
    set_pev(entity, pev_rendercolor, color)
    set_pev(entity, pev_renderamt, brightness)

    #if (defined DEBUG)
        client_print(0, print_center, "Smoke puffs entity count: `%i`", entityCount(1))
    #endif

    return entity
}

static CPartSmokeGrenade_Think(const entity) {
    if (!pev(entity, _pev_popCreated)) {
        set_pev(entity, _pev_popCreated, true)

        static Float: velocity[3]
        pev(entity, pev_velocity, velocity)
        xs_vec_mul_scalar(velocity, 0.035, velocity)

        set_pev(entity, pev_velocity, velocity, sizeof(velocity))
    }

    // Main smoke part time
    new Float: gametime = get_gametime()

    new Float: remainingTime = pev(entity, _pev_dieTime) - gametime
    static Float: brightness
    pev(entity, pev_renderamt, brightness)

    if (brightness >= 255.0 && remainingTime < 3.0) {
        set_pev(entity, _pev_scaleSpeed, 0.0)
        set_pev(entity, _pev_timeCreated, gametime)
        set_pev(entity, pev_avelocity, Float: {0.0, 0.0, 0.0})
    }

    if (remainingTime < 5.0) {
        if (brightness > 0.0) {
            brightness -= (gametime - pev(entity, _pev_timeCreated)) * 0.1
            set_pev(entity, pev_renderamt, brightness)
        }

        if (brightness < 0.0) {
            brightness = 0.0
            set_pev(entity, pev_renderamt, brightness)

            set_pev(entity, _pev_dieTime, gametime)
        }
    }

    static Float: scale
    pev(entity, pev_scale, scale)
    scale += pev(entity, _pev_scaleSpeed)
    set_pev(entity, pev_scale, scale)

    const Float: thinkFreq = 0.05
    if (pev(entity, _pev_dieTime) > gametime) {
        set_pev(entity, pev_nextthink, gametime + thinkFreq)

        return
    }

    // Dissapear part
    const Float: divider = 2.5

    static Float: renderAmt
    pev(entity, pev_renderamt, renderAmt)
    renderAmt = floatmax(0.0, renderAmt - (renderAmt / divider))
    set_pev(entity, pev_renderamt, renderAmt)

    pev(entity, pev_scale, scale)
    scale = floatmax(0.0, scale - (scale / divider))
    set_pev(entity, pev_scale, scale)

    if (renderAmt > 1.0) {
        const Float: fadeSpeed = 0.05
        set_pev(entity, pev_nextthink, gametime + fadeSpeed)

        return
    }

    // Kill particle
    set_pev(entity, pev_effects, EF_NODRAW)
    set_pev(entity, pev_flags, pev(entity, pev_flags) | FL_KILLME)

    #if (defined DEBUG)
        entityCount(-1)
    #endif
}

static CreateGasSmoke(const Float: origin[3], const Float: velocity[3],
                    const Float: color[3],
                    Float: brightness, bool: insideCloud) {

    new entity = CPartSmokeGrenade_Create(
        .origin = origin,
        .velocity = velocity,
        .model = g_sprFile,
        .scale = random_float(2.5, 4.0),
        .color = color,
        .brightness = brightness
    )

    static Float: avelocity[3]
    new Float: gametime = get_gametime()
    new Float: dieTime = gametime + amx_smokegren_lifetime

    const Float: scaleFactor = 1.42
    const Float: maxRotateVelocity = 10.0
    const Float: scaleSpeed = 0.1

    if (insideCloud) {
        avelocity[2] = random_float(-(maxRotateVelocity / scaleFactor), (maxRotateVelocity / scaleFactor))
        set_pev(entity, _pev_scaleSpeed, scaleSpeed / scaleFactor)
        set_pev(entity, _pev_dieTime, dieTime)
    } else {
        avelocity[2] = random_float(-maxRotateVelocity, maxRotateVelocity)
        set_pev(entity, _pev_scaleSpeed, scaleSpeed)
        set_pev(entity, _pev_dieTime, dieTime - (amx_smokegren_lifetime / 3.0))
    }

    set_pev(entity, _pev_spawnInside, insideCloud)
    set_pev(entity, pev_avelocity, avelocity)
    set_pev(entity, pev_nextthink, gametime + 0.15)

    return entity
}

static CreateSmokePop(const Float: origin[3], Float: color[3], Float: brightness) {
    new Float: step = 360.0 / amx_smokegren_pieces

    static Float: angles[3]
    static Float: vForward[3], Float: vRight[3], Float: vUp[3]

    for (new Float: angleStep = 0.0; angleStep < 360.0; angleStep += step) {
        angles[XS_YAW] += angleStep

        engfunc(EngFunc_AngleVectors, angles, vForward, vRight, vUp)
        xs_vec_normalize(vForward, vForward)

        static Float: velocity[3]
        for (new i; i < sizeof(velocity); i++) {
            velocity[i] = (vForward[i] * 375) + (vUp[i] * 165)
        }

        CreateGasSmoke(origin, velocity, color, brightness, .insideCloud = false)
    }

    for (new Float: angleStep = 0.0; angleStep < 360.0; angleStep += step) {
        angles[XS_YAW] += angleStep

        engfunc(EngFunc_AngleVectors, angles, vForward, vRight, vUp)
        xs_vec_normalize(vForward, vForward)

        new Float: velocity[3]
        for (new i; i < sizeof(velocity); i++) {
            velocity[i] = (vForward[i] * 375) + (vUp[i] * 265)
        }

        CreateGasSmoke(origin, velocity, color, brightness, .insideCloud = false)
    }

    #if (defined CLIENT_WIERD_CODE_DONT_USE_THIS)
    {
        angles[XS_YAW] = 45.0

        engfunc(EngFunc_AngleVectors, angles, vForward, vRight, vUp)
        xs_vec_normalize(vForward, vForward)

        static Float: velocity[3]
        for (new i; i < sizeof(velocity); i++) {
            velocity[i] = (vForward[i] * 375) + (vUp[i] * 120)
        }

        CreateGasSmoke(origin , velocity, color, brightness, .insideCloud = false)

        angles[XS_YAW] = 270.0
        engfunc(EngFunc_AngleVectors, angles, vForward, vRight, vUp)
        xs_vec_normalize(vForward, vForward)

        for (new i; i < sizeof(velocity); i++) {
            velocity[i] = (vForward[i] * 375) + (vUp[i] * 120)
        }

        CreateGasSmoke(origin, velocity, color, brightness, .insideCloud = false)
    }
    #endif
}

static CreateGasInside(const Float: origin[3], Float: color[3], const Float: brightness) {
    new Float: step = 360.0 / amx_smokegren_pieces

    static Float: vAngles[3]
    static Float: vForward[3], Float: vRight[3], Float: vUp[3]

    for (new Float: fAngleStep = 0.0; fAngleStep < 360.0; fAngleStep += step) {
        vAngles[XS_YAW] += fAngleStep

        engfunc(EngFunc_AngleVectors, vAngles, vForward, vRight, vUp)
        xs_vec_normalize(vForward, vForward)

        new Float: _origin[3]
        for (new i; i < sizeof(_origin); i++) {
            _origin[i] = origin[i] + (vForward[i] * random_num(90, 110)) + (vUp[i] * 45)
        }

        CreateGasSmoke(
            _origin,
            Float: {0.0, 0.0, 0.0},
            color,
            brightness,
            .insideCloud = true
        )
    }

    for (new Float: fAngleStep = 0.0; fAngleStep < 360.0; fAngleStep += step) {
        vAngles[XS_YAW] += fAngleStep

        engfunc(EngFunc_AngleVectors, vAngles, vForward, vRight, vUp)
        xs_vec_normalize(vForward, vForward)

        new Float: _origin[3]
        for (new i; i < sizeof(_origin); i++) {
            _origin[i] = origin[i] + (vForward[i] * 90) + (vUp[i] * 60)
        }

        CreateGasSmoke(
            _origin,
            Float: {0.0, 0.0, 0.0},
            color,
            brightness,
            .insideCloud = true
        )
    }

    CreateGasSmoke(
        origin,
        Float: {0.0, 0.0, 0.0},
        color,
        brightness,
        .insideCloud = false
    )

    #if (defined CLIENT_WIERD_CODE_DONT_USE_THIS)
    {
        vAngles[XS_YAW] = 45.0

        engfunc(EngFunc_AngleVectors, vAngles, vForward, vRight, vUp)
        xs_vec_normalize(vForward, vForward)

        static Float: _origin[3]
        for (new i; i < sizeof(_origin); i++) {
            _origin[i] = origin[i] + (vForward[i] * 45) + (vUp[i] * 55)
        }

        CreateGasSmoke(
            _origin,
            Float: {0.0, 0.0, 0.0},
            color,
            brightness,
            .insideCloud = false
        )

        _origin = Float: {0.0, 0.0, 0.0}

        vAngles[XS_YAW] = 270.0

        engfunc(EngFunc_AngleVectors, vAngles, vForward, vRight, vUp)
        xs_vec_normalize(vForward, vForward)

        for (new i; i < sizeof(_origin); i++) {
            _origin[i] = origin[i] + (vForward[i] * 45) + (vUp[i] * 55)
        }

        CreateGasSmoke(
            _origin,
            Float: {0.0, 0.0, 0.0},
            color,
            brightness,
            .insideCloud = false
        )
    }
    #endif
}

static Create_ConVars(const bool: createConfigFile = true) {
    bind_pcvar_num(
        create_cvar(
            "amx_smokegren_replacemode", "1",
            .has_min = true, .min_val = 0.0,
            .has_max = true, .max_val = 3.0,
            .description = "0 - disabled (don't change client smoke); ^n\
                            1 - main cloud over default smoke cloud; ^n\
                            2 - main cloud only (optimization*); ^n\
                            3 - fully recreation (2x load)."
        ),
        amx_smokegren_replacemode
    )

    bind_pcvar_num(
        create_cvar(
            "amx_smokegren_fix_waterrender", "1",
            .has_min = true, .min_val = 0.0,
            .has_max = true, .max_val = 1.0,
            .description = "Fix the rendering of smoke cloud in water."
        ),
        amx_smokegren_fix_waterrender
    )

    bind_pcvar_float(
        create_cvar(
            "amx_smokegren_pieces", "8",
            .has_min = true, .min_val = 2.0,
            .has_max = true, .max_val = 10.0,
            .description = "Number of smoke particles for one smoke cloud."
        ),
        amx_smokegren_pieces
    )

    bind_pcvar_float(
        create_cvar(
            "amx_smokegren_color_r", "175",
            .has_min = true, .min_val = 1.0,
            .has_max = true, .max_val = 255.0,
            .description = "Red component of cloud color."
        ),
        amx_smokegren_color_r
    )

    bind_pcvar_float(
        create_cvar(
            "amx_smokegren_color_g", "175",
            .has_min = true, .min_val = 1.0,
            .has_max = true, .max_val = 255.0,
            .description = "Green component of cloud color."
        ),
        amx_smokegren_color_g
    )

    bind_pcvar_float(
        create_cvar(
            "amx_smokegren_color_b", "175.0",
            .has_min = true, .min_val = 1.0,
            .has_max = true, .max_val = 255.0,
            .description = "Blue component of cloud color."
        ),
        amx_smokegren_color_b
    )

    bind_pcvar_float(
        create_cvar(
            "amx_smokegren_color_a", "190.0",
            .has_min = true, .min_val = 0.0,
            .has_max = true, .max_val = 255.0,
            .description = "Alpha (transparency) component of cloud color. Client default is 190."
        ),
        amx_smokegren_color_a
    )

    bind_pcvar_float(
        create_cvar(
            "amx_smokegren_lifetime", "29.5",
            .has_min = true, .min_val = 6.0,
            .has_max = true, .max_val = 60.0,
            .description = "Smoke cloud lifetime (in seconds)."
        ),
        amx_smokegren_lifetime
    )

    if (createConfigFile) {
        AutoExecConfig()
    }
}

static Float: GetColorArray() {
    new Float: color[3]
    color[0] = amx_smokegren_color_r
    color[1] = amx_smokegren_color_g
    color[2] = amx_smokegren_color_b

    return color
}

static ChangeRenderMode(const classname[], const mode = kRenderNormal) {
    new entity = MaxClients
    while ((entity = engfunc(EngFunc_FindEntityByString, entity, "classname", classname))) {
        set_pev(entity, pev_rendermode, mode)
    }
}

static StartupCheck() {
    if (!cstrike_running())
        set_fail_state("Plugin work is supported only for Counter-Strike mod!")

    if (!file_exists(g_sprFile))
        set_fail_state("The file `%s` is missing!", g_sprFile)
}

static stock entityCount(const add = 0) {
    static count
    count += add

    return count
}

C++:
// Этот файл был автоматически создан AMX Mod X (v1.10.0.5467)
// Переменные для плагина "Server-Side SmokeNade" от "Сергея Шорохова" (Server-Side_SmokeNade.amxx, v1.0.0-beta.1)


// 0 - отключено (не изменять клиентский дым);
// 1 - основное облако поверх стандартного дымового облака;
// 2 - только основное облако (оптимизация*);
// 3 - полное воссоздание (удвоенная нагрузка).
// -
// По умолчанию: "1"
// Минимум: "0.000000"
// Максимум: "3.000000"
amx_smokegren_replacemode "1"

// Исправить рендеринг дымового облака в воде.
// -
// По умолчанию: "1"
// Минимум: "0.000000"
// Максимум: "1.000000"
amx_smokegren_fix_waterrender "1"

// Количество частиц дыма для одного дымового облака.
// -
// По умолчанию: "8"
// Минимум: "2.000000"
// Максимум: "10.000000"
amx_smokegren_pieces "8"

// Красная компонента цвета облака.
// -
// По умолчанию: "175"
// Минимум: "1.000000"
// Максимум: "255.000000"
amx_smokegren_color_r "175"

// Зеленая компонента цвета облака.
// -
// По умолчанию: "175"
// Минимум: "1.000000"
// Максимум: "255.000000"
amx_smokegren_color_g "175"

// Синяя компонента цвета облака.
// -
// По умолчанию: "175.0"
// Минимум: "1.000000"
// Максимум: "255.000000"
amx_smokegren_color_b "175.0"

// Альфа (прозрачность) компонента цвета облака. Значение по умолчанию для клиента - 190.
// -
// По умолчанию: "190.0"
// Минимум: "0.000000"
// Максимум: "255.000000"
amx_smokegren_color_a "190.0"

// Время жизни дымового облака (в секундах).
// -
// По умолчанию: "29.5"
// Минимум: "6.000000"
// Максимум: "60.000000"
amx_smokegren_lifetime "29.5"
Требования
Amx Mod X 1.9.0, Amx Mod X 1.10.0 dev
Требование ReAPI
Нет
Поддержка русского языка
Да
Совместимость
  1. HLDS
  2. REHLDS
Сверху Снизу