127 lines
12 KiB
C#
127 lines
12 KiB
C#
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 TMPro;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
using UnityEngine.UI;
|
|
using UnityEngine.EventSystems;
|
|
|
|
public static class VerifyCamp
|
|
{
|
|
static PrototypeFlow Flow => UnityEngine.Object.FindObjectOfType<PrototypeFlow>();
|
|
static void Check(bool b,string message) {if(!b) throw new Exception(message);}
|
|
static T Get<T>(string field)=>(T)typeof(PrototypeFlow).GetField(field,BindingFlags.NonPublic|BindingFlags.Instance).GetValue(Flow);
|
|
static void Set(string field,object value)=>typeof(PrototypeFlow).GetField(field,BindingFlags.NonPublic|BindingFlags.Instance).SetValue(Flow,value);
|
|
static async Task Until(Func<bool> condition,int seconds=20)
|
|
{var end=DateTime.UtcNow.AddSeconds(seconds);while(!condition()){Check(DateTime.UtcNow<end,"Camp timed out at "+Flow?.Page);await Task.Delay(100);}}
|
|
static Button Button(string name)=>UnityEngine.Object.FindObjectsOfType<Button>().Single(b=>b.name=="Btn_"+name&&b.gameObject.activeInHierarchy);
|
|
static void Click(string name)
|
|
{
|
|
var b=Button(name);Check(b.interactable,"Disabled "+name);Canvas.ForceUpdateCanvases();var rect=b.GetComponent<RectTransform>();
|
|
var pointer=new PointerEventData(EventSystem.current){position=RectTransformUtility.WorldToScreenPoint(null,rect.TransformPoint(rect.rect.center)),button=PointerEventData.InputButton.Left};
|
|
var hits=new List<RaycastResult>();EventSystem.current.RaycastAll(pointer,hits);
|
|
Check(hits.Count>0&&ExecuteEvents.GetEventHandler<IPointerClickHandler>(hits[0].gameObject)==b.gameObject,"Camp click blocked "+name);
|
|
ExecuteEvents.Execute(b.gameObject,pointer,ExecuteEvents.pointerClickHandler);
|
|
}
|
|
static async Task Capture(string name)
|
|
{
|
|
await Task.Delay(150);
|
|
var type=AppDomain.CurrentDomain.GetAssemblies().Select(a=>a.GetType("Unity.Pipeline.Editor.Commands.Capture.CaptureCommands")).First(t=>t!=null);
|
|
type.GetMethod("CaptureGameView",BindingFlags.Static|BindingFlags.Public).Invoke(null,new object[]{1080,1920,null,"Screenshots/camp_"+name+".png",false,0,"screen"});
|
|
}
|
|
static async Task SyncHome()
|
|
{Flow.Handle("Home");Set("_needsSync",true);Flow.Handle("Home");await Until(()=>!Flow.IsBusy);await Task.Delay(150);}
|
|
public static string Assets()
|
|
{
|
|
foreach(string screen in new[]{"Camp","Build"})
|
|
{
|
|
var prefab=AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Bundles/UI/Prefab/UI_Prototype"+screen+".prefab");Check(prefab!=null,"Missing camp prefab");
|
|
Check(prefab.GetComponentsInChildren<TextMeshProUGUI>(true).All(t=>t.font!=null),"Missing camp font");
|
|
foreach(var i in prefab.GetComponentsInChildren<Image>(true).Where(i=>i.type==Image.Type.Sliced)) Check(i.sprite!=null&&i.sprite.border.x>0&&i.sprite.border.y>0,"Invalid camp slice");
|
|
}
|
|
for(int i=0;i<3;i++)
|
|
{
|
|
Check(AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Bundles/Scenes/Prototype/CampDecoration"+i+".prefab")!=null,"Missing decoration");
|
|
Check(AssetDatabase.LoadAssetAtPath<Sprite>("Assets/Bundles/UI/Texture/Prototype/proto_decor_"+i+".png")!=null,"Missing decoration portrait");
|
|
}
|
|
return "PASS two camp JSON prefabs, fonts/nine-slices and three authored decorations/portraits.";
|
|
}
|
|
public static async Task<string> Start()
|
|
{
|
|
await Until(()=>Flow?.Game!=null&&!Flow.IsBusy);
|
|
SessionState.SetString("AIGame.G1.original",JsonUtility.ToJson(Flow.Game.State));SessionState.SetString("AIGame.G1.originalEndpoint",Get<string>("_httpBase"));
|
|
AuthResponse account=null;string error=null;bool done=false;string user="g1live_camp_"+Guid.NewGuid().ToString("N");
|
|
Flow.StartCoroutine(AuthApi.Register("http://127.0.0.1:18081",user,"isolated-g1-test-password",(r,e)=>{account=r;error=e;done=true;}));
|
|
await Until(()=>done);Check(account!=null,"Camp register failed "+error);
|
|
File.WriteAllText(Path.Combine(Application.dataPath,"../../Tools/output/g1-live/account.txt"),user);
|
|
var session=new SessionData{Pid=account.pid,Token=account.token,Username=user};
|
|
Set("_session",new Func<SessionData>(()=>session));Set("_profile",new Func<long>(()=>session.Pid));Set("_httpBase","http://127.0.0.1:18081");Set("_boundProfile",0L);
|
|
Flow.Handle("Home");await Until(()=>!Flow.IsBusy);await Task.Delay(150);
|
|
Click("NavHome");await Until(()=>Flow.Page=="UI_PrototypeCamp");
|
|
Check(!Button("Construction").interactable&&!Button("Interact").interactable&&!Button("Training").interactable&&!Button("BallTree").interactable,"Fresh facilities unlocked too soon");await Capture("locked");
|
|
Click("Tent");await Task.Delay(100);Click("Ok");Click("Back");await Task.Delay(150);
|
|
Click("NavPinball");await Until(()=>Flow.Page=="UI_PrototypePinball");Click("Fire");await Until(()=>!Flow.IsBusy&&Flow.Game.State.shots==1);
|
|
Click("Back");await Task.Delay(150);Click("NavHome");await Until(()=>Flow.Page=="UI_PrototypeCamp");
|
|
Check(Button("Training").interactable&&!Button("BallTree").interactable,"Teaching training gate wrong");
|
|
Click("Training");await Until(()=>Flow.Page=="UI_PrototypePets");Check(Flow.Game.State.level==1,"Training doorway charged on open");
|
|
Click("Back");return "PASS real camp HUD entry, locked facilities, tent profile and first-shot training doorway without extra cost.";
|
|
}
|
|
public static async Task<string> Rest()
|
|
{
|
|
await SyncHome();Click("NavHome");await Until(()=>Flow.Page=="UI_PrototypeCamp");
|
|
Check(Button("BallTree").interactable&&Button("Interact").interactable&&!Button("Construction").interactable,"1-3 fixture unlock wrong");
|
|
int coins=Flow.Game.State.coins,balls=Flow.Game.State.balls,candy=Flow.Game.State.candy,tickets=Flow.Game.State.tickets;
|
|
Click("RestPet3");Click("Interact");await Until(()=>!Flow.IsBusy&&Flow.Game.State.campInteractions==1);
|
|
Check(Flow.Game.State.restPet==3&&Flow.Game.State.coins==coins&&Flow.Game.State.balls==balls&&Flow.Game.State.candy==candy&&Flow.Game.State.tickets==tickets,"Rest interaction awarded resources");
|
|
await Task.Delay(800);await Capture("rest");Click("BallTree");await Until(()=>Flow.Page=="UI_PrototypePinball");
|
|
Check(Flow.Game.State.balls==balls,"Ball tree opening duplicated production");Click("Back");return "PASS 1-3 rest gate, persisted electric partner greeting and no rewards; ball tree shares same inventory.";
|
|
}
|
|
public static async Task<string> BuildFlow()
|
|
{
|
|
await SyncHome();Click("NavHome");await Until(()=>Flow.Page=="UI_PrototypeCamp");Check(Button("Construction").interactable,"1-5 build still locked");await Capture("unlocked");
|
|
Click("Construction");await Until(()=>Flow.Page=="UI_PrototypeBuild");Check(Flow.Game.State.coins==1000,"Camp fixture wallet wrong");await Capture("catalogue");
|
|
Click("Decoration0");Click("CampSlot0");await Capture("preview");Check(Flow.Game.State.coins==1000&&Flow.Game.State.decorations[0]==0,"Preview purchased early");
|
|
Click("CancelPreview");Check(Flow.Game.State.coins==1000&&Flow.Game.State.campLayout.All(i=>i==-1)&&!Button("Place").interactable,"Cancel preview mutated authoritative state");
|
|
Click("CampSlot0");Click("Place");await Until(()=>!Flow.IsBusy&&Flow.Game.State.campLayout[0]==0);Check(Flow.Game.State.coins==880&&Flow.Game.State.decorations[0]==1,"Purchase mismatch");await Capture("placed");
|
|
Click("Decoration0");Click("CampSlot1");Click("Place");await Until(()=>!Flow.IsBusy&&Flow.Game.State.campLayout[1]==0);Check(Flow.Game.State.coins==880&&Flow.Game.State.campLayout[0]==-1,"Move charged or duplicated");
|
|
Click("CampSlot1");Click("Store");await Until(()=>!Flow.IsBusy&&Flow.Game.State.campLayout[1]==-1);Check(Flow.Game.State.decorations[0]==1&&Flow.Game.State.coins==880,"Store destroyed ownership or refunded");await Capture("warehouse");
|
|
Click("Decoration0");Click("CampSlot1");Click("Place");await Until(()=>!Flow.IsBusy&&Flow.Game.State.campLayout[1]==0);
|
|
Click("Decoration1");Click("CampSlot1");Check(!Button("Place").interactable,"Occupied preview allows overwrite");Click("CampSlot2");
|
|
var api=Get<PrototypeApi>("_api");var command=api.Command("camp.place",Flow.Game.State.revision,slot:2,item:1);PrototypeReply response=null;string error=null;
|
|
Flow.StartCoroutine(api.Send(command,(r,e)=>{response=r;error=e;}));await Until(()=>response!=null||error!=null);Check(response?.ok==true,"Purchase before lost ack failed");
|
|
var session=Get<Func<SessionData>>("_session")();string key="AIGame.G1.pending."+Uri.EscapeDataString("http://127.0.0.1:18081")+"."+session.Pid;
|
|
PlayerPrefs.SetString(key,JsonUtility.ToJson(command));PlayerPrefs.Save();Set("_needsSync",true);Flow.Handle("Home");await Until(()=>!Flow.IsBusy&&Get<PrototypeReply>("_reply").replayed);
|
|
Check(Flow.Game.State.coins==700&&Flow.Game.State.campLayout.SequenceEqual(new[]{-1,0,1})&&!api.HasPending,"Replayed purchase charged twice or lost layout");await Capture("recovery");Click("Ok");
|
|
Set("_boundProfile",0L);Flow.Handle("Home");await Until(()=>!Flow.IsBusy);await Task.Delay(150);Click("NavHome");await Until(()=>Flow.Page=="UI_PrototypeCamp");Click("Construction");await Until(()=>Flow.Page=="UI_PrototypeBuild");
|
|
Check(Flow.Game.State.coins==700&&Flow.Game.State.campLayout.SequenceEqual(new[]{-1,0,1})&&Flow.Game.State.restPet==3&&Flow.Game.State.campInteractions==1,"Camp session reattach lost state");await Capture("restored");
|
|
return "PASS live build: preview/cancel free, buy 120 once, move/store/warehouse free, occupied refusal, lost-ack purchase 180 once and layout/interaction restored after reattach.";
|
|
}
|
|
public static async Task<string> ReviewPreview()
|
|
{
|
|
await SyncHome();Click("NavHome");await Until(()=>Flow.Page=="UI_PrototypeCamp");Click("Construction");await Until(()=>Flow.Page=="UI_PrototypeBuild");
|
|
int coins=Flow.Game.State.coins;Click("Decoration2");Click("CampSlot0");Check(Button("Place").interactable,"Affordable preview disabled");await Capture("preview");
|
|
Click("CancelPreview");Check(Flow.Game.State.coins==coins&&!Button("Place").interactable,"Final preview cancellation changed wallet");
|
|
return "PASS final slot label spacing, affordable preview and cancellation without purchase.";
|
|
}
|
|
public static async Task<string> Insufficient()
|
|
{
|
|
await SyncHome();Click("NavHome");await Until(()=>Flow.Page=="UI_PrototypeCamp");Click("Construction");await Until(()=>Flow.Page=="UI_PrototypeBuild");
|
|
Click("Decoration2");Click("CampSlot0");Check(Flow.Game.State.coins==239&&!Button("Place").interactable,"Insufficient preview allowed purchase");
|
|
Check(UnityEngine.Object.FindObjectsOfType<TextMeshProUGUI>().Single(t=>t.name=="Txt_Preview").text.Contains("当前不足"),"Missing insufficient funds reason");await Capture("insufficient");
|
|
Click("CancelPreview");return "PASS insufficient wallet displays reason, disables confirmation and allows free cancel.";
|
|
}
|
|
public static async Task<string> Navigation()
|
|
{
|
|
await Until(()=>!Flow.IsBusy);Flow.Handle("Home");await Task.Delay(150);Click("NavHome");await Until(()=>Flow.Page=="UI_PrototypeCamp");
|
|
Check(Button("Construction").interactable,"Final HUD camp routing failed");Click("Back");await Until(()=>Flow.Page=="Home");
|
|
return "PASS original MainHUD.Home event routes to camp; Back returns to illustrated home.";
|
|
}
|
|
}
|