Skip to content

Commit b8e4fd1

Browse files
authored
Merge pull request #127 from RLBot/custom-maps
Custom map support
2 parents b2866b2 + b198c45 commit b8e4fd1

File tree

6 files changed

+145
-12
lines changed

6 files changed

+145
-12
lines changed

RLBotCS/Conversion/FlatToCommand.cs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -388,17 +388,27 @@ private static string GetOption(string option)
388388
return "";
389389
}
390390

391-
public static string MakeOpenCommand(MatchConfigurationT matchConfig)
391+
public static (string, CustomMap?) MakeOpenCommand(MatchConfigurationT matchConfig)
392392
{
393393
var command = "Open ";
394+
CustomMap? customMap = null;
394395

395396
// Parse game map
396-
// With RLBot v5, GameMap enum is now ignored
397-
// You MUST use GameMapUpk instead
398397
// This is the name of the map file without the extension
399398
// And can also be used to tell the game to load custom maps
400399
if (matchConfig.GameMapUpk != "")
401-
command += matchConfig.GameMapUpk;
400+
{
401+
if (CustomMap.IsCustomMap(matchConfig.GameMapUpk))
402+
{
403+
// load the custom map
404+
customMap = new(matchConfig.GameMapUpk);
405+
command += CustomMap.RL_MAP_KEY;
406+
}
407+
else
408+
{
409+
command += matchConfig.GameMapUpk;
410+
}
411+
}
402412
else
403413
{
404414
command += "Stadium_P";
@@ -421,7 +431,7 @@ public static string MakeOpenCommand(MatchConfigurationT matchConfig)
421431
command += ",Freeplay";
422432

423433
if (matchConfig.Mutators is not { } mutatorSettings)
424-
return command;
434+
return (command, customMap);
425435

426436
// Parse mutator settings
427437
command += GetOption(MapMatchLength(mutatorSettings.MatchLength));
@@ -457,7 +467,7 @@ public static string MakeOpenCommand(MatchConfigurationT matchConfig)
457467
command += GetOption(MapInputRestriction(mutatorSettings.InputRestriction));
458468
command += GetOption(MapScoringRule(mutatorSettings.ScoringRule));
459469

460-
return command;
470+
return (command, customMap);
461471
}
462472

463473
public static string MakeGameSpeedCommand(float gameSpeed) =>

RLBotCS/Main.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
if (args.Length > 0 && args[0] == "--version")
1111
{
1212
Console.WriteLine(
13-
$"RLBotServer v5.beta.7.3\n"
13+
$"RLBotServer v5.beta.7.4\n"
1414
+ $"Bridge {BridgeVersion.Version}\n"
1515
+ $"@ https://www.rlbot.org & https://github.com/RLBot/core"
1616
);

RLBotCS/ManagerTools/CustomMap.cs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using Microsoft.Extensions.Logging;
2+
3+
namespace RLBotCS.ManagerTools;
4+
5+
public class CustomMap
6+
{
7+
private static readonly ILogger Logger = Logging.GetLogger("CustomMap");
8+
9+
public const string RL_MAP_KEY = "Haunted_TrainStation_P";
10+
private const string MAP_SACRIFICE = RL_MAP_KEY + ".upk";
11+
private const string TEMP_MAP_NAME = RL_MAP_KEY + "_copy.upk";
12+
13+
public static bool IsCustomMap(string path)
14+
{
15+
bool isMapPath = path.EndsWith(".upk") || path.EndsWith(".udk");
16+
return isMapPath && File.Exists(path);
17+
}
18+
19+
private static string GetMapsBasePath()
20+
{
21+
// rocketleague/Binaries/Win64/RocketLeague.exe
22+
string? gamePath = LaunchManager.GetRocketLeaguePath();
23+
if (gamePath == null)
24+
{
25+
// throw exception because we shouldn't construct this class before Rocket League has launched
26+
throw new InvalidOperationException("Could not find Rocket League executable");
27+
}
28+
29+
// rocketleague/
30+
string basePath = Path.GetDirectoryName(
31+
Path.GetDirectoryName(Path.GetDirectoryName(gamePath)!)!
32+
)!;
33+
// rocketleague/TAGame/CookedPCConsole
34+
return Path.Combine(basePath, "TAGame", "CookedPCConsole");
35+
}
36+
37+
private string _originalMapPath;
38+
private string _tempMapPath;
39+
40+
public CustomMap(string path)
41+
{
42+
if (!IsCustomMap(path))
43+
{
44+
// throw exception because we shouldn't construct this class with an invalid path
45+
throw new ArgumentException("Provided path is not a valid custom map");
46+
}
47+
48+
string mapsBasePath = GetMapsBasePath();
49+
_originalMapPath = Path.Combine(mapsBasePath, MAP_SACRIFICE);
50+
_tempMapPath = Path.Combine(mapsBasePath, TEMP_MAP_NAME);
51+
52+
Logger.LogInformation($"Custom map detected. Temporarily replacing {RL_MAP_KEY}");
53+
54+
// don't overwrite the original map if it already exists
55+
if (!File.Exists(_tempMapPath))
56+
{
57+
// copy the original map to a temporary file so we can restore it later
58+
File.Copy(_originalMapPath, _tempMapPath, false);
59+
}
60+
61+
// replace the original map with the custom map
62+
File.Copy(path, _originalMapPath, true);
63+
}
64+
65+
public void TryRestoreOriginalMap()
66+
{
67+
if (!File.Exists(_tempMapPath))
68+
return;
69+
70+
File.Copy(_tempMapPath, _originalMapPath, true);
71+
File.Delete(_tempMapPath);
72+
Logger.LogInformation($"Restored original map of {RL_MAP_KEY}");
73+
}
74+
75+
~CustomMap()
76+
{
77+
// Doing this in the destructor to ensure that the original map is restored,
78+
// even if an invalid state occurs elsewhere
79+
TryRestoreOriginalMap();
80+
}
81+
}

RLBotCS/ManagerTools/LaunchManager.cs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@ static class LaunchManager
1919

2020
public static string? GetGameArgs(bool kill)
2121
{
22-
Process[] candidates = Process.GetProcessesByName("RocketLeague");
22+
Process[] candidates = Process.GetProcesses();
2323

2424
foreach (var candidate in candidates)
2525
{
26+
if (!candidate.ProcessName.Contains("RocketLeague"))
27+
continue;
28+
2629
string args = GetProcessArgs(candidate);
2730
if (kill)
2831
candidate.Kill();
@@ -99,8 +102,10 @@ private static string[] GetIdealArgs(int gamePort) =>
99102
"-nomovie",
100103
];
101104

105+
#if WINDOWS
102106
private static List<string> ParseCommand(string command)
103107
{
108+
// Only works on Windows due to exes on Linux running under Wine
104109
var parts = new List<string>();
105110
var regex = new Regex(@"(?<match>[\""].+?[\""]|[^ ]+)");
106111
var matches = regex.Matches(command);
@@ -112,6 +117,7 @@ private static List<string> ParseCommand(string command)
112117

113118
return parts;
114119
}
120+
#endif
115121

116122
private static Process RunCommandInShell(string command)
117123
{
@@ -389,6 +395,27 @@ int gamePort
389395
#endif
390396
}
391397

398+
public static string? GetRocketLeaguePath()
399+
{
400+
// Assumes the game has already been launched
401+
string? args = GetGameArgs(false);
402+
if (args is null)
403+
return null;
404+
405+
string directGamePath;
406+
407+
#if WINDOWS
408+
directGamePath = ParseCommand(args)[0];
409+
#else
410+
// On Linux, Rocket League is running under Wine so args is something like
411+
// Z:\home\username\.steam\debian-installation\steamapps\common\rocketleague\Binaries\Win64\RocketLeague.exe-rlbotRLBot_ControllerURL=127.0.0.1:23233RLBot_PacketSendRate=240-nomovie
412+
// and we must get the real path to RocketLeague.exe from this
413+
directGamePath = args.Remove(0, 2).Split("-rlbot")[0].Replace("\\", "/");
414+
#endif
415+
416+
return directGamePath;
417+
}
418+
392419
public static bool IsRocketLeagueRunning() =>
393420
Process
394421
.GetProcesses()

RLBotCS/ManagerTools/MatchStarter.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ class MatchStarter(int gamePort, int rlbotSocketsPort)
2323

2424
private Dictionary<string, string> _hivemindNameMap = new();
2525

26+
/// <summary>
27+
/// If this value is not null,
28+
/// then a custom map is being loaded.
29+
/// </summary>
30+
private CustomMap? _customMap;
31+
2632
public readonly AgentMapping AgentMapping = new();
2733

2834
public bool HasSpawnedCars { get; private set; }
@@ -107,6 +113,11 @@ public void OnMapSpawn(string mapName, PlayerSpawner spawner)
107113
{
108114
Logger.LogInformation("Got map info for " + mapName);
109115
HasSpawnedMap = true;
116+
if (_customMap is not null)
117+
{
118+
_customMap.TryRestoreOriginalMap();
119+
_customMap = null;
120+
}
110121

111122
if (_deferredMatchConfig is { } matchConfig)
112123
{
@@ -246,7 +257,9 @@ private void LoadMatch(MatchConfigurationT matchConfig, PlayerSpawner spawner)
246257
_matchConfig = null;
247258
_deferredMatchConfig = matchConfig;
248259

249-
var cmd = spawner.SpawnMap(matchConfig);
260+
var (cmd, customMap) = spawner.SpawnMap(matchConfig);
261+
_customMap = customMap;
262+
250263
Logger.LogInformation($"Loading map with command: {cmd}");
251264
}
252265
else

RLBotCS/ManagerTools/PlayerSpawner.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,14 @@ public void DespawnPlayers(List<int> playerIds)
113113
}
114114
}
115115

116-
public string SpawnMap(MatchConfigurationT matchConfig)
116+
public (string, CustomMap?) SpawnMap(MatchConfigurationT matchConfig)
117117
{
118-
string loadMapCommand = FlatToCommand.MakeOpenCommand(matchConfig);
118+
(string loadMapCommand, CustomMap? customMap) = FlatToCommand.MakeOpenCommand(
119+
matchConfig
120+
);
119121
spawnCommandQueue.AddConsoleCommand(loadMapCommand);
120122
spawnCommandQueue.Flush();
121-
return loadMapCommand;
123+
return (loadMapCommand, customMap);
122124
}
123125

124126
public void Flush()

0 commit comments

Comments
 (0)