using System.Collections.Generic; using Luneraria.Core; using UnityEngine; namespace Luneraria.View { /// /// Hosts a match in the scene (U2C/U2D; human seat and tempo since U3). Update only hands frame /// time (times the speed) to the runner's fixed clock; it never decides anything in combat. /// Seed, start round and extra gold are the public fixtures of the golden scenarios. With /// HumanSlot >= 0 the HUD plans that seat and planning waits for its Ready; tempo steps /// (1×, 2×, 4×) and pause act only in combat, as in Godot (lune_game.gd). /// public sealed class LuneMatchDriver : MonoBehaviour { /// Tempo steps of the HUD's tempo button. public static readonly int[] SpeedSteps = { 1, 2, 4 }; /// /// True: the scene's match starts at once, without the start screen, as before U4. Tests /// set it before they load the scene; the benchmark (-luneBenchmark) skips the start screen /// by itself. False (the game): the start screen (Luneraria.UI.LuneNetPlay) starts matches. /// public static bool DirectStart; public int Seed = 2; public int StartRound = 1; public int ExtraGold; public string Profile = ""; /// Optional Bünde of the six seats (fixtures like Godot's start option "bunds"); empty seats follow the seed. [System.NonSerialized] public string[] Bunds; /// The seat a human plays through the HUD; -1 for a bot match (U2 tests, benchmark). public int HumanSlot = -1; /// Match speed: simulated seconds per real second (tests run faster than 1). public float Speed = 1f; /// Index into SpeedSteps (the player's tempo, on top of Speed). public int SpeedIndex; public bool Paused; public bool RecordTrace = true; public LuneMatchRunner Runner { get; private set; } /// Main-thread cost of the last frame's simulation (milliseconds). public double LastAdvanceMs { get; private set; } /// Bound network session (U4); null offline. public LuneSession Session; /// True on a guest: the host simulates, this side only shows snapshots. public bool IsClient; readonly System.Diagnostics.Stopwatch _clock = new System.Diagnostics.Stopwatch(); public int Tempo { get { return SpeedSteps[Mathf.Clamp(SpeedIndex, 0, SpeedSteps.Length - 1)]; } } public void Restart() { var options = new MatchOptions { Seed = Seed, HumanSlot = HumanSlot, Bunds = Bunds != null ? new List(Bunds) : new List() }; Runner = new LuneMatchRunner(options, StartRound, ExtraGold, Profile); if (RecordTrace) Runner.Trace = new LuneTrace(); SpeedIndex = 0; Paused = false; Session = null; IsClient = false; } /// /// Adopts the session's match (U4). The host still advances combat and ticks snapshots; /// a guest never advances — its match is a display mirror of the host's snapshots. /// public void Adopt(LuneMatch match, int humanSlot, bool isClient, LuneSession session) { Session = session; IsClient = isClient; Runner = new LuneMatchRunner(match, humanSlot, session); SpeedIndex = 0; Paused = false; } /// Next tempo step (1× → 2× → 4× → 1×). public void CycleSpeed() { SpeedIndex = (SpeedIndex + 1) % SpeedSteps.Length; } /// Pause exists only in combat; planning waits for the player anyway. public bool TogglePause() { if (Runner == null || Runner.Current != LuneMatchRunner.Stage.Combat) return false; Paused = !Paused; return true; } void Awake() { if (Runner == null) Restart(); } void Update() { if (Runner == null) return; _clock.Restart(); // A guest shows the host's snapshots and never advances the match. if (IsClient) { LastAdvanceMs = 0.0; return; } // Pause holds only the combat steps (Godot: a paused game still plans and settles). if (!(Paused && Runner.Current == LuneMatchRunner.Stage.Combat)) Runner.Advance(Time.deltaTime * Speed * Tempo); if (Session != null && Session.IsNetworked() && Session.IsAuthority()) Session.HostTick(Time.deltaTime * Speed * Tempo); LastAdvanceMs = _clock.Elapsed.TotalMilliseconds; } } }