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 _fetchValidator; private Func _deliveryValidator; private Func _storageRequestValidator; private Func _sourceRangeFunc; private Func _candidateRangeFunc; private Func _infiniteRangeFunc; public BuildingManagerSystem(IServiceLocator serviceLocator) : base(serviceLocator) { _fetchValidator = FetchValidator; _deliveryValidator = DeliveryValidator; _storageRequestValidator = StorageRequestValidator; _sourceRangeFunc = SourceRange; _candidateRangeFunc = CandidateRange; _infiniteRangeFunc = InfiniteRange; } public UniTask InitializeAsync() { _signalBus.Subscribe(OnConstructionCompleted); _signalBus.Subscribe(OnDoDeleteTool); return UniTask.CompletedTask; } public void Dispose() { _signalBus.Unsubscribe(OnConstructionCompleted); _signalBus.Unsubscribe(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.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.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(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 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 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 candidates, Func validator, Func 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; } } }