diff --git a/OpenRA.Game/FileSystem/ZipFile.cs b/OpenRA.Game/FileSystem/ZipFile.cs
index 39dc6ec3b1..e7140d00b5 100644
--- a/OpenRA.Game/FileSystem/ZipFile.cs
+++ b/OpenRA.Game/FileSystem/ZipFile.cs
@@ -158,16 +158,6 @@ namespace OpenRA.FileSystem
pkg.CommitUpdate();
Commit();
}
-
- public static ReadWriteZipFile FromBase64String(string data)
- {
- return new ReadWriteZipFile(Convert.FromBase64String(data));
- }
-
- public string ToBase64String()
- {
- return Convert.ToBase64String(pkgStream.ToArray());
- }
}
sealed class ZipFolder : IReadOnlyPackage
diff --git a/OpenRA.Game/GameInformation.cs b/OpenRA.Game/GameInformation.cs
index b200bd962b..6549240915 100644
--- a/OpenRA.Game/GameInformation.cs
+++ b/OpenRA.Game/GameInformation.cs
@@ -13,7 +13,6 @@ using System;
using System.Collections.Frozen;
using System.Collections.Generic;
using System.Linq;
-using OpenRA.FileSystem;
using OpenRA.Network;
using OpenRA.Primitives;
@@ -29,7 +28,6 @@ namespace OpenRA
public string MapUid;
public string MapTitle;
- public string MapData;
public int FinalGameTick;
/// Game start timestamp (when the recoding started).
@@ -44,24 +42,12 @@ namespace OpenRA
public IList Players { get; }
public FrozenSet DisabledSpawnPoints = FrozenSet.Empty;
- public MapPreview MapPreview
- {
- get
- {
- var preview = Game.ModData.MapCache[MapUid];
- if (preview.Status != MapStatus.Available && MapData != null)
- {
- var package = ZipFileLoader.ReadWriteZipFile.FromBase64String(MapData);
- preview.UpdateFromMap(package, MapClassification.Generated);
- }
-
- return preview;
- }
- }
-
public IEnumerable HumanPlayers { get { return Players.Where(p => p.IsHuman); } }
public bool IsSinglePlayer => HumanPlayers.Count() == 1;
+ [FieldLoader.Ignore]
+ public MapGenerationArgs MapGenerationArgs;
+
readonly Dictionary playersByRuntime;
public GameInformation()
@@ -70,6 +56,15 @@ namespace OpenRA
playersByRuntime = [];
}
+ public MapPreview GetMapPreview(ModData modData)
+ {
+ var preview = modData.MapCache[MapUid];
+ if (preview.Status != MapStatus.Available && MapGenerationArgs != null)
+ modData.MapCache.GenerateMap(modData, MapGenerationArgs);
+
+ return preview;
+ }
+
public static GameInformation Deserialize(string data, string path)
{
try
@@ -90,6 +85,10 @@ namespace OpenRA
case "Player":
info.Players.Add(FieldLoader.Load(node.Value));
break;
+
+ case "MapGenerationArgs":
+ info.MapGenerationArgs = FieldLoader.Load(node.Value);
+ break;
}
}
@@ -112,6 +111,9 @@ namespace OpenRA
for (var i = 0; i < Players.Count; i++)
nodes.Add(new MiniYamlNode($"Player@{i}", FieldSaver.Save(Players[i])));
+ if (MapGenerationArgs != null)
+ nodes.Add(new MiniYamlNode("MapGenerationArgs", new MiniYaml("", MiniYaml.FromString(MapGenerationArgs.Serialize(), "MapGenerationArgs"))));
+
return nodes.WriteToString();
}
diff --git a/OpenRA.Game/Map/MapPreview.cs b/OpenRA.Game/Map/MapPreview.cs
index 6f9aec22b2..ec66d5b38b 100644
--- a/OpenRA.Game/Map/MapPreview.cs
+++ b/OpenRA.Game/Map/MapPreview.cs
@@ -84,6 +84,7 @@ namespace OpenRA
public MapClassification Class;
public MapVisibility Visibility;
public DateTime ModifiedDate;
+ public MapGenerationArgs GenerationArgs;
public MiniYaml RuleDefinitions;
public MiniYaml WeaponDefinitions;
@@ -219,19 +220,6 @@ namespace OpenRA
return new Map(modData, package);
}
- public string ToBase64String()
- {
- LoadPackage();
- if (package is not ZipFileLoader.ReadWriteZipFile p)
- {
- var map = new Map(modData, package);
- p = new ZipFileLoader.ReadWriteZipFile();
- map.Save(p);
- }
-
- return p.ToBase64String();
- }
-
IReadOnlyPackage parentPackage;
volatile InnerData innerData;
@@ -259,6 +247,8 @@ namespace OpenRA
public ActorInfo PlayerActorInfo => innerData.PlayerActorInfo;
public DateTime ModifiedDate => innerData.ModifiedDate;
+ public MapGenerationArgs GenerationArgs => innerData.GenerationArgs;
+
public long DownloadBytes { get; private set; }
public int DownloadPercentage { get; private set; }
@@ -482,6 +472,7 @@ namespace OpenRA
newData.Status = MapStatus.Generating;
newData.Title = args.Title;
newData.Author = args.Author;
+ newData.GenerationArgs = args;
}
else
newData.Status = MapStatus.Unavailable;
diff --git a/OpenRA.Game/Network/GameSave.cs b/OpenRA.Game/Network/GameSave.cs
index d4c58f940e..d1a9b6889c 100644
--- a/OpenRA.Game/Network/GameSave.cs
+++ b/OpenRA.Game/Network/GameSave.cs
@@ -93,7 +93,7 @@ namespace OpenRA.Network
public Dictionary Slots { get; private set; }
public Dictionary SlotClients { get; private set; }
public Dictionary TraitData = [];
- public string MapData;
+ public MapGenerationArgs MapGenerationArgs;
// Set on game start
int[] clientsBySlotIndex = [];
@@ -142,7 +142,9 @@ namespace OpenRA.Network
SlotClients.Add(slotClient.Slot, slotClient);
}
- MapData = rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength);
+ var mapGenerationArgs = rs.ReadLengthPrefixedString(Encoding.UTF8, Connection.MaxOrderLength);
+ if (!string.IsNullOrEmpty(mapGenerationArgs))
+ MapGenerationArgs = FieldLoader.Load(new MiniYaml("", MiniYaml.FromString(mapGenerationArgs, $"{filepath}:mapGenerationArgs")));
if (rs.Position != traitDataOffset || rs.ReadInt32() != TraitDataMarker)
throw new InvalidDataException("Invalid orasav file");
@@ -159,7 +161,7 @@ namespace OpenRA.Network
public void StartGame(Session lobbyInfo, MapPreview map)
{
if (map.Class == MapClassification.Generated)
- MapData = map.ToBase64String();
+ MapGenerationArgs = map.GenerationArgs;
// Game orders are mapped from a client index to the slot that they occupy
// Orders from spectators are ignored, which is not a problem in practice
@@ -312,7 +314,7 @@ namespace OpenRA.Network
.ToList();
file.WriteLengthPrefixedString(Encoding.UTF8, slotClientNodes.WriteToString());
- file.WriteLengthPrefixedString(Encoding.UTF8, MapData);
+ file.WriteLengthPrefixedString(Encoding.UTF8, MapGenerationArgs?.Serialize() ?? string.Empty);
var traitDataOffset = file.Length;
file.Write(TraitDataMarker);
diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs
index 778279613b..aae2d2afd4 100644
--- a/OpenRA.Game/Server/Server.cs
+++ b/OpenRA.Game/Server/Server.cs
@@ -1356,7 +1356,7 @@ namespace OpenRA.Server
};
if (Map.Class == MapClassification.Generated)
- gameInfo.MapData = Map.ToBase64String();
+ gameInfo.MapGenerationArgs = Map.GenerationArgs;
// Replay metadata should only include the playable players
foreach (var p in worldPlayers)
diff --git a/OpenRA.Game/World.cs b/OpenRA.Game/World.cs
index fb5d443cd3..6ead128bc9 100644
--- a/OpenRA.Game/World.cs
+++ b/OpenRA.Game/World.cs
@@ -228,7 +228,7 @@ namespace OpenRA
var preview = modData.MapCache[Map.Uid];
if (preview.Class == MapClassification.Generated)
- gameInfo.MapData = preview.ToBase64String();
+ gameInfo.MapGenerationArgs = preview.GenerationArgs;
RulesContainTemporaryBlocker = Map.Rules.Actors.Any(a => a.Value.HasTraitInfo());
gameSettings = GetSettings();
diff --git a/OpenRA.Mods.Common/Widgets/Logic/GameSaveBrowserLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/GameSaveBrowserLogic.cs
index b886e3fee6..eb35fb41e9 100644
--- a/OpenRA.Mods.Common/Widgets/Logic/GameSaveBrowserLogic.cs
+++ b/OpenRA.Mods.Common/Widgets/Logic/GameSaveBrowserLogic.cs
@@ -14,7 +14,6 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
-using OpenRA.FileSystem;
using OpenRA.Network;
using OpenRA.Widgets;
@@ -72,7 +71,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
readonly string baseSavePath;
readonly string defaultSaveFilename;
- string selectedSave;
+ string selectedPath;
+ GameSave selectedSave;
[ObjectCreator.UseCtor]
public GameSaveBrowserLogic(Widget widget, ModData modData, Action onExit, Action onStart, bool isSavePanel, World world)
@@ -127,7 +127,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
panel.Get("LOAD_TITLE").IsVisible = () => true;
var loadButton = panel.Get("LOAD_BUTTON");
loadButton.IsVisible = () => true;
- loadButton.IsDisabled = () => selectedSave == null;
+ loadButton.IsDisabled = () => selectedSave == null || modData.MapCache[selectedSave.GlobalSettings.Map].Status != MapStatus.Available;
loadButton.OnClick = Load;
}
@@ -138,7 +138,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
renameButton.IsDisabled = () => selectedSave == null;
renameButton.OnClick = () =>
{
- var initialName = Path.GetFileNameWithoutExtension(selectedSave);
+ var initialName = Path.GetFileNameWithoutExtension(selectedPath);
var invalidChars = Path.GetInvalidFileNameChars();
ConfirmationDialogs.TextInputPrompt(modData,
@@ -174,10 +174,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic
ConfirmationDialogs.ButtonPrompt(modData,
title: DeleteSaveTitle,
text: DeleteSavePrompt,
- textArguments: ["save", Path.GetFileNameWithoutExtension(selectedSave)],
+ textArguments: ["save", Path.GetFileNameWithoutExtension(selectedPath)],
onConfirm: () =>
{
- Delete(selectedSave);
+ Delete(selectedPath);
if (games.Count == 0 && !isSavePanel)
{
@@ -239,7 +239,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var item = gameTemplate.Clone();
item.ItemKey = savePath;
item.IsVisible = () => true;
- item.IsSelected = () => selectedSave == item.ItemKey;
+ item.IsSelected = () => selectedPath == item.ItemKey;
item.OnClick = () => Select(item.ItemKey);
if (isSavePanel)
@@ -276,8 +276,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
item.Get("TITLE").GetText = () => newName;
}
- if (selectedSave == oldPath)
- selectedSave = newPath;
+ if (selectedPath == oldPath)
+ selectedPath = newPath;
}
catch (Exception ex)
{
@@ -299,7 +299,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
return;
}
- if (savePath == selectedSave)
+ if (savePath == selectedPath)
Select(null);
var item = gameList.Children
@@ -322,7 +322,16 @@ namespace OpenRA.Mods.Common.Widgets.Logic
void Select(string savePath)
{
- selectedSave = savePath;
+ selectedPath = savePath;
+ selectedSave = new GameSave(savePath);
+
+ var map = modData.MapCache[selectedSave.GlobalSettings.Map];
+ if (map.Status != MapStatus.Available && selectedSave.MapGenerationArgs != null)
+ {
+ // Add to the MapCache so the server will accept the map
+ modData.MapCache.GenerateMap(modData, selectedSave.MapGenerationArgs);
+ }
+
if (isSavePanel)
{
saveTextField.Text = savePath == null ? defaultSaveFilename : Path.GetFileNameWithoutExtension(savePath);
@@ -335,17 +344,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
if (selectedSave == null)
return;
- // Parse the save to find the map UID
- var save = new GameSave(selectedSave);
-
- var map = Game.ModData.MapCache[save.GlobalSettings.Map];
- if (map.Status != MapStatus.Available && save.MapData != null)
- {
- // Add to the MapCache so the server will accept the map
- var package = ZipFileLoader.ReadWriteZipFile.FromBase64String(save.MapData);
- map.UpdateFromMap(package, MapClassification.Generated);
- }
-
+ var map = modData.MapCache[selectedSave.GlobalSettings.Map];
if (map.Status != MapStatus.Available)
return;
@@ -353,7 +352,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var orders = new List()
{
- Order.FromTargetString("LoadGameSave", Path.GetFileName(selectedSave), true),
+ Order.FromTargetString("LoadGameSave", Path.GetFileName(selectedPath), true),
Order.Command($"state {Session.ClientState.Ready}")
};
diff --git a/OpenRA.Mods.Common/Widgets/Logic/MapChooserLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/MapChooserLogic.cs
index f0d809f6c2..91b937e7a8 100644
--- a/OpenRA.Mods.Common/Widgets/Logic/MapChooserLogic.cs
+++ b/OpenRA.Mods.Common/Widgets/Logic/MapChooserLogic.cs
@@ -163,6 +163,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var p = modData.MapCache[generatedMapArgs.Uid];
if (p.Status != MapStatus.Available && package is ZipFileLoader.ReadWriteZipFile zipPackage)
{
+ p.UpdateFromGenerationArgs(generatedMapArgs);
p.UpdateFromMap(zipPackage, MapClassification.Generated);
// UpdateFromMap took ownership of the package.
diff --git a/OpenRA.Mods.Common/Widgets/Logic/ReplayBrowserLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/ReplayBrowserLogic.cs
index 5e4c709c80..74589741f5 100644
--- a/OpenRA.Mods.Common/Widgets/Logic/ReplayBrowserLogic.cs
+++ b/OpenRA.Mods.Common/Widgets/Logic/ReplayBrowserLogic.cs
@@ -706,7 +706,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
void SelectReplay(ReplayMetadata replay)
{
selectedReplay = replay;
- map = selectedReplay != null ? selectedReplay.GameInfo.MapPreview : MapCache.UnknownMap;
+ map = selectedReplay != null ? selectedReplay.GameInfo.GetMapPreview(modData) : MapCache.UnknownMap;
if (replay == null)
return;
diff --git a/OpenRA.Mods.Common/Widgets/Logic/ReplayUtils.cs b/OpenRA.Mods.Common/Widgets/Logic/ReplayUtils.cs
index 74fabe43ef..9c19661de1 100644
--- a/OpenRA.Mods.Common/Widgets/Logic/ReplayUtils.cs
+++ b/OpenRA.Mods.Common/Widgets/Logic/ReplayUtils.cs
@@ -63,7 +63,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
if (Game.Mods[mod].Metadata.Version != version)
return IncompatibleReplayDialog(modData, onCancel, IncompatibleVersion, "version", version);
- if (replayMeta.GameInfo.MapPreview.Status != MapStatus.Available)
+ if (replayMeta.GameInfo.GetMapPreview(modData).Status != MapStatus.Available)
return IncompatibleReplayDialog(modData, onCancel, UnvailableMap, "map", replayMeta.GameInfo.MapUid);
return true;