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(); static void Check(bool b,string message) { if(!b) throw new Exception(message); } static T Get(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 condition,int seconds=20) { var deadline=DateTime.UtcNow.AddSeconds(seconds); while(!condition()) {Check(DateTime.UtcNow().Single(x=>x.name=="Btn_"+name&&x.gameObject.activeInHierarchy); Check(b.interactable,"Disabled button "+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,"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("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("Assets/Bundles/UI/Texture/Prototype/proto_pet_"+i+".png")!=null,"Missing portrait "+i); } var capture=AssetDatabase.LoadAssetAtPath("Assets/Bundles/UI/Prefab/UI_PrototypeCapture.prefab"); Check(capture.GetComponentsInChildren(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 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("_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("_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("_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("_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 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(()=>session));Set("_profile",new Func(()=>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().Single(t=>t.name=="Txt_Pity"&&t.gameObject.activeInHierarchy); Check(!pity.text.Contains("最近获得")&&Get("_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 LostAcknowledgement() { Click("Home");await Task.Delay(150);await Until(()=>!Flow.IsBusy); var api=Get("_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>("_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("_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().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."; } }