diff --git a/OpenRA.Game/GameInformation.cs b/OpenRA.Game/GameInformation.cs index 6549240915..04e9bbed66 100644 --- a/OpenRA.Game/GameInformation.cs +++ b/OpenRA.Game/GameInformation.cs @@ -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; } diff --git a/OpenRA.Game/Map/MapCache.cs b/OpenRA.Game/Map/MapCache.cs index 95ae11d2b9..35bd71def3 100644 --- a/OpenRA.Game/Map/MapCache.cs +++ b/OpenRA.Game/Map/MapCache.cs @@ -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() - .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 uids, Action mapDetailsReceived = null, Action mapQueryFailed = null) { diff --git a/OpenRA.Game/Map/MapPreview.cs b/OpenRA.Game/Map/MapPreview.cs index ec66d5b38b..d8ff31e4b7 100644 --- a/OpenRA.Game/Map/MapPreview.cs +++ b/OpenRA.Game/Map/MapPreview.cs @@ -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() + .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() + .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(); diff --git a/OpenRA.Game/Network/UnitOrders.cs b/OpenRA.Game/Network/UnitOrders.cs index a354f48b24..db490176f1 100644 --- a/OpenRA.Game/Network/UnitOrders.cs +++ b/OpenRA.Game/Network/UnitOrders.cs @@ -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(yaml)); + var args = FieldLoader.Load(yaml); + var preview = Game.ModData.MapCache[args.Uid]; + if (preview.Status != MapStatus.Available && preview.Class != MapClassification.Generated) + { + preview.UpdateFromGenerationArgs(args); + preview.Generate(); + } + break; } diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs index aae2d2afd4..6942768e53 100644 --- a/OpenRA.Game/Server/Server.cs +++ b/OpenRA.Game/Server/Server.cs @@ -1127,8 +1127,8 @@ namespace OpenRA.Server var yaml = new MiniYaml(o.OrderString, MiniYaml.FromString(o.TargetString, o.OrderString)); var args = FieldLoader.Load(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)); diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index 47d6da7667..6f0d4cb6fe 100644 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -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 rules); } } diff --git a/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs index cd664c591e..4822e83c71 100644 --- a/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs +++ b/OpenRA.Mods.Common/ServerTraits/LobbyCommands.cs @@ -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); diff --git a/OpenRA.Mods.Common/Traits/World/ClassicMapGenerator.cs b/OpenRA.Mods.Common/Traits/World/ClassicMapGenerator.cs index 59ac400d34..6b198002d7 100644 --- a/OpenRA.Mods.Common/Traits/World/ClassicMapGenerator.cs +++ b/OpenRA.Mods.Common/Traits/World/ClassicMapGenerator.cs @@ -953,6 +953,26 @@ namespace OpenRA.Mods.Common.Traits return map; } + public bool TryGenerateMetadata(ModData modData, MapGenerationArgs args, out MapPlayers players, out Dictionary ruleDefinitions) + { + try + { + var playerCount = FieldLoader.GetValue("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); diff --git a/OpenRA.Mods.Common/Traits/World/ClearMapGenerator.cs b/OpenRA.Mods.Common/Traits/World/ClearMapGenerator.cs index a7a3309557..a45f60fd54 100644 --- a/OpenRA.Mods.Common/Traits/World/ClearMapGenerator.cs +++ b/OpenRA.Mods.Common/Traits/World/ClearMapGenerator.cs @@ -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 ruleDefinitions) + { + ruleDefinitions = []; + players = new MapPlayers(modData.DefaultRules, 0); + + return true; + } + public override object Create(ActorInitializer init) { return new ClearMapGenerator(this); diff --git a/OpenRA.Mods.Common/Widgets/Logic/GameSaveBrowserLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/GameSaveBrowserLogic.cs index 73b4601be4..9da4ac7d75 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/GameSaveBrowserLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/GameSaveBrowserLogic.cs @@ -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 diff --git a/OpenRA.Mods.D2k/Traits/World/D2kMapGenerator.cs b/OpenRA.Mods.D2k/Traits/World/D2kMapGenerator.cs index 4630946b1d..b70db838d4 100644 --- a/OpenRA.Mods.D2k/Traits/World/D2kMapGenerator.cs +++ b/OpenRA.Mods.D2k/Traits/World/D2kMapGenerator.cs @@ -616,6 +616,26 @@ namespace OpenRA.Mods.D2k.Traits return map; } + public bool TryGenerateMetadata(ModData modData, MapGenerationArgs args, out MapPlayers players, out Dictionary ruleDefinitions) + { + try + { + var playerCount = FieldLoader.GetValue("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);