/*
* CSB SoccerJam
* Copyright (C) 2026 counter-strike-boost.com
*
* A football game mode. A bouncing physics ball is spawned from a per-map data
* file; touching it (or knifing into it) kicks it with an impulse along your aim,
* scaled by how fast you are moving. Two goal zones from the data file are watched
* on a task; putting the ball in the enemy goal scores a point, resets the ball
* to centre and plays a goal sound. The score is shown on a HUD.
*
* Inspired by SoccerJam by Freecode. 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 SoccerJam"
new const VERSION[] = "1.0.0"
new const AUTHOR[] = "counter-strike-boost.com"
#define BALL_CLASS "csb_soccerball"
#define BALL_MODEL "models/w_backpack.mdl"
#define GOAL_SOUND "items/9mmclip1.wav"
new g_pEnabled, g_pPower, g_pGoalRadius
new g_iBall
new Float:g_fBallSpawn[3]
new Float:g_fGoal[2][3]
new bool:g_bReady
new g_iScore[2]
new g_iHudSync
new Float:g_fLastKick
public plugin_init()
{
register_plugin(PLUGIN, VERSION, AUTHOR)
g_pEnabled = register_cvar("csb_soccer_enabled", "1")
g_pPower = register_cvar("csb_soccer_power", "3.5")
g_pGoalRadius = register_cvar("csb_soccer_goalradius", "120.0")
RegisterHam(Ham_Touch, BALL_CLASS, "fwBallTouch")
g_iHudSync = CreateHudSyncObj()
set_task(0.2, "taskGoals", 0, _, _, "b")
}
public plugin_precache()
{
engfunc(EngFunc_PrecacheModel, BALL_MODEL)
precache_sound(GOAL_SOUND)
}
public plugin_cfg()
{
if (loadField())
spawnBall()
}
bool:loadField()
{
new map[32]
get_mapname(map, charsmax(map))
new dir[128], path[192]
get_localinfo("amxx_configsdir", dir, charsmax(dir))
if (!dir[0])
copy(dir, charsmax(dir), "addons/amxmodx/configs")
formatex(path, charsmax(path), "%s/csb_soccer/%s.ini", dir, map)
if (!file_exists(path))
return false
new Float:points[3][3]
new line[96], len, pos = 0, n = 0
while (n < 3 && (pos = read_file(path, pos, line, charsmax(line), len)))
{
trim(line)
if (!line[0] || line[0] == ';' || line[0] == '#')
continue
new sx[16], sy[16], sz[16]
parse(line, sx, charsmax(sx), sy, charsmax(sy), sz, charsmax(sz))
if (!sx[0] || !sy[0] || !sz[0])
continue
points[n][0] = str_to_float(sx)
points[n][1] = str_to_float(sy)
points[n][2] = str_to_float(sz)
n++
}
if (n < 3)
return false
g_fBallSpawn[0] = points[0][0]
g_fBallSpawn[1] = points[0][1]
g_fBallSpawn[2] = points[0][2]
for (new k = 0; k < 3; k++)
{
g_fGoal[0][k] = points[1][k]
g_fGoal[1][k] = points[2][k]
}
return true
}
spawnBall()
{
new ent = engfunc(EngFunc_CreateNamedEntity, engfunc(EngFunc_AllocString, "info_target"))
if (!ent)
return
set_pev(ent, pev_classname, BALL_CLASS)
engfunc(EngFunc_SetModel, ent, BALL_MODEL)
set_pev(ent, pev_movetype, MOVETYPE_BOUNCE)
set_pev(ent, pev_solid, SOLID_BBOX)
set_pev(ent, pev_gravity, 1.0)
static Float:fMin[3], Float:fMax[3]
fMin[0] = -12.0
fMin[1] = -12.0
fMin[2] = -12.0
fMax[0] = 12.0
fMax[1] = 12.0
fMax[2] = 12.0
engfunc(EngFunc_SetSize, ent, fMin, fMax)
g_iBall = ent
g_bReady = true
resetBall()
}
resetBall()
{
if (!pev_valid(g_iBall))
return
engfunc(EngFunc_SetOrigin, g_iBall, g_fBallSpawn)
static Float:fZero[3]
set_pev(g_iBall, pev_velocity, fZero)
}
public fwBallTouch(ball, other)
{
if (!g_bReady || other < 1 || other > 32 || !is_user_alive(other))
return HAM_IGNORED
if (get_gametime() - g_fLastKick < 0.15)
return HAM_IGNORED
g_fLastKick = get_gametime()
static Float:fAng[3], Float:fFwd[3], Float:fVel[3], Float:fPlVel[3]
pev(other, pev_v_angle, fAng)
angle_vector(fAng, ANGLEVECTOR_FORWARD, fFwd)
pev(other, pev_velocity, fPlVel)
new Float:pspeed = floatsqroot(fPlVel[0]*fPlVel[0] + fPlVel[1]*fPlVel[1])
new Float:power = get_pcvar_float(g_pPower)
new Float:kick = (150.0 + pspeed) * power
fVel[0] = fFwd[0] * kick
fVel[1] = fFwd[1] * kick
fVel[2] = 250.0
set_pev(ball, pev_velocity, fVel)
return HAM_IGNORED
}
public taskGoals()
{
if (!g_bReady || !get_pcvar_num(g_pEnabled) || !pev_valid(g_iBall))
return
static Float:fPos[3]
pev(g_iBall, pev_origin, fPos)
new Float:r = get_pcvar_float(g_pGoalRadius)
new Float:r2 = r * r
for (new g = 0; g < 2; g++)
{
new Float:dx = fPos[0] - g_fGoal[g][0]
new Float:dy = fPos[1] - g_fGoal[g][1]
new Float:dz = fPos[2] - g_fGoal[g][2]
if (dx*dx + dy*dy + dz*dz < r2)
{
/* ball in goal g -> the other team scores */
new scorer = g == 0 ? 1 : 0
g_iScore[scorer]++
client_print(0, print_chat, "^x04[Soccer]^x01 GOAL! %s scores! T:%d CT:%d",
scorer == 0 ? "Terrorists" : "CTs", g_iScore[0], g_iScore[1])
new players[32], num
get_players(players, num, "ch")
for (new i = 0; i < num; i++)
client_cmd(players[i], "play %s", GOAL_SOUND)
resetBall()
return
}
}
set_hudmessage(0, 255, 0, 0.02, 0.10, 0, 0.0, 0.3, 0.0, 0.0, -1)
ShowSyncHudMsg(0, g_iHudSync, "SOCCER T:%d CT:%d", g_iScore[0], g_iScore[1])
}