Армия: заказы, сроки по часам игры, завершение один раз (TroopsTrainedEvent), сохранение train=. Население отдаёт жителей и принимает новобранцев через свой контракт. GameProgress (код Вальдемара, с разрешения команды): только цена, Казармы, окна и статистика; старая очередь из progress.json переносится один раз с тем же оставшимся временем. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
477 lines
25 KiB
C#
477 lines
25 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using TMPro;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
|
||
// Окно здания. Открывается тапом по зданию (подписей над зданиями больше нет -- всё здесь).
|
||
// У каждого здания своё окно:
|
||
// - добывающее (Лесопилка, Ферма, Каменоломня, Шахта, Рудник): рабочие этой профессии и кнопки "−" / "+",
|
||
// добыча в час, сколько ресурса на складе и когда склад заполнится, что даст улучшение и его цена;
|
||
// - Склад и Амбар: запас / лимит по их ресурсам, что даст улучшение и цена;
|
||
// - Дом: сколько жилья даёт, население / предел, улучшение и цена;
|
||
// - Ратуша: жильё, резерв для найма, что откроется на следующем уровне, цена улучшения;
|
||
// - Кристалл: где добывается, запас / лимит;
|
||
// - Казармы: очередь обучения, какие войска открыты и что нужно для остальных, улучшение (открывает войска),
|
||
// кнопка "Нанять войска" (панель "Войска").
|
||
// Без здания (кнопка "Рабочие" в панели DEV и "Все рабочие" в окне) -- список рабочих по всем профессиям.
|
||
// Окно -- только интерфейс: рабочих ставит модуль Населения, добычу считает модуль Экономики.
|
||
// Пока окно открыто, цифры обновляются сами (раз в полсекунды).
|
||
public static class BuildingWindow
|
||
{
|
||
static GameObject root;
|
||
static GameObject focus; // здание, по которому тапнули (null -- список рабочих)
|
||
static readonly List<Action> live = new List<Action>(); // что обновлять, пока окно открыто
|
||
|
||
public static void OnWorkplaceTapped(ResourceCollector building) { Open(building != null ? building.gameObject : null); }
|
||
public static void OnStorageTapped(StorageBuilding building) { Open(building != null ? building.gameObject : null); }
|
||
public static void OnTownHallTapped(TownHall townHall) { Open(townHall != null ? townHall.gameObject : null); }
|
||
|
||
public static void Open(GameObject building)
|
||
{
|
||
// В режиме стройки тап -- это выбор места, а не здания
|
||
if (BuildModeController.Instance != null && BuildModeController.Instance.IsActive) return;
|
||
focus = building;
|
||
Rebuild();
|
||
}
|
||
|
||
public static void Close()
|
||
{
|
||
if (root != null) UnityEngine.Object.Destroy(root);
|
||
root = null;
|
||
focus = null;
|
||
live.Clear();
|
||
}
|
||
|
||
static void Rebuild()
|
||
{
|
||
if (root != null) UnityEngine.Object.Destroy(root);
|
||
live.Clear();
|
||
var card = UIKit.Modal(TitleFor(focus), out root);
|
||
root.AddComponent<BuildingWindowTicker>();
|
||
|
||
var rc = focus != null ? focus.GetComponent<ResourceCollector>() : null;
|
||
var storage = focus != null ? focus.GetComponent<StorageBuilding>() : null;
|
||
var townHall = focus != null ? focus.GetComponent<TownHall>() : null;
|
||
var barracks = focus != null ? focus.GetComponent<Barracks>() : null;
|
||
|
||
if (focus == null) WorkersList(card);
|
||
else if (townHall != null) TownHallSection(card, townHall);
|
||
else if (barracks != null) BarracksSection(card, barracks);
|
||
else if (rc != null && rc.IsWorkplace) WorkplaceSection(card, rc);
|
||
else if (rc != null && rc.IsHouse) HouseSection(card, rc);
|
||
else if (rc != null && rc.resourceType == ResourceType.Gems) CrystalSection(card);
|
||
else if (storage != null) StorageSection(card, storage);
|
||
|
||
UIKit.Button(card, "Закрыть", UIKit.Theme.Gray, Close, -1, 80, 28);
|
||
}
|
||
|
||
internal static void Tick()
|
||
{
|
||
if (root == null) return;
|
||
if (focus == null && FocusWasDestroyed) { Close(); return; }
|
||
foreach (var update in live) update();
|
||
}
|
||
|
||
static bool focusSet;
|
||
static bool FocusWasDestroyed => focusSet && focus == null;
|
||
|
||
static string TitleFor(GameObject building)
|
||
{
|
||
focusSet = building != null;
|
||
if (building == null) return "РАБОЧИЕ";
|
||
var townHall = building.GetComponent<TownHall>();
|
||
if (townHall != null) return $"РАТУША — {townHall.currentLevel} УР.";
|
||
var id = building.GetComponent<PlacedBuildingId>();
|
||
var leveled = building.GetComponent<ILeveledBuilding>();
|
||
string name = id != null && !string.IsNullOrEmpty(id.buildingName) ? id.buildingName : building.name;
|
||
return leveled != null ? $"{name.ToUpper()} — {leveled.Level} УР." : name.ToUpper();
|
||
}
|
||
|
||
// ---------------- Добывающее здание ----------------
|
||
|
||
static void WorkplaceSection(RectTransform card, ResourceCollector rc)
|
||
{
|
||
if (!WorkJobs.TryFromResource(rc.resourceType, out WorkJob job)) return;
|
||
var production = GameModules.Production;
|
||
if (production == null || !GameServices.TryGet(out IPopulation population) || !GameServices.TryGet(out IEconomy economy))
|
||
{
|
||
UIKit.Text(card, "Модули Населения и Экономики не подключены", 26, UIKit.Theme.Red, TextAlignmentOptions.Center);
|
||
return;
|
||
}
|
||
ResourceType res = WorkJobs.Resource(job);
|
||
string resName = GameData.ResName(res).ToLower();
|
||
double norm = production.Rules.PerWorkerPerHour[(int)job];
|
||
|
||
// Что даёт это здание
|
||
int slots = production.BuildingSlots(rc.level);
|
||
double eff = production.BuildingEfficiency(rc.level);
|
||
UIKit.Text(card, $"Здание даёт {slots} мест, норма ×{eff:0.##} — до {slots * norm * eff:0.#} {resName} в час при полном штате.",
|
||
24, UIKit.Theme.TextDim, TextAlignmentOptions.Center);
|
||
|
||
// Рабочие профессии и кнопки
|
||
Header(card, $"Рабочие: {WorkJobs.Name(job).ToLower()}");
|
||
Live(card, () => $"{population.GetWorkers(job)} / {production.Slots(job)} мест в городе • свободных жителей: {population.FreeWorkers}",
|
||
26, UIKit.Theme.Text);
|
||
var row = UIKit.Row(card, 72, 8);
|
||
StepButton(row, "−5", job, -5);
|
||
StepButton(row, "−1", job, -1);
|
||
StepButton(row, "+1", job, +1);
|
||
StepButton(row, "+5", job, +5);
|
||
|
||
// Добыча и склад
|
||
Header(card, "Добыча и склад");
|
||
Live(card, () =>
|
||
{
|
||
float rate = production.RatePerHour(res);
|
||
string famine = population.IsStarving ? " <color=#ff7a7a>голод: добыча ×0.5</color>" : "";
|
||
return $"Сейчас: <color=#9be29b>+{rate:0.#} {resName} в час</color>{famine}";
|
||
}, 26, UIKit.Theme.Text);
|
||
StorageBar(card, economy, res);
|
||
Live(card, () => FillTimeText(economy, res, production.RatePerHour(res)), 22, UIKit.Theme.TextDim);
|
||
|
||
// Улучшение
|
||
int nextSlots = production.BuildingSlots(rc.level + 1);
|
||
double nextEff = production.BuildingEfficiency(rc.level + 1);
|
||
UpgradeBlock(card, rc.level,
|
||
$"мест {slots} → {nextSlots}, норма ×{eff:0.##} → ×{nextEff:0.##}",
|
||
() => rc.Level = rc.level + 1);
|
||
|
||
UIKit.Button(card, "Все рабочие", UIKit.Theme.Blue, () => Open(null), -1, 70, 24);
|
||
}
|
||
|
||
// ---------------- Дом ----------------
|
||
|
||
static void HouseSection(RectTransform card, ResourceCollector house)
|
||
{
|
||
int perLevel = EconomyConfig.Load().population.housingPerHouseLevel;
|
||
UIKit.Text(card, $"Дом даёт +{perLevel * house.level} к пределу жилья (+{perLevel} за уровень).", 24, UIKit.Theme.TextDim, TextAlignmentOptions.Center);
|
||
if (GameServices.TryGet(out IPopulation population))
|
||
{
|
||
Header(card, "Население");
|
||
LiveBar(card, () => population.HousingCap > 0 ? (float)population.Total / population.HousingCap : 0f,
|
||
() => $"{population.Total} / {population.HousingCap} человек", UIKit.Theme.Blue);
|
||
Live(card, () => population.Total >= population.HousingCap
|
||
? "Предел жилья: прирост стоит, нужны Дома или Ратуша выше"
|
||
: $"Прирост: +{population.GrowthPerHour:0.#} человека в час", 22, UIKit.Theme.TextDim);
|
||
}
|
||
UpgradeBlock(card, house.level, $"жильё +{perLevel * house.level} → +{perLevel * (house.level + 1)}", () => house.Level = house.level + 1);
|
||
}
|
||
|
||
// ---------------- Кристалл ----------------
|
||
|
||
static void CrystalSection(RectTransform card)
|
||
{
|
||
UIKit.Text(card, "Синий кристалл добывается в кристальных разломах на карте мира. Здание не улучшается.", 24, UIKit.Theme.TextDim, TextAlignmentOptions.Center);
|
||
if (GameServices.TryGet(out IEconomy economy)) StorageBar(card, economy, ResourceType.Gems);
|
||
}
|
||
|
||
// ---------------- Склад и Амбар ----------------
|
||
|
||
static void StorageSection(RectTransform card, StorageBuilding storage)
|
||
{
|
||
var s = EconomyConfig.Load().storage;
|
||
bool warehouse = storage.kind == StorageBuilding.Kind.Warehouse;
|
||
float perLevel = warehouse ? s.perWarehouseLevel : s.perGranaryLevel;
|
||
UIKit.Text(card, warehouse
|
||
? $"Склад даёт +{perLevel * storage.level:0} к лимиту дерева, камня, металла и золота."
|
||
: $"Амбар даёт +{perLevel * storage.level:0} к лимиту еды.",
|
||
24, UIKit.Theme.TextDim, TextAlignmentOptions.Center);
|
||
|
||
if (GameServices.TryGet(out IEconomy economy))
|
||
{
|
||
Header(card, "Запас / лимит");
|
||
if (warehouse)
|
||
{
|
||
StorageBar(card, economy, ResourceType.Wood);
|
||
StorageBar(card, economy, ResourceType.Stone);
|
||
StorageBar(card, economy, ResourceType.Iron);
|
||
StorageBar(card, economy, ResourceType.Gold);
|
||
}
|
||
else StorageBar(card, economy, ResourceType.Food);
|
||
UIKit.Text(card, "Сверх лимита ресурс не прибавляется — пропадает.", 22, UIKit.Theme.TextDim, TextAlignmentOptions.Center);
|
||
}
|
||
|
||
UpgradeBlock(card, storage.level, $"лимит +{perLevel * storage.level:0} → +{perLevel * (storage.level + 1):0}",
|
||
() => storage.Level = storage.level + 1);
|
||
}
|
||
|
||
// ---------------- Ратуша ----------------
|
||
|
||
static void TownHallSection(RectTransform card, TownHall townHall)
|
||
{
|
||
var config = EconomyConfig.Load();
|
||
int level = townHall.currentLevel;
|
||
int next = level + 1;
|
||
var p = config.population;
|
||
|
||
UIKit.Text(card, $"Ратуша даёт {p.housingBase + p.housingPerTownHallLevel * (level - 1)} мест жилья. От её уровня зависят резерв для найма и доступные здания и войска.",
|
||
24, UIKit.Theme.TextDim, TextAlignmentOptions.Center);
|
||
|
||
if (GameServices.TryGet(out IPopulation population))
|
||
{
|
||
Header(card, "Город");
|
||
LiveBar(card, () => population.HousingCap > 0 ? (float)population.Total / population.HousingCap : 0f,
|
||
() => $"Население {population.Total} / {population.HousingCap}", UIKit.Theme.Blue);
|
||
Live(card, () => $"Жителей {population.Civilians}: рабочих {population.Workers}, свободных {population.Idle} • воинов {population.Soldiers}",
|
||
22, UIKit.Theme.TextDim);
|
||
Live(card, () => $"Резерв для найма: {population.CivilianReserve} • можно нанять: {population.Recruitable}", 22, UIKit.Theme.TextDim);
|
||
}
|
||
|
||
if (level >= townHall.maxLevel)
|
||
{
|
||
UIKit.Text(card, "Максимальный уровень", 26, UIKit.Theme.Gold, TextAlignmentOptions.Center);
|
||
return;
|
||
}
|
||
|
||
// Что даст следующий уровень
|
||
Header(card, $"Уровень {next}");
|
||
var gains = new List<string> { $"жильё +{p.housingPerTownHallLevel}" };
|
||
int nowReserve = ReserveAt(level), nextReserve = ReserveAt(next);
|
||
if (nextReserve != nowReserve) gains.Add($"резерв для найма {nowReserve} → {nextReserve}");
|
||
UIKit.Text(card, string.Join(", ", gains.ToArray()), 24, UIKit.Theme.Text, TextAlignmentOptions.Center);
|
||
string unlocks = UnlocksAt(next);
|
||
if (unlocks.Length > 0) UIKit.Text(card, "Откроется: " + unlocks, 24, UIKit.Theme.Gold, TextAlignmentOptions.Center);
|
||
|
||
var cost = BuildingData.ToAmounts(townHall.GetUpgradeCost());
|
||
UIKit.Text(card, "Цена: " + CostText(cost), 24, UIKit.Theme.Text, TextAlignmentOptions.Center);
|
||
bool canPay = townHall.CanUpgrade();
|
||
var b = UIKit.Button(card, $"Улучшить до {next} ур.", canPay ? UIKit.Theme.Green : UIKit.Theme.Gray, () =>
|
||
{
|
||
if (!townHall.CanUpgrade()) { UIKit.Toast("Не хватает ресурсов"); return; }
|
||
townHall.Upgrade();
|
||
if (CitySync.Instance != null) CitySync.Instance.SyncNow();
|
||
UIKit.Toast($"Ратуша улучшена до {townHall.currentLevel} уровня");
|
||
Rebuild();
|
||
}, -1, 80, 26);
|
||
b.interactable = canPay;
|
||
}
|
||
|
||
// ---------------- Казармы ----------------
|
||
|
||
static void BarracksSection(RectTransform card, Barracks barracks)
|
||
{
|
||
var gp = GameProgress.Instance;
|
||
UIKit.Text(card, $"Казармы обучают войска. Казарм в городе: {Barracks.Count}, мест в очереди: {Barracks.QueueSize}. " +
|
||
$"Новые войска открывает уровень самых прокачанных Казарм ({Barracks.MaxLevel} ур.) вместе с уровнем Ратуши.",
|
||
24, UIKit.Theme.TextDim, TextAlignmentOptions.Center);
|
||
|
||
if (gp != null)
|
||
{
|
||
Header(card, "Обучение");
|
||
Live(card, () =>
|
||
{
|
||
var training = gp.Training;
|
||
if (training.Count == 0) return "Никто не обучается";
|
||
var o = training[0];
|
||
string more = training.Count > 1 ? $" (ещё заказов: {training.Count - 1})" : "";
|
||
return $"{GameData.Troops[o.Type].name} ×{o.Count} — осталось {GameData.FormatTime(gp.TrainingSecondsLeft(o))}{more}";
|
||
}, 24, UIKit.Theme.Text);
|
||
if (GameServices.TryGet(out IPopulation population))
|
||
Live(card, () => $"Можно нанять из жителей: {population.Recruitable} • в Казармах: {population.InTraining}", 22, UIKit.Theme.TextDim);
|
||
|
||
Header(card, "Войска");
|
||
for (int i = 0; i < GameData.Troops.Count; i++)
|
||
{
|
||
string lockText = gp.TroopLockText(i);
|
||
UIKit.Text(card, lockText.Length == 0
|
||
? $"<color=#9be59b>{GameData.Troops[i].name} — открыты</color>"
|
||
: $"{GameData.Troops[i].name} — нужно: {lockText}",
|
||
24, lockText.Length == 0 ? UIKit.Theme.Text : UIKit.Theme.Gold, TextAlignmentOptions.Center);
|
||
}
|
||
}
|
||
|
||
// Что откроет следующий уровень этих Казарм
|
||
var opens = new List<string>();
|
||
if (gp != null)
|
||
for (int i = 0; i < GameData.Troops.Count; i++)
|
||
if (gp.RequiredBarracksLevel(i) == barracks.level + 1 && Barracks.MaxLevel <= barracks.level) opens.Add(GameData.Troops[i].name);
|
||
string effect = opens.Count > 0
|
||
? $"откроются: {string.Join(", ", opens.ToArray())} (если хватает уровня Ратуши)"
|
||
: "ближе к новым войскам";
|
||
UpgradeBlock(card, barracks.level, effect, () => barracks.Level = barracks.level + 1);
|
||
|
||
UIKit.Button(card, "Нанять войска", UIKit.Theme.Blue, () =>
|
||
{
|
||
Close();
|
||
if (UIPanelManager.Instance != null) UIPanelManager.Instance.Open("Troops");
|
||
}, -1, 80, 26);
|
||
}
|
||
|
||
// Резерв для найма на уровне Ратуши (те же числа, что у модуля Населения)
|
||
static int ReserveAt(int townHallLevel)
|
||
{
|
||
var table = GameModules.Population != null ? GameModules.Population.Rules.CivilianReserveByTownHallLevel : null;
|
||
if (table == null || table.Length == 0) return 0;
|
||
return table[Mathf.Clamp(townHallLevel, 1, table.Length) - 1];
|
||
}
|
||
|
||
// Здания и войска, которые открываются ровно на этом уровне Ратуши
|
||
static string UnlocksAt(int townHallLevel)
|
||
{
|
||
var names = new List<string>();
|
||
if (BuildManager.Instance != null && BuildManager.Instance.allBuildings != null)
|
||
foreach (var b in BuildManager.Instance.allBuildings)
|
||
if (b != null && b.requiredTownHallLevel == townHallLevel) names.Add(b.buildingName);
|
||
foreach (var t in GameData.Troops)
|
||
if (t.requiredTownHall == townHallLevel) names.Add(t.name);
|
||
return string.Join(", ", names.ToArray());
|
||
}
|
||
|
||
// ---------------- Улучшение (общее для зданий с уровнем, кроме Ратуши) ----------------
|
||
|
||
static void UpgradeBlock(RectTransform card, int level, string effect, Action raiseLevel)
|
||
{
|
||
var config = EconomyConfig.Load();
|
||
if (level >= config.upgrade.maxLevel)
|
||
{
|
||
UIKit.Text(card, "Максимальный уровень", 26, UIKit.Theme.Gold, TextAlignmentOptions.Center);
|
||
return;
|
||
}
|
||
Header(card, $"Улучшение до {level + 1} ур.");
|
||
UIKit.Text(card, effect, 24, UIKit.Theme.Text, TextAlignmentOptions.Center);
|
||
var cost = config.UpgradeCost(level);
|
||
UIKit.Text(card, "Цена: " + CostText(cost), 24, UIKit.Theme.Text, TextAlignmentOptions.Center);
|
||
|
||
bool canPay = GameServices.TryGet(out IEconomy economy) && economy.CanAfford(cost);
|
||
var b = UIKit.Button(card, $"Улучшить до {level + 1} ур.", canPay ? UIKit.Theme.Green : UIKit.Theme.Gray, () =>
|
||
{
|
||
if (!GameServices.TryGet(out IEconomy e) || !e.TrySpend(cost)) { UIKit.Toast("Не хватает ресурсов"); return; }
|
||
raiseLevel();
|
||
if (CitySync.Instance != null) CitySync.Instance.SyncNow();
|
||
UIKit.Toast($"Улучшено до {level + 1} уровня");
|
||
Rebuild();
|
||
}, -1, 80, 26);
|
||
b.interactable = canPay;
|
||
}
|
||
|
||
// Цена: чего хватает -- зелёным, чего нет -- красным, со сколько есть
|
||
static string CostText(ResourceAmount[] cost)
|
||
{
|
||
GameServices.TryGet(out IEconomy economy);
|
||
var parts = new string[cost.Length];
|
||
for (int i = 0; i < cost.Length; i++)
|
||
{
|
||
float have = economy != null ? economy.Get(cost[i].Type) : 0f;
|
||
bool ok = have >= cost[i].Amount;
|
||
string name = GameData.ResName(cost[i].Type).ToLower();
|
||
parts[i] = ok
|
||
? $"<color=#9be59b>{cost[i].Amount:0}</color> {name}"
|
||
: $"<color=#ff7a7a>{cost[i].Amount:0}</color> {name} (есть {have:0})";
|
||
}
|
||
return string.Join(", ", parts);
|
||
}
|
||
|
||
// ---------------- Склад: полоска и время до заполнения ----------------
|
||
|
||
static void StorageBar(RectTransform card, IEconomy economy, ResourceType res)
|
||
{
|
||
string name = GameData.ResName(res);
|
||
LiveBar(card, () =>
|
||
{
|
||
float cap = economy.GetCapacity(res);
|
||
return float.IsInfinity(cap) || cap <= 0 ? 0f : economy.Get(res) / cap;
|
||
},
|
||
() =>
|
||
{
|
||
float cap = economy.GetCapacity(res);
|
||
return float.IsInfinity(cap) ? $"{name}: {economy.Get(res):0}" : $"{name}: {economy.Get(res):0} / {cap:0}";
|
||
},
|
||
new Color(0.85f, 0.65f, 0.25f));
|
||
}
|
||
|
||
static string FillTimeText(IEconomy economy, ResourceType res, float ratePerHour)
|
||
{
|
||
float cap = economy.GetCapacity(res), have = economy.Get(res);
|
||
if (float.IsInfinity(cap)) return "";
|
||
if (have >= cap) return "<color=#ff7a7a>Склад полон — добыча пропадает. Улучшите Склад или Амбар.</color>";
|
||
if (ratePerHour <= 0) return "Добычи нет — поставьте рабочих";
|
||
float hours = (cap - have) / ratePerHour;
|
||
return hours < 1f ? $"Склад заполнится через {Mathf.CeilToInt(hours * 60)} мин" : $"Склад заполнится через {hours:0.#} ч";
|
||
}
|
||
|
||
// ---------------- Список рабочих по всем профессиям ----------------
|
||
|
||
static void WorkersList(RectTransform card)
|
||
{
|
||
if (!GameServices.TryGet(out IPopulation pop) || !GameServices.TryGet(out IProduction prod))
|
||
{
|
||
UIKit.Text(card, "Модули Населения и Экономики не подключены", 26, UIKit.Theme.Red, TextAlignmentOptions.Center);
|
||
return;
|
||
}
|
||
|
||
for (int j = 0; j < WorkJobs.Count; j++)
|
||
{
|
||
var job = (WorkJob)j;
|
||
var row = UIKit.Row(card, 72, 8);
|
||
var label = Live(row, () =>
|
||
{
|
||
int slots = prod.Slots(job);
|
||
if (slots <= 0) return $"{WorkJobs.Name(job)}: нет зданий";
|
||
return $"{WorkJobs.Name(job)}: {pop.GetWorkers(job)} / {slots} <color=#9be29b>+{prod.RatePerHour(WorkJobs.Resource(job)):0.#}/ч</color>";
|
||
}, 24, UIKit.Theme.Text);
|
||
label.enableAutoSizing = true; label.fontSizeMin = 14; label.fontSizeMax = 24;
|
||
label.alignment = TextAlignmentOptions.Left;
|
||
UIKit.Size(label, -1, 72, 1);
|
||
|
||
StepButton(row, "−5", job, -5);
|
||
StepButton(row, "−1", job, -1);
|
||
StepButton(row, "+1", job, +1);
|
||
StepButton(row, "+5", job, +5);
|
||
}
|
||
|
||
Live(card, () => $"Свободных жителей: <b>{pop.FreeWorkers}</b> из {pop.Civilians}", 24, UIKit.Theme.Gold);
|
||
}
|
||
|
||
// Кнопка "−" / "+": активна, только если есть кого снять или куда и кого поставить
|
||
static void StepButton(Transform row, string label, WorkJob job, int delta)
|
||
{
|
||
Button b = null;
|
||
b = UIKit.Button(row, label, delta > 0 ? UIKit.Theme.Blue : UIKit.Theme.Gray, () =>
|
||
{
|
||
if (GameServices.TryGet(out IPopulation pop)) pop.SetWorkers(job, pop.GetWorkers(job) + delta);
|
||
Tick();
|
||
}, 86, 64, 24);
|
||
live.Add(() =>
|
||
{
|
||
if (b == null || !GameServices.TryGet(out IPopulation pop) || !GameServices.TryGet(out IProduction prod)) return;
|
||
int workers = pop.GetWorkers(job), slots = prod.Slots(job);
|
||
b.interactable = delta < 0 ? workers > 0 : slots > workers && pop.FreeWorkers > 0;
|
||
});
|
||
}
|
||
|
||
// ---------------- Мелочи ----------------
|
||
|
||
static void Header(RectTransform card, string text)
|
||
{
|
||
UIKit.Text(card, text, 28, UIKit.Theme.Gold, TextAlignmentOptions.Center).fontStyle = FontStyles.Bold;
|
||
}
|
||
|
||
// Текст, который обновляется, пока окно открыто
|
||
static TextMeshProUGUI Live(Transform parent, Func<string> text, float size, Color color)
|
||
{
|
||
var t = UIKit.Text(parent, text(), size, color, TextAlignmentOptions.Center);
|
||
live.Add(() => { if (t != null) t.text = text(); });
|
||
return t;
|
||
}
|
||
|
||
static void LiveBar(RectTransform card, Func<float> fraction, Func<string> label, Color color)
|
||
{
|
||
var bar = UIKit.Bar(card, fraction(), color, 34, label());
|
||
live.Add(() => { if (bar != null) UIKit.SetBar(bar, fraction(), label()); });
|
||
}
|
||
}
|
||
|
||
// Обновляет открытое окно здания раз в полсекунды (добыча идёт, склад наполняется)
|
||
public class BuildingWindowTicker : MonoBehaviour
|
||
{
|
||
float next;
|
||
|
||
void Update()
|
||
{
|
||
if (Time.unscaledTime < next) return;
|
||
next = Time.unscaledTime + 0.5f;
|
||
BuildingWindow.Tick();
|
||
}
|
||
}
|