/*
* 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)
}