Refactor GameSpeed setting

*Remove internal GameSpeed defaults
 Enforce setting values explicitly all the time
 Require definition of a DefaultSpeed

*Remove Global.Timestep default

*Remove the hacky Timestep/OrderLatency setting via LobbyInfo

*Fix shellmaps ignoring mod-defined gamespeeds

*Make DateTimeGlobal use the MapOptions gamespeed
This commit is contained in:
reaperrr
2021-04-02 14:11:45 +02:00
committed by Paul Chote
parent fe129956bb
commit 1a9dfc0893
22 changed files with 214 additions and 205 deletions

View File

@@ -162,8 +162,12 @@ namespace OpenRA
using (new PerfTimer("PrepareMap")) using (new PerfTimer("PrepareMap"))
map = ModData.PrepareMap(mapUID); map = ModData.PrepareMap(mapUID);
using (new PerfTimer("NewWorld")) using (new PerfTimer("NewWorld"))
{
OrderManager.World = new World(ModData, map, OrderManager, type); OrderManager.World = new World(ModData, map, OrderManager, type);
OrderManager.FramesAhead = OrderManager.World.OrderLatency;
}
OrderManager.World.GameOver += FinishBenchmark; OrderManager.World.GameOver += FinishBenchmark;
@@ -581,7 +585,11 @@ namespace OpenRA
Cursor.Tick(); Cursor.Tick();
} }
var worldTimestep = world == null ? Ui.Timestep : world.IsLoadingGameSave ? 1 : world.Timestep; var worldTimestep = world == null ? Ui.Timestep :
world.IsLoadingGameSave ? 1 :
world.IsReplay ? world.ReplayTimestep :
world.Timestep;
var worldTickDelta = tick - orderManager.LastTickTime; var worldTickDelta = tick - orderManager.LastTickTime;
if (worldTimestep != 0 && worldTickDelta >= worldTimestep) if (worldTimestep != 0 && worldTickDelta >= worldTimestep)
{ {
@@ -776,9 +784,14 @@ namespace OpenRA
while (state == RunStatus.Running) while (state == RunStatus.Running)
{ {
// Ideal time between logic updates. Timestep = 0 means the game is paused var logicInterval = Ui.Timestep;
// but we still call LogicTick() because it handles pausing internally. var logicWorld = worldRenderer?.World;
var logicInterval = worldRenderer != null && worldRenderer.World.Timestep != 0 ? worldRenderer.World.Timestep : Ui.Timestep;
// ReplayTimestep = 0 means the replay is paused: we need to keep logicInterval as UI.Timestep to avoid breakage
if (logicWorld != null && !(logicWorld.IsReplay && logicWorld.ReplayTimestep == 0))
logicInterval = logicWorld.IsLoadingGameSave ? 1 :
logicWorld.IsReplay ? logicWorld.ReplayTimestep :
logicWorld.Timestep;
// Ideal time between screen updates // Ideal time between screen updates
var maxFramerate = Settings.Graphics.CapFramerate ? Settings.Graphics.MaxFramerate.Clamp(1, 1000) : 1000; var maxFramerate = Settings.Graphics.CapFramerate ? Settings.Graphics.MaxFramerate.Clamp(1, 1000) : 1000;

View File

@@ -10,26 +10,49 @@
#endregion #endregion
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
namespace OpenRA namespace OpenRA
{ {
public class GameSpeed public class GameSpeed
{ {
public readonly string Name = "Default"; [FieldLoader.Require]
public readonly int Timestep = 40; public readonly string Name;
public readonly int OrderLatency = 3;
[FieldLoader.Require]
public readonly int Timestep;
[FieldLoader.Require]
public readonly int OrderLatency;
} }
public class GameSpeeds : IGlobalModData public class GameSpeeds : IGlobalModData
{ {
[FieldLoader.Require]
public readonly string DefaultSpeed;
[FieldLoader.LoadUsing(nameof(LoadSpeeds))] [FieldLoader.LoadUsing(nameof(LoadSpeeds))]
public readonly Dictionary<string, GameSpeed> Speeds; public readonly Dictionary<string, GameSpeed> Speeds;
static object LoadSpeeds(MiniYaml y) static object LoadSpeeds(MiniYaml y)
{ {
var ret = new Dictionary<string, GameSpeed>(); var ret = new Dictionary<string, GameSpeed>();
foreach (var node in y.Nodes) var speedsNode = y.Nodes.FirstOrDefault(n => n.Key == "Speeds");
ret.Add(node.Key, FieldLoader.Load<GameSpeed>(node.Value)); if (speedsNode == null)
throw new YamlException("Error parsing GameSpeeds: Missing Speeds node!");
foreach (var node in speedsNode.Value.Nodes)
{
try
{
ret.Add(node.Key, FieldLoader.Load<GameSpeed>(node.Value));
}
catch (FieldLoader.MissingFieldsException e)
{
var label = e.Missing.Length > 1 ? "Required properties missing" : "Required property missing";
throw new YamlException("Error parsing GameSpeed {0}: {1}: {2}".F(node.Key, label, e.Missing.JoinWith(", ")));
}
}
return ret; return ret;
} }

View File

@@ -27,7 +27,10 @@ namespace OpenRA.Network
Queue<Chunk> chunks = new Queue<Chunk>(); Queue<Chunk> chunks = new Queue<Chunk>();
List<byte[]> sync = new List<byte[]>(); List<byte[]> sync = new List<byte[]>();
readonly int orderLatency;
int ordersFrame; int ordersFrame;
Dictionary<int, int> lastClientsFrame = new Dictionary<int, int>(); Dictionary<int, int> lastClientsFrame = new Dictionary<int, int>();
public int LocalClientId => -1; public int LocalClientId => -1;
@@ -122,7 +125,10 @@ namespace OpenRA.Network
} }
} }
ordersFrame = LobbyInfo.GlobalSettings.OrderLatency; var gameSpeeds = Game.ModData.Manifest.Get<GameSpeeds>();
var gameSpeedName = LobbyInfo.GlobalSettings.OptionOrDefault("gamespeed", gameSpeeds.DefaultSpeed);
orderLatency = gameSpeeds.Speeds[gameSpeedName].OrderLatency;
ordersFrame = orderLatency;
} }
// Do nothing: ignore locally generated orders // Do nothing: ignore locally generated orders
@@ -137,7 +143,7 @@ namespace OpenRA.Network
sync.Add(ms.GetBuffer()); sync.Add(ms.GetBuffer());
// Store the current frame so Receive() can return the next chunk of orders. // Store the current frame so Receive() can return the next chunk of orders.
ordersFrame = frame + LobbyInfo.GlobalSettings.OrderLatency; ordersFrame = frame + orderLatency;
} }
public void Receive(Action<int, byte[]> packetFn) public void Receive(Action<int, byte[]> packetFn)

