Server bewerben
csb-base-builder / addons / amxmodx / scripting / csb_base_builder.sma
303 lines 7.6 KiB Pawn Raw Permalink History
/*
 * CSB Base Builder
 * Copyright (C) 2026 counter-strike-boost.com
 *
 * Base Builder core. Each round opens with a build phase in which the builder
 * team can grab, carry, rotate and lock map props: /grab pins whatever brush
 * prop your crosshair is on in front of you (moved every frame in
 * PlayerPreThink), /rotate spins it, /lock freezes it in place, /drop releases
 * it. Each builder is limited to a number of locked props. When the build timer
 * expires the fight phase starts, every prop is frozen and the attacking team
 * must break in.
 *
 * Inspired by Base Builder (ConnorMcLeod / Kreedz community). Independent GPL
 * re-implementation.
 *
 * 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>

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

#define PHASE_BUILD 0
#define PHASE_FIGHT 1

new g_pEnabled, g_pBuildTime, g_pBuilderTeam, g_pPropLimit

new g_iPhase
new g_iGrab[33]
new Float:g_fGrabDist[33]
new Float:g_fGrabYaw[33]
new g_iLockedCount[33]
new g_iHudSync

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

    g_pEnabled     = register_cvar("csb_bb_enabled", "1")
    g_pBuildTime   = register_cvar("csb_bb_buildtime", "45")
    g_pBuilderTeam = register_cvar("csb_bb_builderteam", "2")   /* 1=T 2=CT */
    g_pPropLimit   = register_cvar("csb_bb_proplimit", "6")

    register_clcmd("say /grab", "cmdGrab")
    register_clcmd("say /drop", "cmdDrop")
    register_clcmd("say /rotate", "cmdRotate")
    register_clcmd("say /lock", "cmdLock")

    register_forward(FM_PlayerPreThink, "fwPreThink")
    register_logevent("eventRoundStart", 2, "1=Round_Start")

    g_iHudSync = CreateHudSyncObj()

    set_task(1.0, "taskHud", 0, _, _, "b")
}

public client_putinserver(id)
{
    g_iGrab[id] = 0
    g_iLockedCount[id] = 0
}

public client_disconnected(id)
{
    g_iGrab[id] = 0
}

public eventRoundStart()
{
    if (!get_pcvar_num(g_pEnabled))
        return

    g_iPhase = PHASE_BUILD

    for (new id = 1; id <= 32; id++)
    {
        g_iGrab[id] = 0
        g_iLockedCount[id] = 0
    }

    client_print(0, print_chat, "[CSB] BUILD PHASE - %d seconds. Builders: /grab /rotate /lock /drop", get_pcvar_num(g_pBuildTime))

    set_task(float(get_pcvar_num(g_pBuildTime)), "taskFightPhase", 9911)
}

public taskFightPhase()
{
    g_iPhase = PHASE_FIGHT

    /* drop and freeze everything */
    for (new id = 1; id <= 32; id++)
    {
        if (g_iGrab[id] > 0)
        {
            freezeProp(g_iGrab[id])
            g_iGrab[id] = 0
        }
    }

    client_print(0, print_chat, "[CSB] FIGHT PHASE - break into the base!")
}

bool:isBuilder(id)
{
    return get_user_team(id) == get_pcvar_num(g_pBuilderTeam)
}

bool:isGrabbable(ent)
{
    if (ent <= 32 || !pev_valid(ent))
        return false

    static cls[32]
    pev(ent, pev_classname, cls, charsmax(cls))

    if (equal(cls, "worldspawn") || equal(cls, "player"))
        return false

    /* brush props and pushables only */
    if (equal(cls, "func_pushable") || equal(cls, "func_breakable")
        || equal(cls, "func_wall") || equal(cls, "func_door")
        || containi(cls, "func_") == 0)
        return true

    return false
}

