Skip to content

Commit 8199d9a

Browse files
committed
Add Mapbase and Garry's Mod 13 support
- Implement MapbaseBridge with Mapbase-specific VScript files, adding support of Entropy Zero games and Raising The Bar: Redux. - Auto-detect Mapbase mods in sourcemods and standalone installations - Implement GMod 13 Support - Add more verbose information. (set verbose=false to verbose=true in source_bridge.py)
1 parent 8efbed4 commit 8199d9a

File tree

5 files changed

+2328
-143
lines changed

5 files changed

+2328
-143
lines changed

README.md

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ An application with Source Engine integration through VScript and Garry's Mod Lu
2222
- **Auto-Spawner**: Automatically spawns cube at random locations on map load
2323
- **Auto-load Scripts**: Scripts automatically load on every map via `mapspawn.nut`
2424

25-
### Lua Features (Garry's Mod 9-12)
25+
### Lua Features (Garry's Mod 9-13)
2626
- **Automatic Addon Installation**: Creates addon structure in `addons/sourcebox/lua/`
2727
- **Picker (Aimbot)**: Silent targeting system (NPCs → Players → Props)
2828
- **Auto-Spawner**: Spawns cube on map load at random locations
@@ -42,14 +42,25 @@ An application with Source Engine integration through VScript and Garry's Mod Lu
4242
| Half-Life 2: Deathmatch ||||| AWP Quit only for CS:S |
4343
| Half-Life 1 Source: Deathmatch ||||| AWP Quit only for CS:S |
4444

45+
### Mapbase Support (Enhanced VScript)
46+
| Game/Mod | VScript | Picker | Auto-Spawn | Notes |
47+
|----------|---------|--------|------------|-------|
48+
| Entropy Zero 1 |||| Standalone game |
49+
| Entropy Zero 2 |||| Standalone game |
50+
| Raising the Bar: Redux |||| Sourcemod |
51+
| Any Mapbase mod |||| Auto-detected |
52+
53+
> [!NOTE]
54+
> Mapbase mods are automatically detected whether they're standalone games or sourcemods. The system checks for a `mapbase` folder in the game directory.
55+
4556
### Garry's Mod Support (Lua Bridge)
4657
| Version | Lua Bridge | Picker | Auto-Spawn | Notes |
4758
|---------|------------|--------|------------|-------|
4859
| GMod 9 |||| Picker little bit buggy |
4960
| GMod 10 |||| Sourcemod |
5061
| GMod 11 |||| Sourcemod |
5162
| GMod 12 |||| Sourcemod |
52-
| GMod 13 | 🚧 | 🚧 | 🚧 | Coming soon |
63+
| GMod 13 | | | | Retail install |
5364

5465
### Console Injection (Legacy Support - Windows Only)
5566
- Works with any Source mod without VScript support or Lua support
@@ -111,13 +122,18 @@ python Sourcebox.py
111122
### Source Engine Integration (VScript)
112123

113124
#### Automatic Setup
114-
1. Launch a supported Source game (TF2, CS:S, DOD:S, HL2:DM, HL1S:DM)
125+
1. Launch a supported Source game (TF2, CS:S, DOD:S, HL2:DM, HL1S:DM) or any Mapbase mod
115126
2. Launch SourceBox
116127
3. Scripts automatically install to:
117128
- `game/scripts/vscripts/python_listener.nut`
118129
- `game/scripts/vscripts/picker.nut`
119130
- `game/scripts/vscripts/auto_spawner.nut`
120-
- `game/scripts/vscripts/mapspawn.nut`
131+
- `game/scripts/vscripts/vscript_server.nut` (Mapbase only)
132+
133+
**For Mapbase mods:**
134+
- Files are stored in `vscript_io/` instead of `scriptdata/`
135+
- Uses Mapbase-specific VScript API
136+
- Auto-loads via `vscript_server.nut`
121137

122138
#### In-Game Usage
123139

@@ -145,15 +161,23 @@ bind kp_plus "script PickerToggle()"
145161
- Only works in CS:S
146162

147163
**Manual Script Loading** (if auto-load fails):
164+
165+
**TF2 branch of Source Engine:**
148166
```
149167
sv_cheats 1
150168
script_execute python_listener
151169
```
152170