View File

@@ -222,8 +222,6 @@ namespace OpenRA.Network
{ {
public string ServerName; public string ServerName;
public string Map; public string Map;
public int Timestep = 40;
public int OrderLatency = 3; // net tick frames (x 120 = ms)
public int RandomSeed = 0; public int RandomSeed = 0;
public bool AllowSpectators = true; public bool AllowSpectators = true;
public string GameUid; public string GameUid;

View File

@@ -254,7 +254,6 @@ namespace OpenRA.Network
case "SyncInfo": case "SyncInfo":
{ {
orderManager.LobbyInfo = Session.Deserialize(order.TargetString); orderManager.LobbyInfo = Session.Deserialize(order.TargetString);
SetOrderLag(orderManager);
Game.SyncLobbyInfo(); Game.SyncLobbyInfo();
break; break;
} }
@@ -304,7 +303,6 @@ namespace OpenRA.Network
orderManager.LobbyInfo.GlobalSettings = Session.Global.Deserialize(node.Value); orderManager.LobbyInfo.GlobalSettings = Session.Global.Deserialize(node.Value);
} }
SetOrderLag(orderManager);
Game.SyncLobbyInfo(); Game.SyncLobbyInfo();
break; break;
} }
@@ -354,14 +352,5 @@ namespace OpenRA.Network
if (world.OrderValidators.All(vo => vo.OrderValidation(orderManager, world, clientId, order))) if (world.OrderValidators.All(vo => vo.OrderValidation(orderManager, world, clientId, order)))
order.Subject.ResolveOrder(order); order.Subject.ResolveOrder(order);
} }
static void SetOrderLag(OrderManager o)
{
if (o.FramesAhead != o.LobbyInfo.GlobalSettings.OrderLatency && !o.GameStarted)
{
o.FramesAhead = o.LobbyInfo.GlobalSettings.OrderLatency;
Log.Write("server", "Order lag is now {0} frames.", o.LobbyInfo.GlobalSettings.OrderLatency);
}
}
} }
} }

View File

@@ -1200,10 +1200,6 @@ namespace OpenRA.Server
DropClient(c); DropClient(c);
} }
// HACK: Turn down the latency if there is only one real player
if (LobbyInfo.NonBotClients.Count() == 1)
LobbyInfo.GlobalSettings.OrderLatency = 1;
// Enable game saves for singleplayer missions only // Enable game saves for singleplayer missions only
// TODO: Enable for multiplayer (non-dedicated servers only) once the lobby UI has been created // TODO: Enable for multiplayer (non-dedicated servers only) once the lobby UI has been created
LobbyInfo.GlobalSettings.GameSavesEnabled = Type != ServerType.Dedicated && LobbyInfo.NonBotClients.Count() == 1; LobbyInfo.GlobalSettings.GameSavesEnabled = Type != ServerType.Dedicated && LobbyInfo.NonBotClients.Count() == 1;

View File

@@ -36,7 +36,12 @@ namespace OpenRA
readonly Queue<Action<World>> frameEndActions = new Queue<Action<World>>(); readonly Queue<Action<World>> frameEndActions = new Queue<Action<World>>();
public int Timestep; public readonly GameSpeed GameSpeed;
public readonly int Timestep;
public readonly int OrderLatency;
public int ReplayTimestep;
internal readonly OrderManager OrderManager; internal readonly OrderManager OrderManager;
public Session LobbyInfo => OrderManager.LobbyInfo; public Session LobbyInfo => OrderManager.LobbyInfo;
@@ -180,7 +185,18 @@ namespace OpenRA
OrderManager = orderManager; OrderManager = orderManager;
orderGenerator = new UnitOrderGenerator(); orderGenerator = new UnitOrderGenerator();
Map = map; Map = map;
Timestep = orderManager.LobbyInfo.GlobalSettings.Timestep;
var gameSpeeds = modData.Manifest.Get<GameSpeeds>();
var gameSpeedName = orderManager.LobbyInfo.GlobalSettings.OptionOrDefault("gamespeed", gameSpeeds.DefaultSpeed);
GameSpeed = gameSpeeds.Speeds[gameSpeedName];
Timestep = ReplayTimestep = GameSpeed.Timestep;
OrderLatency = GameSpeed.OrderLatency;
// HACK: Turn down the latency if there is only one real player/spectator
if (orderManager.LobbyInfo.NonBotClients.Count() == 1)
OrderLatency = 1;
SharedRandom = new MersenneTwister(orderManager.LobbyInfo.GlobalSettings.RandomSeed); SharedRandom = new MersenneTwister(orderManager.LobbyInfo.GlobalSettings.RandomSeed);
LocalRandom = new MersenneTwister(); LocalRandom = new MersenneTwister();

