Files
MVP1/Assets/Scripts/City3D/City3DGameplay.cs
T

209 lines
11 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
default: return type3d; // "Ратуша"
}
}
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);
default: return Cost();
}
}
// С какого уровня Ратуши можно строить (как в 2D)
public static int RequiredTownHall(string type3d)
{
switch (type3d)
{
case "Шахта": return 3;
case "Госпиталь": return Mathf.Max(1, HospitalConfig.Load().requiredTownHallLevel);
default: return 1;
}
}
static ResourceAmount[] Cost(params object[] pairs)
{
var list = new List<ResourceAmount>();
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<string>();
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<PlacedBuildingId>(FindObjectsSortMode.None))
if (id.buildingName == gameName) n++;
return n;
}
// Новое здание поставлено на карту: списать цену и начать стройку по времени (BuildTimer)
public static void StartConstruction(GameObject view)
{
var tag = view.GetComponent<City3DBuildingTag>();
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<City3DBuildingTag>() ?? view.AddComponent<City3DBuildingTag>();
tag.type3d = type3d;
if (IsTownHall(type3d))
{
var th = view.GetComponent<TownHall>() ?? view.AddComponent<TownHall>();
// Та же цена улучшения, что у Ратуши 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<PlacedBuildingId>().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<Barracks>().Level = Mathf.Max(1, level); break;
case "Госпиталь": view.AddComponent<Hospital>().Level = Mathf.Max(1, level); break;
}
}
static void Collector(GameObject view, ResourceType type, int level)
{
var rc = view.AddComponent<ResourceCollector>();
rc.resourceType = type;
rc.Level = Mathf.Max(1, level);
}
static void Storage(GameObject view, StorageBuilding.Kind kind, int level)
{
var sb = view.AddComponent<StorageBuilding>();
sb.kind = kind;
sb.Level = Mathf.Max(1, level);
}
// Уровень здания (для сохранения и подписи)
public static int LevelOf(GameObject view)
{
var th = view.GetComponent<TownHall>();
if (th != null) return th.currentLevel;
var leveled = view.GetComponent<ILeveledBuilding>();
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<Canvas>(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<SpriteRenderer>(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);
}
}