171+
**Mapbase:**
172+
```
173+
exec mapbase_default
174+
script_execute vscript_server
175+
```
176+
153177
### Garry's Mod Integration (Lua)
154178

155179
#### Automatic Setup
156-
1. Launch GMod 9 (gmod9), 10 (garrysmod10classic), 11 (garrysmod), or 12 (garrysmod12) (sourcemod version)
180+
1. Launch GMod 9 (gmod9), 10 (garrysmod10classic), 11 (garrysmod), 12 (garrysmod12) (sourcemod version), or retail GMod 13 (GarrysMod/garrysmod)
157181
2. Launch SourceBox
158182
3. Addon automatically installs to:
159183
- `garrysmod/addons/sourcebox/lua/autorun/sourcebox_init.lua`
@@ -289,6 +313,13 @@ sourcebox_spawn props/yourmod/model.mdl 300
289313
- **Format**: JSON with session ID and command counter
290314
- **Rate**: Commands checked every 100ms
291315

316+
### Mapbase VScript Communication
317+
- **Method**: File I/O via `vscript_io/` folder (Mapbase-specific)
318+
- **Files**: `python_command.txt`, `python_response.txt`
319+
- **Format**: JSON with session ID and command counter
320+
- **Rate**: Commands checked every 100ms
321+
- **Detection**: Automatic via `mapbase/` folder presence
322+
292323
### Lua Communication (GMod)
293324
- **Method**: File I/O via `data/` folder
294325
- **Files**: `sourcebox_command.txt`, `sourcebox_response.txt`

Sourcebox.py

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,13 +1016,16 @@ def main():
10161016
print("="*70 + "\n")
10171017
# only install VScript features for supported Source Engine games
10181018
elif bridge.vscripts_path:
1019-
bridge.install_listener()
1020-
bridge.install_picker()
1021-
bridge.install_awp_quit()
1022-
bridge.install_auto_spawner()
1023-
bridge.setup_mapspawn()
1019+
# check if Mapbase - scripts already installed by MapbaseBridge
1020+
if bridge.mapbase_bridge is None:
1021+
bridge.install_listener()
1022+
bridge.install_picker()
1023+
bridge.install_awp_quit()
1024+
bridge.install_auto_spawner()
1025+
bridge.setup_mapspawn()
1026+
10241027
bridge.start_listening()
1025-
1028+
10261029
print("\n" + "="*70)
10271030
print("SETUP COMPLETE - SOURCE ENGINE")
10281031
print("="*70)
@@ -1035,7 +1038,11 @@ def main():
10351038
print(" auto-spawner - spawns 1 cube at random locations on map load")
10361039
print("\n[auto-load] all scripts start automatically on map load")
10371040
print("\n[manual] if needed:")
1038-
print(" script_execute python_listener")
1041+
if bridge.mapbase_bridge:
1042+
print(" exec mapbase_default")
1043+
print(" script_execute vscript_server")
1044+
else:
1045+
print(" script_execute python_listener")
10391046
print("="*70 + "\n")
10401047
else:
10411048
print("\n" + "="*70)
@@ -1044,10 +1051,14 @@ def main():
10441051
print(f"\n[game] {bridge.active_game}")
10451052
print(f"[session] {bridge.session_id}")
10461053
print("\n[features]")
1047-
print(" source game with no vscript! ONLY srcbox spawn is supported!")
1048-
print(" mode: legacy console injection")
1049-
print("\n[usage] click cube in SourceBox to spawn")
1050-
print("="*70 + "\n")
1054+
if PLATFORM == 'Windows' and WINDOWS_API_AVAILABLE:
1055+
print(f" source game with no vscript! ONLY srcbox spawn is supported!")
1056+
print(f" mode: legacy console injection (no VScript)")
1057+
print("="*70 + "\n")
1058+
print("\n[usage] click cube in SourceBox to spawn")
1059+
print("="*70 + "\n")
1060+
else:
1061+
print(f" mode: nothing (no VScript and console injection not available in Linux)")
10511062
except Exception as e:
10521063
print(f"Bridge initialization error: {e}")
10531064
bridge = None
@@ -1181,8 +1192,10 @@ def main():
11811192
if bridge and bridge.active_game:
11821193
try:
11831194
bridge.spawn("props/srcbox/srcbox.mdl", 200)
1184-
time.sleep(0.1)
1185-
bridge.reinstall_awp_outputs()
1195+
time.sleep(0.1)
1196+
# only reinstall AWP outputs for non-Mapbase games
1197+
if bridge.mapbase_bridge is None:
1198+
bridge.reinstall_awp_outputs()
11861199
except Exception as e:
11871200
print(f"Bridge spawn error: {e}")
11881201

