/* * CSB Grappling Hook * Copyright (C) 2026 counter-strike-boost.com * * A grappling hook bound to +hook. It traces from the player's eyes along their * aim to the first solid surface within range, anchors there, draws a laser beam * to the anchor and pulls the player toward it by setting pev_velocity every * frame in PlayerPreThink until they release the key or arrive. Includes a max * range, a per-use cooldown and optional VIP/flag gating. * * Inspired by the classic Hook plugin. 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 Grappling Hook" new const VERSION[] = "1.0.0" new const AUTHOR[] = "counter-strike-boost.com" #define ACCESS_FLAG ADMIN_LEVEL_H #if !defined SVC_TEMPENTITY #define SVC_TEMPENTITY 23 #endif #define TE_BEAMPOINTS 0 new g_pEnabled, g_pRange, g_pPull, g_pCooldown, g_pVipOnly new bool:g_bHooking[33] new Float:g_fHook[33][3] new Float:g_fNextHook[33] new g_iBeamSpr public plugin_init() { register_plugin(PLUGIN, VERSION, AUTHOR) g_pEnabled = register_cvar("csb_hook_enabled", "1") g_pRange = register_cvar("csb_hook_range", "1500") g_pPull = register_cvar("csb_hook_pull", "700") g_pCooldown = register_cvar("csb_hook_cooldown", "3.0") g_pVipOnly = register_cvar("csb_hook_viponly", "1") register_clcmd("+hook", "cmdHookStart") register_clcmd("-hook", "cmdHookStop") register_forward(FM_PlayerPreThink, "fwPreThink") } public plugin_precache() { g_iBeamSpr = precache_model("sprites/laserbeam.spr") } public client_putinserver(id) { g_bHooking[id] = false g_fNextHook[id] = 0.0 } public client_disconnected(id) { g_bHooking[id] = false } bool:hasAccess(id) { if (!get_pcvar_num(g_pVipOnly)) return true return (get_user_flags(id) & ACCESS_FLAG) != 0 } public cmdHookStart(id) { if (!get_pcvar_num(g_pEnabled) || !is_user_alive(id)) return PLUGIN_HANDLED if (!hasAccess(id)) { client_print(id, print_chat, "[CSB] The grappling hook is a VIP feature.") return PLUGIN_HANDLED } if (get_gametime() < g_fNextHook[id]) { new left = floatround(g_fNextHook[id] - get_gametime(), floatround_ceil) client_print(id, print_chat, "[CSB] Hook on cooldown (%d s).", left) return PLUGIN_HANDLED } static Float:fStart[3], Float:fOfs[3], Float:fAng[3], Float:fFwd[3], Float:fEnd[3] pev(id, pev_origin, fStart) pev(id, pev_view_ofs, fOfs) fStart[0] += fOfs[0] fStart[1] += fOfs[1] fStart[2] += fOfs[2] pev(id, pev_v_angle, fAng) angle_vector(fAng, ANGLEVECTOR_FORWARD, fFwd) new Float:range = get_pcvar_float(g_pRange) fEnd[0] = fStart[0] + fFwd[0] * range fEnd[1] = fStart[1] + fFwd[1] * range fEnd[2] = fStart[2] + fFwd[2] * range engfunc(EngFunc_TraceLine, fStart, fEnd, IGNORE_MONSTERS, id, 0) static Float:fFrac, Float:fHit[3] get_tr2(0, TR_flFraction, fFrac) get_tr2(0, TR_vecEndPos, fHit) if (fFrac >= 1.0) { client_print(id, print_chat, "[CSB] Nothing in hook range.") return PLUGIN_HANDLED } g_fHook[id][0] = fHit[0] g_fHook[id][1] = fHit[1] g_fHook[id][2] = fHit[2] g_bHooking[id] = true return PLUGIN_HANDLED } public cmdHookStop(id) { if (g_bHooking[id]) { g_bHooking[id] = false g_fNextHook[id] = get_gametime() + get_pcvar_float(g_pCooldown) } return PLUGIN_HANDLED } public fwPreThink(id) { if (!g_bHooking[id]) return FMRES_IGNORED if (!is_user_alive(id)) { g_bHooking[id] = false return FMRES_IGNORED } static Float:fOrigin[3], Float:fDir[3] pev(id, pev_origin, fOrigin) fDir[0] = g_fHook[id][0] - fOrigin[0] fDir[1] = g_fHook[id][1] - fOrigin[1] fDir[2] = g_fHook[id][2] - fOrigin[2] new Float:dist = vector_distance(fOrigin, g_fHook[id]) if (dist < 64.0) { g_bHooking[id] = false g_fNextHook[id] = get_gametime() + get_pcvar_float(g_pCooldown) return FMRES_IGNORED } if (dist > 0.0) { fDir[0] /= dist fDir[1] /= dist fDir[2] /= dist } new Float:pull = get_pcvar_float(g_pPull) static Float:fVel[3] fVel[0] = fDir[0] * pull fVel[1] = fDir[1] * pull fVel[2] = fDir[2] * pull set_pev(id, pev_velocity, fVel) drawBeam(fOrigin, g_fHook[id]) return FMRES_IGNORED } drawBeam(const Float:from[3], const Float:to[3]) { message_begin(MSG_BROADCAST, SVC_TEMPENTITY) write_byte(TE_BEAMPOINTS) write_coord(floatround(from[0])) write_coord(floatround(from[1])) write_coord(floatround(from[2])) write_coord(floatround(to[0])) write_coord(floatround(to[1])) write_coord(floatround(to[2])) write_short(g_iBeamSpr) write_byte(0) write_byte(0) write_byte(1) write_byte(12) write_byte(0) write_byte(200) write_byte(200) write_byte(255) write_byte(200) write_byte(0) message_end() return; }