View File

@@ -20,11 +20,15 @@ namespace OpenRA.Mods.Common.Scripting
public class DateGlobal : ScriptGlobal public class DateGlobal : ScriptGlobal
{ {
readonly TimeLimitManager tlm; readonly TimeLimitManager tlm;
readonly int ticksPerSecond;
public DateGlobal(ScriptContext context) public DateGlobal(ScriptContext context)
: base(context) : base(context)
{ {
tlm = context.World.WorldActor.TraitOrDefault<TimeLimitManager>(); tlm = context.World.WorldActor.TraitOrDefault<TimeLimitManager>();
var gameSpeeds = Game.ModData.Manifest.Get<GameSpeeds>();
var defaultGameSpeed = gameSpeeds.Speeds[gameSpeeds.DefaultSpeed];
ticksPerSecond = 1000 / defaultGameSpeed.Timestep;
} }
[Desc("True on the 31st of October.")] [Desc("True on the 31st of October.")]
@@ -36,7 +40,7 @@ namespace OpenRA.Mods.Common.Scripting
[Desc("Converts the number of seconds into game time (ticks).")] [Desc("Converts the number of seconds into game time (ticks).")]
public int Seconds(int seconds) public int Seconds(int seconds)
{ {
return seconds * 25; return seconds * ticksPerSecond;
} }
[Desc("Converts the number of minutes into game time (ticks).")] [Desc("Converts the number of minutes into game time (ticks).")]

View File

@@ -563,13 +563,6 @@ namespace OpenRA.Mods.Common.Server
oo.Value = oo.PreferredValue = split[1]; oo.Value = oo.PreferredValue = split[1];
if (option.Id == "gamespeed")
{
var speed = server.ModData.Manifest.Get<GameSpeeds>().Speeds[oo.Value];
server.LobbyInfo.GlobalSettings.Timestep = speed.Timestep;
server.LobbyInfo.GlobalSettings.OrderLatency = speed.OrderLatency;
}
server.SyncLobbyGlobalSettings(); server.SyncLobbyGlobalSettings();
server.SendMessage(option.ValueChangedMessage(client.Name, split[1])); server.SendMessage(option.ValueChangedMessage(client.Name, split[1]));
@@ -1080,13 +1073,6 @@ namespace OpenRA.Mods.Common.Server
state.Value = value; state.Value = value;
state.PreferredValue = preferredValue; state.PreferredValue = preferredValue;
gs.LobbyOptions[o.Id] = state; gs.LobbyOptions[o.Id] = state;
if (o.Id == "gamespeed")
{
var speed = server.ModData.Manifest.Get<GameSpeeds>().Speeds[value];
gs.Timestep = speed.Timestep;
gs.OrderLatency = speed.OrderLatency;
}
} }
} }
} }

View File

@@ -112,7 +112,7 @@ namespace OpenRA.Mods.Common.Traits
var randomFactor = world.LocalRandom.Next(0, baseBuilder.Info.StructureProductionRandomBonusDelay); var randomFactor = world.LocalRandom.Next(0, baseBuilder.Info.StructureProductionRandomBonusDelay);
// Needs to be at least 4 * OrderLatency because otherwise the AI frequently duplicates build orders (i.e. makes the same build decision twice) // Needs to be at least 4 * OrderLatency because otherwise the AI frequently duplicates build orders (i.e. makes the same build decision twice)
waitTicks = active ? 4 * world.LobbyInfo.GlobalSettings.OrderLatency + baseBuilder.Info.StructureProductionActiveDelay + randomFactor waitTicks = active ? 4 * world.OrderLatency + baseBuilder.Info.StructureProductionActiveDelay + randomFactor
: baseBuilder.Info.StructureProductionInactiveDelay + randomFactor; : baseBuilder.Info.StructureProductionInactiveDelay + randomFactor;
} }

View File

