Skip to content

Commit

Permalink
Merge branch 'main' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
Brain-dawg committed Jan 29, 2025
2 parents 21e3412 + 0ca58df commit 50b9880
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 50 deletions.
16 changes: 7 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ The biggest obstacle that obviously cannot be worked around is the lack of a pro
## Installation
- Drop the `mapspawn.nut` file and `mge` folder in your `tf/scripts/vscripts` directory. That's it
- If you know github/git, I recommend cloning the repository to this directory so you're always up to date.
- Alternatively, if you are not using any database integration, you can rename mapspawn.nut to something else and add `script_execute new_filename_here` to your server.cfg

## Don't pack this into your map
- I generally don't recommend you do this, this will receive live regular updates like a sourcemod plugin, and will conflict with server configs
Expand Down Expand Up @@ -40,8 +41,6 @@ The biggest obstacle that obviously cannot be worked around is the lack of a pro
| Database tracking (SQLite) | ⚠️ |
| Custom rulesets | ⚠️ |
| Arbitrary team sizes ||
| Custom spawn ordering ||
| In-Game map configuration tool ||

⚠️KOTH works but the logic is super janky right now, partial cap time can go negative and you can just trade the point back and forth (doesn't contest/pause)

Expand Down Expand Up @@ -153,7 +152,7 @@ All chat commands can be prefixed with any of these characters: `/\.!?`
| help/mgehelp | view the help menu
| stats | view your stats breakdown
| language | change your language, this will read your `cl_language` setting by default
| handicap | set a handicap for yourself, example: `!handicap 100` will set your HP to 100
| handicap | set a handicap for yourself, `!handicap 100` will set your HP to 100
| top5 | NOT IMPLEMENTED
| leaderboard | NOT IMPLEMENTED

Expand All @@ -175,20 +174,19 @@ Support [This github issue](https://github.com/ValveSoftware/Source-1-Games/issu
- No leaderboard support currently.

### Database
- Database tracking uses [VScript-Python Interface](https://github.com/potato-tf/VPI) to send data from vscript to python through the filesystem.
- Database tracking uses [VScript-Python Interface](https://github.com/Mince1844/VPI) to send data from vscript to python through the filesystem.
- Open `tf/scripts/vscripts/mge/cfg/config.nut` and set `ELO_TRACKING_MODE` from 1 to 2
- Open `tf/scripts/vscripts/mge/vpi/vpi.nut` and update line 13, change `return "";` to a random unique string. Treat this like a password.
- Install MySQL (recommended) or SQLite and create a database
- Install Python 3.10 or newer if you don't already have it
- Install MySQL (recommended) or SQLite
- Install the `aiomysql` module
- SQLite uses `aiosqlite`
- Add your database credentials to `tf/scripts/mge_python/vpi.py` (use env vars) and run this script constantly in the background, this is your database connection
- Install the `aiomysql` module, SQLite uses `aiosqlite`
- Add your database credentials to `tf/scripts/vscripts/mge/vpi/vpi.py` (use env vars) and run this script constantly in the background, this is your database connection
- You should create a systemd service for this on linux, or whatever the windows equivalent is
- Check server console for any VPI related errors when you join/leave the server.
- This will automatically create the `mge_playerdata` table in your database

## GitHub Auto Updates
- If configured in `cfg/constants.nut`, the python script that handles database connections will also periodically git clone this repo to a specified directory and shorten the map restart timer.
- If configured in `cfg/config.nut`, the python script that handles database connections will also periodically git clone this repo to a specified directory and shorten the map restart timer.

## NavMesh generation

Expand Down
4 changes: 2 additions & 2 deletions mge/cfg/config.nut
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const DEFAULT_LANGUAGE = "english"
* if this cvar is not set it will simply switch to whatever map is listed next in your `mapcyclefile` *
* make sure you configure your mapcycle.txt correctly so your MGE server doesn't switch to cp_granary or something *
********************************************************************************************************************/
const MAP_RESTART_TIMER = 7200
const MAP_RESTART_TIMER = 7200

/***************************************************************************************************************
* setting this to true will send a retry command to every player and kill worldspawn *
Expand Down Expand Up @@ -79,7 +79,7 @@ const MAX_CLEAR_SPAWN_RETRIES = 10
//announcer
const ENABLE_ANNOUNCER = true //enable announcer quips (first blood airshots etc)
const ANNOUNCER_VOLUME = 0.5 //volume of announcer quips
const KILLSTREAK_ANNOUNCER_INTERVAL = 5 //airshot announcer will play every KILLSTREAK_ANNOUNCER_INTERVAL number of kills
const KILLSTREAK_ANNOUNCER_INTERVAL = 5 //killstreak announcer will play every KILLSTREAK_ANNOUNCER_INTERVAL number of kills

//round misc
const DEFAULT_CDTIME = 3 //default countdown time
Expand Down
28 changes: 25 additions & 3 deletions mge/events.nut
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,19 @@ class MGE_Events

local arena_name = scope.arena_info.name
local ruleset_split = split(params.text, " ")

if (ruleset_split.len() == 1 || !(ruleset_split[1] in special_arenas))
{
MGE_ClientPrint(player, HUD_PRINTTALK, "InvalidRuleset", ruleset_split.len() == 1 ? "" : ruleset_split[1])

local valid_rulesets = ""
foreach (ruleset, _ in special_arenas)
valid_rulesets += format(", %s ", ruleset)

valid_rulesets = valid_rulesets.slice(1)
ClientPrint(player, HUD_PRINTTALK, format("\x07%sValid Rulesets:\x07%s %s", MGE_COLOR_MAIN, MGE_COLOR_SUBJECT, valid_rulesets))
return
}
local ruleset = ruleset_split[1]
local fraglimit = 2 in ruleset_split ? ruleset_split[2].tointeger() : arena.fraglimit / 2

Expand Down Expand Up @@ -442,6 +455,9 @@ class MGE_Events
ValidatePlayerClass(player, params["class"], true)

local scope = player.GetScriptScope()

if (player.IsFakeClient()) return

local arena = scope.arena_info.arena

if (arena.State != AS_FIGHT || arena.IsBBall || arena.IsKoth) return
Expand Down Expand Up @@ -642,7 +658,7 @@ class MGE_Events
params.damage = 0
return false
}
if (arena.IsAllMeat)
if ("IsAllMeat" in arena && arena.IsAllMeat)
{
local weapon = params.weapon
if (!weapon) return
Expand Down Expand Up @@ -680,6 +696,8 @@ class MGE_Events

}

if (victim.IsFakeClient() && !("IsEndif" in arena)) return

if (victim_scope && victim.IsPlayer() && attacker != victim && (arena.IsEndif || arena.IsMidair) && params.damage_type & DMG_BLAST && !(victim.GetFlags() & FL_ONGROUND))
{
local trace_dist = arena.IsEndif ? arena.Endif.height_threshold : arena.IsMidair ? arena.Midair.height_threshold : AIRSHOT_HEIGHT_THRESHOLD
Expand All @@ -704,10 +722,14 @@ class MGE_Events
local attacker = GetPlayerFromUserID(params.attacker)
local attacker_scope = attacker ? attacker.GetScriptScope() : {}

if (arena.State == AS_FIGHT && !arena.IsEndif && !arena.IsMidair)
if (!("State" in arena) && victim.IsFakeClient())
{
victim.ForceChangeTeam(TEAM_SPECTATOR, true)
}
if (!victim.IsFakeClient() && arena.State == AS_FIGHT && !arena.IsEndif && !arena.IsMidair)
{
"damage_taken" in victim_scope.stats ? victim_scope.stats.damage_taken += params.damageamount : victim_scope.stats.damage_taken <- params.damageamount
if (attacker)
if (attacker && !attacker.IsFakeClient())
"damage_dealt" in attacker_scope.stats ? attacker_scope.stats.damage_dealt += params.damageamount : attacker_scope.stats.damage_dealt <- params.damageamount
}

Expand Down
2 changes: 1 addition & 1 deletion mge/functions.nut
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@
{
if (!user_info)
{
think_override = 0.2
think_override = 1
user_info = ["NONE", -INT_MAX]
} else {
think_override = LEADERBOARD_UPDATE_INTERVAL
Expand Down
27 changes: 17 additions & 10 deletions mge/mge.nut
Original file line number Diff line number Diff line change
Expand Up @@ -708,16 +708,22 @@ AddOutput(MGE_TIMER, "OnFinished", "!self", "CallScriptFunction", "MGE_DoChangel

DispatchSpawn(MGE_TIMER)
MGE_TIMER.AcceptInput("Resume", "", null, null)
MGE_TIMER.AcceptInput("ShowInHUD", "1", null, null)
// EntFireByHandle(MGE_TIMER, "ShowInHUD", "1", 1.0, null, null)

MGE_TIMER.ValidateScriptScope()

MGE_TIMER.GetScriptScope().counter <- MAP_RESTART_TIMER
// MGE_TIMER.GetScriptScope().counter <- MAP_RESTART_TIMER
local time_remining_string = "m_flTimeRemaining"
SetPropFloat(MGE_TIMER, time_remining_string, MAP_RESTART_TIMER)
MGE_TIMER.GetScriptScope().TimerThink <- function()
{
local counter = GetPropFloat(MGE_TIMER, time_remaining_string)
counter--
SetPropFloat(MGE_TIMER, time_remaining_string, counter)
if (counter)
{
if (!HLTV_TEST && !(counter % VPI_SERVERINFO_UPDATE_INTERVAL))
if (!(counter % VPI_SERVERINFO_UPDATE_INTERVAL))
{
LocalTime(local_time)
SERVER_DATA.update_time = local_time
Expand All @@ -744,15 +750,16 @@ MGE_TIMER.GetScriptScope().TimerThink <- function()
func = "VPI_MGE_UpdateServerData",
kwargs = SERVER_DATA,
callback = function(response, error) {
if (error)
{
// printl(error)
return 1
if (error)
{
// printl(error)
return 1
}
if (SERVER_DATA.address == 0 && "address" in response)
SERVER_DATA.address = response.address
}
if (SERVER_DATA.address == 0 && "address" in response)
SERVER_DATA.address = response.address
}
})
})
}
}
// printl(counter)
// if (counter < 60 && !(counter % 5))
Expand Down
6 changes: 3 additions & 3 deletions mge/vpi/vpi.nut
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,13 @@ local callbacks = {};
local used_tokens = {};

// Strip hostname of characters other than [a-z0-9_]
local hostname = Convars.GetStr("hostname").tolower();
local hostname = @() Convars.GetStr("hostname").tolower();
try
{
local str = "";
foreach (code in hostname)
foreach(code in hostname())
{
if (code < 33 && !endswith(hostname, "_"))
if (code < 33 && !endswith(hostname(), "_"))
{
str += "_";
continue;
Expand Down
46 changes: 24 additions & 22 deletions mge/vpi/vpi_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,46 +166,48 @@ async def VPI_MGE_PopulateLeaderboard(info, cursor):

return await cursor.fetchall()

default_zeroes = ", ".join(["0"] * (len(player_data_columns.split(",")) - 2))
default_zeroes = ", ".join(["0"] * (len(player_data_columns.split(",")) - 3))
@WrapDB
async def VPI_MGE_ReadWritePlayerStats(info, cursor):
kwargs = info["kwargs"]
query_mode = kwargs["query_mode"]
network_id = kwargs["network_id"]

print("name" in kwargs)
name = kwargs["name"]
name = kwargs["name"] # This should be properly escaped

if network_id == "BOT": return

default_elo = kwargs["default_elo"] if "default_elo" in kwargs else 1000

if (query_mode == "read" or query_mode == 0):

print(COLOR['CYAN'], f"Fetching player data for steam ID {network_id}", COLOR['ENDC'])
await cursor.execute(f"SELECT * FROM mge_playerdata WHERE steam_id = {network_id}")
default_elo = kwargs.get("default_elo", 1000)

if query_mode in ("read", 0):
# Use parameterized query
await cursor.execute("SELECT * FROM mge_playerdata WHERE steam_id = %s", (network_id,))
result = await cursor.fetchall()

# If no record exists, create one with default values
if not result:
print(COLOR['YELLOW'], f"No record exists for steam ID {network_id}, adding...", COLOR['ENDC'])
# await cursor.execute(f"INSERT INTO mge_playerdata ({player_data_columns}) VALUES ({network_id}, {default_elo}, {default_zeroes})")
await cursor.execute(f"INSERT INTO mge_playerdata ({player_data_columns}) VALUES ({network_id}, {default_elo}, {default_zeroes}, {name})")
await cursor.execute(f"SELECT * FROM mge_playerdata WHERE steam_id = {network_id}")
# Parameterized INSERT with proper value ordering
await cursor.execute(
f"INSERT INTO mge_playerdata ({player_data_columns}) VALUES (%s, %s, {default_zeroes}, %s)",
(network_id, default_elo, name)
)
await cursor.execute("SELECT * FROM mge_playerdata WHERE steam_id = %s", (network_id,))
result = await cursor.fetchall()

return result
elif (query_mode == "write" or query_mode == 1):
# Build SET clause from stats dictionary

elif query_mode in ("write", 1):
# Parameterized UPDATE
set_clauses = []
params = []
for key, value in kwargs['stats'].items():
set_clauses.append(f"{key} = {value}")
set_clause = ", ".join(set_clauses)
set_clauses.append(f"{key} = %s")
params.append(value)

print(COLOR['CYAN'], f"Updating player data for steam ID {network_id} with stats: {set_clause}", COLOR['ENDC'])
await cursor.execute(f"UPDATE mge_playerdata SET {set_clause} WHERE steam_id = {network_id}")
return await cursor.fetchall()
params.append(network_id) # Add WHERE clause param
query = f"UPDATE mge_playerdata SET {', '.join(set_clauses)} WHERE steam_id = %s"

await cursor.execute(query, params)
return await cursor.fetchall()

banned_files = [".gitignore", ".git", ".vscode", "README.md", "mge_windows_setup.bat", "config.nut"]
@WrapInterface
async def VPI_MGE_AutoUpdate(info, test=False):
Expand Down

0 comments on commit 50b9880

Please sign in to comment.