using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.IO; using Luneraria.Core; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; namespace Luneraria.UI.Tests { /// /// Interactive playtest of the real game (port of tools/luneraria/g3_playtest.gd). It never /// touches the match state: every action is an input a player gives, at pixel coordinates of /// the 1920×1080 picture (origin top left). A click reaches the HUD panel as pointer events /// and the field as LuneGame.ClickField, which ignores points on the HUD, because in the /// player both see the same mouse; game keys go through LuneGame.Key, the camera keys R, Q, /// E and O through the rig as in its own key handling. Pointer moves give hover (tooltips). /// A tester writes command batches and gets a screenshot back after each batch: /// /tmp/lune_play/cmd-N.txt lines: click X Y · move X Y · key NAME · wait SECONDS · shot NAME · quit /// /tmp/lune_play/out-N.png the screen after the batch; /tmp/lune_play/ok-N holds round and phase /// The log of all commands with round and phase goes to docs/port/logs/u3/playtest-log.txt. /// There is no main menu in the port yet, so the playtest starts in the match of the G1 scene /// (seed of the scene, 2; LUNE_PLAYTEST_SEED overrides). It ends with quit or after 30 idle minutes. /// Run: -testFilter "Luneraria.UI.Tests.HudPlaytest". /// [Explicit("Spieltest: wartet auf Befehlsdateien")] public sealed class HudPlaytest { const string Dir = "/tmp/lune_play/"; const string LogFile = "docs/port/logs/u3/playtest-log.txt"; HudSession _s; readonly List _log = new List(); float _started; bool _quit; [UnityTest, Timeout(int.MaxValue)] public IEnumerator Play() { Directory.CreateDirectory(Dir); foreach (string file in Directory.GetFiles(Dir)) File.Delete(file); int seed = int.TryParse(Environment.GetEnvironmentVariable("LUNE_PLAYTEST_SEED"), out int value) ? value : 2; _s = new HudSession(); yield return _s.Open(1920, 1080, seed: seed); _started = Time.realtimeSinceStartup; yield return new WaitForSecondsRealtime(.5f); _s.Capture(Dir + "out-0.png"); File.WriteAllText(Dir + "ok-0", Status()); int next = 1; float idle = 0f; while (!_quit && idle < 1800f) { string path = Dir + "cmd-" + next + ".txt"; if (!File.Exists(path)) { idle += Time.unscaledDeltaTime; yield return null; continue; } idle = 0f; foreach (string raw in File.ReadAllLines(path)) { string line = raw.Trim(); if (line.Length == 0 || line.StartsWith("#", StringComparison.Ordinal)) continue; Write(next, line); yield return Command(line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)); if (_quit) break; } yield return new WaitForSecondsRealtime(.35f); _s.Capture(Dir + "out-" + next + ".png"); File.WriteAllText(Dir + "ok-" + next, Status()); next++; } _s.Close(); Assert.IsTrue(_quit, "the playtest ended with quit"); } string Status() { LuneMatch match = _s.Match; string phase = match.Phase == Phase.Planning ? "PLANUNG" : match.Phase == Phase.Combat ? "KAMPF" : "ENDE"; return "round=" + match.RoundNumber + " phase=" + phase; } void Write(int index, string line) { _log.Add(string.Format(CultureInfo.InvariantCulture, "{0,7:0.0} s #{1} [{2}] {3}", Time.realtimeSinceStartup - _started, index, Status(), line)); string file = Path.Combine(HudSession.Root, LogFile); Directory.CreateDirectory(Path.GetDirectoryName(file)); File.WriteAllText(file, string.Join("\n", _log) + "\n"); } IEnumerator Command(string[] parts) { switch (parts[0]) { case "click": { Vector2 pixel = Pixel(parts); Vector2 at = _s.Hud.PixelToPanel(pixel); _s.MoveTo(at); yield return null; _s.Pointer(at, EventType.MouseDown); // The field sees the same press (LuneGame.HandleClicks); it ignores points on the HUD. _s.Game.ClickField(new Vector2(pixel.x, _s.Height - pixel.y)); yield return null; _s.Pointer(at, EventType.MouseUp); yield return null; break; } case "move": _s.MoveTo(_s.Hud.PixelToPanel(Pixel(parts))); yield return null; break; case "key": Key(parts[1].ToUpperInvariant()); yield return null; break; case "wait": yield return new WaitForSecondsRealtime(float.Parse(parts[1], CultureInfo.InvariantCulture)); break; case "shot": yield return null; _s.Capture(Dir + parts[1] + ".png"); break; case "quit": _quit = true; break; default: Debug.LogWarning("LUNE_PLAYTEST unknown command: " + string.Join(" ", parts)); break; } } static Vector2 Pixel(string[] parts) { return new Vector2(float.Parse(parts[1], CultureInfo.InvariantCulture), float.Parse(parts[2], CultureInfo.InvariantCulture)); } void Key(string name) { // Camera keys of the rig (LuneCameraRig.HandleInput), blocked while the options are open. if (name == "R" || name == "Q" || name == "E" || name == "O") { if (_s.Hud.Settings.Open) return; if (name == "R") _s.Rig.ResetRequested?.Invoke(); else if (name == "O") _s.Rig.Overview(); else _s.Rig.RotateStep(name == "Q" ? -1 : 1); return; } KeyCode code; if (name.Length == 1 && char.IsDigit(name[0])) code = KeyCode.Alpha0 + (name[0] - '0'); else if (name == "ESC") code = KeyCode.Escape; else code = (KeyCode)Enum.Parse(typeof(KeyCode), name, true); _s.Game.Key(code); } } }