@@ -58,7 +58,6 @@ namespace OpenRA.Mods.Common.Traits
int lastIncome; int lastIncome;
int lastIncomeTick; int lastIncomeTick;
int ticks; int ticks;
int replayTimestep;
bool armyGraphDisabled; bool armyGraphDisabled;
bool incomeGraphDisabled; bool incomeGraphDisabled;
@@ -81,7 +80,7 @@ namespace OpenRA.Mods.Common.Traits
{ {
ticks++; ticks++;
var timestep = self.World.IsReplay ? replayTimestep : self.World.Timestep; var timestep = self.World.Timestep;
if (ticks * timestep >= 30000) if (ticks * timestep >= 30000)
{ {
ticks = 0; ticks = 0;
@@ -124,9 +123,6 @@ namespace OpenRA.Mods.Common.Traits
public void WorldLoaded(World w, WorldRenderer wr) public void WorldLoaded(World w, WorldRenderer wr)
{ {
if (w.IsReplay)
replayTimestep = w.WorldActor.Trait<MapOptions>().GameSpeed.Timestep;
if (!armyGraphDisabled) if (!armyGraphDisabled)
ArmySamples.Add(ArmyValue); ArmySamples.Add(ArmyValue);

View File

@@ -93,6 +93,7 @@ namespace OpenRA.Mods.Common.Traits
public class TimeLimitManager : INotifyTimeLimit, ITick, IWorldLoaded public class TimeLimitManager : INotifyTimeLimit, ITick, IWorldLoaded
{ {
readonly TimeLimitManagerInfo info; readonly TimeLimitManagerInfo info;
readonly int ticksPerSecond;
MapOptions mapOptions; MapOptions mapOptions;
LabelWidget countdownLabel; LabelWidget countdownLabel;
CachedTransform<int, string> countdown; CachedTransform<int, string> countdown;
@@ -105,13 +106,14 @@ namespace OpenRA.Mods.Common.Traits
{ {
this.info = info; this.info = info;
Notification = info.Notification; Notification = info.Notification;
ticksPerSecond = 1000 / self.World.Timestep;
var tl = self.World.LobbyInfo.GlobalSettings.OptionOrDefault("timelimit", info.TimeLimitDefault.ToString()); var tl = self.World.LobbyInfo.GlobalSettings.OptionOrDefault("timelimit", info.TimeLimitDefault.ToString());
if (!int.TryParse(tl, out TimeLimit)) if (!int.TryParse(tl, out TimeLimit))
TimeLimit = info.TimeLimitDefault; TimeLimit = info.TimeLimitDefault;
// Convert from minutes to ticks // Convert from minutes to ticks
TimeLimit *= 60 * (1000 / self.World.Timestep); TimeLimit *= 60 * ticksPerSecond;
} }
void IWorldLoaded.WorldLoaded(World w, OpenRA.Graphics.WorldRenderer wr) void IWorldLoaded.WorldLoaded(World w, OpenRA.Graphics.WorldRenderer wr)
@@ -124,7 +126,7 @@ namespace OpenRA.Mods.Common.Traits
if (countdownLabel != null) if (countdownLabel != null)
{ {
countdown = new CachedTransform<int, string>(t => countdown = new CachedTransform<int, string>(t =>
info.CountdownText.F(WidgetUtils.FormatTime(t, true, w.IsReplay ? mapOptions.GameSpeed.Timestep : w.Timestep))); info.CountdownText.F(WidgetUtils.FormatTime(t, true, w.Timestep)));
countdownLabel.GetText = () => countdown.Update(ticksRemaining); countdownLabel.GetText = () => countdown.Update(ticksRemaining);
} }
} }
@@ -134,7 +136,6 @@ namespace OpenRA.Mods.Common.Traits
if (TimeLimit <= 0) if (TimeLimit <= 0)
return; return;
var ticksPerSecond = 1000 / (self.World.IsReplay ? mapOptions.GameSpeed.Timestep : self.World.Timestep);
ticksRemaining = TimeLimit - self.World.WorldTick; ticksRemaining = TimeLimit - self.World.WorldTick;
if (ticksRemaining == 0) if (ticksRemaining == 0)

View File

@@ -60,8 +60,8 @@ namespace OpenRA.Mods.Common.Traits
[Desc("Description of the game speed option in the lobby.")] [Desc("Description of the game speed option in the lobby.")]
public readonly string GameSpeedDropdownDescription = "Change the rate at which time passes"; public readonly string GameSpeedDropdownDescription = "Change the rate at which time passes";
[Desc("Default game speed.")] [Desc("Default game speed (leave empty to use the default defined in mod.yaml).")]
public readonly string GameSpeed = "default"; public readonly string GameSpeed = null;
[Desc("Prevent the game speed from being changed in the lobby.")] [Desc("Prevent the game speed from being changed in the lobby.")]
public readonly bool GameSpeedDropdownLocked = false; public readonly bool GameSpeedDropdownLocked = false;
@@ -84,18 +84,18 @@ namespace OpenRA.Mods.Common.Traits
yield return new LobbyOption("techlevel", TechLevelDropdownLabel, TechLevelDropdownDescription, TechLevelDropdownVisible, TechLevelDropdownDisplayOrder, yield return new LobbyOption("techlevel", TechLevelDropdownLabel, TechLevelDropdownDescription, TechLevelDropdownVisible, TechLevelDropdownDisplayOrder,
techLevels, TechLevel, TechLevelDropdownLocked); techLevels, TechLevel, TechLevelDropdownLocked);
var gameSpeeds = Game.ModData.Manifest.Get<GameSpeeds>().Speeds var gameSpeeds = Game.ModData.Manifest.Get<GameSpeeds>();
.ToDictionary(s => s.Key, s => s.Value.Name); var speeds = gameSpeeds.Speeds.ToDictionary(s => s.Key, s => s.Value.Name);
// NOTE: The server hardcodes special-case logic for this option id // NOTE: This is just exposing the UI, the backend logic for this option is hardcoded in World
yield return new LobbyOption("gamespeed", GameSpeedDropdownLabel, GameSpeedDropdownDescription, GameSpeedDropdownVisible, GameSpeedDropdownDisplayOrder, yield return new LobbyOption("gamespeed", GameSpeedDropdownLabel, GameSpeedDropdownDescription, GameSpeedDropdownVisible, GameSpeedDropdownDisplayOrder,
gameSpeeds, GameSpeed, GameSpeedDropdownLocked); speeds, GameSpeed ?? gameSpeeds.DefaultSpeed, GameSpeedDropdownLocked);
} }
void IRulesetLoaded<ActorInfo>.RulesetLoaded(Ruleset rules, ActorInfo info) void IRulesetLoaded<ActorInfo>.RulesetLoaded(Ruleset rules, ActorInfo info)
{ {
var gameSpeeds = Game.ModData.Manifest.Get<GameSpeeds>().Speeds; var gameSpeeds = Game.ModData.Manifest.Get<GameSpeeds>().Speeds;
if (!gameSpeeds.ContainsKey(GameSpeed)) if (GameSpeed != null && !gameSpeeds.ContainsKey(GameSpeed))
throw new YamlException("Invalid default game speed '{0}'.".F(GameSpeed)); throw new YamlException("Invalid default game speed '{0}'.".F(GameSpeed));
} }
@@ -108,7 +108,6 @@ namespace OpenRA.Mods.Common.Traits
public bool ShortGame { get; private set; } public bool ShortGame { get; private set; }
public string TechLevel { get; private set; } public string TechLevel { get; private set; }
public GameSpeed GameSpeed { get; private set; }
public MapOptions(MapOptionsInfo info) public MapOptions(MapOptionsInfo info)
{ {
@@ -122,11 +121,6 @@ namespace OpenRA.Mods.Common.Traits
TechLevel = self.World.LobbyInfo.GlobalSettings TechLevel = self.World.LobbyInfo.GlobalSettings
.OptionOrDefault("techlevel", info.TechLevel); .OptionOrDefault("techlevel", info.TechLevel);
var speed = self.World.LobbyInfo.GlobalSettings
.OptionOrDefault("gamespeed", info.GameSpeed);
GameSpeed = Game.ModData.Manifest.Get<GameSpeeds>().Speeds[speed];
} }
} }
} }

