Beta Release
This commit is contained in:
+430
-117
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
@@ -9,13 +9,35 @@ namespace MSCO
|
||||
{
|
||||
public enum MsgType : byte
|
||||
{
|
||||
Hello = 1, // Client -> Host: Mod-Version
|
||||
Welcome = 2, // Host -> Client: Handshake ok
|
||||
Reject = 3, // Host -> Client: Grund
|
||||
Player = 4, // Spielerposition
|
||||
SaveBegin = 5, // Host -> Client: Spielstand kommt
|
||||
SaveFile = 6, // Host -> Client: Dateiname + Inhalt
|
||||
StartGame = 7, // Host -> Client: jetzt laden
|
||||
Hello = 1, // Client -> Host: Mod-Version + Name
|
||||
Welcome = 2, // Host -> Client: eigene ID + Spielerliste
|
||||
Reject = 3, // Host -> Client: Grund
|
||||
Player = 4, // Spielerposition
|
||||
SaveBegin = 5, // Host -> Client: Spielstand kommt
|
||||
SaveFile = 6, // Host -> Client: Dateiname + Inhalt
|
||||
StartGame = 7, // Host -> Client: jetzt laden
|
||||
ObjStates = 8, // Positionen der Objekte, die man gerade besitzt
|
||||
ObjOwn = 9, // "Ich übernehme dieses Objekt"
|
||||
ObjRelease = 10, // "Objekt liegt still, wieder frei"
|
||||
FsmEvent = 11, // Schraube angezogen/gelöst, Teil an-/abgebaut
|
||||
PlayerJoined = 12,// Host -> alle: neuer Spieler (ID + Name)
|
||||
PlayerLeft = 13, // Host -> alle: Spieler weg (ID)
|
||||
VehicleFx = 14, // Lichter und Motor-/Dauergeräusche eines Fahrzeugs
|
||||
Ping = 15, // Lebenszeichen (Steam hat keinen Verbindungsabbruch)
|
||||
Ready = 16, // Client -> Server: Spiel geladen, Objekte erfasst
|
||||
WorldSnapshot = 17,// Server -> Client: aktueller Weltzustand
|
||||
Info = 18, // Server-Browser fragt Infos ab (ohne Hello)
|
||||
InfoReply = 19,
|
||||
WorldTime = 20, // Uhrzeit + Wetter vom "Weltuhr"-Spieler
|
||||
MoneyDelta = 21, // Geld hat sich geändert (+/- Betrag, gemeinsame Kasse)
|
||||
DoorState = 22, // Tür/Tor/Haube hat den Zustand gewechselt (FSM-Pfad + Zustand)
|
||||
AdminStatus = 23, // Server -> Client: bool, ist Admin
|
||||
AdminListRequest = 24, // Client -> Server: Spielerliste fürs Admin-Menü
|
||||
AdminList = 25, // Server -> Client: int Anzahl, je [int id, string name, string steamId, bool admin]
|
||||
AdminAction = 26, // Client -> Server: byte (1 = kick, 2 = ban), int Spieler-ID, string Grund
|
||||
AdminResult = 27, // Server -> Client: string Meldung
|
||||
Chat = 28, // Chatnachricht (string)
|
||||
VarSync = 29 // Fahrzeugzustand: int n, je [int key, byte typ, wert]
|
||||
}
|
||||
|
||||
public struct Message
|
||||
@@ -33,49 +55,299 @@ namespace MSCO
|
||||
{
|
||||
public Vector3 Position;
|
||||
public float RotationY;
|
||||
public Vector3 CamPos; // Kopf/Kamera in Weltkoordinaten
|
||||
public Quaternion CamRot; // Blickrichtung inkl. hoch/runter
|
||||
public int HeldId; // ID des gehaltenen Objekts, 0 = nichts
|
||||
public Vector3 HeldLocalPos; // relativ zur Kamera
|
||||
public Quaternion HeldLocalRot; // relativ zur Kamera
|
||||
public bool Seated; // fährt oder sitzt als Beifahrer
|
||||
public float FeetY; // Bodenhöhe unter dem Spieler (für Ducken/Lehnen der Figur)
|
||||
|
||||
public byte[] Serialize()
|
||||
{
|
||||
Vector3 pos = Position;
|
||||
float rotY = RotationY;
|
||||
PlayerState s = this;
|
||||
return NetSession.Build(w =>
|
||||
{
|
||||
w.Write(pos.x); w.Write(pos.y); w.Write(pos.z);
|
||||
w.Write(rotY);
|
||||
Write(w, s.Position);
|
||||
w.Write(s.RotationY);
|
||||
Write(w, s.CamPos);
|
||||
Write(w, s.CamRot);
|
||||
w.Write(s.Seated);
|
||||
w.Write(s.FeetY);
|
||||
w.Write(s.HeldId);
|
||||
if (s.HeldId == 0) return;
|
||||
Write(w, s.HeldLocalPos);
|
||||
Write(w, s.HeldLocalRot);
|
||||
});
|
||||
}
|
||||
|
||||
public static PlayerState Deserialize(BinaryReader r)
|
||||
{
|
||||
return new PlayerState
|
||||
var s = new PlayerState
|
||||
{
|
||||
Position = new Vector3(r.ReadSingle(), r.ReadSingle(), r.ReadSingle()),
|
||||
RotationY = r.ReadSingle()
|
||||
Position = ReadVector(r),
|
||||
RotationY = r.ReadSingle(),
|
||||
CamPos = ReadVector(r),
|
||||
CamRot = ReadQuaternion(r),
|
||||
Seated = r.ReadBoolean(),
|
||||
FeetY = r.ReadSingle(),
|
||||
HeldId = r.ReadInt32(),
|
||||
HeldLocalRot = Quaternion.identity
|
||||
};
|
||||
if (s.HeldId != 0)
|
||||
{
|
||||
s.HeldLocalPos = ReadVector(r);
|
||||
s.HeldLocalRot = ReadQuaternion(r);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
static void Write(BinaryWriter w, Vector3 v) { w.Write(v.x); w.Write(v.y); w.Write(v.z); }
|
||||
static void Write(BinaryWriter w, Quaternion q) { w.Write(q.x); w.Write(q.y); w.Write(q.z); w.Write(q.w); }
|
||||
static Vector3 ReadVector(BinaryReader r) { return new Vector3(r.ReadSingle(), r.ReadSingle(), r.ReadSingle()); }
|
||||
static Quaternion ReadQuaternion(BinaryReader r) { return new Quaternion(r.ReadSingle(), r.ReadSingle(), r.ReadSingle(), r.ReadSingle()); }
|
||||
}
|
||||
|
||||
public interface IConnection
|
||||
{
|
||||
bool IsClosed { get; }
|
||||
string CloseReason { get; }
|
||||
List<Message> Poll();
|
||||
void Send(MsgType type, byte[] payload);
|
||||
void Close(string reason);
|
||||
}
|
||||
|
||||
/// <summary>Setzt aus einem Byte-Strom Nachrichten zusammen: [int Länge][byte Typ][Payload].</summary>
|
||||
public class FrameParser
|
||||
{
|
||||
const int MaxFrame = 32 * 1024 * 1024;
|
||||
|
||||
byte[] buffer = new byte[64 * 1024];
|
||||
int count;
|
||||
|
||||
public void Append(byte[] data, int length)
|
||||
{
|
||||
EnsureCapacity(count + length);
|
||||
Buffer.BlockCopy(data, 0, buffer, count, length);
|
||||
count += length;
|
||||
}
|
||||
|
||||
public void Parse(List<Message> messages)
|
||||
{
|
||||
int offset = 0;
|
||||
while (count - offset >= 5)
|
||||
{
|
||||
int length = BitConverter.ToInt32(buffer, offset);
|
||||
if (length < 1 || length > MaxFrame) throw new IOException("Invalid packet");
|
||||
if (count - offset - 4 < length) break;
|
||||
|
||||
var payload = new byte[length - 1];
|
||||
Buffer.BlockCopy(buffer, offset + 5, payload, 0, payload.Length);
|
||||
messages.Add(new Message { Type = (MsgType)buffer[offset + 4], Payload = payload });
|
||||
offset += 4 + length;
|
||||
}
|
||||
if (offset > 0)
|
||||
{
|
||||
Buffer.BlockCopy(buffer, offset, buffer, 0, count - offset);
|
||||
count -= offset;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] Frame(MsgType type, byte[] payload)
|
||||
{
|
||||
if (payload == null) payload = new byte[0];
|
||||
var frame = new byte[5 + payload.Length];
|
||||
BitConverter.GetBytes(payload.Length + 1).CopyTo(frame, 0);
|
||||
frame[4] = (byte)type;
|
||||
Buffer.BlockCopy(payload, 0, frame, 5, payload.Length);
|
||||
return frame;
|
||||
}
|
||||
|
||||
void EnsureCapacity(int needed)
|
||||
{
|
||||
if (needed <= buffer.Length) return;
|
||||
var bigger = new byte[Math.Max(needed, buffer.Length * 2)];
|
||||
Buffer.BlockCopy(buffer, 0, bigger, 0, count);
|
||||
buffer = bigger;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>TCP-Verbindung (LAN / IP). Lesen ist nicht-blockierend und wird über Poll() abgefragt.</summary>
|
||||
public class Connection : IConnection
|
||||
{
|
||||
public bool IsClosed { get; private set; }
|
||||
public string CloseReason { get; private set; }
|
||||
|
||||
readonly TcpClient client;
|
||||
readonly NetworkStream stream;
|
||||
readonly FrameParser parser = new FrameParser();
|
||||
readonly byte[] readBuffer = new byte[64 * 1024];
|
||||
|
||||
public Connection(TcpClient client)
|
||||
{
|
||||
this.client = client;
|
||||
client.NoDelay = true;
|
||||
client.SendBufferSize = 4 * 1024 * 1024;
|
||||
stream = client.GetStream();
|
||||
}
|
||||
|
||||
public List<Message> Poll()
|
||||
{
|
||||
var messages = new List<Message>();
|
||||
if (IsClosed) return messages;
|
||||
try
|
||||
{
|
||||
// Verbindung von der Gegenseite geschlossen?
|
||||
if (client.Client.Poll(0, SelectMode.SelectRead) && client.Available == 0)
|
||||
{
|
||||
Close("Connection closed");
|
||||
return messages;
|
||||
}
|
||||
while (stream.DataAvailable)
|
||||
{
|
||||
int read = stream.Read(readBuffer, 0, readBuffer.Length);
|
||||
if (read <= 0) break;
|
||||
parser.Append(readBuffer, read);
|
||||
}
|
||||
parser.Parse(messages);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Close("Network error: " + e.Message);
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
public void Send(MsgType type, byte[] payload)
|
||||
{
|
||||
if (IsClosed) return;
|
||||
try
|
||||
{
|
||||
byte[] frame = FrameParser.Frame(type, payload);
|
||||
stream.Write(frame, 0, frame.Length); // blockierend, damit große Dateien komplett rausgehen
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Close("Network error: " + e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
public void Close(string reason)
|
||||
{
|
||||
if (IsClosed) return;
|
||||
IsClosed = true;
|
||||
CloseReason = reason;
|
||||
try { client.Close(); } catch { }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// TCP-Verbindung zu genau einem Mitspieler. Nachrichten: [int Länge][byte Typ][Payload].
|
||||
/// Lesen ist nicht-blockierend und wird jedes Frame über Poll() abgefragt.
|
||||
/// Verbindung über Steam P2P (Steam kümmert sich um NAT/Relay). Pakete sind auf 1 MB
|
||||
/// begrenzt, deshalb werden Nachrichten in Stücke geteilt und beim Empfänger über den
|
||||
/// FrameParser wieder zusammengesetzt (der Reliable-Kanal hält die Reihenfolge ein).
|
||||
/// </summary>
|
||||
public class NetSession
|
||||
public class SteamConnection : IConnection
|
||||
{
|
||||
const int MaxFrame = 32 * 1024 * 1024;
|
||||
const int ChunkSize = 512 * 1024;
|
||||
const float Timeout = 20f; // ohne Pakete (auch Pings) gilt die Verbindung als tot
|
||||
|
||||
public bool IsHost { get; private set; }
|
||||
public bool IsConnected { get { return stream != null; } }
|
||||
public readonly Steamworks.CSteamID Remote;
|
||||
public bool IsClosed { get; private set; }
|
||||
public string CloseReason { get; private set; }
|
||||
|
||||
TcpListener listener;
|
||||
TcpClient client;
|
||||
NetworkStream stream;
|
||||
IAsyncResult connecting;
|
||||
bool justConnected;
|
||||
readonly FrameParser parser = new FrameParser();
|
||||
float lastReceive;
|
||||
|
||||
byte[] buffer = new byte[64 * 1024];
|
||||
int bufferCount;
|
||||
public SteamConnection(Steamworks.CSteamID remote)
|
||||
{
|
||||
Remote = remote;
|
||||
lastReceive = Time.realtimeSinceStartup;
|
||||
}
|
||||
|
||||
public void OnPacket(byte[] data, int length)
|
||||
{
|
||||
parser.Append(data, length);
|
||||
lastReceive = Time.realtimeSinceStartup;
|
||||
}
|
||||
|
||||
public List<Message> Poll()
|
||||
{
|
||||
var messages = new List<Message>();
|
||||
if (IsClosed) return messages;
|
||||
try { parser.Parse(messages); }
|
||||
catch (Exception e) { Close("Network error: " + e.Message); }
|
||||
if (Time.realtimeSinceStartup - lastReceive > Timeout) Close("Timed out");
|
||||
return messages;
|
||||
}
|
||||
|
||||
public void Send(MsgType type, byte[] payload)
|
||||
{
|
||||
if (IsClosed) return;
|
||||
byte[] frame = FrameParser.Frame(type, payload);
|
||||
for (int offset = 0; offset < frame.Length; offset += ChunkSize)
|
||||
{
|
||||
int length = Math.Min(ChunkSize, frame.Length - offset);
|
||||
byte[] chunk = frame;
|
||||
if (offset != 0 || length != frame.Length)
|
||||
{
|
||||
chunk = new byte[length];
|
||||
Buffer.BlockCopy(frame, offset, chunk, 0, length);
|
||||
}
|
||||
if (!Steamworks.SteamNetworking.SendP2PPacket(Remote, chunk, (uint)length, Steamworks.EP2PSend.k_EP2PSendReliable, 0))
|
||||
{
|
||||
Close("Steam send failed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Close(string reason)
|
||||
{
|
||||
if (IsClosed) return;
|
||||
IsClosed = true;
|
||||
CloseReason = reason;
|
||||
try { Steamworks.SteamNetworking.CloseP2PSessionWithUser(Remote); } catch { }
|
||||
}
|
||||
}
|
||||
|
||||
public enum NetEventKind { Connected, Disconnected, Message }
|
||||
|
||||
public struct NetEvent
|
||||
{
|
||||
public NetEventKind Kind;
|
||||
public int PeerId; // beim Client immer NetSession.HostId
|
||||
public Message Message;
|
||||
public string Reason;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stern-Topologie: Der Host hält eine Verbindung zu jedem Client,
|
||||
/// Clients sind nur mit dem Host verbunden. Der Host leitet Nachrichten weiter.
|
||||
/// Transport entweder TCP (LAN/IP) oder Steam P2P.
|
||||
/// </summary>
|
||||
public class NetSession
|
||||
{
|
||||
public const int HostId = 0;
|
||||
|
||||
public bool IsHost { get; private set; }
|
||||
public bool IsSteam { get; private set; }
|
||||
public bool IsClosed { get; private set; }
|
||||
public string CloseReason { get; private set; }
|
||||
|
||||
// Host
|
||||
TcpListener listener;
|
||||
readonly Dictionary<int, IConnection> peers = new Dictionary<int, IConnection>();
|
||||
readonly Dictionary<ulong, int> steamPeerIds = new Dictionary<ulong, int>();
|
||||
Func<Steamworks.CSteamID, bool> allowSteamPeer;
|
||||
int nextPeerId = 1;
|
||||
|
||||
// Client
|
||||
TcpClient connectingClient;
|
||||
IAsyncResult connecting;
|
||||
IConnection server;
|
||||
bool pendingConnected;
|
||||
|
||||
byte[] steamBuffer = new byte[64 * 1024];
|
||||
|
||||
public static NetSession StartHost(int port)
|
||||
{
|
||||
@@ -88,127 +360,169 @@ namespace MSCO
|
||||
public static NetSession StartClient(string ip, int port)
|
||||
{
|
||||
var s = new NetSession();
|
||||
s.client = new TcpClient();
|
||||
s.connecting = s.client.BeginConnect(ip, port, null, null);
|
||||
s.connectingClient = new TcpClient();
|
||||
s.connecting = s.connectingClient.BeginConnect(ip, port, null, null);
|
||||
return s;
|
||||
}
|
||||
|
||||
/// <summary>True genau einmal, direkt nachdem die Verbindung steht.</summary>
|
||||
public bool ConsumeConnected()
|
||||
/// <summary>Steam-Host: nimmt Pakete von Spielern an, die allowPeer erlaubt (Lobby-Mitglieder).</summary>
|
||||
public static NetSession StartSteamHost(Func<Steamworks.CSteamID, bool> allowPeer)
|
||||
{
|
||||
bool c = justConnected;
|
||||
justConnected = false;
|
||||
return c;
|
||||
return new NetSession { IsHost = true, IsSteam = true, allowSteamPeer = allowPeer };
|
||||
}
|
||||
|
||||
public List<Message> Poll()
|
||||
public static NetSession StartSteamClient(Steamworks.CSteamID host)
|
||||
{
|
||||
var messages = new List<Message>();
|
||||
if (IsClosed) return messages;
|
||||
return new NetSession { IsSteam = true, server = new SteamConnection(host), pendingConnected = true };
|
||||
}
|
||||
|
||||
public List<NetEvent> Poll()
|
||||
{
|
||||
var events = new List<NetEvent>();
|
||||
if (IsClosed) return events;
|
||||
try
|
||||
{
|
||||
AcceptOrConnect();
|
||||
if (stream == null) return messages;
|
||||
|
||||
// Verbindung von der Gegenseite geschlossen?
|
||||
if (client.Client.Poll(0, SelectMode.SelectRead) && client.Available == 0)
|
||||
{
|
||||
Close("Connection closed by other player");
|
||||
return messages;
|
||||
}
|
||||
|
||||
while (stream.DataAvailable)
|
||||
{
|
||||
EnsureCapacity(bufferCount + client.Available);
|
||||
int read = stream.Read(buffer, bufferCount, buffer.Length - bufferCount);
|
||||
if (read <= 0) break;
|
||||
bufferCount += read;
|
||||
}
|
||||
|
||||
ParseFrames(messages);
|
||||
if (IsSteam) PumpSteam(events);
|
||||
if (IsHost) PollHost(events);
|
||||
else PollClient(events);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Close("Network error: " + e.Message);
|
||||
}
|
||||
return messages;
|
||||
return events;
|
||||
}
|
||||
|
||||
void AcceptOrConnect()
|
||||
void PumpSteam(List<NetEvent> events)
|
||||
{
|
||||
if (IsHost)
|
||||
uint size;
|
||||
while (Steamworks.SteamNetworking.IsP2PPacketAvailable(out size, 0))
|
||||
{
|
||||
if (steamBuffer.Length < size) steamBuffer = new byte[size];
|
||||
uint read;
|
||||
Steamworks.CSteamID from;
|
||||
if (!Steamworks.SteamNetworking.ReadP2PPacket(steamBuffer, size, out read, out from, 0)) break;
|
||||
|
||||
if (IsHost)
|
||||
{
|
||||
int id;
|
||||
if (!steamPeerIds.TryGetValue(from.m_SteamID, out id))
|
||||
{
|
||||
if (allowSteamPeer == null || !allowSteamPeer(from)) continue; // nicht in der Lobby
|
||||
id = nextPeerId++;
|
||||
steamPeerIds[from.m_SteamID] = id;
|
||||
peers[id] = new SteamConnection(from);
|
||||
events.Add(new NetEvent { Kind = NetEventKind.Connected, PeerId = id });
|
||||
}
|
||||
IConnection c;
|
||||
if (peers.TryGetValue(id, out c)) ((SteamConnection)c).OnPacket(steamBuffer, (int)read);
|
||||
}
|
||||
else
|
||||
{
|
||||
var host = server as SteamConnection;
|
||||
if (host != null && from == host.Remote) host.OnPacket(steamBuffer, (int)read);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PollHost(List<NetEvent> events)
|
||||
{
|
||||
if (listener != null)
|
||||
{
|
||||
while (listener.Pending())
|
||||
{
|
||||
TcpClient incoming = listener.AcceptTcpClient();
|
||||
if (client != null) { incoming.Close(); continue; } // nur ein Mitspieler
|
||||
client = incoming;
|
||||
OnSocketReady();
|
||||
int id = nextPeerId++;
|
||||
peers[id] = new Connection(listener.AcceptTcpClient());
|
||||
events.Add(new NetEvent { Kind = NetEventKind.Connected, PeerId = id });
|
||||
}
|
||||
}
|
||||
else if (connecting != null && connecting.IsCompleted)
|
||||
|
||||
List<int> closed = null;
|
||||
foreach (KeyValuePair<int, IConnection> p in peers)
|
||||
{
|
||||
foreach (Message m in p.Value.Poll())
|
||||
events.Add(new NetEvent { Kind = NetEventKind.Message, PeerId = p.Key, Message = m });
|
||||
if (p.Value.IsClosed)
|
||||
{
|
||||
if (closed == null) closed = new List<int>();
|
||||
closed.Add(p.Key);
|
||||
}
|
||||
}
|
||||
if (closed == null) return;
|
||||
foreach (int id in closed)
|
||||
{
|
||||
IConnection c = peers[id];
|
||||
events.Add(new NetEvent { Kind = NetEventKind.Disconnected, PeerId = id, Reason = c.CloseReason });
|
||||
var steamConn = c as SteamConnection;
|
||||
if (steamConn != null) steamPeerIds.Remove(steamConn.Remote.m_SteamID);
|
||||
peers.Remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
void PollClient(List<NetEvent> events)
|
||||
{
|
||||
if (connecting != null)
|
||||
{
|
||||
if (!connecting.IsCompleted) return;
|
||||
IAsyncResult result = connecting;
|
||||
connecting = null;
|
||||
client.EndConnect(result); // wirft, wenn nicht erreichbar
|
||||
OnSocketReady();
|
||||
connectingClient.EndConnect(result); // wirft, wenn nicht erreichbar
|
||||
server = new Connection(connectingClient);
|
||||
pendingConnected = true;
|
||||
}
|
||||
if (server == null) return;
|
||||
if (pendingConnected)
|
||||
{
|
||||
pendingConnected = false;
|
||||
events.Add(new NetEvent { Kind = NetEventKind.Connected, PeerId = HostId });
|
||||
}
|
||||
|
||||
foreach (Message m in server.Poll())
|
||||
events.Add(new NetEvent { Kind = NetEventKind.Message, PeerId = HostId, Message = m });
|
||||
if (server.IsClosed) Close(server.CloseReason);
|
||||
}
|
||||
|
||||
void OnSocketReady()
|
||||
/// <summary>An einen bestimmten Spieler (Client: immer an den Host).</summary>
|
||||
public void SendTo(int peerId, MsgType type, byte[] payload = null)
|
||||
{
|
||||
client.NoDelay = true;
|
||||
client.SendBufferSize = 4 * 1024 * 1024;
|
||||
stream = client.GetStream();
|
||||
justConnected = true;
|
||||
if (!IsHost) { if (server != null) server.Send(type, payload); return; }
|
||||
IConnection c;
|
||||
if (peers.TryGetValue(peerId, out c)) c.Send(type, payload);
|
||||
}
|
||||
|
||||
void ParseFrames(List<Message> messages)
|
||||
/// <summary>An alle (Client: an den Host). Optional einen Spieler auslassen.</summary>
|
||||
public void SendToAll(MsgType type, byte[] payload = null, int except = -1)
|
||||
{
|
||||
int offset = 0;
|
||||
while (bufferCount - offset >= 5)
|
||||
{
|
||||
int length = BitConverter.ToInt32(buffer, offset);
|
||||
if (length < 1 || length > MaxFrame) throw new IOException("Invalid packet");
|
||||
if (bufferCount - offset - 4 < length) break;
|
||||
|
||||
var payload = new byte[length - 1];
|
||||
Buffer.BlockCopy(buffer, offset + 5, payload, 0, payload.Length);
|
||||
messages.Add(new Message { Type = (MsgType)buffer[offset + 4], Payload = payload });
|
||||
offset += 4 + length;
|
||||
}
|
||||
if (offset > 0)
|
||||
{
|
||||
Buffer.BlockCopy(buffer, offset, buffer, 0, bufferCount - offset);
|
||||
bufferCount -= offset;
|
||||
}
|
||||
if (!IsHost) { if (server != null) server.Send(type, payload); return; }
|
||||
foreach (KeyValuePair<int, IConnection> p in peers)
|
||||
if (p.Key != except) p.Value.Send(type, payload);
|
||||
}
|
||||
|
||||
void EnsureCapacity(int needed)
|
||||
/// <summary>Host: Verbindung zu einem Spieler trennen (wird beim nächsten Poll gemeldet).</summary>
|
||||
public void Kick(int peerId, string reason)
|
||||
{
|
||||
if (needed <= buffer.Length) return;
|
||||
var bigger = new byte[Math.Max(needed, buffer.Length * 2)];
|
||||
Buffer.BlockCopy(buffer, 0, bigger, 0, bufferCount);
|
||||
buffer = bigger;
|
||||
IConnection c;
|
||||
if (peers.TryGetValue(peerId, out c)) c.Close(reason);
|
||||
}
|
||||
|
||||
public void Send(MsgType type, byte[] payload = null)
|
||||
/// <summary>Host: Steam-Spieler hat die Lobby verlassen.</summary>
|
||||
public void DropSteamPeer(ulong steamId, string reason)
|
||||
{
|
||||
if (stream == null) return;
|
||||
if (payload == null) payload = new byte[0];
|
||||
try
|
||||
{
|
||||
var frame = new byte[5 + payload.Length];
|
||||
BitConverter.GetBytes(payload.Length + 1).CopyTo(frame, 0);
|
||||
frame[4] = (byte)type;
|
||||
Buffer.BlockCopy(payload, 0, frame, 5, payload.Length);
|
||||
stream.Write(frame, 0, frame.Length); // blockierend, damit große Dateien komplett rausgehen
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Close("Network error: " + e.Message);
|
||||
}
|
||||
int id;
|
||||
if (steamPeerIds.TryGetValue(steamId, out id)) Kick(id, reason);
|
||||
}
|
||||
|
||||
public void Close(string reason = "Disconnected")
|
||||
{
|
||||
if (IsClosed) return;
|
||||
IsClosed = true;
|
||||
CloseReason = reason;
|
||||
foreach (IConnection c in peers.Values) c.Close(reason);
|
||||
peers.Clear();
|
||||
steamPeerIds.Clear();
|
||||
if (server != null) server.Close(reason);
|
||||
try { if (connectingClient != null && server == null) connectingClient.Close(); } catch { }
|
||||
try { if (listener != null) listener.Stop(); } catch { }
|
||||
}
|
||||
|
||||
public static byte[] Build(Action<BinaryWriter> write)
|
||||
@@ -222,14 +536,13 @@ namespace MSCO
|
||||
}
|
||||
}
|
||||
|
||||
public void Close(string reason = "Disconnected")
|
||||
/// <summary>Stellt der Nachricht die Absender-ID voran (für weitergeleitete Nachrichten).</summary>
|
||||
public static byte[] WithSender(int senderId, byte[] payload)
|
||||
{
|
||||
if (IsClosed) return;
|
||||
IsClosed = true;
|
||||
CloseReason = reason;
|
||||
stream = null;
|
||||
try { if (client != null) client.Close(); } catch { }
|
||||
try { if (listener != null) listener.Stop(); } catch { }
|
||||
var result = new byte[4 + payload.Length];
|
||||
BitConverter.GetBytes(senderId).CopyTo(result, 0);
|
||||
Buffer.BlockCopy(payload, 0, result, 4, payload.Length);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user