Files
riversong-code-showcase/Source/Riversong/Game/World/Buildings/BuildingManagerSystem.cs
2026-05-21 16:04:49 +02:00

324 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using UnityEngine.Pool;
namespace DanieleMarotta.RiversongCodeShowcase
{
[Service(typeof(IBuildingFactory))]
[Service(typeof(IDeleteBuildingService))]
[Service(typeof(IBuildingSpatialQuery))]
public class BuildingManagerSystem : GameSystem, IInitializable, IDisposable, IBuildingFactory, IDeleteBuildingService, IBuildingSpatialQuery
{
[InjectService]
private ISignalBus _signalBus;
[InjectService]
private IEntityCollection _entityCollection;
[InjectService]
private IProductStorageManager _productStorageManager;
[InjectService]
private IProductCatalog _productCatalog;
[InjectService]
private IProductStackFactory _productStackFactory;
[InjectService]
private IEntityCache _entityCache;
[InjectService]
private IAgentFactory _agentFactory;
[InjectService]
private IFailedPathCache _failedPathCache;
[InjectService]
private World _world;
[InjectService]
private GameConfig _config;
private Func<Building, int, bool> _fetchValidator;
private Func<Building, int, bool> _deliveryValidator;
private Func<Building, int, bool> _storageRequestValidator;
private Func<Building, int, int> _sourceRangeFunc;
private Func<Building, int, int> _candidateRangeFunc;
private Func<Building, int, int> _infiniteRangeFunc;
public BuildingManagerSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
_fetchValidator = FetchValidator;
_deliveryValidator = DeliveryValidator;
_storageRequestValidator = StorageRequestValidator;
_sourceRangeFunc = SourceRange;
_candidateRangeFunc = CandidateRange;
_infiniteRangeFunc = InfiniteRange;
}
public UniTask InitializeAsync()
{
_signalBus.Subscribe<ConstructionCompletedSignal>(OnConstructionCompleted);
_signalBus.Subscribe<DoDeleteToolSignal>(OnDoDeleteTool);
return UniTask.CompletedTask;
}
public void Dispose()
{
_signalBus.Unsubscribe<ConstructionCompletedSignal>(OnConstructionCompleted);
_signalBus.Unsubscribe<DoDeleteToolSignal>(OnDoDeleteTool);
}
private void OnConstructionCompleted(ConstructionCompletedSignal signal)
{
var constructionSite = signal.ConstructionSite;
Create(constructionSite.Building, constructionSite.Rect, constructionSite.Orientation);
}
public Building Create(BuildingDefinition definition, TileRect rect, Directions orientation)
{
var building = _entityCollection.Create<Building>();
building.Definition = definition;
building.Rect = rect;
building.Orientation = orientation;
building.WeekCreated = _world.TimeState.TotalWeeks;
InitializeStorage(building);
InitializeProductionState(building);
InitializeAgentSource(building);
InitializeHouse(building);
InitializeSleepState(building);
_entityCollection.Add(building);
_signalBus.Raise(new BuildingCreatedSignal(building));
return building;
}
private void InitializeStorage(Building building)
{
var capacity = building.Definition.StorageCapacity;
if (capacity <= 0) return;
_productStorageManager.InitializeProductStorage(building, capacity);
}
private void InitializeProductionState(Building building)
{
var recipeDefinition = building.Definition.Recipe;
if (!recipeDefinition) return;
ref var productionState = ref building.GetProductionStateRW();
productionState.State = ProducerState.NotWorking;
productionState.Recipe.Inputs.Length = recipeDefinition.Inputs.Count;
for (var i = 0; i < recipeDefinition.Inputs.Count; i++)
{
var input = recipeDefinition.Inputs[i];
var productHandle = _productCatalog.GetHandle(input.Product);
productionState.Recipe.Inputs[i] = (productHandle, input.Amount);
}
productionState.Recipe.OutputProductHandle = _productCatalog.GetHandle(recipeDefinition.Output.Product);
productionState.Recipe.OutputAmount = recipeDefinition.Output.Amount;
productionState.Recipe.ProductionTime = recipeDefinition.ProductionTime;
}
private void InitializeAgentSource(Building building)
{
_agentFactory.InitializeAgentSource(building);
}
private void InitializeHouse(Building building)
{
if (!building.Definition.IsHouse) return;
var tiers = building.Definition.HouseTiers;
building.PopulationCapacity = tiers.Count > 0 ? tiers[0].Capacity : 0;
InitializeHouseNeeds(building);
}
private void InitializeHouseNeeds(Building building)
{
ref var needsState = ref building.GetNeedsStateRW();
var tiers = building.Definition.HouseTiers;
for (var i = 0; i < tiers.Count; i++)
foreach (var need in tiers[i].Needs)
needsState.Needs.Add(
new PopulationNeed
{
TierIndex = (byte)i,
Type = need.Type,
ProductHandle = _productCatalog.GetHandle(need.Product),
HappinessScore = (ushort)need.HappinessScore,
ConsumptionRate = (byte)need.ConsumptionRate,
FetchThreshold = (byte)need.FetchThreshold,
YieldOnFetch = (ushort)need.YieldOnFetch
});
needsState.UpgradeState = tiers.Count > 1 ? TierUpgradeState.NotReady : TierUpgradeState.MaxedOut;
needsState.Happiness = _config.Population.InitialHouseHappiness;
}
private void InitializeSleepState(Building building)
{
ref var sleepState = ref building.GetSleepStateRW();
sleepState.HasHomelessWorkers = false;
sleepState.EfficiencyModifier = _config.Economy.RestedWorkersEfficiencyModifier;
}
private void OnDoDeleteTool(DoDeleteToolSignal signal)
{
using var toDeleteScope = HashSetPool<int>.Get(out var toDelete);
foreach (var tile in TileRange.From(signal.Rect))
foreach (Building building in _entityCollection.GetInternalEntityList(typeof(Building)))
{
if (!building.Definition.CanBeDeleted || !building.Rect.Contains(tile)) continue;
toDelete.Add(building.Id);
break;
}
foreach (var id in toDelete)
{
var building = _entityCollection.Get<Building>(id);
Delete(building, DeleteBuildingOptions.WithFeedback);
}
}
public void Delete(Building building, DeleteBuildingOptions options)
{
_entityCollection.Remove(building.Id);
_productStackFactory.CreateProductStacksFrom(building);
_signalBus.Raise(new BuildingDeletedSignal(building, options));
}
public bool FindStorageForFetch(int sourceId, TileRect sourceRect, int productHandle, out Building building)
{
return FindReachableBuilding(sourceId, sourceRect, 0, productHandle, _entityCache.GetStorageBuildings(), _fetchValidator, _infiniteRangeFunc, out building);
}
public bool FindStorageForFetch(int sourceId, TileRect sourceRect, int sourceRange, int productHandle, out Building building)
{
return FindReachableBuilding(sourceId, sourceRect, sourceRange, productHandle, _entityCache.GetStorageBuildings(), _fetchValidator, _sourceRangeFunc, out building);
}
public bool FindStorageForDelivery(int sourceId, TileRect sourceRect, int productHandle, out Building building)
{
return FindReachableBuilding(sourceId, sourceRect, 0, productHandle, _entityCache.GetStorageBuildings(), _deliveryValidator, _infiniteRangeFunc, out building);
}
public bool FindStorageForRequest(int sourceId, TileRect sourceRect, int sourceRange, int productHandle, out Building building)
{
return FindReachableBuilding(
sourceId,
sourceRect,
sourceRange,
productHandle,
_entityCache.GetStorageRequestBuildings(),
_storageRequestValidator,
_infiniteRangeFunc,
out building);
}
public bool FindProviderForFetch(int sourceId, TileRect sourceRect, int productHandle, out Building building)
{
return FindReachableBuilding(sourceId, sourceRect, 0, productHandle, _entityCache.GetProviders(), _fetchValidator, _candidateRangeFunc, out building);
}
public void FindProvidersForHouse(TileRect sourceRect, List<Building> providers)
{
foreach (var provider in _entityCache.GetProviders())
if (TileMath.StepCount(provider.Rect, sourceRect) <= provider.Definition.Range)
providers.Add(provider);
}
public void FindProvidersForStorage(TileRect sourceRect, List<Building> providers)
{
foreach (var provider in _entityCache.GetProviders())
if (provider.Definition.FetchesProducts && TileMath.StepCount(provider.Rect, sourceRect) <= provider.Definition.Range)
providers.Add(provider);
}
private bool FindReachableBuilding(int sourceId,
TileRect sourceRect,
int sourceRange,
int productHandle,
List<Building> candidates,
Func<Building, int, bool> validator,
Func<Building, int, int> rangeFunc,
out Building building)
{
building = null;
var best = int.MaxValue;
foreach (var candidate in candidates)
{
if (_failedPathCache.IsKnownFailedPath(sourceId, candidate.Id)) continue;
var stepCount = TileMath.StepCount(sourceRect, candidate.Rect);
if (stepCount >= best || stepCount > rangeFunc.Invoke(candidate, sourceRange)) continue;
if (!validator.Invoke(candidate, productHandle)) continue;
best = stepCount;
building = candidate;
}
return best < int.MaxValue;
}
private bool FetchValidator(Building candidate, int productHandle)
{
ref var storage = ref candidate.GetStorageRW();
return storage.AvailableNow(productHandle) > 0;
}
private bool DeliveryValidator(Building candidate, int productHandle)
{
ref var storage = ref candidate.GetStorageRW();
ref var storagePolicy = ref candidate.GetProductStoragePolicyRW();
return storage.FreeSpace() > 0 && storagePolicy.IsTakingAllowed(productHandle);
}
private bool StorageRequestValidator(Building candidate, int productHandle)
{
ref var storage = ref candidate.GetStorageRW();
ref var storagePolicy = ref candidate.GetProductStoragePolicyRW();
return storage.CountIncludingReservations(productHandle) > storagePolicy.GetRequestedAmount(productHandle) && storagePolicy.CanFulfillRequests(productHandle);
}
private int SourceRange(Building candidate, int sourceRange)
{
return sourceRange;
}
private int CandidateRange(Building candidate, int sourceRange)
{
return candidate.Definition.Range;
}
private int InfiniteRange(Building candidate, int sourceRange)
{
return int.MaxValue;
}
}
}