/* * CSB Unstuck * Copyright (C) 2026 counter-strike-boost.com * * "say /unstuck" frees a player wedged inside map geometry or another player. * The plugin confirms the player is actually stuck with a human-hull trace at * his current position, then searches outward in a growing sphere for the * nearest empty spot (PointContents + a hull trace that does not start solid) * and teleports him there. A cooldown and a combat lock stop it being abused * to escape a firefight. * * Inspired by the many community "Stuck / Unstuck" plugins for AMX Mod X. This * is an independent GPL re-implementation; no original code is reused. * * 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 Unstuck" new const VERSION[] = "1.0.0" new const AUTHOR[] = "counter-strike-boost.com" #define HULL_HUMAN 1 #define DONT_IGNORE_MONSTERS 0 #define CONTENTS_EMPTY -1 new g_pEnabled, g_pCooldown, g_pCombatLock, g_pMaxDist new g_iLastUse[33] new g_iLastDamage[33] public plugin_init() { register_plugin(PLUGIN, VERSION, AUTHOR) g_pEnabled = register_cvar("csb_unstuck", "1") g_pCooldown = register_cvar("csb_unstuck_cooldown", "10") g_pCombatLock = register_cvar("csb_unstuck_combatlock", "5") g_pMaxDist = register_cvar("csb_unstuck_maxdist", "128") register_clcmd("say /unstuck", "cmdUnstuck") register_clcmd("say_team /unstuck", "cmdUnstuck") register_clcmd("say /stuck", "cmdUnstuck") register_event("Damage", "onDamage", "b") } public onDamage(id) { if (read_data(2) > 0) g_iLastDamage[id] = get_systime() } public client_disconnected(id) { g_iLastUse[id] = 0 g_iLastDamage[id] = 0 } public cmdUnstuck(id) { if (!get_pcvar_num(g_pEnabled)) return PLUGIN_HANDLED if (!is_user_alive(id)) { client_print(id, print_chat, "[CSB] You have to be alive to use /unstuck.") return PLUGIN_HANDLED } new now = get_systime() new lock = get_pcvar_num(g_pCombatLock) if (lock > 0 && (now - g_iLastDamage[id]) < lock) { client_print(id, print_chat, "[CSB] You cannot unstuck while in combat. Wait %d second(s).", lock - (now - g_iLastDamage[id])) return PLUGIN_HANDLED } new cd = get_pcvar_num(g_pCooldown) if (cd > 0 && g_iLastUse[id] && (now - g_iLastUse[id]) < cd) { client_print(id, print_chat, "[CSB] /unstuck is on cooldown for %d more second(s).", cd - (now - g_iLastUse[id])) return PLUGIN_HANDLED } new Float:origin[3] pev(id, pev_origin, origin) if (!isStuck(id, origin)) { client_print(id, print_chat, "[CSB] You are not stuck.") return PLUGIN_HANDLED } new Float:dest[3] if (findFreeSpot(id, origin, dest)) { set_pev(id, pev_origin, dest) set_pev(id, pev_velocity, Float:{0.0, 0.0, 0.0}) g_iLastUse[id] = now client_print(id, print_chat, "[CSB] You have been freed.") } else { client_print(id, print_chat, "[CSB] Could not find a free spot nearby. Try again in a moment.") } return PLUGIN_HANDLED } bool:isStuck(id, const Float:pos[3]) { new tr = 0 engfunc(EngFunc_TraceHull, pos, pos, DONT_IGNORE_MONSTERS, HULL_HUMAN, id, tr) if (get_tr2(tr, TR_StartSolid) || get_tr2(tr, TR_AllSolid)) return true return false } /* Spiral out from the origin sampling candidate points until one is free. */ bool:findFreeSpot(id, const Float:origin[3], Float:out[3]) { new Float:maxDist = get_pcvar_float(g_pMaxDist) new Float:cand[3] new tr = 0 /* 8 compass directions on the horizontal plane, plus straight up. */ static const Float:dirs[9][3] = { { 1.0, 0.0, 0.0 }, { -1.0, 0.0, 0.0 }, { 0.0, 1.0, 0.0 }, { 0.0, -1.0, 0.0 }, { 0.7, 0.7, 0.0 }, { 0.7, -0.7, 0.0 }, { -0.7, 0.7, 0.0 }, { -0.7, -0.7, 0.0 }, { 0.0, 0.0, 1.0 } } for (new Float:radius = 24.0; radius <= maxDist; radius += 16.0) { for (new d = 0; d < sizeof(dirs); d++) { cand[0] = origin[0] + dirs[d][0] * radius cand[1] = origin[1] + dirs[d][1] * radius cand[2] = origin[2] + dirs[d][2] * radius if (engfunc(EngFunc_PointContents, cand) != CONTENTS_EMPTY) continue engfunc(EngFunc_TraceHull, cand, cand, DONT_IGNORE_MONSTERS, HULL_HUMAN, id, tr) if (get_tr2(tr, TR_StartSolid) || get_tr2(tr, TR_AllSolid)) continue out[0] = cand[0] out[1] = cand[1] out[2] = cand[2] return true } } return false }