/*
* 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 for details.
*/
#include
#include
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")
}
}