Improve replay metadata and the replay browser
List of changes: * Better and more filters with new layout, for both mods. * Rename/Delete/Detele all functionality. * Simplified ReplayMetadata class considerably by introducing a new GameInformation data object. The new GameInformation class contains more information than previously available so the new solution is not compatible with old replays, meaning it can't read old replays. * Better and cleaner game information gathering in order to be written at the end of the replay file. * Revert changes to ReplayConnection, no longer necessary. * Better exception message on missing sprites and fonts. * New "SpawnOccupant" class that holds all the information needed by the MapPreviewWidget to visualize a spawn point. It was using Session.Client before and it was necessary to separate it to be able to show information not available at lobby time. * Fix keyboard focus UI bug when closing a window would not remove focus.
This commit is contained in:
@@ -22,67 +22,33 @@ namespace OpenRA.FileFormats
|
|||||||
public const int MetaEndMarker = -2;
|
public const int MetaEndMarker = -2;
|
||||||
public const int MetaVersion = 0x00000001;
|
public const int MetaVersion = 0x00000001;
|
||||||
|
|
||||||
|
public readonly GameInformation GameInfo;
|
||||||
public string FilePath { get; private set; }
|
public string FilePath { get; private set; }
|
||||||
public DateTime EndTimestampUtc { get; private set; }
|
|
||||||
public TimeSpan Duration { get { return EndTimestampUtc - StartTimestampUtc; } }
|
|
||||||
public WinState Outcome { get; private set; }
|
|
||||||
|
|
||||||
public readonly Lazy<Session> LobbyInfo;
|
public ReplayMetadata(GameInformation info)
|
||||||
public readonly DateTime StartTimestampUtc;
|
|
||||||
readonly string lobbyInfoData;
|
|
||||||
|
|
||||||
ReplayMetadata()
|
|
||||||
{
|
{
|
||||||
Outcome = WinState.Undefined;
|
if (info == null)
|
||||||
|
throw new ArgumentNullException("info");
|
||||||
|
|
||||||
|
GameInfo = info;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReplayMetadata(DateTime startGameTimestampUtc, Session lobbyInfo)
|
ReplayMetadata(BinaryReader reader, string path)
|
||||||
: this()
|
|
||||||
{
|
{
|
||||||
if (startGameTimestampUtc.Kind == DateTimeKind.Unspecified)
|
FilePath = path;
|
||||||
throw new ArgumentException("The 'Kind' property of the timestamp must be specified", "startGameTimestamp");
|
|
||||||
|
|
||||||
StartTimestampUtc = startGameTimestampUtc.ToUniversalTime();
|
|
||||||
|
|
||||||
lobbyInfoData = lobbyInfo.Serialize();
|
|
||||||
LobbyInfo = Exts.Lazy(() => Session.Deserialize(this.lobbyInfoData));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FinalizeReplayMetadata(DateTime endGameTimestampUtc, WinState outcome)
|
|
||||||
{
|
|
||||||
if (endGameTimestampUtc.Kind == DateTimeKind.Unspecified)
|
|
||||||
throw new ArgumentException("The 'Kind' property of the timestamp must be specified", "endGameTimestampUtc");
|
|
||||||
EndTimestampUtc = endGameTimestampUtc.ToUniversalTime();
|
|
||||||
|
|
||||||
Outcome = outcome;
|
|
||||||
}
|
|
||||||
|
|
||||||
ReplayMetadata(BinaryReader reader)
|
|
||||||
: this()
|
|
||||||
{
|
|
||||||
// Read start marker
|
// Read start marker
|
||||||
if (reader.ReadInt32() != MetaStartMarker)
|
if (reader.ReadInt32() != MetaStartMarker)
|
||||||
throw new InvalidOperationException("Expected MetaStartMarker but found an invalid value.");
|
throw new InvalidOperationException("Expected MetaStartMarker but found an invalid value.");
|
||||||
|
|
||||||
// Read version
|
// Read version
|
||||||
var version = reader.ReadInt32();
|
var version = reader.ReadInt32();
|
||||||
if (version > MetaVersion)
|
if (version != MetaVersion)
|
||||||
throw new NotSupportedException("Metadata version {0} is not supported".F(version));
|
throw new NotSupportedException("Metadata version {0} is not supported".F(version));
|
||||||
|
|
||||||
// Read start game timestamp
|
// Read game info
|
||||||
StartTimestampUtc = new DateTime(reader.ReadInt64(), DateTimeKind.Utc);
|
string data = ReadUtf8String(reader);
|
||||||
|
GameInfo = GameInformation.Deserialize(data);
|
||||||
// Read end game timestamp
|
|
||||||
EndTimestampUtc = new DateTime(reader.ReadInt64(), DateTimeKind.Utc);
|
|
||||||
|
|
||||||
// Read game outcome
|
|
||||||
WinState outcome;
|
|
||||||
if (Enum.TryParse(ReadUtf8String(reader), true, out outcome))
|
|
||||||
Outcome = outcome;
|
|
||||||
|
|
||||||
// Read lobby info
|
|
||||||
lobbyInfoData = ReadUtf8String(reader);
|
|
||||||
LobbyInfo = Exts.Lazy(() => Session.Deserialize(this.lobbyInfoData));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Write(BinaryWriter writer)
|
public void Write(BinaryWriter writer)
|
||||||
@@ -94,19 +60,8 @@ namespace OpenRA.FileFormats
|
|||||||
// Write data
|
// Write data
|
||||||
int dataLength = 0;
|
int dataLength = 0;
|
||||||
{
|
{
|
||||||
// Write start game timestamp
|
|
||||||
writer.Write(StartTimestampUtc.Ticks);
|
|
||||||
dataLength += sizeof(long);
|
|
||||||
|
|
||||||
// Write end game timestamp
|
|
||||||
writer.Write(EndTimestampUtc.Ticks);
|
|
||||||
dataLength += sizeof(long);
|
|
||||||
|
|
||||||
// Write game outcome
|
|
||||||
dataLength += WriteUtf8String(writer, Outcome.ToString());
|
|
||||||
|
|
||||||
// Write lobby info data
|
// Write lobby info data
|
||||||
dataLength += WriteUtf8String(writer, lobbyInfoData);
|
dataLength += WriteUtf8String(writer, GameInfo.Serialize());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write total length & end marker
|
// Write total length & end marker
|
||||||
@@ -114,43 +69,27 @@ namespace OpenRA.FileFormats
|
|||||||
writer.Write(MetaEndMarker);
|
writer.Write(MetaEndMarker);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ReplayMetadata Read(string path, bool enableFallbackMethod = true)
|
public void RenameFile(string newFilenameWithoutExtension)
|
||||||
{
|
{
|
||||||
Func<DateTime> timestampProvider = () =>
|
var newPath = Path.Combine(Path.GetDirectoryName(FilePath), newFilenameWithoutExtension) + ".rep";
|
||||||
{
|
File.Move(FilePath, newPath);
|
||||||
try
|
FilePath = newPath;
|
||||||
{
|
|
||||||
return File.GetCreationTimeUtc(path);
|
|
||||||
}
|
}
|
||||||
catch
|
|
||||||
{
|
|
||||||
return DateTime.MinValue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
public static ReplayMetadata Read(string path)
|
||||||
|
{
|
||||||
using (var fs = new FileStream(path, FileMode.Open))
|
using (var fs = new FileStream(path, FileMode.Open))
|
||||||
{
|
return Read(fs, path);
|
||||||
var o = Read(fs, enableFallbackMethod, timestampProvider);
|
|
||||||
if (o != null)
|
|
||||||
o.FilePath = path;
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static ReplayMetadata Read(FileStream fs, bool enableFallbackMethod, Func<DateTime> fallbackTimestampProvider)
|
static ReplayMetadata Read(FileStream fs, string path)
|
||||||
{
|
{
|
||||||
|
if (!fs.CanSeek)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
fs.Seek(-(4 + 4), SeekOrigin.End);
|
||||||
using (var reader = new BinaryReader(fs))
|
using (var reader = new BinaryReader(fs))
|
||||||
{
|
{
|
||||||
// Disposing the BinaryReader will dispose the underlying stream
|
|
||||||
// and we don't want that because ReplayConnection may use the
|
|
||||||
// stream as well.
|
|
||||||
//
|
|
||||||
// Fixed in .NET 4.5.
|
|
||||||
// See: http://msdn.microsoft.com/en-us/library/gg712804%28v=vs.110%29.aspx
|
|
||||||
|
|
||||||
if (fs.CanSeek)
|
|
||||||
{
|
|
||||||
fs.Seek(-(4 + 4), SeekOrigin.End);
|
|
||||||
var dataLength = reader.ReadInt32();
|
var dataLength = reader.ReadInt32();
|
||||||
if (reader.ReadInt32() == MetaEndMarker)
|
if (reader.ReadInt32() == MetaEndMarker)
|
||||||
{
|
{
|
||||||
@@ -158,7 +97,7 @@ namespace OpenRA.FileFormats
|
|||||||
fs.Seek(-(4 + 4 + dataLength + 4 + 4), SeekOrigin.Current);
|
fs.Seek(-(4 + 4 + dataLength + 4 + 4), SeekOrigin.Current);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return new ReplayMetadata(reader);
|
return new ReplayMetadata(reader, path);
|
||||||
}
|
}
|
||||||
catch (InvalidOperationException ex)
|
catch (InvalidOperationException ex)
|
||||||
{
|
{
|
||||||
@@ -169,23 +108,6 @@ namespace OpenRA.FileFormats
|
|||||||
Log.Write("debug", ex.ToString());
|
Log.Write("debug", ex.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset the stream position or the ReplayConnection will fail later
|
|
||||||
fs.Seek(0, SeekOrigin.Begin);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enableFallbackMethod)
|
|
||||||
{
|
|
||||||
using (var conn = new ReplayConnection(fs))
|
|
||||||
{
|
|
||||||
var replay = new ReplayMetadata(fallbackTimestampProvider(), conn.LobbyInfo);
|
|
||||||
if (conn.TickCount == 0)
|
|
||||||
return null;
|
|
||||||
var seconds = (int)Math.Ceiling((conn.TickCount * Game.NetTickScale) / 25f);
|
|
||||||
replay.EndTimestampUtc = replay.StartTimestampUtc.AddSeconds(seconds);
|
|
||||||
return replay;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -210,10 +132,5 @@ namespace OpenRA.FileFormats
|
|||||||
{
|
{
|
||||||
return Encoding.UTF8.GetString(reader.ReadBytes(reader.ReadInt32()));
|
return Encoding.UTF8.GetString(reader.ReadBytes(reader.ReadInt32()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public MapPreview MapPreview
|
|
||||||
{
|
|
||||||
get { return Game.modData.MapCache[LobbyInfo.Value.GlobalSettings.Map]; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -263,10 +263,6 @@ namespace OpenRA
|
|||||||
using (new PerfTimer("LoadComplete"))
|
using (new PerfTimer("LoadComplete"))
|
||||||
orderManager.world.LoadComplete(worldRenderer);
|
orderManager.world.LoadComplete(worldRenderer);
|
||||||
|
|
||||||
var rc = orderManager.Connection as ReplayRecorderConnection;
|
|
||||||
if (rc != null)
|
|
||||||
rc.Metadata = new OpenRA.FileFormats.ReplayMetadata(DateTime.UtcNow, orderManager.LobbyInfo);
|
|
||||||
|
|
||||||
if (orderManager.GameStarted)
|
if (orderManager.GameStarted)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|||||||
230
OpenRA.Game/GameInformation.cs
Normal file
230
OpenRA.Game/GameInformation.cs
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
#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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Contains information about a finished game
|
||||||
|
/// </summary>
|
||||||
|
public class GameInformation
|
||||||
|
{
|
||||||
|
/// <summary>The map identifier.</summary>
|
||||||
|
public string MapUid;
|
||||||
|
/// <summary>The map title.</summary>
|
||||||
|
public string MapTitle;
|
||||||
|
/// <summary>Game start timestamp.</summary>
|
||||||
|
public DateTime StartTimeUtc;
|
||||||
|
/// <summary>Game end timestamp (when the recoding stopped).</summary>
|
||||||
|
public DateTime EndTimeUtc;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the game's duration, from the time the game started until the
|
||||||
|
/// replay recording stopped.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The game's duration.</value>
|
||||||
|
public TimeSpan Duration { get { return EndTimeUtc > StartTimeUtc ? EndTimeUtc - StartTimeUtc : TimeSpan.Zero; } }
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the list of players.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The players.</value>
|
||||||
|
public IList<Player> Players { get; private set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the map preview, using <see cref="Game.modData.MapCache"/> and the <see cref="MapUid"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The map preview.</value>
|
||||||
|
public MapPreview MapPreview { get { return Game.modData.MapCache[MapUid]; } }
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the human players.
|
||||||
|
/// </summary>
|
||||||
|
/// <value>The human players.</value>
|
||||||
|
public IEnumerable<Player> HumanPlayers { get { return Players.Where(p => p.IsHuman); } }
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value indicating whether this instance has just one human player.
|
||||||
|
/// </summary>
|
||||||
|
/// <value><c>true</c> if this instance has just one human player; otherwise, <c>false</c>.</value>
|
||||||
|
public bool IsSinglePlayer { get { return HumanPlayers.Count() == 1; } }
|
||||||
|
|
||||||
|
Dictionary<OpenRA.Player, Player> playersByRuntime;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the class.
|
||||||
|
/// </summary>
|
||||||
|
public GameInformation()
|
||||||
|
{
|
||||||
|
Players = new List<Player>();
|
||||||
|
playersByRuntime = new Dictionary<OpenRA.Player, Player>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deserialize the specified data into a new instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">Data.</param>
|
||||||
|
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("exception", "GameInformation deserialized invalid MiniYaml:\n{0}".F(data));
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Serialize this instance.
|
||||||
|
/// </summary>
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the start-up player information.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="runtimePlayer">Runtime player.</param>
|
||||||
|
/// <param name="lobbyInfo">Lobby info.</param>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the player information for the specified runtime player instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The player, or <c>null</c>.</returns>
|
||||||
|
/// <param name="runtimePlayer">Runtime player.</param>
|
||||||
|
public Player GetPlayer(OpenRA.Player runtimePlayer)
|
||||||
|
{
|
||||||
|
Player player;
|
||||||
|
|
||||||
|
playersByRuntime.TryGetValue(runtimePlayer, out player);
|
||||||
|
|
||||||
|
return player;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Specifies whether the player was defeated, victorious, or there was no outcome defined.</summary>
|
||||||
|
public enum GameOutcome
|
||||||
|
{
|
||||||
|
/// <summary>Unknown outcome.</summary>
|
||||||
|
Undefined,
|
||||||
|
/// <summary>The player was defeated</summary>
|
||||||
|
Defeat,
|
||||||
|
/// <summary>The player was victorious</summary>
|
||||||
|
Victory
|
||||||
|
}
|
||||||
|
|
||||||
|
///<summary>
|
||||||
|
/// Information about a player
|
||||||
|
/// </summary>
|
||||||
|
public class Player
|
||||||
|
{
|
||||||
|
//
|
||||||
|
// Start-up information
|
||||||
|
//
|
||||||
|
|
||||||
|
/// <summary>The client index.</summary>
|
||||||
|
public int ClientIndex;
|
||||||
|
/// <summary>The player name, not guaranteed to be unique.</summary>
|
||||||
|
public string Name;
|
||||||
|
/// <summary><c>true</c> if the player is a human player; otherwise, <c>false</c>.</summary>
|
||||||
|
public bool IsHuman;
|
||||||
|
/// <summary><c>true</c> if the player is a bot; otherwise, <c>false</c>.</summary>
|
||||||
|
public bool IsBot;
|
||||||
|
/// <summary>The faction name (aka Country).</summary>
|
||||||
|
public string FactionName;
|
||||||
|
/// <summary>The faction id (aka Country, aka Race).</summary>
|
||||||
|
public string FactionId;
|
||||||
|
/// <summary>The color used by the player in the game.</summary>
|
||||||
|
public HSLColor Color;
|
||||||
|
/// <summary>The team id on start-up, or 0 if the player is not part of the team.</summary>
|
||||||
|
public int Team;
|
||||||
|
/// <summary>The index of the spawn point on the map, or 0 if the player is not part of the team.</summary>
|
||||||
|
public int SpawnPoint;
|
||||||
|
/// <summary><c>true</c> if the faction was chosen at random; otherwise, <c>false</c>.</summary>
|
||||||
|
public bool IsRandomFaction;
|
||||||
|
/// <summary><c>true</c> if the spawn point was chosen at random; otherwise, <c>false</c>.</summary>
|
||||||
|
public bool IsRandomSpawnPoint;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Information gathered at a later stage
|
||||||
|
//
|
||||||
|
|
||||||
|
/// <summary>The game outcome for this player.</summary>
|
||||||
|
public GameOutcome Outcome;
|
||||||
|
/// <summary>The time when this player won or lost the game.</summary>
|
||||||
|
public DateTime OutcomeTimestampUtc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -72,14 +72,23 @@ namespace OpenRA.Graphics
|
|||||||
collections.Add(name, collection);
|
collections.Add(name, collection);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Sprite GetImage(string collection, string image)
|
public static Sprite GetImage(string collectionName, string imageName)
|
||||||
{
|
{
|
||||||
// Cached sprite
|
// Cached sprite
|
||||||
if (cachedSprites.ContainsKey(collection) && cachedSprites[collection].ContainsKey(image))
|
Dictionary<string, Sprite> cachedCollection;
|
||||||
return cachedSprites[collection][image];
|
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;
|
MappedImage mi;
|
||||||
if (!collections[collection].regions.TryGetValue(image, out mi))
|
if (!collection.regions.TryGetValue(imageName, out mi))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
// Cached sheet
|
// Cached sheet
|
||||||
@@ -93,11 +102,15 @@ namespace OpenRA.Graphics
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cache the sprite
|
// Cache the sprite
|
||||||
if (!cachedSprites.ContainsKey(collection))
|
if (cachedCollection == null)
|
||||||
cachedSprites.Add(collection, new Dictionary<string, Sprite>());
|
{
|
||||||
cachedSprites[collection].Add(image, mi.GetImage(sheet));
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using OpenRA.FileFormats;
|
||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
|
|
||||||
namespace OpenRA.Network
|
namespace OpenRA.Network
|
||||||
@@ -34,28 +35,17 @@ namespace OpenRA.Network
|
|||||||
public readonly Session LobbyInfo;
|
public readonly Session LobbyInfo;
|
||||||
|
|
||||||
public ReplayConnection(string replayFilename)
|
public ReplayConnection(string replayFilename)
|
||||||
{
|
|
||||||
using (var rs = File.OpenRead(replayFilename))
|
|
||||||
Read(rs, ref TickCount, ref IsValid, ref LobbyInfo);
|
|
||||||
|
|
||||||
ordersFrame = LobbyInfo.GlobalSettings.OrderLatency;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReplayConnection(FileStream rs)
|
|
||||||
{
|
|
||||||
Read(rs, ref TickCount, ref IsValid, ref LobbyInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Read(FileStream rs, ref int TickCount, ref bool IsValid, ref Session LobbyInfo)
|
|
||||||
{
|
{
|
||||||
// Parse replay data into a struct that can be fed to the game in chunks
|
// Parse replay data into a struct that can be fed to the game in chunks
|
||||||
// to avoid issues with all immediate orders being resolved on the first tick.
|
// to avoid issues with all immediate orders being resolved on the first tick.
|
||||||
|
using (var rs = File.OpenRead(replayFilename))
|
||||||
|
{
|
||||||
var chunk = new Chunk();
|
var chunk = new Chunk();
|
||||||
|
|
||||||
while (rs.Position < rs.Length)
|
while (rs.Position < rs.Length)
|
||||||
{
|
{
|
||||||
var client = rs.ReadInt32();
|
var client = rs.ReadInt32();
|
||||||
if (client == FileFormats.ReplayMetadata.MetaStartMarker)
|
if (client == ReplayMetadata.MetaStartMarker)
|
||||||
break;
|
break;
|
||||||
var packetLen = rs.ReadInt32();
|
var packetLen = rs.ReadInt32();
|
||||||
var packet = rs.ReadBytes(packetLen);
|
var packet = rs.ReadBytes(packetLen);
|
||||||
@@ -90,6 +80,9 @@ namespace OpenRA.Network
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ordersFrame = LobbyInfo.GlobalSettings.OrderLatency;
|
||||||
|
}
|
||||||
|
|
||||||
// Do nothing: ignore locally generated orders
|
// Do nothing: ignore locally generated orders
|
||||||
public void Send(int frame, List<byte[]> orders) { }
|
public void Send(int frame, List<byte[]> orders) { }
|
||||||
public void SendImmediate(List<byte[]> orders) { }
|
public void SendImmediate(List<byte[]> orders) { }
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ namespace OpenRA.Network
|
|||||||
class ReplayRecorderConnection : IConnection
|
class ReplayRecorderConnection : IConnection
|
||||||
{
|
{
|
||||||
public ReplayMetadata Metadata;
|
public ReplayMetadata Metadata;
|
||||||
public WinState LocalGameState = WinState.Undefined;
|
|
||||||
|
|
||||||
IConnection inner;
|
IConnection inner;
|
||||||
BinaryWriter writer;
|
BinaryWriter writer;
|
||||||
@@ -107,7 +106,8 @@ namespace OpenRA.Network
|
|||||||
|
|
||||||
if (Metadata != null)
|
if (Metadata != null)
|
||||||
{
|
{
|
||||||
Metadata.FinalizeReplayMetadata(DateTime.UtcNow, LocalGameState);
|
if (Metadata.GameInfo != null)
|
||||||
|
Metadata.GameInfo.EndTimeUtc = DateTime.UtcNow;
|
||||||
Metadata.Write(writer);
|
Metadata.Write(writer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -248,6 +248,7 @@
|
|||||||
<Compile Include="GameRules\Ruleset.cs" />
|
<Compile Include="GameRules\Ruleset.cs" />
|
||||||
<Compile Include="GameRules\RulesetCache.cs" />
|
<Compile Include="GameRules\RulesetCache.cs" />
|
||||||
<Compile Include="Support\MersenneTwister.cs" />
|
<Compile Include="Support\MersenneTwister.cs" />
|
||||||
|
<Compile Include="GameInformation.cs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="FileSystem\D2kSoundResources.cs" />
|
<Compile Include="FileSystem\D2kSoundResources.cs" />
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ namespace OpenRA
|
|||||||
public readonly int ClientIndex;
|
public readonly int ClientIndex;
|
||||||
public readonly PlayerReference PlayerReference;
|
public readonly PlayerReference PlayerReference;
|
||||||
public bool IsBot;
|
public bool IsBot;
|
||||||
|
public int SpawnPoint;
|
||||||
|
|
||||||
public Shroud Shroud;
|
public Shroud Shroud;
|
||||||
public World World { get; private set; }
|
public World World { get; private set; }
|
||||||
|
|||||||
@@ -41,9 +41,12 @@ namespace OpenRA.Widgets
|
|||||||
{
|
{
|
||||||
var name = GetImageName();
|
var name = GetImageName();
|
||||||
var collection = GetImageCollection();
|
var collection = GetImageCollection();
|
||||||
WidgetUtils.DrawRGBA(
|
|
||||||
ChromeProvider.GetImage(collection, name),
|
var sprite = ChromeProvider.GetImage(collection, name);
|
||||||
RenderOrigin);
|
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()
|
public override void Draw()
|
||||||
{
|
{
|
||||||
SpriteFont font = Game.Renderer.Fonts[Font];
|
SpriteFont font;
|
||||||
|
if (!Game.Renderer.Fonts.TryGetValue(Font, out font))
|
||||||
|
throw new ArgumentException("Request font '{0}' was not found.".F(Font));
|
||||||
|
|
||||||
var text = GetText();
|
var text = GetText();
|
||||||
if (text == null)
|
if (text == null)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -19,10 +19,42 @@ using OpenRA.Network;
|
|||||||
|
|
||||||
namespace OpenRA.Widgets
|
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 class MapPreviewWidget : Widget
|
||||||
{
|
{
|
||||||
public Func<MapPreview> Preview = () => null;
|
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 Action<MouseInput> OnMouseDown = _ => {};
|
||||||
public bool IgnoreMouseInput = false;
|
public bool IgnoreMouseInput = false;
|
||||||
public bool ShowSpawnPoints = true;
|
public bool ShowSpawnPoints = true;
|
||||||
@@ -44,7 +76,7 @@ namespace OpenRA.Widgets
|
|||||||
: base(other)
|
: base(other)
|
||||||
{
|
{
|
||||||
Preview = other.Preview;
|
Preview = other.Preview;
|
||||||
SpawnClients = other.SpawnClients;
|
SpawnOccupants = other.SpawnOccupants;
|
||||||
ShowSpawnPoints = other.ShowSpawnPoints;
|
ShowSpawnPoints = other.ShowSpawnPoints;
|
||||||
TooltipTemplate = other.TooltipTemplate;
|
TooltipTemplate = other.TooltipTemplate;
|
||||||
TooltipContainer = other.TooltipContainer;
|
TooltipContainer = other.TooltipContainer;
|
||||||
@@ -109,7 +141,7 @@ namespace OpenRA.Widgets
|
|||||||
TooltipSpawnIndex = -1;
|
TooltipSpawnIndex = -1;
|
||||||
if (ShowSpawnPoints)
|
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;
|
var spawnPoints = preview.SpawnPoints;
|
||||||
foreach (var p in spawnPoints)
|
foreach (var p in spawnPoints)
|
||||||
|
|||||||
@@ -258,7 +258,7 @@ namespace OpenRA.Widgets
|
|||||||
return true;
|
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)
|
public virtual bool YieldMouseFocus(MouseInput mi)
|
||||||
{
|
{
|
||||||
if (Ui.MouseFocusWidget == this)
|
if (Ui.MouseFocusWidget == this)
|
||||||
@@ -267,6 +267,12 @@ namespace OpenRA.Widgets
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ForceYieldMouseFocus()
|
||||||
|
{
|
||||||
|
if (Ui.MouseFocusWidget == this && !YieldMouseFocus(default(MouseInput)))
|
||||||
|
Ui.MouseFocusWidget = null;
|
||||||
|
}
|
||||||
|
|
||||||
public virtual bool TakeKeyboardFocus()
|
public virtual bool TakeKeyboardFocus()
|
||||||
{
|
{
|
||||||
if (HasKeyboardFocus)
|
if (HasKeyboardFocus)
|
||||||
@@ -287,6 +293,12 @@ namespace OpenRA.Widgets
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ForceYieldKeyboardFocus()
|
||||||
|
{
|
||||||
|
if (Ui.KeyboardFocusWidget == this && !YieldKeyboardFocus())
|
||||||
|
Ui.KeyboardFocusWidget = null;
|
||||||
|
}
|
||||||
|
|
||||||
public virtual string GetCursor(int2 pos) { return "default"; }
|
public virtual string GetCursor(int2 pos) { return "default"; }
|
||||||
public string GetCursorOuter(int2 pos)
|
public string GetCursorOuter(int2 pos)
|
||||||
{
|
{
|
||||||
@@ -410,6 +422,11 @@ namespace OpenRA.Widgets
|
|||||||
|
|
||||||
public virtual void Removed()
|
public virtual void Removed()
|
||||||
{
|
{
|
||||||
|
// Using the forced versions because the widgets
|
||||||
|
// have been removed
|
||||||
|
ForceYieldKeyboardFocus();
|
||||||
|
ForceYieldMouseFocus();
|
||||||
|
|
||||||
foreach (var c in Children.OfType<Widget>().Reverse())
|
foreach (var c in Children.OfType<Widget>().Reverse())
|
||||||
c.Removed();
|
c.Removed();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,6 +85,7 @@ namespace OpenRA
|
|||||||
public readonly TileSet TileSet;
|
public readonly TileSet TileSet;
|
||||||
public readonly ActorMap ActorMap;
|
public readonly ActorMap ActorMap;
|
||||||
public readonly ScreenMap ScreenMap;
|
public readonly ScreenMap ScreenMap;
|
||||||
|
readonly GameInformation gameInfo;
|
||||||
|
|
||||||
public void IssueOrder(Order o) { orderManager.IssueOrder(o); } /* avoid exposing the OM to mod code */
|
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;
|
p.Stances[q] = Stance.Neutral;
|
||||||
|
|
||||||
Sound.SoundVolumeModifier = 1.0f;
|
Sound.SoundVolumeModifier = 1.0f;
|
||||||
|
|
||||||
|
gameInfo = new GameInformation
|
||||||
|
{
|
||||||
|
MapUid = Map.Uid,
|
||||||
|
MapTitle = Map.Title
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadComplete(WorldRenderer wr)
|
public void LoadComplete(WorldRenderer wr)
|
||||||
@@ -151,6 +158,14 @@ namespace OpenRA
|
|||||||
using (new Support.PerfTimer(wlh.GetType().Name + ".WorldLoaded"))
|
using (new Support.PerfTimer(wlh.GetType().Name + ".WorldLoaded"))
|
||||||
wlh.WorldLoaded(this, wr);
|
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)
|
public Actor CreateActor(string name, TypeDictionary initDict)
|
||||||
@@ -288,11 +303,16 @@ namespace OpenRA
|
|||||||
return traitDict.ActorsWithTraitMultiple<T>(this);
|
return traitDict.ActorsWithTraitMultiple<T>(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnLocalPlayerWinStateChanged()
|
public void OnPlayerWinStateChanged(Player player)
|
||||||
{
|
{
|
||||||
var rc = orderManager.Connection as ReplayRecorderConnection;
|
var pi = gameInfo.GetPlayer(player);
|
||||||
if (rc != null)
|
if (pi != null)
|
||||||
rc.LocalGameState = LocalPlayer.WinState;
|
{
|
||||||
|
pi.Outcome = player.WinState == WinState.Lost ? GameInformation.GameOutcome.Defeat
|
||||||
|
: player.WinState == WinState.Won ? GameInformation.GameOutcome.Victory
|
||||||
|
: GameInformation.GameOutcome.Undefined;
|
||||||
|
pi.OutcomeTimestampUtc = DateTime.UtcNow;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ namespace OpenRA.Mods.RA
|
|||||||
{
|
{
|
||||||
if (self.Owner.WinState == WinState.Lost) return;
|
if (self.Owner.WinState == WinState.Lost) return;
|
||||||
self.Owner.WinState = WinState.Lost;
|
self.Owner.WinState = WinState.Lost;
|
||||||
|
self.World.OnPlayerWinStateChanged(self.Owner);
|
||||||
|
|
||||||
Game.Debug("{0} is defeated.".F(self.Owner.PlayerName));
|
Game.Debug("{0} is defeated.".F(self.Owner.PlayerName));
|
||||||
|
|
||||||
@@ -68,8 +69,6 @@ namespace OpenRA.Mods.RA
|
|||||||
|
|
||||||
if (self.Owner == self.World.LocalPlayer)
|
if (self.Owner == self.World.LocalPlayer)
|
||||||
{
|
{
|
||||||
self.World.OnLocalPlayerWinStateChanged();
|
|
||||||
|
|
||||||
Game.RunAfterDelay(Info.NotificationDelay, () =>
|
Game.RunAfterDelay(Info.NotificationDelay, () =>
|
||||||
{
|
{
|
||||||
if (Game.IsCurrentWorld(self.World))
|
if (Game.IsCurrentWorld(self.World))
|
||||||
@@ -82,16 +81,14 @@ namespace OpenRA.Mods.RA
|
|||||||
{
|
{
|
||||||
if (self.Owner.WinState == WinState.Won) return;
|
if (self.Owner.WinState == WinState.Won) return;
|
||||||
self.Owner.WinState = WinState.Won;
|
self.Owner.WinState = WinState.Won;
|
||||||
|
self.World.OnPlayerWinStateChanged(self.Owner);
|
||||||
|
|
||||||
Game.Debug("{0} is victorious.".F(self.Owner.PlayerName));
|
Game.Debug("{0} is victorious.".F(self.Owner.PlayerName));
|
||||||
if (self.Owner == self.World.LocalPlayer)
|
|
||||||
{
|
|
||||||
self.World.OnLocalPlayerWinStateChanged();
|
|
||||||
|
|
||||||
|
if (self.Owner == self.World.LocalPlayer)
|
||||||
Game.RunAfterDelay(Info.NotificationDelay, () => Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", "Win", self.Owner.Country.Race));
|
Game.RunAfterDelay(Info.NotificationDelay, () => Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", "Win", self.Owner.Country.Race));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
[Desc("Tag trait for things that must be destroyed for a short game to end.")]
|
[Desc("Tag trait for things that must be destroyed for a short game to end.")]
|
||||||
public class MustBeDestroyedInfo : TraitInfo<MustBeDestroyed> { }
|
public class MustBeDestroyedInfo : TraitInfo<MustBeDestroyed> { }
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ namespace OpenRA.Mods.RA
|
|||||||
|
|
||||||
public void WorldLoaded(World world, WorldRenderer wr)
|
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)
|
var taken = world.LobbyInfo.Clients.Where(c => c.SpawnPoint != 0 && c.Slot != null)
|
||||||
.Select(c => spawns[c.SpawnPoint-1]).ToList();
|
.Select(c => spawns[c.SpawnPoint-1]).ToList();
|
||||||
var available = spawns.Except(taken).ToList();
|
var available = spawns.Except(taken).ToList();
|
||||||
@@ -42,9 +42,13 @@ namespace OpenRA.Mods.RA
|
|||||||
var client = world.LobbyInfo.ClientInSlot(kv.Key);
|
var client = world.LobbyInfo.ClientInSlot(kv.Key);
|
||||||
var spid = (client == null || client.SpawnPoint == 0)
|
var spid = (client == null || client.SpawnPoint == 0)
|
||||||
? ChooseSpawnPoint(world, available, taken)
|
? ChooseSpawnPoint(world, available, taken)
|
||||||
: world.Map.GetSpawnPoints()[client.SpawnPoint-1];
|
: spawns[client.SpawnPoint-1];
|
||||||
|
|
||||||
Start.Add(player, spid);
|
Start.Add(player, spid);
|
||||||
|
|
||||||
|
player.SpawnPoint = (client == null || client.SpawnPoint == 0)
|
||||||
|
? spawns.IndexOf(spid) + 1
|
||||||
|
: client.SpawnPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Explore allied shroud
|
// Explore allied shroud
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
|||||||
var preview = available.Get<MapPreviewWidget>("MAP_PREVIEW");
|
var preview = available.Get<MapPreviewWidget>("MAP_PREVIEW");
|
||||||
preview.Preview = () => lobby.Map;
|
preview.Preview = () => lobby.Map;
|
||||||
preview.OnMouseDown = mi => LobbyUtils.SelectSpawnPoint(orderManager, preview, lobby.Map, mi);
|
preview.OnMouseDown = mi => LobbyUtils.SelectSpawnPoint(orderManager, preview, lobby.Map, mi);
|
||||||
preview.SpawnClients = () => LobbyUtils.GetSpawnClients(orderManager.LobbyInfo, lobby.Map);
|
preview.SpawnOccupants = () => LobbyUtils.GetSpawnClients(orderManager.LobbyInfo, lobby.Map);
|
||||||
|
|
||||||
var title = available.GetOrNull<LabelWidget>("MAP_TITLE");
|
var title = available.GetOrNull<LabelWidget>("MAP_TITLE");
|
||||||
if (title != null)
|
if (title != null)
|
||||||
@@ -73,7 +73,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
|||||||
var preview = download.Get<MapPreviewWidget>("MAP_PREVIEW");
|
var preview = download.Get<MapPreviewWidget>("MAP_PREVIEW");
|
||||||
preview.Preview = () => lobby.Map;
|
preview.Preview = () => lobby.Map;
|
||||||
preview.OnMouseDown = mi => LobbyUtils.SelectSpawnPoint(orderManager, preview, lobby.Map, mi);
|
preview.OnMouseDown = mi => LobbyUtils.SelectSpawnPoint(orderManager, preview, lobby.Map, mi);
|
||||||
preview.SpawnClients = () => LobbyUtils.GetSpawnClients(orderManager.LobbyInfo, lobby.Map);
|
preview.SpawnOccupants = () => LobbyUtils.GetSpawnClients(orderManager.LobbyInfo, lobby.Map);
|
||||||
|
|
||||||
var title = download.GetOrNull<LabelWidget>("MAP_TITLE");
|
var title = download.GetOrNull<LabelWidget>("MAP_TITLE");
|
||||||
if (title != null)
|
if (title != null)
|
||||||
@@ -100,7 +100,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
|||||||
var preview = progress.Get<MapPreviewWidget>("MAP_PREVIEW");
|
var preview = progress.Get<MapPreviewWidget>("MAP_PREVIEW");
|
||||||
preview.Preview = () => lobby.Map;
|
preview.Preview = () => lobby.Map;
|
||||||
preview.OnMouseDown = mi => LobbyUtils.SelectSpawnPoint(orderManager, preview, lobby.Map, mi);
|
preview.OnMouseDown = mi => LobbyUtils.SelectSpawnPoint(orderManager, preview, lobby.Map, mi);
|
||||||
preview.SpawnClients = () => LobbyUtils.GetSpawnClients(orderManager.LobbyInfo, lobby.Map);
|
preview.SpawnOccupants = () => LobbyUtils.GetSpawnClients(orderManager.LobbyInfo, lobby.Map);
|
||||||
|
|
||||||
var title = progress.GetOrNull<LabelWidget>("MAP_TITLE");
|
var title = progress.GetOrNull<LabelWidget>("MAP_TITLE");
|
||||||
if (title != null)
|
if (title != null)
|
||||||
|
|||||||
@@ -149,12 +149,19 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
|||||||
color.AttachPanel(colorChooser, onExit);
|
color.AttachPanel(colorChooser, onExit);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Dictionary<CPos, Session.Client> GetSpawnClients(Session lobbyInfo, MapPreview preview)
|
public static Dictionary<CPos, SpawnOccupant> GetSpawnClients(Session lobbyInfo, MapPreview preview)
|
||||||
{
|
{
|
||||||
var spawns = preview.SpawnPoints;
|
var spawns = preview.SpawnPoints;
|
||||||
return lobbyInfo.Clients
|
return lobbyInfo.Clients
|
||||||
.Where(c => c.SpawnPoint != 0)
|
.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> GetSpawnClients(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)
|
public static void SelectSpawnPoint(OrderManager orderManager, MapPreviewWidget mapPreview, MapPreview preview, MouseInput mi)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#region Copyright & License Information
|
#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
|
* 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
|
* available to you under the terms of the GNU General Public License
|
||||||
* as published by the Free Software Foundation. For more information,
|
* as published by the Free Software Foundation. For more information,
|
||||||
@@ -25,12 +25,12 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
|||||||
static Filter filter = new Filter();
|
static Filter filter = new Filter();
|
||||||
|
|
||||||
Widget panel;
|
Widget panel;
|
||||||
ScrollPanelWidget playerList;
|
ScrollPanelWidget replayList, playerList;
|
||||||
ScrollItemWidget playerTemplate, playerHeader;
|
ScrollItemWidget playerTemplate, playerHeader;
|
||||||
List<ReplayMetadata> replays;
|
List<ReplayMetadata> replays;
|
||||||
Dictionary<ReplayMetadata, bool> replayVis = new Dictionary<ReplayMetadata, bool>();
|
Dictionary<ReplayMetadata, ReplayState> replayState = new Dictionary<ReplayMetadata, ReplayState>();
|
||||||
|
|
||||||
Dictionary<CPos, Session.Client> selectedSpawns;
|
Dictionary<CPos, SpawnOccupant> selectedSpawns;
|
||||||
ReplayMetadata selectedReplay;
|
ReplayMetadata selectedReplay;
|
||||||
|
|
||||||
[ObjectCreator.UseCtor]
|
[ObjectCreator.UseCtor]
|
||||||
@@ -45,13 +45,13 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
|||||||
|
|
||||||
panel.Get<ButtonWidget>("CANCEL_BUTTON").OnClick = () => { Ui.CloseWindow(); onExit(); };
|
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 template = panel.Get<ScrollItemWidget>("REPLAY_TEMPLATE");
|
||||||
|
|
||||||
var mod = Game.modData.Manifest.Mod;
|
var mod = Game.modData.Manifest.Mod;
|
||||||
var dir = new[] { Platform.SupportDir, "Replays", mod.Id, mod.Version }.Aggregate(Path.Combine);
|
var dir = new[] { Platform.SupportDir, "Replays", mod.Id, mod.Version }.Aggregate(Path.Combine);
|
||||||
|
|
||||||
rl.RemoveChildren();
|
replayList.RemoveChildren();
|
||||||
if (Directory.Exists(dir))
|
if (Directory.Exists(dir))
|
||||||
{
|
{
|
||||||
using (new Support.PerfTimer("Load replays"))
|
using (new Support.PerfTimer("Load replays"))
|
||||||
@@ -60,37 +60,38 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
|||||||
.GetFiles(dir, "*.rep")
|
.GetFiles(dir, "*.rep")
|
||||||
.Select((filename) => ReplayMetadata.Read(filename))
|
.Select((filename) => ReplayMetadata.Read(filename))
|
||||||
.Where((r) => r != null)
|
.Where((r) => r != null)
|
||||||
.OrderByDescending(r => r.StartTimestampUtc)
|
.OrderByDescending(r => r.GameInfo.StartTimeUtc)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var replay in replays)
|
foreach (var replay in replays)
|
||||||
AddReplay(rl, replay, template);
|
AddReplay(replay, template);
|
||||||
|
|
||||||
ApplyFilter();
|
ApplyFilter();
|
||||||
}
|
}
|
||||||
|
|
||||||
var watch = panel.Get<ButtonWidget>("WATCH_BUTTON");
|
var watch = panel.Get<ButtonWidget>("WATCH_BUTTON");
|
||||||
watch.IsDisabled = () => selectedReplay == null || selectedReplay.MapPreview.Status != MapStatus.Available;
|
watch.IsDisabled = () => selectedReplay == null || selectedReplay.GameInfo.MapPreview.Status != MapStatus.Available;
|
||||||
watch.OnClick = () => { WatchReplay(); onStart(); };
|
watch.OnClick = () => { WatchReplay(); onStart(); };
|
||||||
|
|
||||||
panel.Get("REPLAY_INFO").IsVisible = () => selectedReplay != null;
|
panel.Get("REPLAY_INFO").IsVisible = () => selectedReplay != null;
|
||||||
|
|
||||||
var preview = panel.Get<MapPreviewWidget>("MAP_PREVIEW");
|
var preview = panel.Get<MapPreviewWidget>("MAP_PREVIEW");
|
||||||
preview.SpawnClients = () => selectedSpawns;
|
preview.SpawnOccupants = () => selectedSpawns;
|
||||||
preview.Preview = () => selectedReplay != null ? selectedReplay.MapPreview : null;
|
preview.Preview = () => selectedReplay != null ? selectedReplay.GameInfo.MapPreview : null;
|
||||||
|
|
||||||
var title = panel.GetOrNull<LabelWidget>("MAP_TITLE");
|
var title = panel.GetOrNull<LabelWidget>("MAP_TITLE");
|
||||||
if (title != null)
|
if (title != null)
|
||||||
title.GetText = () => selectedReplay != null ? selectedReplay.MapPreview.Title : null;
|
title.GetText = () => selectedReplay != null ? selectedReplay.GameInfo.MapPreview.Title : null;
|
||||||
|
|
||||||
var type = panel.GetOrNull<LabelWidget>("MAP_TYPE");
|
var type = panel.GetOrNull<LabelWidget>("MAP_TYPE");
|
||||||
if (type != null)
|
if (type != null)
|
||||||
type.GetText = () => selectedReplay.MapPreview.Type;
|
type.GetText = () => selectedReplay.GameInfo.MapPreview.Type;
|
||||||
|
|
||||||
panel.Get<LabelWidget>("DURATION").GetText = () => WidgetUtils.FormatTimeSeconds((int)selectedReplay.Duration.TotalSeconds);
|
panel.Get<LabelWidget>("DURATION").GetText = () => WidgetUtils.FormatTimeSeconds((int)selectedReplay.GameInfo.Duration.TotalSeconds);
|
||||||
|
|
||||||
SetupFilters();
|
SetupFilters();
|
||||||
|
SetupManagement();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetupFilters()
|
void SetupFilters()
|
||||||
@@ -204,25 +205,89 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
|||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Outcome
|
// 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");
|
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_OUTCOME_DROPDOWNBUTTON");
|
||||||
if (ddb != null)
|
if (ddb != null)
|
||||||
{
|
{
|
||||||
|
ddb.IsDisabled = () => string.IsNullOrEmpty(filter.PlayerName);
|
||||||
|
|
||||||
// Using list to maintain the order
|
// Using list to maintain the order
|
||||||
var options = new List<KeyValuePair<WinState, string>>
|
var options = new List<KeyValuePair<GameInformation.GameOutcome, string>>
|
||||||
{
|
{
|
||||||
new KeyValuePair<WinState, string>(WinState.Undefined, ddb.GetText()),
|
new KeyValuePair<GameInformation.GameOutcome, string>(GameInformation.GameOutcome.Undefined, ddb.GetText()),
|
||||||
new KeyValuePair<WinState, string>(WinState.Won, "Won"),
|
new KeyValuePair<GameInformation.GameOutcome, string>(GameInformation.GameOutcome.Defeat, "Defeat"),
|
||||||
new KeyValuePair<WinState, string>(WinState.Lost, "Lost")
|
new KeyValuePair<GameInformation.GameOutcome, string>(GameInformation.GameOutcome.Victory, "Victory")
|
||||||
};
|
};
|
||||||
var lookup = options.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
var lookup = options.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
|
||||||
|
|
||||||
ddb.GetText = () => lookup[filter.Outcome];
|
ddb.GetText = () => lookup[filter.Outcome];
|
||||||
ddb.OnMouseDown = _ =>
|
ddb.OnMouseDown = _ =>
|
||||||
{
|
{
|
||||||
Func<KeyValuePair<WinState, string>, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) =>
|
Func<KeyValuePair<GameInformation.GameOutcome, string>, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) =>
|
||||||
{
|
{
|
||||||
var item = ScrollItemWidget.Setup(
|
var item = ScrollItemWidget.Setup(
|
||||||
tpl,
|
tpl,
|
||||||
@@ -239,28 +304,30 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
|||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Players
|
// Faction (depends on Player)
|
||||||
//
|
//
|
||||||
{
|
{
|
||||||
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_PLAYER_DROPDOWNBUTTON");
|
var ddb = panel.GetOrNull<DropDownButtonWidget>("FLT_FACTION_DROPDOWNBUTTON");
|
||||||
if (ddb != null)
|
if (ddb != null)
|
||||||
{
|
{
|
||||||
var options = new HashSet<string>(replays.SelectMany(r => r.LobbyInfo.Value.Clients.Select(c => c.Name)), StringComparer.OrdinalIgnoreCase).ToList();
|
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.Sort(StringComparer.OrdinalIgnoreCase);
|
||||||
options.Insert(0, null); // no filter
|
options.Insert(0, null); // no filter
|
||||||
|
|
||||||
var nobodyText = ddb.GetText();
|
var anyText = ddb.GetText();
|
||||||
ddb.GetText = () => string.IsNullOrEmpty(filter.PlayerName) ? nobodyText : filter.PlayerName;
|
ddb.GetText = () => string.IsNullOrEmpty(filter.Faction) ? anyText : filter.Faction;
|
||||||
ddb.OnMouseDown = _ =>
|
ddb.OnMouseDown = _ =>
|
||||||
{
|
{
|
||||||
Func<string, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) =>
|
Func<string, ScrollItemWidget, ScrollItemWidget> setupItem = (option, tpl) =>
|
||||||
{
|
{
|
||||||
var item = ScrollItemWidget.Setup(
|
var item = ScrollItemWidget.Setup(
|
||||||
tpl,
|
tpl,
|
||||||
() => string.Compare(filter.PlayerName, option, true) == 0,
|
() => string.Compare(filter.Faction, option, true) == 0,
|
||||||
() => { filter.PlayerName = option; ApplyFilter(); }
|
() => { filter.Faction = option; ApplyFilter(); }
|
||||||
);
|
);
|
||||||
item.Get<LabelWidget>("LABEL").GetText = () => option ?? nobodyText;
|
item.Get<LabelWidget>("LABEL").GetText = () => option ?? anyText;
|
||||||
return item;
|
return item;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -268,12 +335,152 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// 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)
|
bool EvaluateReplayVisibility(ReplayMetadata replay)
|
||||||
{
|
{
|
||||||
// Game type
|
// Game type
|
||||||
if ((filter.Type == GameType.Multiplayer && replay.LobbyInfo.Value.IsSinglePlayer) || (filter.Type == GameType.Singleplayer && !replay.LobbyInfo.Value.IsSinglePlayer))
|
if ((filter.Type == GameType.Multiplayer && replay.GameInfo.IsSinglePlayer) || (filter.Type == GameType.Singleplayer && !replay.GameInfo.IsSinglePlayer))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Date type
|
// Date type
|
||||||
@@ -295,14 +502,14 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
|||||||
t = TimeSpan.FromDays(30d);
|
t = TimeSpan.FromDays(30d);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (replay.StartTimestampUtc < DateTime.UtcNow.Subtract(t))
|
if (replay.GameInfo.StartTimeUtc < DateTime.UtcNow - t)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Duration
|
// Duration
|
||||||
if (filter.Duration != DurationType.Any)
|
if (filter.Duration != DurationType.Any)
|
||||||
{
|
{
|
||||||
double minutes = replay.Duration.TotalMinutes;
|
double minutes = replay.GameInfo.Duration.TotalMinutes;
|
||||||
switch (filter.Duration)
|
switch (filter.Duration)
|
||||||
{
|
{
|
||||||
case DurationType.VeryShort:
|
case DurationType.VeryShort:
|
||||||
@@ -327,16 +534,24 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Outcome
|
// Map
|
||||||
if (filter.Outcome != WinState.Undefined && filter.Outcome != replay.Outcome)
|
if (!string.IsNullOrEmpty(filter.MapName) && string.Compare(filter.MapName, replay.GameInfo.MapTitle, true) != 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// Player
|
// Player
|
||||||
if (!string.IsNullOrEmpty(filter.PlayerName))
|
if (!string.IsNullOrEmpty(filter.PlayerName))
|
||||||
{
|
{
|
||||||
var player = replay.LobbyInfo.Value.Clients.Find(c => string.Compare(filter.PlayerName, c.Name, true) == 0);
|
var player = replay.GameInfo.Players.FirstOrDefault(p => string.Compare(filter.PlayerName, p.Name, true) == 0);
|
||||||
if (player == null)
|
if (player == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// Outcome
|
||||||
|
if (filter.Outcome != GameInformation.GameOutcome.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;
|
return true;
|
||||||
@@ -345,41 +560,41 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
|||||||
void ApplyFilter()
|
void ApplyFilter()
|
||||||
{
|
{
|
||||||
foreach (var replay in replays)
|
foreach (var replay in replays)
|
||||||
replayVis[replay] = EvaluateReplayVisibility(replay);
|
replayState[replay].Visible = EvaluateReplayVisibility(replay);
|
||||||
|
|
||||||
if (selectedReplay == null || replayVis[selectedReplay] == false)
|
if (selectedReplay == null || replayState[selectedReplay].Visible == false)
|
||||||
SelectFirstVisibleReplay();
|
SelectFirstVisibleReplay();
|
||||||
|
|
||||||
panel.Get<ScrollPanelWidget>("REPLAY_LIST").Layout.AdjustChildren();
|
replayList.Layout.AdjustChildren();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SelectFirstVisibleReplay()
|
void SelectFirstVisibleReplay()
|
||||||
{
|
{
|
||||||
SelectReplay(replays.FirstOrDefault(r => replayVis[r]));
|
SelectReplay(replays.FirstOrDefault(r => replayState[r].Visible));
|
||||||
}
|
}
|
||||||
|
|
||||||
void SelectReplay(ReplayMetadata replay)
|
void SelectReplay(ReplayMetadata replay)
|
||||||
{
|
{
|
||||||
selectedReplay = replay;
|
selectedReplay = replay;
|
||||||
selectedSpawns = (selectedReplay != null) ? LobbyUtils.GetSpawnClients(selectedReplay.LobbyInfo.Value, selectedReplay.MapPreview) : null;
|
selectedSpawns = (selectedReplay != null)
|
||||||
|
? LobbyUtils.GetSpawnClients(selectedReplay.GameInfo.Players, selectedReplay.GameInfo.MapPreview)
|
||||||
|
: new Dictionary<CPos, SpawnOccupant>();
|
||||||
|
|
||||||
if (replay == null)
|
if (replay == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var lobby = replay.LobbyInfo.Value;
|
var players = replay.GameInfo.Players
|
||||||
|
.GroupBy(p => p.Team)
|
||||||
var clients = lobby.Clients.Where(c => c.Slot != null)
|
|
||||||
.GroupBy(c => c.Team)
|
|
||||||
.OrderBy(g => g.Key);
|
.OrderBy(g => g.Key);
|
||||||
|
|
||||||
var teams = new Dictionary<string, IEnumerable<Session.Client>>();
|
var teams = new Dictionary<string, IEnumerable<GameInformation.Player>>();
|
||||||
var noTeams = clients.Count() == 1;
|
var noTeams = players.Count() == 1;
|
||||||
foreach (var c in clients)
|
foreach (var p in players)
|
||||||
{
|
{
|
||||||
var label = noTeams ? "Players" : c.Key == 0 ? "No Team" : "Team {0}".F(c.Key);
|
var label = noTeams ? "Players" : p.Key == 0 ? "No Team" : "Team {0}".F(p.Key);
|
||||||
teams.Add(label, c);
|
teams.Add(label, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
playerList.RemoveChildren();
|
playerList.RemoveChildren();
|
||||||
@@ -408,7 +623,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
|||||||
|
|
||||||
var flag = item.Get<ImageWidget>("FLAG");
|
var flag = item.Get<ImageWidget>("FLAG");
|
||||||
flag.GetImageCollection = () => "flags";
|
flag.GetImageCollection = () => "flags";
|
||||||
flag.GetImageName = () => o.Country;
|
flag.GetImageName = () => o.FactionId;
|
||||||
|
|
||||||
playerList.AddChild(item);
|
playerList.AddChild(item);
|
||||||
}
|
}
|
||||||
@@ -430,16 +645,29 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddReplay(ScrollPanelWidget list, ReplayMetadata replay, ScrollItemWidget template)
|
void AddReplay(ReplayMetadata replay, ScrollItemWidget template)
|
||||||
{
|
{
|
||||||
var item = ScrollItemWidget.Setup(template,
|
var item = ScrollItemWidget.Setup(template,
|
||||||
() => selectedReplay == replay,
|
() => selectedReplay == replay,
|
||||||
() => SelectReplay(replay),
|
() => SelectReplay(replay),
|
||||||
() => WatchReplay());
|
() => WatchReplay());
|
||||||
var f = Path.GetFileNameWithoutExtension(replay.FilePath);
|
|
||||||
item.Get<LabelWidget>("TITLE").GetText = () => f;
|
replayState[replay] = new ReplayState
|
||||||
item.IsVisible = () => { bool visible; return replayVis.TryGetValue(replay, out visible) && visible; };
|
{
|
||||||
list.AddChild(item);
|
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
|
class Filter
|
||||||
@@ -447,8 +675,24 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
|||||||
public GameType Type;
|
public GameType Type;
|
||||||
public DateType Date;
|
public DateType Date;
|
||||||
public DurationType Duration;
|
public DurationType Duration;
|
||||||
public WinState Outcome = WinState.Undefined;
|
public GameInformation.GameOutcome Outcome;
|
||||||
public string PlayerName;
|
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(GameInformation.GameOutcome)
|
||||||
|
&& string.IsNullOrEmpty(PlayerName)
|
||||||
|
&& string.IsNullOrEmpty(MapName)
|
||||||
|
&& string.IsNullOrEmpty(Faction);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
enum GameType
|
enum GameType
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -38,10 +38,10 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
|||||||
|
|
||||||
tooltipContainer.BeforeRender = () =>
|
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;
|
var teamWidth = 0;
|
||||||
if (client == null)
|
if (occupant == null)
|
||||||
{
|
{
|
||||||
labelText = "Available spawn";
|
labelText = "Available spawn";
|
||||||
playerCountry = null;
|
playerCountry = null;
|
||||||
@@ -50,9 +50,9 @@ namespace OpenRA.Mods.RA.Widgets.Logic
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
labelText = client.Name;
|
labelText = occupant.PlayerName;
|
||||||
playerCountry = client.Country;
|
playerCountry = occupant.Country;
|
||||||
playerTeam = client.Team;
|
playerTeam = occupant.Team;
|
||||||
widget.Bounds.Height = playerTeam > 0 ? doubleHeight : singleHeight;
|
widget.Bounds.Height = playerTeam > 0 ? doubleHeight : singleHeight;
|
||||||
teamWidth = teamFont.Measure(team.GetText()).X;
|
teamWidth = teamFont.Measure(team.GetText()).X;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +1,212 @@
|
|||||||
Container@REPLAYBROWSER_PANEL:
|
Container@REPLAYBROWSER_PANEL:
|
||||||
Logic: ReplayBrowserLogic
|
Logic: ReplayBrowserLogic
|
||||||
X: (WINDOW_RIGHT - WIDTH)/2
|
X: (WINDOW_RIGHT - WIDTH)/2
|
||||||
Y: (WINDOW_BOTTOM - 500)/2
|
Y: (WINDOW_BOTTOM - HEIGHT)/2
|
||||||
Width: 520
|
Width: 780
|
||||||
Height: 535
|
Height: 500
|
||||||
Children:
|
Children:
|
||||||
Label@TITLE:
|
Label@TITLE:
|
||||||
Width: 520
|
Width: PARENT_RIGHT
|
||||||
Y: 0-25
|
Y: 0-25
|
||||||
Font: BigBold
|
Font: BigBold
|
||||||
Contrast: true
|
Contrast: true
|
||||||
Align: Center
|
Align: Center
|
||||||
Text: Replay Viewer
|
Text: Replay Viewer
|
||||||
Background@bg:
|
Background@bg:
|
||||||
Width: 520
|
Width: PARENT_RIGHT
|
||||||
Height: 500
|
Height: PARENT_BOTTOM
|
||||||
Background: panel-black
|
Background: panel-black
|
||||||
Children:
|
Children:
|
||||||
|
Container@FILTER_AND_MANAGE_CONTAINER:
|
||||||
|
X: 20
|
||||||
|
Y: 20
|
||||||
|
Width: 280
|
||||||
|
Height: PARENT_BOTTOM - 40
|
||||||
|
Children:
|
||||||
|
Container@FILTERS:
|
||||||
|
Width: 280
|
||||||
|
Height: 320
|
||||||
|
Children:
|
||||||
|
Label@FILTERS_TITLE:
|
||||||
|
X: 85
|
||||||
|
Width: PARENT_RIGHT - 85
|
||||||
|
Height: 25
|
||||||
|
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:
|
||||||
|
Label@REPLAYBROWSER_LABEL_TITLE:
|
||||||
|
Width: PARENT_RIGHT
|
||||||
|
Height: 25
|
||||||
|
Text: Choose Replay
|
||||||
|
Align: Center
|
||||||
|
Font: Bold
|
||||||
ScrollPanel@REPLAY_LIST:
|
ScrollPanel@REPLAY_LIST:
|
||||||
X: 15
|
X: 0
|
||||||
Y: 15
|
Y: 30
|
||||||
Width: 282
|
Width: PARENT_RIGHT
|
||||||
Height: PARENT_BOTTOM - 30
|
Height: PARENT_BOTTOM - 30
|
||||||
|
CollapseHiddenChildren: True
|
||||||
Children:
|
Children:
|
||||||
ScrollItem@REPLAY_TEMPLATE:
|
ScrollItem@REPLAY_TEMPLATE:
|
||||||
Width: PARENT_RIGHT-27
|
Width: PARENT_RIGHT-27
|
||||||
Height: 25
|
Height: 25
|
||||||
X: 2
|
X: 2
|
||||||
Y: 0
|
|
||||||
Visible: false
|
Visible: false
|
||||||
Children:
|
Children:
|
||||||
Label@TITLE:
|
Label@TITLE:
|
||||||
X: 10
|
X: 10
|
||||||
Width: PARENT_RIGHT-20
|
Width: PARENT_RIGHT-20
|
||||||
Height: 25
|
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:
|
Background@MAP_BG:
|
||||||
X: PARENT_RIGHT-WIDTH-15
|
Y: 30
|
||||||
Y: 15
|
|
||||||
Width: 194
|
Width: 194
|
||||||
Height: 194
|
Height: 194
|
||||||
Background: panel-gray
|
Background: panel-gray
|
||||||
@@ -48,33 +218,33 @@ Container@REPLAYBROWSER_PANEL:
|
|||||||
Height: 192
|
Height: 192
|
||||||
TooltipContainer: TOOLTIP_CONTAINER
|
TooltipContainer: TOOLTIP_CONTAINER
|
||||||
Container@REPLAY_INFO:
|
Container@REPLAY_INFO:
|
||||||
X: PARENT_RIGHT-WIDTH-15
|
X: PARENT_RIGHT - WIDTH - 20
|
||||||
Y: 15
|
Y: 20 + 30+194 + 10
|
||||||
Width: 194
|
Width: 194
|
||||||
Height: PARENT_BOTTOM - 15
|
Height: PARENT_BOTTOM - 20 - 30-194 - 10 - 20
|
||||||
Children:
|
Children:
|
||||||
Label@MAP_TITLE:
|
Label@MAP_TITLE:
|
||||||
Y: 197
|
Y: 0
|
||||||
Width: PARENT_RIGHT
|
Width: PARENT_RIGHT
|
||||||
Height: 25
|
Height: 15
|
||||||
Font: Bold
|
Font: Bold
|
||||||
Align: Center
|
Align: Center
|
||||||
Label@MAP_TYPE:
|
Label@MAP_TYPE:
|
||||||
Y: 212
|
Y: 15
|
||||||
Width: PARENT_RIGHT
|
Width: PARENT_RIGHT
|
||||||
Height: 25
|
Height: 15
|
||||||
Font: TinyBold
|
Font: TinyBold
|
||||||
Align: Center
|
Align: Center
|
||||||
Label@DURATION:
|
Label@DURATION:
|
||||||
Y: 225
|
Y: 30
|
||||||
Width: PARENT_RIGHT
|
Width: PARENT_RIGHT
|
||||||
Height: 25
|
Height: 15
|
||||||
Font: Tiny
|
Font: Tiny
|
||||||
Align: Center
|
Align: Center
|
||||||
ScrollPanel@PLAYER_LIST:
|
ScrollPanel@PLAYER_LIST:
|
||||||
Y: 250
|
Y: 50
|
||||||
Width: PARENT_RIGHT
|
Width: PARENT_RIGHT
|
||||||
Height: PARENT_BOTTOM - 250 - 15
|
Height: PARENT_BOTTOM - 50
|
||||||
IgnoreChildMouseOver: true
|
IgnoreChildMouseOver: true
|
||||||
Children:
|
Children:
|
||||||
ScrollItem@HEADER:
|
ScrollItem@HEADER:
|
||||||
@@ -98,7 +268,7 @@ Container@REPLAYBROWSER_PANEL:
|
|||||||
Children:
|
Children:
|
||||||
Image@FLAG:
|
Image@FLAG:
|
||||||
X: 4
|
X: 4
|
||||||
Y: 4
|
Y: 6
|
||||||
Width: 32
|
Width: 32
|
||||||
Height: 16
|
Height: 16
|
||||||
Label@LABEL:
|
Label@LABEL:
|
||||||
@@ -112,14 +282,14 @@ Container@REPLAYBROWSER_PANEL:
|
|||||||
Button@CANCEL_BUTTON:
|
Button@CANCEL_BUTTON:
|
||||||
Key: escape
|
Key: escape
|
||||||
X: 0
|
X: 0
|
||||||
Y: 499
|
Y: PARENT_BOTTOM - 1
|
||||||
Width: 140
|
Width: 140
|
||||||
Height: 35
|
Height: 35
|
||||||
Text: Back
|
Text: Back
|
||||||
Button@WATCH_BUTTON:
|
Button@WATCH_BUTTON:
|
||||||
Key: return
|
Key: return
|
||||||
X: 380
|
X: PARENT_RIGHT - 140
|
||||||
Y: 499
|
Y: PARENT_BOTTOM - 1
|
||||||
Width: 140
|
Width: 140
|
||||||
Height: 35
|
Height: 35
|
||||||
Text: Watch
|
Text: Watch
|
||||||
|
|||||||
@@ -2,17 +2,22 @@ Background@REPLAYBROWSER_PANEL:
|
|||||||
Logic: ReplayBrowserLogic
|
Logic: ReplayBrowserLogic
|
||||||
X: (WINDOW_RIGHT - WIDTH)/2
|
X: (WINDOW_RIGHT - WIDTH)/2
|
||||||
Y: (WINDOW_BOTTOM - HEIGHT)/2
|
Y: (WINDOW_BOTTOM - HEIGHT)/2
|
||||||
Width: 490
|
Width: 780
|
||||||
Height: 535
|
Height: 535
|
||||||
Children:
|
Children:
|
||||||
Container@FILTERS:
|
Container@FILTER_AND_MANAGE_CONTAINER:
|
||||||
X: 20
|
X: 20
|
||||||
Y: 20
|
Y: 20
|
||||||
Width: 280
|
Width: 280
|
||||||
Height: 180
|
Height: PARENT_BOTTOM - 75
|
||||||
|
Children:
|
||||||
|
Container@FILTERS:
|
||||||
|
Width: 280
|
||||||
|
Height: 320
|
||||||
Children:
|
Children:
|
||||||
Label@FILTERS_TITLE:
|
Label@FILTERS_TITLE:
|
||||||
Width: PARENT_RIGHT
|
X: 85
|
||||||
|
Width: PARENT_RIGHT - 85
|
||||||
Height: 25
|
Height: 25
|
||||||
Font: Bold
|
Font: Bold
|
||||||
Align: Center
|
Align: Center
|
||||||
@@ -27,9 +32,8 @@ Background@REPLAYBROWSER_PANEL:
|
|||||||
DropDownButton@FLT_GAMETYPE_DROPDOWNBUTTON:
|
DropDownButton@FLT_GAMETYPE_DROPDOWNBUTTON:
|
||||||
X: 85
|
X: 85
|
||||||
Y: 30
|
Y: 30
|
||||||
Width: 160
|
Width: PARENT_RIGHT - 85
|
||||||
Height: 25
|
Height: 25
|
||||||
Font: Regular
|
|
||||||
Text: Any
|
Text: Any
|
||||||
Label@FLT_DATE_DESC:
|
Label@FLT_DATE_DESC:
|
||||||
X: 0
|
X: 0
|
||||||
@@ -41,57 +45,118 @@ Background@REPLAYBROWSER_PANEL:
|
|||||||
DropDownButton@FLT_DATE_DROPDOWNBUTTON:
|
DropDownButton@FLT_DATE_DROPDOWNBUTTON:
|
||||||
X: 85
|
X: 85
|
||||||
Y: 60
|
Y: 60
|
||||||
Width: 160
|
Width: PARENT_RIGHT - 85
|
||||||
Height: 25
|
Height: 25
|
||||||
Font: Regular
|
|
||||||
Text: Any
|
|
||||||
Label@FLT_PLAYER_DESC:
|
|
||||||
X: 0
|
|
||||||
Y: 90
|
|
||||||
Width: 80
|
|
||||||
Height: 25
|
|
||||||
Text: Player:
|
|
||||||
Align: Right
|
|
||||||
DropDownButton@FLT_PLAYER_DROPDOWNBUTTON:
|
|
||||||
X: 85
|
|
||||||
Y: 90
|
|
||||||
Width: 160
|
|
||||||
Height: 25
|
|
||||||
Font: Regular
|
|
||||||
Text: Anyone
|
|
||||||
Label@FLT_OUTCOME_DESC:
|
|
||||||
X: 0
|
|
||||||
Y: 120
|
|
||||||
Width: 80
|
|
||||||
Height: 25
|
|
||||||
Text: Outcome:
|
|
||||||
Align: Right
|
|
||||||
DropDownButton@FLT_OUTCOME_DROPDOWNBUTTON:
|
|
||||||
X: 85
|
|
||||||
Y: 120
|
|
||||||
Width: 160
|
|
||||||
Height: 25
|
|
||||||
Font: Regular
|
|
||||||
Text: Any
|
Text: Any
|
||||||
Label@FLT_DURATION_DESC:
|
Label@FLT_DURATION_DESC:
|
||||||
X: 0
|
X: 0
|
||||||
Y: 150
|
Y: 90
|
||||||
Width: 80
|
Width: 80
|
||||||
Height: 25
|
Height: 25
|
||||||
Text: Duration:
|
Text: Duration:
|
||||||
Align: Right
|
Align: Right
|
||||||
DropDownButton@FLT_DURATION_DROPDOWNBUTTON:
|
DropDownButton@FLT_DURATION_DROPDOWNBUTTON:
|
||||||
X: 85
|
X: 85
|
||||||
Y: 150
|
Y: 90
|
||||||
Width: 160
|
Width: PARENT_RIGHT - 85
|
||||||
Height: 25
|
Height: 25
|
||||||
Font: Regular
|
|
||||||
Text: Any
|
Text: Any
|
||||||
Container@REPLAY_LIST_CONTAINER:
|
Label@FLT_MAPNAME_DESC:
|
||||||
X: 20
|
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
|
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
|
Width: 245
|
||||||
Height: PARENT_BOTTOM - 270
|
Height: PARENT_BOTTOM - 20 - 55
|
||||||
Children:
|
Children:
|
||||||
Label@REPLAYBROWSER_LABEL_TITLE:
|
Label@REPLAYBROWSER_LABEL_TITLE:
|
||||||
Width: PARENT_RIGHT
|
Width: PARENT_RIGHT
|
||||||
@@ -103,7 +168,7 @@ Background@REPLAYBROWSER_PANEL:
|
|||||||
X: 0
|
X: 0
|
||||||
Y: 30
|
Y: 30
|
||||||
Width: PARENT_RIGHT
|
Width: PARENT_RIGHT
|
||||||
Height: PARENT_BOTTOM - 25
|
Height: PARENT_BOTTOM - 30
|
||||||
CollapseHiddenChildren: True
|
CollapseHiddenChildren: True
|
||||||
Children:
|
Children:
|
||||||
ScrollItem@REPLAY_TEMPLATE:
|
ScrollItem@REPLAY_TEMPLATE:
|
||||||
@@ -120,7 +185,7 @@ Background@REPLAYBROWSER_PANEL:
|
|||||||
X: PARENT_RIGHT - WIDTH - 20
|
X: PARENT_RIGHT - WIDTH - 20
|
||||||
Y: 20
|
Y: 20
|
||||||
Width: 194
|
Width: 194
|
||||||
Height: 194
|
Height: 30 + 194
|
||||||
Children:
|
Children:
|
||||||
Label@MAP_BG_TITLE:
|
Label@MAP_BG_TITLE:
|
||||||
Width: PARENT_RIGHT
|
Width: PARENT_RIGHT
|
||||||
@@ -142,32 +207,32 @@ Background@REPLAYBROWSER_PANEL:
|
|||||||
TooltipContainer: TOOLTIP_CONTAINER
|
TooltipContainer: TOOLTIP_CONTAINER
|
||||||
Container@REPLAY_INFO:
|
Container@REPLAY_INFO:
|
||||||
X: PARENT_RIGHT - WIDTH - 20
|
X: PARENT_RIGHT - WIDTH - 20
|
||||||
Y: 50
|
Y: 20 + 30+194 + 10
|
||||||
Width: 194
|
Width: 194
|
||||||
Height: PARENT_BOTTOM - 15
|
Height: PARENT_BOTTOM - 20 - 30-194 - 10 - 55
|
||||||
Children:
|
Children:
|
||||||
Label@MAP_TITLE:
|
Label@MAP_TITLE:
|
||||||
Y: 197
|
Y: 0
|
||||||
Width: PARENT_RIGHT
|
Width: PARENT_RIGHT
|
||||||
Height: 25
|
Height: 15
|
||||||
Font: Bold
|
Font: Bold
|
||||||
Align: Center
|
Align: Center
|
||||||
Label@MAP_TYPE:
|
Label@MAP_TYPE:
|
||||||
Y: 212
|
Y: 15
|
||||||
Width: PARENT_RIGHT
|
Width: PARENT_RIGHT
|
||||||
Height: 25
|
Height: 15
|
||||||
Font: TinyBold
|
Font: TinyBold
|
||||||
Align: Center
|
Align: Center
|
||||||
Label@DURATION:
|
Label@DURATION:
|
||||||
Y: 225
|
Y: 30
|
||||||
Width: PARENT_RIGHT
|
Width: PARENT_RIGHT
|
||||||
Height: 25
|
Height: 15
|
||||||
Font: Tiny
|
Font: Tiny
|
||||||
Align: Center
|
Align: Center
|
||||||
ScrollPanel@PLAYER_LIST:
|
ScrollPanel@PLAYER_LIST:
|
||||||
Y: 250
|
Y: 50
|
||||||
Width: PARENT_RIGHT
|
Width: PARENT_RIGHT
|
||||||
Height: PARENT_BOTTOM - 340
|
Height: PARENT_BOTTOM - 50
|
||||||
IgnoreChildMouseOver: true
|
IgnoreChildMouseOver: true
|
||||||
Children:
|
Children:
|
||||||
ScrollItem@HEADER:
|
ScrollItem@HEADER:
|
||||||
@@ -192,7 +257,7 @@ Background@REPLAYBROWSER_PANEL:
|
|||||||
Children:
|
Children:
|
||||||
Image@FLAG:
|
Image@FLAG:
|
||||||
X: 4
|
X: 4
|
||||||
Y: 4
|
Y: 6
|
||||||
Width: 32
|
Width: 32
|
||||||
Height: 16
|
Height: 16
|
||||||
Label@LABEL:
|
Label@LABEL:
|
||||||
|
|||||||
Reference in New Issue
Block a user