Der M4-Umfang ist als Protokoll 4 portiert, serverautoritativ. Der Host besitzt LuneMatch und rechnet jeden Kampf; Clients senden nur Absichten und zeigen den Kampf aus Host-Momentaufnahmen. - LuneSession (Core/Net): Rollen offline/Host/Gast, sechs Sitze, Lobby, Hallo mit Protokollprüfung und Namenskürzung, Beitritt und Wieder- einstieg über den Anzeigenamen, private Planung mit Spoof-Schutz, Bereit ist verbindlich, Bots planen einmal je Runde, Disconnect gibt den Platz an einen Bot (nie Kernverlust), Übernahme verwirft den unbestätigten Bot-Entwurf, Revanche hält die Plätze, Hostverlust meldet und führt ins Menü zurück. - Kampfsynchronisation: Beginn mit Einheitenätzen und Gebäude-HP, alle 0,05 s neue Einheiten und ein Frame (13 Werte je Einheit), späte Frames über die Serial verworfen, Gast interpoliert (frame_alpha). - Transport: LuneLoopback (In-Prozess) für die Tests, LuneTcp + LuneWire (Cursor-Codec) für Echtverbindungen statt ENet; ein Socket je Gast. - Oberfläche: LuneMainMenu (Offline/Hosten/Beitreten, Bund, Lane, Name, Adresse, Port) als Startbildschirm, LuneNetPlay als Schale wie lune_main.gd. LuneGame leitet Befehle, Rückgängig und Bereit im Netzspiel über die Sitzung; Tempo und Pause bestimmt der Host. - Tests: NetSuite 43/43 (Port von test_lune_net.gd), NetRoundsSuite 12/12 (Port von net_rounds.py: 5 Runden, Prüfsummen gleich, Gast rechnet 0 Schritte, Drop und Wiedereinstieg auf denselben Sitz), LuneWireTests Round-trip. EditMode 29/29, PlayMode 13/13, Kerntests 457/457. Fingerabdrücke der Bot-Partien unverändert. - Dokumentation: U4-NETWORK (Abnahme, Grenzen), PORT-QA (U4), PORT-STATUS (U4 fertig, U5 nächster Schritt), KNOWN-BEHAVIOR-DIFFERENCES. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
122 lines
4.3 KiB
C#
122 lines
4.3 KiB
C#
using System.Collections.Generic;
|
|
|
|
namespace Luneraria.Core
|
|
{
|
|
/// <summary>Roles of a session (lune_session.gd Role).</summary>
|
|
public enum LuneRole { Offline, Host, Client }
|
|
|
|
/// <summary>One lobby row: peer 0 is a bot, peer 1 the host.</summary>
|
|
public sealed class LobbyEntry
|
|
{
|
|
public int Peer;
|
|
public int Slot;
|
|
public string Name = "";
|
|
public bool Ready;
|
|
public bool Bot;
|
|
|
|
public LobbyEntry Clone() { return (LobbyEntry)MemberwiseClone(); }
|
|
}
|
|
|
|
/// <summary>Result of a session intent (Godot returns a Dictionary with ok, message, pending).</summary>
|
|
public struct SessionResult
|
|
{
|
|
public bool Ok;
|
|
public string Message;
|
|
public bool Pending;
|
|
|
|
public SessionResult(bool ok, string message, bool pending = false)
|
|
{
|
|
Ok = ok;
|
|
Message = message;
|
|
Pending = pending;
|
|
}
|
|
}
|
|
|
|
/// <summary>One player row of the public state; Draft is set only for the receiving seat.</summary>
|
|
public sealed class NetPlayerRow
|
|
{
|
|
public int Slot;
|
|
public string Name = "";
|
|
public bool IsHuman;
|
|
public int Gold;
|
|
public bool Ready;
|
|
public int Earned;
|
|
public int Spent;
|
|
public int NextSeq;
|
|
public List<RosterEntry> Roster = new List<RosterEntry>();
|
|
public HeroState Hero;
|
|
public List<Booking> Bookings = new List<Booking>();
|
|
/// <summary>Private draft of the receiving seat only; null for every other seat.</summary>
|
|
public Draft Draft;
|
|
/// <summary>Undo stack size of the receiving seat (0 for others).</summary>
|
|
public int Undo;
|
|
}
|
|
|
|
/// <summary>Public state of the match for one seat (rpc_state).</summary>
|
|
public sealed class NetState
|
|
{
|
|
public Phase Phase;
|
|
public int Round;
|
|
public int Winner;
|
|
public string EndNote = "";
|
|
public Dictionary<string, double> BuildingHp = new Dictionary<string, double>();
|
|
public BossState Boss = new BossState();
|
|
public Settlement LastSettlement;
|
|
public List<int> DestroyedCores = new List<int>();
|
|
public List<NetPlayerRow> Players = new List<NetPlayerRow>();
|
|
}
|
|
|
|
/// <summary>A mobile unit as it crosses the wire (rpc_combat_begin / rpc_units_spawned).</summary>
|
|
public sealed class NetUnitRecord
|
|
{
|
|
public int Id;
|
|
public string Kind = "";
|
|
public int Team;
|
|
public int TeamIndex;
|
|
public int OwnerSlot = -1;
|
|
public string Uid = "";
|
|
public string Lane = "";
|
|
public string Zone = "";
|
|
public UnitDef Def = new UnitDef();
|
|
public double X;
|
|
public double Y;
|
|
public double Facing;
|
|
public double Hp;
|
|
public double MaxHp;
|
|
public bool Alive;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Outbound session messages (the Godot RPCs). A transport or a test recorder implements this;
|
|
/// without a link the session still runs offline and for in-process host tests.
|
|
/// </summary>
|
|
public interface ILuneNetLink
|
|
{
|
|
/// <summary>Reliable message to one peer (1 = host).</summary>
|
|
void Send(int peer, string kind, object payload);
|
|
/// <summary>Unreliable ordered combat frame to one peer.</summary>
|
|
void SendFrame(int peer, int serial, double elapsed, float[] units, float[] buildingHp);
|
|
}
|
|
|
|
/// <summary>Frames carry 13 floats per mobile unit (lune_session.gd FRAME_STRIDE).</summary>
|
|
public static class LuneNet
|
|
{
|
|
public const int DefaultPort = 24571;
|
|
public const int MaxClients = 5;
|
|
public const int Protocol = 4;
|
|
/// <summary>Seconds between combat snapshots the host sends.</summary>
|
|
public const double FrameInterval = 0.05;
|
|
public const int FrameStride = 13;
|
|
|
|
public const string KHello = "hello";
|
|
public const string KSubmit = "submit";
|
|
public const string KReady = "ready";
|
|
public const string KRefused = "refused";
|
|
public const string KFeedback = "feedback";
|
|
public const string KSetup = "setup";
|
|
public const string KState = "state";
|
|
public const string KLobby = "lobby";
|
|
public const string KCombatBegin = "combat_begin";
|
|
public const string KUnitsSpawned = "units_spawned";
|
|
}
|
|
}
|