@@ -1322,4 +1335,4 @@ def main():
13221335
print("Goodbye!")
13231336

13241337
if __name__ == "__main__":
1325-
main()
1338+
main()

gmod_bridge.py

Lines changed: 77 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""
2-
Garry's Mod Bridge for SourceBox (GMod 9-12)
3-
Automatically installs Lua addon to sourcemods
2+
Garry's Mod Bridge for SourceBox (GMod 9-13)
3+
Automatically installs Lua addon to sourcemods and retail installs
44
"""
55

66
import os
@@ -18,6 +18,15 @@ class GModBridge:
1818
'garrysmod': 'GMod 11',
1919
'garrysmod12': 'GMod 12'
2020
}
21+
22+
# retail gmod 13 install (steamapps/common/GarrysMod/garrysmod)
23+
GMOD_RETAIL = {
24+
'gmod13': {
25+
'name': 'GMod 13',
26+
'install_dir': 'GarrysMod',
27+
'game_dir': 'garrysmod'
28+
}
29+
}
2130

2231
def __init__(self):
2332
self.data_path = None
@@ -131,6 +140,16 @@ def _parse_library_folders_vdf(self, steam_path):
131140
return libraries
132141
except:
133142
return [steam_path]
143+
144+
def _get_retail_gmod_path(self, library_path):
145+
"""return retail gmod13 garrysmod directory if it exists"""
146+
retail_info = self.GMOD_RETAIL['gmod13']
147+
for steamapps_dir in ['steamapps', 'SteamApps']:
148+
game_root = os.path.join(library_path, steamapps_dir, 'common', retail_info['install_dir'])
149+
mod_path = os.path.join(game_root, retail_info['game_dir'])
150+
if os.path.exists(mod_path):
151+
return mod_path
152+
return None
134153

135154
def _detect_gmod(self):
136155
"""detect gmod installation"""
@@ -155,6 +174,7 @@ def _detect_gmod(self):
155174

156175
running_gmod = self._detect_running_gmod()
157176

177+
# first scan sourcemod variants (gmod 9-12)
158178
for library_path in all_libraries:
159179
sourcemods_path = os.path.join(library_path, 'steamapps', 'sourcemods')
160180
if not os.path.exists(sourcemods_path):
@@ -176,13 +196,32 @@ def _detect_gmod(self):
176196
os.makedirs(data_path, exist_ok=True)
177197

178198
if running_gmod and running_gmod == mod_folder:
179-
self._setup_paths(data_path, addon_path, mod_name, is_gmod9)
199+
self._setup_paths(data_path, addon_path, mod_name, is_gmod9, mod_folder)
180200
self._install_lua_addon()
181201
print(f" [found] {mod_name} (RUNNING)")
182202
print(f" [library] {library_path}")
183203
return
184204
elif not self.active_gmod:
185205
print(f" [installed] {mod_name} (in {library_path})")
206+
207+
# then scan retail gmod 13 install
208+
for library_path in all_libraries:
209+
retail_path = self._get_retail_gmod_path(library_path)
210+
if not retail_path:
211+
continue
212+
213+
data_path = os.path.join(retail_path, 'data')
214+
addon_path = os.path.join(retail_path, 'addons', 'sourcebox')
215+
os.makedirs(data_path, exist_ok=True)
216+
217+
if running_gmod in ['gmod13', 'garrysmod']:
218+
self._setup_paths(data_path, addon_path, self.GMOD_RETAIL['gmod13']['name'], False, 'gmod13')
219+
self._install_lua_addon()
220+
print(f" [found] {self.GMOD_RETAIL['gmod13']['name']} (RUNNING)")
221+
print(f" [library] {library_path}")
222+
return
223+
elif not self.active_gmod:
224+
print(f" [installed] {self.GMOD_RETAIL['gmod13']['name']} (in {library_path})")
186225

187226
def _detect_running_gmod(self):
188227
"""detect running gmod"""
@@ -198,21 +237,31 @@ def _detect_running_gmod(self):
198237
continue
199238

200239
cmdline_str = ' '.join(cmdline).lower()
240+
exe_path = proc.info.get('exe') or ''
241+
exe_lower = exe_path.lower()
242+
proc_lower = proc_name.lower()
201243

202-
# check for hl2.exe with -game argument (sourcemods)
203-
if proc_name.lower() == 'hl2.exe':
244+
# hl2 based binaries or gmod specific binaries
245+
if proc_lower in ['hl2.exe', 'hl2_linux', 'gmod.exe', 'gmod', 'gmod64', 'gmod32', 'gmod_linux']:
204246
for i, arg in enumerate(cmdline):
205247
if arg.lower() == '-game' and i + 1 < len(cmdline):
206248
game_arg = cmdline[i + 1].strip('"').lower()
207249

208-
if 'gmod9' in game_arg:
250+
if 'gmod9' in game_arg or 'garrysmod9' in game_arg:
209251
return 'gmod9'
210252
elif 'garrysmod12' in game_arg:
211253
return 'garrysmod12'
212254
elif 'garrysmod10classic' in game_arg:
213255
return 'garrysmod10classic'
214-
elif 'garrysmod' in game_arg:
215-
return 'garrysmod'
256+
elif 'garrysmod' in game_arg:
257+
# differentiate sourcemod vs retail by path hint
258+
if 'sourcemods' in game_arg or 'sourcemods' in exe_lower:
259+
return 'garrysmod'
260+
return 'gmod13'
261+
262+
# fallback: detect retail gmod if binary path clearly in GarrysMod
263+
if 'garrysmod' in exe_lower and 'sourcemods' not in exe_lower:
264+
return 'gmod13'
216265

217266
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
218267
continue
@@ -221,12 +270,13 @@ def _detect_running_gmod(self):
221270

222271
return None
223272

224-
def _setup_paths(self, data_path, addon_path, gmod_name, is_gmod9=False):
273+
def _setup_paths(self, data_path, addon_path, gmod_name, is_gmod9=False, gmod_version=None):
225274
"""setup paths"""
226275
self.data_path = data_path
227276
self.addon_path = addon_path
228277
self.active_gmod = gmod_name
229278
self.is_gmod9 = is_gmod9
279+
self.gmod_version = gmod_version or ('gmod9' if is_gmod9 else None)
230280
self.command_file = os.path.join(data_path, "sourcebox_command.txt")
231281
self.response_file = os.path.join(data_path, "sourcebox_response.txt")
232282

@@ -276,18 +326,30 @@ def _install_lua_addon(self):
276326
sourcebox_path = os.path.join(autorun_path, 'sourcebox')
277327

278328
os.makedirs(sourcebox_path, exist_ok=True)
279-
280-
# write info.txt
281-
info_content = '''sourcebox
329+
# write addon metadata
330+
if self.gmod_version == 'gmod13':
331+
addon_content = '''"AddonInfo"
332+
{
333+
"name" "SourceBox"
334+
"version" "1.0"
335+
"author_name" "SourceBox Team"
336+
"info" "Python bridge for Garry's Mod"
337+
"override" "0"
338+
}
339+
'''
340+
with open(os.path.join(self.addon_path, 'addon.txt'), 'w') as f:
341+
f.write(addon_content)
342+
else:
343+
info_content = '''sourcebox
282344
{
283345
name "SourceBox"
284346
version "1.0"
285347
author "SourceBox Team"
286348
info "Python bridge for Garry's Mod"
287349
}
288350
'''
289-
with open(os.path.join(self.addon_path, 'info.txt'), 'w') as f:
290-
f.write(info_content)
351+
with open(os.path.join(self.addon_path, 'info.txt'), 'w') as f:
352+
f.write(info_content)
291353

292354
# write sourcebox_init.lua
293355
init_content = self._get_init_lua()
@@ -1777,4 +1839,4 @@ def cleanup(self):
17771839
if self.response_file and os.path.exists(self.response_file):
17781840
os.remove(self.response_file)
17791841
except:
1780-
pass
1842+
pass

0 commit comments

Comments
 (0)