Remove requirement for server to explicitly generate maps.

This commit is contained in:
Paul Chote
2026-01-03 14:12:39 +00:00
committed by Gustas Kažukauskas
parent 156122f9e3
commit ed1ebd536b
11 changed files with 141 additions and 70 deletions

View File

@@ -60,7 +60,10 @@ namespace OpenRA
{
var preview = modData.MapCache[MapUid];
if (preview.Status != MapStatus.Available && MapGenerationArgs != null)
modData.MapCache.GenerateMap(modData, MapGenerationArgs);
{
preview.UpdateFromGenerationArgs(MapGenerationArgs);
preview.Generate();
}
return preview;
}

View File

@@ -20,7 +20,6 @@ using OpenRA.FileSystem;
using OpenRA.Graphics;
using OpenRA.Primitives;
using OpenRA.Support;
using OpenRA.Traits;
using FS = OpenRA.FileSystem.FileSystem;
namespace OpenRA
@@ -221,45 +220,6 @@ namespace OpenRA
yield return mapPackage;
}
public void GenerateMap(ModData modData, MapGenerationArgs args)
{
var p = previews[args.Uid];
if (p.Class == MapClassification.Generated)
return;
p.UpdateFromGenerationArgs(args);
Task.Run(() =>
{
try
{
var generator = modData.DefaultRules.Actors[SystemActors.EditorWorld]
.TraitInfos<IMapGeneratorInfo>()
.FirstOrDefault(info => info.Type == args.Generator);
if (generator == null)
throw new Exception($"Unknown map generator type {args.Generator}");
var map = generator.Generate(modData, args);
// Uid is generated when the map is saved
map.Save(new ZipFileLoader.ReadWriteZipFile());
if (map.Uid != args.Uid)
throw new InvalidOperationException("Map generation UID mismatch");
Game.RunAfterTick(() => p.UpdateFromMap(map.Package, MapClassification.Generated));
}
catch (Exception e)
{
Log.Write("debug", "Map generation failed with error:");
Log.Write("debug", e);
p.UpdateFromGenerationArgs(null);
}
});
}
public void QueryRemoteMapDetails(string repositoryUrl, IEnumerable<string> uids,
Action<MapPreview> mapDetailsReceived = null, Action<MapPreview> mapQueryFailed = null)
{

View File

@@ -24,10 +24,11 @@ using OpenRA.FileSystem;
using OpenRA.Graphics;
using OpenRA.Primitives;
using OpenRA.Support;
using OpenRA.Traits;
namespace OpenRA
{
public enum MapStatus { Available, Unavailable, Searching, DownloadAvailable, Downloading, DownloadError, Generating }
public enum MapStatus { Available, Unavailable, Searching, DownloadAvailable, Downloading, DownloadError, Generatable, Generating }
// Used for grouping maps in the UI
[Flags]
@@ -467,20 +468,84 @@ namespace OpenRA
{
var newData = innerData.Clone();
newData.Class = MapClassification.Generated;
if (args != null)
newData.Status = MapStatus.Generatable;
newData.Title = args.Title;
newData.Author = args.Author;
newData.TileSet = args.Tileset;
newData.GenerationArgs = args;
newData.MapFormat = Map.CurrentMapFormat;
try
{
newData.Status = MapStatus.Generating;
newData.Title = args.Title;
newData.Author = args.Author;
newData.GenerationArgs = args;
var generator = modData.DefaultRules.Actors[SystemActors.EditorWorld]
.TraitInfos<IMapGeneratorInfo>()
.FirstOrDefault(info => info.Type == args.Generator);
if (generator == null)
throw new Exception($"Unknown map generator type {args.Generator}");
if (!generator.TryGenerateMetadata(modData, args, out var players, out var ruleDefinitions))
throw new Exception("Failed to generate map metadata");
newData.Players = players;
newData.PlayerCount = newData.Players.Players.Count(x => x.Value.Playable);
newData.SetCustomRules(modData, this, ruleDefinitions, null);
// Placeholder to satisfy server-side lint checks
newData.SpawnPoints = Exts.MakeArray(newData.PlayerCount, i => new CPos(i, i)).ToImmutableArray();
}
else
catch (Exception e)
{
Log.Write("debug", "Map generation failed with error:");
Log.Write("debug", e);
newData.Status = MapStatus.Unavailable;
}
lock (syncRoot)
innerData = newData;
}
public void Generate()
{
if (Class != MapClassification.Generated || Status != MapStatus.Generatable)
return;
lock (syncRoot)
innerData.Status = MapStatus.Generating;
Task.Run(() =>
{
try
{
var generator = modData.DefaultRules.Actors[SystemActors.EditorWorld]
.TraitInfos<IMapGeneratorInfo>()
.FirstOrDefault(info => info.Type == GenerationArgs.Generator);
if (generator == null)
throw new Exception($"Unknown map generator type {GenerationArgs.Generator}");
var map = generator.Generate(modData, GenerationArgs);
// Uid is generated when the map is saved
map.Save(new ZipFileLoader.ReadWriteZipFile());
if (map.Uid != GenerationArgs.Uid)
throw new InvalidOperationException("Map generation UID mismatch");
Game.RunAfterTick(() => UpdateFromMap(map.Package, MapClassification.Generated));
}
catch (Exception e)
{
Log.Write("debug", "Map generation failed with error:");
Log.Write("debug", e);
lock (syncRoot)
innerData.Status = MapStatus.Unavailable;
}
});
}
public void BeginRemoteSearch()
{
var newData = innerData.Clone();

View File

@@ -393,7 +393,14 @@ namespace OpenRA.Network
case "GenerateMap":
{
var yaml = new MiniYaml(order.OrderString, MiniYaml.FromString(order.TargetString, order.OrderString));
Game.ModData.MapCache.GenerateMap(Game.ModData, FieldLoader.Load<MapGenerationArgs>(yaml));
var args = FieldLoader.Load<MapGenerationArgs>(yaml);
var preview = Game.ModData.MapCache[args.Uid];
if (preview.Status != MapStatus.Available && preview.Class != MapClassification.Generated)
{
preview.UpdateFromGenerationArgs(args);
preview.Generate();
}
break;
}

View File

@@ -1127,8 +1127,8 @@ namespace OpenRA.Server
var yaml = new MiniYaml(o.OrderString, MiniYaml.FromString(o.TargetString, o.OrderString));
var args = FieldLoader.Load<MapGenerationArgs>(yaml);
var preview = ModData.MapCache[args.Uid];
if (preview.Status != MapStatus.Available)
ModData.MapCache.GenerateMap(ModData, args);
if (preview.Status != MapStatus.Available && preview.Class != MapClassification.Generated)
preview.UpdateFromGenerationArgs(args);
GeneratedMapData = o.TargetString;
DispatchServerOrdersToClients(Order.FromTargetString("GenerateMap", o.TargetString, true));

View File

@@ -659,5 +659,6 @@ namespace OpenRA.Traits
string MapTitle { get; }
Map Generate(ModData modData, MapGenerationArgs args);
bool TryGenerateMetadata(ModData modData, MapGenerationArgs args, out MapPlayers players, out Dictionary<string, MiniYaml> rules);
}
}

View File

@@ -656,23 +656,8 @@ namespace OpenRA.Mods.Common.Server
}
var m = server.ModData.MapCache[s];
if (m.Status == MapStatus.Available || m.Status == MapStatus.DownloadAvailable)
if (m.Status is MapStatus.Available or MapStatus.DownloadAvailable or MapStatus.Generatable or MapStatus.Generating)
SelectMap(m);
else if (m.Class == MapClassification.Generated)
{
if (m.Status == MapStatus.Generating)
{
// Wait up to 5 seconds for the map to be generated
var stopwatch = Stopwatch.StartNew();
while (m.Status == MapStatus.Generating && stopwatch.ElapsedMilliseconds < 5000)
Thread.Sleep(100);
}
if (m.Status == MapStatus.Available)
SelectMap(m);
else
QueryFailed();
}
else if (server.Settings.QueryMapRepository)
{
server.SendFluentMessageTo(conn, SearchingMap);

View File

@@ -953,6 +953,26 @@ namespace OpenRA.Mods.Common.Traits
return map;
}
public bool TryGenerateMetadata(ModData modData, MapGenerationArgs args, out MapPlayers players, out Dictionary<string, MiniYaml> ruleDefinitions)
{
try
{
var playerCount = FieldLoader.GetValue<int>("Players", args.Settings.NodeWithKey("Players").Value.Value);
// Generated maps use the default ruleset
ruleDefinitions = [];
players = new MapPlayers(modData.DefaultRules, playerCount);
return true;
}
catch
{
players = null;
ruleDefinitions = null;
return false;
}
}
public override object Create(ActorInitializer init)
{
return new ClassicMapGenerator(init, this);

View File

@@ -9,6 +9,7 @@
*/
#endregion
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using OpenRA.Mods.Common.MapGenerator;
@@ -93,6 +94,14 @@ namespace OpenRA.Mods.Common.Traits
return map;
}
public bool TryGenerateMetadata(ModData modData, MapGenerationArgs args, out MapPlayers players, out Dictionary<string, MiniYaml> ruleDefinitions)
{
ruleDefinitions = [];
players = new MapPlayers(modData.DefaultRules, 0);
return true;
}
public override object Create(ActorInitializer init)
{
return new ClearMapGenerator(this);

View File

@@ -327,11 +327,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic
if (savePath != null)
{
selectedSave = new GameSave(savePath);
var map = modData.MapCache[selectedSave.GlobalSettings.Map];
if (map.Status != MapStatus.Available && selectedSave.MapGenerationArgs != null)
var preview = modData.MapCache[selectedSave.GlobalSettings.Map];
if (preview.Status != MapStatus.Available && selectedSave.MapGenerationArgs != null)
{
// Add to the MapCache so the server will accept the map
modData.MapCache.GenerateMap(modData, selectedSave.MapGenerationArgs);
preview.UpdateFromGenerationArgs(selectedSave.MapGenerationArgs);
preview.Generate();
}
}
else

View File

@@ -616,6 +616,26 @@ namespace OpenRA.Mods.D2k.Traits
return map;
}
public bool TryGenerateMetadata(ModData modData, MapGenerationArgs args, out MapPlayers players, out Dictionary<string, MiniYaml> ruleDefinitions)
{
try
{
var playerCount = FieldLoader.GetValue<int>("Players", args.Settings.NodeWithKey("Players").Value.Value);
// Generated maps use the default ruleset
ruleDefinitions = [];
players = new MapPlayers(modData.DefaultRules, playerCount);
return true;
}
catch
{
players = null;
ruleDefinitions = null;
return false;
}
}
public override object Create(ActorInitializer init)
{
return new D2kMapGenerator(this);