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