Merge pull request #4792 from pchote/replays

Fix chat synchronization in replays.
This commit is contained in:
Matthias Mailänder
2014-03-08 06:36:32 +01:00
3 changed files with 82 additions and 57 deletions

View File

@@ -21,6 +21,7 @@ NEW:
Added a toggle spectators to multiplayer.
Removed the ability of commandos to plant C4 on walls.
Order lines are now shown on unit selection.
Fixed chat synchronization in replays.
Dune 2000:
Added the Atreides grenadier from the 1.06 patch.
Added randomized tiles for Sand and Rock terrain.

View File

@@ -12,7 +12,6 @@ using System;
namespace OpenRA.Network
{
/* HACK: a maze of twisty little hacks... */
public class Replay
{
public readonly string Filename;
@@ -22,34 +21,12 @@ namespace OpenRA.Network
public Replay(string filename)
{
Filename = filename;
var lastFrame = 0;
var hasSeenGameStart = false;
var lobbyInfo = null as Session;
using (var conn = new ReplayConnection(filename))
conn.Receive((client, packet) =>
{
var frame = BitConverter.ToInt32(packet, 0);
if (packet.Length == 5 && packet[4] == 0xBF)
return; // disconnect
else if (packet.Length >= 5 && packet[4] == 0x65)
return; // sync
else if (frame == 0)
{
/* decode this to recover lobbyinfo, etc */
var orders = packet.ToOrderList(null);
foreach (var o in orders)
if (o.OrderString == "StartGame")
hasSeenGameStart = true;
else if (o.OrderString == "SyncInfo" && !hasSeenGameStart)
lobbyInfo = Session.Deserialize(o.TargetString);
}
else
lastFrame = Math.Max(lastFrame, frame);
});
Duration = lastFrame * Game.NetTickScale;
LobbyInfo = lobbyInfo;
{
Duration = conn.TickCount * Game.NetTickScale;
LobbyInfo = conn.LobbyInfo;
}
}
public Map Map()

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2011 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2014 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation. For more information,
@@ -11,52 +11,99 @@
using System;
using System.Collections.Generic;
using System.IO;
using OpenRA.FileFormats;
namespace OpenRA.Network
{
public class ReplayConnection : IConnection
{
FileStream replayStream;
List<byte[]> sync = new List<byte[]>();
class Chunk
{
public int Frame;
public List<Pair<int, byte[]>> Packets = new List<Pair<int, byte[]>>();
}
public ReplayConnection( string replayFilename ) { replayStream = File.OpenRead( replayFilename ); }
Queue<Chunk> chunks = new Queue<Chunk>();
List<byte[]> sync = new List<byte[]>();
int ordersFrame = 1;
public int LocalClientId { get { return 0; } }
public ConnectionState ConnectionState { get { return ConnectionState.Connected; } }
public readonly int TickCount;
public readonly bool IsValid;
public readonly Session LobbyInfo;
// do nothing; ignore locally generated orders
public void Send( int frame, List<byte[]> orders ) { }
public void SendImmediate( List<byte[]> orders ) { }
public void SendSync( int frame, byte[] syncData )
public ReplayConnection(string replayFilename)
{
var ms = new MemoryStream();
ms.Write( BitConverter.GetBytes( frame ) );
ms.Write( syncData );
sync.Add( ms.ToArray() );
// 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.
using (var rs = File.OpenRead(replayFilename))
{
var chunk = new Chunk();
while (rs.Position < rs.Length)
{
var client = rs.ReadInt32();
var packetLen = rs.ReadInt32();
var packet = rs.ReadBytes(packetLen);
var frame = BitConverter.ToInt32(packet, 0);
chunk.Packets.Add(Pair.New(client, packet));
if (packet.Length == 5 && packet[4] == 0xBF)
continue; // disconnect
else if (packet.Length >= 5 && packet[4] == 0x65)
continue; // sync
else if (frame == 0)
{
// Parse replay metadata from orders stream
var orders = packet.ToOrderList(null);
foreach (var o in orders)
{
if (o.OrderString == "StartGame")
IsValid = true;
else if (o.OrderString == "SyncInfo" && !IsValid)
LobbyInfo = Session.Deserialize(o.TargetString);
}
}
else
{
// Regular order - finalize the chunk
chunk.Frame = frame;
chunks.Enqueue(chunk);
chunk = new Chunk();
TickCount = Math.Max(TickCount, frame);
}
}
}
}
public void Receive( Action<int, byte[]> packetFn )
// Do nothing: ignore locally generated orders
public void Send(int frame, List<byte[]> orders) { }
public void SendImmediate(List<byte[]> orders) { }
public void SendSync(int frame, byte[] syncData)
{
while( sync.Count != 0 )
var ms = new MemoryStream();
ms.Write(BitConverter.GetBytes(frame));
ms.Write(syncData);
sync.Add(ms.ToArray());
// Store the current frame so Receive() can return the next chunk of orders.
ordersFrame = frame + 1;
}
public void Receive(Action<int, byte[]> packetFn)
{
while (sync.Count != 0)
{
packetFn( LocalClientId, sync[ 0 ] );
sync.RemoveAt( 0 );
packetFn(LocalClientId, sync[0]);
sync.RemoveAt(0);
}
if( replayStream == null ) return;
var reader = new BinaryReader( replayStream );
while( replayStream.Position < replayStream.Length )
{
var client = reader.ReadInt32();
var packetLen = reader.ReadInt32();
var packet = reader.ReadBytes( packetLen );
packetFn( client, packet );
}
replayStream = null;
while (chunks.Count != 0 && chunks.Peek().Frame <= ordersFrame)
foreach (var o in chunks.Dequeue().Packets)
packetFn(o.First, o.Second);
}
public void Dispose() { }