- Ядро (Bogatyri.Core): контракты модулей, EventBus, GameServices, SaveRegistry - Модули: Экономика, Поселение, Армия, Глобальный мир, Боевая система, Сохранения (каждый — отдельный asmdef, видит только ядро) - App/GameModules: регистрация модулей и связь с GameProgress/Казармами - Сохранения v2 по секциям модулей, перенос старого save.json и походов из progress.json - Сброс сохранений: ISaveSystem.ResetAll, ResetState у модулей, меню Tools - EditMode-тесты модулей, документы по каждому модулю с журналом изменений Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
183 lines
8.2 KiB
C#
183 lines
8.2 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
|
||
// МОДУЛЬ ПОСЕЛЕНИЯ: точка входа. Реализует ISettlement и сам сохраняет город (секция "settlement").
|
||
// Документ: Docs/Modules/Settlement.md
|
||
//
|
||
// Город живёт на сцене MVP1, а сохраняться игра может и на карте мира. Поэтому модуль держит
|
||
// последнее известное состояние города в памяти: на карте мира сохраняется оно, а не пустая сцена.
|
||
public static class SettlementModule
|
||
{
|
||
[Serializable]
|
||
public class PlacedBuildingSaveEntry
|
||
{
|
||
public string buildingName; // совпадает с BuildingData.buildingName
|
||
public float posX;
|
||
public float posY;
|
||
public int level;
|
||
}
|
||
|
||
// Имена полей совпадают со старым save.json -- старые сохранения читаются без переделки
|
||
[Serializable]
|
||
public class SettlementSaveData
|
||
{
|
||
public int townHallLevel = 1;
|
||
public List<PlacedBuildingSaveEntry> placedBuildings = new List<PlacedBuildingSaveEntry>();
|
||
}
|
||
|
||
static SettlementSaveData cached; // последнее состояние города (из сцены или из файла)
|
||
static BuildManager appliedToBuildManager; // BuildManager сцены, для которой город уже восстановлен
|
||
static readonly Service service = new Service();
|
||
|
||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterAssembliesLoaded)]
|
||
static void Init()
|
||
{
|
||
cached = null;
|
||
appliedToBuildManager = null;
|
||
GameServices.Register<ISettlement>(service);
|
||
SaveRegistry.Register(service);
|
||
}
|
||
|
||
static bool CityLoaded => BuildManager.Instance != null;
|
||
|
||
// ---------------- Сцена города <-> данные ----------------
|
||
|
||
static SettlementSaveData CaptureScene()
|
||
{
|
||
var data = new SettlementSaveData();
|
||
if (TownHall.Instance != null) data.townHallLevel = TownHall.Instance.currentLevel;
|
||
|
||
foreach (var placed in UnityEngine.Object.FindObjectsByType<PlacedBuildingId>(FindObjectsSortMode.None))
|
||
{
|
||
if (string.IsNullOrEmpty(placed.buildingName)) continue; // уже убрано, Destroy сработает в конце кадра
|
||
var leveled = placed.GetComponent<ILeveledBuilding>();
|
||
data.placedBuildings.Add(new PlacedBuildingSaveEntry
|
||
{
|
||
buildingName = placed.buildingName,
|
||
posX = placed.transform.position.x,
|
||
posY = placed.transform.position.y,
|
||
level = leveled != null ? leveled.Level : 1
|
||
});
|
||
}
|
||
return data;
|
||
}
|
||
|
||
// Восстановить город на сцене из последнего известного состояния.
|
||
// Без force -- один раз на каждую загрузку сцены города (BuildManager.Start и загрузка сохранения
|
||
// могут прийти в любом порядке, повторное восстановление не нужно).
|
||
public static void ApplyToScene(bool force = false)
|
||
{
|
||
if (cached == null || !CityLoaded) return;
|
||
if (!force && appliedToBuildManager == BuildManager.Instance) return;
|
||
appliedToBuildManager = BuildManager.Instance;
|
||
|
||
EventBus.Publish(new SettlementRestoringEvent()); // например, Казармы должны попасть в список стройки
|
||
|
||
if (TownHall.Instance != null) TownHall.Instance.currentLevel = cached.townHallLevel;
|
||
|
||
// Сначала убираем уже заспавненные здания, чтобы не задвоить постройки
|
||
ClearPlacedBuildings();
|
||
|
||
int restored = 0;
|
||
foreach (var entry in cached.placedBuildings)
|
||
{
|
||
BuildingData matchingData = FindBuildingDataByName(entry.buildingName);
|
||
if (matchingData == null)
|
||
{
|
||
Debug.LogWarning($"[ПОСЕЛЕНИЕ] Не найден BuildingData для '{entry.buildingName}', здание пропущено.");
|
||
continue;
|
||
}
|
||
|
||
Vector3 pos = new Vector3(entry.posX, entry.posY, 0f);
|
||
GameObject built = UnityEngine.Object.Instantiate(matchingData.prefab, pos, Quaternion.identity);
|
||
if (!built.activeSelf) built.SetActive(true); // шаблон казарм хранится выключенным
|
||
|
||
built.AddComponent<PlacedBuildingId>().buildingName = entry.buildingName;
|
||
|
||
var leveled = built.GetComponent<ILeveledBuilding>();
|
||
if (leveled != null) leveled.Level = entry.level;
|
||
|
||
// Помечаем слот занятым, чтобы поверх нельзя было поставить ещё одно здание
|
||
BuildSlot slot = BuildManager.Instance.FindSlotAtPosition(pos);
|
||
if (slot != null)
|
||
{
|
||
slot.Occupy(built);
|
||
built.AddComponent<BuildingSlotLink>().linkedSlot = slot;
|
||
}
|
||
restored++;
|
||
}
|
||
|
||
Debug.Log($"[ПОСЕЛЕНИЕ] Город восстановлен. Построек: {restored}");
|
||
}
|
||
|
||
// Убрать все постройки со сцены. Участок освобождаем сразу: Destroy срабатывает в конце кадра,
|
||
// и иначе старое здание освободило бы участок, на который уже встало новое
|
||
static void ClearPlacedBuildings()
|
||
{
|
||
foreach (var e in UnityEngine.Object.FindObjectsByType<PlacedBuildingId>(FindObjectsSortMode.None))
|
||
{
|
||
var link = e.GetComponent<BuildingSlotLink>();
|
||
if (link != null && link.linkedSlot != null)
|
||
{
|
||
link.linkedSlot.Free();
|
||
link.linkedSlot = null;
|
||
}
|
||
e.buildingName = null; // чтобы сохранение в этом же кадре уже не видело здание
|
||
UnityEngine.Object.Destroy(e.gameObject);
|
||
}
|
||
}
|
||
|
||
static BuildingData FindBuildingDataByName(string buildingName)
|
||
{
|
||
foreach (var b in BuildManager.Instance.allBuildings)
|
||
{
|
||
if (b != null && b.buildingName == buildingName) return b;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
// ---------------- Контракты ----------------
|
||
|
||
class Service : ISettlement, ISaveParticipant
|
||
{
|
||
public int TownHallLevel =>
|
||
TownHall.Instance != null ? TownHall.Instance.currentLevel : (cached?.townHallLevel ?? 1);
|
||
|
||
public int BuildingCount =>
|
||
CityLoaded
|
||
? UnityEngine.Object.FindObjectsByType<PlacedBuildingId>(FindObjectsSortMode.None).Length
|
||
: (cached?.placedBuildings.Count ?? 0);
|
||
|
||
public bool IsCityLoaded => CityLoaded;
|
||
|
||
public string SaveKey => "settlement";
|
||
|
||
public string Capture()
|
||
{
|
||
if (CityLoaded) cached = CaptureScene();
|
||
// Город не открыт и ни разу не загружался -- не трогаем то, что уже лежит в файле
|
||
return cached != null ? JsonUtility.ToJson(cached) : null;
|
||
}
|
||
|
||
// Новая игра: Ратуша 1 уровня, построек нет -- и на сцене, и в памяти модуля
|
||
public void ResetState()
|
||
{
|
||
cached = new SettlementSaveData();
|
||
if (!CityLoaded) return;
|
||
|
||
ClearPlacedBuildings();
|
||
if (TownHall.Instance != null) TownHall.Instance.currentLevel = 1;
|
||
appliedToBuildManager = BuildManager.Instance; // город уже в состоянии новой игры
|
||
EventBus.Publish(new TownHallLevelChangedEvent { Level = 1 });
|
||
}
|
||
|
||
public void Restore(string json)
|
||
{
|
||
cached = JsonUtility.FromJson<SettlementSaveData>(json) ?? new SettlementSaveData();
|
||
if (cached.placedBuildings == null) cached.placedBuildings = new List<PlacedBuildingSaveEntry>();
|
||
ApplyToScene(force: true);
|
||
}
|
||
}
|
||
}
|