Files
MVP1/Assets/Scripts/App/CitySync.cs
T

137 lines
6.2 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 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();
}
}