Files
AIGame/AIClient/AgentScripts/VerifyCollection.cs
T

121 lines
10 KiB
C#

using System;
using System.Collections.Generic;
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 VerifyCollection
{
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 deadline=DateTime.UtcNow.AddSeconds(seconds);
while(!condition()) {Check(DateTime.UtcNow<deadline,"Collection condition timed out at "+Flow?.Page);await Task.Delay(100);}
}
static void Click(string name)
{
var b=UnityEngine.Object.FindObjectsOfType<Button>().Single(x=>x.name=="Btn_"+name&&x.gameObject.activeInHierarchy);
Check(b.interactable,"Disabled button "+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,"Raycast blocked "+name);
ExecuteEvents.Execute(b.gameObject,pointer,ExecuteEvents.pointerClickHandler);
}
static void Capture(string name)
{
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/collection_"+name+".png",false,0,"screen"});
}
public static string Assets()
{
var arena=AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Bundles/Scenes/Prototype/PrototypeArena.prefab");
for(int i=0;i<10;i++)
{
Check(arena.transform.Find("Heroes/Hero"+i)!=null&&arena.transform.Find("Projectiles/Shot"+i)!=null,"Missing authored hero/projectile "+i);
Check(AssetDatabase.LoadAssetAtPath<Sprite>("Assets/Bundles/UI/Texture/Prototype/proto_pet_"+i+".png")!=null,"Missing portrait "+i);
}
var capture=AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Bundles/UI/Prefab/UI_PrototypeCapture.prefab");
Check(capture.GetComponentsInChildren<TextMeshProUGUI>(true).Count(t=>t.name.StartsWith("Txt_CaptureOdds"))==10,"Missing ten odds rows");
return "PASS ten authored partner models, projectiles, portraits and capture odds rows.";
}
public static async Task<string> PityAndBattle()
{
Check(Flow!=null,"Enter Play and attach isolated profile first");
if(Flow.Page!="Home") Click("Home");
Set("_needsSync",true);Flow.Handle("Home");await Until(()=>!Flow.IsBusy);
Check(Flow.Game.State.duplicateStreak==4&&Flow.Game.State.stages[4]==0,"Run isolated seed_collection_test.py first");
Flow.Handle("Pinball");await Until(()=>Flow.Page=="UI_PrototypePinball");Click("Capture");await Until(()=>Flow.Page=="UI_PrototypeCapture");
var reply=Get<PrototypeReply>("_reply");Check(reply.capture.pityReady&&reply.capture.entries[4].probability==1,"Water guarantee odds not shown");
Capture("pity");int tickets=Flow.Game.State.tickets;
Click("Capture");await Until(()=>!Flow.IsBusy&&Flow.Game.State.stages[4]==1);
Check(Flow.Game.State.tickets==tickets-1&&Get<PrototypeReply>("_reply").result.pity,"Pity draw not committed");
Capture("water_result");Click("Ok");await Until(()=>Flow.Page=="UI_PrototypePets");Capture("pets");
Click("Back");await Task.Delay(150);Flow.Handle("Pinball");await Until(()=>Flow.Page=="UI_PrototypePinball");Click("Capture");await Until(()=>Flow.Page=="UI_PrototypeCapture");
Check(Get<PrototypeReply>("_reply").capture.completed,"Complete pool did not pause pity");Capture("complete");
int shards=Flow.Game.State.shards.Sum(),count=Flow.Game.State.captureCount;tickets=Flow.Game.State.tickets;
Click("Capture");await Until(()=>!Flow.IsBusy&&Flow.Game.State.captureCount==count+1);
Check(Flow.Game.State.shards.Sum()==shards+20&&Flow.Game.State.tickets==tickets-1&&Flow.Game.State.duplicateStreak==0,"Complete-pool duplicate cost/shards incorrect");
Capture("duplicate_result");Click("Ok");await Until(()=>Flow.Page=="UI_PrototypePets");
Click("Pet3");Click("ToFormation");await Until(()=>Flow.Page=="UI_PrototypeFormation");Click("Slot1");await Until(()=>!Flow.IsBusy&&Flow.Game.State.formation[1]==3);
Click("Pet4");Click("Slot0");await Until(()=>!Flow.IsBusy&&Flow.Game.State.formation[0]==4);Capture("formation");
Click("ToStages");await Until(()=>Flow.Page=="UI_PrototypeStages");Click("Stage2");await Until(()=>Flow.Page=="UI_PrototypeFormation"&&!Flow.IsBusy);
SessionState.SetInt("Collection.coins",Flow.Game.State.coins);SessionState.SetInt("Collection.candy",Flow.Game.State.candy);SessionState.SetInt("Collection.balls",Flow.Game.State.balls);
Click("StartBattle");await Until(()=>Flow.Page=="UI_PrototypeBattle"&&Flow.Battle?.StageId==2);Click("Speed");await Until(()=>Flow.Battle.Time>12);
Check(Flow.Battle.Heroes.Any(h=>h.Pet==3)&&Flow.Battle.Heroes.Any(h=>h.Pet==4),"New pets missing in battle");Capture("battle");
return "PASS live collection: disclosed 100% missing-water pity, one ticket, five-owned catalogue, paused pity and random duplicate +20 shards, new water/electric team deployed and fighting Stage2.";
}
public static string FinishBattle()
{
if(Flow.Battle==null||!Flow.Battle.Finished||Flow.IsBusy||Flow.Game.State.clearedStages[1]!=1) return "RUNNING time="+Flow?.Battle?.Time;
Check(Flow.Battle.Won&&Flow.Game.State.coins==SessionState.GetInt("Collection.coins",0)+90&&Flow.Game.State.candy==SessionState.GetInt("Collection.candy",0)+50&&Flow.Game.State.balls==SessionState.GetInt("Collection.balls",0)+3,"New-team authoritative settlement mismatch");
Capture("result");return "PASS authored water/electric partners won Stage2; configured 90/50/3 reward committed once.";
}
public static string SettledBattle()
{
var receipt=Get<PrototypeReply>("_reply").lastBattleResult;
Check(receipt!=null&&receipt.stageId==2&&receipt.won&&receipt.firstClear&&Flow.Game.State.clearedStages[1]==1&&string.IsNullOrEmpty(Flow.Game.State.pendingBattle),"New-team victory receipt missing");
Check(Flow.Game.State.coins==SessionState.GetInt("Collection.coins",0)+90&&Flow.Game.State.candy==SessionState.GetInt("Collection.candy",0)+50&&Flow.Game.State.balls==SessionState.GetInt("Collection.balls",0)+3,"Settled new-team wallet mismatch");
return "PASS persisted water/electric Stage2 first-clear receipt and exact 90/50/3 wallet change after capture recovery.";
}
public static async Task<string> FreshTutorial()
{
AuthResponse account=null;string error=null;bool done=false;
string user="g1live_preview_"+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,"Preview register failed "+error);
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);
Flow.Handle("Pinball");await Until(()=>Flow.Page=="UI_PrototypePinball");Click("Fire");await Until(()=>!Flow.IsBusy&&Flow.Game.State.tickets==1);
Click("Capture");await Until(()=>Flow.Page=="UI_PrototypeCapture");
var pity=UnityEngine.Object.FindObjectsOfType<TextMeshProUGUI>().Single(t=>t.name=="Txt_Pity"&&t.gameObject.activeInHierarchy);
Check(!pity.text.Contains("最近获得")&&Get<PrototypeReply>("_reply").capture.entries[2].probability==1,"Fresh tutorial invents capture history");
Capture("tutorial");return "PASS fresh tutorial: first-shot ticket, 100% grass probability and no fabricated latest capture.";
}
public static async Task<string> LostAcknowledgement()
{
Click("Home");await Task.Delay(150);await Until(()=>!Flow.IsBusy);
var api=Get<PrototypeApi>("_api");var command=api.Command("capture",Flow.Game.State.revision);
int tickets=Flow.Game.State.tickets,shards=Flow.Game.State.shards.Sum(),count=Flow.Game.State.captureCount;
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,"Capture failed before simulated crash");
var session=Get<Func<SessionData>>("_session")();string key="AIGame.G1.pending."+Uri.EscapeDataString("http://127.0.0.1:18081")+"."+session.Pid;
UnityEngine.PlayerPrefs.SetString(key,JsonUtility.ToJson(command));UnityEngine.PlayerPrefs.Save();Set("_needsSync",true);Flow.Handle("Home");
await Until(()=>!Flow.IsBusy&&Get<PrototypeReply>("_reply").replayed);
Check(Flow.Game.State.tickets==tickets-1&&Flow.Game.State.captureCount==count+1&&Flow.Game.State.shards.Sum()==shards+20&&!api.HasPending,"Replayed capture redrew or double-debited");
var dialog=UiManager.Instance.Get(ConfirmDialogView.PrefabName).GetComponent<ConfirmDialogView>().CaptureState();Check(dialog.Visible&&dialog.Title=="获得进化碎片","Recovered draw result not surfaced");Capture("recovery");Click("Ok");
await Until(()=>Flow.Page=="UI_PrototypePets");return "PASS lost capture acknowledgement: same recorded result, one ticket and +20 shards, receipt shown after replay and journal cleared.";
}
}