124
OpenRA.Game/FileFormats/ReplayMetadata.cs
Normal file
124
OpenRA.Game/FileFormats/ReplayMetadata.cs
Normal file
@@ -0,0 +1,124 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using OpenRA.Network;
|
||||
|
||||
namespace OpenRA.FileFormats
|
||||
{
|
||||
public class ReplayMetadata
|
||||
{
|
||||
// Must be an invalid replay 'client' value
|
||||
public const int MetaStartMarker = -1;
|
||||
public const int MetaEndMarker = -2;
|
||||
public const int MetaVersion = 0x00000001;
|
||||
|
||||
public readonly GameInformation GameInfo;
|
||||
public string FilePath { get; private set; }
|
||||
|
||||
public ReplayMetadata(GameInformation info)
|
||||
{
|
||||
if (info == null)
|
||||
throw new ArgumentNullException("info");
|
||||
|
||||
GameInfo = info;
|
||||
}
|
||||
|
||||
ReplayMetadata(FileStream fs, string path)
|
||||
{
|
||||
FilePath = path;
|
||||
|
||||
// Read start marker
|
||||
if (fs.ReadInt32() != MetaStartMarker)
|
||||
throw new InvalidOperationException("Expected MetaStartMarker but found an invalid value.");
|
||||
|
||||
// Read version
|
||||
var version = fs.ReadInt32();
|
||||
if (version != MetaVersion)
|
||||
throw new NotSupportedException("Metadata version {0} is not supported".F(version));
|
||||
|
||||
// Read game info (max 100K limit as a safeguard against corrupted files)
|
||||
string data = fs.ReadString(Encoding.UTF8, 1024 * 100);
|
||||
GameInfo = GameInformation.Deserialize(data);
|
||||
}
|
||||
|
||||
public void Write(BinaryWriter writer)
|
||||
{
|
||||
// Write start marker & version
|
||||
writer.Write(MetaStartMarker);
|
||||
writer.Write(MetaVersion);
|
||||
|
||||
// Write data
|
||||
int dataLength = 0;
|
||||
{
|
||||
// Write lobby info data
|
||||
writer.Flush();
|
||||
dataLength += writer.BaseStream.WriteString(Encoding.UTF8, GameInfo.Serialize());
|
||||
}
|
||||
|
||||
// Write total length & end marker
|
||||
writer.Write(dataLength);
|
||||
writer.Write(MetaEndMarker);
|
||||
}
|
||||
|
||||
public void RenameFile(string newFilenameWithoutExtension)
|
||||
{
|
||||
var newPath = Path.Combine(Path.GetDirectoryName(FilePath), newFilenameWithoutExtension) + ".rep";
|
||||
File.Move(FilePath, newPath);
|
||||
FilePath = newPath;
|
||||
}
|
||||
|
||||
public static ReplayMetadata Read(string path)
|
||||
{
|
||||
using (var fs = new FileStream(path, FileMode.Open))
|
||||
return Read(fs, path);
|
||||
}
|
||||
|
||||
static ReplayMetadata Read(FileStream fs, string path)
|
||||
{
|
||||
if (!fs.CanSeek)
|
||||
return null;
|
||||
|
||||
if (fs.Length < 20)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
fs.Seek(-(4 + 4), SeekOrigin.End);
|
||||
var dataLength = fs.ReadInt32();
|
||||
if (fs.ReadInt32() == MetaEndMarker)
|
||||
{
|
||||
// go back by (end marker + length storage + data + version + start marker) bytes
|
||||
fs.Seek(-(4 + 4 + dataLength + 4 + 4), SeekOrigin.Current);
|
||||
try
|
||||
{
|
||||
return new ReplayMetadata(fs, path);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
Log.Write("debug", ex.ToString());
|
||||
}
|
||||
catch (NotSupportedException ex)
|
||||
{
|
||||
Log.Write("debug", ex.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
Log.Write("debug", ex.ToString());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
168
OpenRA.Game/GameInformation.cs
Normal file
168
OpenRA.Game/GameInformation.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
* see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Network;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
public class GameInformation
|
||||
{
|
||||
public string MapUid;
|
||||
public string MapTitle;
|
||||
public DateTime StartTimeUtc;
|
||||
// Game end timestamp (when the recoding stopped).
|
||||
public DateTime EndTimeUtc;
|
||||
|
||||
// Gets the game's duration, from the time the game started until the
|
||||
// replay recording stopped.
|
||||
public TimeSpan Duration { get { return EndTimeUtc > StartTimeUtc ? EndTimeUtc - StartTimeUtc : TimeSpan.Zero; } }
|
||||
public IList<Player> Players { get; private set; }
|
||||
public MapPreview MapPreview { get { return Game.modData.MapCache[MapUid]; } }
|
||||
public IEnumerable<Player> HumanPlayers { get { return Players.Where(p => p.IsHuman); } }
|
||||
public bool IsSinglePlayer { get { return HumanPlayers.Count() == 1; } }
|
||||
|
||||
Dictionary<OpenRA.Player, Player> playersByRuntime;
|
||||
|
||||
public GameInformation()
|
||||
{
|
||||
Players = new List<Player>();
|
||||
playersByRuntime = new Dictionary<OpenRA.Player, Player>();
|
||||
}
|
||||
|
||||
public static GameInformation Deserialize(string data)
|
||||
{
|
||||
try
|
||||
{
|
||||
var info = new GameInformation();
|
||||
|
||||
var nodes = MiniYaml.FromString(data);
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
var keyParts = node.Key.Split('@');
|
||||
|
||||
switch (keyParts[0])
|
||||
{
|
||||
case "Root":
|
||||
FieldLoader.Load(info, node.Value);
|
||||
break;
|
||||
|
||||
case "Player":
|
||||
info.Players.Add(FieldLoader.Load<Player>(node.Value));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
Log.Write("debug", "GameInformation deserialized invalid MiniYaml:\n{0}".F(data));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public string Serialize()
|
||||
{
|
||||
var nodes = new List<MiniYamlNode>();
|
||||
|
||||
nodes.Add(new MiniYamlNode("Root", FieldSaver.Save(this)));
|
||||
|
||||
for (var i=0; i<Players.Count; i++)
|
||||
nodes.Add(new MiniYamlNode("Player@{0}".F(i), FieldSaver.Save(Players[i])));
|
||||
|
||||
return nodes.WriteToString();
|
||||
}
|
||||
|
||||
// Adds the player information at start-up.
|
||||
public void AddPlayer(OpenRA.Player runtimePlayer, Session lobbyInfo)
|
||||
{
|
||||
if (runtimePlayer == null)
|
||||
throw new ArgumentNullException("runtimePlayer");
|
||||
|
||||
if (lobbyInfo == null)
|
||||
throw new ArgumentNullException("lobbyInfo");
|
||||
|
||||
// We don't care about spectators and map players
|
||||
if (runtimePlayer.NonCombatant || !runtimePlayer.Playable)
|
||||
return;
|
||||
|
||||
// Find the lobby client that created the runtime player
|
||||
var client = lobbyInfo.ClientWithIndex(runtimePlayer.ClientIndex);
|
||||
if (client == null)
|
||||
return;
|
||||
|
||||
var player = new Player
|
||||
{
|
||||
ClientIndex = runtimePlayer.ClientIndex,
|
||||
Name = runtimePlayer.PlayerName,
|
||||
IsHuman = !runtimePlayer.IsBot,
|
||||
IsBot = runtimePlayer.IsBot,
|
||||
FactionName = runtimePlayer.Country.Name,
|
||||
FactionId = runtimePlayer.Country.Race,
|
||||
Color = runtimePlayer.Color,
|
||||
Team = client.Team,
|
||||
SpawnPoint = runtimePlayer.SpawnPoint,
|
||||
IsRandomFaction = runtimePlayer.Country.Race != client.Country,
|
||||
IsRandomSpawnPoint = runtimePlayer.SpawnPoint != client.SpawnPoint
|
||||
};
|
||||
|
||||
playersByRuntime.Add(runtimePlayer, player);
|
||||
Players.Add(player);
|
||||
}
|
||||
|
||||
// Gets the player information for the specified runtime player instance.
|
||||
public Player GetPlayer(OpenRA.Player runtimePlayer)
|
||||
{
|
||||
Player player;
|
||||
|
||||
playersByRuntime.TryGetValue(runtimePlayer, out player);
|
||||
|
||||
return player;
|
||||
}
|
||||
|
||||
public class Player
|
||||
{
|
||||
//
|
||||
// Start-up information
|
||||
//
|
||||
|
||||
public int ClientIndex;
|
||||
// The player name, not guaranteed to be unique.
|
||||
public string Name;
|
||||
public bool IsHuman;
|
||||
public bool IsBot;
|
||||
// The faction name (aka Country)
|
||||
public string FactionName;
|
||||
// The faction id (aka Country, aka Race)
|
||||
public string FactionId;
|
||||
public HSLColor Color;
|
||||
// The team id on start-up, or 0 if the player is not part of the team.
|
||||
public int Team;
|
||||
public int SpawnPoint;
|
||||
// True if the faction was chosen at random; otherwise, false
|
||||
public bool IsRandomFaction;
|
||||
// True if the spawn point was chosen at random; otherwise, false.</summary>
|
||||
public bool IsRandomSpawnPoint;
|
||||
|
||||
//
|
||||
// Information gathered at a later stage
|
||||
//
|
||||
|
||||
// The game outcome for this player
|
||||
public WinState Outcome;
|
||||
// The time when this player won or lost the game
|
||||
public DateTime OutcomeTimestampUtc;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,14 +72,23 @@ namespace OpenRA.Graphics
|
||||
collections.Add(name, collection);
|
||||
}
|
||||
|
||||
public static Sprite GetImage(string collection, string image)
|
||||
public static Sprite GetImage(string collectionName, string imageName)
|
||||
{
|
||||
// Cached sprite
|
||||
if (cachedSprites.ContainsKey(collection) && cachedSprites[collection].ContainsKey(image))
|
||||
return cachedSprites[collection][image];
|
||||
Dictionary<string, Sprite> cachedCollection;
|
||||
Sprite sprite;
|
||||
if (cachedSprites.TryGetValue(collectionName, out cachedCollection) && cachedCollection.TryGetValue(imageName, out sprite))
|
||||
return sprite;
|
||||
|
||||
Collection collection;
|
||||
if (!collections.TryGetValue(collectionName, out collection))
|
||||
{
|
||||
Log.Write("debug", "Could not find collection '{0}'", collectionName);
|
||||
return null;
|
||||
}
|
||||
|
||||
MappedImage mi;
|
||||
if (!collections[collection].regions.TryGetValue(image, out mi))
|
||||
if (!collection.regions.TryGetValue(imageName, out mi))
|
||||
return null;
|
||||
|
||||
// Cached sheet
|
||||
@@ -93,11 +102,15 @@ namespace OpenRA.Graphics
|
||||
}
|
||||
|
||||
// Cache the sprite
|
||||
if (!cachedSprites.ContainsKey(collection))
|
||||
cachedSprites.Add(collection, new Dictionary<string, Sprite>());
|
||||
cachedSprites[collection].Add(image, mi.GetImage(sheet));
|
||||
if (cachedCollection == null)
|
||||
{
|
||||
cachedCollection = new Dictionary<string, Sprite>();
|
||||
cachedSprites.Add(collectionName, cachedCollection);
|
||||
}
|
||||
var image = mi.GetImage(sheet);
|
||||
cachedCollection.Add(imageName, image);
|
||||
|
||||
return cachedSprites[collection][image];
|
||||
return image;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Network
|
||||
@@ -44,6 +45,8 @@ namespace OpenRA.Network
|
||||
while (rs.Position < rs.Length)
|
||||
{
|
||||
var client = rs.ReadInt32();
|
||||
if (client == ReplayMetadata.MetaStartMarker)
|
||||
break;
|
||||
var packetLen = rs.ReadInt32();
|
||||
var packet = rs.ReadBytes(packetLen);
|
||||
var frame = BitConverter.ToInt32(packet, 0);
|
||||
|
||||
@@ -12,12 +12,15 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Widgets;
|
||||
|
||||
namespace OpenRA.Network
|
||||
{
|
||||
class ReplayRecorderConnection : IConnection
|
||||
{
|
||||
public ReplayMetadata Metadata;
|
||||
|
||||
IConnection inner;
|
||||
BinaryWriter writer;
|
||||
Func<string> chooseFilename;
|
||||
@@ -101,6 +104,13 @@ namespace OpenRA.Network
|
||||
if (disposed)
|
||||
return;
|
||||
|
||||
if (Metadata != null)
|
||||
{
|
||||
if (Metadata.GameInfo != null)
|
||||
Metadata.GameInfo.EndTimeUtc = DateTime.UtcNow;
|
||||
Metadata.Write(writer);
|
||||
}
|
||||
|
||||
writer.Close();
|
||||
inner.Dispose();
|
||||
disposed = true;
|
||||
|
||||
@@ -248,6 +248,7 @@
|
||||
<Compile Include="GameRules\Ruleset.cs" />
|
||||
<Compile Include="GameRules\RulesetCache.cs" />
|
||||
<Compile Include="Support\MersenneTwister.cs" />
|
||||
<Compile Include="GameInformation.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="FileSystem\D2kSoundResources.cs" />
|
||||
@@ -330,6 +331,7 @@
|
||||
<Compile Include="Graphics\PlayerColorRemap.cs" />
|
||||
<Compile Include="Graphics\Palette.cs" />
|
||||
<Compile Include="FileSystem\GlobalFileSystem.cs" />
|
||||
<Compile Include="FileFormats\ReplayMetadata.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">
|
||||
|
||||
@@ -23,7 +23,7 @@ using OpenRA.Traits;
|
||||
namespace OpenRA
|
||||
{
|
||||
public enum PowerState { Normal, Low, Critical };
|
||||
public enum WinState { Won, Lost, Undefined };
|
||||
public enum WinState { Undefined, Won, Lost };
|
||||
|
||||
public class Player : IScriptBindable, IScriptNotifyBind, ILuaTableBinding, ILuaEqualityBinding, ILuaToStringBinding
|
||||
{
|
||||
@@ -41,6 +41,7 @@ namespace OpenRA
|
||||
public readonly int ClientIndex;
|
||||
public readonly PlayerReference PlayerReference;
|
||||
public bool IsBot;
|
||||
public int SpawnPoint;
|
||||
|
||||
public Shroud Shroud;
|
||||
public World World { get; private set; }
|
||||
|
||||
@@ -64,6 +64,11 @@ namespace OpenRA
|
||||
return BitConverter.ToInt32(s.ReadBytes(4), 0);
|
||||
}
|
||||
|
||||
public static void Write(this Stream s, int value)
|
||||
{
|
||||
s.Write(BitConverter.GetBytes(value));
|
||||
}
|
||||
|
||||
public static float ReadFloat(this Stream s)
|
||||
{
|
||||
return BitConverter.ToSingle(s.ReadBytes(4), 0);
|
||||
@@ -134,5 +139,32 @@ namespace OpenRA
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The string is assumed to be length-prefixed, as written by WriteString()
|
||||
public static string ReadString(this Stream s, Encoding encoding, int maxLength)
|
||||
{
|
||||
var length = s.ReadInt32();
|
||||
if (length > maxLength)
|
||||
throw new InvalidOperationException("The length of the string ({0}) is longer than the maximum allowed ({1}).".F(length, maxLength));
|
||||
|
||||
return encoding.GetString(s.ReadBytes(length));
|
||||
}
|
||||
|
||||
// Writes a length-prefixed string using the specified encoding and returns
|
||||
// the number of bytes written.
|
||||
public static int WriteString(this Stream s, Encoding encoding, string text)
|
||||
{
|
||||
byte[] bytes;
|
||||
|
||||
if (!string.IsNullOrEmpty(text))
|
||||
bytes = encoding.GetBytes(text);
|
||||
else
|
||||
bytes = new byte[0];
|
||||
|
||||
s.Write(bytes.Length);
|
||||
s.Write(bytes);
|
||||
|
||||
return 4 + bytes.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,9 +41,12 @@ namespace OpenRA.Widgets
|
||||
{
|
||||
var name = GetImageName();
|
||||
var collection = GetImageCollection();
|
||||
WidgetUtils.DrawRGBA(
|
||||
ChromeProvider.GetImage(collection, name),
|
||||
RenderOrigin);
|
||||
|
||||
var sprite = ChromeProvider.GetImage(collection, name);
|
||||
if (sprite == null)
|
||||
throw new ArgumentException("Sprite {0}/{1} was not found.".F(collection, name));
|
||||
|
||||
WidgetUtils.DrawRGBA(sprite, RenderOrigin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,10 @@ namespace OpenRA.Widgets
|
||||
|
||||
public override void Draw()
|
||||
{
|
||||
SpriteFont font = Game.Renderer.Fonts[Font];
|
||||
SpriteFont font;
|
||||
if (!Game.Renderer.Fonts.TryGetValue(Font, out font))
|
||||
throw new ArgumentException("Requested font '{0}' was not found.".F(Font));
|
||||
|
||||
var text = GetText();
|
||||
if (text == null)
|
||||
return;
|
||||
|
||||
@@ -22,7 +22,8 @@ namespace OpenRA.Widgets
|
||||
widget.ContentHeight = widget.ItemSpacing;
|
||||
|
||||
w.Bounds.Y = widget.ContentHeight;
|
||||
widget.ContentHeight += w.Bounds.Height + widget.ItemSpacing;
|
||||
if (!widget.CollapseHiddenChildren || w.IsVisible())
|
||||
widget.ContentHeight += w.Bounds.Height + widget.ItemSpacing;
|
||||
}
|
||||
|
||||
public void AdjustChildren()
|
||||
@@ -31,7 +32,8 @@ namespace OpenRA.Widgets
|
||||
foreach (var w in widget.Children)
|
||||
{
|
||||
w.Bounds.Y = widget.ContentHeight;
|
||||
widget.ContentHeight += w.Bounds.Height + widget.ItemSpacing;
|
||||
if (!widget.CollapseHiddenChildren || w.IsVisible())
|
||||
widget.ContentHeight += w.Bounds.Height + widget.ItemSpacing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,10 +19,42 @@ using OpenRA.Network;
|
||||
|
||||
namespace OpenRA.Widgets
|
||||
{
|
||||
public class SpawnOccupant
|
||||
{
|
||||
public readonly HSLColor Color;
|
||||
public readonly int ClientIndex;
|
||||
public readonly string PlayerName;
|
||||
public readonly int Team;
|
||||
public readonly string Country;
|
||||
public readonly int SpawnPoint;
|
||||
|
||||
public SpawnOccupant()
|
||||
{
|
||||
}
|
||||
public SpawnOccupant(Session.Client client)
|
||||
{
|
||||
Color = client.Color;
|
||||
ClientIndex = client.Index;
|
||||
PlayerName = client.Name;
|
||||
Team = client.Team;
|
||||
Country = client.Country;
|
||||
SpawnPoint = client.SpawnPoint;
|
||||
}
|
||||
public SpawnOccupant(GameInformation.Player player)
|
||||
{
|
||||
Color = player.Color;
|
||||
ClientIndex = player.ClientIndex;
|
||||
PlayerName = player.Name;
|
||||
Team = player.Team;
|
||||
Country = player.FactionId;
|
||||
SpawnPoint = player.SpawnPoint;
|
||||
}
|
||||
}
|
||||
|
||||
public class MapPreviewWidget : Widget
|
||||
{
|
||||
public Func<MapPreview> Preview = () => null;
|
||||
public Func<Dictionary<CPos, Session.Client>> SpawnClients = () => new Dictionary<CPos, Session.Client>();
|
||||
public Func<Dictionary<CPos, SpawnOccupant>> SpawnOccupants = () => new Dictionary<CPos, SpawnOccupant>();
|
||||
public Action<MouseInput> OnMouseDown = _ => {};
|
||||
public bool IgnoreMouseInput = false;
|
||||
public bool ShowSpawnPoints = true;
|
||||
@@ -44,7 +76,7 @@ namespace OpenRA.Widgets
|
||||
: base(other)
|
||||
{
|
||||
Preview = other.Preview;
|
||||
SpawnClients = other.SpawnClients;
|
||||
SpawnOccupants = other.SpawnOccupants;
|
||||
ShowSpawnPoints = other.ShowSpawnPoints;
|
||||
TooltipTemplate = other.TooltipTemplate;
|
||||
TooltipContainer = other.TooltipContainer;
|
||||
@@ -109,7 +141,7 @@ namespace OpenRA.Widgets
|
||||
TooltipSpawnIndex = -1;
|
||||
if (ShowSpawnPoints)
|
||||
{
|
||||
var colors = SpawnClients().ToDictionary(c => c.Key, c => c.Value.Color.RGB);
|
||||
var colors = SpawnOccupants().ToDictionary(c => c.Key, c => c.Value.Color.RGB);
|
||||
|
||||
var spawnPoints = preview.SpawnPoints;
|
||||
foreach (var p in spawnPoints)
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace OpenRA.Widgets
|
||||
public ILayout Layout;
|
||||
public int MinimumThumbSize = 10;
|
||||
public ScrollPanelAlign Align = ScrollPanelAlign.Top;
|
||||
public bool CollapseHiddenChildren = false;
|
||||
protected float ListOffset = 0;
|
||||
protected bool UpPressed = false;
|
||||
protected bool DownPressed = false;
|
||||
|
||||
@@ -31,12 +31,15 @@ namespace OpenRA.Widgets
|
||||
public Func<bool> OnTabKey = () => false;
|
||||
public Func<bool> OnEscKey = () => false;
|
||||
public Action OnLoseFocus = () => { };
|
||||
public Action OnTextEdited = () => { };
|
||||
public int CursorPosition { get; set; }
|
||||
|
||||
public Func<bool> IsDisabled = () => false;
|
||||
public Func<bool> IsValid = () => true;
|
||||
public string Font = ChromeMetrics.Get<string>("TextfieldFont");
|
||||
public Color TextColor = ChromeMetrics.Get<Color>("TextfieldColor");
|
||||
public Color TextColorDisabled = ChromeMetrics.Get<Color>("TextfieldColorDisabled");
|
||||
public Color TextColorInvalid = ChromeMetrics.Get<Color>("TextfieldColorInvalid");
|
||||
|
||||
public TextFieldWidget() {}
|
||||
protected TextFieldWidget(TextFieldWidget widget)
|
||||
@@ -47,6 +50,7 @@ namespace OpenRA.Widgets
|
||||
Font = widget.Font;
|
||||
TextColor = widget.TextColor;
|
||||
TextColorDisabled = widget.TextColorDisabled;
|
||||
TextColorInvalid = widget.TextColorInvalid;
|
||||
VisualHeight = widget.VisualHeight;
|
||||
}
|
||||
|
||||
@@ -148,7 +152,10 @@ namespace OpenRA.Widgets
|
||||
if (e.Key == Keycode.DELETE)
|
||||
{
|
||||
if (CursorPosition < Text.Length)
|
||||
{
|
||||
Text = Text.Remove(CursorPosition, 1);
|
||||
OnTextEdited();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -156,6 +163,7 @@ namespace OpenRA.Widgets
|
||||
{
|
||||
CursorPosition--;
|
||||
Text = Text.Remove(CursorPosition, 1);
|
||||
OnTextEdited();
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -171,6 +179,7 @@ namespace OpenRA.Widgets
|
||||
|
||||
Text = Text.Insert(CursorPosition, text);
|
||||
CursorPosition++;
|
||||
OnTextEdited();
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -228,7 +237,9 @@ namespace OpenRA.Widgets
|
||||
Bounds.Width - LeftMargin - RightMargin, Bounds.Bottom));
|
||||
}
|
||||
|
||||
var color = disabled ? TextColorDisabled : TextColor;
|
||||
var color = disabled ? TextColorDisabled
|
||||
: IsValid() ? TextColor
|
||||
: TextColorInvalid;
|
||||
font.DrawText(apparentText, textPos, color);
|
||||
|
||||
if (showCursor && HasKeyboardFocus)
|
||||
|
||||
@@ -258,7 +258,7 @@ namespace OpenRA.Widgets
|
||||
return true;
|
||||
}
|
||||
|
||||
// Remove focus from this widget; return false if you don't want to give it up
|
||||
// Remove focus from this widget; return false to hint that you don't want to give it up
|
||||
public virtual bool YieldMouseFocus(MouseInput mi)
|
||||
{
|
||||
if (Ui.MouseFocusWidget == this)
|
||||
@@ -267,6 +267,12 @@ namespace OpenRA.Widgets
|
||||
return true;
|
||||
}
|
||||
|
||||
void ForceYieldMouseFocus()
|
||||
{
|
||||
if (Ui.MouseFocusWidget == this && !YieldMouseFocus(default(MouseInput)))
|
||||
Ui.MouseFocusWidget = null;
|
||||
}
|
||||
|
||||
public virtual bool TakeKeyboardFocus()
|
||||
{
|
||||
if (HasKeyboardFocus)
|
||||
@@ -287,6 +293,12 @@ namespace OpenRA.Widgets
|
||||
return true;
|
||||
}
|
||||
|
||||
void ForceYieldKeyboardFocus()
|
||||
{
|
||||
if (Ui.KeyboardFocusWidget == this && !YieldKeyboardFocus())
|
||||
Ui.KeyboardFocusWidget = null;
|
||||
}
|
||||
|
||||
public virtual string GetCursor(int2 pos) { return "default"; }
|
||||
public string GetCursorOuter(int2 pos)
|
||||
{
|
||||
@@ -410,6 +422,11 @@ namespace OpenRA.Widgets
|
||||
|
||||
public virtual void Removed()
|
||||
{
|
||||
// Using the forced versions because the widgets
|
||||
// have been removed
|
||||
ForceYieldKeyboardFocus();
|
||||
ForceYieldMouseFocus();
|
||||
|
||||
foreach (var c in Children.OfType<Widget>().Reverse())
|
||||
c.Removed();
|
||||
}
|
||||
|
||||
@@ -85,6 +85,7 @@ namespace OpenRA
|
||||
public readonly TileSet TileSet;
|
||||
public readonly ActorMap ActorMap;
|
||||
public readonly ScreenMap ScreenMap;
|
||||
readonly GameInformation gameInfo;
|
||||
|
||||
public void IssueOrder(Order o) { orderManager.IssueOrder(o); } /* avoid exposing the OM to mod code */
|
||||
|
||||
@@ -142,6 +143,12 @@ namespace OpenRA
|
||||
p.Stances[q] = Stance.Neutral;
|
||||
|
||||
Sound.SoundVolumeModifier = 1.0f;
|
||||
|
||||
gameInfo = new GameInformation
|
||||
{
|
||||
MapUid = Map.Uid,
|
||||
MapTitle = Map.Title
|
||||
};
|
||||
}
|
||||
|
||||
public void LoadComplete(WorldRenderer wr)
|
||||
@@ -151,6 +158,14 @@ namespace OpenRA
|
||||
using (new Support.PerfTimer(wlh.GetType().Name + ".WorldLoaded"))
|
||||
wlh.WorldLoaded(this, wr);
|
||||
}
|
||||
|
||||
gameInfo.StartTimeUtc = DateTime.UtcNow;
|
||||
foreach (var player in Players)
|
||||
gameInfo.AddPlayer(player, orderManager.LobbyInfo);
|
||||
|
||||
var rc = orderManager.Connection as ReplayRecorderConnection;
|
||||
if (rc != null)
|
||||
rc.Metadata = new ReplayMetadata(gameInfo);
|
||||
}
|
||||
|
||||
public Actor CreateActor(string name, TypeDictionary initDict)
|
||||
@@ -287,6 +302,16 @@ namespace OpenRA
|
||||
{
|
||||
return traitDict.ActorsWithTraitMultiple<T>(this);
|
||||
}
|
||||
|
||||
public void OnPlayerWinStateChanged(Player player)
|
||||
{
|
||||
var pi = gameInfo.GetPlayer(player);
|
||||
if (pi != null)
|
||||
{
|
||||
pi.Outcome = player.WinState;
|
||||
pi.OutcomeTimestampUtc = DateTime.UtcNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct TraitPair<T>
|
||||
|
||||
@@ -60,6 +60,7 @@ namespace OpenRA.Mods.RA
|
||||
{
|
||||
if (self.Owner.WinState == WinState.Lost) return;
|
||||
self.Owner.WinState = WinState.Lost;
|
||||
self.World.OnPlayerWinStateChanged(self.Owner);
|
||||
|
||||
Game.Debug("{0} is defeated.".F(self.Owner.PlayerName));
|
||||
|
||||
@@ -67,19 +68,23 @@ namespace OpenRA.Mods.RA
|
||||
a.Kill(a);
|
||||
|
||||
if (self.Owner == self.World.LocalPlayer)
|
||||
{
|
||||
Game.RunAfterDelay(Info.NotificationDelay, () =>
|
||||
{
|
||||
if (Game.IsCurrentWorld(self.World))
|
||||
Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", "Lose", self.Owner.Country.Race);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void Win(Actor self)
|
||||
{
|
||||
if (self.Owner.WinState == WinState.Won) return;
|
||||
self.Owner.WinState = WinState.Won;
|
||||
self.World.OnPlayerWinStateChanged(self.Owner);
|
||||
|
||||
Game.Debug("{0} is victorious.".F(self.Owner.PlayerName));
|
||||
|
||||
if (self.Owner == self.World.LocalPlayer)
|
||||
Game.RunAfterDelay(Info.NotificationDelay, () => Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", "Win", self.Owner.Country.Race));
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace OpenRA.Mods.RA
|
||||
|
||||
public void WorldLoaded(World world, WorldRenderer wr)
|
||||
{
|
||||
var spawns = world.Map.GetSpawnPoints();
|
||||
var spawns = world.Map.GetSpawnPoints().ToList();
|
||||
var taken = world.LobbyInfo.Clients.Where(c => c.SpawnPoint != 0 && c.Slot != null)
|
||||
.Select(c => spawns[c.SpawnPoint-1]).ToList();
|
||||
var available = spawns.Except(taken).ToList();
|
||||
@@ -42,9 +42,13 @@ namespace OpenRA.Mods.RA
|
||||
var client = world.LobbyInfo.ClientInSlot(kv.Key);
|
||||
var spid = (client == null || client.SpawnPoint == 0)
|
||||
? ChooseSpawnPoint(world, available, taken)
|
||||
: world.Map.GetSpawnPoints()[client.SpawnPoint-1];
|
||||
: spawns[client.SpawnPoint-1];
|
||||
|
||||
Start.Add(player, spid);
|
||||
|
||||
player.SpawnPoint = (client == null || client.SpawnPoint == 0)
|
||||
? spawns.IndexOf(spid) + 1
|
||||
: client.SpawnPoint;
|
||||
}
|
||||
|
||||
// Explore allied shroud
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using OpenRA.Widgets;
|
||||
|
||||
namespace OpenRA.Mods.RA.Widgets
|
||||
@@ -38,5 +39,101 @@ namespace OpenRA.Mods.RA.Widgets
|
||||
onCancel();
|
||||
};
|
||||
}
|
||||
|
||||
public static void TextInputPrompt(
|
||||
string title, string prompt, string initialText,
|
||||
Action<string> onAccept, Action onCancel = null,
|
||||
string acceptText = null, string cancelText = null,
|
||||
Func<string, bool> inputValidator = null)
|
||||
{
|
||||
var panel = Ui.OpenWindow("TEXT_INPUT_PROMPT");
|
||||
Func<bool> doValidate = null;
|
||||
ButtonWidget acceptButton = null, cancelButton = null;
|
||||
|
||||
//
|
||||
// Title
|
||||
//
|
||||
panel.Get<LabelWidget>("PROMPT_TITLE").GetText = () => title;
|
||||
|
||||
//
|
||||
// Prompt
|
||||
//
|
||||
panel.Get<LabelWidget>("PROMPT_TEXT").GetText = () => prompt;
|
||||
|
||||
//
|
||||
// Text input
|
||||
//
|
||||
var input = panel.Get<TextFieldWidget>("INPUT_TEXT");
|
||||
var isValid = false;
|
||||
input.Text = initialText;
|
||||
input.IsValid = () => isValid;
|
||||
input.OnEnterKey = () =>
|
||||
{
|
||||
if (acceptButton.IsDisabled())
|
||||
return false;
|
||||
|
||||
acceptButton.OnClick();
|
||||
return true;
|
||||
};
|
||||
input.OnEscKey = () =>
|
||||
{
|
||||
if (cancelButton.IsDisabled())
|
||||
return false;
|
||||
|
||||
cancelButton.OnClick();
|
||||
return true;
|
||||
};
|
||||
input.TakeKeyboardFocus();
|
||||
input.CursorPosition = input.Text.Length;
|
||||
input.OnTextEdited = () => doValidate();
|
||||
|
||||
//
|
||||
// Buttons
|
||||
//
|
||||
acceptButton = panel.Get<ButtonWidget>("ACCEPT_BUTTON");
|
||||
if (!string.IsNullOrEmpty(acceptText))
|
||||
acceptButton.GetText = () => acceptText;
|
||||
|
||||
acceptButton.OnClick = () =>
|
||||
{
|
||||
if (!doValidate())
|
||||
return;
|
||||
|
||||
Ui.CloseWindow();
|
||||
onAccept(input.Text);
|
||||
};
|
||||
|
||||
cancelButton = panel.Get<ButtonWidget>("CANCEL_BUTTON");
|
||||
if (!string.IsNullOrEmpty(cancelText))
|
||||
cancelButton.GetText = () => cancelText;
|
||||
|
||||
cancelButton.OnClick = () =>
|
||||
{
|
||||
Ui.CloseWindow();
|
||||
if (onCancel != null)
|
||||
onCancel();
|
||||
};
|
||||
|
||||
//
|
||||
// Validation
|
||||
//
|
||||
doValidate = () =>
|
||||
{
|
||||
if (inputValidator == null)
|
||||
return true;
|
||||
|
||||
isValid = inputValidator(input.Text);
|
||||
if (isValid)
|
||||
{
|
||||
acceptButton.Disabled = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
acceptButton.Disabled = true;
|
||||
return false;
|
||||
};
|
||||
|
||||
doValidate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
||||
var preview = available.Get<MapPreviewWidget>("MAP_PREVIEW");
|
||||
preview.Preview = () => lobby.Map;
|
||||
preview.OnMouseDown = mi => LobbyUtils.SelectSpawnPoint(orderManager, preview, lobby.Map, mi);
|
||||
preview.SpawnClients = () => LobbyUtils.GetSpawnClients(orderManager.LobbyInfo, lobby.Map);
|
||||
preview.SpawnOccupants = () => LobbyUtils.GetSpawnOccupants(orderManager.LobbyInfo, lobby.Map);
|
||||
|
||||
var title = available.GetOrNull<LabelWidget>("MAP_TITLE");
|
||||
if (title != null)
|
||||
@@ -54,7 +54,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
||||
var preview = invalid.Get<MapPreviewWidget>("MAP_PREVIEW");
|
||||
preview.Preview = () => lobby.Map;
|
||||
preview.OnMouseDown = mi => LobbyUtils.SelectSpawnPoint(orderManager, preview, lobby.Map, mi);
|
||||
preview.SpawnClients = () => LobbyUtils.GetSpawnClients(orderManager.LobbyInfo, lobby.Map);
|
||||
preview.SpawnOccupants = () => LobbyUtils.GetSpawnOccupants(orderManager.LobbyInfo, lobby.Map);
|
||||
|
||||
var title = invalid.GetOrNull<LabelWidget>("MAP_TITLE");
|
||||
if (title != null)
|
||||
@@ -73,7 +73,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
||||
var preview = download.Get<MapPreviewWidget>("MAP_PREVIEW");
|
||||
preview.Preview = () => lobby.Map;
|
||||
preview.OnMouseDown = mi => LobbyUtils.SelectSpawnPoint(orderManager, preview, lobby.Map, mi);
|
||||
preview.SpawnClients = () => LobbyUtils.GetSpawnClients(orderManager.LobbyInfo, lobby.Map);
|
||||
preview.SpawnOccupants = () => LobbyUtils.GetSpawnOccupants(orderManager.LobbyInfo, lobby.Map);
|
||||
|
||||
var title = download.GetOrNull<LabelWidget>("MAP_TITLE");
|
||||
if (title != null)
|
||||
@@ -100,7 +100,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
||||
var preview = progress.Get<MapPreviewWidget>("MAP_PREVIEW");
|
||||
preview.Preview = () => lobby.Map;
|
||||
preview.OnMouseDown = mi => LobbyUtils.SelectSpawnPoint(orderManager, preview, lobby.Map, mi);
|
||||
preview.SpawnClients = () => LobbyUtils.GetSpawnClients(orderManager.LobbyInfo, lobby.Map);
|
||||
preview.SpawnOccupants = () => LobbyUtils.GetSpawnOccupants(orderManager.LobbyInfo, lobby.Map);
|
||||
|
||||
var title = progress.GetOrNull<LabelWidget>("MAP_TITLE");
|
||||
if (title != null)
|
||||
|
||||
@@ -149,12 +149,19 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
||||
color.AttachPanel(colorChooser, onExit);
|
||||
}
|
||||
|
||||
public static Dictionary<CPos, Session.Client> GetSpawnClients(Session lobbyInfo, MapPreview preview)
|
||||
public static Dictionary<CPos, SpawnOccupant> GetSpawnOccupants(Session lobbyInfo, MapPreview preview)
|
||||
{
|
||||
var spawns = preview.SpawnPoints;
|
||||
return lobbyInfo.Clients
|
||||
.Where(c => c.SpawnPoint != 0)
|
||||
.ToDictionary(c => spawns[c.SpawnPoint - 1], c => c);
|
||||
.ToDictionary(c => spawns[c.SpawnPoint - 1], c => new SpawnOccupant(c));
|
||||
}
|
||||
public static Dictionary<CPos, SpawnOccupant> GetSpawnOccupants(IEnumerable<GameInformation.Player> players, MapPreview preview)
|
||||
{
|
||||
var spawns = preview.SpawnPoints;
|
||||
return players
|
||||
.Where(c => c.SpawnPoint != 0)
|
||||
.ToDictionary(c => spawns[c.SpawnPoint - 1], c => new SpawnOccupant(c));
|
||||
}
|
||||
|
||||
public static void SelectSpawnPoint(OrderManager orderManager, MapPreviewWidget mapPreview, MapPreview preview, MouseInput mi)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright 2007-2013 The OpenRA Developers (see AUTHORS)
|
||||
* Copyright 2007-2014 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. For more information,
|
||||
@@ -13,22 +13,25 @@ using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using OpenRA.FileFormats;
|
||||
using OpenRA.Network;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Widgets;
|
||||
|
||||
namespace OpenRA.Mods.RA.Widgets.Logic
|
||||
{
|
||||
public class ReplayBrowserLogic
|
||||
{
|
||||
Widget panel;
|
||||
ScrollPanelWidget playerList;
|
||||
ScrollItemWidget playerTemplate, playerHeader;
|
||||
static Filter filter = new Filter();
|
||||
|
||||
MapPreview selectedMap = MapCache.UnknownMap;
|
||||
Dictionary<CPos, Session.Client> selectedSpawns;
|
||||
string selectedFilename;
|
||||
string selectedDuration;
|
||||
bool selectedValid;
|
||||
Widget panel;
|
||||
ScrollPanelWidget replayList, playerList;
|
||||
ScrollItemWidget playerTemplate, playerHeader;
|
||||
List<ReplayMetadata> replays;
|
||||
Dictionary<ReplayMetadata, ReplayState> replayState = new Dictionary<ReplayMetadata, ReplayState>();
|
||||
|
||||
Dictionary<CPos, SpawnOccupant> selectedSpawns;
|
||||
ReplayMetadata selectedReplay;
|
||||
|
||||
[ObjectCreator.UseCtor]
|
||||
public ReplayBrowserLogic(Widget widget, Action onExit, Action onStart)
|
||||
@@ -42,130 +45,685 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
||||
|
||||
panel.Get<ButtonWidget>("CANCEL_BUTTON").OnClick = () => { Ui.CloseWindow(); onExit(); };
|
||||
|
||||
var rl = panel.Get<ScrollPanelWidget>("REPLAY_LIST");
|
||||
replayList = panel.Get<ScrollPanelWidget>("REPLAY_LIST");
|
||||
var template = panel.Get<ScrollItemWidget>("REPLAY_TEMPLATE");
|
||||
|
||||
var mod = Game.modData.Manifest.Mod;
|
||||
var dir = new[] { Platform.SupportDir, "Replays", mod.Id, mod.Version }.Aggregate(Path.Combine);
|
||||
|
||||
rl.RemoveChildren();
|
||||
replayList.RemoveChildren();
|
||||
if (Directory.Exists(dir))
|
||||
{
|
||||
var files = Directory.GetFiles(dir, "*.rep").Reverse();
|
||||
foreach (var replayFile in files)
|
||||
AddReplay(rl, replayFile, template);
|
||||
using (new Support.PerfTimer("Load replays"))
|
||||
{
|
||||
replays = Directory
|
||||
.GetFiles(dir, "*.rep")
|
||||
.Select((filename) => ReplayMetadata.Read(filename))
|
||||
.Where((r) => r != null)
|
||||
.OrderByDescending(r => r.GameInfo.StartTimeUtc)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
SelectReplay(files.FirstOrDefault());
|
||||
foreach (var replay in replays)
|
||||
AddReplay(replay, template);
|
||||
|
||||
ApplyFilter();
|
||||
}
|
||||
|
||||
var watch = panel.Get<ButtonWidget>("WATCH_BUTTON");
|
||||
watch.IsDisabled = () => !selectedValid || selectedMap.Status != MapStatus.Available;
|
||||
watch.IsDisabled = () => selectedReplay == null || selectedReplay.GameInfo.MapPreview.Status != MapStatus.Available;
|
||||
watch.OnClick = () => { WatchReplay(); onStart(); };
|
||||
|
||||
panel.Get("REPLAY_INFO").IsVisible = () => selectedFilename != null;
|
||||
panel.Get("REPLAY_INFO").IsVisible = () => selectedReplay != null;
|
||||
|
||||
var preview = panel.Get<MapPreviewWidget>("MAP_PREVIEW");
|
||||
preview.SpawnClients = () => selectedSpawns;
|
||||
preview.Preview = () => selectedMap;
|
||||
preview.SpawnOccupants = () => selectedSpawns;
|
||||
preview.Preview = () => selectedReplay != null ? selectedReplay.GameInfo.MapPreview : null;
|
||||
|
||||
var title = panel.GetOrNull<LabelWidget>("MAP_TITLE");
|
||||
if (title != null)
|
||||
title.GetText = () => selectedMap.Title;
|
||||
title.GetText = () => selectedReplay != null ? selectedReplay.GameInfo.MapPreview.Title : null;
|
||||
|
||||
var type = panel.GetOrNull<LabelWidget>("MAP_TYPE");
|
||||
if (type != null)
|
||||
type.GetText = () => selectedMap.Type;
|
||||
type.GetText = () => selectedReplay.GameInfo.MapPreview.Type;
|
||||
|
||||
panel.Get<LabelWidget>("DURATION").GetText = () => selectedDuration;
|
||||
panel.Get<LabelWidget>("DURATION").GetText = () => WidgetUtils.FormatTimeSeconds((int)selectedReplay.GameInfo.Duration.TotalSeconds);
|
||||
|
||||
SetupFilters();
|
||||
SetupManagement();
|
||||
}
|
||||
|
||||
void SelectReplay(string filename)
|
||||
void SetupFilters()
|
||||
{
|
||||
if (filename == null)
|
||||
//
|
||||
// Game type
|
||||
//
|
||||
{
|
||||
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_GAMETYPE_DROPDOWNBUTTON");
|
||||
if (ddb != null)
|
||||
{
|
||||
// Using list to maintain the order
|
||||
var options = new List<Pair<GameType, string>>
|
||||
{
|
||||
Pair.New(GameType.Any, ddb.GetText()),
|
||||
Pair.New(GameType.Singleplayer, "Singleplayer"),
|
||||
Pair.New(GameType.Multiplayer, "Multiplayer")
|
||||
};
|
||||
var lookup = options.ToDictionary(kvp => kvp.First, kvp => kvp.Second);
|
||||
|
||||
ddb.GetText = () => lookup[filter.Type];
|
||||
ddb.OnMouseDown = _ =>
|
||||
{
|
||||
Func<Pair<GameType, string>, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) =>
|
||||
{
|
||||
var item = ScrollItemWidget.Setup(
|
||||
tpl,
|
||||
() => filter.Type == option.First,
|
||||
() => { filter.Type = option.First; ApplyFilter(); }
|
||||
);
|
||||
item.Get<LabelWidget>("LABEL").GetText = () => option.Second;
|
||||
return item;
|
||||
};
|
||||
|
||||
ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", options.Count * 30, options, setupItem);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Date type
|
||||
//
|
||||
{
|
||||
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_DATE_DROPDOWNBUTTON");
|
||||
if (ddb != null)
|
||||
{
|
||||
// Using list to maintain the order
|
||||
var options = new List<Pair<DateType, string>>
|
||||
{
|
||||
Pair.New(DateType.Any, ddb.GetText()),
|
||||
Pair.New(DateType.Today, "Today"),
|
||||
Pair.New(DateType.LastWeek, "Last 7 days"),
|
||||
Pair.New(DateType.LastFortnight, "Last 14 days"),
|
||||
Pair.New(DateType.LastMonth, "Last 30 days")
|
||||
};
|
||||
var lookup = options.ToDictionary(kvp => kvp.First, kvp => kvp.Second);
|
||||
|
||||
ddb.GetText = () => lookup[filter.Date];
|
||||
ddb.OnMouseDown = _ =>
|
||||
{
|
||||
Func<Pair<DateType, string>, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) =>
|
||||
{
|
||||
var item = ScrollItemWidget.Setup(
|
||||
tpl,
|
||||
() => filter.Date == option.First,
|
||||
() => { filter.Date = option.First; ApplyFilter(); }
|
||||
);
|
||||
item.Get<LabelWidget>("LABEL").GetText = () => option.Second;
|
||||
return item;
|
||||
};
|
||||
|
||||
ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", options.Count * 30, options, setupItem);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Duration
|
||||
//
|
||||
{
|
||||
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_DURATION_DROPDOWNBUTTON");
|
||||
if (ddb != null)
|
||||
{
|
||||
// Using list to maintain the order
|
||||
var options = new List<Pair<DurationType, string>>
|
||||
{
|
||||
Pair.New(DurationType.Any, ddb.GetText()),
|
||||
Pair.New(DurationType.VeryShort, "Under 5 min"),
|
||||
Pair.New(DurationType.Short, "Short (10 min)"),
|
||||
Pair.New(DurationType.Medium, "Medium (30 min)"),
|
||||
Pair.New(DurationType.Long, "Long (60+ min)")
|
||||
};
|
||||
var lookup = options.ToDictionary(kvp => kvp.First, kvp => kvp.Second);
|
||||
|
||||
ddb.GetText = () => lookup[filter.Duration];
|
||||
ddb.OnMouseDown = _ =>
|
||||
{
|
||||
Func<Pair<DurationType, string>, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) =>
|
||||
{
|
||||
var item = ScrollItemWidget.Setup(
|
||||
tpl,
|
||||
() => filter.Duration == option.First,
|
||||
() => { filter.Duration = option.First; ApplyFilter(); }
|
||||
);
|
||||
item.Get<LabelWidget>("LABEL").GetText = () => option.Second;
|
||||
return item;
|
||||
};
|
||||
|
||||
ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", options.Count * 30, options, setupItem);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Map
|
||||
//
|
||||
{
|
||||
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_MAPNAME_DROPDOWNBUTTON");
|
||||
if (ddb != null)
|
||||
{
|
||||
var options = new HashSet<string>(replays.Select(r => r.GameInfo.MapTitle), StringComparer.OrdinalIgnoreCase).ToList();
|
||||
options.Sort(StringComparer.OrdinalIgnoreCase);
|
||||
options.Insert(0, null); // no filter
|
||||
|
||||
var anyText = ddb.GetText();
|
||||
ddb.GetText = () => string.IsNullOrEmpty(filter.MapName) ? anyText : filter.MapName;
|
||||
ddb.OnMouseDown = _ =>
|
||||
{
|
||||
Func<string, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) =>
|
||||
{
|
||||
var item = ScrollItemWidget.Setup(
|
||||
tpl,
|
||||
() => string.Compare(filter.MapName, option, true) == 0,
|
||||
() => { filter.MapName = option; ApplyFilter(); }
|
||||
);
|
||||
item.Get<LabelWidget>("LABEL").GetText = () => option ?? anyText;
|
||||
return item;
|
||||
};
|
||||
|
||||
ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", options.Count * 30, options, setupItem);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Players
|
||||
//
|
||||
{
|
||||
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_PLAYER_DROPDOWNBUTTON");
|
||||
if (ddb != null)
|
||||
{
|
||||
var options = new HashSet<string>(replays.SelectMany(r => r.GameInfo.Players.Select(p => p.Name)), StringComparer.OrdinalIgnoreCase).ToList();
|
||||
options.Sort(StringComparer.OrdinalIgnoreCase);
|
||||
options.Insert(0, null); // no filter
|
||||
|
||||
var anyText = ddb.GetText();
|
||||
ddb.GetText = () => string.IsNullOrEmpty(filter.PlayerName) ? anyText : filter.PlayerName;
|
||||
ddb.OnMouseDown = _ =>
|
||||
{
|
||||
Func<string, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) =>
|
||||
{
|
||||
var item = ScrollItemWidget.Setup(
|
||||
tpl,
|
||||
() => string.Compare(filter.PlayerName, option, true) == 0,
|
||||
() => { filter.PlayerName = option; ApplyFilter(); }
|
||||
);
|
||||
item.Get<LabelWidget>("LABEL").GetText = () => option ?? anyText;
|
||||
return item;
|
||||
};
|
||||
|
||||
ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", options.Count * 30, options, setupItem);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Outcome (depends on Player)
|
||||
//
|
||||
{
|
||||
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_OUTCOME_DROPDOWNBUTTON");
|
||||
if (ddb != null)
|
||||
{
|
||||
ddb.IsDisabled = () => string.IsNullOrEmpty(filter.PlayerName);
|
||||
|
||||
// Using list to maintain the order
|
||||
var options = new List<Pair<WinState, string>>
|
||||
{
|
||||
Pair.New(WinState.Undefined, ddb.GetText()),
|
||||
Pair.New(WinState.Lost, "Defeat"),
|
||||
Pair.New(WinState.Won, "Victory")
|
||||
};
|
||||
var lookup = options.ToDictionary(kvp => kvp.First, kvp => kvp.Second);
|
||||
|
||||
ddb.GetText = () => lookup[filter.Outcome];
|
||||
ddb.OnMouseDown = _ =>
|
||||
{
|
||||
Func<Pair<WinState, string>, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) =>
|
||||
{
|
||||
var item = ScrollItemWidget.Setup(
|
||||
tpl,
|
||||
() => filter.Outcome == option.First,
|
||||
() => { filter.Outcome = option.First; ApplyFilter(); }
|
||||
);
|
||||
item.Get<LabelWidget>("LABEL").GetText = () => option.Second;
|
||||
return item;
|
||||
};
|
||||
|
||||
ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", options.Count * 30, options, setupItem);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Faction (depends on Player)
|
||||
//
|
||||
{
|
||||
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_FACTION_DROPDOWNBUTTON");
|
||||
if (ddb != null)
|
||||
{
|
||||
ddb.IsDisabled = () => string.IsNullOrEmpty(filter.PlayerName);
|
||||
|
||||
var options = new HashSet<string>(replays.SelectMany(r => r.GameInfo.Players.Select(p => p.FactionName).Where(n => !string.IsNullOrEmpty(n))), StringComparer.OrdinalIgnoreCase).ToList();
|
||||
options.Sort(StringComparer.OrdinalIgnoreCase);
|
||||
options.Insert(0, null); // no filter
|
||||
|
||||
var anyText = ddb.GetText();
|
||||
ddb.GetText = () => string.IsNullOrEmpty(filter.Faction) ? anyText : filter.Faction;
|
||||
ddb.OnMouseDown = _ =>
|
||||
{
|
||||
Func<string, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) =>
|
||||
{
|
||||
var item = ScrollItemWidget.Setup(
|
||||
tpl,
|
||||
() => string.Compare(filter.Faction, option, true) == 0,
|
||||
() => { filter.Faction = option; ApplyFilter(); }
|
||||
);
|
||||
item.Get<LabelWidget>("LABEL").GetText = () => option ?? anyText;
|
||||
return item;
|
||||
};
|
||||
|
||||
ddb.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", options.Count * 30, options, setupItem);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Reset button
|
||||
//
|
||||
{
|
||||
var button = panel.Get<ButtonWidget>("FLT_RESET_BUTTON");
|
||||
button.IsDisabled = () => filter.IsEmpty;
|
||||
button.OnClick = () => { filter = new Filter(); ApplyFilter(); };
|
||||
}
|
||||
}
|
||||
|
||||
void SetupManagement()
|
||||
{
|
||||
{
|
||||
var button = panel.Get<ButtonWidget>("MNG_RENSEL_BUTTON");
|
||||
button.IsDisabled = () => selectedReplay == null;
|
||||
button.OnClick = () =>
|
||||
{
|
||||
var r = selectedReplay;
|
||||
var initialName = Path.GetFileNameWithoutExtension(r.FilePath);
|
||||
var directoryName = Path.GetDirectoryName(r.FilePath);
|
||||
var invalidChars = Path.GetInvalidFileNameChars();
|
||||
|
||||
ConfirmationDialogs.TextInputPrompt(
|
||||
"Rename Replay",
|
||||
"Enter a new file name:",
|
||||
initialName,
|
||||
onAccept: (newName) =>
|
||||
{
|
||||
RenameReplay(r, newName);
|
||||
},
|
||||
onCancel: null,
|
||||
acceptText: "Rename",
|
||||
cancelText: null,
|
||||
inputValidator: (newName) =>
|
||||
{
|
||||
if (newName == initialName)
|
||||
return false;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(newName))
|
||||
return false;
|
||||
|
||||
if (newName.IndexOfAny(invalidChars) >= 0)
|
||||
return false;
|
||||
|
||||
if (File.Exists(Path.Combine(directoryName, newName)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
Action<ReplayMetadata, Action> onDeleteReplay = (r, after) =>
|
||||
{
|
||||
ConfirmationDialogs.PromptConfirmAction(
|
||||
"Delete selected replay?",
|
||||
"Delete replay '{0}'?".F(Path.GetFileNameWithoutExtension(r.FilePath)),
|
||||
() =>
|
||||
{
|
||||
DeleteReplay(r);
|
||||
if (after != null)
|
||||
after.Invoke();
|
||||
},
|
||||
null,
|
||||
"Delete");
|
||||
};
|
||||
|
||||
{
|
||||
var button = panel.Get<ButtonWidget>("MNG_DELSEL_BUTTON");
|
||||
button.IsDisabled = () => selectedReplay == null;
|
||||
button.OnClick = () =>
|
||||
{
|
||||
onDeleteReplay(selectedReplay, () =>
|
||||
{
|
||||
if (selectedReplay == null)
|
||||
SelectFirstVisibleReplay();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
{
|
||||
var button = panel.Get<ButtonWidget>("MNG_DELALL_BUTTON");
|
||||
button.IsDisabled = () => replayState.Count(kvp => kvp.Value.Visible) == 0;
|
||||
button.OnClick = () =>
|
||||
{
|
||||
var list = replayState.Where(kvp => kvp.Value.Visible).Select(kvp => kvp.Key).ToList();
|
||||
if (list.Count == 0)
|
||||
return;
|
||||
|
||||
if (list.Count == 1)
|
||||
{
|
||||
onDeleteReplay(list[0], () => { if (selectedReplay == null) SelectFirstVisibleReplay(); });
|
||||
return;
|
||||
}
|
||||
|
||||
ConfirmationDialogs.PromptConfirmAction(
|
||||
"Delete all selected replays?",
|
||||
"Delete {0} replays?".F(list.Count),
|
||||
() =>
|
||||
{
|
||||
list.ForEach((r) => DeleteReplay(r));
|
||||
if (selectedReplay == null)
|
||||
SelectFirstVisibleReplay();
|
||||
},
|
||||
null,
|
||||
"Delete All");
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void RenameReplay(ReplayMetadata replay, string newFilenameWithoutExtension)
|
||||
{
|
||||
try
|
||||
{
|
||||
replay.RenameFile(newFilenameWithoutExtension);
|
||||
replayState[replay].Item.Text = newFilenameWithoutExtension;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Write("debug", ex.ToString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void DeleteReplay(ReplayMetadata replay)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(replay.FilePath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Game.Debug("Failed to delete replay file '{0}'. See the logs for details.", replay.FilePath);
|
||||
Log.Write("debug", ex.ToString());
|
||||
return;
|
||||
}
|
||||
|
||||
if (replay == selectedReplay)
|
||||
SelectReplay(null);
|
||||
|
||||
replayList.RemoveChild(replayState[replay].Item);
|
||||
replays.Remove(replay);
|
||||
replayState.Remove(replay);
|
||||
}
|
||||
|
||||
bool EvaluateReplayVisibility(ReplayMetadata replay)
|
||||
{
|
||||
// Game type
|
||||
if ((filter.Type == GameType.Multiplayer && replay.GameInfo.IsSinglePlayer) || (filter.Type == GameType.Singleplayer && !replay.GameInfo.IsSinglePlayer))
|
||||
return false;
|
||||
|
||||
// Date type
|
||||
if (filter.Date != DateType.Any)
|
||||
{
|
||||
TimeSpan t;
|
||||
switch (filter.Date)
|
||||
{
|
||||
case DateType.Today:
|
||||
t = TimeSpan.FromDays(1d);
|
||||
break;
|
||||
|
||||
case DateType.LastWeek:
|
||||
t = TimeSpan.FromDays(7d);
|
||||
break;
|
||||
|
||||
case DateType.LastFortnight:
|
||||
t = TimeSpan.FromDays(14d);
|
||||
break;
|
||||
|
||||
case DateType.LastMonth:
|
||||
default:
|
||||
t = TimeSpan.FromDays(30d);
|
||||
break;
|
||||
}
|
||||
if (replay.GameInfo.StartTimeUtc < DateTime.UtcNow - t)
|
||||
return false;
|
||||
}
|
||||
|
||||
// Duration
|
||||
if (filter.Duration != DurationType.Any)
|
||||
{
|
||||
var minutes = replay.GameInfo.Duration.TotalMinutes;
|
||||
switch (filter.Duration)
|
||||
{
|
||||
case DurationType.VeryShort:
|
||||
if (minutes >= 5)
|
||||
return false;
|
||||
break;
|
||||
|
||||
case DurationType.Short:
|
||||
if (minutes < 5 || minutes >= 20)
|
||||
return false;
|
||||
break;
|
||||
|
||||
case DurationType.Medium:
|
||||
if (minutes < 20 || minutes >= 60)
|
||||
return false;
|
||||
break;
|
||||
|
||||
case DurationType.Long:
|
||||
if (minutes < 60)
|
||||
return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Map
|
||||
if (!string.IsNullOrEmpty(filter.MapName) && string.Compare(filter.MapName, replay.GameInfo.MapTitle, true) != 0)
|
||||
return false;
|
||||
|
||||
// Player
|
||||
if (!string.IsNullOrEmpty(filter.PlayerName))
|
||||
{
|
||||
var player = replay.GameInfo.Players.FirstOrDefault(p => string.Compare(filter.PlayerName, p.Name, true) == 0);
|
||||
if (player == null)
|
||||
return false;
|
||||
|
||||
// Outcome
|
||||
if (filter.Outcome != WinState.Undefined && filter.Outcome != player.Outcome)
|
||||
return false;
|
||||
|
||||
// Faction
|
||||
if (!string.IsNullOrEmpty(filter.Faction) && string.Compare(filter.Faction, player.FactionName, true) != 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ApplyFilter()
|
||||
{
|
||||
foreach (var replay in replays)
|
||||
replayState[replay].Visible = EvaluateReplayVisibility(replay);
|
||||
|
||||
if (selectedReplay == null || replayState[selectedReplay].Visible == false)
|
||||
SelectFirstVisibleReplay();
|
||||
|
||||
replayList.Layout.AdjustChildren();
|
||||
}
|
||||
|
||||
void SelectFirstVisibleReplay()
|
||||
{
|
||||
SelectReplay(replays.FirstOrDefault(r => replayState[r].Visible));
|
||||
}
|
||||
|
||||
void SelectReplay(ReplayMetadata replay)
|
||||
{
|
||||
selectedReplay = replay;
|
||||
selectedSpawns = (selectedReplay != null)
|
||||
? LobbyUtils.GetSpawnOccupants(selectedReplay.GameInfo.Players, selectedReplay.GameInfo.MapPreview)
|
||||
: new Dictionary<CPos, SpawnOccupant>();
|
||||
|
||||
if (replay == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
using (var conn = new ReplayConnection(filename))
|
||||
var players = replay.GameInfo.Players
|
||||
.GroupBy(p => p.Team)
|
||||
.OrderBy(g => g.Key);
|
||||
|
||||
var teams = new Dictionary<string, IEnumerable<GameInformation.Player>>();
|
||||
var noTeams = players.Count() == 1;
|
||||
foreach (var p in players)
|
||||
{
|
||||
selectedFilename = filename;
|
||||
selectedMap = Game.modData.MapCache[conn.LobbyInfo.GlobalSettings.Map];
|
||||
selectedSpawns = LobbyUtils.GetSpawnClients(conn.LobbyInfo, selectedMap);
|
||||
selectedDuration = WidgetUtils.FormatTime(conn.TickCount * Game.NetTickScale);
|
||||
selectedValid = conn.TickCount > 0;
|
||||
var label = noTeams ? "Players" : p.Key == 0 ? "No Team" : "Team {0}".F(p.Key);
|
||||
teams.Add(label, p);
|
||||
}
|
||||
|
||||
var clients = conn.LobbyInfo.Clients.Where(c => c.Slot != null)
|
||||
.GroupBy(c => c.Team)
|
||||
.OrderBy(g => g.Key);
|
||||
playerList.RemoveChildren();
|
||||
|
||||
var teams = new Dictionary<string, IEnumerable<Session.Client>>();
|
||||
var noTeams = clients.Count() == 1;
|
||||
foreach (var c in clients)
|
||||
foreach (var kv in teams)
|
||||
{
|
||||
var group = kv.Key;
|
||||
if (group.Length > 0)
|
||||
{
|
||||
var label = noTeams ? "Players" : c.Key == 0 ? "No Team" : "Team {0}".F(c.Key);
|
||||
teams.Add(label, c);
|
||||
var header = ScrollItemWidget.Setup(playerHeader, () => true, () => {});
|
||||
header.Get<LabelWidget>("LABEL").GetText = () => group;
|
||||
playerList.AddChild(header);
|
||||
}
|
||||
|
||||
playerList.RemoveChildren();
|
||||
|
||||
foreach (var kv in teams)
|
||||
foreach (var option in kv.Value)
|
||||
{
|
||||
var group = kv.Key;
|
||||
if (group.Length > 0)
|
||||
{
|
||||
var header = ScrollItemWidget.Setup(playerHeader, () => true, () => {});
|
||||
header.Get<LabelWidget>("LABEL").GetText = () => group;
|
||||
playerList.AddChild(header);
|
||||
}
|
||||
var o = option;
|
||||
|
||||
foreach (var option in kv.Value)
|
||||
{
|
||||
var o = option;
|
||||
var color = o.Color.RGB;
|
||||
|
||||
var color = o.Color.RGB;
|
||||
var item = ScrollItemWidget.Setup(playerTemplate, () => false, () => { });
|
||||
|
||||
var item = ScrollItemWidget.Setup(playerTemplate, () => false, () => { });
|
||||
var label = item.Get<LabelWidget>("LABEL");
|
||||
label.GetText = () => o.Name;
|
||||
label.GetColor = () => color;
|
||||
|
||||
var label = item.Get<LabelWidget>("LABEL");
|
||||
label.GetText = () => o.Name;
|
||||
label.GetColor = () => color;
|
||||
var flag = item.Get<ImageWidget>("FLAG");
|
||||
flag.GetImageCollection = () => "flags";
|
||||
flag.GetImageName = () => o.FactionId;
|
||||
|
||||
var flag = item.Get<ImageWidget>("FLAG");
|
||||
flag.GetImageCollection = () => "flags";
|
||||
flag.GetImageName = () => o.Country;
|
||||
|
||||
playerList.AddChild(item);
|
||||
}
|
||||
playerList.AddChild(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Write("debug", "Exception while parsing replay: {0}", e);
|
||||
selectedFilename = null;
|
||||
selectedValid = false;
|
||||
selectedMap = MapCache.UnknownMap;
|
||||
SelectReplay(null);
|
||||
}
|
||||
}
|
||||
|
||||
void WatchReplay()
|
||||
{
|
||||
if (selectedFilename != null)
|
||||
if (selectedReplay != null)
|
||||
{
|
||||
Game.JoinReplay(selectedFilename);
|
||||
Game.JoinReplay(selectedReplay.FilePath);
|
||||
Ui.CloseWindow();
|
||||
}
|
||||
}
|
||||
|
||||
void AddReplay(ScrollPanelWidget list, string filename, ScrollItemWidget template)
|
||||
void AddReplay(ReplayMetadata replay, ScrollItemWidget template)
|
||||
{
|
||||
var item = ScrollItemWidget.Setup(template,
|
||||
() => selectedFilename == filename,
|
||||
() => SelectReplay(filename),
|
||||
() => selectedReplay == replay,
|
||||
() => SelectReplay(replay),
|
||||
() => WatchReplay());
|
||||
var f = Path.GetFileName(filename);
|
||||
item.Get<LabelWidget>("TITLE").GetText = () => f;
|
||||
list.AddChild(item);
|
||||
|
||||
replayState[replay] = new ReplayState
|
||||
{
|
||||
Item = item,
|
||||
Visible = true
|
||||
};
|
||||
|
||||
item.Text = Path.GetFileNameWithoutExtension(replay.FilePath);
|
||||
item.Get<LabelWidget>("TITLE").GetText = () => item.Text;
|
||||
item.IsVisible = () => replayState[replay].Visible;
|
||||
replayList.AddChild(item);
|
||||
}
|
||||
|
||||
class ReplayState
|
||||
{
|
||||
public bool Visible;
|
||||
public ScrollItemWidget Item;
|
||||
}
|
||||
|
||||
class Filter
|
||||
{
|
||||
public GameType Type;
|
||||
public DateType Date;
|
||||
public DurationType Duration;
|
||||
public WinState Outcome;
|
||||
public string PlayerName;
|
||||
public string MapName;
|
||||
public string Faction;
|
||||
|
||||
public bool IsEmpty
|
||||
{
|
||||
get
|
||||
{
|
||||
return Type == default(GameType)
|
||||
&& Date == default(DateType)
|
||||
&& Duration == default(DurationType)
|
||||
&& Outcome == default(WinState)
|
||||
&& string.IsNullOrEmpty(PlayerName)
|
||||
&& string.IsNullOrEmpty(MapName)
|
||||
&& string.IsNullOrEmpty(Faction);
|
||||
}
|
||||
}
|
||||
}
|
||||
enum GameType
|
||||
{
|
||||
Any,
|
||||
Singleplayer,
|
||||
Multiplayer
|
||||
}
|
||||
enum DateType
|
||||
{
|
||||
Any,
|
||||
Today,
|
||||
LastWeek,
|
||||
LastFortnight,
|
||||
LastMonth
|
||||
}
|
||||
enum DurationType
|
||||
{
|
||||
Any,
|
||||
VeryShort,
|
||||
Short,
|
||||
Medium,
|
||||
Long
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,10 +38,10 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
||||
|
||||
tooltipContainer.BeforeRender = () =>
|
||||
{
|
||||
var client = preview.SpawnClients().Values.FirstOrDefault(c => c.SpawnPoint == preview.TooltipSpawnIndex);
|
||||
var occupant = preview.SpawnOccupants().Values.FirstOrDefault(c => c.SpawnPoint == preview.TooltipSpawnIndex);
|
||||
|
||||
var teamWidth = 0;
|
||||
if (client == null)
|
||||
if (occupant == null)
|
||||
{
|
||||
labelText = "Available spawn";
|
||||
playerCountry = null;
|
||||
@@ -50,9 +50,9 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
||||
}
|
||||
else
|
||||
{
|
||||
labelText = client.Name;
|
||||
playerCountry = client.Country;
|
||||
playerTeam = client.Team;
|
||||
labelText = occupant.PlayerName;
|
||||
playerCountry = occupant.Country;
|
||||
playerTeam = occupant.Team;
|
||||
widget.Bounds.Height = playerTeam > 0 ? doubleHeight : singleHeight;
|
||||
teamWidth = teamFont.Measure(team.GetText()).X;
|
||||
}
|
||||
|
||||
@@ -154,3 +154,49 @@ Container@CONFIRM_PROMPT:
|
||||
Height: 35
|
||||
Text: Confirm
|
||||
|
||||
|
||||
Container@TEXT_INPUT_PROMPT:
|
||||
X: (WINDOW_RIGHT - WIDTH)/2
|
||||
Y: (WINDOW_BOTTOM - HEIGHT)/2
|
||||
Width: 370
|
||||
Height: 80
|
||||
Children:
|
||||
Label@PROMPT_TITLE:
|
||||
Width: PARENT_RIGHT
|
||||
Y: 0-25
|
||||
Font: BigBold
|
||||
Contrast: true
|
||||
Align: Center
|
||||
Background@bg:
|
||||
Width: PARENT_RIGHT
|
||||
Height: 80
|
||||
Background: panel-black
|
||||
Children:
|
||||
Label@PROMPT_TEXT:
|
||||
X: 20
|
||||
Y: 10
|
||||
Width: PARENT_RIGHT - 40
|
||||
Height: 25
|
||||
Font: Bold
|
||||
Align: Center
|
||||
TextField@INPUT_TEXT:
|
||||
X: 20
|
||||
Y: 40
|
||||
Width: PARENT_RIGHT - 40
|
||||
Height: 25
|
||||
Button@ACCEPT_BUTTON:
|
||||
X: PARENT_RIGHT - 160
|
||||
Y: PARENT_BOTTOM - 1
|
||||
Width: 160
|
||||
Height: 30
|
||||
Text: OK
|
||||
Font: Bold
|
||||
Key: return
|
||||
Button@CANCEL_BUTTON:
|
||||
X: 0
|
||||
Y: PARENT_BOTTOM - 1
|
||||
Width: 160
|
||||
Height: 30
|
||||
Text: Cancel
|
||||
Font: Bold
|
||||
Key: escape
|
||||
|
||||
@@ -1,80 +1,250 @@
|
||||
Container@REPLAYBROWSER_PANEL:
|
||||
Logic: ReplayBrowserLogic
|
||||
X: (WINDOW_RIGHT - WIDTH)/2
|
||||
Y: (WINDOW_BOTTOM - 500)/2
|
||||
Width: 520
|
||||
Height: 535
|
||||
Y: (WINDOW_BOTTOM - HEIGHT)/2
|
||||
Width: 780
|
||||
Height: 500
|
||||
Children:
|
||||
Label@TITLE:
|
||||
Width: 520
|
||||
Width: PARENT_RIGHT
|
||||
Y: 0-25
|
||||
Font: BigBold
|
||||
Contrast: true
|
||||
Align: Center
|
||||
Text: Replay Viewer
|
||||
Background@bg:
|
||||
Width: 520
|
||||
Height: 500
|
||||
Width: PARENT_RIGHT
|
||||
Height: PARENT_BOTTOM
|
||||
Background: panel-black
|
||||
Children:
|
||||
ScrollPanel@REPLAY_LIST:
|
||||
X: 15
|
||||
Y: 15
|
||||
Width: 282
|
||||
Height: PARENT_BOTTOM-30
|
||||
Container@FILTER_AND_MANAGE_CONTAINER:
|
||||
X: 20
|
||||
Y: 20
|
||||
Width: 280
|
||||
Height: PARENT_BOTTOM - 40
|
||||
Children:
|
||||
ScrollItem@REPLAY_TEMPLATE:
|
||||
Width: PARENT_RIGHT-27
|
||||
Height: 25
|
||||
X: 2
|
||||
Y: 0
|
||||
Visible: false
|
||||
Container@FILTERS:
|
||||
Width: 280
|
||||
Height: 320
|
||||
Children:
|
||||
Label@TITLE:
|
||||
X: 10
|
||||
Width: PARENT_RIGHT-20
|
||||
Label@FILTERS_TITLE:
|
||||
X: 85
|
||||
Width: PARENT_RIGHT - 85
|
||||
Height: 25
|
||||
Background@MAP_BG:
|
||||
X: PARENT_RIGHT-WIDTH-15
|
||||
Y: 15
|
||||
Width: 194
|
||||
Height: 194
|
||||
Background: panel-gray
|
||||
Font: Bold
|
||||
Align: Center
|
||||
Text: Filter
|
||||
Label@FLT_GAMETYPE_DESC:
|
||||
X: 0
|
||||
Y: 30
|
||||
Width: 80
|
||||
Height: 25
|
||||
Text: Type:
|
||||
Align: Right
|
||||
DropDownButton@FLT_GAMETYPE_DROPDOWNBUTTON:
|
||||
X: 85
|
||||
Y: 30
|
||||
Width: PARENT_RIGHT - 85
|
||||
Height: 25
|
||||
Text: Any
|
||||
Label@FLT_DATE_DESC:
|
||||
X: 0
|
||||
Y: 60
|
||||
Width: 80
|
||||
Height: 25
|
||||
Text: Date:
|
||||
Align: Right
|
||||
DropDownButton@FLT_DATE_DROPDOWNBUTTON:
|
||||
X: 85
|
||||
Y: 60
|
||||
Width: PARENT_RIGHT - 85
|
||||
Height: 25
|
||||
Text: Any
|
||||
Label@FLT_DURATION_DESC:
|
||||
X: 0
|
||||
Y: 90
|
||||
Width: 80
|
||||
Height: 25
|
||||
Text: Duration:
|
||||
Align: Right
|
||||
DropDownButton@FLT_DURATION_DROPDOWNBUTTON:
|
||||
X: 85
|
||||
Y: 90
|
||||
Width: PARENT_RIGHT - 85
|
||||
Height: 25
|
||||
Text: Any
|
||||
Label@FLT_MAPNAME_DESC:
|
||||
X: 0
|
||||
Y: 120
|
||||
Width: 80
|
||||
Height: 25
|
||||
Text: Map:
|
||||
Align: Right
|
||||
DropDownButton@FLT_MAPNAME_DROPDOWNBUTTON:
|
||||
X: 85
|
||||
Y: 120
|
||||
Width: PARENT_RIGHT - 85
|
||||
Height: 25
|
||||
Text: Any
|
||||
Label@FLT_PLAYER_DESC:
|
||||
X: 0
|
||||
Y: 150
|
||||
Width: 80
|
||||
Height: 25
|
||||
Text: Player:
|
||||
Align: Right
|
||||
DropDownButton@FLT_PLAYER_DROPDOWNBUTTON:
|
||||
X: 85
|
||||
Y: 150
|
||||
Width: PARENT_RIGHT - 85
|
||||
Height: 25
|
||||
Text: Anyone
|
||||
Label@FLT_OUTCOME_DESC:
|
||||
X: 0
|
||||
Y: 180
|
||||
Width: 80
|
||||
Height: 25
|
||||
Text: Outcome:
|
||||
Align: Right
|
||||
DropDownButton@FLT_OUTCOME_DROPDOWNBUTTON:
|
||||
X: 85
|
||||
Y: 180
|
||||
Width: PARENT_RIGHT - 85
|
||||
Height: 25
|
||||
Text: Any
|
||||
Label@FLT_FACTION_DESC:
|
||||
X: 0
|
||||
Y: 210
|
||||
Width: 80
|
||||
Height: 25
|
||||
Text: Faction:
|
||||
Align: Right
|
||||
DropDownButton@FLT_FACTION_DROPDOWNBUTTON:
|
||||
X: 85
|
||||
Y: 210
|
||||
Width: PARENT_RIGHT - 85
|
||||
Height: 25
|
||||
Text: Any
|
||||
Button@FLT_RESET_BUTTON:
|
||||
X: 85
|
||||
Y: 250
|
||||
Width: PARENT_RIGHT - 85
|
||||
Height: 25
|
||||
Text: Reset Filters
|
||||
Font: Bold
|
||||
Container@MANAGEMENT:
|
||||
X: 85
|
||||
Y: PARENT_BOTTOM - 115
|
||||
Width: PARENT_RIGHT - 85
|
||||
Height: 115
|
||||
Children:
|
||||
Label@MANAGE_TITLE:
|
||||
Width: PARENT_RIGHT
|
||||
Height: 25
|
||||
Font: Bold
|
||||
Align: Center
|
||||
Text: Manage
|
||||
Button@MNG_RENSEL_BUTTON:
|
||||
Y: 30
|
||||
Width: PARENT_RIGHT
|
||||
Height: 25
|
||||
Text: Rename
|
||||
Font: Bold
|
||||
Key: F2
|
||||
Button@MNG_DELSEL_BUTTON:
|
||||
Y: 60
|
||||
Width: PARENT_RIGHT
|
||||
Height: 25
|
||||
Text: Delete
|
||||
Font: Bold
|
||||
Key: Delete
|
||||
Button@MNG_DELALL_BUTTON:
|
||||
Y: 90
|
||||
Width: PARENT_RIGHT
|
||||
Height: 25
|
||||
Text: Delete All
|
||||
Font: Bold
|
||||
Container@REPLAY_LIST_CONTAINER:
|
||||
X: 310
|
||||
Y: 20
|
||||
Width: 245
|
||||
Height: PARENT_BOTTOM - 20 - 20
|
||||
Children:
|
||||
MapPreview@MAP_PREVIEW:
|
||||
X: 1
|
||||
Y: 1
|
||||
Width: 192
|
||||
Height: 192
|
||||
TooltipContainer: TOOLTIP_CONTAINER
|
||||
Container@REPLAY_INFO:
|
||||
X: PARENT_RIGHT-WIDTH-15
|
||||
Y: 15
|
||||
Width: 194
|
||||
Height: PARENT_BOTTOM - 15
|
||||
Children:
|
||||
Label@MAP_TITLE:
|
||||
Y: 197
|
||||
Label@REPLAYBROWSER_LABEL_TITLE:
|
||||
Width: PARENT_RIGHT
|
||||
Height: 25
|
||||
Text: Choose Replay
|
||||
Align: Center
|
||||
Font: Bold
|
||||
ScrollPanel@REPLAY_LIST:
|
||||
X: 0
|
||||
Y: 30
|
||||
Width: PARENT_RIGHT
|
||||
Height: PARENT_BOTTOM - 30
|
||||
CollapseHiddenChildren: True
|
||||
Children:
|
||||
ScrollItem@REPLAY_TEMPLATE:
|
||||
Width: PARENT_RIGHT-27
|
||||
Height: 25
|
||||
X: 2
|
||||
Visible: false
|
||||
Children:
|
||||
Label@TITLE:
|
||||
X: 10
|
||||
Width: PARENT_RIGHT-20
|
||||
Height: 25
|
||||
Container@MAP_BG_CONTAINER:
|
||||
X: PARENT_RIGHT - WIDTH - 20
|
||||
Y: 20
|
||||
Width: 194
|
||||
Height: 30 + 194
|
||||
Children:
|
||||
Label@MAP_BG_TITLE:
|
||||
Width: PARENT_RIGHT
|
||||
Height: 25
|
||||
Text: Preview
|
||||
Align: Center
|
||||
Font: Bold
|
||||
Background@MAP_BG:
|
||||
Y: 30
|
||||
Width: 194
|
||||
Height: 194
|
||||
Background: panel-gray
|
||||
Children:
|
||||
MapPreview@MAP_PREVIEW:
|
||||
X: 1
|
||||
Y: 1
|
||||
Width: 192
|
||||
Height: 192
|
||||
TooltipContainer: TOOLTIP_CONTAINER
|
||||
Container@REPLAY_INFO:
|
||||
X: PARENT_RIGHT - WIDTH - 20
|
||||
Y: 20 + 30+194 + 10
|
||||
Width: 194
|
||||
Height: PARENT_BOTTOM - 20 - 30-194 - 10 - 20
|
||||
Children:
|
||||
Label@MAP_TITLE:
|
||||
Y: 0
|
||||
Width: PARENT_RIGHT
|
||||
Height: 15
|
||||
Font: Bold
|
||||
Align: Center
|
||||
Label@MAP_TYPE:
|
||||
Y: 212
|
||||
Y: 15
|
||||
Width: PARENT_RIGHT
|
||||
Height: 25
|
||||
Height: 15
|
||||
Font: TinyBold
|
||||
Align: Center
|
||||
Label@DURATION:
|
||||
Y: 225
|
||||
Y: 30
|
||||
Width: PARENT_RIGHT
|
||||
Height: 25
|
||||
Height: 15
|
||||
Font: Tiny
|
||||
Align: Center
|
||||
ScrollPanel@PLAYER_LIST:
|
||||
Y: 250
|
||||
Y: 50
|
||||
Width: PARENT_RIGHT
|
||||
Height: PARENT_BOTTOM - 250 - 15
|
||||
Height: PARENT_BOTTOM - 50
|
||||
IgnoreChildMouseOver: true
|
||||
Children:
|
||||
ScrollItem@HEADER:
|
||||
@@ -98,7 +268,7 @@ Container@REPLAYBROWSER_PANEL:
|
||||
Children:
|
||||
Image@FLAG:
|
||||
X: 4
|
||||
Y: 4
|
||||
Y: 6
|
||||
Width: 32
|
||||
Height: 16
|
||||
Label@LABEL:
|
||||
@@ -112,14 +282,14 @@ Container@REPLAYBROWSER_PANEL:
|
||||
Button@CANCEL_BUTTON:
|
||||
Key: escape
|
||||
X: 0
|
||||
Y: 499
|
||||
Y: PARENT_BOTTOM - 1
|
||||
Width: 140
|
||||
Height: 35
|
||||
Text: Back
|
||||
Button@WATCH_BUTTON:
|
||||
Key: return
|
||||
X: 380
|
||||
Y: 499
|
||||
X: PARENT_RIGHT - 140
|
||||
Y: PARENT_BOTTOM - 1
|
||||
Width: 140
|
||||
Height: 35
|
||||
Text: Watch
|
||||
|
||||
@@ -14,6 +14,7 @@ Metrics:
|
||||
TextfieldFont: Regular
|
||||
TextfieldColor: 255,255,255
|
||||
TextfieldColorDisabled: 128,128,128
|
||||
TextfieldColorInvalid: 255,192,192
|
||||
TextFont: Regular
|
||||
TextColor: 255,255,255
|
||||
TextContrast: false
|
||||
|
||||
@@ -14,6 +14,7 @@ Metrics:
|
||||
TextfieldFont: Regular
|
||||
TextfieldColor: 255,255,255
|
||||
TextfieldColorDisabled: 128,128,128
|
||||
TextfieldColorInvalid: 255,192,192
|
||||
TextFont: Regular
|
||||
TextColor: 255,255,255
|
||||
TextContrast: false
|
||||
|
||||
@@ -14,6 +14,7 @@ Metrics:
|
||||
TextfieldFont: Regular
|
||||
TextfieldColor: 255,255,255
|
||||
TextfieldColorDisabled: 128,128,128
|
||||
TextfieldColorInvalid: 255,192,192
|
||||
TextFont: Regular
|
||||
TextColor: 255,255,255
|
||||
TextContrast: false
|
||||
|
||||
@@ -32,3 +32,43 @@ Background@CONFIRM_PROMPT:
|
||||
Text: Cancel
|
||||
Font: Bold
|
||||
Key: escape
|
||||
|
||||
Background@TEXT_INPUT_PROMPT:
|
||||
X: (WINDOW_RIGHT - WIDTH)/2
|
||||
Y: (WINDOW_BOTTOM - HEIGHT)/2
|
||||
Width: 370
|
||||
Height: 175
|
||||
Children:
|
||||
Label@PROMPT_TITLE:
|
||||
Width: PARENT_RIGHT
|
||||
Y: 20
|
||||
Height: 25
|
||||
Font: Bold
|
||||
Align: Center
|
||||
Label@PROMPT_TEXT:
|
||||
X: 20
|
||||
Y: 50
|
||||
Width: PARENT_RIGHT - 40
|
||||
Height: 25
|
||||
Align: Center
|
||||
TextField@INPUT_TEXT:
|
||||
X: 20
|
||||
Y: 80
|
||||
Width: PARENT_RIGHT - 40
|
||||
Height: 25
|
||||
Button@ACCEPT_BUTTON:
|
||||
X: 20
|
||||
Y: PARENT_BOTTOM - 45
|
||||
Width: 160
|
||||
Height: 25
|
||||
Text: OK
|
||||
Font: Bold
|
||||
Key: return
|
||||
Button@CANCEL_BUTTON:
|
||||
X: PARENT_RIGHT - 180
|
||||
Y: PARENT_BOTTOM - 45
|
||||
Width: 160
|
||||
Height: 25
|
||||
Text: Cancel
|
||||
Font: Bold
|
||||
Key: escape
|
||||
|
||||
@@ -2,73 +2,237 @@ Background@REPLAYBROWSER_PANEL:
|
||||
Logic: ReplayBrowserLogic
|
||||
X: (WINDOW_RIGHT - WIDTH)/2
|
||||
Y: (WINDOW_BOTTOM - HEIGHT)/2
|
||||
Width: 530
|
||||
Width: 780
|
||||
Height: 535
|
||||
Children:
|
||||
Label@REPLAYBROWSER_LABEL_TITLE:
|
||||
Y: 20
|
||||
Width: PARENT_RIGHT
|
||||
Height: 25
|
||||
Text: Choose Replay
|
||||
Align: Center
|
||||
Font: Bold
|
||||
ScrollPanel@REPLAY_LIST:
|
||||
Container@FILTER_AND_MANAGE_CONTAINER:
|
||||
X: 20
|
||||
Y: 50
|
||||
Width: 282
|
||||
Height: 430
|
||||
Y: 20
|
||||
Width: 280
|
||||
Height: PARENT_BOTTOM - 75
|
||||
Children:
|
||||
ScrollItem@REPLAY_TEMPLATE:
|
||||
Width: PARENT_RIGHT-27
|
||||
Height: 25
|
||||
X: 2
|
||||
Visible: false
|
||||
Container@FILTERS:
|
||||
Width: 280
|
||||
Height: 320
|
||||
Children:
|
||||
Label@TITLE:
|
||||
X: 10
|
||||
Width: PARENT_RIGHT-20
|
||||
Label@FILTERS_TITLE:
|
||||
X: 85
|
||||
Width: PARENT_RIGHT - 85
|
||||
Height: 25
|
||||
Background@MAP_BG:
|
||||
X: PARENT_RIGHT-WIDTH-20
|
||||
Y: 50
|
||||
Width: 194
|
||||
Height: 194
|
||||
Background: dialog3
|
||||
Font: Bold
|
||||
Align: Center
|
||||
Text: Filter
|
||||
Label@FLT_GAMETYPE_DESC:
|
||||
X: 0
|
||||
Y: 30
|
||||
Width: 80
|
||||
Height: 25
|
||||
Text: Type:
|
||||
Align: Right
|
||||
DropDownButton@FLT_GAMETYPE_DROPDOWNBUTTON:
|
||||
X: 85
|
||||
Y: 30
|
||||
Width: PARENT_RIGHT - 85
|
||||
Height: 25
|
||||
Text: Any
|
||||
Label@FLT_DATE_DESC:
|
||||
X: 0
|
||||
Y: 60
|
||||
Width: 80
|
||||
Height: 25
|
||||
Text: Date:
|
||||
Align: Right
|
||||
DropDownButton@FLT_DATE_DROPDOWNBUTTON:
|
||||
X: 85
|
||||
Y: 60
|
||||
Width: PARENT_RIGHT - 85
|
||||
Height: 25
|
||||
Text: Any
|
||||
Label@FLT_DURATION_DESC:
|
||||
X: 0
|
||||
Y: 90
|
||||
Width: 80
|
||||
Height: 25
|
||||
Text: Duration:
|
||||
Align: Right
|
||||
DropDownButton@FLT_DURATION_DROPDOWNBUTTON:
|
||||
X: 85
|
||||
Y: 90
|
||||
Width: PARENT_RIGHT - 85
|
||||
Height: 25
|
||||
Text: Any
|
||||
Label@FLT_MAPNAME_DESC:
|
||||
X: 0
|
||||
Y: 120
|
||||
Width: 80
|
||||
Height: 25
|
||||
Text: Map:
|
||||
Align: Right
|
||||
DropDownButton@FLT_MAPNAME_DROPDOWNBUTTON:
|
||||
X: 85
|
||||
Y: 120
|
||||
Width: PARENT_RIGHT - 85
|
||||
Height: 25
|
||||
Text: Any
|
||||
Label@FLT_PLAYER_DESC:
|
||||
X: 0
|
||||
Y: 150
|
||||
Width: 80
|
||||
Height: 25
|
||||
Text: Player:
|
||||
Align: Right
|
||||
DropDownButton@FLT_PLAYER_DROPDOWNBUTTON:
|
||||
X: 85
|
||||
Y: 150
|
||||
Width: PARENT_RIGHT - 85
|
||||
Height: 25
|
||||
Text: Anyone
|
||||
Label@FLT_OUTCOME_DESC:
|
||||
X: 0
|
||||
Y: 180
|
||||
Width: 80
|
||||
Height: 25
|
||||
Text: Outcome:
|
||||
Align: Right
|
||||
DropDownButton@FLT_OUTCOME_DROPDOWNBUTTON:
|
||||
X: 85
|
||||
Y: 180
|
||||
Width: PARENT_RIGHT - 85
|
||||
Height: 25
|
||||
Text: Any
|
||||
Label@FLT_FACTION_DESC:
|
||||
X: 0
|
||||
Y: 210
|
||||
Width: 80
|
||||
Height: 25
|
||||
Text: Faction:
|
||||
Align: Right
|
||||
DropDownButton@FLT_FACTION_DROPDOWNBUTTON:
|
||||
X: 85
|
||||
Y: 210
|
||||
Width: PARENT_RIGHT - 85
|
||||
Height: 25
|
||||
Text: Any
|
||||
Button@FLT_RESET_BUTTON:
|
||||
X: 85
|
||||
Y: 250
|
||||
Width: PARENT_RIGHT - 85
|
||||
Height: 25
|
||||
Text: Reset Filters
|
||||
Font: Bold
|
||||
Container@MANAGEMENT:
|
||||
X: 85
|
||||
Y: PARENT_BOTTOM - 115
|
||||
Width: PARENT_RIGHT - 85
|
||||
Height: 115
|
||||
Children:
|
||||
Label@MANAGE_TITLE:
|
||||
Width: PARENT_RIGHT
|
||||
Height: 25
|
||||
Font: Bold
|
||||
Align: Center
|
||||
Text: Manage
|
||||
Button@MNG_RENSEL_BUTTON:
|
||||
Y: 30
|
||||
Width: PARENT_RIGHT
|
||||
Height: 25
|
||||
Text: Rename
|
||||
Font: Bold
|
||||
Key: F2
|
||||
Button@MNG_DELSEL_BUTTON:
|
||||
Y: 60
|
||||
Width: PARENT_RIGHT
|
||||
Height: 25
|
||||
Text: Delete
|
||||
Font: Bold
|
||||
Key: Delete
|
||||
Button@MNG_DELALL_BUTTON:
|
||||
Y: 90
|
||||
Width: PARENT_RIGHT
|
||||
Height: 25
|
||||
Text: Delete All
|
||||
Font: Bold
|
||||
Container@REPLAY_LIST_CONTAINER:
|
||||
X: 310
|
||||
Y: 20
|
||||
Width: 245
|
||||
Height: PARENT_BOTTOM - 20 - 55
|
||||
Children:
|
||||
MapPreview@MAP_PREVIEW:
|
||||
X: 1
|
||||
Y: 1
|
||||
Width: 192
|
||||
Height: 192
|
||||
TooltipContainer: TOOLTIP_CONTAINER
|
||||
Container@REPLAY_INFO:
|
||||
X: PARENT_RIGHT-WIDTH - 20
|
||||
Y: 50
|
||||
Width: 194
|
||||
Height: PARENT_BOTTOM - 15
|
||||
Children:
|
||||
Label@MAP_TITLE:
|
||||
Y: 197
|
||||
Label@REPLAYBROWSER_LABEL_TITLE:
|
||||
Width: PARENT_RIGHT
|
||||
Height: 25
|
||||
Text: Choose Replay
|
||||
Align: Center
|
||||
Font: Bold
|
||||
ScrollPanel@REPLAY_LIST:
|
||||
X: 0
|
||||
Y: 30
|
||||
Width: PARENT_RIGHT
|
||||
Height: PARENT_BOTTOM - 30
|
||||
CollapseHiddenChildren: True
|
||||
Children:
|
||||
ScrollItem@REPLAY_TEMPLATE:
|
||||
Width: PARENT_RIGHT-27
|
||||
Height: 25
|
||||
X: 2
|
||||
Visible: false
|
||||
Children:
|
||||
Label@TITLE:
|
||||
X: 10
|
||||
Width: PARENT_RIGHT-20
|
||||
Height: 25
|
||||
Container@MAP_BG_CONTAINER:
|
||||
X: PARENT_RIGHT - WIDTH - 20
|
||||
Y: 20
|
||||
Width: 194
|
||||
Height: 30 + 194
|
||||
Children:
|
||||
Label@MAP_BG_TITLE:
|
||||
Width: PARENT_RIGHT
|
||||
Height: 25
|
||||
Text: Preview
|
||||
Align: Center
|
||||
Font: Bold
|
||||
Background@MAP_BG:
|
||||
Y: 30
|
||||
Width: 194
|
||||
Height: 194
|
||||
Background: dialog3
|
||||
Children:
|
||||
MapPreview@MAP_PREVIEW:
|
||||
X: 1
|
||||
Y: 1
|
||||
Width: 192
|
||||
Height: 192
|
||||
TooltipContainer: TOOLTIP_CONTAINER
|
||||
Container@REPLAY_INFO:
|
||||
X: PARENT_RIGHT - WIDTH - 20
|
||||
Y: 20 + 30+194 + 10
|
||||
Width: 194
|
||||
Height: PARENT_BOTTOM - 20 - 30-194 - 10 - 55
|
||||
Children:
|
||||
Label@MAP_TITLE:
|
||||
Y: 0
|
||||
Width: PARENT_RIGHT
|
||||
Height: 15
|
||||
Font: Bold
|
||||
Align: Center
|
||||
Label@MAP_TYPE:
|
||||
Y: 212
|
||||
Y: 15
|
||||
Width: PARENT_RIGHT
|
||||
Height: 25
|
||||
Height: 15
|
||||
Font: TinyBold
|
||||
Align: Center
|
||||
Label@DURATION:
|
||||
Y: 225
|
||||
Y: 30
|
||||
Width: PARENT_RIGHT
|
||||
Height: 25
|
||||
Height: 15
|
||||
Font: Tiny
|
||||
Align: Center
|
||||
ScrollPanel@PLAYER_LIST:
|
||||
Y: 250
|
||||
Y: 50
|
||||
Width: PARENT_RIGHT
|
||||
Height: PARENT_BOTTOM - 340
|
||||
Height: PARENT_BOTTOM - 50
|
||||
IgnoreChildMouseOver: true
|
||||
Children:
|
||||
ScrollItem@HEADER:
|
||||
@@ -93,7 +257,7 @@ Background@REPLAYBROWSER_PANEL:
|
||||
Children:
|
||||
Image@FLAG:
|
||||
X: 4
|
||||
Y: 4
|
||||
Y: 6
|
||||
Width: 32
|
||||
Height: 16
|
||||
Label@LABEL:
|
||||
|
||||
@@ -14,6 +14,7 @@ Metrics:
|
||||
TextfieldFont: Regular
|
||||
TextfieldColor: 255,255,255
|
||||
TextfieldColorDisabled: 128,128,128
|
||||
TextfieldColorInvalid: 255,192,192
|
||||
TextFont: Regular
|
||||
TextColor: 255,255,255
|
||||
TextContrast: false
|
||||
|
||||
@@ -14,6 +14,7 @@ Metrics:
|
||||
TextfieldFont: Regular
|
||||
TextfieldColor: 255,255,255
|
||||
TextfieldColorDisabled: 128,128,128
|
||||
TextfieldColorInvalid: 255,192,192
|
||||
TextFont: Regular
|
||||
TextColor: 255,255,255
|
||||
TextContrast: false
|
||||
|
||||
Reference in New Issue
Block a user