using UnityEditor; using UnityEngine; using Ashwild.Building; using FishNet.Object; namespace Ashwild.EditorTools { /// /// One-shot migration for the move to server-spawned builds: every committed structure is now a real /// NetworkObject, so each buildable's BuiltPrefab needs a NetworkObject and a BuiltStructure on its /// root. Prefabs authored before that change have neither (or only BuiltStructure, which is now a /// NetworkBehaviour and cannot function alone), and a missing NetworkObject makes the prefab silently /// unspawnable — the exact failure that used to leave a placed chest deactivated. /// /// Safe to re-run: it only touches prefabs that are actually missing a component, and reports what it /// changed. Kept as an explicit menu action rather than an automatic postprocessor because it rewrites /// authored assets, which should never happen behind the designer's back. /// public static class BuiltPrefabMigrator { #region Menu /// /// Scans every BuildableData, ensures its BuiltPrefab carries NetworkObject + BuiltStructure, and /// asks FishNet to rescan its spawnable prefab collection afterwards. /// [MenuItem("Tools/Ashwild/Migrate Built Prefabs To Network Objects")] public static void Migrate() { string[] guids = AssetDatabase.FindAssets("t:BuildableData"); int migrated = 0; int alreadyFine = 0; int missingPrefab = 0; foreach (string guid in guids) { BuildableData buildable = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guid)); if (buildable == null) continue; if (buildable.BuiltPrefab == null) { Debug.LogWarning($"[BuiltPrefabMigrator] '{buildable.name}' has no BuiltPrefab — skipped.", buildable); missingPrefab++; continue; } if (EnsureNetworked(buildable.BuiltPrefab)) migrated++; else alreadyFine++; } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); RefreshNetworkPrefabRegistry(); Debug.Log($"[BuiltPrefabMigrator] Done — {migrated} prefab(s) migrated, {alreadyFine} already correct, {missingPrefab} buildable(s) without a BuiltPrefab."); } #endregion #region Internal Helpers /// /// Adds whatever the prefab root is missing and saves it. The NetworkObject goes on first so that /// BuiltStructure's RequireComponent is already satisfied when it is added. Returns true when the /// prefab was actually changed. /// private static bool EnsureNetworked(GameObject prefab) { string path = AssetDatabase.GetAssetPath(prefab); if (string.IsNullOrEmpty(path)) return false; GameObject root = PrefabUtility.LoadPrefabContents(path); bool changed = false; if (root.GetComponent() == null) { root.AddComponent(); changed = true; } if (root.GetComponent() == null) { root.AddComponent(); changed = true; } if (changed) { PrefabUtility.SaveAsPrefabAsset(root, path); Debug.Log($"[BuiltPrefabMigrator] Migrated '{path}'.", AssetDatabase.LoadAssetAtPath(path)); } PrefabUtility.UnloadPrefabContents(root); return changed; } /// /// Forces FishNet to rescan DefaultPrefabObjects so the migrated prefabs are spawnable. Invoked /// through the menu item because the generator API is internal to the FishNet assembly. /// private static void RefreshNetworkPrefabRegistry() { const string menu = "Tools/Fish-Networking/Utility/Refresh Default Prefabs"; if (!EditorApplication.ExecuteMenuItem(menu)) Debug.LogWarning($"[BuiltPrefabMigrator] Could not run '{menu}' — run it by hand so the built prefabs are registered as spawnable."); } #endregion } }