using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using HotUpdate.Gameplay;
using HotUpdate.Net;
using HotUpdate.UI;
using UnityEditor;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
/// Play Mode check that real clicks and server results trigger the expected UI one-shots, and the SFX toggle mutes them.
public static class VerifyUiSfx
{
const string Endpoint = "http://127.0.0.1:18081";
static PrototypeFlow Flow => UnityEngine.Object.FindObjectOfType();
static UiSfx Player => UnityEngine.Object.FindObjectOfType();
static void Check(bool value, string message) { if (!value) throw new Exception(message); }
static T Get(string name) => (T)typeof(PrototypeFlow).GetField(name, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(Flow);
static void Set(string name, object value) => typeof(PrototypeFlow).GetField(name, BindingFlags.Instance | BindingFlags.NonPublic).SetValue(Flow, value);
static T Sfx(string name) => (T)typeof(UiSfx).GetField(name, BindingFlags.Instance | BindingFlags.NonPublic).GetValue(Player);
static async Task Until(Func condition, int seconds = 25)
{
var end = DateTime.UtcNow.AddSeconds(seconds);
while (!condition()) { Check(DateTime.UtcNow < end, "UI SFX timed out: " + Flow?.Page); await Task.Delay(100); }
}
static void Click(string name)
{
var b = UnityEngine.Object.FindObjectsOfType().Single(x => x.name == "Btn_" + name && x.gameObject.activeInHierarchy);
Check(b.interactable, "Disabled " + name); Canvas.ForceUpdateCanvases();
var rect = b.GetComponent();
var pointer = new PointerEventData(EventSystem.current) { position = RectTransformUtility.WorldToScreenPoint(null, rect.TransformPoint(rect.rect.center)), button = PointerEventData.InputButton.Left };
var hits = new List(); EventSystem.current.RaycastAll(pointer, hits);
Check(hits.Count > 0 && ExecuteEvents.GetEventHandler(hits[0].gameObject) == b.gameObject, "Blocked " + name);
ExecuteEvents.Execute(b.gameObject, pointer, ExecuteEvents.pointerClickHandler);
}
/// Clicks a button and returns the last UiSound it caused (null when nothing played).
static async Task Sound(string button, Func settled)
{
float before = Sfx("_lastAt");
Click(button); await Until(settled); await Task.Delay(250);
return Mathf.Approximately(Sfx("_lastAt"), before) ? (UiSound?)null : Sfx("_last");
}
public static async Task Play()
{
Check(EditorApplication.isPlaying, "Enter Play Mode first");
await Until(() => Flow?.Game != null && !Flow.IsBusy && Player != null);
Check(Player.Source != null && Enum.GetValues(typeof(UiSound)).Cast().All(s => Player.Clip(s) != null), "UiSfx prefab not loaded with clips");
SessionState.SetString("AIGame.UiSfx.original", JsonUtility.ToJson(Flow.Game.State));
SessionState.SetString("AIGame.UiSfx.originalEndpoint", Get("_httpBase"));
SessionState.SetBool("AIGame.UiSfx.sfx", PrototypeClientPrefs.Sfx);
PrototypeClientPrefs.Sfx = true;
AuthResponse account = null; string error = null; bool done = false;
string user = "uisfx_" + Guid.NewGuid().ToString("N");
Directory.CreateDirectory(Path.Combine(Application.dataPath, "../../Tools/output/ui_sfx_v2"));
File.WriteAllText(Path.Combine(Application.dataPath, "../../Tools/output/ui_sfx_v2/account.txt"), user);
Flow.StartCoroutine(AuthApi.Register(Endpoint, user, "isolated-ui-sfx-password", (r, e) => { account = r; error = e; done = true; }));
await Until(() => done);
Check(account != null, "Isolated register failed " + error);
var session = new SessionData { Pid = account.pid, Token = account.token, Username = user };
UiManager.Instance.Get(ConfirmDialogView.PrefabName).GetComponent().Hide();
Set("_session", new Func(() => session));
Set("_profile", new Func(() => session.Pid));
Set("_httpBase", Endpoint);
Set("_boundProfile", 0L);
Flow.Handle("Home");
await Until(() => Flow.Game != null && !Flow.IsBusy);
var log = new List();
void Expect(string step, UiSound? actual, UiSound? expected) { log.Add(step + "=" + (actual?.ToString() ?? "none")); Check(actual == expected, step + " played " + (actual?.ToString() ?? "nothing") + ", expected " + (expected?.ToString() ?? "nothing")); }
Expect("hud.settings", await Sound("Settings", () => Flow.Page == "UI_PrototypeSettings"), UiSound.Open);
Expect("settings.quality", await Sound("CycleQuality", () => true), UiSound.Toggle);
Click("CycleQuality"); Click("CycleQuality"); await Task.Delay(100);
Expect("settings.sfxOff", await Sound("ToggleSfx", () => !PrototypeClientPrefs.Sfx), UiSound.Toggle);
Expect("muted.close", await Sound("Close", () => Flow.Page == "Home"), null);
Expect("muted.settings", await Sound("Settings", () => Flow.Page == "UI_PrototypeSettings"), null);
Click("ToggleSfx"); await Until(() => PrototypeClientPrefs.Sfx);
Expect("settings.close", await Sound("Close", () => Flow.Page == "Home"), UiSound.Close);
Expect("hud.rewards", await Sound("Rewards", () => Flow.Page == "UI_PrototypeChest"), UiSound.Open);
Expect("chest.claimDaily", await Sound("ClaimDailyChest", () => !Flow.IsBusy && Flow.Game.State.chestDailyClaimed == 1), UiSound.Claim);
UiManager.Instance.Get(ConfirmDialogView.PrefabName).GetComponent().Hide();
Expect("chest.back", await Sound("Back", () => Flow.Page == "Home"), UiSound.Close);
return "PASS " + string.Join(", ", log);
}
public static async Task Cleanup()
{
var net = UnityEngine.Object.FindObjectOfType();
UiManager.Instance.Get(ConfirmDialogView.PrefabName).GetComponent().Hide();
PrototypeClientPrefs.Sfx = SessionState.GetBool("AIGame.UiSfx.sfx", true);
Set("_session", new Func(() => net.Session));
Set("_profile", new Func(() => net.Session?.Pid ?? 0));
Set("_httpBase", SessionState.GetString("AIGame.UiSfx.originalEndpoint", "http://127.0.0.1:8080"));
Set("_boundProfile", 0L);
Flow.Handle("Home");
await Until(() => Flow.Game != null && !Flow.IsBusy);
var original = JsonUtility.FromJson(SessionState.GetString("AIGame.UiSfx.original", ""));
Check(Flow.Game.State.coins == original.coins && Flow.Game.State.candy == original.candy && Flow.Game.State.shots == original.shots && Flow.Game.State.level == original.level, "Real account was changed by UI SFX test");
return "PASS cleanup: real account and SFX preference restored; checks used isolated :18081 only.";
}
}