View File

@@ -26,27 +26,22 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var tlm = world.WorldActor.TraitOrDefault<TimeLimitManager>(); var tlm = world.WorldActor.TraitOrDefault<TimeLimitManager>();
var startTick = Ui.LastTickTime; var startTick = Ui.LastTickTime;
Func<bool> shouldShowStatus = () => (world.Paused || world.Timestep != world.LobbyInfo.GlobalSettings.Timestep) Func<bool> shouldShowStatus = () => (world.Paused || world.ReplayTimestep != world.Timestep)
&& (Ui.LastTickTime - startTick) / 1000 % 2 == 0; && (Ui.LastTickTime - startTick) / 1000 % 2 == 0;
Func<string> statusText = () => Func<string> statusText = () =>
{ {
if (world.Paused || world.Timestep == 0) if (world.Paused || world.ReplayTimestep == 0)
return "Paused"; return "Paused";
if (world.Timestep == 1) if (world.ReplayTimestep == 1)
return "Max Speed"; return "Max Speed";
return "{0}% Speed".F(world.LobbyInfo.GlobalSettings.Timestep * 100 / world.Timestep); return "{0}% Speed".F(world.Timestep * 100 / world.ReplayTimestep);
}; };
if (timer != null) if (timer != null)
{ {
// Timers in replays should be synced to the effective game time, not the playback time.
var timestep = world.Timestep;
if (world.IsReplay)
timestep = world.WorldActor.Trait<MapOptions>().GameSpeed.Timestep;
timer.GetText = () => timer.GetText = () =>
{ {
if (status == null && shouldShowStatus()) if (status == null && shouldShowStatus())
@@ -54,7 +49,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var timeLimit = tlm?.TimeLimit ?? 0; var timeLimit = tlm?.TimeLimit ?? 0;
var displayTick = timeLimit > 0 ? timeLimit - world.WorldTick : world.WorldTick; var displayTick = timeLimit > 0 ? timeLimit - world.WorldTick : world.WorldTick;
return WidgetUtils.FormatTime(Math.Max(0, displayTick), timestep); return WidgetUtils.FormatTime(Math.Max(0, displayTick), world.Timestep);
}; };
} }

View File

