diff --git a/Assets/Audio.meta b/Assets/Audio.meta new file mode 100644 index 0000000..46411c7 --- /dev/null +++ b/Assets/Audio.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f0fad760a5c49f94ea668f15bad0b624 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Audio/SFX.meta b/Assets/Audio/SFX.meta new file mode 100644 index 0000000..c255fb7 --- /dev/null +++ b/Assets/Audio/SFX.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 6255e3e53840c534a9b742ff3b0e50cf +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Audio/SFX/capture.wav b/Assets/Audio/SFX/capture.wav new file mode 100644 index 0000000..d3f1a86 Binary files /dev/null and b/Assets/Audio/SFX/capture.wav differ diff --git a/Assets/Audio/SFX/capture.wav.meta b/Assets/Audio/SFX/capture.wav.meta new file mode 100644 index 0000000..ddd7f80 --- /dev/null +++ b/Assets/Audio/SFX/capture.wav.meta @@ -0,0 +1,23 @@ +fileFormatVersion: 2 +guid: d1116462f5aa2ed4faa8c787e3daecda +AudioImporter: + externalObjects: {} + serializedVersion: 8 + defaultSettings: + serializedVersion: 2 + loadType: 0 + sampleRateSetting: 0 + sampleRateOverride: 44100 + compressionFormat: 1 + quality: 1 + conversionMode: 0 + preloadAudioData: 0 + platformSettingOverrides: {} + forceToMono: 0 + normalize: 1 + loadInBackground: 0 + ambisonic: 0 + 3D: 1 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Audio/SFX/check.wav b/Assets/Audio/SFX/check.wav new file mode 100644 index 0000000..57b444d Binary files /dev/null and b/Assets/Audio/SFX/check.wav differ diff --git a/Assets/Audio/SFX/check.wav.meta b/Assets/Audio/SFX/check.wav.meta new file mode 100644 index 0000000..0e70cd9 --- /dev/null +++ b/Assets/Audio/SFX/check.wav.meta @@ -0,0 +1,23 @@ +fileFormatVersion: 2 +guid: e23a5aff8454f13418bf5946c74c1b95 +AudioImporter: + externalObjects: {} + serializedVersion: 8 + defaultSettings: + serializedVersion: 2 + loadType: 0 + sampleRateSetting: 0 + sampleRateOverride: 44100 + compressionFormat: 1 + quality: 1 + conversionMode: 0 + preloadAudioData: 0 + platformSettingOverrides: {} + forceToMono: 0 + normalize: 1 + loadInBackground: 0 + ambisonic: 0 + 3D: 1 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Audio/SFX/checkmate.wav b/Assets/Audio/SFX/checkmate.wav new file mode 100644 index 0000000..9c9969e Binary files /dev/null and b/Assets/Audio/SFX/checkmate.wav differ diff --git a/Assets/Audio/SFX/checkmate.wav.meta b/Assets/Audio/SFX/checkmate.wav.meta new file mode 100644 index 0000000..dcc1948 --- /dev/null +++ b/Assets/Audio/SFX/checkmate.wav.meta @@ -0,0 +1,23 @@ +fileFormatVersion: 2 +guid: 82487001f2a9b4443a0d2a2ca1c05c41 +AudioImporter: + externalObjects: {} + serializedVersion: 8 + defaultSettings: + serializedVersion: 2 + loadType: 0 + sampleRateSetting: 0 + sampleRateOverride: 44100 + compressionFormat: 1 + quality: 1 + conversionMode: 0 + preloadAudioData: 0 + platformSettingOverrides: {} + forceToMono: 0 + normalize: 1 + loadInBackground: 0 + ambisonic: 0 + 3D: 1 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Audio/SFX/invalid_move.wav b/Assets/Audio/SFX/invalid_move.wav new file mode 100644 index 0000000..3bd9e75 Binary files /dev/null and b/Assets/Audio/SFX/invalid_move.wav differ diff --git a/Assets/Audio/SFX/invalid_move.wav.meta b/Assets/Audio/SFX/invalid_move.wav.meta new file mode 100644 index 0000000..fa5e51c --- /dev/null +++ b/Assets/Audio/SFX/invalid_move.wav.meta @@ -0,0 +1,23 @@ +fileFormatVersion: 2 +guid: 52b4cb0754a08f844ab08975a263ef77 +AudioImporter: + externalObjects: {} + serializedVersion: 8 + defaultSettings: + serializedVersion: 2 + loadType: 0 + sampleRateSetting: 0 + sampleRateOverride: 44100 + compressionFormat: 1 + quality: 1 + conversionMode: 0 + preloadAudioData: 0 + platformSettingOverrides: {} + forceToMono: 0 + normalize: 1 + loadInBackground: 0 + ambisonic: 0 + 3D: 1 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Audio/SFX/move.wav b/Assets/Audio/SFX/move.wav new file mode 100644 index 0000000..e7773a5 Binary files /dev/null and b/Assets/Audio/SFX/move.wav differ diff --git a/Assets/Audio/SFX/move.wav.meta b/Assets/Audio/SFX/move.wav.meta new file mode 100644 index 0000000..b30c42b --- /dev/null +++ b/Assets/Audio/SFX/move.wav.meta @@ -0,0 +1,23 @@ +fileFormatVersion: 2 +guid: 294db75531d2c664da0b7cd6bc38c9de +AudioImporter: + externalObjects: {} + serializedVersion: 8 + defaultSettings: + serializedVersion: 2 + loadType: 0 + sampleRateSetting: 0 + sampleRateOverride: 44100 + compressionFormat: 1 + quality: 1 + conversionMode: 0 + preloadAudioData: 0 + platformSettingOverrides: {} + forceToMono: 0 + normalize: 1 + loadInBackground: 0 + ambisonic: 0 + 3D: 1 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Audio/SFX/promotion.wav b/Assets/Audio/SFX/promotion.wav new file mode 100644 index 0000000..db08ca8 Binary files /dev/null and b/Assets/Audio/SFX/promotion.wav differ diff --git a/Assets/Audio/SFX/promotion.wav.meta b/Assets/Audio/SFX/promotion.wav.meta new file mode 100644 index 0000000..e69b363 --- /dev/null +++ b/Assets/Audio/SFX/promotion.wav.meta @@ -0,0 +1,23 @@ +fileFormatVersion: 2 +guid: a5ac5d35758bfc741b5602b80736c4e8 +AudioImporter: + externalObjects: {} + serializedVersion: 8 + defaultSettings: + serializedVersion: 2 + loadType: 0 + sampleRateSetting: 0 + sampleRateOverride: 44100 + compressionFormat: 1 + quality: 1 + conversionMode: 0 + preloadAudioData: 0 + platformSettingOverrides: {} + forceToMono: 0 + normalize: 1 + loadInBackground: 0 + ambisonic: 0 + 3D: 1 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Editor/AudioClipAssigner.cs b/Assets/Editor/AudioClipAssigner.cs new file mode 100644 index 0000000..0cf2676 --- /dev/null +++ b/Assets/Editor/AudioClipAssigner.cs @@ -0,0 +1,87 @@ +using System.Linq; +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; +using UnityEngine.SceneManagement; +using AjedrezPurgatorio.Audio; + +namespace AjedrezPurgatorio.EditorTools +{ + /// + /// Asigna los SFX placeholder (Assets/Audio/SFX/*.wav) a todos los + /// AudioManager de las escenas y guarda las escenas modificadas. + /// Ejecutar con: + /// Unity -batchmode -projectPath . -executeMethod + /// AjedrezPurgatorio.EditorTools.AudioClipAssigner.AssignPlaceholderClipsToAllScenes + /// + public static class AudioClipAssigner + { + private static readonly (string field, string file)[] Bindings = + { + ("_moveSFX", "move"), + ("_captureSFX", "capture"), + ("_checkSFX", "check"), + ("_checkmateSFX", "checkmate"), + ("_promotionSFX", "promotion"), + ("_invalidMoveSFX","invalid_move"), + }; + + private static readonly string[] SceneNames = + { + "MainMenu", "Chapter1", "Chapter2", "Chapter3", + }; + + public static void AssignPlaceholderClipsToAllScenes() + { + int assignedTotal = 0; + + try + { + foreach (string sceneName in SceneNames) + { + Scene scene = EditorSceneManager.OpenScene( + $"Assets/Scenes/{sceneName}.unity", OpenSceneMode.Single); + + foreach (AudioManager am in Object.FindObjectsByType( + FindObjectsInactive.Include, FindObjectsSortMode.None)) + { + var so = new SerializedObject(am); + int assigned = 0; + + foreach (var (field, file) in Bindings) + { + SerializedProperty prop = so.FindProperty(field); + var clip = AssetDatabase.LoadAssetAtPath( + $"Assets/Audio/SFX/{file}.wav"); + + if (prop == null || clip == null || prop.objectReferenceValue != null) + continue; + + prop.objectReferenceValue = clip; + assigned++; + } + + if (assigned > 0) + { + so.ApplyModifiedPropertiesWithoutUndo(); + EditorSceneManager.MarkSceneDirty(scene); + Debug.Log($"[AudioClipAssigner] '{sceneName}': {assigned} clips asignados a AudioManager."); + } + + assignedTotal += assigned; + } + + EditorSceneManager.SaveScene(scene); + } + + Debug.Log($"[AudioClipAssigner] COMPLETO: {assignedTotal} referencias asignadas."); + EditorApplication.Exit(0); + } + catch (System.Exception ex) + { + Debug.LogError($"[AudioClipAssigner] FALLÓ: {ex}"); + EditorApplication.Exit(1); + } + } + } +} diff --git a/Assets/Editor/AudioClipAssigner.cs.meta b/Assets/Editor/AudioClipAssigner.cs.meta new file mode 100644 index 0000000..3334f5a --- /dev/null +++ b/Assets/Editor/AudioClipAssigner.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8cfc3f0fcd46dc94c86a1fb4f5bca113 \ No newline at end of file diff --git a/Assets/Editor/DiagAssign.cs b/Assets/Editor/DiagAssign.cs new file mode 100644 index 0000000..c9be381 --- /dev/null +++ b/Assets/Editor/DiagAssign.cs @@ -0,0 +1,29 @@ +using System.Reflection; +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; + +public static class DiagAssign2 +{ + public static void Run() + { + var db = AssetDatabase.LoadAssetAtPath("Assets/Game/Data/Resources/SpeakerDatabase.asset"); + + var scene = EditorSceneManager.OpenScene("Assets/Scenes/Chapter1.unity", OpenSceneMode.Single); + var ui = Object.FindFirstObjectByType(FindObjectsInactive.Include); + + var field = typeof(DialogueUI).GetField("_speakerDatabase", BindingFlags.NonPublic | BindingFlags.Instance); + field.SetValue(ui, db); + Debug.Log($"[Diag] tras reflexion={field.GetValue(ui) != null}"); + EditorUtility.SetDirty(ui); + + EditorSceneManager.MarkSceneDirty(scene); + bool ok = EditorSceneManager.SaveScene(scene); + Debug.Log($"[Diag] SaveScene ok={ok}"); + + var scene2 = EditorSceneManager.OpenScene("Assets/Scenes/Chapter1.unity", OpenSceneMode.Single); + var ui2 = Object.FindFirstObjectByType(FindObjectsInactive.Include); + Debug.Log($"[Diag] tras recargar={field.GetValue(ui2) != null}"); + EditorApplication.Exit(0); + } +} diff --git a/Assets/Editor/DiagAssign.cs.meta b/Assets/Editor/DiagAssign.cs.meta new file mode 100644 index 0000000..c29d5ee --- /dev/null +++ b/Assets/Editor/DiagAssign.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6b27ce28b927f4e4d91a89cd4ef5deda \ No newline at end of file diff --git a/Assets/Editor/PurgatoryCanvasMaintenance.cs b/Assets/Editor/PurgatoryCanvasMaintenance.cs new file mode 100644 index 0000000..f0280f9 --- /dev/null +++ b/Assets/Editor/PurgatoryCanvasMaintenance.cs @@ -0,0 +1,469 @@ +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; +using UnityEngine.SceneManagement; +using AjedrezPurgatorio.UI; + +namespace AjedrezPurgatorio.EditorTools +{ + /// + /// Herramientas de mantenimiento para las escenas de capítulo: + /// 1. Elimina jerarquías de UI duplicadas (sets completos creados por corridas + /// repetidas de los scripts de setup bajo PurgatoryCanvas). + /// 2. Copia los paneles finales (Victory/Defeat/Memorial) desde Chapter3 a la escena activa. + /// + /// Uso: abrir la escena objetivo -> ejecutar menú -> revisar en jerarquía -> guardar (Ctrl+S). + /// Ninguna operación guarda la escena automáticamente. + /// + public static class PurgatoryCanvasMaintenance + { + private const string Chapter3ScenePath = "Assets/Scenes/Chapter3.unity"; + private const string MenuRoot = "Tools/Purgatory UI/"; + + // ------------------------------------------------------------------ + // 1) Deduplicación de paneles + // ------------------------------------------------------------------ + + [MenuItem(MenuRoot + "Deduplicate Panels (Active Scene)")] + public static void DeduplicateActiveScene() + { + Scene scene = EditorSceneManager.GetActiveScene(); + if (!scene.IsValid() || string.IsNullOrEmpty(scene.path)) + { + EditorUtility.DisplayDialog("Deduplicate Panels", + "Abre una escena guardada antes de ejecutar la deduplicación.", "OK"); + return; + } + + HashSet protectedAncestorIds = CollectProtectedAncestorIds(scene); + List groups = CollectDuplicateGroups(scene); + + if (groups.Count == 0) + { + EditorUtility.DisplayDialog("Deduplicate Panels", + $"No se encontraron grupos duplicados en '{scene.name}'.", "OK"); + return; + } + + // Planificar sin destruir nada todavía. + List toDelete = new List(); + StringBuilder summary = new StringBuilder(); + int keptTotal = 0; + + foreach (DuplicateGroup group in groups.OrderByDescending(g => g.Depth)) + { + List alive = group.Members.Where(t => t != null).ToList(); + if (alive.Count < 2) + continue; + + List survivors = + alive.Where(t => IsProtected(t.gameObject, protectedAncestorIds)).ToList(); + + if (survivors.Count == 0) + { + survivors.Add(alive.OrderByDescending(t => t.GetSiblingIndex()).First()); + } + + foreach (Transform t in alive.Where(t => !survivors.Contains(t))) + toDelete.Add(t.gameObject); + + keptTotal += survivors.Count; + summary.AppendLine( + $"{group.DisplayName}: conservar {survivors.Count}, eliminar {alive.Count - survivors.Count}"); + } + + if (toDelete.Count == 0) + { + EditorUtility.DisplayDialog("Deduplicate Panels", + "Hay grupos duplicados pero todos sus miembros están protegidos por referencias serializadas. " + + "No se eliminará nada.", "OK"); + return; + } + + string preview = summary.ToString(); + if (preview.Length > 1200) + preview = preview.Substring(0, 1200) + "\n..."; + + bool confirmed = EditorUtility.DisplayDialog( + "Deduplicate Panels", + $"Escena '{scene.name}': se eliminarán {toDelete.Count} objetos duplicados " + + $"(quedan {keptTotal} copias funcionales).\n\n{preview}\n\n" + + "La operación es reversible con Ctrl+Z y NO guarda la escena.", + "Eliminar duplicados", "Cancelar"); + + if (!confirmed) + return; + + int removed = 0; + foreach (GameObject go in toDelete) + { + if (go == null) + continue; // ya murió junto a un ancestro eliminado + + Undo.DestroyObjectImmediate(go); + removed++; + } + + EditorSceneManager.MarkSceneDirty(scene); + Debug.Log($"[PurgatoryCanvasMaintenance] Deduplicación completa en '{scene.name}': " + + $"{removed} objetos eliminados. Revisa la jerarquía y guarda la escena."); + + EditorUtility.DisplayDialog("Deduplicate Panels", + $"{removed} objetos eliminados en '{scene.name}'.\n\n" + + "Revisa la jerarquía y guarda la escena (Ctrl+S).", "OK"); + } + + private sealed class DuplicateGroup + { + public string DisplayName; + public int Depth; + public List Members = new List(); + } + + /// + /// Recolecta los instance IDs de todos los objetos referenciados por campos + /// serializados de la escena, incluyendo todos sus ancestros. Así se conserva + /// la copia exacta a la que apuntan campos como PurgatoryManager._offerUI, + /// aunque la referencia apunte a un hijo profundo del panel. + /// + private static HashSet CollectProtectedAncestorIds(Scene scene) + { + HashSet protectedIds = new HashSet(); + + foreach (GameObject root in scene.GetRootGameObjects()) + { + foreach (Component comp in root.GetComponentsInChildren(true)) + { + if (comp == null) + continue; // script faltante + + TryCollectReferences(comp, protectedIds); + } + } + + HashSet ancestors = new HashSet(); + foreach (int id in protectedIds) + { + UnityEngine.Object obj = EditorUtility.InstanceIDToObject(id); + Transform t = null; + + if (obj is Component component && component != null) + t = component.transform; + else if (obj is GameObject go && go != null) + t = go.transform; + + while (t != null) + { + ancestors.Add(t.gameObject.GetInstanceID()); + t = t.parent; + } + } + + return ancestors; + } + + private static void TryCollectReferences(Component comp, HashSet into) + { + try + { + SerializedObject so = new SerializedObject(comp); + SerializedProperty prop = so.GetIterator(); + bool enterChildren = true; + + while (prop.NextVisible(enterChildren)) + { + enterChildren = true; + + if (prop.propertyType == SerializedPropertyType.ObjectReference && + prop.objectReferenceValue != null) + { + into.Add(prop.objectReferenceValue.GetInstanceID()); + } + } + } + catch (System.Exception e) + { + Debug.LogWarning($"[PurgatoryCanvasMaintenance] No se pudo leer {comp.GetType().Name}: {e.Message}"); + } + } + + /// + /// Agrupa RectTransform hermanos con nombre idéntico. Solo considera objetos + /// con padre (no raíces) para acotar el riesgo al UI generado por setup. + /// + private static List CollectDuplicateGroups(Scene scene) + { + Dictionary byKey = new Dictionary(); + + foreach (GameObject root in scene.GetRootGameObjects()) + { + foreach (RectTransform rt in root.GetComponentsInChildren(true)) + { + if (rt == null || rt.parent == null) + continue; + + Transform parent = rt.parent; + string key = parent.GetInstanceID() + "|" + rt.name; + + if (!byKey.TryGetValue(key, out DuplicateGroup group)) + { + group = new DuplicateGroup + { + DisplayName = GetPath(rt), + Depth = CountDepth(rt) + }; + byKey[key] = group; + } + + group.Members.Add(rt); + } + } + + return byKey.Values.Where(g => g.Members.Count > 1).ToList(); + } + + private static bool IsProtected(GameObject go, HashSet protectedAncestorIds) + { + return protectedAncestorIds.Contains(go.GetInstanceID()); + } + + private static int CountDepth(Transform t) + { + int depth = 0; + while (t.parent != null) + { + depth++; + t = t.parent; + } + return depth; + } + + private static string GetPath(Transform t) + { + var parts = new List(); + while (t != null) + { + parts.Insert(0, t.name); + t = t.parent; + } + return string.Join("/", parts); + } + + // ------------------------------------------------------------------ + // 2) Copia de paneles finales desde Chapter3 + // ------------------------------------------------------------------ + + [MenuItem(MenuRoot + "Copy End Screens from Chapter3 (Active Scene)")] + public static void CopyEndScreensFromChapter3() + { + Scene active = EditorSceneManager.GetActiveScene(); + if (!active.IsValid() || string.IsNullOrEmpty(active.path)) + { + EditorUtility.DisplayDialog("Copy End Screens", + "Abre una escena guardada (Chapter1 o Chapter2) primero.", "OK"); + return; + } + + if (active.path == Chapter3ScenePath) + { + EditorUtility.DisplayDialog("Copy End Screens", + "La escena activa ES Chapter3. Abre Chapter1 o Chapter2 como destino.", "OK"); + return; + } + + GameObject targetCanvas = active.GetRootGameObjects() + .FirstOrDefault(go => go.name == "PurgatoryCanvas"); + + if (targetCanvas == null) + { + EditorUtility.DisplayDialog("Copy End Screens", + $"No se encontró 'PurgatoryCanvas' como raíz de '{active.name}'.", "OK"); + return; + } + + Scene chapter3 = default; + int copied = 0; + StringBuilder report = new StringBuilder(); + + try + { + chapter3 = EditorSceneManager.OpenScene(Chapter3ScenePath, OpenSceneMode.Additive); + + foreach (GameObject src in EnumerateSourcePanels(chapter3)) + { + if (targetCanvas.transform.Find(src.name) != null) + { + report.AppendLine($"{src.name}: ya existe, omitido."); + continue; + } + + GameObject copy = Object.Instantiate(src); + SceneManager.MoveGameObjectToScene(copy, active); + copy.transform.SetParent(targetCanvas.transform, false); + copy.name = src.name; + Undo.RegisterCreatedObjectUndo(copy, "Copy End Screen Panel"); + + copied++; + report.AppendLine($"{src.name}: copiado."); + } + } + finally + { + if (chapter3.IsValid()) + EditorSceneManager.CloseScene(chapter3, true); + } + + EditorSceneManager.MarkSceneDirty(active); + Debug.Log($"[PurgatoryCanvasMaintenance] Copy End Screens en '{active.name}': " + + $"{copied} paneles copiados desde Chapter3. Revisa y guarda la escena."); + + EditorUtility.DisplayDialog("Copy End Screens", + $"'{active.name}': {copied} paneles copiados.\n\n{report}\n" + + "Revisa la jerarquía y guarda la escena (Ctrl+S).", "OK"); + } + + private static IEnumerable EnumerateSourcePanels(Scene chapter3) + { + foreach (var type in new[] { typeof(VictoryUI), typeof(DefeatUI), typeof(MemorialUI) }) + { + foreach (Component comp in Object.FindObjectsByType(type, FindObjectsInactive.Include, FindObjectsSortMode.None)) + { + if (comp != null && comp.gameObject.scene == chapter3) + yield return comp.gameObject; + } + } + } + + // ------------------------------------------------------------------ + // 3) Automatización headless (batchmode -executeMethod) + // ------------------------------------------------------------------ + + /// + /// Procesa Chapter1/2/3 sin interacción: deduplica paneles y copia las + /// pantallas finales de Chapter3 a Chapter1/2. Guarda cada escena. + /// Ejecutar con: + /// Unity -batchmode -projectPath . -executeMethod + /// AjedrezPurgatorio.EditorTools.PurgatoryCanvasMaintenance.BatchProcessChapterScenes + /// + public static void BatchProcessChapterScenes() + { + var chapterPaths = new[] + { + "Assets/Scenes/Chapter1.unity", + "Assets/Scenes/Chapter2.unity", + "Assets/Scenes/Chapter3.unity", + }; + + int totalRemoved = 0; + int totalCopied = 0; + + try + { + foreach (string path in chapterPaths) + { + Scene scene = EditorSceneManager.OpenScene(path, OpenSceneMode.Single); + + int removed = RemoveDuplicatesHeadless(scene); + totalRemoved += removed; + + int copied = 0; + if (scene.path != Chapter3ScenePath) + copied = CopyEndScreensHeadless(scene); + totalCopied += copied; + + EditorSceneManager.SaveScene(scene); + Debug.Log($"[PurgatoryCanvasMaintenance][Batch] '{scene.name}': " + + $"{removed} duplicados eliminados, {copied} pantallas copiadas. Escena guardada."); + } + + Debug.Log($"[PurgatoryCanvasMaintenance][Batch] COMPLETO: {totalRemoved} duplicados, {totalCopied} copias."); + EditorApplication.Exit(0); + } + catch (System.Exception ex) + { + Debug.LogError($"[PurgatoryCanvasMaintenance][Batch] FALLÓ: {ex}"); + EditorApplication.Exit(1); + } + } + + private static int RemoveDuplicatesHeadless(Scene scene) + { + HashSet protectedAncestorIds = CollectProtectedAncestorIds(scene); + List groups = CollectDuplicateGroups(scene); + + int removed = 0; + foreach (DuplicateGroup group in groups.OrderByDescending(g => g.Depth)) + { + List alive = group.Members.Where(t => t != null).ToList(); + if (alive.Count < 2) + continue; + + List survivors = + alive.Where(t => IsProtected(t.gameObject, protectedAncestorIds)).ToList(); + + if (survivors.Count == 0) + survivors.Add(alive.OrderByDescending(t => t.GetSiblingIndex()).First()); + + foreach (Transform t in alive.Where(t => !survivors.Contains(t))) + { + GameObject go = t.gameObject; + if (go == null) + continue; // ya murió junto a un ancestro eliminado + + Object.DestroyImmediate(go); + removed++; + } + } + + if (removed > 0) + EditorSceneManager.MarkSceneDirty(scene); + + return removed; + } + + private static int CopyEndScreensHeadless(Scene active) + { + GameObject targetCanvas = active.GetRootGameObjects() + .FirstOrDefault(go => go.name == "PurgatoryCanvas"); + + if (targetCanvas == null) + { + Debug.LogWarning($"[PurgatoryCanvasMaintenance][Batch] '{active.name}' sin PurgatoryCanvas; se omiten las copias."); + return 0; + } + + Scene chapter3 = default; + int copied = 0; + + try + { + chapter3 = EditorSceneManager.OpenScene(Chapter3ScenePath, OpenSceneMode.Additive); + + foreach (GameObject src in EnumerateSourcePanels(chapter3)) + { + if (targetCanvas.transform.Find(src.name) != null) + continue; + + GameObject copy = Object.Instantiate(src); + SceneManager.MoveGameObjectToScene(copy, active); + copy.transform.SetParent(targetCanvas.transform, false); + copy.name = src.name; + copied++; + } + } + finally + { + if (chapter3.IsValid()) + EditorSceneManager.CloseScene(chapter3, true); + } + + if (copied > 0) + EditorSceneManager.MarkSceneDirty(active); + + return copied; + } + } +} diff --git a/Assets/Editor/PurgatoryCanvasMaintenance.cs.meta b/Assets/Editor/PurgatoryCanvasMaintenance.cs.meta new file mode 100644 index 0000000..77c1ffa --- /dev/null +++ b/Assets/Editor/PurgatoryCanvasMaintenance.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c50f0212759220b4990b34b8fe3c7ff9 \ No newline at end of file diff --git a/Assets/Editor/SpeakerDatabaseSetup.cs b/Assets/Editor/SpeakerDatabaseSetup.cs new file mode 100644 index 0000000..27f627e --- /dev/null +++ b/Assets/Editor/SpeakerDatabaseSetup.cs @@ -0,0 +1,166 @@ +using System.Linq; +using UnityEditor; +using UnityEditor.SceneManagement; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace AjedrezPurgatorio.EditorTools +{ + /// + /// Puebla el SpeakerDatabase.asset con los retratos existentes y asigna el + /// asset a todos los DialogueUI de las escenas. Guarda los cambios. + /// Ejecutar con: + /// Unity -batchmode -projectPath . -executeMethod + /// AjedrezPurgatorio.EditorTools.SpeakerDatabaseSetup.PopulateAndAssign + /// + /// Nota: 'muerte' y 'ricardo' no tienen PNG de retrato todavía; se registran + /// con portrait nulo para evitar warnings y facilitar añadir arte después. + /// + public static class SpeakerDatabaseSetup + { + private const string DatabasePath = "Assets/Game/Data/Resources/SpeakerDatabase.asset"; + private const string PortraitsFolder = "Assets/Game/Data/Resources/Portraits"; + + private static readonly (string speaker, string portrait)[] Entries = + { + ("narrator", "narrator"), + ("muerte", null), + ("ricardo", null), + ("alfil", "alfil"), + ("caballo", "caballo"), + ("peon", "peon"), + ("reina", "reina"), + ("torre", "torre"), + ("espejo", "espejo"), + ("coleccionista", "coleccionista"), + ("enemigo", "enemigo"), + ("voz_nino", "voz_nino"), + }; + + private static readonly string[] SceneNames = + { + "MainMenu", "Chapter1", "Chapter2", "Chapter3", + }; + + public static void PopulateAndAssign() + { + try + { + int populated = PopulateDatabase(); + int assigned = AssignToScenes(); + + Debug.Log($"[SpeakerDatabaseSetup] COMPLETO: {populated} speakers poblados, {assigned} DialogueUI actualizados."); + EditorApplication.Exit(0); + } + catch (System.Exception ex) + { + Debug.LogError($"[SpeakerDatabaseSetup] FALLÓ: {ex}"); + EditorApplication.Exit(1); + } + } + + private static int PopulateDatabase() + { + var db = AssetDatabase.LoadAssetAtPath(DatabasePath); + if (db == null) + throw new System.IO.FileNotFoundException($"No se encontró {DatabasePath}"); + + // Los PNG de Portraits pueden no estar importados en la AssetDatabase + // (p.ej. Library regenerada). Importar explícitamente sin Refresh global. + int imported = 0; + foreach (var (_, portraitFile) in Entries) + { + if (string.IsNullOrEmpty(portraitFile)) + continue; + + string path = $"{PortraitsFolder}/{portraitFile}.png"; + if (!System.IO.File.Exists(path)) + { + Debug.LogError($"[SpeakerDatabaseSetup] No existe en disco: {path}"); + continue; + } + + try + { + AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate); + imported++; + } + catch (System.Exception ex) + { + Debug.LogError($"[SpeakerDatabaseSetup] Fallo importando {path}: {ex.Message}"); + } + } + Debug.Log($"[SpeakerDatabaseSetup] {imported} retratos re-importados."); + + var so = new SerializedObject(db); + SerializedProperty list = so.FindProperty("_speakers"); + if (list == null) + throw new System.MissingMemberException("_speakers no encontrado en SpeakerDatabase."); + + list.ClearArray(); + + int count = 0; + foreach (var (speaker, portraitFile) in Entries) + { + list.InsertArrayElementAtIndex(list.arraySize); + SerializedProperty element = list.GetArrayElementAtIndex(list.arraySize - 1); + + element.FindPropertyRelative("speakerName").stringValue = speaker; + + Sprite sprite = null; + if (!string.IsNullOrEmpty(portraitFile)) + sprite = AssetDatabase.LoadAssetAtPath($"{PortraitsFolder}/{portraitFile}.png"); + + if (!string.IsNullOrEmpty(portraitFile) && sprite == null) + Debug.LogWarning($"[SpeakerDatabaseSetup] Portrait no encontrado para '{speaker}': {portraitFile}.png"); + + element.FindPropertyRelative("portrait").objectReferenceValue = sprite; + count++; + } + + so.ApplyModifiedPropertiesWithoutUndo(); + EditorUtility.SetDirty(db); + AssetDatabase.SaveAssets(); + return count; + } + + private static int AssignToScenes() + { + var db = AssetDatabase.LoadAssetAtPath(DatabasePath); + int assignedTotal = 0; + + // Nota: SerializedObject.ApplyModifiedProperties* no persiste este + // cambio en este proyecto (probado: el valor se pierde incluso en + // memoria tras Apply). Reflexión directa + SetDirty sí funciona. + var field = typeof(DialogueUI).GetField("_speakerDatabase", + System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + + foreach (string sceneName in SceneNames) + { + Scene scene = EditorSceneManager.OpenScene( + $"Assets/Scenes/{sceneName}.unity", OpenSceneMode.Single); + + bool dirty = false; + + foreach (DialogueUI ui in Object.FindObjectsByType( + FindObjectsInactive.Include, FindObjectsSortMode.None)) + { + if (ui == null || field.GetValue(ui) as SpeakerDatabase != null) + continue; + + field.SetValue(ui, db); + EditorUtility.SetDirty(ui); + assignedTotal++; + dirty = true; + } + + if (dirty) + EditorSceneManager.MarkSceneDirty(scene); + + EditorSceneManager.SaveScene(scene); + } + + return assignedTotal; + } + } +} diff --git a/Assets/Editor/SpeakerDatabaseSetup.cs.meta b/Assets/Editor/SpeakerDatabaseSetup.cs.meta new file mode 100644 index 0000000..81d97c1 --- /dev/null +++ b/Assets/Editor/SpeakerDatabaseSetup.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: bdc614fe81279584a9cc6267514ff99c \ No newline at end of file diff --git a/Assets/Game/Data/Resources/Portraits/alfil.png.meta b/Assets/Game/Data/Resources/Portraits/alfil.png.meta index 54cdb9b..426e60c 100644 --- a/Assets/Game/Data/Resources/Portraits/alfil.png.meta +++ b/Assets/Game/Data/Resources/Portraits/alfil.png.meta @@ -1,14 +1,17 @@ fileFormatVersion: 2 -guid: 337871385a0b47dca76233089ebc113a +guid: 4c1965114e4cdad458cc690285ff2074 TextureImporter: - internalIDToNameTable: [] + internalIDToNameTable: + - first: + 213: 1676898138316344188 + second: alfil_0 externalObjects: {} serializedVersion: 13 mipmaps: mipMapMode: 0 enableMipMap: 0 + sRGBTexture: 1 linearTexture: 0 - correctGamma: 0 fadeOut: 0 borderMipMap: 0 mipMapsPreserveCoverage: 0 @@ -25,7 +28,7 @@ TextureImporter: streamingMipmaps: 0 streamingMipmapsPriority: 0 vTOnly: 0 - ignoreMasterTextureLimit: 0 + ignoreMipmapLimit: 0 grayScaleToAlpha: 0 generateCubemap: 6 cubemapConvolution: 0 @@ -43,7 +46,7 @@ TextureImporter: nPOTScale: 0 lightmap: 0 compressionQuality: 50 - spriteMode: 1 + spriteMode: 2 spriteExtrude: 1 spriteMeshType: 1 alignment: 0 @@ -63,11 +66,11 @@ TextureImporter: compressionQualitySet: 0 textureFormatSet: 0 ignorePngGamma: 0 - applyGammaDecay: 0 + applyGammaDecoding: 0 swizzle: 50462976 cookieLightType: 0 platformSettings: - - serializedVersion: 3 + - serializedVersion: 4 buildTarget: DefaultTexturePlatform maxTextureSize: 2048 resizeAlgorithm: 0 @@ -80,22 +83,74 @@ TextureImporter: ignorePlatformSupport: 0 androidETC2FallbackOverride: 0 forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 spriteSheet: serializedVersion: 2 - sprites: [] + sprites: + - serializedVersion: 2 + name: alfil_0 + rect: + serializedVersion: 2 + x: 0 + y: 0 + width: 256 + height: 256 + alignment: 0 + pivot: {x: 0, y: 0} + border: {x: 0, y: 0, z: 0, w: 0} + customData: + outline: [] + physicsShape: [] + tessellationDetail: -1 + bones: [] + spriteID: c7b0cd2e8f9854710800000000000000 + internalID: 1676898138316344188 + vertices: [] + indices: + edges: [] + weights: [] outline: [] + customData: physicsShape: [] bones: [] - spriteID: + spriteID: internalID: 0 vertices: [] - indices: + indices: edges: [] weights: [] secondaryTextures: [] - nameFileIdTable: {} - spritePackingTag: + spriteCustomMetadata: + entries: [] + nameFileIdTable: + alfil_0: 1676898138316344188 + mipmapLimitGroupName: pSDRemoveMatte: 0 - userData: - assetBundleName: - assetBundleVariant: \ No newline at end of file + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Game/Data/Resources/Portraits/caballo.png.meta b/Assets/Game/Data/Resources/Portraits/caballo.png.meta index c899b10..a40f6f6 100644 --- a/Assets/Game/Data/Resources/Portraits/caballo.png.meta +++ b/Assets/Game/Data/Resources/Portraits/caballo.png.meta @@ -1,14 +1,17 @@ fileFormatVersion: 2 -guid: e993fcc745b44d74bdff78ca54ecae2f +guid: a579bb1b0b7e3d642a72c423e82aa57e TextureImporter: - internalIDToNameTable: [] + internalIDToNameTable: + - first: + 213: -8012410325477036581 + second: caballo_0 externalObjects: {} serializedVersion: 13 mipmaps: mipMapMode: 0 enableMipMap: 0 + sRGBTexture: 1 linearTexture: 0 - correctGamma: 0 fadeOut: 0 borderMipMap: 0 mipMapsPreserveCoverage: 0 @@ -25,7 +28,7 @@ TextureImporter: streamingMipmaps: 0 streamingMipmapsPriority: 0 vTOnly: 0 - ignoreMasterTextureLimit: 0 + ignoreMipmapLimit: 0 grayScaleToAlpha: 0 generateCubemap: 6 cubemapConvolution: 0 @@ -43,7 +46,7 @@ TextureImporter: nPOTScale: 0 lightmap: 0 compressionQuality: 50 - spriteMode: 1 + spriteMode: 2 spriteExtrude: 1 spriteMeshType: 1 alignment: 0 @@ -63,11 +66,11 @@ TextureImporter: compressionQualitySet: 0 textureFormatSet: 0 ignorePngGamma: 0 - applyGammaDecay: 0 + applyGammaDecoding: 0 swizzle: 50462976 cookieLightType: 0 platformSettings: - - serializedVersion: 3 + - serializedVersion: 4 buildTarget: DefaultTexturePlatform maxTextureSize: 2048 resizeAlgorithm: 0 @@ -80,22 +83,74 @@ TextureImporter: ignorePlatformSupport: 0 androidETC2FallbackOverride: 0 forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 spriteSheet: serializedVersion: 2 - sprites: [] + sprites: + - serializedVersion: 2 + name: caballo_0 + rect: + serializedVersion: 2 + x: 0 + y: 0 + width: 256 + height: 256 + alignment: 0 + pivot: {x: 0, y: 0} + border: {x: 0, y: 0, z: 0, w: 0} + customData: + outline: [] + physicsShape: [] + tessellationDetail: -1 + bones: [] + spriteID: bdde443b2433ec090800000000000000 + internalID: -8012410325477036581 + vertices: [] + indices: + edges: [] + weights: [] outline: [] + customData: physicsShape: [] bones: [] - spriteID: + spriteID: internalID: 0 vertices: [] - indices: + indices: edges: [] weights: [] secondaryTextures: [] - nameFileIdTable: {} - spritePackingTag: + spriteCustomMetadata: + entries: [] + nameFileIdTable: + caballo_0: -8012410325477036581 + mipmapLimitGroupName: pSDRemoveMatte: 0 - userData: - assetBundleName: - assetBundleVariant: \ No newline at end of file + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Game/Data/Resources/Portraits/coleccionista.png.meta b/Assets/Game/Data/Resources/Portraits/coleccionista.png.meta index 79d436f..37d1493 100644 --- a/Assets/Game/Data/Resources/Portraits/coleccionista.png.meta +++ b/Assets/Game/Data/Resources/Portraits/coleccionista.png.meta @@ -1,14 +1,17 @@ fileFormatVersion: 2 -guid: 0aa3b637028c46aea491d5f19c5bf328 +guid: bd525630b7cffb8478281a6102df7005 TextureImporter: - internalIDToNameTable: [] + internalIDToNameTable: + - first: + 213: 434492148875499951 + second: coleccionista_0 externalObjects: {} serializedVersion: 13 mipmaps: mipMapMode: 0 enableMipMap: 0 + sRGBTexture: 1 linearTexture: 0 - correctGamma: 0 fadeOut: 0 borderMipMap: 0 mipMapsPreserveCoverage: 0 @@ -25,7 +28,7 @@ TextureImporter: streamingMipmaps: 0 streamingMipmapsPriority: 0 vTOnly: 0 - ignoreMasterTextureLimit: 0 + ignoreMipmapLimit: 0 grayScaleToAlpha: 0 generateCubemap: 6 cubemapConvolution: 0 @@ -43,7 +46,7 @@ TextureImporter: nPOTScale: 0 lightmap: 0 compressionQuality: 50 - spriteMode: 1 + spriteMode: 2 spriteExtrude: 1 spriteMeshType: 1 alignment: 0 @@ -63,11 +66,11 @@ TextureImporter: compressionQualitySet: 0 textureFormatSet: 0 ignorePngGamma: 0 - applyGammaDecay: 0 + applyGammaDecoding: 0 swizzle: 50462976 cookieLightType: 0 platformSettings: - - serializedVersion: 3 + - serializedVersion: 4 buildTarget: DefaultTexturePlatform maxTextureSize: 2048 resizeAlgorithm: 0 @@ -80,22 +83,74 @@ TextureImporter: ignorePlatformSupport: 0 androidETC2FallbackOverride: 0 forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 spriteSheet: serializedVersion: 2 - sprites: [] + sprites: + - serializedVersion: 2 + name: coleccionista_0 + rect: + serializedVersion: 2 + x: 0 + y: 0 + width: 256 + height: 256 + alignment: 0 + pivot: {x: 0, y: 0} + border: {x: 0, y: 0, z: 0, w: 0} + customData: + outline: [] + physicsShape: [] + tessellationDetail: -1 + bones: [] + spriteID: fa99b6fae40a70600800000000000000 + internalID: 434492148875499951 + vertices: [] + indices: + edges: [] + weights: [] outline: [] + customData: physicsShape: [] bones: [] - spriteID: + spriteID: internalID: 0 vertices: [] - indices: + indices: edges: [] weights: [] secondaryTextures: [] - nameFileIdTable: {} - spritePackingTag: + spriteCustomMetadata: + entries: [] + nameFileIdTable: + coleccionista_0: 434492148875499951 + mipmapLimitGroupName: pSDRemoveMatte: 0 - userData: - assetBundleName: - assetBundleVariant: \ No newline at end of file + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Game/Data/Resources/Portraits/enemigo.png.meta b/Assets/Game/Data/Resources/Portraits/enemigo.png.meta index 0aebfc2..b5cf643 100644 --- a/Assets/Game/Data/Resources/Portraits/enemigo.png.meta +++ b/Assets/Game/Data/Resources/Portraits/enemigo.png.meta @@ -1,14 +1,17 @@ fileFormatVersion: 2 -guid: 66d4d365262a43c48947a40f1ad7dc14 +guid: bbbab8e1671274e4ba56866bfc33c2d6 TextureImporter: - internalIDToNameTable: [] + internalIDToNameTable: + - first: + 213: -1092110371768519499 + second: enemigo_0 externalObjects: {} serializedVersion: 13 mipmaps: mipMapMode: 0 enableMipMap: 0 + sRGBTexture: 1 linearTexture: 0 - correctGamma: 0 fadeOut: 0 borderMipMap: 0 mipMapsPreserveCoverage: 0 @@ -25,7 +28,7 @@ TextureImporter: streamingMipmaps: 0 streamingMipmapsPriority: 0 vTOnly: 0 - ignoreMasterTextureLimit: 0 + ignoreMipmapLimit: 0 grayScaleToAlpha: 0 generateCubemap: 6 cubemapConvolution: 0 @@ -43,7 +46,7 @@ TextureImporter: nPOTScale: 0 lightmap: 0 compressionQuality: 50 - spriteMode: 1 + spriteMode: 2 spriteExtrude: 1 spriteMeshType: 1 alignment: 0 @@ -63,11 +66,11 @@ TextureImporter: compressionQualitySet: 0 textureFormatSet: 0 ignorePngGamma: 0 - applyGammaDecay: 0 + applyGammaDecoding: 0 swizzle: 50462976 cookieLightType: 0 platformSettings: - - serializedVersion: 3 + - serializedVersion: 4 buildTarget: DefaultTexturePlatform maxTextureSize: 2048 resizeAlgorithm: 0 @@ -80,22 +83,74 @@ TextureImporter: ignorePlatformSupport: 0 androidETC2FallbackOverride: 0 forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 spriteSheet: serializedVersion: 2 - sprites: [] + sprites: + - serializedVersion: 2 + name: enemigo_0 + rect: + serializedVersion: 2 + x: 0 + y: 0 + width: 256 + height: 256 + alignment: 0 + pivot: {x: 0, y: 0} + border: {x: 0, y: 0, z: 0, w: 0} + customData: + outline: [] + physicsShape: [] + tessellationDetail: -1 + bones: [] + spriteID: 5b4b733376b08d0f0800000000000000 + internalID: -1092110371768519499 + vertices: [] + indices: + edges: [] + weights: [] outline: [] + customData: physicsShape: [] bones: [] - spriteID: + spriteID: internalID: 0 vertices: [] - indices: + indices: edges: [] weights: [] secondaryTextures: [] - nameFileIdTable: {} - spritePackingTag: + spriteCustomMetadata: + entries: [] + nameFileIdTable: + enemigo_0: -1092110371768519499 + mipmapLimitGroupName: pSDRemoveMatte: 0 - userData: - assetBundleName: - assetBundleVariant: \ No newline at end of file + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Game/Data/Resources/Portraits/espejo.png.meta b/Assets/Game/Data/Resources/Portraits/espejo.png.meta index bbbd2c4..52568eb 100644 --- a/Assets/Game/Data/Resources/Portraits/espejo.png.meta +++ b/Assets/Game/Data/Resources/Portraits/espejo.png.meta @@ -1,14 +1,17 @@ fileFormatVersion: 2 -guid: f7197410ae7f45c4b45da28c2c8b4905 +guid: 1ca7029a0492e9c40b6c8a99db8f0f1c TextureImporter: - internalIDToNameTable: [] + internalIDToNameTable: + - first: + 213: -6550897566918066470 + second: espejo_0 externalObjects: {} serializedVersion: 13 mipmaps: mipMapMode: 0 enableMipMap: 0 + sRGBTexture: 1 linearTexture: 0 - correctGamma: 0 fadeOut: 0 borderMipMap: 0 mipMapsPreserveCoverage: 0 @@ -25,7 +28,7 @@ TextureImporter: streamingMipmaps: 0 streamingMipmapsPriority: 0 vTOnly: 0 - ignoreMasterTextureLimit: 0 + ignoreMipmapLimit: 0 grayScaleToAlpha: 0 generateCubemap: 6 cubemapConvolution: 0 @@ -43,7 +46,7 @@ TextureImporter: nPOTScale: 0 lightmap: 0 compressionQuality: 50 - spriteMode: 1 + spriteMode: 2 spriteExtrude: 1 spriteMeshType: 1 alignment: 0 @@ -63,11 +66,11 @@ TextureImporter: compressionQualitySet: 0 textureFormatSet: 0 ignorePngGamma: 0 - applyGammaDecay: 0 + applyGammaDecoding: 0 swizzle: 50462976 cookieLightType: 0 platformSettings: - - serializedVersion: 3 + - serializedVersion: 4 buildTarget: DefaultTexturePlatform maxTextureSize: 2048 resizeAlgorithm: 0 @@ -80,22 +83,74 @@ TextureImporter: ignorePlatformSupport: 0 androidETC2FallbackOverride: 0 forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 spriteSheet: serializedVersion: 2 - sprites: [] + sprites: + - serializedVersion: 2 + name: espejo_0 + rect: + serializedVersion: 2 + x: 0 + y: 0 + width: 256 + height: 256 + alignment: 0 + pivot: {x: 0, y: 0} + border: {x: 0, y: 0, z: 0, w: 0} + customData: + outline: [] + physicsShape: [] + tessellationDetail: -1 + bones: [] + spriteID: adeb0ebfe598615a0800000000000000 + internalID: -6550897566918066470 + vertices: [] + indices: + edges: [] + weights: [] outline: [] + customData: physicsShape: [] bones: [] - spriteID: + spriteID: internalID: 0 vertices: [] - indices: + indices: edges: [] weights: [] secondaryTextures: [] - nameFileIdTable: {} - spritePackingTag: + spriteCustomMetadata: + entries: [] + nameFileIdTable: + espejo_0: -6550897566918066470 + mipmapLimitGroupName: pSDRemoveMatte: 0 - userData: - assetBundleName: - assetBundleVariant: \ No newline at end of file + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Game/Data/Resources/Portraits/narrator.png.meta b/Assets/Game/Data/Resources/Portraits/narrator.png.meta index 06c8f51..8e7f174 100644 --- a/Assets/Game/Data/Resources/Portraits/narrator.png.meta +++ b/Assets/Game/Data/Resources/Portraits/narrator.png.meta @@ -1,14 +1,17 @@ fileFormatVersion: 2 -guid: 148fa96098ae472f9bbf4cde9fab00d6 +guid: 737c4034b70db6d47978d8d288f51f59 TextureImporter: - internalIDToNameTable: [] + internalIDToNameTable: + - first: + 213: 5364273658523971450 + second: narrator_0 externalObjects: {} serializedVersion: 13 mipmaps: mipMapMode: 0 enableMipMap: 0 + sRGBTexture: 1 linearTexture: 0 - correctGamma: 0 fadeOut: 0 borderMipMap: 0 mipMapsPreserveCoverage: 0 @@ -25,7 +28,7 @@ TextureImporter: streamingMipmaps: 0 streamingMipmapsPriority: 0 vTOnly: 0 - ignoreMasterTextureLimit: 0 + ignoreMipmapLimit: 0 grayScaleToAlpha: 0 generateCubemap: 6 cubemapConvolution: 0 @@ -43,7 +46,7 @@ TextureImporter: nPOTScale: 0 lightmap: 0 compressionQuality: 50 - spriteMode: 1 + spriteMode: 2 spriteExtrude: 1 spriteMeshType: 1 alignment: 0 @@ -63,11 +66,11 @@ TextureImporter: compressionQualitySet: 0 textureFormatSet: 0 ignorePngGamma: 0 - applyGammaDecay: 0 + applyGammaDecoding: 0 swizzle: 50462976 cookieLightType: 0 platformSettings: - - serializedVersion: 3 + - serializedVersion: 4 buildTarget: DefaultTexturePlatform maxTextureSize: 2048 resizeAlgorithm: 0 @@ -80,22 +83,74 @@ TextureImporter: ignorePlatformSupport: 0 androidETC2FallbackOverride: 0 forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 spriteSheet: serializedVersion: 2 - sprites: [] + sprites: + - serializedVersion: 2 + name: narrator_0 + rect: + serializedVersion: 2 + x: 0 + y: 0 + width: 256 + height: 256 + alignment: 0 + pivot: {x: 0, y: 0} + border: {x: 0, y: 0, z: 0, w: 0} + customData: + outline: [] + physicsShape: [] + tessellationDetail: -1 + bones: [] + spriteID: a7f6e46a87ab17a40800000000000000 + internalID: 5364273658523971450 + vertices: [] + indices: + edges: [] + weights: [] outline: [] + customData: physicsShape: [] bones: [] - spriteID: + spriteID: internalID: 0 vertices: [] - indices: + indices: edges: [] weights: [] secondaryTextures: [] - nameFileIdTable: {} - spritePackingTag: + spriteCustomMetadata: + entries: [] + nameFileIdTable: + narrator_0: 5364273658523971450 + mipmapLimitGroupName: pSDRemoveMatte: 0 - userData: - assetBundleName: - assetBundleVariant: \ No newline at end of file + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Game/Data/Resources/Portraits/peon.png.meta b/Assets/Game/Data/Resources/Portraits/peon.png.meta index 7f05aae..eff3334 100644 --- a/Assets/Game/Data/Resources/Portraits/peon.png.meta +++ b/Assets/Game/Data/Resources/Portraits/peon.png.meta @@ -1,14 +1,17 @@ fileFormatVersion: 2 -guid: 77143a27a96a42c281bb2d9d26385e71 +guid: e1b4509d5eb75d54a8706bd0e7c1682f TextureImporter: - internalIDToNameTable: [] + internalIDToNameTable: + - first: + 213: 7304130777000316518 + second: peon_0 externalObjects: {} serializedVersion: 13 mipmaps: mipMapMode: 0 enableMipMap: 0 + sRGBTexture: 1 linearTexture: 0 - correctGamma: 0 fadeOut: 0 borderMipMap: 0 mipMapsPreserveCoverage: 0 @@ -25,7 +28,7 @@ TextureImporter: streamingMipmaps: 0 streamingMipmapsPriority: 0 vTOnly: 0 - ignoreMasterTextureLimit: 0 + ignoreMipmapLimit: 0 grayScaleToAlpha: 0 generateCubemap: 6 cubemapConvolution: 0 @@ -43,7 +46,7 @@ TextureImporter: nPOTScale: 0 lightmap: 0 compressionQuality: 50 - spriteMode: 1 + spriteMode: 2 spriteExtrude: 1 spriteMeshType: 1 alignment: 0 @@ -63,11 +66,11 @@ TextureImporter: compressionQualitySet: 0 textureFormatSet: 0 ignorePngGamma: 0 - applyGammaDecay: 0 + applyGammaDecoding: 0 swizzle: 50462976 cookieLightType: 0 platformSettings: - - serializedVersion: 3 + - serializedVersion: 4 buildTarget: DefaultTexturePlatform maxTextureSize: 2048 resizeAlgorithm: 0 @@ -80,22 +83,74 @@ TextureImporter: ignorePlatformSupport: 0 androidETC2FallbackOverride: 0 forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 spriteSheet: serializedVersion: 2 - sprites: [] + sprites: + - serializedVersion: 2 + name: peon_0 + rect: + serializedVersion: 2 + x: 0 + y: 0 + width: 256 + height: 256 + alignment: 0 + pivot: {x: 0, y: 0} + border: {x: 0, y: 0, z: 0, w: 0} + customData: + outline: [] + physicsShape: [] + tessellationDetail: -1 + bones: [] + spriteID: 66e0a142e3c7d5560800000000000000 + internalID: 7304130777000316518 + vertices: [] + indices: + edges: [] + weights: [] outline: [] + customData: physicsShape: [] bones: [] - spriteID: + spriteID: internalID: 0 vertices: [] - indices: + indices: edges: [] weights: [] secondaryTextures: [] - nameFileIdTable: {} - spritePackingTag: + spriteCustomMetadata: + entries: [] + nameFileIdTable: + peon_0: 7304130777000316518 + mipmapLimitGroupName: pSDRemoveMatte: 0 - userData: - assetBundleName: - assetBundleVariant: \ No newline at end of file + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Game/Data/Resources/Portraits/reina.png.meta b/Assets/Game/Data/Resources/Portraits/reina.png.meta index 1083a2d..2348133 100644 --- a/Assets/Game/Data/Resources/Portraits/reina.png.meta +++ b/Assets/Game/Data/Resources/Portraits/reina.png.meta @@ -1,14 +1,17 @@ fileFormatVersion: 2 -guid: d628de6a3bbf438ea574cef6994edc02 +guid: efef67743c203fa418f587d7794ad737 TextureImporter: - internalIDToNameTable: [] + internalIDToNameTable: + - first: + 213: 9131763069496480379 + second: reina_0 externalObjects: {} serializedVersion: 13 mipmaps: mipMapMode: 0 enableMipMap: 0 + sRGBTexture: 1 linearTexture: 0 - correctGamma: 0 fadeOut: 0 borderMipMap: 0 mipMapsPreserveCoverage: 0 @@ -25,7 +28,7 @@ TextureImporter: streamingMipmaps: 0 streamingMipmapsPriority: 0 vTOnly: 0 - ignoreMasterTextureLimit: 0 + ignoreMipmapLimit: 0 grayScaleToAlpha: 0 generateCubemap: 6 cubemapConvolution: 0 @@ -43,7 +46,7 @@ TextureImporter: nPOTScale: 0 lightmap: 0 compressionQuality: 50 - spriteMode: 1 + spriteMode: 2 spriteExtrude: 1 spriteMeshType: 1 alignment: 0 @@ -63,11 +66,11 @@ TextureImporter: compressionQualitySet: 0 textureFormatSet: 0 ignorePngGamma: 0 - applyGammaDecay: 0 + applyGammaDecoding: 0 swizzle: 50462976 cookieLightType: 0 platformSettings: - - serializedVersion: 3 + - serializedVersion: 4 buildTarget: DefaultTexturePlatform maxTextureSize: 2048 resizeAlgorithm: 0 @@ -80,22 +83,74 @@ TextureImporter: ignorePlatformSupport: 0 androidETC2FallbackOverride: 0 forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 spriteSheet: serializedVersion: 2 - sprites: [] + sprites: + - serializedVersion: 2 + name: reina_0 + rect: + serializedVersion: 2 + x: 0 + y: 0 + width: 256 + height: 256 + alignment: 0 + pivot: {x: 0, y: 0} + border: {x: 0, y: 0, z: 0, w: 0} + customData: + outline: [] + physicsShape: [] + tessellationDetail: -1 + bones: [] + spriteID: b725e5a212a8abe70800000000000000 + internalID: 9131763069496480379 + vertices: [] + indices: + edges: [] + weights: [] outline: [] + customData: physicsShape: [] bones: [] - spriteID: + spriteID: internalID: 0 vertices: [] - indices: + indices: edges: [] weights: [] secondaryTextures: [] - nameFileIdTable: {} - spritePackingTag: + spriteCustomMetadata: + entries: [] + nameFileIdTable: + reina_0: 9131763069496480379 + mipmapLimitGroupName: pSDRemoveMatte: 0 - userData: - assetBundleName: - assetBundleVariant: \ No newline at end of file + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Game/Data/Resources/Portraits/torre.png.meta b/Assets/Game/Data/Resources/Portraits/torre.png.meta index 3889f54..7269c09 100644 --- a/Assets/Game/Data/Resources/Portraits/torre.png.meta +++ b/Assets/Game/Data/Resources/Portraits/torre.png.meta @@ -1,14 +1,17 @@ fileFormatVersion: 2 -guid: e26e45f73d204da189b4248ba0a84d7c +guid: c6c9116fbc38e4643ab2beb0d17759cc TextureImporter: - internalIDToNameTable: [] + internalIDToNameTable: + - first: + 213: -5933413741351382349 + second: torre_0 externalObjects: {} serializedVersion: 13 mipmaps: mipMapMode: 0 enableMipMap: 0 + sRGBTexture: 1 linearTexture: 0 - correctGamma: 0 fadeOut: 0 borderMipMap: 0 mipMapsPreserveCoverage: 0 @@ -25,7 +28,7 @@ TextureImporter: streamingMipmaps: 0 streamingMipmapsPriority: 0 vTOnly: 0 - ignoreMasterTextureLimit: 0 + ignoreMipmapLimit: 0 grayScaleToAlpha: 0 generateCubemap: 6 cubemapConvolution: 0 @@ -43,7 +46,7 @@ TextureImporter: nPOTScale: 0 lightmap: 0 compressionQuality: 50 - spriteMode: 1 + spriteMode: 2 spriteExtrude: 1 spriteMeshType: 1 alignment: 0 @@ -63,11 +66,11 @@ TextureImporter: compressionQualitySet: 0 textureFormatSet: 0 ignorePngGamma: 0 - applyGammaDecay: 0 + applyGammaDecoding: 0 swizzle: 50462976 cookieLightType: 0 platformSettings: - - serializedVersion: 3 + - serializedVersion: 4 buildTarget: DefaultTexturePlatform maxTextureSize: 2048 resizeAlgorithm: 0 @@ -80,22 +83,74 @@ TextureImporter: ignorePlatformSupport: 0 androidETC2FallbackOverride: 0 forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 spriteSheet: serializedVersion: 2 - sprites: [] + sprites: + - serializedVersion: 2 + name: torre_0 + rect: + serializedVersion: 2 + x: 0 + y: 0 + width: 256 + height: 256 + alignment: 0 + pivot: {x: 0, y: 0} + border: {x: 0, y: 0, z: 0, w: 0} + customData: + outline: [] + physicsShape: [] + tessellationDetail: -1 + bones: [] + spriteID: 3b2545983a748ada0800000000000000 + internalID: -5933413741351382349 + vertices: [] + indices: + edges: [] + weights: [] outline: [] + customData: physicsShape: [] bones: [] - spriteID: + spriteID: internalID: 0 vertices: [] - indices: + indices: edges: [] weights: [] secondaryTextures: [] - nameFileIdTable: {} - spritePackingTag: + spriteCustomMetadata: + entries: [] + nameFileIdTable: + torre_0: -5933413741351382349 + mipmapLimitGroupName: pSDRemoveMatte: 0 - userData: - assetBundleName: - assetBundleVariant: \ No newline at end of file + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Game/Data/Resources/Portraits/voz_nino.png.meta b/Assets/Game/Data/Resources/Portraits/voz_nino.png.meta index 21f1efa..4341f51 100644 --- a/Assets/Game/Data/Resources/Portraits/voz_nino.png.meta +++ b/Assets/Game/Data/Resources/Portraits/voz_nino.png.meta @@ -1,14 +1,17 @@ fileFormatVersion: 2 -guid: f2b9a076c55b47c992bc2c7cbefa9532 +guid: 4358198be608bd747bce86a521d1d173 TextureImporter: - internalIDToNameTable: [] + internalIDToNameTable: + - first: + 213: 1191133803369584460 + second: voz_nino_0 externalObjects: {} serializedVersion: 13 mipmaps: mipMapMode: 0 enableMipMap: 0 + sRGBTexture: 1 linearTexture: 0 - correctGamma: 0 fadeOut: 0 borderMipMap: 0 mipMapsPreserveCoverage: 0 @@ -25,7 +28,7 @@ TextureImporter: streamingMipmaps: 0 streamingMipmapsPriority: 0 vTOnly: 0 - ignoreMasterTextureLimit: 0 + ignoreMipmapLimit: 0 grayScaleToAlpha: 0 generateCubemap: 6 cubemapConvolution: 0 @@ -43,7 +46,7 @@ TextureImporter: nPOTScale: 0 lightmap: 0 compressionQuality: 50 - spriteMode: 1 + spriteMode: 2 spriteExtrude: 1 spriteMeshType: 1 alignment: 0 @@ -63,11 +66,11 @@ TextureImporter: compressionQualitySet: 0 textureFormatSet: 0 ignorePngGamma: 0 - applyGammaDecay: 0 + applyGammaDecoding: 0 swizzle: 50462976 cookieLightType: 0 platformSettings: - - serializedVersion: 3 + - serializedVersion: 4 buildTarget: DefaultTexturePlatform maxTextureSize: 2048 resizeAlgorithm: 0 @@ -80,22 +83,74 @@ TextureImporter: ignorePlatformSupport: 0 androidETC2FallbackOverride: 0 forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Standalone + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + - serializedVersion: 4 + buildTarget: Android + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + ignorePlatformSupport: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 spriteSheet: serializedVersion: 2 - sprites: [] + sprites: + - serializedVersion: 2 + name: voz_nino_0 + rect: + serializedVersion: 2 + x: 0 + y: 0 + width: 256 + height: 256 + alignment: 0 + pivot: {x: 0, y: 0} + border: {x: 0, y: 0, z: 0, w: 0} + customData: + outline: [] + physicsShape: [] + tessellationDetail: -1 + bones: [] + spriteID: c47ddcd12e1c78010800000000000000 + internalID: 1191133803369584460 + vertices: [] + indices: + edges: [] + weights: [] outline: [] + customData: physicsShape: [] bones: [] - spriteID: + spriteID: internalID: 0 vertices: [] - indices: + indices: edges: [] weights: [] secondaryTextures: [] - nameFileIdTable: {} - spritePackingTag: + spriteCustomMetadata: + entries: [] + nameFileIdTable: + voz_nino_0: 1191133803369584460 + mipmapLimitGroupName: pSDRemoveMatte: 0 - userData: - assetBundleName: - assetBundleVariant: \ No newline at end of file + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Game/Data/Resources/SpeakerDatabase.asset b/Assets/Game/Data/Resources/SpeakerDatabase.asset index 03d1374..8f8de32 100644 --- a/Assets/Game/Data/Resources/SpeakerDatabase.asset +++ b/Assets/Game/Data/Resources/SpeakerDatabase.asset @@ -11,5 +11,29 @@ MonoBehaviour: m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 281f13885be2fde46bc468d72ea1291a, type: 3} m_Name: SpeakerDatabase - m_EditorClassIdentifier: - _speakers: [] + m_EditorClassIdentifier: + _speakers: + - speakerName: narrator + portrait: {fileID: 5364273658523971450, guid: 737c4034b70db6d47978d8d288f51f59, type: 3} + - speakerName: muerte + portrait: {fileID: 0} + - speakerName: ricardo + portrait: {fileID: 0} + - speakerName: alfil + portrait: {fileID: 1676898138316344188, guid: 4c1965114e4cdad458cc690285ff2074, type: 3} + - speakerName: caballo + portrait: {fileID: -8012410325477036581, guid: a579bb1b0b7e3d642a72c423e82aa57e, type: 3} + - speakerName: peon + portrait: {fileID: 7304130777000316518, guid: e1b4509d5eb75d54a8706bd0e7c1682f, type: 3} + - speakerName: reina + portrait: {fileID: 9131763069496480379, guid: efef67743c203fa418f587d7794ad737, type: 3} + - speakerName: torre + portrait: {fileID: -5933413741351382349, guid: c6c9116fbc38e4643ab2beb0d17759cc, type: 3} + - speakerName: espejo + portrait: {fileID: -6550897566918066470, guid: 1ca7029a0492e9c40b6c8a99db8f0f1c, type: 3} + - speakerName: coleccionista + portrait: {fileID: 434492148875499951, guid: bd525630b7cffb8478281a6102df7005, type: 3} + - speakerName: enemigo + portrait: {fileID: -1092110371768519499, guid: bbbab8e1671274e4ba56866bfc33c2d6, type: 3} + - speakerName: voz_nino + portrait: {fileID: 1191133803369584460, guid: 4358198be608bd747bce86a521d1d173, type: 3} diff --git a/Assets/Game/Scripts/Data/CampaignState.cs b/Assets/Game/Scripts/Data/CampaignState.cs index fbc7a7f..d0c63c9 100644 --- a/Assets/Game/Scripts/Data/CampaignState.cs +++ b/Assets/Game/Scripts/Data/CampaignState.cs @@ -229,6 +229,29 @@ public class CampaignState : ScriptableObject return new List(_alivePieces); } + /// + /// Obtiene las PieceIdentity muertas (no presentes en _alivePieces). + /// Usado por el Duelo del Purgatorio para elegir al candidato de rescate. + /// + public List GetDeadIdentities() + { + var result = new List(); + + if (_allIdentities == null || _allIdentities.Count == 0) + return result; + + foreach (var identity in _allIdentities) + { + if (identity != null && !string.IsNullOrEmpty(identity.characterName) + && !_alivePieces.Contains(identity.characterName.ToLower())) + { + result.Add(identity); + } + } + + return result; + } + /// /// Obtiene la lista de PieceIdentity disponibles para el próximo tablero. /// Compatibilidad con BoardManager y PieceIdentityManager. diff --git a/Assets/Game/Scripts/Mono/Core/CampaignManager.cs b/Assets/Game/Scripts/Mono/Core/CampaignManager.cs index 808d544..2c0a5d7 100644 --- a/Assets/Game/Scripts/Mono/Core/CampaignManager.cs +++ b/Assets/Game/Scripts/Mono/Core/CampaignManager.cs @@ -44,6 +44,12 @@ public class CampaignManager : MonoBehaviour // Propiedades public ChapterData CurrentChapter { get; private set; } public int CurrentChapterIndex => _currentChapterIndex; + + /// + /// Estado de campaña activo (ScriptableObject). Expuesto para sistemas runtime + /// como el Duelo del Purgatorio que necesitan registrar piezas muertas/recuperadas. + /// + public CampaignState State => _campaignState; public bool IsCampaignActive => _campaignActive; public int TotalChapters => _activeConfig?.ChapterCount ?? 0; public DeadKingData CurrentDeadKing => _currentDeadKing; @@ -436,13 +442,61 @@ public class CampaignManager : MonoBehaviour private void OnGameCheckmate(bool whiteWins) { if (!_campaignActive) return; - + bool playerWins = whiteWins; // Player controls white + if (playerWins && PurgatoryDuelManager.TryStartFromCampaign(this)) + { + return; // El duelo del Purgatorio decide el desenlace + } + if (playerWins) OnChapterVictory(); else OnChapterDefeat(); } + + /// + /// El jugador capturó a La Muerte en el duelo: continuar como victoria de capítulo. + /// + public void ResumeAfterDuelVictory() + { + OnChapterVictory(); + } + + /// + /// El rescate por dados tuvo éxito: el capítulo continúa con el tablero restaurado. + /// + public void ResumeAfterDuelRescue() + { + Debug.Log("[CampaignManager] Rescate del duelo exitoso. El capítulo continúa."); + TransitionToPlaying(); + } + + /// + /// Detiene los sistemas de capítulo (niebla, malditas, reloj, jefes, triggers) + /// mientras el Duelo del Purgatorio está activo. + /// + public void SuspendChapterSystems() + { + EndChapterSystems(); + } + + /// + /// Derrota final en el duelo: reiniciar la campaña desde el primer tablero. + /// + public void RestartAtFirstChapter() + { + Debug.Log("[CampaignManager] El Purgatorio reclama la partida. Reinicio al Capítulo 1."); + + if (DialogueSystem.Instance != null) + DialogueSystem.Instance.ForceEndDialogue(); + + if (GameStateManager.Instance != null) + GameStateManager.Instance.TransitionTo(GameState.Menu); + + _campaignActive = true; + LoadChapterScene(0); + } private void OnGameDraw(string reason) { diff --git a/Assets/Game/Scripts/Mono/Core/GameStateManager.cs b/Assets/Game/Scripts/Mono/Core/GameStateManager.cs index 2be76e8..30e8e6a 100644 --- a/Assets/Game/Scripts/Mono/Core/GameStateManager.cs +++ b/Assets/Game/Scripts/Mono/Core/GameStateManager.cs @@ -58,9 +58,10 @@ public class GameStateManager : MonoBehaviour public enum GameState { - Menu, // Menú principal - Playing, // Partida de ajedrez en curso - Dialogue, // Diálogo (intro/outro de capítulo) — overlay en la escena del capítulo - Purgatory, // Mini-juego de dados — overlay en la escena del capítulo - GameOver // Dead King input / resultado — overlay en la escena del capítulo + Menu, // Menú principal + Playing, // Partida de ajedrez en curso + Dialogue, // Diálogo (intro/outro de capítulo) — overlay en la escena del capítulo + Purgatory, // Mini-juego de dados — overlay en la escena del capítulo + PurgatoryDuel, // Duelo final contra La Muerte tras dar jaque mate + GameOver // Dead King input / resultado — overlay en la escena del capítulo } diff --git a/Assets/Game/Scripts/Mono/Meta/DeadKingPool.cs b/Assets/Game/Scripts/Mono/Meta/DeadKingPool.cs index a9e84ad..4fbdac7 100644 --- a/Assets/Game/Scripts/Mono/Meta/DeadKingPool.cs +++ b/Assets/Game/Scripts/Mono/Meta/DeadKingPool.cs @@ -99,7 +99,11 @@ namespace AjedrezPurgatorio.Meta if (_isInitialized) return; - _persistentFilePath = Path.Combine(Application.persistentDataPath, "dead_kings.json"); + // Preservar una ruta ya asignada (inyección para tests); solo + // asignar la persistente por defecto si el campo está vacío. + if (string.IsNullOrEmpty(_persistentFilePath)) + _persistentFilePath = Path.Combine(Application.persistentDataPath, "dead_kings.json"); + LoadPool(); _isInitialized = true; diff --git a/Assets/Game/Scripts/Mono/Purgatory/Duel.meta b/Assets/Game/Scripts/Mono/Purgatory/Duel.meta new file mode 100644 index 0000000..9b7855d --- /dev/null +++ b/Assets/Game/Scripts/Mono/Purgatory/Duel.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ecc57bd50bd471047882bb2df0ed6097 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Game/Scripts/Mono/Purgatory/Duel/MuerteQuotes.cs b/Assets/Game/Scripts/Mono/Purgatory/Duel/MuerteQuotes.cs new file mode 100644 index 0000000..500cc08 --- /dev/null +++ b/Assets/Game/Scripts/Mono/Purgatory/Duel/MuerteQuotes.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using UnityEngine; + +/// +/// Frases de La Muerte durante el Duelo del Purgatorio. +/// Voz solemne según purgatorio-cosmology.md ("la Muerte", máscara formal). +/// +public static class MuerteQuotes +{ + private static readonly string[] Intro = + { + "Mate perfecto... para el tablero. Aquí abajo, la partida sigue.", + "Llegaste al final del tablero. Bienvenido al principio del mío.", + "Todos los caminos del ajedrez terminan frente a mí. Tú llegaste caminando.", + "Tu reina no me mateó a mí. Solo despejó la mesa." + }; + + private static readonly string[] Milestone = + { + "El Purgatorio tiene hambre. Cada turno, una casilla menos.", + "El suelo que pisas ya fue. Apúrate.", + "No te apresuro. El vacío lo hace por mí.", + "Cada casilla que cae fue una partida más. Como la tuya." + }; + + private static readonly string[] PlayerWin = + { + "...Bien jugado. El tablero es tuyo. Por ahora.", + "Me capturas hoy. Mañana seré tu siguiente jaque.", + "Toma tu victoria. Yo me quedo con el tiempo.", + "Nadie me había alcanzado sin perder algo primero. Vuelve pronto." + }; + + private static readonly string[] PlayerLost = + { + "Te dije que nadie gana aquí. Solo algunos posponen.", + "Tu pieza descansa. ¿Quieres negociar por ella? Los dados no mienten.", + "El vacío también juega. Y siempre va conmigo.", + "Otra partida termina. Otra alma aprende." + }; + + public static string GetIntro() => Pick(Intro); + public static string GetMilestone() => Pick(Milestone); + public static string GetPlayerWin() => Pick(PlayerWin); + public static string GetPlayerLost() => Pick(PlayerLost); + + private static string Pick(string[] pool) + { + if (pool == null || pool.Length == 0) + return string.Empty; + return pool[Random.Range(0, pool.Length)]; + } +} diff --git a/Assets/Game/Scripts/Mono/Purgatory/Duel/MuerteQuotes.cs.meta b/Assets/Game/Scripts/Mono/Purgatory/Duel/MuerteQuotes.cs.meta new file mode 100644 index 0000000..e12f986 --- /dev/null +++ b/Assets/Game/Scripts/Mono/Purgatory/Duel/MuerteQuotes.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c4d50f6d2066ae74fa6450df51918194 \ No newline at end of file diff --git a/Assets/Game/Scripts/Mono/Purgatory/Duel/PurgatoryDuelManager.cs b/Assets/Game/Scripts/Mono/Purgatory/Duel/PurgatoryDuelManager.cs new file mode 100644 index 0000000..b14a14d --- /dev/null +++ b/Assets/Game/Scripts/Mono/Purgatory/Duel/PurgatoryDuelManager.cs @@ -0,0 +1,613 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using AjedrezPurgatorio.Audio; +using UnityEngine; + +/// +/// Orquesta el Duelo del Purgatorio: tras el jaque mate del jugador, su pieza +/// queda sola contra La Muerte mientras el tablero se devora a sí mismo. +/// Todo el flujo es runtime (sin edición de escenas) según purgatorio-final.md. +/// +public class PurgatoryDuelManager : MonoBehaviour +{ + public static PurgatoryDuelManager Instance { get; private set; } + + private enum Phase + { + Idle, + PlayerTurn, + Resolving, + Ended + } + + private Phase _phase = Phase.Idle; + private int _turn; + private bool _rescueUsed; + + private Piece _playerPiece; + private Piece _boss; + private readonly HashSet _devoured = new HashSet(); + private readonly List<(Piece piece, Vector2Int pos)> _hiddenPieces = new List<(Piece, Vector2Int)>(); + private readonly Dictionary _originalTileColors = new Dictionary(); + private readonly System.Random _rng = new System.Random(); + + private CampaignManager _campaign; + private PurgatoryDuelUI _ui; + + public bool IsActive => _phase != Phase.Idle && _phase != Phase.Ended; + + // ══════════════════════════════════════════════════════ + // INICIO + // ══════════════════════════════════════════════════════ + + /// + /// Punto de entrada llamado por CampaignManager al recibir OnCheckmate(playerWins). + /// Retorna false si el duelo no puede iniciarse y la campaña debe resolver victoria normal. + /// + public static bool TryStartFromCampaign(CampaignManager campaign) + { + if (Instance != null && Instance.IsActive) + return false; + + if (campaign == null || GameManager.Instance == null || BoardManager.Instance == null) + return false; + + Piece[,] board = GameManager.Instance.board; + if (board == null) + return false; + + Piece matingPiece = FindMatingPiece(board); + if (matingPiece == null) + return false; + + GameObject go = new GameObject(typeof(PurgatoryDuelManager).Name); + go.transform.SetParent(campaign.transform, worldPositionStays: false); + Instance = go.AddComponent(); + Instance.Begin(campaign, matingPiece); + return true; + } + + /// + /// La pieza de mate es la pieza blanca de mayor valor que ataca al rey negro. + /// + private static Piece FindMatingPiece(Piece[,] board) + { + Vector2Int blackKingPos = default; + bool kingFound = false; + foreach (Piece piece in board) + { + if (piece is King && !piece.isWhite) + { + blackKingPos = piece.currentPos; + kingFound = true; + break; + } + } + + if (!kingFound) + return null; + + Piece best = null; + foreach (Piece piece in board) + { + if (piece == null || !piece.isWhite) + continue; + + if (!piece.GetAttackSquares(board).Contains(blackKingPos)) + continue; + + if (best == null || piece.pieceValue > best.pieceValue) + best = piece; + } + + return best; + } + + private void Begin(CampaignManager campaign, Piece matingPiece) + { + _campaign = campaign; + _playerPiece = matingPiece; + _turn = 0; + _rescueUsed = false; + _devoured.Clear(); + _hiddenPieces.Clear(); + _originalTileColors.Clear(); + _phase = Phase.PlayerTurn; + + if (GameStateManager.Instance != null) + GameStateManager.Instance.TransitionTo(GameState.PurgatoryDuel); + + var ai = FindObjectOfType(); + if (ai != null) + ai.SetEnabled(false); + + if (_campaign != null) + _campaign.SuspendChapterSystems(); + + BuildArena(); + BuildUI(); + + Debug.Log($"[PurgatoryDuel] Iniciado. Pieza del jugador: {_playerPiece.GetType().Name} en {_playerPiece.currentPos}."); + } + + private void BuildArena() + { + Piece[,] board = GameManager.Instance.board; + + for (int x = 0; x < 8; x++) + { + for (int y = 0; y < 8; y++) + { + Piece piece = board[x, y]; + if (piece == null || piece == _playerPiece) + continue; + + _hiddenPieces.Add((piece, new Vector2Int(x, y))); + board[x, y] = null; + piece.gameObject.SetActive(false); + } + } + + _boss = SpawnBoss(board); + } + + private Piece SpawnBoss(Piece[,] board) + { + GameObject prefab = BoardManager.Instance.blackKingPrefab != null + ? BoardManager.Instance.blackKingPrefab + : BoardManager.Instance.kingPrefab; + + if (prefab == null) + { + Debug.LogError("[PurgatoryDuel] Sin prefab de Rey para La Muerte."); + return null; + } + + float tileSize = BoardManager.Instance.tileSize; + Vector2Int spawnPos = PickBossSpawn(board); + GameObject bossObj = Instantiate(prefab, new Vector3(spawnPos.x * tileSize, spawnPos.y * tileSize, -1f), Quaternion.identity); + bossObj.name = "LaMuerte"; + + Piece boss = bossObj.GetComponent(); + boss.isWhite = false; + boss.currentPos = spawnPos; + boss.hasMoved = true; + board[spawnPos.x, spawnPos.y] = boss; + + foreach (var spriteRenderer in bossObj.GetComponentsInChildren(true)) + spriteRenderer.color = new Color(0.55f, 0.07f, 0.07f); + + return boss; + } + + private static Vector2Int PickBossSpawn(Piece[,] board) + { + foreach (Vector2Int candidate in new[] + { + new Vector2Int(4, 4), + new Vector2Int(3, 3), + new Vector2Int(4, 3), + new Vector2Int(3, 4), + new Vector2Int(0, 7), + new Vector2Int(7, 7) + }) + { + if (board[candidate.x, candidate.y] == null) + return candidate; + } + + return new Vector2Int(0, 7); + } + + private void BuildUI() + { + _ui = gameObject.AddComponent(); + _ui.Build(); + _ui.ShowQuote(MuerteQuotes.GetIntro()); + _ui.SetTurn(1); + } + + // ══════════════════════════════════════════════════════ + // INPUT DEL JUGADOR + // ══════════════════════════════════════════════════════ + + public void HandleTileClick(Vector2Int position) + { + if (_phase != Phase.PlayerTurn || _playerPiece == null) + return; + + if (position == _playerPiece.currentPos) + { + ToggleHighlights(); + return; + } + + if (!GetLegalMoves().Contains(position)) + return; + + StartCoroutine(ExecutePlayerMove(position)); + } + + private void ToggleHighlights() + { + BoardManager.Instance.HideAllIndicators(); + if (_highlightVisible) + { + _highlightVisible = false; + return; + } + + foreach (Vector2Int move in GetLegalMoves()) + BoardManager.Instance.ShowIndicator(move); + + _highlightVisible = true; + } + + private bool _highlightVisible; + + /// + /// Movimientos legales de la pieza del jugador: reglas de ajedrez de la pieza, + /// sin casillas devoradas y solo hacia casillas vacías o con La Muerte. + /// No usa MoveValidator porque no hay rey blanco que dejar en jaque. + /// + private List GetLegalMoves() + { + Piece[,] board = GameManager.Instance.board; + var moves = new List(); + + foreach (Vector2Int move in _playerPiece.GetAvailableMoves(board)) + { + if (_devoured.Contains(move)) + continue; + + Piece target = board[move.x, move.y]; + if (target != null && target.isWhite) + continue; + + moves.Add(move); + } + + return moves; + } + + private IEnumerator ExecutePlayerMove(Vector2Int targetPos) + { + _phase = Phase.Resolving; + BoardManager.Instance.HideAllIndicators(); + _highlightVisible = false; + + if (_boss != null && targetPos == _boss.currentPos) + { + CaptureBoss(targetPos); + yield break; + } + + MovePieceOnBoard(_playerPiece, targetPos); + + yield return ResolveTurn(); + } + + private void MovePieceOnBoard(Piece piece, Vector2Int newPos) + { + float tileSize = BoardManager.Instance.tileSize; + Vector2Int oldPos = piece.currentPos; + + GameManager.Instance.board[oldPos.x, oldPos.y] = null; + GameManager.Instance.board[newPos.x, newPos.y] = piece; + + piece.transform.position = new Vector3(newPos.x * tileSize, newPos.y * tileSize, -1f); + piece.currentPos = newPos; + piece.hasMoved = true; + } + + private void CaptureBoss(Vector2Int bossPos) + { + Debug.Log("[PurgatoryDuel] ¡La Muerte capturada!"); + _phase = Phase.Ended; + + GameManager.Instance.board[bossPos.x, bossPos.y] = null; + Destroy(_boss.gameObject); + _boss = null; + + MovePieceOnBoard(_playerPiece, bossPos); + + if (AudioManager.Instance != null) + AudioManager.Instance.PlayCapture(); + + _ui.ShowQuote(MuerteQuotes.GetPlayerWin()); + _ui.ShowVictory("EL TABLERO ES TUYO"); + + CleanupDuel(restorePieces: true); + + StartCoroutine(FinishAfterDelay(() => + { + if (_campaign != null) + _campaign.ResumeAfterDuelVictory(); + TearDown(); + })); + } + + // ══════════════════════════════════════════════════════ + // RESOLUCIÓN DE TURNO: COLAPSO + JEFE + // ══════════════════════════════════════════════════════ + + private IEnumerator ResolveTurn() + { + _turn++; + _ui.SetTurn(Mathf.Max(1, _turn)); + _ui.ShowQuote(MuerteQuotes.GetMilestone()); + + yield return CollapseStep(); + + if (_phase == Phase.Ended) + yield break; + + yield return BossStep(); + + if (_phase == Phase.Ended) + yield break; + + if (GetLegalMoves().Count == 0) + { + Debug.Log("[PurgatoryDuel] Sin movimientos legales: turno pasado."); + StartCoroutine(ResolveTurn()); + yield break; + } + + _phase = Phase.PlayerTurn; + } + + private IEnumerator CollapseStep() + { + int tiles = PurgatoryDuelRules.TilesForTurn(_turn); + if (tiles <= 0) + yield break; + + var excluded = new HashSet(_devoured); + if (_boss != null) + excluded.Add(_boss.currentPos); + + List victims = PurgatoryDuelRules.PickTilesToDevour(tiles, excluded, _rng); + foreach (Vector2Int tile in victims) + { + DevourTile(tile); + + if (_playerPiece != null && tile == _playerPiece.currentPos) + { + Defeat("El vacío te tragó"); + yield break; + } + + yield return new WaitForSeconds(0.15f); + } + } + + private void DevourTile(Vector2Int tile) + { + _devoured.Add(tile); + + GameObject square = BoardManager.Instance.GetSquare(tile.x, tile.y); + if (square != null) + { + foreach (Renderer renderer in square.GetComponentsInChildren()) + { + _originalTileColors[renderer] = renderer.material.color; + renderer.material.color = new Color(0.05f, 0.03f, 0.06f); + } + } + + Debug.Log($"[PurgatoryDuel] Casilla devorada: ({tile.x}, {tile.y})."); + } + + private IEnumerator BossStep() + { + if (_boss == null) + yield break; + + for (int step = 0; step < PurgatoryDuelRules.BossStepsPerTurn; step++) + { + var blocked = new HashSet(_devoured); + Vector2Int next = PurgatoryDuelRules.NextChaseStep(_boss.currentPos, _playerPiece.currentPos, blocked); + + if (next == _boss.currentPos) + yield break; + + MovePieceOnBoard(_boss, next); + + if (next == _playerPiece.currentPos) + { + Defeat("La Muerte te alcanzó"); + yield break; + } + + yield return new WaitForSeconds(0.25f); + } + } + + // ══════════════════════════════════════════════════════ + // DERROTA Y RESCATE + // ══════════════════════════════════════════════════════ + + private void Defeat(string reason) + { + Debug.LogWarning($"[PurgatoryDuel] Derrota: {reason}"); + _phase = Phase.Ended; + BoardManager.Instance.HideAllIndicators(); + + PieceIdentity rescueCandidate = GetRescueCandidate(); + if (!_rescueUsed && rescueCandidate != null) + { + _ui.ShowDefeatPanel( + reason, + () => StartRescue(rescueCandidate), + DeclineRescue); + } + else + { + FinalDefeat(); + } + } + + /// + /// Identidad muerta más valiosa disponible para el rescate por dados. + /// + private PieceIdentity GetRescueCandidate() + { + CampaignState state = _campaign != null ? _campaign.State : null; + if (state == null) + return null; + + return state.GetDeadIdentities() + .OrderByDescending(IdentityValue) + .FirstOrDefault(); + } + + private static int IdentityValue(PieceIdentity identity) + { + return identity.pieceType switch + { + PieceType.Queen => 9, + PieceType.Rook => 5, + PieceType.Bishop => 3, + PieceType.Knight => 3, + PieceType.Pawn => 1, + _ => 0 + }; + } + + private void StartRescue(PieceIdentity candidate) + { + _rescueUsed = true; + _ui.HideDefeatPanel(); + StartCoroutine(ExecuteRescue(candidate)); + } + + private IEnumerator ExecuteRescue(PieceIdentity candidate) + { + DiceSystem diceSystem = FindObjectOfType(); + if (diceSystem == null) + { + Debug.LogError("[PurgatoryDuel] DiceSystem no encontrado. Rescate imposible."); + FinalDefeat(); + yield break; + } + + DiceRollResult playerRoll = diceSystem.RollForPlayer(candidate, 0); + DiceRollResult deathRoll = diceSystem.RollForDeath(); + PurgatoryWinner winner = diceSystem.DetermineWinner(playerRoll.finalResult, deathRoll.finalResult); + + var rollUI = FindObjectOfType(true); + var resultUI = FindObjectOfType(true); + + if (rollUI != null && resultUI != null) + { + rollUI.Show(); + yield return rollUI.AnimatePlayerRoll(playerRoll); + yield return rollUI.AnimateDeathRoll(deathRoll); + rollUI.Hide(); + resultUI.Show(winner, candidate, playerRoll.finalResult, deathRoll.finalResult); + yield return new WaitUntil(() => !resultUI.IsShowing); + } + else + { + Debug.Log($"[PurgatoryDuel] Rescate sin UI: jugador {playerRoll.finalResult} vs Muerte {deathRoll.finalResult}."); + } + + if (winner == PurgatoryWinner.Player) + { + Debug.Log($"[PurgatoryDuel] Rescate exitoso: {candidate.characterName} vuelve al tablero."); + + if (_campaign.State != null) + _campaign.State.MarkPieceRecovered(candidate); + + _ui.ShowQuote("...Tómalas. El tablero te espera."); + CleanupDuel(restorePieces: true); + + yield return FinishAfterDelay(() => + { + if (_campaign != null) + _campaign.ResumeAfterDuelRescue(); + TearDown(); + }); + } + else + { + Debug.Log("[PurgatoryDuel] Rescate fallido."); + FinalDefeat(); + } + } + + private void DeclineRescue() + { + _rescueUsed = true; + _ui.HideDefeatPanel(); + FinalDefeat(); + } + + private void FinalDefeat() + { + _ui.ShowQuote(MuerteQuotes.GetPlayerLost()); + + CleanupDuel(restorePieces: false); + + StartCoroutine(FinishAfterDelay(() => + { + if (_campaign != null) + _campaign.RestartAtFirstChapter(); + TearDown(); + })); + } + + private IEnumerator FinishAfterDelay(System.Action action) + { + yield return new WaitForSeconds(1.6f); + action?.Invoke(); + } + + // ══════════════════════════════════════════════════════ + // LIMPIEZA + // ══════════════════════════════════════════════════════ + + /// + /// Devuelve las piezas ocultas a sus casillas originales y restaura el color + /// de las casillas devoradas. Con restorePieces=false (derrota final) las + /// piezas permanecen apartadas: la escena se recarga de inmediato. + /// + private void CleanupDuel(bool restorePieces) + { + Piece[,] board = GameManager.Instance.board; + if (board != null && restorePieces) + { + foreach (var entry in _hiddenPieces) + { + if (entry.piece == null) + continue; + + board[entry.pos.x, entry.pos.y] = entry.piece; + entry.piece.gameObject.SetActive(true); + } + } + + foreach (var pair in _originalTileColors) + { + if (pair.Key != null) + pair.Key.material.color = pair.Value; + } + + if (BoardManager.Instance != null) + BoardManager.Instance.HideAllIndicators(); + + _hiddenPieces.Clear(); + _originalTileColors.Clear(); + } + + private void TearDown() + { + _phase = Phase.Idle; + if (Instance == this) + Instance = null; + + Destroy(gameObject); + } +} diff --git a/Assets/Game/Scripts/Mono/Purgatory/Duel/PurgatoryDuelManager.cs.meta b/Assets/Game/Scripts/Mono/Purgatory/Duel/PurgatoryDuelManager.cs.meta new file mode 100644 index 0000000..89a6e11 --- /dev/null +++ b/Assets/Game/Scripts/Mono/Purgatory/Duel/PurgatoryDuelManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d51ec1b9d6be556448a34c159b5f2c6e \ No newline at end of file diff --git a/Assets/Game/Scripts/Mono/Purgatory/Duel/PurgatoryDuelRules.cs b/Assets/Game/Scripts/Mono/Purgatory/Duel/PurgatoryDuelRules.cs new file mode 100644 index 0000000..93ea5b0 --- /dev/null +++ b/Assets/Game/Scripts/Mono/Purgatory/Duel/PurgatoryDuelRules.cs @@ -0,0 +1,97 @@ +using System.Collections.Generic; +using UnityEngine; + +/// +/// Reglas puras del Duelo del Purgatorio: curva de colapso y persecución del Jefe. +/// Sin dependencias de escena para poder testear en EditMode. +/// +public static class PurgatoryDuelRules +{ + public const int BoardSize = 8; + public const int CollapseGraceTurns = 2; + public const int DoubleCollapseFromTurn = 8; + public const int BossStepsPerTurn = 2; + + /// + /// Casillas devoradas en el turno dado (1-indexed). + /// Turnos 1-2: 0 · Turnos 3-7: 1 · Turno 8+: 2. + /// + public static int TilesForTurn(int turn) + { + if (turn <= CollapseGraceTurns) + return 0; + if (turn < DoubleCollapseFromTurn) + return 1; + return 2; + } + + /// + /// Elige casillas aleatorias a devorar, excluyendo vacíos previos y la casilla + /// del Jefe. La casilla del jugador SÍ es candidata (derrota por colapso). + /// + public static List PickTilesToDevour(int count, HashSet excluded, System.Random rng) + { + var candidates = new List(BoardSize * BoardSize); + for (int x = 0; x < BoardSize; x++) + { + for (int y = 0; y < BoardSize; y++) + { + var p = new Vector2Int(x, y); + if (!excluded.Contains(p)) + candidates.Add(p); + } + } + + for (int i = candidates.Count - 1; i > 0; i--) + { + int j = rng.Next(i + 1); + (candidates[i], candidates[j]) = (candidates[j], candidates[i]); + } + + int n = Mathf.Min(count, candidates.Count); + return candidates.GetRange(0, n); + } + + /// + /// Un paso greedy del Jefe hacia el jugador (movimiento de rey, 1 casilla), + /// evitando casillas bloqueadas (vacíos). Prefiere la diagonal; si está + /// bloqueada acepta un paso axial que no aumente la distancia Chebyshev + /// (permite rodear huecos). Devuelve 'from' si no hay paso válido. + /// + public static Vector2Int NextChaseStep(Vector2Int from, Vector2Int target, HashSet blocked) + { + int dx = Mathf.Clamp(target.x - from.x, -1, 1); + int dy = Mathf.Clamp(target.y - from.y, -1, 1); + + if (dx == 0 && dy == 0) + return from; + + Vector2Int diag = from + new Vector2Int(dx, dy); + if (dx != 0 && dy != 0 && !blocked.Contains(diag)) + return diag; + + Vector2Int stepX = from + new Vector2Int(dx, 0); + Vector2Int stepY = from + new Vector2Int(0, dy); + bool xOk = dx != 0 && !blocked.Contains(stepX) && Chebyshev(stepX, target) <= Chebyshev(from, target); + bool yOk = dy != 0 && !blocked.Contains(stepY) && Chebyshev(stepY, target) <= Chebyshev(from, target); + + if (xOk && yOk) + { + return Mathf.Abs(target.x - from.x) >= Mathf.Abs(target.y - from.y) ? stepX : stepY; + } + if (xOk) return stepX; + if (yOk) return stepY; + + return from; + } + + public static int Chebyshev(Vector2Int a, Vector2Int b) + { + return Mathf.Max(Mathf.Abs(a.x - b.x), Mathf.Abs(a.y - b.y)); + } + + public static bool IsInBounds(Vector2Int p) + { + return p.x >= 0 && p.x < BoardSize && p.y >= 0 && p.y < BoardSize; + } +} diff --git a/Assets/Game/Scripts/Mono/Purgatory/Duel/PurgatoryDuelRules.cs.meta b/Assets/Game/Scripts/Mono/Purgatory/Duel/PurgatoryDuelRules.cs.meta new file mode 100644 index 0000000..c8fd82a --- /dev/null +++ b/Assets/Game/Scripts/Mono/Purgatory/Duel/PurgatoryDuelRules.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e27f0d42be82acf45a36399e09bb0102 \ No newline at end of file diff --git a/Assets/Game/Scripts/Mono/Purgatory/Duel/PurgatoryDuelUI.cs b/Assets/Game/Scripts/Mono/Purgatory/Duel/PurgatoryDuelUI.cs new file mode 100644 index 0000000..90c78ba --- /dev/null +++ b/Assets/Game/Scripts/Mono/Purgatory/Duel/PurgatoryDuelUI.cs @@ -0,0 +1,161 @@ +using TMPro; +using UnityEngine; +using UnityEngine.EventSystems; +using UnityEngine.UI; + +/// +/// Overlay de UI del Duelo del Purgatorio. Se construye íntegramente en runtime +/// (sin edición de escenas) sobre su propio Canvas con sorting order alto. +/// +public class PurgatoryDuelUI : MonoBehaviour +{ + private Canvas _canvas; + private TextMeshProUGUI _turnText; + private TextMeshProUGUI _quoteText; + private GameObject _defeatPanel; + private TextMeshProUGUI _defeatReasonText; + private Button _rescueButton; + private Button _surrenderButton; + private GameObject _victoryBanner; + + public void Build() + { + EnsureEventSystem(); + + var canvasGO = new GameObject("PurgatoryDuelCanvas"); + _canvas = canvasGO.AddComponent(); + _canvas.renderMode = RenderMode.ScreenSpaceOverlay; + _canvas.sortingOrder = 500; + canvasGO.AddComponent().uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize; + canvasGO.AddComponent(); + canvasGO.transform.SetParent(transform, false); + + TMP_FontAsset font = TMP_Settings.defaultFontAsset; + + var title = CreateText("Title", "EL PURGATORIO", 42, new Color(0.85f, 0.12f, 0.12f), font); + Anchor(title.rectTransform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0, -70), new Vector2(900, 60)); + + _turnText = CreateText("Turn", "Turno 1", 26, new Color(0.9f, 0.88f, 0.8f), font); + Anchor(_turnText.rectTransform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0, -115), new Vector2(600, 40)); + + _quoteText = CreateText("Quote", string.Empty, 24, new Color(0.75f, 0.72f, 0.65f), font); + _quoteText.fontStyle = FontStyles.Italic; + Anchor(_quoteText.rectTransform, new Vector2(0.5f, 1f), new Vector2(0.5f, 1f), new Vector2(0, -165), new Vector2(1100, 70)); + + BuildDefeatPanel(font); + BuildVictoryBanner(font); + HideDefeatPanel(); + HideVictory(); + } + + private void BuildDefeatPanel(TMP_FontAsset font) + { + _defeatPanel = new GameObject("DefeatPanel"); + _defeatPanel.transform.SetParent(_canvas.transform, false); + + var bg = _defeatPanel.AddComponent(); + bg.color = new Color(0f, 0f, 0f, 0.82f); + bg.raycastTarget = true; + Stretch(bg.rectTransform); + + _defeatReasonText = CreateText("Reason", string.Empty, 34, new Color(0.9f, 0.88f, 0.8f), font); + _defeatReasonText.transform.SetParent(_defeatPanel.transform, false); + Anchor(_defeatReasonText.rectTransform, new Vector2(0.5f, 0.55f), new Vector2(0.5f, 0.55f), Vector2.zero, new Vector2(1000, 90)); + + _rescueButton = CreateButton("RescueButton", "Desafiar por rescate", font, new Color(0.45f, 0.08f, 0.08f)); + _rescueButton.transform.SetParent(_defeatPanel.transform, false); + Anchor((RectTransform)_rescueButton.transform, new Vector2(0.5f, 0.32f), new Vector2(0.5f, 0.32f), new Vector2(-160, 0), new Vector2(300, 64)); + + _surrenderButton = CreateButton("SurrenderButton", "Rendirse", font, new Color(0.15f, 0.15f, 0.17f)); + _surrenderButton.transform.SetParent(_defeatPanel.transform, false); + Anchor((RectTransform)_surrenderButton.transform, new Vector2(0.5f, 0.32f), new Vector2(0.5f, 0.32f), new Vector2(160, 0), new Vector2(240, 64)); + } + + private void BuildVictoryBanner(TMP_FontAsset font) + { + _victoryBanner = new GameObject("VictoryBanner"); + _victoryBanner.transform.SetParent(_canvas.transform, false); + + var text = CreateText("Text", string.Empty, 52, new Color(0.95f, 0.9f, 0.7f), font); + text.transform.SetParent(_victoryBanner.transform, false); + Stretch(text.rectTransform); + } + + public void SetTurn(int turn) => _turnText.text = $"Turno {turn}"; + + public void ShowQuote(string quote) => _quoteText.text = quote; + + public void ShowDefeatPanel(string reason, UnityEngine.Events.UnityAction onRescue, UnityEngine.Events.UnityAction onSurrender) + { + _defeatPanel.SetActive(true); + _defeatReasonText.text = reason; + _rescueButton.onClick.RemoveAllListeners(); + _surrenderButton.onClick.RemoveAllListeners(); + if (onRescue != null) _rescueButton.onClick.AddListener(onRescue); + if (onSurrender != null) _surrenderButton.onClick.AddListener(onSurrender); + } + + public void HideDefeatPanel() => _defeatPanel.SetActive(false); + + public void ShowVictory(string message) + { + _victoryBanner.SetActive(true); + _victoryBanner.GetComponentInChildren(true).text = message; + } + + public void HideVictory() => _victoryBanner.SetActive(false); + + private static TextMeshProUGUI CreateText(string name, string content, int size, Color color, TMP_FontAsset font) + { + var go = new GameObject(name); + var tmp = go.AddComponent(); + tmp.text = content; + tmp.fontSize = size; + tmp.color = color; + tmp.alignment = TextAlignmentOptions.Center; + tmp.raycastTarget = false; + if (font != null) + tmp.font = font; + return tmp; + } + + private static Button CreateButton(string name, string label, TMP_FontAsset font, Color color) + { + var go = new GameObject(name); + var image = go.AddComponent(); + image.color = color; + var button = go.AddComponent