137 lines
6.2 KiB
C#
137 lines
6.2 KiB
C#
using System.Collections.Generic;
|
||
using System.Text;
|
||
using UnityEngine;
|
||
|
||
// Слой приложения: сообщает модулям, какие здания стоят в городе.
|
||
// - рабочие места (Лес, Еда, Камень, Металл, Золото) и их уровни -> модуль добычи (места и эффективность);
|
||
// - Дома (здание "Население") -> модуль Населения (предел жилья);
|
||
// - Склады и Амбары -> лимиты склада в Экономике и защита ресурсов от грабежа (App/ResourceProtection).
|
||
// Здания, которые ещё строятся (BuildTimer), не считаются: они пока не работают.
|
||
// Модули сами здания не ищут: они без Unity и живут и на карте мира, где города нет.
|
||
//
|
||
// Проверяет город раз в полсекунды и применяет изменения, только когда картина устоялась
|
||
// (два одинаковых замера подряд) -- чтобы не поймать город посреди восстановления из сохранения.
|
||
public class CitySync : MonoBehaviour
|
||
{
|
||
const float Interval = 0.5f;
|
||
|
||
public static CitySync Instance { get; private set; }
|
||
|
||
// Город открыт не в 2D-сцене, а в 3D (City3D): здания с PlacedBuildingId там тоже есть (05.11)
|
||
public static bool ExternalCity;
|
||
static bool CityOpen => BuildManager.Instance != null || ExternalCity;
|
||
|
||
float next;
|
||
string pending; // замер, который ждёт подтверждения
|
||
string applied; // последний применённый замер
|
||
|
||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)]
|
||
static void Create()
|
||
{
|
||
if (Instance != null) return;
|
||
var go = new GameObject("CitySync");
|
||
DontDestroyOnLoad(go);
|
||
Instance = go.AddComponent<CitySync>();
|
||
}
|
||
|
||
void OnDestroy()
|
||
{
|
||
if (Instance == this) Instance = null;
|
||
}
|
||
|
||
void Update()
|
||
{
|
||
if (Time.unscaledTime < next) return;
|
||
next = Time.unscaledTime + Interval;
|
||
if (!CityOpen) return; // город не открыт -- модули помнят последнее
|
||
|
||
var city = Scan();
|
||
if (city.Signature == applied) { pending = null; return; }
|
||
if (city.Signature != pending) { pending = city.Signature; return; } // ждём подтверждения
|
||
Apply(city);
|
||
}
|
||
|
||
// Применить сразу (после улучшения здания в окне -- чтобы окно показало новые места)
|
||
public void SyncNow()
|
||
{
|
||
if (!CityOpen) return;
|
||
Apply(Scan());
|
||
}
|
||
|
||
class City
|
||
{
|
||
public readonly List<KeyValuePair<WorkJob, int>> Workplaces = new List<KeyValuePair<WorkJob, int>>();
|
||
public int HouseLevels, WarehouseLevels, GranaryLevels;
|
||
public string Signature;
|
||
}
|
||
|
||
static City Scan()
|
||
{
|
||
var city = new City();
|
||
var sig = new List<string>();
|
||
foreach (var placed in FindObjectsByType<PlacedBuildingId>(FindObjectsSortMode.None))
|
||
{
|
||
if (string.IsNullOrEmpty(placed.buildingName)) continue; // убирается в этом кадре
|
||
if (BuildTimer.IsConstructing(placed)) continue; // ещё строится -- не работает
|
||
|
||
var rc = placed.GetComponent<ResourceCollector>();
|
||
if (rc != null)
|
||
{
|
||
if (WorkJobs.TryFromResource(rc.resourceType, out WorkJob job))
|
||
{
|
||
city.Workplaces.Add(new KeyValuePair<WorkJob, int>(job, rc.level));
|
||
sig.Add("w" + (int)job + ":" + rc.level);
|
||
}
|
||
else if (rc.IsHouse)
|
||
{
|
||
city.HouseLevels += Mathf.Max(1, rc.level);
|
||
sig.Add("h:" + rc.level);
|
||
}
|
||
}
|
||
|
||
var sb = placed.GetComponent<StorageBuilding>();
|
||
if (sb != null)
|
||
{
|
||
if (sb.kind == StorageBuilding.Kind.Warehouse) city.WarehouseLevels += Mathf.Max(1, sb.level);
|
||
else city.GranaryLevels += Mathf.Max(1, sb.level);
|
||
sig.Add("s" + (int)sb.kind + ":" + sb.level);
|
||
}
|
||
}
|
||
sig.Sort(System.StringComparer.Ordinal);
|
||
var text = new StringBuilder();
|
||
foreach (var s in sig) text.Append(s).Append(';');
|
||
city.Signature = text.ToString();
|
||
return city;
|
||
}
|
||
|
||
void Apply(City city)
|
||
{
|
||
applied = city.Signature;
|
||
pending = null;
|
||
|
||
var config = EconomyConfig.Load();
|
||
|
||
// Лимиты склада
|
||
var rm = ResourceManager.Instance;
|
||
if (rm != null)
|
||
{
|
||
ResourceType[] stored = { ResourceType.Wood, ResourceType.Stone, ResourceType.Iron, ResourceType.Gold, ResourceType.Food, ResourceType.Gems };
|
||
foreach (var t in stored) rm.SetCapacity(t, config.GetCapacity(t, city.WarehouseLevels, city.GranaryLevels));
|
||
}
|
||
ResourceProtection.SetLevels(city.WarehouseLevels, city.GranaryLevels); // защита от грабежа
|
||
|
||
var production = GameModules.Production;
|
||
var population = GameModules.Population;
|
||
if (production == null || population == null) return;
|
||
|
||
// Места для рабочих. Новые места (построили, улучшили) остаются пустыми, пока игрок сам
|
||
// не поставит рабочих (регламент 31.10). Мест стало меньше -- лишних рабочих снимаем.
|
||
production.SetBuildings(city.Workplaces);
|
||
population.SetHouseLevels(city.HouseLevels);
|
||
population.ClampJobs();
|
||
|
||
// Единственное автозаполнение -- стартовая расстановка, один раз: новая игра или первая загрузка старого сохранения
|
||
if (!population.JobsInitialized) population.AutoFillAll();
|
||
}
|
||
}
|