Initial commit: OpenRA game engine
Fork from OpenRA/OpenRA with one-click launch script (start-ra.cmd) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
136
OpenRA.Game/Network/ReplayConnection.cs
Normal file
136
OpenRA.Game/Network/ReplayConnection.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
#region Copyright & License Information
|
||||
/*
|
||||
* Copyright (c) The OpenRA Developers and Contributors
|
||||
* 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, either version 3 of
|
||||
* the License, or (at your option) any later version. For more
|
||||
* information, see COPYING.
|
||||
*/
|
||||
#endregion
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using OpenRA.FileFormats;
|
||||
|
||||
namespace OpenRA.Network
|
||||
{
|
||||
public sealed class ReplayConnection : IConnection
|
||||
{
|
||||
sealed class Chunk
|
||||
{
|
||||
public int Frame;
|
||||
public (int ClientId, byte[] Packet)[] Packets;
|
||||
}
|
||||
|
||||
readonly Queue<Chunk> chunks = [];
|
||||
readonly Queue<(int Frame, int SyncHash, ulong DefeatState)> sync = [];
|
||||
readonly int orderLatency;
|
||||
|
||||
public readonly int TickCount;
|
||||
public readonly int FinalGameTick;
|
||||
public readonly bool IsValid;
|
||||
public readonly Session LobbyInfo;
|
||||
public readonly string Filename;
|
||||
|
||||
public ReplayConnection(string replayFilename)
|
||||
{
|
||||
Filename = replayFilename;
|
||||
FinalGameTick = ReplayMetadata.Read(replayFilename).GameInfo.FinalGameTick;
|
||||
|
||||
// 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 packets = new List<(int ClientId, byte[] Packet)>();
|
||||
var chunk = new Chunk();
|
||||
while (rs.Position < rs.Length)
|
||||
{
|
||||
var client = rs.ReadInt32();
|
||||
if (client == ReplayMetadata.MetaStartMarker)
|
||||
break;
|
||||
|
||||
var packetLen = rs.ReadInt32();
|
||||
var packet = rs.ReadBytes(packetLen);
|
||||
var frame = BitConverter.ToInt32(packet, 0);
|
||||
packets.Add((client, packet));
|
||||
|
||||
if (packet.Length > 4 && (packet[4] == (byte)OrderType.Disconnect || packet[4] == (byte)OrderType.SyncHash))
|
||||
continue;
|
||||
|
||||
if (frame == 0)
|
||||
{
|
||||
// Parse replay metadata from orders stream
|
||||
if (OrderIO.TryParseOrderPacket(packet, out var orders))
|
||||
{
|
||||
foreach (var o in orders.Orders.GetOrders(null))
|
||||
{
|
||||
if (o.OrderString == "StartGame")
|
||||
IsValid = true;
|
||||
else if (o.OrderString == "SyncInfo" && !IsValid)
|
||||
LobbyInfo = Session.Deserialize(o.TargetString, o.OrderString);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Regular order - finalize the chunk
|
||||
chunk.Frame = frame;
|
||||
chunk.Packets = packets.ToArray();
|
||||
packets.Clear();
|
||||
chunks.Enqueue(chunk);
|
||||
chunk = new Chunk();
|
||||
|
||||
TickCount = Math.Max(TickCount, frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var gameSpeeds = Game.ModData.GetOrCreate<GameSpeeds>();
|
||||
var gameSpeedName = LobbyInfo.GlobalSettings.OptionOrDefault("gamespeed", gameSpeeds.DefaultSpeed);
|
||||
orderLatency = gameSpeeds.Speeds[gameSpeedName].OrderLatency;
|
||||
}
|
||||
|
||||
void IConnection.StartGame() { }
|
||||
|
||||
// Do nothing: ignore locally generated orders
|
||||
void IConnection.Send(int frame, IEnumerable<Order> orders) { }
|
||||
void IConnection.SendImmediate(IEnumerable<Order> orders) { }
|
||||
|
||||
void IConnection.SendSync(int frame, int syncHash, ulong defeatState)
|
||||
{
|
||||
sync.Enqueue((frame, syncHash, defeatState));
|
||||
}
|
||||
|
||||
void IConnection.Receive(OrderManager orderManager)
|
||||
{
|
||||
while (sync.Count != 0)
|
||||
orderManager.ReceiveSync(sync.Dequeue());
|
||||
|
||||
while (chunks.Count != 0 && chunks.Peek().Frame <= orderManager.NetFrameNumber + orderLatency)
|
||||
{
|
||||
foreach (var o in chunks.Dequeue().Packets)
|
||||
{
|
||||
if (OrderIO.TryParseDisconnect(o, out var disconnect))
|
||||
orderManager.ReceiveDisconnect(disconnect.ClientId, disconnect.Frame);
|
||||
else if (OrderIO.TryParseSync(o.Packet, out var sync))
|
||||
orderManager.ReceiveSync(sync);
|
||||
else if (OrderIO.TryParseOrderPacket(o.Packet, out var orders))
|
||||
{
|
||||
if (orders.Frame == 0)
|
||||
orderManager.ReceiveImmediateOrders(o.ClientId, orders.Orders);
|
||||
else
|
||||
orderManager.ReceiveOrders(o.ClientId, orders);
|
||||
}
|
||||
else
|
||||
throw new InvalidDataException($"Received unknown packet from client {o.ClientId} with length {o.Packet.Length}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int IConnection.LocalClientId => -1;
|
||||
|
||||
void IDisposable.Dispose() { }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user