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