using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Luneraria.Core;
using Luneraria.View;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.UIElements;
namespace Luneraria.UI.Tests
{
///
/// The real game for the U3 tests (port of the helpers of tools/luneraria/verify_g3.gd): the G1
/// scene with its game controller, a human seat, the HUD and the camera rendering into textures
/// of one size, so screen points of the camera and of the HUD agree. Input goes through the
/// real paths: UI Toolkit pointer events for the HUD, LuneGame.Key and ClickField for keys and
/// the field. Nothing sets HUD state directly; public match state (round, HP, gold, claims) is
/// set like Godot's g3_views.gd did.
///
public sealed class HudSession
{
public const int Human = 1;
public LuneGame Game;
public LuneHud Hud;
public LuneMatchDriver Driver;
public LuneMatchView View;
public LuneCameraRig Rig;
public Camera Camera;
public RenderTexture Target;
RenderTexture _sceneTarget;
public int Width;
public int Height;
public LuneMatch Match { get { return Driver.Runner.Match; } }
public LuneMatchRunner Runner { get { return Driver.Runner; } }
public LuneHudModel Model { get { return Hud.Model; } }
public Player Me { get { return Match.Players[Human]; } }
public static string Root { get { return Path.GetDirectoryName(Application.dataPath); } }
/// Loads the scene and starts a match with the human seat, HUD into a width×height texture.
public IEnumerator Open(int width = 1920, int height = 1080, int seed = 4, float uiScale = 1f, bool reducedMotion = false, int startRound = 1, int extraGold = 0)
{
LunePreferences.ResetSession(uiScale, reducedMotion);
yield return HudCaptureTests.LoadScene();
Width = width;
Height = height;
Game = Object.FindAnyObjectByType();
Assert.IsNotNull(Game, "the scene has a game controller");
Driver = Game.Driver;
View = Game.View;
Rig = Game.Rig;
Camera = View.ViewCamera != null ? View.ViewCamera : Camera.main;
Target = new RenderTexture(width, height, 0, RenderTextureFormat.ARGB32);
_sceneTarget = new RenderTexture(width, height, 24, RenderTextureFormat.ARGB32);
Camera.targetTexture = _sceneTarget;
Game.Target = Target;
Driver.HumanSlot = Human;
Driver.Seed = seed;
Driver.StartRound = startRound;
Driver.ExtraGold = extraGold;
Driver.Speed = 1f;
Driver.Restart();
yield return Frames(3);
Hud = Game.Hud;
Assert.AreSame(Driver.Runner.Match, Hud.Match, "the HUD shows the driver's match");
Game.ResetCamera(true);
yield return Frames(2);
}
public void Close()
{
if (Camera != null)
Camera.targetTexture = null;
Target?.Release();
_sceneTarget?.Release();
}
public static IEnumerator Frames(int count)
{
for (int index = 0; index < count; index++)
yield return null;
}
/// The HUD rebuilds now (Godot sync()).
public void Sync()
{
Hud.MarkDirty();
Hud.Tick(0f);
}
/// HUD time passes (tooltips, feed, rates) without simulating the match.
public void Tick(float seconds, float step = .02f)
{
for (float time = 0f; time < seconds - 1e-4f; time += step)
Hud.Tick(step);
}
// ---------------------------------------------------------------- pointer input (UI Toolkit events)
IPanel Panel { get { return Hud.Root.panel; } }
public void Pointer(Vector2 at, EventType type)
{
var source = new Event { type = type, mousePosition = at, button = 0, clickCount = 1 };
EventBase evt = type == EventType.MouseDown ? PointerDownEvent.GetPooled(source)
: type == EventType.MouseUp ? PointerUpEvent.GetPooled(source)
: (EventBase)PointerMoveEvent.GetPooled(source);
using (evt)
Panel.visualTree.SendEvent(evt);
}
public static Vector2 Center(VisualElement element)
{
return element.worldBound.center;
}
public void MoveTo(Vector2 at) { Pointer(at, EventType.MouseMove); }
public void ClickAt(Vector2 at)
{
Pointer(at, EventType.MouseMove);
Pointer(at, EventType.MouseDown);
Pointer(at, EventType.MouseUp);
}
/// A real click on an element (pointer move, down and up at its centre).
public void Click(VisualElement element)
{
Assert.IsNotNull(element);
Assert.IsTrue(Visible(element), "click on a hidden element: " + element.name + " " + element.GetType().Name);
ClickAt(Center(element));
Sync();
}
///
/// A real click on an element that may just have been rebuilt: one layout frame first (a
/// player can only click what is drawn), then pointer move, down and up, then a sync.
///
public IEnumerator Tap(System.Func find)
{
yield return null;
VisualElement element = find();
Assert.IsNotNull(element, "nothing to tap");
Assert.IsTrue(Visible(element), "tap on a hidden element: " + element.GetType().Name);
Rect bound = element.worldBound;
Assert.IsFalse(float.IsNaN(bound.width) || bound.width <= 0f, "tap on an element without layout: " + element.GetType().Name);
ClickAt(bound.center);
Sync();
yield return null;
}
public LuneTooltip Hover(VisualElement element, float wait = .35f)
{
MoveTo(Center(element));
Tick(wait);
return Hud.Tooltip;
}
public void Leave()
{
MoveTo(new Vector2(-50f, -50f));
Tick(.05f);
}
// ---------------------------------------------------------------- queries
public static bool Visible(VisualElement element)
{
for (VisualElement node = element; node != null; node = node.parent)
{
if (node.style.display == DisplayStyle.None)
return false;
}
return element.panel != null;
}
/// All visible text of the HUD (Godot texts(root)).
public string Texts(VisualElement root = null)
{
var parts = new List();
root = root ?? Hud.Root;
root.Query().ForEach(text =>
{
if (Visible(text) && !string.IsNullOrEmpty(text.text))
parts.Add(text.text);
});
return string.Join("\n", parts);
}
public T First(System.Func where = null) where T : VisualElement
{
return Hud.Root.Query().ToList().FirstOrDefault(e => Visible(e) && (where == null || where(e)));
}
public LuneButton ButtonWithText(string text, VisualElement root = null)
{
return (root ?? Hud.Root).Query().ToList().FirstOrDefault(b => Visible(b) && b.Text == text);
}
// ---------------------------------------------------------------- match helpers (public state, like g3_views.gd)
public IEnumerator Starter()
{
yield return Tap(() => ButtonWithText("Startaufstellung"));
}
/// Plays the running combat to its end through the host loop (no HUD shortcuts).
public IEnumerator FinishCombat(int maxFrames = 20000)
{
int frames = 0;
while (Runner.Current == LuneMatchRunner.Stage.Combat && frames++ < maxFrames)
{
Runner.Advance(.25);
if (frames % 20 == 0)
yield return null;
}
Runner.Advance(.02);
yield return Frames(2);
Sync();
}
/// Steps the combat for some simulated seconds.
public IEnumerator Fight(double seconds)
{
double done = 0.0;
while (done < seconds && Runner.Current == LuneMatchRunner.Stage.Combat)
{
Runner.Advance(.05);
done += .05;
}
yield return Frames(2);
Sync();
}
// ---------------------------------------------------------------- screenshots
public void Capture(string relative)
{
LunePortraits.Shared?.Flush();
Camera.Render();
Texture2D scene = LuneHudCapture.Read(_sceneTarget);
Texture2D overlay = LuneHudCapture.Read(Target);
Texture2D image = LuneHudCapture.Compose(scene, overlay);
LuneHudCapture.Save(image, Path.Combine(Root, relative));
Object.Destroy(scene);
Object.Destroy(overlay);
Object.Destroy(image);
}
/// Waits until portraits and the HUD have caught up, then captures.
public IEnumerator Shot(string relative)
{
Sync();
LunePortraits.Shared?.Flush();
yield return Frames(3);
Capture(relative);
}
}
}