using System; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; [InitializeOnLoad] public static class HandbookSceneValidation { public static void ConfigureFeedbackAndValidate() { try { var scene = EditorSceneManager.OpenScene("Assets/Scenes/Tim_Test_UI.unity"); var controller = UnityEngine.Object.FindFirstObjectByType(FindObjectsInactive.Include); var serialized = new SerializedObject(controller); serialized.FindProperty("successfulPlacementDelay").floatValue = 0.5f; serialized.ApplyModifiedPropertiesWithoutUndo(); EditorSceneManager.SaveScene(scene); Validate(); } catch (Exception e) { Debug.LogException(e); EditorApplication.Exit(1); } } public static void SetupAndValidate() { try { const string dataFolder = "Assets/HandbookData"; const string towerFolder = dataFolder + "/TestTowers"; if (!AssetDatabase.IsValidFolder(dataFolder)) AssetDatabase.CreateFolder("Assets", "HandbookData"); if (!AssetDatabase.IsValidFolder(towerFolder)) AssetDatabase.CreateFolder(dataFolder, "TestTowers"); var names = new[] { new[] { "Gewehr", "Minigun", "Sniper", "Granatenwerfer", "Howitzer", "Flammenwerfer" }, new[] { "Laser-Piercer", "Bouncer", "Pusher", "Knocker", "Laser-Beam" }, new[] { "Falle", "Generator", "Staubsauger", "Spiegel" } }; var prefixes = new[] { "ammo", "energy", "special" }; var entries = new System.Collections.Generic.List(); for (int category = 0; category < names.Length; category++) for (int i = 0; i < names[category].Length; i++) { string path = towerFolder + "/" + prefixes[category] + "_" + i + "_" + names[category][i] + ".asset"; var entry = AssetDatabase.LoadAssetAtPath(path); if (!entry) { entry = ScriptableObject.CreateInstance(); entry.id = prefixes[category] + "_" + i; entry.displayName = names[category][i]; entry.category = (DepthsOfGreed.HandbookUI.HandbookTowerCategory)category; entry.energyCost = 100 + i * 25; entry.summary = category == 0 ? "Schaden: 20 · Schüsse/s: 2" : category == 1 ? "Energieverbrauch: 5/s" : new[] { "Falle · Testwirkung", "Energieerzeugung · Testwert: 5/s", "Sammelfunktion · Testeintrag", "Umlenkung · Testeintrag" }[i]; entry.description = entry.displayName + " – Testeintrag\n\nHier steht später eine kurze Erklärung der Funktion. Kosten und Werte sind vorläufige Testdaten.\n\nKlicken: zurück zur Baukarte"; AssetDatabase.CreateAsset(entry, path); } entries.Add(entry); } const string catalogPath = dataFolder + "/TestTurmkatalog.asset"; var catalog = AssetDatabase.LoadAssetAtPath(catalogPath); if (!catalog) { catalog = ScriptableObject.CreateInstance(); catalog.towers = entries.ToArray(); AssetDatabase.CreateAsset(catalog, catalogPath); } AssetDatabase.SaveAssets(); var scene = EditorSceneManager.OpenScene("Assets/Scenes/Tim_Test_UI.unity"); catalog = AssetDatabase.LoadAssetAtPath(catalogPath); if (!catalog) throw new Exception("Catalog asset could not be loaded after scene switch."); var controller = UnityEngine.Object.FindFirstObjectByType(FindObjectsInactive.Include); var serialized = new SerializedObject(controller); serialized.FindProperty("towerCatalog").objectReferenceValue = catalog; serialized.FindProperty("successfulPlacementDelay").floatValue = 1f; serialized.ApplyModifiedPropertiesWithoutUndo(); EditorSceneManager.SaveScene(scene); Debug.Log("HANDBOOK_DATA_SETUP_OK: 15 editable tower assets and catalog connected; delay set to 1 second."); Validate(); } catch (Exception exception) { Debug.LogException(exception); EditorApplication.Exit(1); } } static HandbookSceneValidation() { if (SessionState.GetBool("HandbookValidationPending", false)) EditorApplication.playModeStateChanged += CheckPlayMode; } public static void Validate() { try { var scene = EditorSceneManager.OpenScene("Assets/Scenes/Tim_Test_UI.unity"); var controllers = UnityEngine.Object.FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); if (controllers.Length != 1) throw new Exception("Expected exactly one handbook controller."); var serialized = new SerializedObject(controllers[0]); foreach (var field in new[] { "closedBook", "openBook", "bookButton", "closeButton", "closedTabsRoot", "openTabsRoot", "towerCatalog", "testTowerPrefab" }) if (!serialized.FindProperty(field).objectReferenceValue) throw new Exception("Missing reference: " + field); foreach (var field in new[] { "closedTabs", "openTabs", "pages" }) { var array = serialized.FindProperty(field); if (array.arraySize != 4) throw new Exception("Invalid array: " + field); for (int i = 0; i < array.arraySize; i++) if (!array.GetArrayElementAtIndex(i).objectReferenceValue) throw new Exception("Missing array reference: " + field); } foreach (var root in scene.GetRootGameObjects()) foreach (var transform in root.GetComponentsInChildren(true)) if (GameObjectUtility.GetMonoBehavioursWithMissingScriptCount(transform.gameObject) != 0) throw new Exception("Missing script on " + transform.name); Debug.Log("HANDBOOK_SCENE_VALIDATION_OK: Scene loaded; all controller references resolved; no missing scripts."); EditorApplication.playModeStateChanged += CheckPlayMode; SessionState.SetBool("HandbookValidationPending", true); EditorApplication.EnterPlaymode(); } catch (Exception exception) { Debug.LogException(exception); EditorApplication.Exit(1); } } private static void CheckPlayMode(PlayModeStateChange state) { if (state != PlayModeStateChange.EnteredPlayMode) return; SessionState.SetBool("HandbookValidationPending", false); EditorApplication.playModeStateChanged -= CheckPlayMode; try { var controller = UnityEngine.Object.FindFirstObjectByType(); controller.Open(0); var menu = UnityEngine.Object.FindFirstObjectByType(); if (!menu) throw new Exception("Build menu did not initialize."); foreach (var pair in new[] { (0, 6), (1, 5), (2, 4) }) { menu.SelectCategory(pair.Item1); var cards = menu.GetComponentsInChildren(); if (cards.Length != pair.Item2) throw new Exception("Wrong category card count."); foreach (var card in cards) if (card.Entry == null || !card.transform.Find("Front").gameObject.activeSelf || card.transform.Find("Back").gameObject.activeSelf) throw new Exception("Invalid initial card state."); } menu.SelectCategory(0); var first = menu.GetComponentsInChildren()[0]; menu.ToggleCard(first); double deadline = EditorApplication.timeSinceStartup + 0.6; EditorApplication.CallbackFunction check = null; check = () => { if (EditorApplication.timeSinceStartup < deadline) return; EditorApplication.update -= check; try { if (!first.transform.Find("Back").gameObject.activeSelf) throw new Exception("Card flip did not complete."); menu.SelectCategory(1); if (first.transform.Find("Back").gameObject.activeSelf) throw new Exception("Category did not reset card."); controller.Close(); controller.Open(0); if (menu.GetComponentsInChildren().Length != 6) throw new Exception("Closing did not restore the default build category."); controller.Close(); foreach (var card in menu.GetComponentsInChildren(true)) if (card.transform.Find("Back").gameObject.activeSelf) throw new Exception("Book close did not reset cards."); controller.Open(3); var indexMenu = UnityEngine.Object.FindFirstObjectByType(); if (!indexMenu || indexMenu.VisibleEntryCount != 15 || indexMenu.SelectedEntry != null) throw new Exception("Index initialization failed."); indexMenu.SelectEntry(0); if (!indexMenu.SelectedEntry) throw new Exception("Index selection failed."); indexMenu.ListScroll.verticalNormalizedPosition = 0f; controller.Open(0); controller.Open(3); if (indexMenu.SelectedEntry != null || indexMenu.ListScroll.verticalNormalizedPosition < 0.99f) throw new Exception("Index re-entry did not reset selection and scroll."); indexMenu.SelectFilter(2); if (indexMenu.VisibleEntryCount != 0 || indexMenu.SelectedEntry != null) throw new Exception("Enemy filter failed."); indexMenu.SelectFilter(1); if (indexMenu.VisibleEntryCount != 15) throw new Exception("Tower filter failed."); indexMenu.SelectEntry(1); controller.Close(); controller.Open(3); if (indexMenu.SelectedEntry != null || indexMenu.ListScroll.verticalNormalizedPosition < 0.99f) throw new Exception("Reopening index did not reset selection and scroll."); controller.Close(); var placement = controller.GetComponent(); var entry = first.Entry; if (!placement.enabled) throw new Exception("Placement initialization failed."); controller.Open(0); var validScreen = (Vector2)Camera.main.WorldToScreenPoint(new Vector3(3.5f, 0, 0)); if (!placement.Begin(entry, validScreen) || !placement.IsDragging) throw new Exception("Drag did not begin."); placement.Cancel(); if (placement.IsDragging || placement.TowerCount != 0) throw new Exception("Cancel created a tower."); if (placement.TryPlaceAt(entry, Vector3.zero, out _)) throw new Exception("Center must be invalid."); if (placement.TryPlaceAt(entry, new Vector3(3.1f, 0, 0), out _)) throw new Exception("Base crosses inner wall."); if (placement.TryPlaceAt(entry, new Vector3(3.9f, 0, 0), out _)) throw new Exception("Base crosses outer wall."); int initialEnergy = placement.Energy; if (!placement.Begin(entry, validScreen)) throw new Exception("Second drag did not begin."); placement.End(validScreen); if (placement.TowerCount != 1 || placement.IsDragging) throw new Exception("Drag release failed."); if (!placement.IsWaitingForReturn) throw new Exception("Successful placement did not start delay."); for (int i = 1; i < 8; i++) { float angle = i * Mathf.PI / 4f; if (!placement.TryPlaceAt(entry, new Vector3(Mathf.Cos(angle) * 3.5f, Mathf.Sin(angle) * 3.5f, 0), out string reason)) throw new Exception("Valid placement failed: " + reason); } if (placement.TowerCount != 8 || placement.Energy != initialEnergy - entry.energyCost * 8) throw new Exception("Wrong tower count or resource deduction."); if (placement.TryPlaceAt(entry, new Vector3(3.5f, 0, 0), out _)) throw new Exception("Overlap allowed."); float freeAngle = Mathf.PI / 8f; if (placement.TryPlaceAt(entry, new Vector3(Mathf.Cos(freeAngle) * 3.5f, Mathf.Sin(freeAngle) * 3.5f, 0), out string limitReason) || limitReason != "Turmlimit erreicht") throw new Exception("Tower limit failed."); double delayDeadline = EditorApplication.timeSinceStartup + 1.5; EditorApplication.CallbackFunction delayCheck = null; delayCheck = () => { if (EditorApplication.timeSinceStartup < delayDeadline) return; EditorApplication.update -= delayCheck; try { if (placement.IsWaitingForReturn) throw new Exception("Book delay never expired."); var group = menu.GetComponentInParent(); if (!group.blocksRaycasts) throw new Exception("Book input not restored after delay."); if (placement.Begin(entry, validScreen)) throw new Exception("Limit should prevent drag."); var toast = controller.transform.Find("FeedbackBox") ?? controller.transform.Find("Feedbackbox"); var message = controller.transform.Find("FeedbackMessage").GetComponent(); if (!toast || !toast.gameObject.activeSelf || message.text != "Turmlimit erreicht") throw new Exception("Limit notice did not show background and message."); double toastDeadline = EditorApplication.timeSinceStartup + 2.9; EditorApplication.CallbackFunction toastCheck = null; toastCheck = () => { if (EditorApplication.timeSinceStartup < toastDeadline) return; EditorApplication.update -= toastCheck; try { if (toast.gameObject.activeSelf || message.text != "") throw new Exception("Notice did not disappear."); controller.Close(); Debug.Log("HANDBOOK_RUNTIME_VALIDATION_OK: Category reset, 0.5-second placement delay, limit notice background and automatic dismissal passed; existing placement checks passed."); EditorApplication.Exit(0); } catch (Exception e) { Debug.LogException(e); EditorApplication.Exit(1); } }; EditorApplication.update += toastCheck; } catch (Exception e) { Debug.LogException(e); EditorApplication.Exit(1); } }; EditorApplication.update += delayCheck; } catch (Exception e) { Debug.LogException(e); EditorApplication.Exit(1); } }; EditorApplication.update += check; } catch (Exception e) { Debug.LogException(e); EditorApplication.Exit(1); } } }