Seu servidor aqui Dê seu lance no leilão semanal Promover servidor
csb-map-spawn-editor / addons / amxmodx / scripting / csb_map_spawn_editor.sma
262 lines 6.4 KiB Pawn Raw Permalink History
/*
 * CSB Map Spawn Editor
 * Copyright (C) 2026 counter-strike-boost.com
 *
 * In-game spawn editor for admins. /addspawn stores your current origin and
 * angles, /delspawn removes the closest saved point, /savespawns writes them,
 * and /listspawns reports the count. Spawns are kept per map in the AMXX data
 * directory. On respawn the plugin teleports the player to a saved point with
 * engfunc(EngFunc_SetOrigin) - useful for DM and CSDM style maps.
 *
 * Inspired by the CSDM spawn editor by BAILOPAN. 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 <amxmisc>
#include <fakemeta>
#include <hamsandwich>

#define MAX_SPAWNS 64

new const PLUGIN[] = "CSB Map Spawn Editor"
new const VERSION[] = "1.0.0"
new const AUTHOR[]  = "counter-strike-boost.com"

new Float:g_fSpawn[MAX_SPAWNS][6]
new g_iSpawnCount = 0

new g_pEnabled, g_pOverride
new g_szFile[192]

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

    g_pEnabled  = register_cvar("csb_spawn_enabled", "1")
    g_pOverride = register_cvar("csb_spawn_override", "1")

    register_clcmd("say /addspawn",  "cmdAdd")
    register_clcmd("say /delspawn",  "cmdDel")
    register_clcmd("say /savespawns", "cmdSave")
    register_clcmd("say /listspawns", "cmdList")

    RegisterHam(Ham_Spawn, "player", "fwdSpawnPost", 1)
}

public plugin_cfg()
{
    new dir[128]
    get_localinfo("amxx_datadir", dir, charsmax(dir))

    new map[40]
    get_mapname(map, charsmax(map))

    formatex(g_szFile, charsmax(g_szFile), "%s/csb_spawns_%s.txt", dir, map)

    loadSpawns()
}

loadSpawns()
{
    g_iSpawnCount = 0

    if (!file_exists(g_szFile))
        return

    new f = fopen(g_szFile, "rt")

    if (!f)
        return

    new line[128], p[6][16]

    while (!feof(f) && g_iSpawnCount < MAX_SPAWNS)
    {
        fgets(f, line, charsmax(line))
        trim(line)

        if (!line[0] || line[0] == ';')
            continue

        if (parse(line, p[0], 15, p[1], 15, p[2], 15, p[3], 15, p[4], 15, p[5], 15) < 6)
            continue

        for (new j = 0; j < 6; j++)
            g_fSpawn[g_iSpawnCount][j] = str_to_float(p[j])

        g_iSpawnCount++
    }

    fclose(f)

    log_amx("[CSB Spawn] loaded %d spawn(s) for this map.", g_iSpawnCount)
}

bool:hasAccess(id)
{
    if (get_user_flags(id) & ADMIN_MAP)
        return true

    client_print(id, print_chat, "[CSB] You need the map admin flag (e) to edit spawns.")
    return false
}

public cmdAdd(id)
{
    if (!hasAccess(id))
        return PLUGIN_HANDLED

    if (g_iSpawnCount >= MAX_SPAWNS)
    {
        client_print(id, print_chat, "[CSB] Spawn list is full (%d).", MAX_SPAWNS)
        return PLUGIN_HANDLED
    }

    new Float:origin[3], Float:angles[3]
    pev(id, pev_origin, origin)
    pev(id, pev_v_angle, angles)

    g_fSpawn[g_iSpawnCount][0] = origin[0]
    g_fSpawn[g_iSpawnCount][1] = origin[1]
    g_fSpawn[g_iSpawnCount][2] = origin[2]
    g_fSpawn[g_iSpawnCount][3] = angles[0]
    g_fSpawn[g_iSpawnCount][4] = angles[1]
    g_fSpawn[g_iSpawnCount][5] = angles[2]

    g_iSpawnCount++

    client_print(id, print_chat, "[CSB] Spawn #%d saved at your position. Use /savespawns to write to disk.", g_iSpawnCount)
    return PLUGIN_HANDLED
}

public cmdDel(id)
{
    if (!hasAccess(id))
        return PLUGIN_HANDLED

    if (!g_iSpawnCount)
    {
        client_print(id, print_chat, "[CSB] No spawns to delete.")
        return PLUGIN_HANDLED
    }

    new Float:origin[3]
    pev(id, pev_origin, origin)

    new closest = -1
    new Float:best = 999999.0

    for (new i = 0; i < g_iSpawnCount; i++)
    {
        new Float:pt[3]
        pt[0] = g_fSpawn[i][0]
        pt[1] = g_fSpawn[i][1]
        pt[2] = g_fSpawn[i][2]

        new Float:d = get_distance_f(origin, pt)

        if (d < best)
        {
            best = d
            closest = i
        }
    }

    if (closest < 0)
        return PLUGIN_HANDLED

    /* shift the rest down */
    for (new i = closest; i < g_iSpawnCount - 1; i++)
    {
        for (new j = 0; j < 6; j++)
            g_fSpawn[i][j] = g_fSpawn[i + 1][j]
    }

    g_iSpawnCount--

    client_print(id, print_chat, "[CSB] Removed the closest spawn (%.0f units away). %d left.", best, g_iSpawnCount)
    return PLUGIN_HANDLED
}

