using BepInEx; using HarmonyLib; // Necesario para Traverse using System.Collections; using System.Collections.Generic; using UnityEngine; using TMPro; using UnityEngine.UI; // Necesario para Outline namespace StrategicMapPlus { [BepInPlugin("com.mod.strategicmapplus", "Strategic Map Plus", "1.0.0")] public class StrategicMapMod : BaseUnityPlugin { private float timer = 0f; // Escaneamos cada 0.5 segundos (balance perfecto entre respuesta y rendimiento) private float interval = 0.5f; void Awake() { Logger.LogInfo("Strategic Map Plus: Iniciado correctamente."); } void Update() { timer += Time.deltaTime; if (timer < interval) return; timer = 0f; ScanStrategicMap(); } void ScanStrategicMap() { // Buscamos el contenedor de regiones GameObject regionsRoot = GameObject.Find("UI/2DRoot/Layout/Limit/FrontDialogs/StrategicView/Root/Regions"); if (regionsRoot == null || !regionsRoot.activeInHierarchy) return; foreach (Transform region in regionsRoot.transform) { // Buscamos el Army (contenedor lógico) Transform army = region.Find("Army"); if (army == null) continue; // Buscamos el componente del juego (DefenseCountRenderer) por nombre Component defenseRenderer = army.GetComponent("iron.match.renderers.strategicview.DefenseCountRenderer"); // Buscamos el contenedor visual "Root" para anclar nuestro texto Transform visualRoot = army.Find("Root"); Transform targetParent = (visualRoot != null) ? visualRoot : army; if (defenseRenderer != null) { ProcessRegion((MonoBehaviour)defenseRenderer, targetParent); } } } void ProcessRegion(MonoBehaviour renderer, Transform parentTransform) { // 1. Configurar o Buscar el Visualizador var visualizer = parentTransform.GetComponent(); if (visualizer == null) { visualizer = parentTransform.gameObject.AddComponent(); visualizer.Initialize(); } // 2. Acceder al Modelo de datos usando Reflection (Traverse) var traverse = Traverse.Create(renderer); var model = traverse.Property("model").GetValue(); if (model == null) { visualizer.UpdateOrder("", false); return; } // 3. Obtener la lista de hijos (Children) // Ya sabemos que es un Campo llamado "Children" (con mayúscula) var children = Traverse.Create(model).Field("Children").GetValue() as IEnumerable; // Fallback a minúscula por si acaso cambia en otra versión if (children == null) children = Traverse.Create(model).Field("children").GetValue() as IEnumerable; string finalText = ""; bool foundOrder = false; if (children != null) { foreach (var child in children) { // 4. Leer Atributos Crudos var childTrav = Traverse.Create(child); var attributesObj = childTrav.Field("attributes").GetValue(); if (attributesObj != null) { // Accedemos al diccionario interno 'data' var valuesDict = Traverse.Create(attributesObj).Field("data").GetValue() as IDictionary; if (valuesDict != null) { // ID 400003 = IronType (Tipo de unidad) if (valuesDict.Contains(400003)) { // Desempaquetamos el MutableAttribute object typeContainer = valuesDict[400003]; object typeVal = Traverse.Create(typeContainer).Property("Value").GetValue(); string typeName = typeVal?.ToString(); if (typeName == "Order") { // ¡Es una orden! // ID 400038 = OrderType (Tipo de orden) string orderCode = "?"; if (valuesDict.Contains(400038)) { object orderContainer = valuesDict[400038]; object orderVal = Traverse.Create(orderContainer).Property("Value").GetValue(); var list = orderVal as IList; if (list != null && list.Count > 0) { orderCode = list[0].ToString(); } } // ID 400039 = HasStar (Es especial) bool isStar = false; if (valuesDict.Contains(400039)) { object starContainer = valuesDict[400039]; object starVal = Traverse.Create(starContainer).Property("Value").GetValue(); if (starVal != null && starVal.ToString().ToLower() == "true") isStar = true; } finalText = GetOrderShortName(orderCode, isStar); foundOrder = true; break; // Solo mostramos 1 orden por región } } } } } } // 5. Actualizar UI visualizer.UpdateOrder(finalText, foundOrder); } static string GetOrderShortName(string typeName, bool isSpecial) { string symbol = ""; string star = isSpecial ? "*" : ""; if (typeName.Contains("March")) symbol = "M"; else if (typeName.Contains("Defense")) symbol = "D"; else if (typeName.Contains("Support")) symbol = "S"; else if (typeName.Contains("Raid")) symbol = "R"; else if (typeName.Contains("Consolidate")) symbol = "$"; // Usamos $ porque parece una moneda/corona else symbol = typeName.Substring(0, 1); return symbol + star; } } // --- COMPONENTE VISUAL --- public class StrategicOrderDisplay : MonoBehaviour { private TextMeshProUGUI orderText; private GameObject displayObj; public void Initialize() { if (displayObj != null) return; displayObj = new GameObject("OrderDisplay_Mod"); displayObj.transform.SetParent(this.transform, false); // Ajustamos posición: X=60 (derecha del escudo), Z=-100 (encima de todo) displayObj.transform.localPosition = new Vector3(60f, 0f, -100f); orderText = displayObj.AddComponent(); orderText.fontSize = 24; orderText.alignment = TextAlignmentOptions.Left; // Amarillo Dorado (#FFD700) - Clásico de Game of Thrones orderText.color = new Color(1f, 0.84f, 0f); orderText.fontStyle = FontStyles.Bold; orderText.enableWordWrapping = false; // Añadimos borde negro para que se lea bien sobre cualquier fondo var outline = displayObj.AddComponent(); outline.effectColor = Color.black; outline.effectDistance = new Vector2(1, -1); } public void UpdateOrder(string text, bool visible) { if (displayObj == null) Initialize(); // Gestión eficiente de SetActive para no parpadear if (displayObj.activeSelf != visible) displayObj.SetActive(visible); if (visible && orderText != null && orderText.text != text) { orderText.text = text; } } } }