/* * CSB 1v1 Arena * Copyright (C) 2026 counter-strike-boost.com * * A multi-arena 1v1 duel manager. Arena spawn points are read in pairs from a * per-map data file (addons/amxmodx/configs/csb_arenas/.ini, one "x y z" * per line); if none exist the plugin falls back to the map's own spawn points. * Players are queued, paired into free arenas, teleported to their spawns and * given identical weapons. The winner of a duel stays and the loser drops to the * back of the queue; a ladder counts wins and shows them on the HUD. * * Inspired by the community MultiArena / 1v1 plugins. 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 #include new const PLUGIN[] = "CSB 1v1 Arena" new const VERSION[] = "1.0.0" new const AUTHOR[] = "counter-strike-boost.com" #define MAX_ARENAS 16 #define MAX_PLAYERS 32 new g_pEnabled new Float:g_fArena[MAX_ARENAS][2][3] new g_iArenaCount new g_iArenaPlayer[MAX_ARENAS][2] /* player id in each slot, 0 = empty */ new g_iPlayerArena[MAX_PLAYERS + 1] /* which arena a player is in, -1 = none */ new g_iPlayerSlot[MAX_PLAYERS + 1] new g_iQueue[MAX_PLAYERS] new g_iQueueCount new g_iWins[MAX_PLAYERS + 1] new g_iHudSync public plugin_init() { register_plugin(PLUGIN, VERSION, AUTHOR) g_pEnabled = register_cvar("csb_arena_enabled", "1") RegisterHam(Ham_Spawn, "player", "fwSpawnPost", 1) register_event("DeathMsg", "eventDeath", "a", "1>0") g_iHudSync = CreateHudSyncObj() set_task(1.0, "taskHud", 0, _, _, "b") } public plugin_cfg() { loadArenas() } loadArenas() { g_iArenaCount = 0 new map[32] get_mapname(map, charsmax(map)) new path[160], dir[128] get_localinfo("amxx_configsdir", dir, charsmax(dir)) if (!dir[0]) copy(dir, charsmax(dir), "addons/amxmodx/configs") formatex(path, charsmax(path), "%s/csb_arenas/%s.ini", dir, map) new pointCount = 0 new Float:points[MAX_ARENAS * 2][3] if (file_exists(path)) { new line[96], len, pos = 0 while (pointCount < MAX_ARENAS * 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 points[pointCount][0] = str_to_float(sx) points[pointCount][1] = str_to_float(sy) points[pointCount][2] = str_to_float(sz) pointCount++ } } /* fall back to map spawn points */ if (pointCount < 2) pointCount = collectMapSpawns(points, MAX_ARENAS * 2) /* pair up points into arenas */ new pair = 0 while (pair + 1 < pointCount && g_iArenaCount < MAX_ARENAS) { for (new k = 0; k < 3; k++) { g_fArena[g_iArenaCount][0][k] = points[pair][k] g_fArena[g_iArenaCount][1][k] = points[pair + 1][k] } g_iArenaCount++ pair += 2 } for (new a = 0; a < MAX_ARENAS; a++) { g_iArenaPlayer[a][0] = 0 g_iArenaPlayer[a][1] = 0 } } collectMapSpawns(Float:out[][3], maxPoints) { new count = 0 new ent = 0 new const classes[][] = { "info_player_deathmatch", "info_player_start" } for (new c = 0; c < sizeof(classes); c++) { ent = 0 while (count < maxPoints && (ent = engfunc(EngFunc_FindEntityByString, ent, "classname", classes[c])) > 0) { pev(ent, pev_origin, out[count]) count++ } } return count } public client_putinserver(id) { g_iPlayerArena[id] = -1 g_iPlayerSlot[id] = -1 g_iWins[id] = 0 } public client_disconnected(id) { leaveArena(id) removeFromQueue(id) } public fwSpawnPost(id) { if (!get_pcvar_num(g_pEnabled) || !is_user_alive(id)) return if (g_iArenaCount == 0) return /* not in an arena yet -> queue and try to place */ if (g_iPlayerArena[id] == -1) { enqueue(id) fillArenas() } else { placeAndEquip(id) } } enqueue(id) { if (inQueue(id) || g_iQueueCount >= MAX_PLAYERS) return g_iQueue[g_iQueueCount++] = id } bool:inQueue(id) { for (new i = 0; i < g_iQueueCount; i++) if (g_iQueue[i] == id) return true return false } removeFromQueue(id) { new w = 0 for (new i = 0; i < g_iQueueCount; i++) { if (g_iQueue[i] != id) g_iQueue[w++] = g_iQueue[i] } g_iQueueCount = w } fillArenas() { for (new a = 0; a < g_iArenaCount && g_iQueueCount > 0; a++) { for (new s = 0; s < 2; s++) { if (g_iArenaPlayer[a][s] != 0) continue if (g_iQueueCount == 0) break new id = g_iQueue[0] removeFromQueue(id) if (!is_user_connected(id)) continue g_iArenaPlayer[a][s] = id g_iPlayerArena[id] = a g_iPlayerSlot[id] = s placeAndEquip(id) } } } placeAndEquip(id) { new a = g_iPlayerArena[id] new s = g_iPlayerSlot[id] if (a < 0 || a >= g_iArenaCount) return if (!is_user_alive(id)) ExecuteHamB(Ham_CS_RoundRespawn, id) engfunc(EngFunc_SetOrigin, id, g_fArena[a][s]) static Float:fZero[3] set_pev(id, pev_velocity, fZero) strip_user_weapons(id) give_item(id, "weapon_knife") give_item(id, "weapon_deagle") client_print(id, print_chat, "[CSB] Arena %d - fight!", a + 1) } public eventDeath() { new killer = read_data(1) new victim = read_data(2) if (victim < 1 || victim > 32) return new a = g_iPlayerArena[victim] if (a < 0) return /* winner is the other slot */ new winSlot = g_iPlayerSlot[victim] == 0 ? 1 : 0 new winner = g_iArenaPlayer[a][winSlot] if (killer >= 1 && killer <= 32 && killer == winner) g_iWins[winner]++ /* loser leaves the arena and requeues */ leaveArena(victim) enqueue(victim) /* respawn the winner in place, pull a challenger from the queue */ if (winner >= 1 && winner <= 32 && is_user_connected(winner)) set_task(1.0, "taskReset", winner) set_task(1.5, "taskFill") } public taskReset(id) { if (is_user_connected(id) && g_iPlayerArena[id] >= 0) placeAndEquip(id) } public taskFill() { fillArenas() } leaveArena(id) { new a = g_iPlayerArena[id] if (a >= 0 && a < MAX_ARENAS) { new s = g_iPlayerSlot[id] if (s == 0 || s == 1) g_iArenaPlayer[a][s] = 0 } g_iPlayerArena[id] = -1 g_iPlayerSlot[id] = -1 } 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] new a = g_iPlayerArena[id] set_hudmessage(0, 255, 0, 0.02, 0.15, 0, 0.0, 1.1, 0.0, 0.0, -1) if (a >= 0) ShowSyncHudMsg(id, g_iHudSync, "Arena %d | Wins: %d", a + 1, g_iWins[id]) else ShowSyncHudMsg(id, g_iHudSync, "In queue | Wins: %d", g_iWins[id]) } }