public cmdSave(id)
{
    if (!hasAccess(id))
        return PLUGIN_HANDLED

    new f = fopen(g_szFile, "wt")

    if (!f)
    {
        client_print(id, print_chat, "[CSB] Could not open the spawn file for writing.")
        return PLUGIN_HANDLED
    }

    fprintf(f, "; CSB Map Spawn Editor - x y z pitch yaw roll^n")

    for (new i = 0; i < g_iSpawnCount; i++)
    {
        fprintf(f, "%.2f %.2f %.2f %.2f %.2f %.2f^n",
            g_fSpawn[i][0], g_fSpawn[i][1], g_fSpawn[i][2],
            g_fSpawn[i][3], g_fSpawn[i][4], g_fSpawn[i][5])
    }

    fclose(f)

    client_print(id, print_chat, "[CSB] Wrote %d spawn(s) to disk.", g_iSpawnCount)
    log_amx("[CSB Spawn] admin saved %d spawn(s).", g_iSpawnCount)
    return PLUGIN_HANDLED
}

public cmdList(id)
{
    if (!hasAccess(id))
        return PLUGIN_HANDLED

    client_print(id, print_chat, "[CSB] %d custom spawn(s) loaded for this map.", g_iSpawnCount)
    return PLUGIN_HANDLED
}

public fwdSpawnPost(id)
{
    if (!get_pcvar_num(g_pEnabled) || !get_pcvar_num(g_pOverride))
        return

    if (!g_iSpawnCount || !is_user_alive(id))
        return

    set_task(0.1, "taskTeleport", id)
}

public taskTeleport(id)
{
    if (!is_user_alive(id) || !g_iSpawnCount)
        return

    new pick = random_num(0, g_iSpawnCount - 1)

    new Float:origin[3], Float:angles[3]
    origin[0] = g_fSpawn[pick][0]
    origin[1] = g_fSpawn[pick][1]
    origin[2] = g_fSpawn[pick][2]
    angles[0] = g_fSpawn[pick][3]
    angles[1] = g_fSpawn[pick][4]
    angles[2] = g_fSpawn[pick][5]

    engfunc(EngFunc_SetOrigin, id, origin)

    set_pev(id, pev_angles, angles)
    set_pev(id, pev_v_angle, angles)
    set_pev(id, pev_fixangle, 1)

    /* stop any residual momentum from the map's default spawn */
    new Float:zero[3]
    set_pev(id, pev_velocity, zero)
}