using System.Collections.Generic; using UnityEngine; // 3D-ПОСЕЛЕНИЕ: здания 3D-сцены становятся зданиями игры (05.11, Валдемар). // На 3D-здание вешаются те же компоненты, что на префабы 2D-города: рабочее место или Дом (ResourceCollector), // Склад / Амбар (StorageBuilding), Казармы, Госпиталь, Ратуша (TownHall) и PlacedBuildingId с именем здания. // Поэтому дальше всё работает так же, как в 2D: CitySync даёт места, жильё и лимиты; окно здания (BuildingWindow) -- // рабочие, добыча, улучшение по таймеру; стройка идёт через BuildTimer (цена, строители, лимиты, ускорение, офлайн). // Место на карте проверяет SettlementMap Саши, цену, время и лимиты -- этот код и EconomyConfig. public static class City3DGameplay { // Имя типа 3D-сцены -> имя здания игры (как в 2D: по нему лимиты, окна, сохранение) public static string GameName(string type3d) { switch (type3d) { case "Дом": return "Население"; case "Ферма": return "Еда"; case "Лесопилка": return "Лес"; case "Каменоломня": return "Камень"; case "Шахта": return "Металл"; case "Склад": return StorageBootstrap.WarehouseName; case "Амбар": return StorageBootstrap.GranaryName; case "Казармы": return BarracksBootstrap.BarracksName; case "Госпиталь": return HospitalBootstrap.HospitalName; case "Золотой рудник": return "Золото"; case "Кристалл": return "Кристалл"; case "Терем": return HeroCastleBootstrap.CastleName; default: return type3d; // "Ратуша" } } // Обратно: имя здания игры (как в 2D-сохранении) -> тип 3D-сцены. null -- такого здания в 3D нет public static string Type3DFor(string gameName) { foreach (var t in AllTypes3D) if (GameName(t) == gameName) return t; return null; } static readonly string[] AllTypes3D = { "Ратуша", "Дом", "Ферма", "Лесопилка", "Каменоломня", "Шахта", "Склад", "Амбар", "Казармы", "Госпиталь", "Золотой рудник", "Кристалл", "Терем", }; public static bool IsTownHall(string type3d) => type3d == "Ратуша"; // Цена постройки -- та же, что у зданий 2D-города (Data_*.asset, Склады -- EconomyConfig, Госпиталь -- HospitalConfig) public static ResourceAmount[] BuildCost(string type3d) { var e = EconomyConfig.Load().storage; switch (type3d) { case "Дом": return Cost(ResourceType.Wood, 100, ResourceType.Food, 200, ResourceType.Stone, 150); case "Ферма": return Cost(ResourceType.Wood, 100); case "Лесопилка": return Cost(); case "Каменоломня": return Cost(ResourceType.Wood, 50); case "Шахта": return Cost(ResourceType.Wood, 600, ResourceType.Stone, 550); case "Склад": return Cost(ResourceType.Wood, e.warehouseWood, ResourceType.Stone, e.warehouseStone); case "Амбар": return Cost(ResourceType.Wood, e.granaryWood, ResourceType.Stone, e.granaryStone); case "Казармы": return Cost(ResourceType.Wood, 300, ResourceType.Stone, 200); case "Госпиталь": var h = HospitalConfig.Load(); return Cost(ResourceType.Wood, h.buildWood, ResourceType.Stone, h.buildStone); // Перенесены из 2D-города (решение 05.11): цены -- из Data_GoldMine, Data_Kristall и HeroCastleBootstrap case "Золотой рудник": return Cost(ResourceType.Stone, 50); case "Кристалл": return Cost(ResourceType.Wood, 2000, ResourceType.Stone, 3200, ResourceType.Gold, 12000, ResourceType.Iron, 1600); case "Терем": return Cost(ResourceType.Wood, HeroCastleBootstrap.CostWood, ResourceType.Stone, HeroCastleBootstrap.CostStone); default: return Cost(); } } // С какого уровня Ратуши можно строить (как в 2D) public static int RequiredTownHall(string type3d) { switch (type3d) { case "Шахта": return 3; case "Золотой рудник": return 2; case "Кристалл": return 6; case "Госпиталь": return Mathf.Max(1, HospitalConfig.Load().requiredTownHallLevel); default: return 1; } } static ResourceAmount[] Cost(params object[] pairs) { var list = new List(); for (int i = 0; i + 1 < pairs.Length; i += 2) list.Add(new ResourceAmount((ResourceType)pairs[i], System.Convert.ToSingle(pairs[i + 1]))); return list.ToArray(); } public static int TownHallLevel => GameServices.TryGet(out ISettlement s) ? s.TownHallLevel : 1; // Можно ли строить: уровень Ратуши, лимит зданий, строители, ресурсы. null -- можно, иначе текст для игрока public static string CanBuild(string type3d) { int th = TownHallLevel; if (th < RequiredTownHall(type3d)) return $"Откроется на Ратуше {RequiredTownHall(type3d)} ур."; string name = GameName(type3d); int max = EconomyConfig.Load().BuildingLimitFor(name, th), have = CountBuilt(name); if (max >= 0 && have >= max) return $"Лимит: {have} / {max} на Ратуше {th} ур."; if (BuildTimer.DurationMs(false, 1) > 0 && BuildTimer.FreeBuilders == 0) return "Все строители заняты"; var cost = BuildCost(type3d); if (cost.Length > 0 && (!GameServices.TryGet(out IEconomy economy) || !economy.CanAfford(cost))) return "Не хватает ресурсов: " + CostText(cost); return null; } public static string CostText(ResourceAmount[] cost) { if (cost.Length == 0) return "бесплатно"; var parts = new List(); foreach (var c in cost) parts.Add($"{c.Amount:0} {GameData.ResName(c.Type).ToLower()}"); return string.Join(", ", parts.ToArray()); } public static int CountBuilt(string gameName) { int n = 0; foreach (var id in Object.FindObjectsByType(FindObjectsSortMode.None)) if (id.buildingName == gameName) n++; return n; } // Новое здание поставлено на карту: списать цену и начать стройку по времени (BuildTimer) public static void StartConstruction(GameObject view) { var tag = view.GetComponent(); if (tag == null) return; var cost = BuildCost(tag.type3d); if (cost.Length > 0 && GameServices.TryGet(out IEconomy economy)) economy.TrySpend(cost); long ms = BuildTimer.DurationMs(false, 1); if (ms > 0) { long now = GameClock.UnixNowMs; BuildTimer.Begin(view, BuildTimer.Kind.Build, 1, now, now + ms); } if (CitySync.Instance != null) CitySync.Instance.SyncNow(); } // Повесить на 3D-здание компоненты здания игры (как у префабов 2D). Без цены и таймера public static void Attach(GameObject view, string type3d, int level) { var tag = view.GetComponent() ?? view.AddComponent(); tag.type3d = type3d; if (IsTownHall(type3d)) { var th = view.GetComponent() ?? view.AddComponent(); // Та же цена улучшения, что у Ратуши 2D-города (сцена MVP1): 250 дерева, 160 камня, 25 людей, ×1.5 за уровень th.baseUpgradeCost = new[] { new ResourceCost { type = ResourceType.Wood, amount = 250 }, new ResourceCost { type = ResourceType.Stone, amount = 160 }, new ResourceCost { type = ResourceType.Population, amount = 25 }, }; th.costMultiplierPerLevel = 1.5f; th.maxLevel = 10; th.currentLevel = Mathf.Max(1, level); return; } view.AddComponent().buildingName = GameName(type3d); switch (type3d) { case "Дом": Collector(view, ResourceType.Population, level); break; case "Ферма": Collector(view, ResourceType.Food, level); break; case "Лесопилка": Collector(view, ResourceType.Wood, level); break; case "Каменоломня": Collector(view, ResourceType.Stone, level); break; case "Шахта": Collector(view, ResourceType.Iron, level); break; case "Склад": Storage(view, StorageBuilding.Kind.Warehouse, level); break; case "Амбар": Storage(view, StorageBuilding.Kind.Granary, level); break; case "Казармы": view.AddComponent().Level = Mathf.Max(1, level); break; case "Госпиталь": view.AddComponent().Level = Mathf.Max(1, level); break; case "Золотой рудник": Collector(view, ResourceType.Gold, level); break; case "Кристалл": Collector(view, ResourceType.Gems, level); break; // рабочих нет, как в 2D case "Терем": view.AddComponent(); break; // окно героя -- BuildingWindow } } static void Collector(GameObject view, ResourceType type, int level) { var rc = view.AddComponent(); rc.resourceType = type; rc.Level = Mathf.Max(1, level); } static void Storage(GameObject view, StorageBuilding.Kind kind, int level) { var sb = view.AddComponent(); sb.kind = kind; sb.Level = Mathf.Max(1, level); } // Уровень здания (для сохранения и подписи) public static int LevelOf(GameObject view) { var th = view.GetComponent(); if (th != null) return th.currentLevel; var leveled = view.GetComponent(); return leveled != null ? leveled.Level : 1; } // HUD 2D-города нарисован на холсте "в камере" (Screen Space - Camera). В 3D-сцене холст стоял бы на расстоянии // от камеры, и холмы и деревья перекрывали бы кнопки. Ставим холст вплотную к камере -- он всегда поверх 3D. public static void PutHudInFront(Camera cam) { if (cam == null) return; foreach (var canvas in Object.FindObjectsByType(FindObjectsSortMode.None)) { if (!canvas.isRootCanvas || canvas.renderMode != RenderMode.ScreenSpaceCamera) continue; canvas.worldCamera = cam; canvas.planeDistance = cam.nearClipPlane + 0.05f; HideWorldSprites(canvas); } } // В HUD есть картинка-спрайт мира (SpriteRenderer) -- плашка с мечом по центру нижней панели. На холсте у // камеры она рисуется поверх всего HUD, даже поверх открытых окон, а сама картинка без прозрачности // ("шахматка"). Решение 05.11 (Миша, Володя): в 3D-сцене её убираем, пока не будет новой картинки или кнопки. static void HideWorldSprites(Canvas canvas) { foreach (var sr in canvas.GetComponentsInChildren(true)) sr.enabled = false; } // Награда за расчистку (решение 04.11): +10 дерева за дерево или +10 камня за валун, один раз, в пределах склада public static void RewardClearing(bool tree) { if (GameServices.TryGet(out IEconomy economy)) economy.Add(tree ? ResourceType.Wood : ResourceType.Stone, 10); } }