@@ -46,12 +46,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic
var originalTimestep = world.Timestep; var originalTimestep = world.Timestep;
var pauseButton = widget.Get<ButtonWidget>("BUTTON_PAUSE"); var pauseButton = widget.Get<ButtonWidget>("BUTTON_PAUSE");
pauseButton.IsVisible = () => world.Timestep != 0 && orderManager.NetFrameNumber < replayNetTicks; pauseButton.IsVisible = () => world.ReplayTimestep != 0 && orderManager.NetFrameNumber < replayNetTicks;
pauseButton.OnClick = () => world.Timestep = 0; pauseButton.OnClick = () => world.ReplayTimestep = 0;
var playButton = widget.Get<ButtonWidget>("BUTTON_PLAY"); var playButton = widget.Get<ButtonWidget>("BUTTON_PLAY");
playButton.IsVisible = () => world.Timestep == 0 || orderManager.NetFrameNumber >= replayNetTicks; playButton.IsVisible = () => world.ReplayTimestep == 0 || orderManager.NetFrameNumber >= replayNetTicks;
playButton.OnClick = () => world.Timestep = (int)Math.Ceiling(originalTimestep * multipliers[speed]); playButton.OnClick = () => world.ReplayTimestep = (int)Math.Ceiling(originalTimestep * multipliers[speed]);
playButton.IsDisabled = () => orderManager.NetFrameNumber >= replayNetTicks; playButton.IsDisabled = () => orderManager.NetFrameNumber >= replayNetTicks;
var slowButton = widget.Get<ButtonWidget>("BUTTON_SLOW"); var slowButton = widget.Get<ButtonWidget>("BUTTON_SLOW");
@@ -60,8 +60,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
slowButton.OnClick = () => slowButton.OnClick = () =>
{ {
speed = PlaybackSpeed.Slow; speed = PlaybackSpeed.Slow;
if (world.Timestep != 0) if (world.ReplayTimestep != 0)
world.Timestep = (int)Math.Ceiling(originalTimestep * multipliers[speed]); world.ReplayTimestep = (int)Math.Ceiling(originalTimestep * multipliers[speed]);
}; };
var normalSpeedButton = widget.Get<ButtonWidget>("BUTTON_REGULAR"); var normalSpeedButton = widget.Get<ButtonWidget>("BUTTON_REGULAR");
@@ -70,8 +70,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
normalSpeedButton.OnClick = () => normalSpeedButton.OnClick = () =>
{ {
speed = PlaybackSpeed.Regular; speed = PlaybackSpeed.Regular;
if (world.Timestep != 0) if (world.ReplayTimestep != 0)
world.Timestep = (int)Math.Ceiling(originalTimestep * multipliers[speed]); world.ReplayTimestep = (int)Math.Ceiling(originalTimestep * multipliers[speed]);
}; };
var fastButton = widget.Get<ButtonWidget>("BUTTON_FAST"); var fastButton = widget.Get<ButtonWidget>("BUTTON_FAST");
@@ -80,8 +80,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
fastButton.OnClick = () => fastButton.OnClick = () =>
{ {
speed = PlaybackSpeed.Fast; speed = PlaybackSpeed.Fast;
if (world.Timestep != 0) if (world.ReplayTimestep != 0)
world.Timestep = (int)Math.Ceiling(originalTimestep * multipliers[speed]); world.ReplayTimestep = (int)Math.Ceiling(originalTimestep * multipliers[speed]);
}; };
var maximumButton = widget.Get<ButtonWidget>("BUTTON_MAXIMUM"); var maximumButton = widget.Get<ButtonWidget>("BUTTON_MAXIMUM");
@@ -90,8 +90,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
maximumButton.OnClick = () => maximumButton.OnClick = () =>
{ {
speed = PlaybackSpeed.Maximum; speed = PlaybackSpeed.Maximum;
if (world.Timestep != 0) if (world.ReplayTimestep != 0)
world.Timestep = (int)Math.Ceiling(originalTimestep * multipliers[speed]); world.ReplayTimestep = (int)Math.Ceiling(originalTimestep * multipliers[speed]);
}; };
} }
} }

View File

