Add rank, friends, fishing, and camp treasure with T2 studio models and skill presentation.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
udx
2026-10-21 23:01:31 +08:00
co-authored by Cursor
parent 89e0fa246f
commit 079313fb06
677 changed files with 395371 additions and 9692 deletions
+18 -6
View File
@@ -114,12 +114,12 @@ public static class VerifyEnemyCombatFx
finally{PrototypeClientPrefs.Sfx=sfx;Object.Destroy(bank);}
}
/// <summary>Uses the real PrototypeFlow.Update/RefreshBattle with an isolated in-memory battle. No login or reward request.</summary>
public static async Task<string> PlayBattle()
public static async Task<string> PlayBattle(bool studio = false)
{
Check(EditorApplication.isPlaying,"Enter Play Mode first");
var battle=Battle(0);var config=battle.Config;
config.stages[0].enemyIds=new[]{0,1,2,3,4,0,1,2,4};config.stages[0].waveGap=.5f;
foreach(var enemy in config.enemies){enemy.hp=100;enemy.hpStep=0;enemy.speed=20;enemy.attack=1;enemy.cooldown=.5f;}
foreach(var enemy in config.enemies){enemy.hp=studio?240:100;enemy.hpStep=0;enemy.speed=20;enemy.attack=1;enemy.cooldown=.5f;}
config.pets[0].cooldown=.15f;battle.Heroes[0].Attack=60;
var arena=Object.Instantiate(AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Bundles/Scenes/Prototype/PrototypeArena.prefab"));
var ui=Object.Instantiate(AssetDatabase.LoadAssetAtPath<GameObject>("Assets/Bundles/UI/Prefab/UI_PrototypeBattle.prefab"));ui.name="UI_PrototypeBattle";ui.SetActive(false);
@@ -133,19 +133,30 @@ public static class VerifyEnemyCombatFx
var heroes=Enumerable.Range(0,pets).Select(i=>arena.transform.Find("Heroes/Hero"+i)).ToArray();
var slots=Enumerable.Range(0,9).Select(i=>arena.transform.Find("Enemies/Enemy"+i)).ToArray();
var types=slots.Select(s=>Enumerable.Range(0,5).Select(i=>s.Find("Type"+i)).ToArray()).ToArray();
if(studio)for(int s=0;s<slots.Length;s++)for(int t=0;t<5;t++)
Check(types[s][t].GetComponentsInChildren<SkinnedMeshRenderer>(true).All(r=>AssetDatabase.GetAssetPath(r.sharedMesh).Contains("/"+Actors[t]+"Studio/")),"Old enemy mesh in slot "+s+" type "+t);
set("_heroViews",heroes);set("_enemyViews",slots);set("_enemyTypes",types);
set("_heroAnimators",heroes.Select(h=>h.GetComponentInChildren<Animator>(true)).ToArray());set("_heroPulse",new float[pets]);set("_heroSkillPulse",new float[pets]);
var t1=heroes.Select(h=>h.Find("Visual")).ToArray();var t2=heroes.Select(h=>h.Find("VisualT2")).ToArray();
set("_heroT1Visuals",t1);set("_heroT2Visuals",t2);
set("_heroAnimators",t1.Select(h=>h==null?null:h.GetComponentInChildren<Animator>(true)).ToArray());
set("_heroT2Animators",t2.Select(h=>h==null?null:h.GetComponentInChildren<Animator>(true)).ToArray());set("_heroPulse",new float[pets]);set("_heroSkillPulse",new float[pets]);
set("_enemyAnimators",types.Select(ts=>ts.Select(t=>t.GetComponentInChildren<Animator>(true)).ToArray()).ToArray());set("_enemyModels",new PrototypeBattle.Enemy[9]);set("_enemyStrikes",new int[9]);
foreach(var name in new[]{"_projectiles","_skillViews","_shieldViews"})set(name,Enumerable.Range(0,pets).Select(i=>arena.transform.Find((name=="_projectiles"?"Projectiles/Shot":name=="_skillViews"?"SkillEffects/Skill":"SkillEffects/Shield")+i)).ToArray());
var feedback=arena.GetComponentInChildren<EnemyCombatPresentation>();Check(feedback!=null,"Arena feedback missing");set("_enemyFeedback",feedback);
host.SetActive(true);int last=0;bool captured=false;var until=DateTime.UtcNow.AddSeconds(8);
var walked=new HashSet<int>();var attacked=new HashSet<int>();
host.SetActive(true);int last=0;bool captured=false;var until=DateTime.UtcNow.AddSeconds(10);
while(!battle.Finished&&DateTime.UtcNow<until)
{
foreach(var e in battle.Enemies.Where(e=>e.Alive))
{
var anim=types[e.Index][e.Type].GetComponentInChildren<Animator>();var state=anim.GetCurrentAnimatorStateInfo(0);
if(state.IsName("Walk"))walked.Add(e.Type);if(state.IsName("Attack"))attacked.Add(e.Type);
}
if(!captured&&battle.Enemies.Any(e=>e.Type==4&&e.Strikes>0)&&feedback.PlayedEffects>last)
{
captured=true;set("_paused",true);await Task.Delay(35);
var camera=arena.GetComponentsInChildren<Camera>().First();var rt=new RenderTexture(900,1200,24);var tex=new Texture2D(900,1200,TextureFormat.RGB24,false);var previous=RenderTexture.active;
try{camera.targetTexture=rt;camera.Render();RenderTexture.active=rt;tex.ReadPixels(new Rect(0,0,900,1200),0,0);tex.Apply();File.WriteAllBytes(Path.Combine(Repo,"AIClient/Assets/Screenshots/enemy_combat_fx_battle.png"),tex.EncodeToPNG());}
try{camera.targetTexture=rt;camera.Render();RenderTexture.active=rt;tex.ReadPixels(new Rect(0,0,900,1200),0,0);tex.Apply();File.WriteAllBytes(Path.Combine(Repo,"AIClient/Assets/Screenshots/"+(studio?"enemy_studio_arena":"enemy_combat_fx_battle")+".png"),tex.EncodeToPNG());}
finally{camera.targetTexture=null;RenderTexture.active=previous;rt.Release();Object.Destroy(rt);Object.Destroy(tex);}
set("_paused",false);
}
@@ -153,7 +164,8 @@ public static class VerifyEnemyCombatFx
}
Check(captured&&feedback.PlayedEffects>0&&battle.Enemies.Select(e=>e.Type).Distinct().Count()==5,"Real flow did not play every type");
Check(battle.Enemies.GroupBy(e=>e.Type).All(g=>g.Sum(e=>e.Strikes)>0),"Some type never attacked");
Report("battle",new{status="pass",realFlow=true,isolatedInMemory=true,networkRewardRequests=false,allFiveTypes=true,effects=feedback.PlayedEffects,sounds=feedback.PlayedSounds,captured,battle.Defeated});
if(studio)Check(walked.Count==5&&attacked.Count==5,"Missing Studio Walk/Attack: "+string.Join(",",walked)+" / "+string.Join(",",attacked));
Report(studio?"studio_arena":"battle",new{status="pass",realFlow=true,isolatedInMemory=true,networkRewardRequests=false,allFiveTypes=true,studio,walked=walked.OrderBy(t=>t),attacked=attacked.OrderBy(t=>t),effects=feedback.PlayedEffects,sounds=feedback.PlayedSounds,captured,battle.Defeated});
return "PASS real PrototypeFlow battle: all five types attack, hit cues consumed, screenshot enemy_combat_fx_battle.png; no account writes";
}
finally{Object.Destroy(host);Object.Destroy(ui);Object.Destroy(arena);}