2026-10-19 18:39:28 +02:00
|
|
|
|
using UnityEngine;
|
|
|
|
|
|
|
|
|
|
|
|
// ТОЧКА СБОРКИ МОДУЛЕЙ (слой приложения). Документ: Docs/Modules/README.md
|
|
|
|
|
|
//
|
|
|
|
|
|
// Здесь и только здесь модули соединяются с остальной игрой:
|
|
|
|
|
|
// 1. регистрируются реализации контрактов, которые не регистрируются сами;
|
|
|
|
|
|
// 2. события модулей подключаются к системам, которые ещё не разнесены по модулям (GameProgress, Казармы).
|
|
|
|
|
|
// Модули друг о друге ничего не знают -- всю "проводку" видно в одном файле.
|
|
|
|
|
|
public static class GameModules
|
|
|
|
|
|
{
|
2026-10-27 18:51:35 +03:00
|
|
|
|
// Модули экономики для слоя приложения (окна, синхронизация города). Модули друг друга так не берут -- только через контракты.
|
|
|
|
|
|
public static PopulationService Population { get; private set; }
|
|
|
|
|
|
public static ProductionService Production { get; private set; }
|
|
|
|
|
|
|
2026-10-19 18:39:28 +02:00
|
|
|
|
// После SubsystemRegistration (там ядро очищается) и раньше любых Awake
|
|
|
|
|
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
|
|
|
|
|
|
static void Register()
|
|
|
|
|
|
{
|
2026-10-28 19:03:14 +01:00
|
|
|
|
// Боевая система -- чистый C#, сама зарегистрироваться не может.
|
|
|
|
|
|
// Числа боя -- из таблицы BattleConfig, сила воина каждого типа -- из GameData.Troops
|
2026-10-27 16:34:58 +01:00
|
|
|
|
var unitPower = new int[TroopType.Count];
|
|
|
|
|
|
for (int i = 0; i < TroopType.Count && i < GameData.Troops.Count; i++) unitPower[i] = GameData.Troops[i].power;
|
2026-10-28 19:03:14 +01:00
|
|
|
|
GameServices.Register<IBattleSystem>(new BattleSystem(BattleConfig.Load().ToRules(unitPower)));
|
2026-10-19 18:39:28 +02:00
|
|
|
|
|
2026-10-31 16:05:55 +01:00
|
|
|
|
// Марши: скорости по типам -- в модуль Мира, слоты похода -- в модуль Армии (таблица MarchConfig)
|
|
|
|
|
|
var marchConfig = MarchConfig.Load();
|
|
|
|
|
|
MarchManager.SpeedRules = marchConfig.ToSpeedRules();
|
2026-10-31 16:28:50 +01:00
|
|
|
|
MarchManager.PointRules = WorldResourceConfig.Load().ToRules(); // ресурсные точки (регламент 31.10)
|
2026-10-31 17:05:17 +01:00
|
|
|
|
MarchManager.Patrols = marchConfig.ToPatrolRules(); // патрули-заглушки соседей (Разведка М1)
|
|
|
|
|
|
|
|
|
|
|
|
// Разведка -- чистый C#: туман, обзор, чужие армии (секция "intelligence"). Карту и армии читает у Мира
|
|
|
|
|
|
// через IWorldMap; досчитывает наблюдение перед каждым расчётом мира (WorldAdvancingEvent)
|
|
|
|
|
|
var intelConfig = IntelligenceConfig.Load();
|
|
|
|
|
|
var intelligence = new IntelligenceService(intelConfig.ToRules());
|
|
|
|
|
|
GameServices.Register<IIntelligence>(intelligence);
|
|
|
|
|
|
SaveRegistry.Register(intelligence);
|
|
|
|
|
|
EventBus.Subscribe<WorldAdvancingEvent>(e => intelligence.ObserveUntil(e.ToMs));
|
|
|
|
|
|
// «Обзор» для теста, пока нет дерева технологий (после загрузки и в новой игре)
|
|
|
|
|
|
void TestObservation() { if (intelConfig.testObservationLevel > 0) intelligence.ObservationLevel = intelConfig.testObservationLevel; }
|
|
|
|
|
|
TestObservation();
|
|
|
|
|
|
EventBus.Subscribe<GameLoadedEvent>(_ => TestObservation());
|
|
|
|
|
|
EventBus.Subscribe<GameResetEvent>(_ => TestObservation());
|
2026-10-31 16:05:55 +01:00
|
|
|
|
|
2026-10-23 18:33:04 +02:00
|
|
|
|
// Армия -- чистый C#, единственное место хранения войск (секция "army")
|
2026-11-04 15:16:43 +01:00
|
|
|
|
var army = new ArmyService(marchConfig.baseMarchSlots, HospitalConfig.Load().ToRules(), HospitalConfig.Load().ToMissingRules()); // госпиталь, пропавшие
|
2026-10-23 18:33:04 +02:00
|
|
|
|
GameServices.Register<IArmy>(army);
|
|
|
|
|
|
SaveRegistry.Register(army);
|
|
|
|
|
|
EventBus.Subscribe<TroopsChangedEvent>(_ => { if (GameProgress.HasInstance) GameProgress.Instance.MarkChanged(); });
|
|
|
|
|
|
EventBus.Subscribe<GameLoadedEvent>(_ => MigrateLegacyArmy(army));
|
2026-11-01 13:19:34 +01:00
|
|
|
|
// Очередь обучения -- у Армии (решения 31.10, п. 8): статистика заданий по её событию, старая очередь -- переносится
|
|
|
|
|
|
EventBus.Subscribe<TroopsTrainedEvent>(e => { if (GameProgress.HasInstance) GameProgress.Instance.OnTroopsTrained(e); });
|
|
|
|
|
|
EventBus.Subscribe<GameLoadedEvent>(_ => MigrateLegacyTraining(army));
|
2026-11-04 15:16:43 +01:00
|
|
|
|
// Пропавшие без вести вернулись / списаны (решения 01.11, п. 5)
|
|
|
|
|
|
EventBus.Subscribe<MissingReturnedEvent>(e =>
|
|
|
|
|
|
{
|
|
|
|
|
|
if (e.Healthy + e.Wounded > 0)
|
|
|
|
|
|
UIKit.Toast($"Вернулись пропавшие без вести: {e.Healthy + e.Wounded}" + (e.Wounded > 0 ? $" (ранены {e.Wounded} -- в госпиталь)" : ""));
|
|
|
|
|
|
if (GameProgress.HasInstance) GameProgress.Instance.MarkChanged();
|
|
|
|
|
|
});
|
|
|
|
|
|
EventBus.Subscribe<MissingWrittenOffEvent>(e =>
|
|
|
|
|
|
{
|
|
|
|
|
|
if (e.Count > 0) UIKit.Toast($"Не вернулись и списаны пропавшие без вести: {e.Count}");
|
|
|
|
|
|
if (GameProgress.HasInstance) GameProgress.Instance.MarkChanged();
|
|
|
|
|
|
});
|
2026-11-01 13:32:25 +01:00
|
|
|
|
// Раненые вылечены -- вернулись в город
|
|
|
|
|
|
EventBus.Subscribe<SoldiersHealedEvent>(e =>
|
|
|
|
|
|
{
|
|
|
|
|
|
if (e.Type >= 0 && e.Type < GameData.Troops.Count) UIKit.Toast("Вылечены в госпитале: " + e.Count + " × " + GameData.Troops[e.Type].name);
|
|
|
|
|
|
if (GameProgress.HasInstance) GameProgress.Instance.MarkChanged();
|
|
|
|
|
|
});
|
2026-10-19 18:39:28 +02:00
|
|
|
|
|
2026-10-27 18:51:35 +03:00
|
|
|
|
// Население -- чистый C#: жители, рабочие, еда, прирост (секция "population"). Числа -- из EconomyConfig.
|
2026-10-27 18:04:27 +03:00
|
|
|
|
var config = EconomyConfig.Load();
|
|
|
|
|
|
var population = new PopulationService(ToPopulationRules(config));
|
2026-10-27 18:51:35 +03:00
|
|
|
|
Population = population;
|
2026-10-27 18:04:27 +03:00
|
|
|
|
GameServices.Register<IPopulation>(population);
|
|
|
|
|
|
SaveRegistry.Register(population);
|
2026-10-27 18:51:35 +03:00
|
|
|
|
|
|
|
|
|
|
// Добыча через рабочих -- чистый C# (секция "production"). Какие здания стоят, сообщает App/CitySync.
|
|
|
|
|
|
var production = new ProductionService(ToProductionRules(config));
|
|
|
|
|
|
Production = production;
|
|
|
|
|
|
GameServices.Register<IProduction>(production);
|
|
|
|
|
|
SaveRegistry.Register(production);
|
|
|
|
|
|
|
|
|
|
|
|
// Склад и Амбар появляются в списке стройки кодом -- до восстановления зданий из сохранения
|
|
|
|
|
|
EventBus.Subscribe<SettlementRestoringEvent>(_ => StorageBootstrap.Ensure());
|
|
|
|
|
|
|
|
|
|
|
|
// Тап по зданию -> окно здания и рабочих (статические события переживают Play в редакторе -- сначала отписка)
|
|
|
|
|
|
ResourceCollector.Tapped -= BuildingWindow.OnWorkplaceTapped;
|
|
|
|
|
|
ResourceCollector.Tapped += BuildingWindow.OnWorkplaceTapped;
|
|
|
|
|
|
StorageBuilding.Tapped -= BuildingWindow.OnStorageTapped;
|
|
|
|
|
|
StorageBuilding.Tapped += BuildingWindow.OnStorageTapped;
|
2026-10-31 20:52:28 +03:00
|
|
|
|
TownHall.Tapped -= BuildingWindow.OnTownHallTapped;
|
|
|
|
|
|
TownHall.Tapped += BuildingWindow.OnTownHallTapped;
|
2026-11-02 00:01:51 +03:00
|
|
|
|
|
|
|
|
|
|
// Стройка и улучшение по времени, строители (01.11, ТЕСТ): числа -- EconomyConfig, раздел "Стройка"
|
|
|
|
|
|
BuildTimer.Builders = Mathf.Max(1, config.construction.builders);
|
2026-11-04 23:36:14 +03:00
|
|
|
|
ConstructionClock.Init(); // офлайн: стройка вступает в силу в момент своего завершения (решения 04.11)
|
2026-11-02 00:01:51 +03:00
|
|
|
|
BuildTimer.DurationMs = (townHall, level) => EconomyConfig.Load().ConstructionMs(townHall, level);
|
|
|
|
|
|
BuildTimer.Tapped -= OnBuildTimerTapped;
|
|
|
|
|
|
BuildTimer.Tapped += OnBuildTimerTapped;
|
|
|
|
|
|
BuildTimer.Completed -= OnBuildCompleted;
|
|
|
|
|
|
BuildTimer.Completed += OnBuildCompleted;
|
|
|
|
|
|
|
2026-11-05 14:18:22 +03:00
|
|
|
|
// 3D-поселение (сцена City3D, 05.11): свой город, своя секция сохранения "settlement3d"
|
|
|
|
|
|
SaveRegistry.Register(City3DSave.Instance);
|
|
|
|
|
|
|
2026-11-02 00:01:51 +03:00
|
|
|
|
// Защита ресурсов от грабежа (01.11, ТЕСТ): сколько у города нельзя унести в набеге
|
|
|
|
|
|
GameServices.Register<IResourceProtection>(new ResourceProtection());
|
2026-10-27 18:04:27 +03:00
|
|
|
|
EventBus.Subscribe<GameLoadedEvent>(_ => population.OnGameLoaded(GameClock.UnixNow)); // офлайн-прогресс
|
|
|
|
|
|
EventBus.Subscribe<GameTimeAdvancedEvent>(e => population.Tick(e.UnixNow)); // перемотка (отладка)
|
|
|
|
|
|
EventBus.Subscribe<TroopsChangedEvent>(_ => population.Changed()); // воины -- тоже люди
|
|
|
|
|
|
EventBus.Subscribe<TownHallLevelChangedEvent>(_ => population.Changed()); // предел жилья вырос
|
|
|
|
|
|
EventBus.Subscribe<PopulationChangedEvent>(SyncPopulationResource);
|
|
|
|
|
|
EventBus.Subscribe<NewGameStartedEvent>(_ => GiveStartingKit(population, army, config));
|
|
|
|
|
|
EventBus.Subscribe<GameResetEvent>(_ => GiveStartingKit(population, army, config));
|
|
|
|
|
|
|
2026-10-19 19:07:45 +02:00
|
|
|
|
// Круг -- чистый C#, сам сохраняет, в каком Круге игрок (секция "circle")
|
|
|
|
|
|
var circle = new CircleService();
|
|
|
|
|
|
GameServices.Register<ICircle>(circle);
|
|
|
|
|
|
SaveRegistry.Register(circle);
|
|
|
|
|
|
|
|
|
|
|
|
// Чат -- чистый C#; история живёт, пока открыта игра (между сценами тоже)
|
|
|
|
|
|
GameServices.Register<IChat>(new ChatService(System.DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()));
|
|
|
|
|
|
|
2026-10-19 18:39:28 +02:00
|
|
|
|
// Казармы появляются в списке стройки кодом -- до восстановления зданий из сохранения
|
|
|
|
|
|
EventBus.Subscribe<SettlementRestoringEvent>(_ => BarracksBootstrap.Ensure());
|
|
|
|
|
|
|
2026-11-01 13:52:47 +01:00
|
|
|
|
// Госпиталь (решения 31.10, п. 7): здание города даёт места для лечения Армии через контракт IHospital
|
|
|
|
|
|
GameServices.Register<IHospital>(new HospitalBeds());
|
|
|
|
|
|
EventBus.Subscribe<SettlementRestoringEvent>(_ => HospitalBootstrap.Ensure());
|
|
|
|
|
|
|
2026-11-02 00:01:51 +03:00
|
|
|
|
// Терем богатыря: дом героя, герой гуляет по городу (Meta/Build/HeroCastle, HeroWalker)
|
|
|
|
|
|
EventBus.Subscribe<SettlementRestoringEvent>(_ => HeroCastleBootstrap.Ensure());
|
|
|
|
|
|
|
2026-10-19 18:39:28 +02:00
|
|
|
|
// Статистика для заданий (GameProgress) -- по событиям Мира
|
|
|
|
|
|
EventBus.Subscribe<MarchStartedEvent>(OnMarchStarted);
|
|
|
|
|
|
EventBus.Subscribe<MarchReturnedEvent>(OnMarchReturned);
|
2026-10-23 18:33:04 +02:00
|
|
|
|
|
|
|
|
|
|
// Окно "ВОЙСКО ВЕРНУЛОСЬ" в городе
|
|
|
|
|
|
MarchReturnPopup.Reset();
|
|
|
|
|
|
EventBus.Subscribe<MarchReturnedEvent>(MarchReturnPopup.OnMarchReturned);
|
2026-10-19 18:39:28 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-02 00:01:51 +03:00
|
|
|
|
static void OnBuildTimerTapped(BuildTimer timer)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (timer != null) BuildingWindow.Open(timer.gameObject);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void OnBuildCompleted(BuildTimer timer)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (timer == null) return;
|
|
|
|
|
|
if (CitySync.Instance != null) CitySync.Instance.SyncNow();
|
|
|
|
|
|
var id = timer.GetComponent<PlacedBuildingId>();
|
|
|
|
|
|
string name = timer.GetComponent<TownHall>() != null ? "Ратуша" : id != null ? id.buildingName : timer.name;
|
|
|
|
|
|
UIKit.Toast(timer.IsConstruction ? $"Построено: {name}" : $"{name}: улучшено до {timer.targetLevel} уровня");
|
|
|
|
|
|
BuildingWindow.OnBuildCompleted(timer);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-05 11:54:20 +03:00
|
|
|
|
// Сцена загружена, Awake всех объектов прошли, Start -- ещё нет (сохранение грузится в Start)
|
|
|
|
|
|
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
|
2026-10-19 18:39:28 +02:00
|
|
|
|
static void MigrateLegacyMarches()
|
|
|
|
|
|
{
|
|
|
|
|
|
// Раньше походы хранились в progress.json (GameProgress.Data.sashaMarches).
|
|
|
|
|
|
// Теперь их сохраняет модуль Мира в save.json, секция "world". Переносим один раз.
|
|
|
|
|
|
var data = GameProgress.Instance.Data;
|
|
|
|
|
|
if (data.sashaMarches == null || data.sashaMarches.Count == 0 || MarchManager.Instance == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
// Если в save.json уже есть секция "world", загрузка сохранения заменит эти походы на сохранённые
|
|
|
|
|
|
MarchManager.Instance.LoadMarches(new System.Collections.Generic.List<Bogatyri.World.MarchData>(data.sashaMarches));
|
|
|
|
|
|
|
|
|
|
|
|
// Из progress.json убираем, только когда походы уже записаны в save.json -- иначе вылет игры их потеряет
|
|
|
|
|
|
EventBus.Subscribe<GameSavedEvent>(ClearLegacyMarches);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void ClearLegacyMarches(GameSavedEvent e)
|
|
|
|
|
|
{
|
|
|
|
|
|
EventBus.Unsubscribe<GameSavedEvent>(ClearLegacyMarches);
|
|
|
|
|
|
GameProgress.Instance.Data.sashaMarches.Clear();
|
|
|
|
|
|
GameProgress.Instance.MarkChanged();
|
|
|
|
|
|
Debug.Log("[МОДУЛИ] Походы перенесены из progress.json в модуль Мира.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-23 18:33:04 +02:00
|
|
|
|
// Раньше войска хранились в progress.json (GameProgress.Data.troops и sashaMarchUnits моста ArmyBridge).
|
|
|
|
|
|
// Теперь -- в модуле Армии, save.json, секция "army". Переносим один раз, после загрузки сохранения.
|
|
|
|
|
|
static void MigrateLegacyArmy(ArmyService army)
|
|
|
|
|
|
{
|
|
|
|
|
|
var data = GameProgress.Instance.Data;
|
|
|
|
|
|
if (data.armyInModule) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (!army.RestoredFromSave)
|
|
|
|
|
|
{
|
|
|
|
|
|
var legacyMarches = new System.Collections.Generic.List<System.Collections.Generic.KeyValuePair<int, int[]>>();
|
|
|
|
|
|
foreach (var m in data.sashaMarchUnits)
|
|
|
|
|
|
legacyMarches.Add(new System.Collections.Generic.KeyValuePair<int, int[]>(m.marchId, m.units));
|
|
|
|
|
|
army.ImportLegacy(data.troops, legacyMarches);
|
|
|
|
|
|
Debug.Log($"[МОДУЛИ] Войска перенесены из progress.json в модуль Армии: в городе {army.SoldiersAtHome}, в походах {army.TroopsInMarches}.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Из progress.json убираем, только когда войска уже записаны в save.json -- иначе вылет игры их потеряет
|
|
|
|
|
|
if (army.RestoredFromSave) ClearLegacyArmy(default);
|
|
|
|
|
|
else EventBus.Subscribe<GameSavedEvent>(ClearLegacyArmy);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void ClearLegacyArmy(GameSavedEvent e)
|
|
|
|
|
|
{
|
|
|
|
|
|
EventBus.Unsubscribe<GameSavedEvent>(ClearLegacyArmy);
|
|
|
|
|
|
var data = GameProgress.Instance.Data;
|
|
|
|
|
|
data.armyInModule = true;
|
|
|
|
|
|
System.Array.Clear(data.troops, 0, data.troops.Length);
|
|
|
|
|
|
data.sashaMarchUnits.Clear();
|
|
|
|
|
|
GameProgress.Instance.MarkChanged();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-11-01 13:19:34 +01:00
|
|
|
|
// Очередь обучения раньше вёл GameProgress (progress.json, Data.training, время -- тики реальных часов).
|
|
|
|
|
|
// Теперь -- Армия (save.json, "army", мс часов игры). Переносим один раз, с тем же оставшимся временем.
|
|
|
|
|
|
static void MigrateLegacyTraining(ArmyService army)
|
|
|
|
|
|
{
|
|
|
|
|
|
var data = GameProgress.Instance.Data;
|
|
|
|
|
|
if (data.training == null || data.training.Count == 0) return;
|
|
|
|
|
|
|
|
|
|
|
|
// В Армии уже есть заказы -- перенос уже был (сохранение записалось, а progress.json -- ещё нет): только убрать старое
|
|
|
|
|
|
if (army.Training.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
long epochTicks = new System.DateTime(1970, 1, 1, 0, 0, 0, System.DateTimeKind.Utc).Ticks;
|
|
|
|
|
|
long offsetMs = GameClock.OffsetSeconds * 1000; // старые тики -- реальное время, Армия -- часы игры
|
|
|
|
|
|
var orders = new System.Collections.Generic.List<(int, int, long, long, bool)>();
|
|
|
|
|
|
foreach (var o in data.training)
|
|
|
|
|
|
orders.Add((o.troopIndex, o.count, (o.startTicks - epochTicks) / 10_000 + offsetMs, (o.endTicks - epochTicks) / 10_000 + offsetMs, o.recruited));
|
|
|
|
|
|
army.ImportLegacyTraining(orders);
|
|
|
|
|
|
Debug.Log($"[МОДУЛИ] Очередь обучения перенесена из progress.json в модуль Армии: заказов {orders.Count}.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Из progress.json убираем, только когда очередь уже записана в save.json -- иначе вылет игры её потеряет
|
|
|
|
|
|
EventBus.Subscribe<GameSavedEvent>(ClearLegacyTraining);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void ClearLegacyTraining(GameSavedEvent e)
|
|
|
|
|
|
{
|
|
|
|
|
|
EventBus.Unsubscribe<GameSavedEvent>(ClearLegacyTraining);
|
|
|
|
|
|
GameProgress.Instance.Data.training.Clear();
|
|
|
|
|
|
GameProgress.Instance.MarkChanged();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-27 18:04:27 +03:00
|
|
|
|
// ---------------- Население и экономика ----------------
|
|
|
|
|
|
|
|
|
|
|
|
static PopulationRules ToPopulationRules(EconomyConfig config)
|
|
|
|
|
|
{
|
|
|
|
|
|
var p = config.population;
|
|
|
|
|
|
return new PopulationRules
|
|
|
|
|
|
{
|
|
|
|
|
|
StartTotal = p.startTotal,
|
|
|
|
|
|
StartWorkers = p.startWorkers,
|
|
|
|
|
|
StartSoldiers = p.startSoldiers,
|
|
|
|
|
|
FoodPerCivilianPerHour = p.foodPerCivilianPerHour,
|
|
|
|
|
|
FoodPerSoldierPerHour = p.foodPerSoldierPerHour,
|
2026-11-04 15:13:02 +01:00
|
|
|
|
FoodPerTroopTypePerHour = p.foodPerTroopTypePerHour != null && p.foodPerTroopTypePerHour.Length > 0
|
|
|
|
|
|
? System.Array.ConvertAll(p.foodPerTroopTypePerHour, f => (double)f) : null,
|
2026-10-27 18:04:27 +03:00
|
|
|
|
GrowthPerHour = p.growthPerHour,
|
|
|
|
|
|
HousingBase = p.housingBase,
|
|
|
|
|
|
HousingPerTownHallLevel = p.housingPerTownHallLevel,
|
2026-10-27 18:51:35 +03:00
|
|
|
|
HousingPerHouseLevel = p.housingPerHouseLevel,
|
2026-10-28 22:11:36 +03:00
|
|
|
|
MaxOfflineHours = p.maxOfflineHours,
|
2026-10-31 19:34:33 +03:00
|
|
|
|
CivilianReserveByTownHallLevel = p.civilianReserveByTownHallLevel != null && p.civilianReserveByTownHallLevel.Length > 0
|
|
|
|
|
|
? (int[])p.civilianReserveByTownHallLevel.Clone()
|
|
|
|
|
|
: new PopulationRules().CivilianReserveByTownHallLevel,
|
2026-10-28 22:11:36 +03:00
|
|
|
|
PeoplePerSoldier = Mathf.Max(1, config.soldier.people)
|
2026-10-27 18:04:27 +03:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-27 18:51:35 +03:00
|
|
|
|
static ProductionRules ToProductionRules(EconomyConfig config)
|
|
|
|
|
|
{
|
|
|
|
|
|
var p = config.production;
|
|
|
|
|
|
return new ProductionRules
|
|
|
|
|
|
{
|
|
|
|
|
|
// порядок как в WorkJob: лесоруб, фермер, каменотёс, шахтёр-металл, шахтёр-золото
|
|
|
|
|
|
PerWorkerPerHour = new double[] { p.woodcutterPerHour, p.farmerPerHour, p.stonecutterPerHour, p.minerIronPerHour, p.minerGoldPerHour },
|
|
|
|
|
|
SlotsPerBuilding = p.workerSlotsT1,
|
|
|
|
|
|
SlotsPerLevel = p.workerSlotsPerLevel,
|
2026-10-31 19:34:33 +03:00
|
|
|
|
EfficiencyPerLevel = p.efficiencyPerLevel,
|
|
|
|
|
|
FamineNonFoodMultiplier = p.famineNonFoodMultiplier
|
2026-10-27 18:51:35 +03:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-27 18:04:27 +03:00
|
|
|
|
// Новая игра: стартовое войско (пехота) и стартовые ресурсы из EconomyConfig. Один раз на новую игру.
|
|
|
|
|
|
static void GiveStartingKit(PopulationService population, ArmyService army, EconomyConfig config)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!population.TryTakeStartingKit()) return;
|
|
|
|
|
|
|
|
|
|
|
|
if (population.Rules.StartSoldiers > 0) army.AddTroops(0, population.Rules.StartSoldiers);
|
|
|
|
|
|
|
|
|
|
|
|
if (GameServices.TryGet(out IEconomy economy))
|
|
|
|
|
|
{
|
|
|
|
|
|
ResourceType[] types = { ResourceType.Wood, ResourceType.Food, ResourceType.Stone, ResourceType.Iron, ResourceType.Gold, ResourceType.Gems };
|
|
|
|
|
|
foreach (var t in types)
|
|
|
|
|
|
{
|
|
|
|
|
|
float target = config.GetStartResource(t);
|
|
|
|
|
|
float current = economy.Get(t);
|
|
|
|
|
|
if (target > current) economy.Add(t, target - current); // не меньше стартового
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
Debug.Log($"[НАСЕЛЕНИЕ] Новая игра: {population.Total} человек, воинов {population.Soldiers}.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Верхняя панель показывает людей как ресурс "Люди" -- держим его равным населению
|
|
|
|
|
|
static void SyncPopulationResource(PopulationChangedEvent e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!GameServices.TryGet(out IEconomy economy)) return;
|
|
|
|
|
|
float delta = e.Total - economy.Get(ResourceType.Population);
|
|
|
|
|
|
if (Mathf.Abs(delta) >= 0.5f) economy.Add(ResourceType.Population, delta);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-19 18:39:28 +02:00
|
|
|
|
static void OnMarchStarted(MarchStartedEvent e)
|
|
|
|
|
|
{
|
|
|
|
|
|
var data = GameProgress.Instance.Data;
|
|
|
|
|
|
data.marchesSent++;
|
|
|
|
|
|
data.dailyMarches++;
|
|
|
|
|
|
GameProgress.Instance.MarkChanged();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void OnMarchReturned(MarchReturnedEvent e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (!e.Victory) return;
|
|
|
|
|
|
GameProgress.Instance.Data.worldWins++;
|
|
|
|
|
|
GameProgress.Instance.MarkChanged();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|