@@ -27,7 +27,6 @@ namespace OpenRA.Mods.Common.Widgets
public Func<Player> GetPlayer; public Func<Player> GetPlayer;
readonly World world; readonly World world;
readonly WorldRenderer worldRenderer; readonly WorldRenderer worldRenderer;
readonly int timestep;
public int IconWidth = 32; public int IconWidth = 32;
public int IconHeight = 24; public int IconHeight = 24;
@@ -56,7 +55,6 @@ namespace OpenRA.Mods.Common.Widgets
this.world = world; this.world = world;
this.worldRenderer = worldRenderer; this.worldRenderer = worldRenderer;
clocks = new Dictionary<ProductionQueue, Animation>(); clocks = new Dictionary<ProductionQueue, Animation>();
timestep = world.IsReplay ? world.WorldActor.Trait<MapOptions>().GameSpeed.Timestep : world.Timestep;
GetTooltipIcon = () => TooltipIcon; GetTooltipIcon = () => TooltipIcon;
tooltipContainer = Exts.Lazy(() => tooltipContainer = Exts.Lazy(() =>
Ui.Root.Get<TooltipContainerWidget>(TooltipContainer)); Ui.Root.Get<TooltipContainerWidget>(TooltipContainer));
@@ -69,7 +67,6 @@ namespace OpenRA.Mods.Common.Widgets
GetPlayer = other.GetPlayer; GetPlayer = other.GetPlayer;
world = other.world; world = other.world;
worldRenderer = other.worldRenderer; worldRenderer = other.worldRenderer;
timestep = other.timestep;
clocks = other.clocks; clocks = other.clocks;
IconWidth = other.IconWidth; IconWidth = other.IconWidth;
@@ -198,7 +195,7 @@ namespace OpenRA.Mods.Common.Widgets
foreach (var icon in productionIcons) foreach (var icon in productionIcons)
{ {
var current = icon.Queued.First(); var current = icon.Queued.First();
var text = GetOverlayForItem(current, timestep); var text = GetOverlayForItem(current, world.Timestep);
tiny.DrawTextWithContrast(text, tiny.DrawTextWithContrast(text,
icon.Pos + new float2(16, 12) - new float2(tiny.Measure(text).X / 2, 0), icon.Pos + new float2(16, 12) - new float2(tiny.Measure(text).X / 2, 0),
Color.White, Color.Black, 1); Color.White, Color.Black, 1);

View File

@@ -26,7 +26,6 @@ namespace OpenRA.Mods.Common.Widgets
readonly World world; readonly World world;
readonly WorldRenderer worldRenderer; readonly WorldRenderer worldRenderer;
readonly Dictionary<string, Animation> clocks; readonly Dictionary<string, Animation> clocks;
readonly int timestep;
readonly Lazy<TooltipContainerWidget> tooltipContainer; readonly Lazy<TooltipContainerWidget> tooltipContainer;
@@ -55,11 +54,6 @@ namespace OpenRA.Mods.Common.Widgets
this.worldRenderer = worldRenderer; this.worldRenderer = worldRenderer;
clocks = new Dictionary<string, Animation>(); clocks = new Dictionary<string, Animation>();
// Timers in replays should be synced to the effective game time, not the playback time.
timestep = world.Timestep;
if (world.IsReplay)
timestep = world.WorldActor.Trait<MapOptions>().GameSpeed.Timestep;
tooltipContainer = Exts.Lazy(() => tooltipContainer = Exts.Lazy(() =>
Ui.Root.Get<TooltipContainerWidget>(TooltipContainer)); Ui.Root.Get<TooltipContainerWidget>(TooltipContainer));
} }
@@ -72,7 +66,6 @@ namespace OpenRA.Mods.Common.Widgets
world = other.world; world = other.world;
worldRenderer = other.worldRenderer; worldRenderer = other.worldRenderer;
clocks = other.clocks; clocks = other.clocks;
timestep = other.timestep;
IconWidth = other.IconWidth; IconWidth = other.IconWidth;
IconHeight = other.IconHeight; IconHeight = other.IconHeight;
@@ -146,7 +139,7 @@ namespace OpenRA.Mods.Common.Widgets
var tiny = Game.Renderer.Fonts["Tiny"]; var tiny = Game.Renderer.Fonts["Tiny"];
foreach (var icon in supportPowerIconsIcons) foreach (var icon in supportPowerIconsIcons)
{ {
var text = GetOverlayForItem(icon.Power, timestep); var text = GetOverlayForItem(icon.Power, world.Timestep);
tiny.DrawTextWithContrast(text, tiny.DrawTextWithContrast(text,
icon.Pos + new float2(16, 12) - new float2(tiny.Measure(text).X / 2, 0), icon.Pos + new float2(16, 12) - new float2(tiny.Measure(text).X / 2, 0),
Color.White, Color.Black, 1); Color.White, Color.Black, 1);

View File

@@ -25,7 +25,6 @@ namespace OpenRA.Mods.Common.Widgets
public readonly TextAlign Align = TextAlign.Left; public readonly TextAlign Align = TextAlign.Left;
public readonly TimerOrder Order = TimerOrder.Descending; public readonly TimerOrder Order = TimerOrder.Descending;
readonly int timestep;
readonly IEnumerable<SupportPowerInstance> powers; readonly IEnumerable<SupportPowerInstance> powers;
readonly Color bgDark, bgLight; readonly Color bgDark, bgLight;
(string Text, Color Color)[] texts; (string Text, Color Color)[] texts;
@@ -38,11 +37,6 @@ namespace OpenRA.Mods.Common.Widgets
.SelectMany(s => s.Trait.Powers.Values) .SelectMany(s => s.Trait.Powers.Values)
.Where(p => p.Instances.Any() && p.Info.DisplayTimerRelationships != PlayerRelationship.None && !p.Disabled); .Where(p => p.Instances.Any() && p.Info.DisplayTimerRelationships != PlayerRelationship.None && !p.Disabled);
// Timers in replays should be synced to the effective game time, not the playback time.
timestep = world.Timestep;
if (world.IsReplay)
timestep = world.WorldActor.Trait<MapOptions>().GameSpeed.Timestep;
bgDark = ChromeMetrics.Get<Color>("TextContrastColorDark"); bgDark = ChromeMetrics.Get<Color>("TextContrastColorDark");
bgLight = ChromeMetrics.Get<Color>("TextContrastColorLight"); bgLight = ChromeMetrics.Get<Color>("TextContrastColorLight");
} }
@@ -58,9 +52,9 @@ namespace OpenRA.Mods.Common.Widgets
texts = displayedPowers.Select(p => texts = displayedPowers.Select(p =>
{ {
var time = WidgetUtils.FormatTime(p.RemainingTicks, false, timestep);
var text = Format.F(p.Info.Description, time);
var self = p.Instances[0].Self; var self = p.Instances[0].Self;
var time = WidgetUtils.FormatTime(p.RemainingTicks, false, self.World.Timestep);
var text = Format.F(p.Info.Description, time);
var playerColor = self.Owner.Color; var playerColor = self.Owner.Color;
if (Game.Settings.Game.UsePlayerStanceColors) if (Game.Settings.Game.UsePlayerStanceColors)

View File

@@ -236,30 +236,32 @@ AssetBrowser:
SupportedExtensions: .shp, .tem, .des, .sno, .jun, .vqa, .wsa SupportedExtensions: .shp, .tem, .des, .sno, .jun, .vqa, .wsa
GameSpeeds: GameSpeeds:
slowest: DefaultSpeed: default
Name: Slowest Speeds:
Timestep: 80 slowest:
OrderLatency: 2 Name: Slowest
slower: Timestep: 80
Name: Slower OrderLatency: 2
Timestep: 50 slower:
OrderLatency: 3 Name: Slower
default: Timestep: 50
Name: Normal OrderLatency: 3
Timestep: 40 default:
OrderLatency: 3 Name: Normal
fast: Timestep: 40
Name: Fast OrderLatency: 3
Timestep: 35 fast:
OrderLatency: 4 Name: Fast
faster: Timestep: 35
Name: Faster OrderLatency: 4
Timestep: 30 faster:
OrderLatency: 4 Name: Faster
fastest: Timestep: 30
Name: Fastest OrderLatency: 4
Timestep: 20 fastest:
OrderLatency: 6 Name: Fastest
Timestep: 20
OrderLatency: 6
ColorValidator: ColorValidator:
TeamColorPresets: f70606, ff7a22, f8d3b3, f8e947, 94b319, f335a0, a64d6c, ce08f9, f5b2db, 12b572, 502048, 1d06f7, 328dff, 78dbf8, cef6b1, 391d1d TeamColorPresets: f70606, ff7a22, f8d3b3, f8e947, 94b319, f335a0, a64d6c, ce08f9, f5b2db, 12b572, 502048, 1d06f7, 328dff, 78dbf8, cef6b1, 391d1d

View File

@@ -211,30 +211,32 @@ AssetBrowser:
SupportedExtensions: .shp, .r8, .vqa SupportedExtensions: .shp, .r8, .vqa
GameSpeeds: GameSpeeds:
slowest: DefaultSpeed: default
Name: Slowest Speeds:
Timestep: 80 slowest:
OrderLatency: 2 Name: Slowest
slower: Timestep: 80
Name: Slower OrderLatency: 2
Timestep: 50 slower:
OrderLatency: 3 Name: Slower
default: Timestep: 50
Name: Normal OrderLatency: 3
Timestep: 40 default:
OrderLatency: 3 Name: Normal
fast: Timestep: 40
Name: Fast OrderLatency: 3
Timestep: 35 fast:
OrderLatency: 4 Name: Fast
faster: Timestep: 35
Name: Faster OrderLatency: 4
Timestep: 30 faster:
OrderLatency: 4 Name: Faster
fastest: Timestep: 30
Name: Fastest OrderLatency: 4
Timestep: 20 fastest:
OrderLatency: 6 Name: Fastest
Timestep: 20
OrderLatency: 6
ColorValidator: ColorValidator:
TeamColorPresets: 9023cd, f53333, ffae00, fff830, 87f506, f872ad, da06f3, ddb8ff, def7b2, 39c46f, 200738, 280df6, 2f86f2, 76d2f8, 498221, 392929 TeamColorPresets: 9023cd, f53333, ffae00, fff830, 87f506, f872ad, da06f3, ddb8ff, def7b2, 39c46f, 200738, 280df6, 2f86f2, 76d2f8, 498221, 392929

View File

@@ -241,30 +241,32 @@ AssetBrowser:
SupportedExtensions: .shp, .tmp, .tem, .des, .sno, .int, .vqa, .wsa SupportedExtensions: .shp, .tmp, .tem, .des, .sno, .int, .vqa, .wsa
GameSpeeds: GameSpeeds:
slowest: DefaultSpeed: default
Name: Slowest Speeds:
Timestep: 80 slowest:
OrderLatency: 2 Name: Slowest
slower: Timestep: 80
Name: Slower OrderLatency: 2
Timestep: 50 slower:
OrderLatency: 3 Name: Slower
default: Timestep: 50
Name: Normal OrderLatency: 3
Timestep: 40 default:
OrderLatency: 3 Name: Normal
fast: Timestep: 40
Name: Fast OrderLatency: 3
Timestep: 35 fast:
OrderLatency: 4 Name: Fast
faster: Timestep: 35
Name: Faster OrderLatency: 4
Timestep: 30 faster:
OrderLatency: 4 Name: Faster
fastest: Timestep: 30
Name: Fastest OrderLatency: 4
Timestep: 20 fastest:
OrderLatency: 6 Name: Fastest
Timestep: 20
OrderLatency: 6
ColorValidator: ColorValidator:
TeamColorPresets: f7b3b3, f50606, 98331f, f57606, f7bb06, f861a4, da06f3, ddb8ff, 06f739, cef7b2, 200738, 280df6, 2f86f2, 76d2f8, 34ba93, 391d1d TeamColorPresets: f7b3b3, f50606, 98331f, f57606, f7bb06, f861a4, da06f3, ddb8ff, 06f739, cef7b2, 200738, 280df6, 2f86f2, 76d2f8, 34ba93, 391d1d

View File

@@ -270,30 +270,32 @@ AssetBrowser:
SupportedExtensions: .shp, .tem, .sno, .vqa, .vxl SupportedExtensions: .shp, .tem, .sno, .vqa, .vxl
GameSpeeds: GameSpeeds:
slowest: DefaultSpeed: default
Name: Slowest Speeds:
Timestep: 80 slowest:
OrderLatency: 2 Name: Slowest
slower: Timestep: 80
Name: Slower OrderLatency: 2
Timestep: 50 slower:
OrderLatency: 3 Name: Slower
default: Timestep: 50
Name: Normal OrderLatency: 3
Timestep: 40 default:
OrderLatency: 3 Name: Normal
fast: Timestep: 40
Name: Fast OrderLatency: 3
Timestep: 35 fast:
OrderLatency: 4 Name: Fast
faster: Timestep: 35
Name: Faster OrderLatency: 4
Timestep: 30 faster:
OrderLatency: 4 Name: Faster
fastest: Timestep: 30
Name: Fastest OrderLatency: 4
Timestep: 20 fastest:
OrderLatency: 6 Name: Fastest
Timestep: 20
OrderLatency: 6
ColorValidator: ColorValidator:
TeamColorPresets: f70606, ff7a22, f8d3b3, f8e947, 94b319, f335a0, a64d6c, ce08f9, f5b2db, 12b572, 4A1948, 1d06f7, 328dff, 78dbf8, cef6b1, 391d1d TeamColorPresets: f70606, ff7a22, f8d3b3, f8e947, 94b319, f335a0, a64d6c, ce08f9, f5b2db, 12b572, 4A1948, 1d06f7, 328dff, 78dbf8, cef6b1, 391d1d