#region Copyright & License Information
/*
* Copyright 2007,2009,2010 Chris Forbes, Robert Pepperell, Matthew Bowra-Dean, Paul Chote, Alli Witheford.
* This file is part of OpenRA.
*
* OpenRA is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* OpenRA is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenRA. If not, see .
*/
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using OpenRA.FileFormats;
namespace OpenRA.Network
{
class OrderManager
{
public int FrameNumber { get; private set; }
public int FramesAhead = 0;
public bool GameStarted { get { return FrameNumber != 0; } }
public IConnection Connection { get; private set; }
public readonly int SyncHeaderSize = 9;
Dictionary clientQuitTimes = new Dictionary();
Dictionary> frameClientData =
new Dictionary>();
List localOrders = new List();
FileStream replaySaveFile;
public void StartGame()
{
if (GameStarted) return;
FrameNumber = 1;
for( int i = FrameNumber ; i <= FramesAhead ; i++ )
Connection.Send( new List().Serialize( i ) );
}
public OrderManager( IConnection conn )
{
Connection = conn;
}
public OrderManager( IConnection conn, string replayFilename )
: this( conn )
{
replaySaveFile = File.Create( replayFilename );
}
public void IssueOrders( Order[] orders )
{
foreach( var order in orders )
IssueOrder( order );
}
public void IssueOrder( Order order )
{
localOrders.Add( order );
}
public void TickImmediate( World world )
{
var immediateOrders = localOrders.Where( o => o.IsImmediate ).ToList();
if( immediateOrders.Count != 0 )
Connection.Send( immediateOrders.Serialize( 0 ) );
localOrders.RemoveAll( o => o.IsImmediate );
var immediatePackets = new List>();
Connection.Receive(
( clientId, packet ) =>
{
var frame = BitConverter.ToInt32( packet, 0 );
if( packet.Length == 5 && packet[ 4 ] == 0xBF )
clientQuitTimes[ clientId ] = frame;
else if( packet.Length >= 5 && packet[ 4 ] == 0x65 )
CheckSync( packet );
else if( frame == 0 )
immediatePackets.Add( Pair.New( clientId, packet ) );
else
frameClientData.GetOrAdd( frame ).Add( clientId, packet );
} );
foreach( var p in immediatePackets )
{
foreach( var o in p.Second.ToOrderList( world ) )
UnitOrders.ProcessOrder( world, p.First, o );
WriteImmediateToReplay( immediatePackets );
}
}
Dictionary syncForFrame = new Dictionary();
void CheckSync( byte[] packet )
{
var frame = BitConverter.ToInt32( packet, 0 );
byte[] existingSync;
if( syncForFrame.TryGetValue( frame, out existingSync ) )
{
if( packet.Length != existingSync.Length )
OutOfSync( frame );
else
{
for( int i = 0 ; i < packet.Length ; i++ )
{
if( packet[ i ] != existingSync[ i ] )
{
if ( i < SyncHeaderSize )
OutOfSync(frame, "Tick");
else
OutOfSync( frame , (i - SyncHeaderSize) / 4);
}
}
}
}
else
syncForFrame.Add( frame, packet );
}
void OutOfSync( int frame , int index)
{
var frameData = clientQuitTimes
.Where( x => frame <= x.Value )
.OrderBy( x => x.Key )
.ToDictionary( k => k.Key, v => frameClientData[ FrameNumber ][ v.Key ] );
var order = frameData.SelectMany( o => o.Value.ToOrderList( Game.world ).Select( a => new { Client = o.Key, Order = a } ) ).ElementAt(index);
throw new InvalidOperationException("Out of sync in frame {0}.\n {1}".F(frame, order.Order.ToString()));
}
void OutOfSync(int frame)
{
throw new InvalidOperationException("Out of sync in frame {0}.\n".F(frame));
}
void OutOfSync(int frame, string blame)
{
throw new InvalidOperationException("Out of sync in frame {0}: Blame {1}.\n".F(frame, blame));
}
public bool IsReadyForNextFrame
{
get
{
return FrameNumber > 0 &&
clientQuitTimes
.Where( x => FrameNumber <= x.Value )
.All( x => frameClientData.GetOrAdd( FrameNumber ).ContainsKey( x.Key ) );
}
}
public void Tick( World world )
{
if( !IsReadyForNextFrame )
throw new InvalidOperationException();
Connection.Send( localOrders.Serialize( FrameNumber + FramesAhead ) );
localOrders.Clear();
var frameData = clientQuitTimes
.Where( x => FrameNumber <= x.Value )
.OrderBy( x => x.Key )
.ToDictionary( k => k.Key, v => frameClientData[ FrameNumber ][ v.Key ] );
var sync = new List();
sync.Add( world.SyncHash() );
foreach( var order in frameData.SelectMany( o => o.Value.ToOrderList( world ).Select( a => new { Client = o.Key, Order = a } ) ) )
{
UnitOrders.ProcessOrder( world, order.Client, order.Order );
sync.Add( world.SyncHash() );
}
var ss = sync.SerializeSync( FrameNumber );
Connection.Send( ss );
WriteToReplay( frameData, ss );
CheckSync( ss );
++FrameNumber;
}
void WriteToReplay( Dictionary frameData, byte[] syncData )
{
if( replaySaveFile == null ) return;
foreach( var f in frameData )
{
replaySaveFile.Write( BitConverter.GetBytes( f.Key ) );
replaySaveFile.Write( BitConverter.GetBytes( f.Value.Length ) );
replaySaveFile.Write( f.Value );
}
replaySaveFile.Write( BitConverter.GetBytes( (int)0 ) );
replaySaveFile.Write( BitConverter.GetBytes( (int)syncData.Length ) );
replaySaveFile.Write( syncData );
}
void WriteImmediateToReplay( List> immediatePackets )
{
if( replaySaveFile == null ) return;
foreach( var i in immediatePackets )
{
replaySaveFile.Write( BitConverter.GetBytes( i.First ) );
replaySaveFile.Write( BitConverter.GetBytes( i.Second.Length ) );
replaySaveFile.Write( i.Second );
}
}
}
}