/* * CSB Capture The Flag * Copyright (C) 2026 counter-strike-boost.com * * A capture-the-flag layer for any map. Two flag entities are spawned at base * coordinates read from a per-map data file (addons/amxmodx/configs/csb_ctf/ * .ini, two "x y z" lines: Terrorist base then CT base). Touching the enemy * flag picks it up (Ham_Touch); it then follows the carrier. Bringing it to your * own base while your flag is home scores a capture. Dropped flags auto-return, * and captures are tracked on the HUD. * * Inspired by CTF by Avalanche. 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 #include new const PLUGIN[] = "CSB Capture The Flag" new const VERSION[] = "1.0.0" new const AUTHOR[] = "counter-strike-boost.com" #define FLAG_CLASS "csb_ctf_flag" #define FLAG_MODEL "models/w_backpack.mdl" #define CAP_RANGE2 6400.0 /* 80 units */ new g_pEnabled new g_iFlagEnt[2] new Float:g_fHome[2][3] new g_iCarrier[2] new bool:g_bAtHome[2] new g_iScore[2] new bool:g_bReady new g_iHudSync public plugin_init() { register_plugin(PLUGIN, VERSION, AUTHOR) g_pEnabled = register_cvar("csb_ctf_enabled", "1") RegisterHam(Ham_Touch, FLAG_CLASS, "fwFlagTouch") register_event("DeathMsg", "eventDeath", "a", "1>0") g_iHudSync = CreateHudSyncObj() set_task(0.1, "taskFlags", 0, _, _, "b") } public plugin_precache() { engfunc(EngFunc_PrecacheModel, FLAG_MODEL) } public plugin_cfg() { if (loadBases()) spawnFlags() } bool:loadBases() { new map[32] get_mapname(map, charsmax(map)) new dir[128], path[192] get_localinfo("amxx_configsdir", dir, charsmax(dir)) if (!dir[0]) copy(dir, charsmax(dir), "addons/amxmodx/configs") formatex(path, charsmax(path), "%s/csb_ctf/%s.ini", dir, map) if (!file_exists(path)) return false new line[96], len, pos = 0, n = 0 while (n < 2 && (pos = read_file(path, pos, line, charsmax(line), len))) { trim(line) if (!line[0] || line[0] == ';' || line[0] == '#') continue new sx[16], sy[16], sz[16] parse(line, sx, charsmax(sx), sy, charsmax(sy), sz, charsmax(sz)) if (!sx[0] || !sy[0] || !sz[0]) continue g_fHome[n][0] = str_to_float(sx) g_fHome[n][1] = str_to_float(sy) g_fHome[n][2] = str_to_float(sz) n++ } return (n == 2) } spawnFlags() { for (new i = 0; i < 2; i++) { new ent = engfunc(EngFunc_CreateNamedEntity, engfunc(EngFunc_AllocString, "info_target")) if (!ent) return set_pev(ent, pev_classname, FLAG_CLASS) engfunc(EngFunc_SetModel, ent, FLAG_MODEL) set_pev(ent, pev_solid, SOLID_TRIGGER) set_pev(ent, pev_movetype, MOVETYPE_NONE) set_pev(ent, pev_iuser1, i) engfunc(EngFunc_SetOrigin, ent, g_fHome[i]) g_iFlagEnt[i] = ent g_iCarrier[i] = 0 g_bAtHome[i] = true } g_bReady = true } /* flag i belongs to team (i==0 -> T(1), i==1 -> CT(2)) */ flagTeam(i) { return i == 0 ? 1 : 2 } public fwFlagTouch(flag, other) { if (!g_bReady || other < 1 || other > 32 || !is_user_alive(other)) return HAM_IGNORED new i = pev(flag, pev_iuser1) if (i < 0 || i > 1) return HAM_IGNORED new team = get_user_team(other) /* touching your own flag returns it if it was dropped away from home */ if (team == flagTeam(i)) { if (!g_bAtHome[i] && g_iCarrier[i] == 0) returnFlag(i) return HAM_IGNORED } /* enemy flag: pick it up */ if (g_iCarrier[i] == 0) { g_iCarrier[i] = other g_bAtHome[i] = false new name[32] get_user_name(other, name, charsmax(name)) client_print(0, print_chat, "^x04[CTF]^x01 %s picked up the %s flag!", name, i == 0 ? "Terrorist" : "CT") } return HAM_IGNORED } returnFlag(i) { g_iCarrier[i] = 0 g_bAtHome[i] = true if (pev_valid(g_iFlagEnt[i])) engfunc(EngFunc_SetOrigin, g_iFlagEnt[i], g_fHome[i]) } public taskFlags() { if (!g_bReady || !get_pcvar_num(g_pEnabled)) return for (new i = 0; i < 2; i++) { new c = g_iCarrier[i] if (c == 0 || !pev_valid(g_iFlagEnt[i])) continue if (!is_user_alive(c)) { dropFlag(i) continue } /* flag follows the carrier */ static Float:fPos[3] pev(c, pev_origin, fPos) fPos[2] += 20.0 engfunc(EngFunc_SetOrigin, g_iFlagEnt[i], fPos) /* capture check: carrier at their own base with own flag home */ new own = get_user_team(c) == 1 ? 0 : 1 if (g_bAtHome[own]) { static Float:fMe[3] pev(c, pev_origin, fMe) new Float:dx = fMe[0] - g_fHome[own][0] new Float:dy = fMe[1] - g_fHome[own][1] new Float:dz = fMe[2] - g_fHome[own][2] if (dx*dx + dy*dy + dz*dz < CAP_RANGE2) capture(c, i, own) } } drawHud() } capture(player, enemyFlag, ownIndex) { g_iScore[ownIndex]++ new name[32] get_user_name(player, name, charsmax(name)) client_print(0, print_chat, "^x04[CTF]^x01 %s captured the flag! Score - T:%d CT:%d", name, g_iScore[0], g_iScore[1]) returnFlag(enemyFlag) } dropFlag(i) { new c = g_iCarrier[i] g_iCarrier[i] = 0 if (c >= 1 && c <= 32) { static Float:fPos[3] pev(c, pev_origin, fPos) engfunc(EngFunc_SetOrigin, g_iFlagEnt[i], fPos) } /* auto-return after a delay */ set_task(12.0, "taskAutoReturn", 3000 + i) } public taskAutoReturn(taskid) { new i = taskid - 3000 if (i >= 0 && i <= 1 && g_iCarrier[i] == 0 && !g_bAtHome[i]) { returnFlag(i) client_print(0, print_chat, "^x04[CTF]^x01 The %s flag returned to base.", i == 0 ? "Terrorist" : "CT") } } public eventDeath() { new victim = read_data(2) for (new i = 0; i < 2; i++) if (g_iCarrier[i] == victim) dropFlag(i) } public client_disconnected(id) { for (new i = 0; i < 2; i++) if (g_iCarrier[i] == id) dropFlag(i) } drawHud() { set_hudmessage(0, 200, 255, 0.02, 0.10, 0, 0.0, 0.3, 0.0, 0.0, -1) ShowSyncHudMsg(0, g_iHudSync, "CTF T:%d CT:%d", g_iScore[0], g_iScore[1]) }