Rework MapPreview custom rule handling.
The previous asynchronous approach did not work particularly well, leading to large janks when switching to custom maps or opening the mission browser. This commit introduces two key changes: * Rule loading for WorldActorInfo and PlayerActorInfo is made synchronous, in preparation for the next commit which will significantly optimize this path. * The full ruleset loading, which is required for map validation, is moved to the server-side and managed by a new ServerMapStatusCache. The previous syntax check is expanded to include the ability to run lint tests.
This commit is contained in:
@@ -38,9 +38,6 @@ namespace OpenRA
|
|||||||
Remote = 4
|
Remote = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used for verifying map availability in the lobby
|
|
||||||
public enum MapRuleStatus { Unknown, Cached, Invalid }
|
|
||||||
|
|
||||||
[SuppressMessage("StyleCop.CSharp.NamingRules",
|
[SuppressMessage("StyleCop.CSharp.NamingRules",
|
||||||
"SA1307:AccessibleFieldsMustBeginWithUpperCaseLetter",
|
"SA1307:AccessibleFieldsMustBeginWithUpperCaseLetter",
|
||||||
Justification = "Fields names must match the with the remote API.")]
|
Justification = "Fields names must match the with the remote API.")]
|
||||||
@@ -85,45 +82,50 @@ namespace OpenRA
|
|||||||
public MapClassification Class;
|
public MapClassification Class;
|
||||||
public MapVisibility Visibility;
|
public MapVisibility Visibility;
|
||||||
|
|
||||||
Lazy<Ruleset> rules;
|
public MiniYaml RuleDefinitions;
|
||||||
public bool InvalidCustomRules { get; private set; }
|
public MiniYaml WeaponDefinitions;
|
||||||
public bool DefinesUnsafeCustomRules { get; private set; }
|
public MiniYaml VoiceDefinitions;
|
||||||
public bool RulesLoaded { get; private set; }
|
public MiniYaml MusicDefinitions;
|
||||||
|
public MiniYaml NotificationDefinitions;
|
||||||
|
public MiniYaml SequenceDefinitions;
|
||||||
|
public MiniYaml ModelSequenceDefinitions;
|
||||||
|
|
||||||
public ActorInfo WorldActorInfo => rules?.Value.Actors[SystemActors.World];
|
public ActorInfo WorldActorInfo { get; private set; }
|
||||||
public ActorInfo PlayerActorInfo => rules?.Value.Actors[SystemActors.Player];
|
public ActorInfo PlayerActorInfo { get; private set; }
|
||||||
|
|
||||||
public void SetRulesetGenerator(ModData modData, Func<(Ruleset Ruleset, bool DefinesUnsafeCustomRules)> generator)
|
static MiniYaml LoadRuleSection(Dictionary<string, MiniYaml> yaml, string section)
|
||||||
{
|
{
|
||||||
InvalidCustomRules = false;
|
if (!yaml.TryGetValue(section, out var node))
|
||||||
RulesLoaded = false;
|
return null;
|
||||||
DefinesUnsafeCustomRules = false;
|
|
||||||
|
|
||||||
// Note: multiple threads may try to access the value at the same time
|
return node;
|
||||||
// We rely on the thread-safety guarantees given by Lazy<T> to prevent race conitions.
|
}
|
||||||
// If you're thinking about replacing this, then you must be careful to keep this safe.
|
|
||||||
rules = Exts.Lazy(() =>
|
public void SetCustomRules(ModData modData, IReadOnlyFileSystem fileSystem, Dictionary<string, MiniYaml> yaml)
|
||||||
|
{
|
||||||
|
RuleDefinitions = LoadRuleSection(yaml, "Rules");
|
||||||
|
WeaponDefinitions = LoadRuleSection(yaml, "Weapons");
|
||||||
|
VoiceDefinitions = LoadRuleSection(yaml, "Voices");
|
||||||
|
MusicDefinitions = LoadRuleSection(yaml, "Music");
|
||||||
|
NotificationDefinitions = LoadRuleSection(yaml, "Notifications");
|
||||||
|
SequenceDefinitions = LoadRuleSection(yaml, "Sequences");
|
||||||
|
ModelSequenceDefinitions = LoadRuleSection(yaml, "ModelSequences");
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (generator == null)
|
var rules = Ruleset.Load(modData, fileSystem, TileSet, RuleDefinitions,
|
||||||
return Ruleset.LoadDefaultsForTileSet(modData, TileSet);
|
WeaponDefinitions, VoiceDefinitions, NotificationDefinitions,
|
||||||
|
MusicDefinitions, SequenceDefinitions, ModelSequenceDefinitions);
|
||||||
|
|
||||||
try
|
WorldActorInfo = rules.Actors[SystemActors.World];
|
||||||
{
|
PlayerActorInfo = rules.Actors[SystemActors.Player];
|
||||||
var ret = generator();
|
}
|
||||||
DefinesUnsafeCustomRules = ret.DefinesUnsafeCustomRules;
|
catch (Exception e)
|
||||||
return ret.Ruleset;
|
{
|
||||||
}
|
Log.Write("debug", "Failed to load rules for `{0}` with error :{1}", Title, e.Message);
|
||||||
catch (Exception e)
|
WorldActorInfo = modData.DefaultRules.Actors[SystemActors.World];
|
||||||
{
|
PlayerActorInfo = modData.DefaultRules.Actors[SystemActors.Player];
|
||||||
Log.Write("debug", "Failed to load rules for `{0}` with error :{1}", Title, e.Message);
|
}
|
||||||
InvalidCustomRules = true;
|
|
||||||
return Ruleset.LoadDefaultsForTileSet(modData, TileSet);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
RulesLoaded = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public InnerData Clone()
|
public InnerData Clone()
|
||||||
@@ -132,7 +134,7 @@ namespace OpenRA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static readonly CPos[] NoSpawns = new CPos[] { };
|
static readonly CPos[] NoSpawns = { };
|
||||||
readonly MapCache cache;
|
readonly MapCache cache;
|
||||||
readonly ModData modData;
|
readonly ModData modData;
|
||||||
|
|
||||||
@@ -156,22 +158,9 @@ namespace OpenRA
|
|||||||
public MapClassification Class => innerData.Class;
|
public MapClassification Class => innerData.Class;
|
||||||
public MapVisibility Visibility => innerData.Visibility;
|
public MapVisibility Visibility => innerData.Visibility;
|
||||||
|
|
||||||
public bool InvalidCustomRules => innerData.InvalidCustomRules;
|
|
||||||
public bool RulesLoaded => innerData.RulesLoaded;
|
|
||||||
|
|
||||||
public ActorInfo WorldActorInfo => innerData.WorldActorInfo;
|
public ActorInfo WorldActorInfo => innerData.WorldActorInfo;
|
||||||
public ActorInfo PlayerActorInfo => innerData.PlayerActorInfo;
|
public ActorInfo PlayerActorInfo => innerData.PlayerActorInfo;
|
||||||
|
|
||||||
public bool DefinesUnsafeCustomRules
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
// Force lazy rules to be evaluated
|
|
||||||
var force = innerData.WorldActorInfo;
|
|
||||||
return innerData.DefinesUnsafeCustomRules;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public long DownloadBytes { get; private set; }
|
public long DownloadBytes { get; private set; }
|
||||||
public int DownloadPercentage { get; private set; }
|
public int DownloadPercentage { get; private set; }
|
||||||
|
|
||||||
@@ -197,6 +186,20 @@ namespace OpenRA
|
|||||||
generatingMinimap = false;
|
generatingMinimap = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool DefinesUnsafeCustomRules()
|
||||||
|
{
|
||||||
|
return Ruleset.DefinesUnsafeCustomRules(modData, this, innerData.RuleDefinitions,
|
||||||
|
innerData.WeaponDefinitions, innerData.VoiceDefinitions,
|
||||||
|
innerData.NotificationDefinitions, innerData.SequenceDefinitions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Ruleset LoadRuleset()
|
||||||
|
{
|
||||||
|
return Ruleset.Load(modData, this, TileSet, innerData.RuleDefinitions,
|
||||||
|
innerData.WeaponDefinitions, innerData.VoiceDefinitions, innerData.NotificationDefinitions,
|
||||||
|
innerData.MusicDefinitions, innerData.SequenceDefinitions, innerData.ModelSequenceDefinitions);
|
||||||
|
}
|
||||||
|
|
||||||
public MapPreview(ModData modData, string uid, MapGridType gridType, MapCache cache)
|
public MapPreview(ModData modData, string uid, MapGridType gridType, MapCache cache)
|
||||||
{
|
{
|
||||||
this.cache = cache;
|
this.cache = cache;
|
||||||
@@ -308,21 +311,7 @@ namespace OpenRA
|
|||||||
newData.Status = MapStatus.Unavailable;
|
newData.Status = MapStatus.Unavailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
newData.SetRulesetGenerator(modData, () =>
|
newData.SetCustomRules(modData, this, yaml);
|
||||||
{
|
|
||||||
var ruleDefinitions = LoadRuleSection(yaml, "Rules");
|
|
||||||
var weaponDefinitions = LoadRuleSection(yaml, "Weapons");
|
|
||||||
var voiceDefinitions = LoadRuleSection(yaml, "Voices");
|
|
||||||
var musicDefinitions = LoadRuleSection(yaml, "Music");
|
|
||||||
var notificationDefinitions = LoadRuleSection(yaml, "Notifications");
|
|
||||||
var sequenceDefinitions = LoadRuleSection(yaml, "Sequences");
|
|
||||||
var modelSequenceDefinitions = LoadRuleSection(yaml, "ModelSequences");
|
|
||||||
var rules = Ruleset.Load(modData, this, TileSet, ruleDefinitions, weaponDefinitions,
|
|
||||||
voiceDefinitions, notificationDefinitions, musicDefinitions, sequenceDefinitions, modelSequenceDefinitions);
|
|
||||||
var flagged = Ruleset.DefinesUnsafeCustomRules(modData, this, ruleDefinitions,
|
|
||||||
weaponDefinitions, voiceDefinitions, notificationDefinitions, sequenceDefinitions);
|
|
||||||
return (rules, flagged);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (p.Contains("map.png"))
|
if (p.Contains("map.png"))
|
||||||
using (var dataStream = p.GetStream("map.png"))
|
using (var dataStream = p.GetStream("map.png"))
|
||||||
@@ -332,19 +321,6 @@ namespace OpenRA
|
|||||||
innerData = newData;
|
innerData = newData;
|
||||||
}
|
}
|
||||||
|
|
||||||
MiniYaml LoadRuleSection(Dictionary<string, MiniYaml> yaml, string section)
|
|
||||||
{
|
|
||||||
if (!yaml.TryGetValue(section, out var node))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void PreloadRules()
|
|
||||||
{
|
|
||||||
var unused = WorldActorInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateRemoteSearch(MapStatus status, MiniYaml yaml, Action<MapPreview> parseMetadata = null)
|
public void UpdateRemoteSearch(MapStatus status, MiniYaml yaml, Action<MapPreview> parseMetadata = null)
|
||||||
{
|
{
|
||||||
var newData = innerData.Clone();
|
var newData = innerData.Clone();
|
||||||
@@ -389,23 +365,9 @@ namespace OpenRA
|
|||||||
var playersString = Encoding.UTF8.GetString(Convert.FromBase64String(r.players_block));
|
var playersString = Encoding.UTF8.GetString(Convert.FromBase64String(r.players_block));
|
||||||
newData.Players = new MapPlayers(MiniYaml.FromString(playersString));
|
newData.Players = new MapPlayers(MiniYaml.FromString(playersString));
|
||||||
|
|
||||||
newData.SetRulesetGenerator(modData, () =>
|
var rulesString = Encoding.UTF8.GetString(Convert.FromBase64String(r.rules));
|
||||||
{
|
var rulesYaml = new MiniYaml("", MiniYaml.FromString(rulesString)).ToDictionary();
|
||||||
var rulesString = Encoding.UTF8.GetString(Convert.FromBase64String(r.rules));
|
newData.SetCustomRules(modData, this, rulesYaml);
|
||||||
var rulesYaml = new MiniYaml("", MiniYaml.FromString(rulesString)).ToDictionary();
|
|
||||||
var ruleDefinitions = LoadRuleSection(rulesYaml, "Rules");
|
|
||||||
var weaponDefinitions = LoadRuleSection(rulesYaml, "Weapons");
|
|
||||||
var voiceDefinitions = LoadRuleSection(rulesYaml, "Voices");
|
|
||||||
var musicDefinitions = LoadRuleSection(rulesYaml, "Music");
|
|
||||||
var notificationDefinitions = LoadRuleSection(rulesYaml, "Notifications");
|
|
||||||
var sequenceDefinitions = LoadRuleSection(rulesYaml, "Sequences");
|
|
||||||
var modelSequenceDefinitions = LoadRuleSection(rulesYaml, "ModelSequences");
|
|
||||||
var rules = Ruleset.Load(modData, this, TileSet, ruleDefinitions, weaponDefinitions,
|
|
||||||
voiceDefinitions, notificationDefinitions, musicDefinitions, sequenceDefinitions, modelSequenceDefinitions);
|
|
||||||
var flagged = Ruleset.DefinesUnsafeCustomRules(modData, this, ruleDefinitions,
|
|
||||||
weaponDefinitions, voiceDefinitions, notificationDefinitions, sequenceDefinitions);
|
|
||||||
return (rules, flagged);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -473,21 +435,19 @@ namespace OpenRA
|
|||||||
|
|
||||||
mapInstallPackage.Update(mapFilename, fileStream.ToArray());
|
mapInstallPackage.Update(mapFilename, fileStream.ToArray());
|
||||||
Log.Write("debug", "Downloaded map to '{0}'", mapFilename);
|
Log.Write("debug", "Downloaded map to '{0}'", mapFilename);
|
||||||
Game.RunAfterTick(() =>
|
|
||||||
|
var package = mapInstallPackage.OpenPackage(mapFilename, modData.ModFiles);
|
||||||
|
if (package == null)
|
||||||
|
innerData.Status = MapStatus.DownloadError;
|
||||||
|
else
|
||||||
{
|
{
|
||||||
var package = mapInstallPackage.OpenPackage(mapFilename, modData.ModFiles);
|
UpdateFromMap(package, mapInstallPackage, MapClassification.User, null, GridType);
|
||||||
if (package == null)
|
Game.RunAfterTick(onSuccess);
|
||||||
innerData.Status = MapStatus.DownloadError;
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
UpdateFromMap(package, mapInstallPackage, MapClassification.User, null, GridType);
|
|
||||||
onSuccess();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Console.WriteLine(e.Message);
|
Log.Write("debug", "Map installation failed with error: {0}", e);
|
||||||
innerData.Status = MapStatus.DownloadError;
|
innerData.Status = MapStatus.DownloadError;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ using System.Linq;
|
|||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Sockets;
|
using System.Net.Sockets;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
|
using OpenRA.Server;
|
||||||
|
|
||||||
namespace OpenRA.Network
|
namespace OpenRA.Network
|
||||||
{
|
{
|
||||||
@@ -218,10 +219,21 @@ namespace OpenRA.Network
|
|||||||
public bool IsEnabled => Value == "True";
|
public bool IsEnabled => Value == "True";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum MapStatus
|
||||||
|
{
|
||||||
|
Unknown = 0,
|
||||||
|
Validating = 1,
|
||||||
|
Playable = 2,
|
||||||
|
Incompatible = 4,
|
||||||
|
UnsafeCustomRules = 8,
|
||||||
|
}
|
||||||
|
|
||||||
public class Global
|
public class Global
|
||||||
{
|
{
|
||||||
public string ServerName;
|
public string ServerName;
|
||||||
public string Map;
|
public string Map;
|
||||||
|
public MapStatus MapStatus;
|
||||||
public int RandomSeed = 0;
|
public int RandomSeed = 0;
|
||||||
public bool AllowSpectators = true;
|
public bool AllowSpectators = true;
|
||||||
public string GameUid;
|
public string GameUid;
|
||||||
|
|||||||
100
OpenRA.Game/Server/MapStatusCache.cs
Normal file
100
OpenRA.Game/Server/MapStatusCache.cs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
#region Copyright & License Information
|
||||||
|
/*
|
||||||
|
* Copyright 2007-2021 The OpenRA Developers (see AUTHORS)
|
||||||
|
* This file is part of OpenRA, which is free software. It is made
|
||||||
|
* available to you under the terms of the GNU General Public License
|
||||||
|
* as published by the Free Software Foundation, either version 3 of
|
||||||
|
* the License, or (at your option) any later version. For more
|
||||||
|
* information, see COPYING.
|
||||||
|
*/
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using OpenRA.Network;
|
||||||
|
|
||||||
|
namespace OpenRA.Server
|
||||||
|
{
|
||||||
|
public interface ILintServerMapPass { void Run(Action<string> emitError, Action<string> emitWarning, ModData modData, MapPreview map, Ruleset mapRules); }
|
||||||
|
|
||||||
|
public class MapStatusCache
|
||||||
|
{
|
||||||
|
readonly Dictionary<MapPreview, Session.MapStatus> cache = new Dictionary<MapPreview, Session.MapStatus>();
|
||||||
|
readonly Action<string, Session.MapStatus> onStatusChanged;
|
||||||
|
readonly bool enableRemoteLinting;
|
||||||
|
readonly ModData modData;
|
||||||
|
|
||||||
|
public MapStatusCache(ModData modData, Action<string, Session.MapStatus> onStatusChanged, bool enableRemoteLinting)
|
||||||
|
{
|
||||||
|
this.modData = modData;
|
||||||
|
this.enableRemoteLinting = enableRemoteLinting;
|
||||||
|
this.onStatusChanged = onStatusChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RunLintTests(MapPreview map, Ruleset rules)
|
||||||
|
{
|
||||||
|
var status = cache[map];
|
||||||
|
var failed = false;
|
||||||
|
|
||||||
|
Action<string> onLintFailure = message =>
|
||||||
|
{
|
||||||
|
Log.Write("server", "Map {0} failed lint with error: {1}", map.Title, message);
|
||||||
|
failed = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
Action<string> onLintWarning = _ => { };
|
||||||
|
|
||||||
|
foreach (var customMapPassType in modData.ObjectCreator.GetTypesImplementing<ILintServerMapPass>())
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var customMapPass = (ILintServerMapPass)modData.ObjectCreator.CreateBasic(customMapPassType);
|
||||||
|
customMapPass.Run(onLintFailure, onLintWarning, modData, map, rules);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
onLintFailure(e.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status &= ~Session.MapStatus.Validating;
|
||||||
|
status |= failed ? Session.MapStatus.Incompatible : Session.MapStatus.Playable;
|
||||||
|
|
||||||
|
cache[map] = status;
|
||||||
|
onStatusChanged?.Invoke(map.Uid, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Session.MapStatus this[MapPreview map]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (cache.TryGetValue(map, out var status))
|
||||||
|
return status;
|
||||||
|
|
||||||
|
Ruleset rules = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
rules = map.LoadRuleset();
|
||||||
|
|
||||||
|
// Locally installed maps are assumed to not require linting
|
||||||
|
status = enableRemoteLinting && map.Status != MapStatus.Available ? Session.MapStatus.Validating : Session.MapStatus.Playable;
|
||||||
|
if (map.DefinesUnsafeCustomRules())
|
||||||
|
status |= Session.MapStatus.UnsafeCustomRules;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Log.Write("server", "Failed to load rules for `{0}` with error :{1}", map.Title, e.Message);
|
||||||
|
status = Session.MapStatus.Incompatible;
|
||||||
|
}
|
||||||
|
|
||||||
|
cache[map] = status;
|
||||||
|
|
||||||
|
if ((status & Session.MapStatus.Validating) != 0)
|
||||||
|
ThreadPool.QueueUserWorkItem(_ => RunLintTests(map, rules));
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -68,6 +68,6 @@ namespace OpenRA.Server
|
|||||||
// The protocol for server and world orders
|
// The protocol for server and world orders
|
||||||
// This applies after the handshake has completed, and is provided to support
|
// This applies after the handshake has completed, and is provided to support
|
||||||
// alternative server implementations that wish to support multiple versions in parallel
|
// alternative server implementations that wish to support multiple versions in parallel
|
||||||
public const int Orders = 11;
|
public const int Orders = 12;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ namespace OpenRA.Server
|
|||||||
|
|
||||||
// Managed by LobbyCommands
|
// Managed by LobbyCommands
|
||||||
public MapPreview Map;
|
public MapPreview Map;
|
||||||
|
public readonly MapStatusCache MapStatusCache;
|
||||||
public GameSave GameSave = null;
|
public GameSave GameSave = null;
|
||||||
|
|
||||||
readonly int randomSeed;
|
readonly int randomSeed;
|
||||||
@@ -170,6 +171,17 @@ namespace OpenRA.Server
|
|||||||
}.Serialize());
|
}.Serialize());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MapStatusChanged(string uid, Session.MapStatus status)
|
||||||
|
{
|
||||||
|
lock (LobbyInfo)
|
||||||
|
{
|
||||||
|
if (LobbyInfo.GlobalSettings.Map == uid)
|
||||||
|
LobbyInfo.GlobalSettings.MapStatus = status;
|
||||||
|
|
||||||
|
SyncLobbyInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Server(List<IPEndPoint> endpoints, ServerSettings settings, ModData modData, ServerType type)
|
public Server(List<IPEndPoint> endpoints, ServerSettings settings, ModData modData, ServerType type)
|
||||||
{
|
{
|
||||||
Log.AddChannel("server", "server.log", true);
|
Log.AddChannel("server", "server.log", true);
|
||||||
@@ -229,12 +241,16 @@ namespace OpenRA.Server
|
|||||||
|
|
||||||
serverTraits.TrimExcess();
|
serverTraits.TrimExcess();
|
||||||
|
|
||||||
|
Map = ModData.MapCache[settings.Map];
|
||||||
|
MapStatusCache = new MapStatusCache(modData, MapStatusChanged, type == ServerType.Dedicated && settings.EnableLintChecks);
|
||||||
|
|
||||||
LobbyInfo = new Session
|
LobbyInfo = new Session
|
||||||
{
|
{
|
||||||
GlobalSettings =
|
GlobalSettings =
|
||||||
{
|
{
|
||||||
RandomSeed = randomSeed,
|
RandomSeed = randomSeed,
|
||||||
Map = settings.Map,
|
Map = Map.Uid,
|
||||||
|
MapStatus = Session.MapStatus.Unknown,
|
||||||
ServerName = settings.Name,
|
ServerName = settings.Name,
|
||||||
EnableSingleplayer = settings.EnableSingleplayer || Type != ServerType.Dedicated,
|
EnableSingleplayer = settings.EnableSingleplayer || Type != ServerType.Dedicated,
|
||||||
EnableSyncReports = settings.EnableSyncReports,
|
EnableSyncReports = settings.EnableSyncReports,
|
||||||
@@ -254,6 +270,8 @@ namespace OpenRA.Server
|
|||||||
|
|
||||||
new Thread(_ =>
|
new Thread(_ =>
|
||||||
{
|
{
|
||||||
|
// Initial status is set off the main thread to avoid triggering a load screen when joining a skirmish game
|
||||||
|
LobbyInfo.GlobalSettings.MapStatus = MapStatusCache[Map];
|
||||||
foreach (var t in serverTraits.WithInterface<INotifyServerStart>())
|
foreach (var t in serverTraits.WithInterface<INotifyServerStart>())
|
||||||
t.ServerStarted(this);
|
t.ServerStarted(this);
|
||||||
|
|
||||||
@@ -541,7 +559,7 @@ namespace OpenRA.Server
|
|||||||
SendOrderTo(newConn, "Message", motd);
|
SendOrderTo(newConn, "Message", motd);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Map.DefinesUnsafeCustomRules)
|
if ((LobbyInfo.GlobalSettings.MapStatus & Session.MapStatus.UnsafeCustomRules) != 0)
|
||||||
SendOrderTo(newConn, "Message", "This map contains custom rules. Game experience may change.");
|
SendOrderTo(newConn, "Message", "This map contains custom rules. Game experience may change.");
|
||||||
|
|
||||||
if (!LobbyInfo.GlobalSettings.EnableSingleplayer)
|
if (!LobbyInfo.GlobalSettings.EnableSingleplayer)
|
||||||
|
|||||||
@@ -91,6 +91,9 @@ namespace OpenRA
|
|||||||
[Desc("For dedicated servers only, save replays for all games played.")]
|
[Desc("For dedicated servers only, save replays for all games played.")]
|
||||||
public bool RecordReplays = false;
|
public bool RecordReplays = false;
|
||||||
|
|
||||||
|
[Desc("For dedicated servers only, treat maps that fail the lint checks as invalid.")]
|
||||||
|
public bool EnableLintChecks = true;
|
||||||
|
|
||||||
public ServerSettings Clone()
|
public ServerSettings Clone()
|
||||||
{
|
{
|
||||||
return (ServerSettings)MemberwiseClone();
|
return (ServerSettings)MemberwiseClone();
|
||||||
|
|||||||
@@ -431,6 +431,7 @@ namespace OpenRA.Mods.Common.Server
|
|||||||
|
|
||||||
var oldSlots = server.LobbyInfo.Slots.Keys.ToArray();
|
var oldSlots = server.LobbyInfo.Slots.Keys.ToArray();
|
||||||
server.Map = server.ModData.MapCache[server.LobbyInfo.GlobalSettings.Map];
|
server.Map = server.ModData.MapCache[server.LobbyInfo.GlobalSettings.Map];
|
||||||
|
server.LobbyInfo.GlobalSettings.MapStatus = server.MapStatusCache[server.Map];
|
||||||
|
|
||||||
server.LobbyInfo.Slots = server.Map.Players.Players
|
server.LobbyInfo.Slots = server.Map.Players.Players
|
||||||
.Select(p => MakeSlotFromPlayerReference(p.Value))
|
.Select(p => MakeSlotFromPlayerReference(p.Value))
|
||||||
@@ -492,7 +493,7 @@ namespace OpenRA.Mods.Common.Server
|
|||||||
|
|
||||||
server.SendMessage("{0} changed the map to {1}.".F(client.Name, server.Map.Title));
|
server.SendMessage("{0} changed the map to {1}.".F(client.Name, server.Map.Title));
|
||||||
|
|
||||||
if (server.Map.DefinesUnsafeCustomRules)
|
if ((server.LobbyInfo.GlobalSettings.MapStatus & Session.MapStatus.UnsafeCustomRules) != 0)
|
||||||
server.SendMessage("This map contains custom rules. Game experience may change.");
|
server.SendMessage("This map contains custom rules. Game experience may change.");
|
||||||
|
|
||||||
if (!server.LobbyInfo.GlobalSettings.EnableSingleplayer)
|
if (!server.LobbyInfo.GlobalSettings.EnableSingleplayer)
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
readonly TabCompletionLogic tabCompletion = new TabCompletionLogic();
|
readonly TabCompletionLogic tabCompletion = new TabCompletionLogic();
|
||||||
|
|
||||||
MapPreview map;
|
MapPreview map;
|
||||||
|
Session.MapStatus mapStatus;
|
||||||
|
|
||||||
bool addBotOnMapLoad;
|
bool addBotOnMapLoad;
|
||||||
bool disableTeamChat;
|
bool disableTeamChat;
|
||||||
bool insufficientPlayerSpawns;
|
bool insufficientPlayerSpawns;
|
||||||
@@ -67,6 +69,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
|
|
||||||
readonly string chatLineSound = ChromeMetrics.Get<string>("ChatLineSound");
|
readonly string chatLineSound = ChromeMetrics.Get<string>("ChatLineSound");
|
||||||
|
|
||||||
|
bool MapIsPlayable => (mapStatus & Session.MapStatus.Playable) == Session.MapStatus.Playable;
|
||||||
|
|
||||||
// Listen for connection failures
|
// Listen for connection failures
|
||||||
void ConnectionStateChanged(OrderManager om)
|
void ConnectionStateChanged(OrderManager om)
|
||||||
{
|
{
|
||||||
@@ -131,7 +135,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
var mapContainer = Ui.LoadWidget("MAP_PREVIEW", lobby.Get("MAP_PREVIEW_ROOT"), new WidgetArgs
|
var mapContainer = Ui.LoadWidget("MAP_PREVIEW", lobby.Get("MAP_PREVIEW_ROOT"), new WidgetArgs
|
||||||
{
|
{
|
||||||
{ "orderManager", orderManager },
|
{ "orderManager", orderManager },
|
||||||
{ "getMap", (Func<MapPreview>)(() => map) },
|
{ "getMap", (Func<(MapPreview, Session.MapStatus)>)(() => (map, mapStatus)) },
|
||||||
{
|
{
|
||||||
"onMouseDown", (Action<MapPreviewWidget, MapPreview, MouseInput>)((preview, mapPreview, mi) =>
|
"onMouseDown", (Action<MapPreviewWidget, MapPreview, MouseInput>)((preview, mapPreview, mi) =>
|
||||||
LobbyUtils.SelectSpawnPoint(orderManager, preview, mapPreview, mi))
|
LobbyUtils.SelectSpawnPoint(orderManager, preview, mapPreview, mi))
|
||||||
@@ -163,8 +167,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
|
|
||||||
var gameStarting = false;
|
var gameStarting = false;
|
||||||
Func<bool> configurationDisabled = () => !Game.IsHost || gameStarting ||
|
Func<bool> configurationDisabled = () => !Game.IsHost || gameStarting ||
|
||||||
panel == PanelType.Kick || panel == PanelType.ForceStart ||
|
panel == PanelType.Kick || panel == PanelType.ForceStart || !MapIsPlayable ||
|
||||||
!map.RulesLoaded || map.InvalidCustomRules ||
|
|
||||||
orderManager.LocalClient == null || orderManager.LocalClient.IsReady;
|
orderManager.LocalClient == null || orderManager.LocalClient.IsReady;
|
||||||
|
|
||||||
var mapButton = lobby.GetOrNull<ButtonWidget>("CHANGEMAP_BUTTON");
|
var mapButton = lobby.GetOrNull<ButtonWidget>("CHANGEMAP_BUTTON");
|
||||||
@@ -330,7 +333,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
optionsTab.IsHighlighted = () => panel == PanelType.Options;
|
optionsTab.IsHighlighted = () => panel == PanelType.Options;
|
||||||
optionsTab.IsDisabled = OptionsTabDisabled;
|
optionsTab.IsDisabled = OptionsTabDisabled;
|
||||||
optionsTab.OnClick = () => panel = PanelType.Options;
|
optionsTab.OnClick = () => panel = PanelType.Options;
|
||||||
optionsTab.GetText = () => !map.RulesLoaded ? "Loading..." : optionsTab.Text;
|
|
||||||
|
|
||||||
var playersTab = tabContainer.Get<ButtonWidget>("PLAYERS_TAB");
|
var playersTab = tabContainer.Get<ButtonWidget>("PLAYERS_TAB");
|
||||||
playersTab.IsHighlighted = () => panel == PanelType.Players;
|
playersTab.IsHighlighted = () => panel == PanelType.Players;
|
||||||
@@ -476,7 +478,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
|
|
||||||
bool OptionsTabDisabled()
|
bool OptionsTabDisabled()
|
||||||
{
|
{
|
||||||
return !map.RulesLoaded || map.InvalidCustomRules || panel == PanelType.Kick || panel == PanelType.ForceStart;
|
return !MapIsPlayable || panel == PanelType.Kick || panel == PanelType.ForceStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Tick()
|
public override void Tick()
|
||||||
@@ -505,14 +507,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoadMapPreviewRules(MapPreview map)
|
|
||||||
{
|
|
||||||
// Force map rules to be loaded on this background thread
|
|
||||||
new Task(map.PreloadRules).Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void UpdateCurrentMap()
|
void UpdateCurrentMap()
|
||||||
{
|
{
|
||||||
|
mapStatus = orderManager.LobbyInfo.GlobalSettings.MapStatus;
|
||||||
var uid = orderManager.LobbyInfo.GlobalSettings.Map;
|
var uid = orderManager.LobbyInfo.GlobalSettings.Map;
|
||||||
if (map.Uid == uid)
|
if (map.Uid == uid)
|
||||||
return;
|
return;
|
||||||
@@ -520,39 +517,22 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
map = modData.MapCache[uid];
|
map = modData.MapCache[uid];
|
||||||
if (map.Status == MapStatus.Available)
|
if (map.Status == MapStatus.Available)
|
||||||
{
|
{
|
||||||
// Maps need to be validated and pre-loaded before they can be accessed
|
// Tell the server that we have the map
|
||||||
var currentMap = map;
|
orderManager.IssueOrder(Order.Command("state {0}".F(Session.ClientState.NotReady)));
|
||||||
new Task(() =>
|
|
||||||
|
if (addBotOnMapLoad)
|
||||||
{
|
{
|
||||||
// Force map rules to be loaded on this background thread
|
var slot = orderManager.LobbyInfo.FirstEmptyBotSlot();
|
||||||
currentMap.PreloadRules();
|
var bot = map.PlayerActorInfo.TraitInfos<IBotInfo>().Select(t => t.Type).FirstOrDefault();
|
||||||
Game.RunAfterTick(() =>
|
var botController = orderManager.LobbyInfo.Clients.FirstOrDefault(c => c.IsAdmin);
|
||||||
{
|
if (slot != null && bot != null)
|
||||||
// Map may have changed in the meantime
|
orderManager.IssueOrder(Order.Command("slot_bot {0} {1} {2}".F(slot, botController.Index, bot)));
|
||||||
if (currentMap != map)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Tell the server that we have the map
|
addBotOnMapLoad = false;
|
||||||
if (!currentMap.InvalidCustomRules)
|
}
|
||||||
orderManager.IssueOrder(Order.Command("state {0}".F(Session.ClientState.NotReady)));
|
|
||||||
|
|
||||||
if (addBotOnMapLoad)
|
|
||||||
{
|
|
||||||
var slot = orderManager.LobbyInfo.FirstEmptyBotSlot();
|
|
||||||
var bot = currentMap.PlayerActorInfo.TraitInfos<IBotInfo>().Select(t => t.Type).FirstOrDefault();
|
|
||||||
var botController = orderManager.LobbyInfo.Clients.FirstOrDefault(c => c.IsAdmin);
|
|
||||||
if (slot != null && bot != null)
|
|
||||||
orderManager.IssueOrder(Order.Command("slot_bot {0} {1} {2}".F(slot, botController.Index, bot)));
|
|
||||||
|
|
||||||
addBotOnMapLoad = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}).Start();
|
|
||||||
}
|
}
|
||||||
else if (map.Status == MapStatus.DownloadAvailable)
|
else if (map.Status != MapStatus.DownloadAvailable && Game.Settings.Game.AllowDownloading)
|
||||||
LoadMapPreviewRules(map);
|
modData.MapCache.QueryRemoteMapDetails(services.MapRepository, new[] { uid });
|
||||||
else if (Game.Settings.Game.AllowDownloading)
|
|
||||||
modData.MapCache.QueryRemoteMapDetails(services.MapRepository, new[] { uid }, LoadMapPreviewRules);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdatePlayerList()
|
void UpdatePlayerList()
|
||||||
@@ -626,7 +606,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
LobbyUtils.SetupEditableTeamWidget(template, slot, client, orderManager, map);
|
LobbyUtils.SetupEditableTeamWidget(template, slot, client, orderManager, map);
|
||||||
LobbyUtils.SetupEditableHandicapWidget(template, slot, client, orderManager, map);
|
LobbyUtils.SetupEditableHandicapWidget(template, slot, client, orderManager, map);
|
||||||
LobbyUtils.SetupEditableSpawnWidget(template, slot, client, orderManager, map);
|
LobbyUtils.SetupEditableSpawnWidget(template, slot, client, orderManager, map);
|
||||||
LobbyUtils.SetupEditableReadyWidget(template, slot, client, orderManager, map);
|
LobbyUtils.SetupEditableReadyWidget(template, slot, client, orderManager, map, MapIsPlayable);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -686,7 +666,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
LobbyUtils.SetupEditableNameWidget(template, null, c, orderManager, worldRenderer);
|
LobbyUtils.SetupEditableNameWidget(template, null, c, orderManager, worldRenderer);
|
||||||
|
|
||||||
if (client.IsAdmin)
|
if (client.IsAdmin)
|
||||||
LobbyUtils.SetupEditableReadyWidget(template, null, client, orderManager, map);
|
LobbyUtils.SetupEditableReadyWidget(template, null, client, orderManager, map, MapIsPlayable);
|
||||||
else
|
else
|
||||||
LobbyUtils.HideReadyWidgets(template);
|
LobbyUtils.HideReadyWidgets(template);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
readonly OrderManager orderManager;
|
readonly OrderManager orderManager;
|
||||||
readonly Func<bool> configurationDisabled;
|
readonly Func<bool> configurationDisabled;
|
||||||
MapPreview mapPreview;
|
MapPreview mapPreview;
|
||||||
bool validOptions;
|
|
||||||
|
|
||||||
[ObjectCreator.UseCtor]
|
[ObjectCreator.UseCtor]
|
||||||
internal LobbyOptionsLogic(Widget widget, OrderManager orderManager, Func<MapPreview> getMap, Func<bool> configurationDisabled)
|
internal LobbyOptionsLogic(Widget widget, OrderManager orderManager, Func<MapPreview> getMap, Func<bool> configurationDisabled)
|
||||||
@@ -42,7 +41,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
panel = (ScrollPanelWidget)widget;
|
panel = (ScrollPanelWidget)widget;
|
||||||
optionsContainer = widget.Get("LOBBY_OPTIONS");
|
optionsContainer = widget.Get("LOBBY_OPTIONS");
|
||||||
yMargin = optionsContainer.Bounds.Y;
|
yMargin = optionsContainer.Bounds.Y;
|
||||||
optionsContainer.IsVisible = () => validOptions;
|
|
||||||
checkboxRowTemplate = optionsContainer.Get("CHECKBOX_ROW_TEMPLATE");
|
checkboxRowTemplate = optionsContainer.Get("CHECKBOX_ROW_TEMPLATE");
|
||||||
dropdownRowTemplate = optionsContainer.Get("DROPDOWN_ROW_TEMPLATE");
|
dropdownRowTemplate = optionsContainer.Get("DROPDOWN_ROW_TEMPLATE");
|
||||||
|
|
||||||
@@ -56,24 +54,18 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
if (newMapPreview == mapPreview)
|
if (newMapPreview == mapPreview)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (newMapPreview.RulesLoaded)
|
// We are currently enumerating the widget tree and so can't modify any layout
|
||||||
|
// Defer it to the end of tick instead
|
||||||
|
Game.RunAfterTick(() =>
|
||||||
{
|
{
|
||||||
// We are currently enumerating the widget tree and so can't modify any layout
|
mapPreview = newMapPreview;
|
||||||
// Defer it to the end of tick instead
|
RebuildOptions();
|
||||||
Game.RunAfterTick(() =>
|
});
|
||||||
{
|
|
||||||
mapPreview = newMapPreview;
|
|
||||||
RebuildOptions();
|
|
||||||
validOptions = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else
|
|
||||||
validOptions = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RebuildOptions()
|
void RebuildOptions()
|
||||||
{
|
{
|
||||||
if (mapPreview == null || mapPreview.WorldActorInfo == null || mapPreview.InvalidCustomRules)
|
if (mapPreview == null || mapPreview.WorldActorInfo == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
optionsContainer.RemoveChildren();
|
optionsContainer.RemoveChildren();
|
||||||
|
|||||||
@@ -630,13 +630,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
HideChildWidget(parent, "SPAWN_DROPDOWN");
|
HideChildWidget(parent, "SPAWN_DROPDOWN");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SetupEditableReadyWidget(Widget parent, Session.Slot s, Session.Client c, OrderManager orderManager, MapPreview map)
|
public static void SetupEditableReadyWidget(Widget parent, Session.Slot s, Session.Client c, OrderManager orderManager, MapPreview map, bool isEnabled)
|
||||||
{
|
{
|
||||||
var status = parent.Get<CheckboxWidget>("STATUS_CHECKBOX");
|
var status = parent.Get<CheckboxWidget>("STATUS_CHECKBOX");
|
||||||
status.IsChecked = () => orderManager.LocalClient.IsReady || c.Bot != null;
|
status.IsChecked = () => orderManager.LocalClient.IsReady || c.Bot != null;
|
||||||
status.IsVisible = () => true;
|
status.IsVisible = () => true;
|
||||||
status.IsDisabled = () => c.Bot != null || map.Status != MapStatus.Available ||
|
status.IsDisabled = () => c.Bot != null || map.Status != MapStatus.Available || !isEnabled;
|
||||||
!map.RulesLoaded || map.InvalidCustomRules;
|
|
||||||
|
|
||||||
var state = orderManager.LocalClient.IsReady ? Session.ClientState.NotReady : Session.ClientState.Ready;
|
var state = orderManager.LocalClient.IsReady ? Session.ClientState.NotReady : Session.ClientState.Ready;
|
||||||
status.OnClick = () => orderManager.IssueOrder(Order.Command("state {0}".F(state)));
|
status.OnClick = () => orderManager.IssueOrder(Order.Command("state {0}".F(state)));
|
||||||
|
|||||||
@@ -24,8 +24,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
int blinkTick;
|
int blinkTick;
|
||||||
|
|
||||||
[ObjectCreator.UseCtor]
|
[ObjectCreator.UseCtor]
|
||||||
internal MapPreviewLogic(Widget widget, ModData modData, OrderManager orderManager, Func<MapPreview> getMap, Action<MapPreviewWidget, MapPreview, MouseInput> onMouseDown,
|
internal MapPreviewLogic(Widget widget, ModData modData, OrderManager orderManager, Func<(MapPreview Map, Session.MapStatus Status)> getMap,
|
||||||
Func<Dictionary<int, SpawnOccupant>> getSpawnOccupants, Func<HashSet<int>> getDisabledSpawnPoints, bool showUnoccupiedSpawnpoints)
|
Action<MapPreviewWidget, MapPreview, MouseInput> onMouseDown, Func<Dictionary<int, SpawnOccupant>> getSpawnOccupants,
|
||||||
|
Func<HashSet<int>> getDisabledSpawnPoints, bool showUnoccupiedSpawnpoints)
|
||||||
{
|
{
|
||||||
var mapRepository = modData.Manifest.Get<WebServices>().MapRepository;
|
var mapRepository = modData.Manifest.Get<WebServices>().MapRepository;
|
||||||
|
|
||||||
@@ -34,8 +35,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
{
|
{
|
||||||
available.IsVisible = () =>
|
available.IsVisible = () =>
|
||||||
{
|
{
|
||||||
var map = getMap();
|
var (map, serverStatus) = getMap();
|
||||||
return map.Status == MapStatus.Available && (!map.RulesLoaded || !map.InvalidCustomRules);
|
var isPlayable = (serverStatus & Session.MapStatus.Playable) == Session.MapStatus.Playable;
|
||||||
|
return map.Status == MapStatus.Available && isPlayable;
|
||||||
};
|
};
|
||||||
|
|
||||||
SetupWidgets(available, getMap, onMouseDown, getSpawnOccupants, getDisabledSpawnPoints, showUnoccupiedSpawnpoints);
|
SetupWidgets(available, getMap, onMouseDown, getSpawnOccupants, getDisabledSpawnPoints, showUnoccupiedSpawnpoints);
|
||||||
@@ -46,17 +48,30 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
{
|
{
|
||||||
invalid.IsVisible = () =>
|
invalid.IsVisible = () =>
|
||||||
{
|
{
|
||||||
var map = getMap();
|
var (map, serverStatus) = getMap();
|
||||||
return map.Status == MapStatus.Available && map.InvalidCustomRules;
|
return map.Status == MapStatus.Available && (serverStatus & Session.MapStatus.Incompatible) != 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
SetupWidgets(invalid, getMap, onMouseDown, getSpawnOccupants, getDisabledSpawnPoints, showUnoccupiedSpawnpoints);
|
SetupWidgets(invalid, getMap, onMouseDown, getSpawnOccupants, getDisabledSpawnPoints, showUnoccupiedSpawnpoints);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var validating = widget.GetOrNull("MAP_VALIDATING");
|
||||||
|
if (validating != null)
|
||||||
|
{
|
||||||
|
validating.IsVisible = () =>
|
||||||
|
{
|
||||||
|
var (map, serverStatus) = getMap();
|
||||||
|
return map.Status == MapStatus.Available && (serverStatus & Session.MapStatus.Validating) != 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
SetupWidgets(validating, getMap, onMouseDown, getSpawnOccupants, getDisabledSpawnPoints, showUnoccupiedSpawnpoints);
|
||||||
|
}
|
||||||
|
|
||||||
var download = widget.GetOrNull("MAP_DOWNLOADABLE");
|
var download = widget.GetOrNull("MAP_DOWNLOADABLE");
|
||||||
if (download != null)
|
if (download != null)
|
||||||
{
|
{
|
||||||
download.IsVisible = () => getMap().Status == MapStatus.DownloadAvailable;
|
download.IsVisible = () => getMap().Map.Status == MapStatus.DownloadAvailable;
|
||||||
|
|
||||||
SetupWidgets(download, getMap, onMouseDown, getSpawnOccupants, getDisabledSpawnPoints, showUnoccupiedSpawnpoints);
|
SetupWidgets(download, getMap, onMouseDown, getSpawnOccupants, getDisabledSpawnPoints, showUnoccupiedSpawnpoints);
|
||||||
|
|
||||||
var install = download.GetOrNull<ButtonWidget>("MAP_INSTALL");
|
var install = download.GetOrNull<ButtonWidget>("MAP_INSTALL");
|
||||||
@@ -64,10 +79,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
{
|
{
|
||||||
install.OnClick = () =>
|
install.OnClick = () =>
|
||||||
{
|
{
|
||||||
var map = getMap();
|
getMap().Map.Install(mapRepository, () =>
|
||||||
map.Install(mapRepository, () =>
|
|
||||||
{
|
{
|
||||||
map.PreloadRules();
|
|
||||||
if (orderManager != null)
|
if (orderManager != null)
|
||||||
Game.RunAfterTick(() => orderManager.IssueOrder(Order.Command("state {0}".F(Session.ClientState.NotReady))));
|
Game.RunAfterTick(() => orderManager.IssueOrder(Order.Command("state {0}".F(Session.ClientState.NotReady))));
|
||||||
});
|
});
|
||||||
@@ -82,7 +95,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
{
|
{
|
||||||
progress.IsVisible = () =>
|
progress.IsVisible = () =>
|
||||||
{
|
{
|
||||||
var map = getMap();
|
var (map, _) = getMap();
|
||||||
return map.Status != MapStatus.Available && map.Status != MapStatus.DownloadAvailable;
|
return map.Status != MapStatus.Available && map.Status != MapStatus.DownloadAvailable;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -90,29 +103,36 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
|
|
||||||
var statusSearching = progress.GetOrNull("MAP_STATUS_SEARCHING");
|
var statusSearching = progress.GetOrNull("MAP_STATUS_SEARCHING");
|
||||||
if (statusSearching != null)
|
if (statusSearching != null)
|
||||||
statusSearching.IsVisible = () => getMap().Status == MapStatus.Searching;
|
{
|
||||||
|
statusSearching.IsVisible = () =>
|
||||||
|
{
|
||||||
|
var (map, _) = getMap();
|
||||||
|
return map.Status == MapStatus.Searching;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
var statusUnavailable = progress.GetOrNull("MAP_STATUS_UNAVAILABLE");
|
var statusUnavailable = progress.GetOrNull("MAP_STATUS_UNAVAILABLE");
|
||||||
if (statusUnavailable != null)
|
if (statusUnavailable != null)
|
||||||
{
|
{
|
||||||
statusUnavailable.IsVisible = () =>
|
statusUnavailable.IsVisible = () =>
|
||||||
{
|
{
|
||||||
var map = getMap();
|
var (map, _) = getMap();
|
||||||
return map.Status == MapStatus.Unavailable && map != MapCache.UnknownMap;
|
return map.Status == MapStatus.Unavailable && map != MapCache.UnknownMap;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var statusError = progress.GetOrNull("MAP_STATUS_ERROR");
|
var statusError = progress.GetOrNull("MAP_STATUS_ERROR");
|
||||||
if (statusError != null)
|
if (statusError != null)
|
||||||
statusError.IsVisible = () => getMap().Status == MapStatus.DownloadError;
|
statusError.IsVisible = () => getMap().Map.Status == MapStatus.DownloadError;
|
||||||
|
|
||||||
var statusDownloading = progress.GetOrNull<LabelWidget>("MAP_STATUS_DOWNLOADING");
|
var statusDownloading = progress.GetOrNull<LabelWidget>("MAP_STATUS_DOWNLOADING");
|
||||||
if (statusDownloading != null)
|
if (statusDownloading != null)
|
||||||
{
|
{
|
||||||
statusDownloading.IsVisible = () => getMap().Status == MapStatus.Downloading;
|
statusDownloading.IsVisible = () => getMap().Map.Status == MapStatus.Downloading;
|
||||||
|
|
||||||
statusDownloading.GetText = () =>
|
statusDownloading.GetText = () =>
|
||||||
{
|
{
|
||||||
var map = getMap();
|
var (map, _) = getMap();
|
||||||
if (map.DownloadBytes == 0)
|
if (map.DownloadBytes == 0)
|
||||||
return "Connecting...";
|
return "Connecting...";
|
||||||
|
|
||||||
@@ -129,18 +149,17 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
{
|
{
|
||||||
retry.IsVisible = () =>
|
retry.IsVisible = () =>
|
||||||
{
|
{
|
||||||
var map = getMap();
|
var (map, _) = getMap();
|
||||||
return (map.Status == MapStatus.DownloadError || map.Status == MapStatus.Unavailable) && map != MapCache.UnknownMap;
|
return (map.Status == MapStatus.DownloadError || map.Status == MapStatus.Unavailable) && map != MapCache.UnknownMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
retry.OnClick = () =>
|
retry.OnClick = () =>
|
||||||
{
|
{
|
||||||
var map = getMap();
|
var (map, _) = getMap();
|
||||||
if (map.Status == MapStatus.DownloadError)
|
if (map.Status == MapStatus.DownloadError)
|
||||||
{
|
{
|
||||||
map.Install(mapRepository, () =>
|
map.Install(mapRepository, () =>
|
||||||
{
|
{
|
||||||
map.PreloadRules();
|
|
||||||
if (orderManager != null)
|
if (orderManager != null)
|
||||||
Game.RunAfterTick(() => orderManager.IssueOrder(Order.Command("state {0}".F(Session.ClientState.NotReady))));
|
Game.RunAfterTick(() => orderManager.IssueOrder(Order.Command("state {0}".F(Session.ClientState.NotReady))));
|
||||||
});
|
});
|
||||||
@@ -149,15 +168,15 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
modData.MapCache.QueryRemoteMapDetails(mapRepository, new[] { map.Uid });
|
modData.MapCache.QueryRemoteMapDetails(mapRepository, new[] { map.Uid });
|
||||||
};
|
};
|
||||||
|
|
||||||
retry.GetText = () => getMap().Status == MapStatus.DownloadError ? "Retry Install" : "Retry Search";
|
retry.GetText = () => getMap().Map.Status == MapStatus.DownloadError ? "Retry Install" : "Retry Search";
|
||||||
}
|
}
|
||||||
|
|
||||||
var progressbar = progress.GetOrNull<ProgressBarWidget>("MAP_PROGRESSBAR");
|
var progressbar = progress.GetOrNull<ProgressBarWidget>("MAP_PROGRESSBAR");
|
||||||
if (progressbar != null)
|
if (progressbar != null)
|
||||||
{
|
{
|
||||||
progressbar.IsIndeterminate = () => getMap().DownloadPercentage == 0;
|
progressbar.IsIndeterminate = () => getMap().Map.DownloadPercentage == 0;
|
||||||
progressbar.GetPercentage = () => getMap().DownloadPercentage;
|
progressbar.GetPercentage = () => getMap().Map.DownloadPercentage;
|
||||||
progressbar.IsVisible = () => getMap().Status == MapStatus.Downloading;
|
progressbar.IsVisible = () => getMap().Map.Status == MapStatus.Downloading;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,12 +190,16 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetupWidgets(Widget parent, Func<MapPreview> getMap,
|
void SetupWidgets(Widget parent,
|
||||||
Action<MapPreviewWidget, MapPreview, MouseInput> onMouseDown, Func<Dictionary<int, SpawnOccupant>> getSpawnOccupants, Func<HashSet<int>> getDisabledSpawnPoints, bool showUnoccupiedSpawnpoints)
|
Func<(MapPreview Map, Session.MapStatus Status)> getMap,
|
||||||
|
Action<MapPreviewWidget, MapPreview, MouseInput> onMouseDown,
|
||||||
|
Func<Dictionary<int, SpawnOccupant>> getSpawnOccupants,
|
||||||
|
Func<HashSet<int>> getDisabledSpawnPoints,
|
||||||
|
bool showUnoccupiedSpawnpoints)
|
||||||
{
|
{
|
||||||
var preview = parent.Get<MapPreviewWidget>("MAP_PREVIEW");
|
var preview = parent.Get<MapPreviewWidget>("MAP_PREVIEW");
|
||||||
preview.Preview = () => getMap();
|
preview.Preview = () => getMap().Map;
|
||||||
preview.OnMouseDown = mi => onMouseDown(preview, getMap(), mi);
|
preview.OnMouseDown = mi => onMouseDown(preview, getMap().Map, mi);
|
||||||
preview.SpawnOccupants = getSpawnOccupants;
|
preview.SpawnOccupants = getSpawnOccupants;
|
||||||
preview.DisabledSpawnPoints = getDisabledSpawnPoints;
|
preview.DisabledSpawnPoints = getDisabledSpawnPoints;
|
||||||
preview.ShowUnoccupiedSpawnpoints = showUnoccupiedSpawnpoints;
|
preview.ShowUnoccupiedSpawnpoints = showUnoccupiedSpawnpoints;
|
||||||
@@ -184,18 +207,18 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
var titleLabel = parent.GetOrNull<LabelWithTooltipWidget>("MAP_TITLE");
|
var titleLabel = parent.GetOrNull<LabelWithTooltipWidget>("MAP_TITLE");
|
||||||
if (titleLabel != null)
|
if (titleLabel != null)
|
||||||
{
|
{
|
||||||
titleLabel.IsVisible = () => getMap() != MapCache.UnknownMap;
|
titleLabel.IsVisible = () => getMap().Map != MapCache.UnknownMap;
|
||||||
var font = Game.Renderer.Fonts[titleLabel.Font];
|
var font = Game.Renderer.Fonts[titleLabel.Font];
|
||||||
var title = new CachedTransform<MapPreview, string>(m => WidgetUtils.TruncateText(m.Title, titleLabel.Bounds.Width, font));
|
var title = new CachedTransform<MapPreview, string>(m => WidgetUtils.TruncateText(m.Title, titleLabel.Bounds.Width, font));
|
||||||
titleLabel.GetText = () => title.Update(getMap());
|
titleLabel.GetText = () => title.Update(getMap().Map);
|
||||||
titleLabel.GetTooltipText = () => getMap().Title;
|
titleLabel.GetTooltipText = () => getMap().Map.Title;
|
||||||
}
|
}
|
||||||
|
|
||||||
var typeLabel = parent.GetOrNull<LabelWidget>("MAP_TYPE");
|
var typeLabel = parent.GetOrNull<LabelWidget>("MAP_TYPE");
|
||||||
if (typeLabel != null)
|
if (typeLabel != null)
|
||||||
{
|
{
|
||||||
var type = new CachedTransform<MapPreview, string>(m => m.Categories.FirstOrDefault() ?? "");
|
var type = new CachedTransform<MapPreview, string>(m => m.Categories.FirstOrDefault() ?? "");
|
||||||
typeLabel.GetText = () => type.Update(getMap());
|
typeLabel.GetText = () => type.Update(getMap().Map);
|
||||||
}
|
}
|
||||||
|
|
||||||
var authorLabel = parent.GetOrNull<LabelWidget>("MAP_AUTHOR");
|
var authorLabel = parent.GetOrNull<LabelWidget>("MAP_AUTHOR");
|
||||||
@@ -204,7 +227,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
var font = Game.Renderer.Fonts[authorLabel.Font];
|
var font = Game.Renderer.Fonts[authorLabel.Font];
|
||||||
var author = new CachedTransform<MapPreview, string>(
|
var author = new CachedTransform<MapPreview, string>(
|
||||||
m => WidgetUtils.TruncateText("Created by {0}".F(m.Author), authorLabel.Bounds.Width, font));
|
m => WidgetUtils.TruncateText("Created by {0}".F(m.Author), authorLabel.Bounds.Width, font));
|
||||||
authorLabel.GetText = () => author.Update(getMap());
|
authorLabel.GetText = () => author.Update(getMap().Map);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,19 +139,16 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
if (allPreviews.Any())
|
if (allPreviews.Any())
|
||||||
SelectMap(allPreviews.First());
|
SelectMap(allPreviews.First());
|
||||||
|
|
||||||
// Preload map preview and rules to reduce jank
|
// Preload map preview to reduce jank
|
||||||
new Thread(() =>
|
new Thread(() =>
|
||||||
{
|
{
|
||||||
foreach (var p in allPreviews)
|
foreach (var p in allPreviews)
|
||||||
{
|
|
||||||
p.GetMinimap();
|
p.GetMinimap();
|
||||||
p.PreloadRules();
|
|
||||||
}
|
|
||||||
}).Start();
|
}).Start();
|
||||||
|
|
||||||
var startButton = widget.Get<ButtonWidget>("STARTGAME_BUTTON");
|
var startButton = widget.Get<ButtonWidget>("STARTGAME_BUTTON");
|
||||||
startButton.OnClick = StartMissionClicked;
|
startButton.OnClick = StartMissionClicked;
|
||||||
startButton.IsDisabled = () => selectedMap == null || selectedMap.InvalidCustomRules;
|
startButton.IsDisabled = () => selectedMap == null;
|
||||||
|
|
||||||
widget.Get<ButtonWidget>("BACK_BUTTON").OnClick = () =>
|
widget.Get<ButtonWidget>("BACK_BUTTON").OnClick = () =>
|
||||||
{
|
{
|
||||||
@@ -373,9 +370,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
{
|
{
|
||||||
StopVideo(videoPlayer);
|
StopVideo(videoPlayer);
|
||||||
|
|
||||||
if (selectedMap.InvalidCustomRules)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var orders = new List<Order>();
|
var orders = new List<Order>();
|
||||||
if (difficulty != null)
|
if (difficulty != null)
|
||||||
orders.Add(Order.Command("option difficulty {0}".F(difficulty)));
|
orders.Add(Order.Command("option difficulty {0}".F(difficulty)));
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ using System.Linq;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using OpenRA.FileFormats;
|
using OpenRA.FileFormats;
|
||||||
|
using OpenRA.Network;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
using OpenRA.Traits;
|
using OpenRA.Traits;
|
||||||
using OpenRA.Widgets;
|
using OpenRA.Widgets;
|
||||||
@@ -93,7 +94,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
Ui.LoadWidget("MAP_PREVIEW", mapPreviewRoot, new WidgetArgs
|
Ui.LoadWidget("MAP_PREVIEW", mapPreviewRoot, new WidgetArgs
|
||||||
{
|
{
|
||||||
{ "orderManager", null },
|
{ "orderManager", null },
|
||||||
{ "getMap", (Func<MapPreview>)(() => map) },
|
{ "getMap", (Func<(MapPreview, Session.MapStatus)>)(() => (map, Session.MapStatus.Playable)) },
|
||||||
{ "onMouseDown", (Action<MapPreviewWidget, MapPreview, MouseInput>)((preview, mapPreview, mi) => { }) },
|
{ "onMouseDown", (Action<MapPreviewWidget, MapPreview, MouseInput>)((preview, mapPreview, mi) => { }) },
|
||||||
{ "getSpawnOccupants", (Func<Dictionary<int, SpawnOccupant>>)(() => spawnOccupants.Update(selectedReplay)) },
|
{ "getSpawnOccupants", (Func<Dictionary<int, SpawnOccupant>>)(() => spawnOccupants.Update(selectedReplay)) },
|
||||||
{ "getDisabledSpawnPoints", (Func<HashSet<int>>)(() => disabledSpawnPoints.Update(selectedReplay)) },
|
{ "getDisabledSpawnPoints", (Func<HashSet<int>>)(() => disabledSpawnPoints.Update(selectedReplay)) },
|
||||||
@@ -623,13 +624,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (map.Status != MapStatus.Available)
|
if (map.Status == MapStatus.Unavailable && Game.Settings.Game.AllowDownloading)
|
||||||
{
|
modData.MapCache.QueryRemoteMapDetails(services.MapRepository, new[] { map.Uid });
|
||||||
if (map.Status == MapStatus.DownloadAvailable)
|
|
||||||
LoadMapPreviewRules(map);
|
|
||||||
else if (Game.Settings.Game.AllowDownloading)
|
|
||||||
modData.MapCache.QueryRemoteMapDetails(services.MapRepository, new[] { map.Uid }, LoadMapPreviewRules);
|
|
||||||
}
|
|
||||||
|
|
||||||
var players = replay.GameInfo.Players
|
var players = replay.GameInfo.Players
|
||||||
.GroupBy(p => p.Team)
|
.GroupBy(p => p.Team)
|
||||||
@@ -685,15 +681,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoadMapPreviewRules(MapPreview map)
|
|
||||||
{
|
|
||||||
new Task(() =>
|
|
||||||
{
|
|
||||||
// Force map rules to be loaded on this background thread
|
|
||||||
map.PreloadRules();
|
|
||||||
}).Start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WatchReplay()
|
void WatchReplay()
|
||||||
{
|
{
|
||||||
if (selectedReplay != null && ReplayUtils.PromptConfirmReplayCompatibility(selectedReplay))
|
if (selectedReplay != null && ReplayUtils.PromptConfirmReplayCompatibility(selectedReplay))
|
||||||
|
|||||||
@@ -16,12 +16,13 @@ set ProfileIDWhitelist=""
|
|||||||
set EnableSingleplayer=False
|
set EnableSingleplayer=False
|
||||||
set EnableSyncReports=False
|
set EnableSyncReports=False
|
||||||
set EnableGeoIP=True
|
set EnableGeoIP=True
|
||||||
|
set EnableLintChecks=True
|
||||||
set ShareAnonymizedIPs=True
|
set ShareAnonymizedIPs=True
|
||||||
|
|
||||||
set SupportDir=""
|
set SupportDir=""
|
||||||
|
|
||||||
:loop
|
:loop
|
||||||
|
|
||||||
bin\OpenRA.Server.exe Engine.EngineDir=".." Game.Mod=%Mod% Server.Name=%Name% Server.ListenPort=%ListenPort% Server.AdvertiseOnline=%AdvertiseOnline% Server.EnableSingleplayer=%EnableSingleplayer% Server.Password=%Password% Server.RecordReplays=%RecordReplays% Server.RequireAuthentication=%RequireAuthentication% Server.ProfileIDBlacklist=%ProfileIDBlacklist% Server.ProfileIDWhitelist=%ProfileIDWhitelist% Server.EnableSyncReports=%EnableSyncReports% Server.EnableGeoIP=%EnableGeoIP% Server.ShareAnonymizedIPs=%ShareAnonymizedIPs% Engine.SupportDir=%SupportDir%
|
bin\OpenRA.Server.exe Engine.EngineDir=".." Game.Mod=%Mod% Server.Name=%Name% Server.ListenPort=%ListenPort% Server.AdvertiseOnline=%AdvertiseOnline% Server.EnableSingleplayer=%EnableSingleplayer% Server.Password=%Password% Server.RecordReplays=%RecordReplays% Server.RequireAuthentication=%RequireAuthentication% Server.ProfileIDBlacklist=%ProfileIDBlacklist% Server.ProfileIDWhitelist=%ProfileIDWhitelist% Server.EnableSyncReports=%EnableSyncReports% Server.EnableGeoIP=%EnableGeoIP% Server.EnableLintChecks=%EnableLintChecks% Server.ShareAnonymizedIPs=%ShareAnonymizedIPs% Engine.SupportDir=%SupportDir%
|
||||||
|
|
||||||
goto loop
|
goto loop
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ ProfileIDWhitelist="${ProfileIDWhitelist:-""}"
|
|||||||
EnableSingleplayer="${EnableSingleplayer:-"False"}"
|
EnableSingleplayer="${EnableSingleplayer:-"False"}"
|
||||||
EnableSyncReports="${EnableSyncReports:-"False"}"
|
EnableSyncReports="${EnableSyncReports:-"False"}"
|
||||||
EnableGeoIP="${EnableGeoIP:-"True"}"
|
EnableGeoIP="${EnableGeoIP:-"True"}"
|
||||||
|
EnableLintChecks="${EnableLintChecks:-"True"}"
|
||||||
ShareAnonymizedIPs="${ShareAnonymizedIPs:-"True"}"
|
ShareAnonymizedIPs="${ShareAnonymizedIPs:-"True"}"
|
||||||
|
|
||||||
SupportDir="${SupportDir:-""}"
|
SupportDir="${SupportDir:-""}"
|
||||||
@@ -43,6 +44,7 @@ while true; do
|
|||||||
Server.ProfileIDWhitelist="$ProfileIDWhitelist" \
|
Server.ProfileIDWhitelist="$ProfileIDWhitelist" \
|
||||||
Server.EnableSyncReports="$EnableSyncReports" \
|
Server.EnableSyncReports="$EnableSyncReports" \
|
||||||
Server.EnableGeoIP="$EnableGeoIP" \
|
Server.EnableGeoIP="$EnableGeoIP" \
|
||||||
|
Server.EnableLintChecks="$EnableLintChecks" \
|
||||||
Server.ShareAnonymizedIPs="$ShareAnonymizedIPs" \
|
Server.ShareAnonymizedIPs="$ShareAnonymizedIPs" \
|
||||||
Engine.SupportDir="$SupportDir"
|
Engine.SupportDir="$SupportDir"
|
||||||
done
|
done
|
||||||
|
|||||||
@@ -77,6 +77,42 @@ Container@MAP_PREVIEW:
|
|||||||
Font: Tiny
|
Font: Tiny
|
||||||
Align: Center
|
Align: Center
|
||||||
Text: with this version of OpenRA
|
Text: with this version of OpenRA
|
||||||
|
Container@MAP_VALIDATING:
|
||||||
|
Width: PARENT_RIGHT
|
||||||
|
Height: PARENT_BOTTOM
|
||||||
|
Children:
|
||||||
|
Background@MAP_BG:
|
||||||
|
Width: PARENT_RIGHT
|
||||||
|
Height: 158
|
||||||
|
Background: panel-gray
|
||||||
|
Children:
|
||||||
|
MapPreview@MAP_PREVIEW:
|
||||||
|
X: 1
|
||||||
|
Y: 1
|
||||||
|
Width: PARENT_RIGHT - 2
|
||||||
|
Height: PARENT_BOTTOM - 2
|
||||||
|
TooltipContainer: TOOLTIP_CONTAINER
|
||||||
|
LabelWithTooltip@MAP_TITLE:
|
||||||
|
Y: 159
|
||||||
|
Width: PARENT_RIGHT
|
||||||
|
Height: 25
|
||||||
|
Font: Bold
|
||||||
|
Align: Center
|
||||||
|
TooltipContainer: TOOLTIP_CONTAINER
|
||||||
|
TooltipTemplate: SIMPLE_TOOLTIP
|
||||||
|
Label@MAP_STATUS_VALIDATING:
|
||||||
|
Y: 174
|
||||||
|
Width: PARENT_RIGHT
|
||||||
|
Height: 25
|
||||||
|
Font: Tiny
|
||||||
|
Align: Center
|
||||||
|
Text: Validating...
|
||||||
|
IgnoreMouseOver: true
|
||||||
|
ProgressBar@MAP_PROGRESSBAR:
|
||||||
|
Y: 194
|
||||||
|
Width: PARENT_RIGHT
|
||||||
|
Height: 25
|
||||||
|
Indeterminate: True
|
||||||
Container@MAP_DOWNLOADABLE:
|
Container@MAP_DOWNLOADABLE:
|
||||||
Width: PARENT_RIGHT
|
Width: PARENT_RIGHT
|
||||||
Height: PARENT_BOTTOM
|
Height: PARENT_BOTTOM
|
||||||
|
|||||||
@@ -77,6 +77,42 @@ Container@MAP_PREVIEW:
|
|||||||
Font: Tiny
|
Font: Tiny
|
||||||
Align: Center
|
Align: Center
|
||||||
Text: with this version of OpenRA
|
Text: with this version of OpenRA
|
||||||
|
Container@MAP_VALIDATING:
|
||||||
|
Width: PARENT_RIGHT
|
||||||
|
Height: PARENT_BOTTOM
|
||||||
|
Children:
|
||||||
|
Background@MAP_BG:
|
||||||
|
Width: PARENT_RIGHT
|
||||||
|
Height: 158
|
||||||
|
Background: dialog3
|
||||||
|
Children:
|
||||||
|
MapPreview@MAP_PREVIEW:
|
||||||
|
X: 1
|
||||||
|
Y: 1
|
||||||
|
Width: PARENT_RIGHT - 2
|
||||||
|
Height: PARENT_BOTTOM - 2
|
||||||
|
TooltipContainer: TOOLTIP_CONTAINER
|
||||||
|
LabelWithTooltip@MAP_TITLE:
|
||||||
|
Y: 159
|
||||||
|
Width: PARENT_RIGHT
|
||||||
|
Height: 25
|
||||||
|
Font: Bold
|
||||||
|
Align: Center
|
||||||
|
TooltipContainer: TOOLTIP_CONTAINER
|
||||||
|
TooltipTemplate: SIMPLE_TOOLTIP
|
||||||
|
Label@MAP_STATUS_VALIDATING:
|
||||||
|
Y: 174
|
||||||
|
Width: PARENT_RIGHT
|
||||||
|
Height: 25
|
||||||
|
Font: Tiny
|
||||||
|
Align: Center
|
||||||
|
Text: Validating...
|
||||||
|
IgnoreMouseOver: true
|
||||||
|
ProgressBar@MAP_PROGRESSBAR:
|
||||||
|
Y: 194
|
||||||
|
Width: PARENT_RIGHT
|
||||||
|
Height: 25
|
||||||
|
Indeterminate: True
|
||||||
Container@MAP_DOWNLOADABLE:
|
Container@MAP_DOWNLOADABLE:
|
||||||
Width: PARENT_RIGHT
|
Width: PARENT_RIGHT
|
||||||
Height: PARENT_BOTTOM
|
Height: PARENT_BOTTOM
|
||||||
|
|||||||
Reference in New Issue
Block a user