Files
AIGame/AIClient/AgentScripts/VerifyInventory.cs
T

94 lines
11 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 VerifyInventory
{
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 name)=>(T)typeof(PrototypeFlow).GetField(name,BindingFlags.NonPublic|BindingFlags.Instance).GetValue(Flow);
static void Set(string name,object value)=>typeof(PrototypeFlow).GetField(name,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,"Inventory timed out: "+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,"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/inventory_"+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);}
static async Task ConfirmBuy(int item)
{
int count=Flow.Game.State.shopBought[item];Click("BuyGoods"+item);await Task.Delay(150);Click("Ok");await Until(()=>!Flow.IsBusy&&Flow.Game.State.shopBought[item]==count+1);Click("Ok");
}
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_inventory_"+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,"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);
Check(Flow.Game.State.schema==7&&Flow.Game.State.shopBought.All(n=>n==0),"Fresh stock wrong");Click("Bag");await Until(()=>Flow.Page=="UI_PrototypeBag");Check(!Button("UseTicket").interactable,"Empty bag allows use");await Capture("bag_empty");
Click("BagTab1");await Capture("materials_empty");Click("BagPet0");await Until(()=>Flow.Page=="UI_PrototypePets");Click("Back");await Task.Delay(150);Click("Bag");await Until(()=>Flow.Page=="UI_PrototypeBag");
Click("BagTab2");Check(!Button("BagDecor0").interactable,"Locked construction opened");Click("BagTab3");await Capture("events_empty");Click("EventToConsumables");await Task.Delay(150);Click("EarnTicket");await Until(()=>Flow.Page=="UI_PrototypePinball");Click("Back");await Task.Delay(150);
Click("NavShop");await Until(()=>Flow.Page=="UI_PrototypeShop");await Capture("free");Click("ShopTab1");Check(!Button("BuyGoods1").interactable&&!Button("BuyGoods2").interactable,"Locked gold goods purchasable");await Capture("locked");
Click("ShopTab0");await Task.Delay(150);int balls=Flow.Game.State.balls;Click("BuyGoods0");await Until(()=>!Flow.IsBusy&&Flow.Game.State.shopBought[0]==1);Check(Flow.Game.State.balls==balls+5&&!Button("BuyGoods0").interactable,"Free supply not exactly once");Click("Ok");await Capture("free_claimed");
Set("_boundProfile",0L);Flow.Handle("Home");await Until(()=>!Flow.IsBusy);await Task.Delay(150);Click("NavShop");await Until(()=>Flow.Page=="UI_PrototypeShop");Check(!Button("BuyGoods0").interactable&&Flow.Game.State.balls==balls+5,"Reattach repeated free supply");
return "PASS bag empty/material/decor/event states and use doorways; shop locks, daily free +5 once and stock survives session reattach.";
}
public static async Task<string> Purchases()
{
await SyncHome();Click("Coin");await Until(()=>Flow.Page=="UI_PrototypeShop");Check(Button("BuyGoods1").interactable&&Flow.Game.State.coins==1000,"Gold fixture missing");
Click("BuyGoods1");await Capture("confirm");Check(Flow.Game.State.coins==1000&&Flow.Game.State.candy==0&&Flow.Game.State.shopBought[1]==0,"Opening confirm spent currency");Click("Cancel");Check(Flow.Game.State.coins==1000,"Cancelled purchase charged");
await ConfirmBuy(1);Check(Flow.Game.State.coins==900&&Flow.Game.State.candy==20,"First sugar purchase wrong");
var api=Get<PrototypeApi>("_api");var command=api.Command("shop.buy",Flow.Game.State.revision,item:1,day:Flow.Game.State.dailyDay);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==800&&Flow.Game.State.candy==40&&Flow.Game.State.shopBought[1]==2&&!api.HasPending,"Shop replay duplicated price reward or stock");
var dialog=UiManager.Instance.Get(ConfirmDialogView.PrefabName).GetComponent<ConfirmDialogView>().CaptureState();Check(dialog.Visible&&dialog.Title=="补给已到账","Replayed receipt not shown");await Capture("recovery");Click("Ok");
await ConfirmBuy(1);Check(Flow.Game.State.coins==700&&Flow.Game.State.candy==60&&!Button("BuyGoods1").interactable,"Sugar daily stock not exhausted");
await ConfirmBuy(2);Check(Flow.Game.State.coins==400&&Flow.Game.State.tickets==1&&!Button("BuyGoods2").interactable,"Ticket purchase wrong");await Capture("sold_out");
Click("ShopToBag");await Until(()=>Flow.Page=="UI_PrototypeBag");Check(Button("UseTicket").interactable,"Bought ticket not available in bag");await Capture("bag_ticket");Click("UseTicket");await Until(()=>Flow.Page=="UI_PrototypeCapture");
Click("Capture");await Until(()=>!Flow.IsBusy&&Flow.Game.State.tickets==0&&Flow.Game.State.stages[2]==1);Click("Ok");await Until(()=>Flow.Page=="UI_PrototypePets");Click("Train");await Until(()=>!Flow.IsBusy&&Flow.Game.State.level==2);
Check(Flow.Game.State.candy==30&&Flow.Game.State.coins==400,"Purchased inventory did not feed existing capture/training");Click("Back");await Task.Delay(150);Click("Bag");await Until(()=>Flow.Page=="UI_PrototypeBag");Check(!Button("UseTicket").interactable,"Bag retained consumed ticket");Click("BagTab1");await Capture("materials");
Click("BagPet0");await Until(()=>Flow.Page=="UI_PrototypePets");Check(Get<int>("_selected")==0,"Fragment doorway lost selected species");
return "PASS confirm/cancel free, three sugar purchases exhaust limit, lost-ack +20 candy charges 100 once, ticket +1 usable in capture, candy usable in training, bag quantities and species doorway synchronized.";
}
public static async Task<string> Decorations()
{
await SyncHome();int coins=Flow.Game.State.coins;Click("NavShop");await Until(()=>Flow.Page=="UI_PrototypeShop");Click("ShopTab2");Check(Button("ShopDecor0").interactable,"Decorations gate wrong");await Capture("shop_decor");
Click("ShopDecor0");await Until(()=>Flow.Page=="UI_PrototypeBuild");Check(Get<int>("_decorItem")==0,"Shop decoration did not select item");Click("CampSlot0");Click("Place");await Until(()=>!Flow.IsBusy&&Flow.Game.State.campLayout[0]==0);Check(Flow.Game.State.coins==coins,"Owned decoration placement charged");
Click("Back");await Until(()=>Flow.Page=="UI_PrototypeCamp");Click("Back");await Task.Delay(150);Click("Bag");await Until(()=>Flow.Page=="UI_PrototypeBag");Click("BagTab2");await Capture("bag_decor");
Check(UnityEngine.Object.FindObjectsOfType<TextMeshProUGUI>().Single(t=>t.name=="Txt_BagDecorInfo0").text.Contains("位置1"),"Bag placement state stale");Click("BagDecor0");await Until(()=>Flow.Page=="UI_PrototypeBuild");Check(Get<int>("_decorItem")==0,"Bag decoration did not select item");
return "PASS shop and bag decoration doorways share authored build preview, owned placement is free and bag shows actual saved position.";
}
public static async Task<string> Insufficient()
{
await SyncHome();Click("Coin");await Until(()=>Flow.Page=="UI_PrototypeShop");Check(Flow.Game.State.coins==99&&!Button("BuyGoods1").interactable,"Insufficient gold purchase enabled");await Capture("insufficient");
return "PASS insufficient gold disables purchase with stock remaining.";
}
public static async Task<string> Refreshed()
{
await SyncHome();Click("NavShop");await Until(()=>Flow.Page=="UI_PrototypeShop");Check(Flow.Game.State.shopBought.All(n=>n==0)&&Button("BuyGoods0").interactable,"Shared day refresh failed");await Capture("refreshed");
int balls=Flow.Game.State.balls;Click("BuyGoods0");await Until(()=>!Flow.IsBusy&&Flow.Game.State.shopBought[0]==1);Check(Flow.Game.State.balls==balls+5,"Refreshed free claim amount wrong");Click("Ok");
return "PASS shared day stock refresh and newly available free supply gives five once.";
}
}