/* * CSB Portal Gun * Copyright (C) 2026 counter-strike-boost.com * * A two-portal teleporter. Bind a key to +portalblue and one to +portalorange; * each fires a trace from your aim and drops that colour portal on the surface * you hit. When both of your portals exist, walking into one teleports you to * the other while keeping your velocity, so you shoot out the far side. Portals * are per-player, marked with a coloured sprite, and cleared at round end. * * 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 Portal Gun" new const VERSION[] = "1.0.0" new const AUTHOR[] = "counter-strike-boost.com" new g_pEnabled, g_pRadius new bool:g_bHas[33][2] new Float:g_fPortal[33][2][3] new Float:g_fTpCooldown[33] new g_iSpr public plugin_init() { register_plugin(PLUGIN, VERSION, AUTHOR) g_pEnabled = register_cvar("csb_portal_enabled", "1") g_pRadius = register_cvar("csb_portal_radius", "48.0") register_clcmd("+portalblue", "cmdBlue") register_clcmd("+portalorange", "cmdOrange") register_logevent("eventRoundStart", 2, "1=Round_Start") set_task(0.1, "taskPortals", 0, _, _, "b") } public plugin_precache() { g_iSpr = precache_model("sprites/laserbeam.spr") } public client_putinserver(id) { resetPortals(id) } resetPortals(id) { g_bHas[id][0] = false g_bHas[id][1] = false g_fTpCooldown[id] = 0.0 } public eventRoundStart() { for (new id = 1; id <= 32; id++) resetPortals(id) } public cmdBlue(id) { placePortal(id, 0) return PLUGIN_HANDLED } public cmdOrange(id) { placePortal(id, 1) return PLUGIN_HANDLED } placePortal(id, slot) { if (!get_pcvar_num(g_pEnabled) || !is_user_alive(id)) return static Float:fEye[3], Float:fOfs[3], Float:fAim[3], Float:fFwd[3], Float:fEnd[3] pev(id, pev_origin, fEye) pev(id, pev_view_ofs, fOfs) fEye[0] += fOfs[0] fEye[1] += fOfs[1] fEye[2] += fOfs[2] pev(id, pev_v_angle, fAim) angle_vector(fAim, ANGLEVECTOR_FORWARD, fFwd) fEnd[0] = fEye[0] + fFwd[0] * 8192.0 fEnd[1] = fEye[1] + fFwd[1] * 8192.0 fEnd[2] = fEye[2] + fFwd[2] * 8192.0 engfunc(EngFunc_TraceLine, fEye, fEnd, IGNORE_MONSTERS, id, 0) static Float:fHit[3], Float:fNormal[3], Float:fFrac get_tr2(0, TR_flFraction, fFrac) get_tr2(0, TR_vecEndPos, fHit) get_tr2(0, TR_vecPlaneNormal, fNormal) if (fFrac >= 1.0) { client_print(id, print_chat, "[CSB] No surface in range for the portal.") return } /* nudge the portal off the surface so the player can step into it */ fHit[0] += fNormal[0] * 20.0 fHit[1] += fNormal[1] * 20.0 fHit[2] += fNormal[2] * 20.0 g_fPortal[id][slot][0] = fHit[0] g_fPortal[id][slot][1] = fHit[1] g_fPortal[id][slot][2] = fHit[2] g_bHas[id][slot] = true client_print(id, print_chat, "[CSB] %s portal placed.", slot == 0 ? "Blue" : "Orange") } public taskPortals() { new players[32], num get_players(players, num, "a") new Float:radius = get_pcvar_float(g_pRadius) new Float:r2 = radius * radius new Float:now = get_gametime() for (new i = 0; i < num; i++) { new id = players[i] /* draw whichever portals exist */ if (g_bHas[id][0]) drawPortal(g_fPortal[id][0], 0, 90, 255) if (g_bHas[id][1]) drawPortal(g_fPortal[id][1], 255, 140, 0) if (!g_bHas[id][0] || !g_bHas[id][1]) continue if (now < g_fTpCooldown[id]) continue static Float:fMe[3] pev(id, pev_origin, fMe) if (dist2(fMe, g_fPortal[id][0]) < r2) { teleport(id, g_fPortal[id][1]) g_fTpCooldown[id] = now + 1.0 } else if (dist2(fMe, g_fPortal[id][1]) < r2) { teleport(id, g_fPortal[id][0]) g_fTpCooldown[id] = now + 1.0 } } } teleport(id, const Float:dest[3]) { static Float:fVel[3] pev(id, pev_velocity, fVel) engfunc(EngFunc_SetOrigin, id, dest) set_pev(id, pev_velocity, fVel) /* teleport sparkle */ message_begin(MSG_PVS, SVC_TEMPENTITY, dest) write_byte(TE_TELEPORT) engfunc(EngFunc_WriteCoord, dest[0]) engfunc(EngFunc_WriteCoord, dest[1]) engfunc(EngFunc_WriteCoord, dest[2]) message_end() } drawPortal(const Float:pos[3], r, g, b) { static Float:top[3] top[0] = pos[0] top[1] = pos[1] top[2] = pos[2] + 40.0 message_begin(MSG_BROADCAST, SVC_TEMPENTITY) write_byte(TE_BEAMPOINTS) engfunc(EngFunc_WriteCoord, pos[0]) engfunc(EngFunc_WriteCoord, pos[1]) engfunc(EngFunc_WriteCoord, pos[2] - 40.0) engfunc(EngFunc_WriteCoord, top[0]) engfunc(EngFunc_WriteCoord, top[1]) engfunc(EngFunc_WriteCoord, top[2]) write_short(g_iSpr) write_byte(0) write_byte(0) write_byte(2) write_byte(30) write_byte(0) write_byte(r) write_byte(g) write_byte(b) write_byte(200) write_byte(0) message_end() } Float:dist2(const Float:a[3], const Float:b[3]) { new Float:dx = a[0] - b[0] new Float:dy = a[1] - b[1] new Float:dz = a[2] - b[2] return dx * dx + dy * dy + dz * dz }