Promocionar servidor
csb-ctf / addons / amxmodx / scripting / csb_ctf.sma
286 lines 6.7 KiB Pawn Raw Permalink History
/*
 * 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/
 * <map>.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 <https://www.gnu.org/licenses/> for details.
 */

#include <amxmodx>
#include <fakemeta>
#include <hamsandwich>

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])
}