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;
/// S1: extended rules for matches the driver starts itself (tests, benchmark); the start screen decides for the game.
public bool Extended;
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(), Extended = Extended };
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;
}
}
}