using System; using System.IO; using System.Linq; using System.Collections.Generic; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; using UnityEngine.EventSystems; using DepthsOfGreed.HandbookUI; [InitializeOnLoad] public static class UINotesValidation { static UINotesValidation(){if(SessionState.GetBool("NotesValidation",false))EditorApplication.playModeStateChanged+=Ready;} public static void Validate(){EditorSceneManager.OpenScene("Assets/Scenes/Tim_Test_UI.unity");SessionState.SetBool("NotesValidation",true);EditorApplication.playModeStateChanged+=Ready;EditorApplication.EnterPlaymode();} static void Check(bool condition,string message){if(!condition)throw new Exception(message);} static void Ready(PlayModeStateChange state) { if(state!=PlayModeStateChange.EnteredPlayMode)return;SessionState.SetBool("NotesValidation",false);EditorApplication.playModeStateChanged-=Ready; int stage=0;double due=EditorApplication.timeSinceStartup+1;SupplyBasementController c=null;HandbookStatisticsController stats=null;SupplyContractTestBackend supply=null;UIOverviewTestBackend battle=null;BasementBattleOverview monitor=null;HandbookTowerCatalog catalog=null;float truckX=0;Camera main=null; EditorApplication.CallbackFunction run=null;run=()=> { if(EditorApplication.timeSinceStartup();main=Camera.main;catalog=AssetDatabase.LoadAssetAtPath("Assets/HandbookData/TestTurmkatalog.asset"); Check(c.roomView && c.roomView.roomCanvas.renderMode==RenderMode.WorldSpace && c.roomView.roomCanvas.transform.position.x>30,"Room exists outside game area"); Check(c.basementView.activeInHierarchy && !c.roomView.IsVisible && main.enabled,"Room active while hidden, main camera active"); var combat=new CombatDefinition{range=5,minimumRange=1.5f,rotation=RotationMode.Free,attackShape=AttackShape.Cone,coneAngle=60,forwardAngleOffset=90}; Check(AttackRangeGeometry.CoverageAngle(combat)==360,"Rotating cone covers full circle"); Check(!AttackRangeGeometry.CanReach(combat,Vector2.zero,Vector2.up,0) && AttackRangeGeometry.CanReach(combat,Vector2.zero,Vector2.up*3,0),"Minimum range respected"); combat.coverage=AttackCoverage.Sector;Check(AttackRangeGeometry.CanReach(combat,Vector2.zero,Vector2.up*3,0) && !AttackRangeGeometry.CanReach(combat,Vector2.zero,Vector2.right*3,0),"Cone angle and orientation"); combat.coverage=AttackCoverage.Circle;combat.unlimitedRange=true;Check(AttackRangeGeometry.CanReach(combat,Vector2.zero,Vector2.right*1000,0),"Unlimited range has no false maximum"); truckX=c.truck.anchoredPosition.x; typeof(SupplyBasementController).GetField("truckPauseUntil",System.Reflection.BindingFlags.Instance|System.Reflection.BindingFlags.NonPublic).SetValue(c,0f); stage=1;due=EditorApplication.timeSinceStartup+.8; } else if(stage==1) { Check(Mathf.Abs(c.truck.anchoredPosition.x-truckX)>.1f,"Truck preview continues outside basement"); var howitzer=catalog.towers.First(t=>t.id=="ammo_4");Check(howitzer.combat.minimumRange>0,"Howitzer minimum seeded"); var range=UnityEngine.Object.FindFirstObjectByType(FindObjectsInactive.Include); c.handbook.Open(0);Check(c.placement.Begin(howitzer,main.WorldToScreenPoint(new Vector3(3.5f,0,0))),"Begin range preview"); Check(range.gameObject.activeInHierarchy && range.minimumEdge.gameObject.activeSelf && range.outerEdge.loop,"Annular placement preview"); stage=2;due=EditorApplication.timeSinceStartup+.7; } else if(stage==2) { c.placement.Move(main.WorldToScreenPoint(new Vector3(3.5f,0,0))); Capture(main,c.handbook.GetComponentInParent(),c.GetComponentInParent(),"range-preview.png");c.placement.Cancel(); supply=new GameObject("TEST_ONLY_Supply").AddComponent(); supply.state.channels=new[]{new SupplyChannelState{kind=SupplyKind.Physical,storage=180,storageCapacity=300,buffer=60,bufferCapacity=100,consumedPerSecond=2}}; supply.state.towers=new[]{new TowerSupplyState{type=catalog.towers.First(t=>t.id=="ammo_0"),instanceId=811,magazine=12,magazineCapacity=30,consumedPerSecond=2}}; c.BindSupplyBackend(supply);c.handbook.Open(1);stats=UnityEngine.Object.FindFirstObjectByType();stats.Refresh(); Check(stats.channelRates[0].text.Contains("120 / min") && stats.channelTimes[0].text.Contains("00:30"),"Measured rates and reserve estimate"); Check(stats.towerNames[0].text.Contains("811") && stats.towerMagazines[0].text=="12 / 30","Per-instance magazine and rate"); stats.SetUnit(false);stats.Refresh();Check(stats.channelRates[0].text.Contains("2 / s"),"Rate unit conversion"); Check(HandbookStatisticsController.FormatRate(null,true)=="—" && HandbookStatisticsController.Estimate(60,0)=="kein Verbrauch","Unknown differs from zero"); stats.SetUnit(true);stats.Refresh();stage=3;due=EditorApplication.timeSinceStartup+.4; } else if(stage==3) { Capture(main,c.handbook.GetComponentInParent(),c.GetComponentInParent(),"statistics-preview.png"); battle=new GameObject("TEST_ONLY_CombatOverview").AddComponent(); battle.state.floorName="Ebene 2";battle.state.gates=new[]{new GateOverviewState{direction=GateDirection.North,health=100,maxHealth=100},new GateOverviewState{direction=GateDirection.East,health=80,maxHealth=100,isOpen=true},new GateOverviewState{direction=GateDirection.South,health=100,maxHealth=100},new GateOverviewState{direction=GateDirection.West,health=100,maxHealth=100}}; monitor=UnityEngine.Object.FindFirstObjectByType();monitor.BindCombatBackend(battle);monitor.SendMessage("Update"); Check(monitor.gateLabels[0].text.Contains("100") && monitor.gateLabels[1].text.Contains("offen"),"Gate state and open gate separate"); battle.state.gates[0].health=20;battle.state.gates[0].underAttack=true;battle.state.gates[0].hitSequence++; c.UseLift();stage=4;due=EditorApplication.timeSinceStartup+1.7; } else if(stage==4) { Check(c.IsInBasement && c.roomView.IsVisible && c.basementView.activeInHierarchy && c.roomView.roomCamera.enabled && main.enabled,"Only view changes, both rooms stay active"); Check(monitor.gateLabels[0].text.Contains("20") && monitor.gateLabels[0].text.Contains("!"),"Gate damage appears in basement"); Check(monitor.gateIcons[0].color.r>monitor.gateIcons[0].color.g,"Critical gate warning color"); var pointer=new PointerEventData(EventSystem.current){position=RectTransformUtility.WorldToScreenPoint(c.roomView.roomCamera,c.connectionButtons[0].transform.position)}; var hits=new List();EventSystem.current.RaycastAll(pointer,hits); Check(hits.Any(hit=>hit.gameObject==c.connectionButtons[0].gameObject),"World-space room buttons receive UI pointer hits"); c.roomView.roomCamera.aspect=2.5f;c.roomView.SendMessage("LateUpdate"); Check(((RectTransform)c.roomView.roomCanvas.transform).rect.width>=2699,"Room expands to wide screen"); Capture(c.roomView.roomCamera,null,c.GetComponentInParent(),"basement-monitor-preview.png"); c.UseLift();stage=5;due=EditorApplication.timeSinceStartup+1.7; } else { Check(!c.IsInBasement && !c.roomView.roomCamera.enabled && c.basementView.activeInHierarchy && main.enabled,"Return keeps room and main simulation active"); Debug.Log("UI_NOTES_VALIDATION_OK: separate active room, background preview, attack/minimum/sector ranges, statistics, unit conversion and gate alerts.");EditorApplication.update-=run;EditorApplication.Exit(0); } } catch(Exception e){EditorApplication.update-=run;Debug.LogException(e);EditorApplication.Exit(1);} };EditorApplication.update+=run; } static void Capture(Camera camera,Canvas book,Canvas lift,string filename) { if(SystemInfo.graphicsDeviceType==GraphicsDeviceType.Null)return; var target=new RenderTexture(1920,1080,24);target.Create();camera.targetTexture=target;camera.aspect=1920f/1080; var room=camera.GetComponentInParent();if(room)room.SendMessage("LateUpdate"); int oldMask=camera.cullingMask;camera.cullingMask|=1; if(book){book.renderMode=RenderMode.ScreenSpaceCamera;book.worldCamera=camera;book.planeDistance=1;book.sortingOrder=100;} lift.renderMode=RenderMode.ScreenSpaceCamera;lift.worldCamera=camera;lift.planeDistance=.5f;lift.sortingOrder=200; Canvas.ForceUpdateCanvases();RenderPipeline.SubmitRenderRequest(camera,new UniversalRenderPipeline.SingleCameraRequest{destination=target}); var previous=RenderTexture.active;RenderTexture.active=target;var texture=new Texture2D(1920,1080,TextureFormat.RGB24,false);texture.ReadPixels(new Rect(0,0,1920,1080),0,0);texture.Apply();RenderTexture.active=previous; var folder=Path.Combine(Path.GetDirectoryName(Application.dataPath),"Logs","HandbookValidation");Directory.CreateDirectory(folder);File.WriteAllBytes(Path.Combine(folder,filename),texture.EncodeToPNG()); UnityEngine.Object.DestroyImmediate(texture);camera.targetTexture=null;camera.cullingMask=oldMask;target.Release();UnityEngine.Object.DestroyImmediate(target);if(book)book.renderMode=RenderMode.ScreenSpaceOverlay;lift.renderMode=RenderMode.ScreenSpaceOverlay; } }