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 MetaVersion = 0x00000001;
|
||||
|
||||
public readonly GameInformation GameInfo;
|
||||
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 readonly DateTime StartTimestampUtc;
|
||||
readonly string lobbyInfoData;
|
||||
|
||||
ReplayMetadata()
|
||||
public ReplayMetadata(GameInformation info)
|
||||
{
|
||||
Outcome = WinState.Undefined;
|
||||
if (info == null)
|
||||
throw new ArgumentNullException("info");
|
||||
|
||||
GameInfo = info;
|
||||
}
|
||||
|
||||
public ReplayMetadata(DateTime startGameTimestampUtc, Session lobbyInfo)
|
||||
: this()
|
||||
ReplayMetadata(BinaryReader reader, string path)
|
||||
{
|
||||
if (startGameTimestampUtc.Kind == DateTimeKind.Unspecified)
|
||||
throw new ArgumentException("The 'Kind' property of the timestamp must be specified", "startGameTimestamp");
|
||||
FilePath = path;
|
||||
|
||||
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
|
||||
if (reader.ReadInt32() != MetaStartMarker)
|
||||
throw new InvalidOperationException("Expected MetaStartMarker but found an invalid value.");
|
||||
|
||||
// Read version
|
||||
var version = reader.ReadInt32();
|
||||
if (version > MetaVersion)
|
||||
if (version != MetaVersion)
|
||||
throw new NotSupportedException("Metadata version {0} is not supported".F(version));
|
||||
|
||||
// Read start game timestamp
|
||||
StartTimestampUtc = new DateTime(reader.ReadInt64(), DateTimeKind.Utc);
|
||||
|
||||
// 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));
|
||||
// Read game info
|
||||
string data = ReadUtf8String(reader);
|
||||
GameInfo = GameInformation.Deserialize(data);
|
||||
}
|
||||
|
||||
public void Write(BinaryWriter writer)
|
||||
@@ -94,19 +60,8 @@ namespace OpenRA.FileFormats
|
||||
// Write data
|
||||
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
|
||||
dataLength += WriteUtf8String(writer, lobbyInfoData);
|
||||
dataLength += WriteUtf8String(writer, GameInfo.Serialize());
|
||||
}
|
||||
|
||||
// Write total length & end marker
|
||||
@@ -114,76 +69,43 @@ namespace OpenRA.FileFormats
|
||||
writer.Write(MetaEndMarker);
|
||||
}
|
||||
|
||||
public static ReplayMetadata Read(string path, bool enableFallbackMethod = true)
|
||||
public void RenameFile(string newFilenameWithoutExtension)
|
||||
{
|
||||
Func<DateTime> timestampProvider = () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return File.GetCreationTimeUtc(path);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return DateTime.MinValue;
|
||||
}
|
||||
};
|
||||
|
||||
using (var fs = new FileStream(path, FileMode.Open))
|
||||
{
|
||||
var o = Read(fs, enableFallbackMethod, timestampProvider);
|
||||
if (o != null)
|
||||
o.FilePath = path;
|
||||
return o;
|
||||
}
|
||||
var newPath = Path.Combine(Path.GetDirectoryName(FilePath), newFilenameWithoutExtension) + ".rep";
|
||||
File.Move(FilePath, newPath);
|
||||
FilePath = newPath;
|
||||
}
|
||||
|
||||
static ReplayMetadata Read(FileStream fs, bool enableFallbackMethod, Func<DateTime> fallbackTimestampProvider)
|
||||
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;
|
||||
|
||||
fs.Seek(-(4 + 4), SeekOrigin.End);
|
||||
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)
|
||||
var dataLength = reader.ReadInt32();
|
||||
if (reader.ReadInt32() == MetaEndMarker)
|
||||
{
|
||||
fs.Seek(-(4 + 4), SeekOrigin.End);
|
||||
var dataLength = reader.ReadInt32();
|
||||
if (reader.ReadInt32() == MetaEndMarker)
|
||||
// go back end marker + length storage + data + version + start marker
|
||||
fs.Seek(-(4 + 4 + dataLength + 4 + 4), SeekOrigin.Current);
|
||||
try
|
||||
{
|
||||
// go back end marker + length storage + data + version + start marker
|
||||
fs.Seek(-(4 + 4 + dataLength + 4 + 4), SeekOrigin.Current);
|
||||
try
|
||||
{
|
||||
return new ReplayMetadata(reader);
|
||||
}
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
Log.Write("debug", ex.ToString());
|
||||
}
|
||||
catch (NotSupportedException ex)
|
||||
{
|
||||
Log.Write("debug", ex.ToString());
|
||||
}
|
||||
return new ReplayMetadata(reader, path);
|
||||
}
|
||||
|
||||
// Reset the stream position or the ReplayConnection will fail later
|
||||
fs.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
if (enableFallbackMethod)
|
||||
{
|
||||
using (var conn = new ReplayConnection(fs))
|
||||
catch (InvalidOperationException ex)
|
||||
{
|
||||
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;
|
||||
Log.Write("debug", ex.ToString());
|
||||
}
|
||||
catch (NotSupportedException ex)
|
||||
{
|
||||
Log.Write("debug", ex.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -210,10 +132,5 @@ namespace OpenRA.FileFormats
|
||||
{
|
||||
return Encoding.UTF8.GetString(reader.ReadBytes(reader.ReadInt32()));
|
||||
}
|
||||
|
||||
public MapPreview MapPreview
|
||||
{
|
||||
get { return Game.modData.MapCache[LobbyInfo.Value.GlobalSettings.Map]; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user