public cmdGrab(id)
{
    if (!get_pcvar_num(g_pEnabled) || g_iPhase != PHASE_BUILD)
    {
        client_print(id, print_chat, "[CSB] You can only build during the build phase.")
        return PLUGIN_HANDLED
    }

    if (!isBuilder(id) || !is_user_alive(id))
        return PLUGIN_HANDLED

    if (g_iGrab[id] > 0)
    {
        client_print(id, print_chat, "[CSB] You are already carrying a prop. /drop or /lock first.")
        return PLUGIN_HANDLED
    }

    new ent, body
    get_user_aiming(id, ent, body, 700)

    if (!isGrabbable(ent))
    {
        client_print(id, print_chat, "[CSB] No movable prop in your crosshair.")
        return PLUGIN_HANDLED
    }

    static Float:fMe[3], Float:fProp[3]
    pev(id, pev_origin, fMe)
    pev(ent, pev_origin, fProp)

    g_iGrab[id] = ent
    g_fGrabDist[id] = vector_distance(fMe, fProp)

    if (g_fGrabDist[id] > 200.0)
        g_fGrabDist[id] = 200.0
    else if (g_fGrabDist[id] < 80.0)
        g_fGrabDist[id] = 80.0

    static Float:fAng[3]
    pev(ent, pev_angles, fAng)
    g_fGrabYaw[id] = fAng[1]

    /* make it float freely while carried */
    set_pev(ent, pev_movetype, MOVETYPE_NOCLIP)

    client_print(id, print_chat, "[CSB] Prop grabbed. /rotate to spin, /lock to place.")
    return PLUGIN_HANDLED
}

public cmdDrop(id)
{
    if (g_iGrab[id] > 0)
    {
        set_pev(g_iGrab[id], pev_movetype, MOVETYPE_TOSS)
        g_iGrab[id] = 0
        client_print(id, print_chat, "[CSB] Prop dropped.")
    }

    return PLUGIN_HANDLED
}

public cmdRotate(id)
{
    if (g_iGrab[id] > 0)
    {
        g_fGrabYaw[id] += 45.0

        if (g_fGrabYaw[id] >= 360.0)
            g_fGrabYaw[id] -= 360.0

        client_print(id, print_chat, "[CSB] Rotated to %d degrees.", floatround(g_fGrabYaw[id]))
    }

    return PLUGIN_HANDLED
}

public cmdLock(id)
{
    if (g_iGrab[id] <= 0)
        return PLUGIN_HANDLED

    if (g_iLockedCount[id] >= get_pcvar_num(g_pPropLimit))
    {
        client_print(id, print_chat, "[CSB] You reached your prop limit (%d).", get_pcvar_num(g_pPropLimit))
        return PLUGIN_HANDLED
    }

    new ent = g_iGrab[id]
    freezeProp(ent)
    set_pev(ent, pev_owner, id)

    g_iGrab[id] = 0
    g_iLockedCount[id]++

    client_print(id, print_chat, "[CSB] Prop locked in place (%d/%d).", g_iLockedCount[id], get_pcvar_num(g_pPropLimit))
    return PLUGIN_HANDLED
}

freezeProp(ent)
{
    if (!pev_valid(ent))
        return

    static Float:fZero[3]
    set_pev(ent, pev_velocity, fZero)
    set_pev(ent, pev_movetype, MOVETYPE_PUSHSTEP)
}

public fwPreThink(id)
{
    new ent = g_iGrab[id]

    if (ent <= 0)
        return

    if (!is_user_alive(id) || !pev_valid(ent))
    {
        g_iGrab[id] = 0
        return
    }

    static Float:fEye[3], Float:fOfs[3], Float:fAng[3], Float:fFwd[3], Float:fDest[3]
    pev(id, pev_origin, fEye)
    pev(id, pev_view_ofs, fOfs)
    fEye[0] += fOfs[0]
    fEye[1] += fOfs[1]
    fEye[2] += fOfs[2]

    pev(id, pev_v_angle, fAng)
    angle_vector(fAng, ANGLEVECTOR_FORWARD, fFwd)

    fDest[0] = fEye[0] + fFwd[0] * g_fGrabDist[id]
    fDest[1] = fEye[1] + fFwd[1] * g_fGrabDist[id]
    fDest[2] = fEye[2] + fFwd[2] * g_fGrabDist[id]

    static Float:fZero[3]
    set_pev(ent, pev_velocity, fZero)
    engfunc(EngFunc_SetOrigin, ent, fDest)

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

public taskHud()
{
    if (!get_pcvar_num(g_pEnabled))
        return

    new players[32], num
    get_players(players, num, "ch")

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

        set_hudmessage(255, 180, 0, 0.02, 0.12, 0, 0.0, 1.1, 0.0, 0.0, -1)

        if (g_iPhase == PHASE_BUILD && isBuilder(id))
            ShowSyncHudMsg(id, g_iHudSync, "BUILD PHASE | props %d/%d", g_iLockedCount[id], get_pcvar_num(g_pPropLimit))
        else if (g_iPhase == PHASE_BUILD)
            ShowSyncHudMsg(id, g_iHudSync, "BUILD PHASE | wait for the fight")
        else
            ShowSyncHudMsg(id, g_iHudSync, "FIGHT PHASE")
    }
}