Add project files.

This commit is contained in:
2026-09-26 00:08:05 +02:00
parent 499699f7c8
commit adca4d9167
10 changed files with 1055 additions and 0 deletions
+235
View File
@@ -0,0 +1,235 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using UnityEngine;
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
}
public struct Message
{
public MsgType Type;
public byte[] Payload;
public BinaryReader Reader()
{
return new BinaryReader(new MemoryStream(Payload));
}
}
public struct PlayerState
{
public Vector3 Position;
public float RotationY;
public byte[] Serialize()
{
Vector3 pos = Position;
float rotY = RotationY;
return NetSession.Build(w =>
{
w.Write(pos.x); w.Write(pos.y); w.Write(pos.z);
w.Write(rotY);
});
}
public static PlayerState Deserialize(BinaryReader r)
{
return new PlayerState
{
Position = new Vector3(r.ReadSingle(), r.ReadSingle(), r.ReadSingle()),
RotationY = r.ReadSingle()
};
}
}
/// <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.
/// </summary>
public class NetSession
{
const int MaxFrame = 32 * 1024 * 1024;
public bool IsHost { get; private set; }
public bool IsConnected { get { return stream != null; } }
public bool IsClosed { get; private set; }
public string CloseReason { get; private set; }
TcpListener listener;
TcpClient client;
NetworkStream stream;
IAsyncResult connecting;
bool justConnected;
byte[] buffer = new byte[64 * 1024];
int bufferCount;
public static NetSession StartHost(int port)
{
var s = new NetSession { IsHost = true };
s.listener = new TcpListener(IPAddress.Any, port);
s.listener.Start();
return s;
}
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);
return s;
}
/// <summary>True genau einmal, direkt nachdem die Verbindung steht.</summary>
public bool ConsumeConnected()
{
bool c = justConnected;
justConnected = false;
return c;
}
public List<Message> Poll()
{
var messages = new List<Message>();
if (IsClosed) return messages;
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);
}
catch (Exception e)
{
Close("Network error: " + e.Message);
}
return messages;
}
void AcceptOrConnect()
{
if (IsHost)
{
while (listener.Pending())
{
TcpClient incoming = listener.AcceptTcpClient();
if (client != null) { incoming.Close(); continue; } // nur ein Mitspieler
client = incoming;
OnSocketReady();
}
}
else if (connecting != null && connecting.IsCompleted)
{
IAsyncResult result = connecting;
connecting = null;
client.EndConnect(result); // wirft, wenn nicht erreichbar
OnSocketReady();
}
}
void OnSocketReady()
{
client.NoDelay = true;
client.SendBufferSize = 4 * 1024 * 1024;
stream = client.GetStream();
justConnected = true;
}
void ParseFrames(List<Message> messages)
{
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;
}
}
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, bufferCount);
buffer = bigger;
}
public void Send(MsgType type, byte[] payload = null)
{
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);
}
}
public static byte[] Build(Action<BinaryWriter> write)
{
using (var ms = new MemoryStream())
using (var w = new BinaryWriter(ms))
{
write(w);
w.Flush();
return ms.ToArray();
}
}
public void Close(string reason = "Disconnected")
{
if (IsClosed) return;
IsClosed = true;
CloseReason = reason;
stream = null;
try { if (client != null) client.Close(); } catch { }
try { if (listener != null) listener.Stop(); } catch { }
}
}
}