/* * CSB Laser Mine * Copyright (C) 2026 counter-strike-boost.com * * Limited, placeable laser mines. A player aims at a wall and types /mine to * stick a mine there; the mine draws a laser beam straight ahead and, on a * think loop, traces that beam. The first living enemy that crosses the beam * takes damage (lethal by cvar). Owners can pick their mine back up with /mine * while looking at it. Each player is limited to csb_mine_limit active mines. * * Inspired by Laser Mine by Fry!. 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 new const PLUGIN[] = "CSB Laser Mine" new const VERSION[] = "1.0.0" new const AUTHOR[] = "counter-strike-boost.com" #define MINE_CLASS "csb_lasermine" #define MAX_RANGE 700.0 new g_pEnabled, g_pLimit, g_pDamage, g_pRange new g_iBeamSpr new g_iMineCount[33] public plugin_init() { register_plugin(PLUGIN, VERSION, AUTHOR) g_pEnabled = register_cvar("csb_mine_enabled", "1") g_pLimit = register_cvar("csb_mine_limit", "2") g_pDamage = register_cvar("csb_mine_damage", "120.0") g_pRange = register_cvar("csb_mine_range", "700.0") register_clcmd("say /mine", "cmdMine") register_clcmd("say_team /mine", "cmdMine") set_task(0.15, "taskMines", 0, _, _, "b") } public plugin_precache() { g_iBeamSpr = precache_model("sprites/laserbeam.spr") } public client_putinserver(id) { g_iMineCount[id] = 0 } public cmdMine(id) { if (!get_pcvar_num(g_pEnabled) || !is_user_alive(id)) return PLUGIN_HANDLED /* if aiming at own mine, pick it up */ new aimEnt, body get_user_aiming(id, aimEnt, body, 100) if (aimEnt > 0 && pev_valid(aimEnt) && isMine(aimEnt) && pev(aimEnt, pev_owner) == id) { engfunc(EngFunc_RemoveEntity, aimEnt) if (g_iMineCount[id] > 0) g_iMineCount[id]-- client_print(id, print_chat, "[CSB] You picked up your laser mine.") return PLUGIN_HANDLED } if (g_iMineCount[id] >= get_pcvar_num(g_pLimit)) { client_print(id, print_chat, "[CSB] You have reached your mine limit (%d).", get_pcvar_num(g_pLimit)) return PLUGIN_HANDLED } 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] * 90.0 fEnd[1] = fEye[1] + fFwd[1] * 90.0 fEnd[2] = fEye[2] + fFwd[2] * 90.0 engfunc(EngFunc_TraceLine, fEye, fEnd, IGNORE_MONSTERS, id, 0) static Float:fHit[3], Float:fFrac get_tr2(0, TR_flFraction, fFrac) get_tr2(0, TR_vecEndPos, fHit) if (fFrac >= 1.0) { client_print(id, print_chat, "[CSB] Aim at a wall to place a mine.") return PLUGIN_HANDLED } new ent = engfunc(EngFunc_CreateNamedEntity, engfunc(EngFunc_AllocString, "info_target")) if (!ent) return PLUGIN_HANDLED set_pev(ent, pev_classname, MINE_CLASS) set_pev(ent, pev_owner, id) engfunc(EngFunc_SetOrigin, ent, fHit) /* store aim direction so the beam always fires the same way */ set_pev(ent, pev_angles, fAim) set_pev(ent, pev_iuser1, get_user_team(id)) g_iMineCount[id]++ client_print(id, print_chat, "[CSB] Laser mine armed.") return PLUGIN_HANDLED } bool:isMine(ent) { static cls[32] pev(ent, pev_classname, cls, charsmax(cls)) return equal(cls, MINE_CLASS) } public taskMines() { new ent = 0 while ((ent = engfunc(EngFunc_FindEntityByString, ent, "classname", MINE_CLASS)) > 0) { mineTick(ent) } } mineTick(ent) { if (!pev_valid(ent)) return static Float:fOrigin[3], Float:fAng[3], Float:fFwd[3], Float:fEnd[3] pev(ent, pev_origin, fOrigin) pev(ent, pev_angles, fAng) angle_vector(fAng, ANGLEVECTOR_FORWARD, fFwd) new Float:range = get_pcvar_float(g_pRange) if (range > MAX_RANGE) range = MAX_RANGE fEnd[0] = fOrigin[0] + fFwd[0] * range fEnd[1] = fOrigin[1] + fFwd[1] * range fEnd[2] = fOrigin[2] + fFwd[2] * range /* draw the beam */ engfunc(EngFunc_TraceLine, fOrigin, fEnd, IGNORE_MONSTERS, ent, 0) static Float:fBeamEnd[3] get_tr2(0, TR_vecEndPos, fBeamEnd) drawBeam(fOrigin, fBeamEnd) /* find the first enemy near the beam line */ new owner = pev(ent, pev_owner) new team = pev(ent, pev_iuser1) new players[32], num get_players(players, num, "a") for (new i = 0; i < num; i++) { new pl = players[i] if (pl == owner) continue if (get_user_team(pl) == team) continue static Float:fPl[3] pev(pl, pev_origin, fPl) if (pointNearSegment(fOrigin, fBeamEnd, fPl)) { new Float:dmg = get_pcvar_float(g_pDamage) new attacker = owner if (!is_user_connected(attacker)) attacker = pl ExecuteHamB(Ham_TakeDamage, pl, ent, attacker, dmg, DMG_BULLET) if (owner >= 1 && owner <= 32 && is_user_connected(owner)) client_print(owner, print_chat, "[CSB] Your laser mine hit an enemy!") engfunc(EngFunc_RemoveEntity, ent) if (owner >= 1 && owner <= 32 && g_iMineCount[owner] > 0) g_iMineCount[owner]-- return } } } drawBeam(const Float:start[3], const Float:end[3]) { message_begin(MSG_BROADCAST, SVC_TEMPENTITY) write_byte(TE_BEAMPOINTS) engfunc(EngFunc_WriteCoord, start[0]) engfunc(EngFunc_WriteCoord, start[1]) engfunc(EngFunc_WriteCoord, start[2]) engfunc(EngFunc_WriteCoord, end[0]) engfunc(EngFunc_WriteCoord, end[1]) engfunc(EngFunc_WriteCoord, end[2]) write_short(g_iBeamSpr) write_byte(0) write_byte(0) write_byte(2) write_byte(6) write_byte(0) write_byte(255) write_byte(0) write_byte(0) write_byte(200) write_byte(0) message_end() } bool:pointNearSegment(const Float:a[3], const Float:b[3], const Float:p[3]) { static Float:ab[3], Float:ap[3] ab[0] = b[0] - a[0] ab[1] = b[1] - a[1] ab[2] = b[2] - a[2] ap[0] = p[0] - a[0] ap[1] = p[1] - a[1] ap[2] = p[2] - a[2] new Float:abLen2 = ab[0] * ab[0] + ab[1] * ab[1] + ab[2] * ab[2] if (abLen2 < 1.0) return false new Float:t = (ap[0] * ab[0] + ap[1] * ab[1] + ap[2] * ab[2]) / abLen2 if (t < 0.0) t = 0.0 else if (t > 1.0) t = 1.0 static Float:closest[3] closest[0] = a[0] + ab[0] * t closest[1] = a[1] + ab[1] * t closest[2] = a[2] + ab[2] * t new Float:dx = p[0] - closest[0] new Float:dy = p[1] - closest[1] new Float:dz = p[2] - closest[2] new Float:dist2 = dx * dx + dy * dy + dz * dz /* within ~32 units of the beam */ return (dist2 < 1024.0) }