riversong code showcase
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct BuildingProductionState
|
||||
{
|
||||
public ProducerState State;
|
||||
|
||||
public Recipe Recipe;
|
||||
|
||||
public int Progress;
|
||||
|
||||
public ProductionRequirements MetRequirements;
|
||||
|
||||
public bool AllRequirementsMet => MetRequirements == ProductionRequirements.All;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[GameSystemGroup(typeof(DefaultGameSystemGroup))]
|
||||
public class EconomySystemGroup : GameSystemGroup
|
||||
{
|
||||
}
|
||||
}
|
||||
13
Source/Riversong/Game/World/Production/IProductCatalog.cs
Normal file
13
Source/Riversong/Game/World/Production/IProductCatalog.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IProductCatalog
|
||||
{
|
||||
public const int InvalidHandle = -1;
|
||||
|
||||
int ProductTypeCount { get; }
|
||||
|
||||
int GetHandle(ProductDefinition product);
|
||||
|
||||
ProductDefinition GetProduct(int productHandle);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IProductStackFactory
|
||||
{
|
||||
ProductStack Create(int2 tile, ProductDefinition product, int amount);
|
||||
|
||||
ProductStack CreateOrMerge(int2 tile, ProductDefinition product, int amount);
|
||||
|
||||
void CreateProductStacksFrom(ConstructionSite constructionSite);
|
||||
|
||||
void CreateProductStacksFrom(Building building);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IProductStackVisualizationCollection
|
||||
{
|
||||
bool TryGetVisualization(int id, out GameObject visualization);
|
||||
}
|
||||
}
|
||||
15
Source/Riversong/Game/World/Production/LaborTier.cs
Normal file
15
Source/Riversong/Game/World/Production/LaborTier.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public enum LaborTier
|
||||
{
|
||||
Minimum,
|
||||
|
||||
Low,
|
||||
|
||||
Medium,
|
||||
|
||||
High,
|
||||
|
||||
Count
|
||||
}
|
||||
}
|
||||
51
Source/Riversong/Game/World/Production/LaborUpdateSystem.cs
Normal file
51
Source/Riversong/Game/World/Production/LaborUpdateSystem.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[GameSystemGroup(typeof(EconomySystemGroup))]
|
||||
[UpdateBefore(typeof(ProductionTickGameSystem))]
|
||||
public class LaborUpdateSystem : GameSystem, IUpdatable
|
||||
{
|
||||
[InjectService]
|
||||
private GameConfig _config;
|
||||
|
||||
[InjectService]
|
||||
private World _world;
|
||||
|
||||
[InjectService]
|
||||
private IEntityCache _entityCache;
|
||||
|
||||
public LaborUpdateSystem(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
var state = _world.ProductionState;
|
||||
state.LaborDemand = 0;
|
||||
|
||||
foreach (var building in _entityCache.GetBuildingsWithWorkers()) state.LaborDemand += building.Definition.WorkerCount;
|
||||
|
||||
var population = _world.PopulationState.Population;
|
||||
state.LaborSupply = Mathf.FloorToInt(population * _config.Economy.PopulationToLaborFactor);
|
||||
|
||||
state.LaborFulfillmentRatio = Mathf.Clamp01(state.LaborDemand > 0 ? state.LaborSupply / (float)state.LaborDemand : 1);
|
||||
|
||||
var tiers = _config.Economy.LaborTiers;
|
||||
state.LaborTier = LaborTier.Minimum;
|
||||
|
||||
for (var tier = Mathf.Min((int)LaborTier.Count, tiers.Count) - 1; tier >= 0; tier--)
|
||||
{
|
||||
if (state.LaborFulfillmentRatio < tiers[tier].Threshold) continue;
|
||||
|
||||
state.LaborTier = (LaborTier)tier;
|
||||
break;
|
||||
}
|
||||
|
||||
var minTier = Mathf.Max(0, (int)LaborTier.High - population / _config.Economy.PopulationPerMinLaborTierStep);
|
||||
state.LaborTier = (LaborTier)Mathf.Max((int)state.LaborTier, minTier);
|
||||
|
||||
state.LaborEfficiencyModifier = (int)state.LaborTier < tiers.Count ? tiers[(int)state.LaborTier].EfficiencyModifier : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
Source/Riversong/Game/World/Production/ProducerState.cs
Normal file
9
Source/Riversong/Game/World/Production/ProducerState.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public enum ProducerState
|
||||
{
|
||||
NotWorking,
|
||||
|
||||
Working
|
||||
}
|
||||
}
|
||||
36
Source/Riversong/Game/World/Production/ProductAmount.cs
Normal file
36
Source/Riversong/Game/World/Production/ProductAmount.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using Sirenix.OdinInspector;
|
||||
using Unity.Properties;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IProductAmount
|
||||
{
|
||||
public ProductDefinition Product { get; set; }
|
||||
|
||||
public int Amount { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class ProductAmountAuthoring : IProductAmount
|
||||
{
|
||||
[field: SerializeField]
|
||||
[field: HorizontalGroup]
|
||||
[field: HideLabel]
|
||||
[CreateProperty]
|
||||
public ProductDefinition Product { get; set; }
|
||||
|
||||
[field: SerializeField]
|
||||
[field: HorizontalGroup(50)]
|
||||
[field: HideLabel]
|
||||
[CreateProperty]
|
||||
public int Amount { get; set; }
|
||||
|
||||
public void Deconstruct(out ProductDefinition product, out int amount)
|
||||
{
|
||||
product = Product;
|
||||
amount = Amount;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Collections.Generic;
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[Service(typeof(IProductCatalog))]
|
||||
[GameSystemGroup(typeof(EarlyGameSystemGroup))]
|
||||
[InitializeAfter(typeof(GameDatabaseSystem))]
|
||||
public class ProductCatalogSystem : GameSystem, IInitializable, IProductCatalog
|
||||
{
|
||||
[InjectService]
|
||||
private IGameDatabase _gameDatabase;
|
||||
|
||||
private Dictionary<int, int> _handles = new();
|
||||
|
||||
private List<ProductDefinition> _products = new();
|
||||
|
||||
public ProductCatalogSystem(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public int ProductTypeCount => _handles.Count;
|
||||
|
||||
public UniTask InitializeAsync()
|
||||
{
|
||||
foreach (var product in _gameDatabase.OfType<ProductDefinition>())
|
||||
{
|
||||
_handles[product.RuntimeId] = _products.Count;
|
||||
_products.Add(product);
|
||||
}
|
||||
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
public int GetHandle(ProductDefinition product)
|
||||
{
|
||||
return _handles[product.RuntimeId];
|
||||
}
|
||||
|
||||
public ProductDefinition GetProduct(int productHandle)
|
||||
{
|
||||
return productHandle != IProductCatalog.InvalidHandle ? _products[productHandle] : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Source/Riversong/Game/World/Production/ProductDefinition.cs
Normal file
19
Source/Riversong/Game/World/Production/ProductDefinition.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AddressableAssets;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[CreateAssetMenu(fileName = "ProductDefinition", menuName = "Riversong Code Showcase/Product Definition")]
|
||||
public class ProductDefinition : GameDataAsset
|
||||
{
|
||||
public string ProductName;
|
||||
|
||||
[PreviewField]
|
||||
public Sprite Icon;
|
||||
|
||||
public AssetReferenceGameObject ProductStackVisualization;
|
||||
|
||||
public AssetReferenceGameObject CarriedVisualization;
|
||||
}
|
||||
}
|
||||
27
Source/Riversong/Game/World/Production/ProductStack.cs
Normal file
27
Source/Riversong/Game/World/Production/ProductStack.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class ProductStack : Entity, IProductStorageEntity
|
||||
{
|
||||
private ProductStorage _storage;
|
||||
|
||||
private ProductStoragePolicyState _productStoragePolicy;
|
||||
|
||||
public int2 Tile { get; set; }
|
||||
|
||||
public ProductDefinition Product { get; set; }
|
||||
|
||||
public int ProductHandle { get; set; }
|
||||
|
||||
public ref ProductStorage GetStorageRW()
|
||||
{
|
||||
return ref _storage;
|
||||
}
|
||||
|
||||
public ref ProductStoragePolicyState GetProductStoragePolicyRW()
|
||||
{
|
||||
return ref _productStoragePolicy;
|
||||
}
|
||||
}
|
||||
}
|
||||
216
Source/Riversong/Game/World/Production/ProductStackManager.cs
Normal file
216
Source/Riversong/Game/World/Production/ProductStackManager.cs
Normal file
@@ -0,0 +1,216 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Pool;
|
||||
using Object = UnityEngine.Object;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[Service(typeof(IProductStackFactory))]
|
||||
[Service(typeof(IProductStackVisualizationCollection))]
|
||||
public class ProductStackManager : GameSystem, IInitializable, IDisposable, IUpdatable, IProductStackFactory, IProductStackVisualizationCollection
|
||||
{
|
||||
private const int ProductStackCapacity = 10000;
|
||||
|
||||
[InjectService]
|
||||
private IEntityCollection _entityCollection;
|
||||
|
||||
[InjectService]
|
||||
private ITileSpace _tileSpace;
|
||||
|
||||
[InjectService]
|
||||
private IScene _scene;
|
||||
|
||||
[InjectService]
|
||||
private World _world;
|
||||
|
||||
[InjectService]
|
||||
private IProductCatalog _productCatalog;
|
||||
|
||||
[InjectService]
|
||||
private IProductStorageManager _productStorageManager;
|
||||
|
||||
[InjectService]
|
||||
private ISignalBus _signalBus;
|
||||
|
||||
private Dictionary<int, GameObject> _visualizations = new();
|
||||
|
||||
public ProductStackManager(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public UniTask InitializeAsync()
|
||||
{
|
||||
_signalBus.Subscribe<CollectDeletedGameObjectsSignal>(OnCollectDeletedGameObjects);
|
||||
_signalBus.Subscribe<DoDeleteToolSignal>(OnDoDeleteTool);
|
||||
_signalBus.Subscribe<BuildingPlacementAnimationStartedSignal>(OnBuildingPlacementAnimationStarted);
|
||||
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_signalBus.Unsubscribe<CollectDeletedGameObjectsSignal>(OnCollectDeletedGameObjects);
|
||||
_signalBus.Unsubscribe<DoDeleteToolSignal>(OnDoDeleteTool);
|
||||
_signalBus.Unsubscribe<BuildingPlacementAnimationStartedSignal>(OnBuildingPlacementAnimationStarted);
|
||||
}
|
||||
|
||||
public ProductStack Create(int2 tile, ProductDefinition product, int amount)
|
||||
{
|
||||
var productStack = _entityCollection.CreateAndAdd<ProductStack>();
|
||||
productStack.Tile = tile;
|
||||
productStack.Product = product;
|
||||
productStack.ProductHandle = _productCatalog.GetHandle(product);
|
||||
|
||||
_world.ProductStacks.AddProductStack(productStack);
|
||||
_world.BlockMap.AddReason(tile, BlockReason.ProductStack);
|
||||
|
||||
ref var storage = ref _productStorageManager.InitializeProductStorage(productStack, ProductStackCapacity);
|
||||
storage.Put(productStack.ProductHandle, math.min(amount, ProductStackCapacity));
|
||||
|
||||
CreateVisualization(productStack);
|
||||
|
||||
return productStack;
|
||||
}
|
||||
|
||||
public ProductStack CreateOrMerge(int2 tile, ProductDefinition product, int amount)
|
||||
{
|
||||
if (_world.ProductStacks.At(tile, out var productStack) && productStack.Product == product)
|
||||
{
|
||||
ref var storage = ref productStack.GetStorageRW();
|
||||
storage.Put(productStack.ProductHandle, amount);
|
||||
return productStack;
|
||||
}
|
||||
|
||||
return Create(tile, product, amount);
|
||||
}
|
||||
|
||||
public void CreateProductStacksFrom(ConstructionSite constructionSite)
|
||||
{
|
||||
ref var storage = ref constructionSite.GetStorageRW();
|
||||
using var tiles = TileRange.From(constructionSite.Rect).GetEnumerator();
|
||||
|
||||
foreach (var (productHandle, amount) in storage)
|
||||
{
|
||||
if (!tiles.MoveNext())
|
||||
{
|
||||
Debug.LogError("Could not find tile for product stack");
|
||||
break;
|
||||
}
|
||||
|
||||
var tile = tiles.Current;
|
||||
var product = _productCatalog.GetProduct(productHandle);
|
||||
_ = CreateDelayedAsync(tile, product, amount);
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateProductStacksFrom(Building building)
|
||||
{
|
||||
using var productsScope = DictionaryPool<int, int>.Get(out var products);
|
||||
ref var storage = ref building.GetStorageRW();
|
||||
using var tiles = TileRange.From(building.Rect).GetEnumerator();
|
||||
|
||||
foreach (var (product, amount) in building.Definition.BuildingMaterials)
|
||||
{
|
||||
var productHandle = _productCatalog.GetHandle(product);
|
||||
products.TryGetValue(productHandle, out var current);
|
||||
products[productHandle] = current + amount;
|
||||
}
|
||||
|
||||
foreach (var (productHandle, amount) in storage)
|
||||
{
|
||||
products.TryGetValue(productHandle, out var current);
|
||||
products[productHandle] = current + amount;
|
||||
}
|
||||
|
||||
foreach (var (productHandle, amount) in products)
|
||||
{
|
||||
if (!tiles.MoveNext())
|
||||
{
|
||||
Debug.LogError("Could not find tile for product stack");
|
||||
break;
|
||||
}
|
||||
|
||||
var tile = tiles.Current;
|
||||
_ = CreateDelayedAsync(tile, _productCatalog.GetProduct(productHandle), amount);
|
||||
}
|
||||
}
|
||||
|
||||
private async UniTask<ProductStack> CreateDelayedAsync(int2 tile, ProductDefinition product, int amount)
|
||||
{
|
||||
await UniTask.NextFrame();
|
||||
return Create(tile, product, amount);
|
||||
}
|
||||
|
||||
private void CreateVisualization(ProductStack productStack)
|
||||
{
|
||||
var prefab = (GameObject)productStack.Product.ProductStackVisualization.Asset;
|
||||
var position = _tileSpace.TileToWorld(productStack.Tile);
|
||||
var rotation = Quaternion.Euler(0, Random.Range(0, 8) * 45, 0);
|
||||
var folder = _scene.SceneFolders.ProductStacks;
|
||||
|
||||
var visualization = Object.Instantiate(prefab, position, rotation, folder);
|
||||
|
||||
_visualizations.Add(productStack.Id, visualization);
|
||||
}
|
||||
|
||||
public bool TryGetVisualization(int id, out GameObject visualization)
|
||||
{
|
||||
return _visualizations.TryGetValue(id, out visualization);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
using var deSpawnScope = ListPool<ProductStack>.Get(out var deSpawn);
|
||||
|
||||
foreach (ProductStack productStack in _entityCollection.GetInternalEntityList(typeof(ProductStack)))
|
||||
{
|
||||
ref var storage = ref productStack.GetStorageRW();
|
||||
if (storage.TotalCount <= 0) deSpawn.Add(productStack);
|
||||
}
|
||||
|
||||
foreach (var productStack in deSpawn) DeSpawn(productStack);
|
||||
}
|
||||
|
||||
private void OnCollectDeletedGameObjects(CollectDeletedGameObjectsSignal signal)
|
||||
{
|
||||
if ((signal.Filter & DeletedGameObjectsFilter.ProductStacks) == 0) return;
|
||||
|
||||
using var productStacksScope = ListPool<ProductStack>.Get(out var productStacks);
|
||||
_world.ProductStacks.Find(signal.Rect, productStacks);
|
||||
|
||||
foreach (var productStack in productStacks) signal.GameObjects.Add(_visualizations[productStack.Id]);
|
||||
}
|
||||
|
||||
private void OnDoDeleteTool(DoDeleteToolSignal signal)
|
||||
{
|
||||
DeSpawn(signal.Rect);
|
||||
}
|
||||
|
||||
private void OnBuildingPlacementAnimationStarted(BuildingPlacementAnimationStartedSignal signal)
|
||||
{
|
||||
DeSpawn(signal.Rect);
|
||||
}
|
||||
|
||||
private void DeSpawn(TileRect rect)
|
||||
{
|
||||
using var productStacksScope = ListPool<ProductStack>.Get(out var productStacks);
|
||||
_world.ProductStacks.Find(rect, productStacks);
|
||||
|
||||
foreach (var productStack in productStacks) DeSpawn(productStack);
|
||||
}
|
||||
|
||||
private void DeSpawn(ProductStack productStack)
|
||||
{
|
||||
_entityCollection.Remove(productStack.Id);
|
||||
_world.ProductStacks.RemoveProductStack(productStack);
|
||||
_world.BlockMap.RemoveReason(productStack.Tile, BlockReason.ProductStack);
|
||||
|
||||
_visualizations.Remove(productStack.Id, out var visualization);
|
||||
Object.Destroy(visualization);
|
||||
}
|
||||
}
|
||||
}
|
||||
113
Source/Riversong/Game/World/Production/ProductStacksState.cs
Normal file
113
Source/Riversong/Game/World/Production/ProductStacksState.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine.Pool;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class ProductStacksState
|
||||
{
|
||||
private const int SpatialLookupSize = 5;
|
||||
|
||||
private SpatialLookup<ProductStack> _spatialLookup = new(SpatialLookupSize);
|
||||
|
||||
private Func<ProductStack, int> _scoreExplicitProductFunc;
|
||||
|
||||
private Func<ProductStack, int> _scoreAllowedByPolicyFunc;
|
||||
|
||||
private ScoreFuncContext _scoreFuncContext;
|
||||
|
||||
public void AddProductStack(ProductStack productStack)
|
||||
{
|
||||
_spatialLookup.Add(productStack, TileRect.OneTile(productStack.Tile), productStack.Id);
|
||||
}
|
||||
|
||||
public void RemoveProductStack(ProductStack productStack)
|
||||
{
|
||||
_spatialLookup.Remove(TileRect.OneTile(productStack.Tile), productStack.Id);
|
||||
}
|
||||
|
||||
public bool At(int2 tile, out ProductStack productStack)
|
||||
{
|
||||
using var resultScope = ListPool<ProductStack>.Get(out var result);
|
||||
_spatialLookup.Find(TileRect.OneTile(tile), result);
|
||||
productStack = result.Count > 0 ? result[0] : null;
|
||||
return productStack != null;
|
||||
}
|
||||
|
||||
public void Find(TileRect rect, List<ProductStack> productStacks)
|
||||
{
|
||||
_spatialLookup.Find(rect, productStacks);
|
||||
}
|
||||
|
||||
public bool FindClosest(TileRect sourceRect, int range, int productHandle, int amount, out ProductStack closest)
|
||||
{
|
||||
_scoreExplicitProductFunc ??= ScoreExplicitProduct;
|
||||
_scoreFuncContext = new ScoreFuncContext(sourceRect, productHandle, amount);
|
||||
|
||||
return _spatialLookup.FindMax(sourceRect.Inflate(range), _scoreExplicitProductFunc, out closest);
|
||||
}
|
||||
|
||||
public bool FindClosest(TileRect sourceRect, int range, int amount, in ProductStoragePolicyState storagePolicy, out ProductStack closest)
|
||||
{
|
||||
_scoreAllowedByPolicyFunc ??= ScoreAllowedByPolicy;
|
||||
_scoreFuncContext = new ScoreFuncContext(sourceRect, amount, storagePolicy);
|
||||
|
||||
return _spatialLookup.FindMax(sourceRect.Inflate(range), _scoreAllowedByPolicyFunc, out closest);
|
||||
}
|
||||
|
||||
public bool FindClosest(TileRect sourceTile, int range, out ProductStack closest)
|
||||
{
|
||||
return FindClosest(sourceTile, range, IProductCatalog.InvalidHandle, 1, out closest);
|
||||
}
|
||||
|
||||
private int ScoreExplicitProduct(ProductStack productStack)
|
||||
{
|
||||
if (_scoreFuncContext.ProductHandle != IProductCatalog.InvalidHandle && _scoreFuncContext.ProductHandle != productStack.ProductHandle) return int.MinValue;
|
||||
|
||||
return Score(productStack);
|
||||
}
|
||||
|
||||
private int ScoreAllowedByPolicy(ProductStack productStack)
|
||||
{
|
||||
if (!_scoreFuncContext.StoragePolicy.IsTakingAllowed(productStack.ProductHandle)) return int.MinValue;
|
||||
|
||||
return Score(productStack);
|
||||
}
|
||||
|
||||
private int Score(ProductStack productStack)
|
||||
{
|
||||
ref var storage = ref productStack.GetStorageRW();
|
||||
if (storage.AvailableNow(productStack.ProductHandle) < _scoreFuncContext.Amount) return int.MinValue;
|
||||
|
||||
return -TileMath.StepCount(_scoreFuncContext.Rect, productStack.Tile);
|
||||
}
|
||||
|
||||
private struct ScoreFuncContext
|
||||
{
|
||||
public TileRect Rect;
|
||||
|
||||
public int ProductHandle;
|
||||
|
||||
public int Amount;
|
||||
|
||||
public ProductStoragePolicyState StoragePolicy;
|
||||
|
||||
public ScoreFuncContext(TileRect rect, int productHandle, int amount)
|
||||
{
|
||||
Rect = rect;
|
||||
ProductHandle = productHandle;
|
||||
Amount = amount;
|
||||
StoragePolicy = default;
|
||||
}
|
||||
|
||||
public ScoreFuncContext(TileRect rect, int amount, in ProductStoragePolicyState storagePolicy)
|
||||
{
|
||||
Rect = rect;
|
||||
ProductHandle = IProductCatalog.InvalidHandle;
|
||||
Amount = amount;
|
||||
StoragePolicy = storagePolicy;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[Flags]
|
||||
public enum ProductionRequirements
|
||||
{
|
||||
None = 0,
|
||||
|
||||
InputsAvailable = 1,
|
||||
|
||||
All = InputsAvailable
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[GameSystemGroup(typeof(EconomySystemGroup))]
|
||||
public class ProductionRequirementsGameSystem : GameSystem, IUpdatable
|
||||
{
|
||||
[InjectService]
|
||||
private IEntityCache _entityCache;
|
||||
|
||||
public ProductionRequirementsGameSystem(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
foreach (var producer in _entityCache.GetProducers())
|
||||
{
|
||||
ref var productionState = ref producer.GetProductionStateRW();
|
||||
ref var recipe = ref productionState.Recipe;
|
||||
ref var storage = ref producer.GetStorageRW();
|
||||
|
||||
productionState.MetRequirements = ProductionRequirements.All;
|
||||
|
||||
foreach (var (product, amount) in recipe.Inputs)
|
||||
if (storage.AvailableNow(product) < amount)
|
||||
{
|
||||
productionState.MetRequirements &= ~ProductionRequirements.InputsAvailable;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[GameSystemGroup(typeof(EconomySystemGroup))]
|
||||
[UpdateAfter(typeof(ProductionRequirementsGameSystem))]
|
||||
public class ProductionTickGameSystem : GameSystem, IUpdatable
|
||||
{
|
||||
[InjectService]
|
||||
private GameConfig _config;
|
||||
|
||||
[InjectService]
|
||||
private World _world;
|
||||
|
||||
[InjectService]
|
||||
private IEntityCache _entityCache;
|
||||
|
||||
public ProductionTickGameSystem(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (_world.TimeState.DayNightCycleStep != DayNightCycleStep.Day) return;
|
||||
|
||||
var state = _world.ProductionState;
|
||||
|
||||
state.ProductionTickTimer += Time.deltaTime;
|
||||
if (state.ProductionTickTimer < _config.Economy.ProductionTickInterval) return;
|
||||
|
||||
state.ProductionTickTimer = 0;
|
||||
ProductionTick();
|
||||
}
|
||||
|
||||
private void ProductionTick()
|
||||
{
|
||||
var laborEfficiencyModifier = _world.ProductionState.LaborEfficiencyModifier;
|
||||
|
||||
foreach (var producer in _entityCache.GetProducers())
|
||||
{
|
||||
ref var storage = ref producer.GetStorageRW();
|
||||
ref var productionState = ref producer.GetProductionStateRW();
|
||||
ref var recipe = ref productionState.Recipe;
|
||||
|
||||
if (productionState.State == ProducerState.NotWorking)
|
||||
{
|
||||
if (!productionState.AllRequirementsMet || !storage.ReservePutting(recipe.OutputProductHandle, recipe.OutputAmount)) continue;
|
||||
|
||||
foreach (var (productHandle, amount) in recipe.Inputs) storage.Take(productHandle, amount);
|
||||
|
||||
productionState.State = ProducerState.Working;
|
||||
}
|
||||
|
||||
ref var sleepState = ref producer.GetSleepStateRW();
|
||||
var efficiencyMultiplier = Mathf.Max(0, 1 + sleepState.EfficiencyModifier + laborEfficiencyModifier);
|
||||
productionState.Progress += Mathf.RoundToInt(1000 * efficiencyMultiplier);
|
||||
if (productionState.Progress < 1000 * recipe.ProductionTime) continue;
|
||||
|
||||
productionState.Progress = 0;
|
||||
if (!storage.FulfillPut(recipe.OutputProductHandle, recipe.OutputAmount)) Debug.LogError("Producer failed fulfilling put");
|
||||
|
||||
productionState.State = ProducerState.NotWorking;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
Source/Riversong/Game/World/Production/Recipe.cs
Normal file
15
Source/Riversong/Game/World/Production/Recipe.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using Unity.Collections;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct Recipe
|
||||
{
|
||||
public FixedList32Bytes<(int, int)> Inputs;
|
||||
|
||||
public int OutputProductHandle;
|
||||
|
||||
public int OutputAmount;
|
||||
|
||||
public int ProductionTime;
|
||||
}
|
||||
}
|
||||
15
Source/Riversong/Game/World/Production/RecipeDefinition.cs
Normal file
15
Source/Riversong/Game/World/Production/RecipeDefinition.cs
Normal file
@@ -0,0 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[CreateAssetMenu(fileName = "RecipeDefinition", menuName = "Riversong Code Showcase/Recipe Definition")]
|
||||
public class RecipeDefinition : GameDataAsset
|
||||
{
|
||||
public List<ProductAmountAuthoring> Inputs;
|
||||
|
||||
public ProductAmountAuthoring Output;
|
||||
|
||||
public int ProductionTime;
|
||||
}
|
||||
}
|
||||
154
Source/Riversong/Game/World/Production/RestedWorkersSystem.cs
Normal file
154
Source/Riversong/Game/World/Production/RestedWorkersSystem.cs
Normal file
@@ -0,0 +1,154 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Pool;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[GameSystemGroup(typeof(EconomySystemGroup))]
|
||||
public class RestedWorkersSystem : GameSystem, IInitializable, IDisposable
|
||||
{
|
||||
[InjectService]
|
||||
private ISignalBus _signalBus;
|
||||
|
||||
[InjectService]
|
||||
private GameConfig _config;
|
||||
|
||||
[InjectService]
|
||||
private IEntityCache _entityCache;
|
||||
|
||||
[InjectService]
|
||||
private IBuildingFactory _buildingFactory;
|
||||
|
||||
[InjectService]
|
||||
private IEditToolValidatorService _editToolValidatorService;
|
||||
|
||||
public RestedWorkersSystem(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public UniTask InitializeAsync()
|
||||
{
|
||||
_signalBus.Subscribe<NightStartedSignal>(OnNightStarted);
|
||||
_signalBus.Subscribe<DayStartedSignal>(OnDayStarted);
|
||||
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_signalBus.Unsubscribe<NightStartedSignal>(OnNightStarted);
|
||||
_signalBus.Unsubscribe<DayStartedSignal>(OnDayStarted);
|
||||
}
|
||||
|
||||
private void OnNightStarted(NightStartedSignal signal)
|
||||
{
|
||||
foreach (var building in _entityCache.GetBuildingsWithWorkers())
|
||||
{
|
||||
ref var sleepState = ref building.GetSleepStateRW();
|
||||
|
||||
sleepState.RestedWorkerCount = 0;
|
||||
sleepState.HasHomelessWorkers = false;
|
||||
|
||||
while (sleepState.RestedWorkerCount < building.Definition.WorkerCount)
|
||||
{
|
||||
if (!TryFindClosestFreeHouse(building, out var house)) break;
|
||||
|
||||
ref var houseSleepState = ref house.GetSleepStateRW();
|
||||
|
||||
var remainingToAllocate = building.Definition.WorkerCount - sleepState.RestedWorkerCount;
|
||||
var remainingCapacity = house.PopulationCapacity - houseSleepState.AllocatedWorkerCount;
|
||||
|
||||
var allocatedWorkerCount = Mathf.Min(remainingToAllocate, remainingCapacity);
|
||||
|
||||
sleepState.RestedWorkerCount += allocatedWorkerCount;
|
||||
houseSleepState.AllocatedWorkerCount += allocatedWorkerCount;
|
||||
}
|
||||
|
||||
sleepState.HasHomelessWorkers = sleepState.RestedWorkerCount < building.Definition.WorkerCount;
|
||||
|
||||
if (sleepState.HasHomelessWorkers) TrySpawnTent(building);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryFindClosestFreeHouse(Building source, out Building house)
|
||||
{
|
||||
house = null;
|
||||
|
||||
var best = int.MaxValue;
|
||||
foreach (var candidate in _entityCache.GetHouses())
|
||||
{
|
||||
ref var sleepState = ref candidate.GetSleepStateRW();
|
||||
if (sleepState.AllocatedWorkerCount >= candidate.PopulationCapacity) continue;
|
||||
|
||||
var stepCount = TileMath.StepCount(source.Rect, candidate.Rect);
|
||||
if (stepCount > _config.Economy.RestedWorkersHouseMaxStepCount || stepCount >= best) continue;
|
||||
|
||||
best = stepCount;
|
||||
house = candidate;
|
||||
}
|
||||
|
||||
return house != null;
|
||||
}
|
||||
|
||||
private void TrySpawnTent(Building source)
|
||||
{
|
||||
if (!TryFindTentTile(source, out var tile)) return;
|
||||
|
||||
var definition = (BuildingDefinition)_config.Population.TentBuilding.Asset;
|
||||
_buildingFactory.Create(definition, TileRect.OneTile(tile), (Directions)(2 * Random.Range(0, 4)));
|
||||
}
|
||||
|
||||
private bool TryFindTentTile(Building source, out int2 tile)
|
||||
{
|
||||
using var candidatesScope = ListPool<int2>.Get(out var candidates);
|
||||
|
||||
var min = source.Rect.Min - 1;
|
||||
var max = source.Rect.Max + 1;
|
||||
|
||||
for (var x = min.x; x <= max.x; x++)
|
||||
{
|
||||
Validate(new int2(x, min.y), candidates);
|
||||
Validate(new int2(x, max.y), candidates);
|
||||
}
|
||||
|
||||
for (var y = min.y + 1; y <= max.y - 1; y++)
|
||||
{
|
||||
Validate(new int2(min.x, y), candidates);
|
||||
Validate(new int2(max.x, y), candidates);
|
||||
}
|
||||
|
||||
tile = candidates.Count > 0 ? candidates[Random.Range(0, candidates.Count)] : int2.zero;
|
||||
|
||||
return candidates.Count > 0;
|
||||
|
||||
void Validate(int2 tile, List<int2> l)
|
||||
{
|
||||
var rect = TileRect.OneTile(tile);
|
||||
var validationResult = _editToolValidatorService.DoCommonValidation(rect, BlockReason.CannotMakeCamp);
|
||||
if (validationResult == EditToolValidationResult.Success) l.Add(tile);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDayStarted(DayStartedSignal signal)
|
||||
{
|
||||
var efficiencyModifier = _config.Economy.RestedWorkersEfficiencyModifier;
|
||||
|
||||
foreach (var building in _entityCache.GetBuildingsWithWorkers())
|
||||
{
|
||||
ref var sleepState = ref building.GetSleepStateRW();
|
||||
sleepState.EfficiencyModifier = efficiencyModifier * ((float)sleepState.RestedWorkerCount / building.Definition.WorkerCount);
|
||||
sleepState.RestedWorkerCount = 0;
|
||||
}
|
||||
|
||||
foreach (var house in _entityCache.GetHouses())
|
||||
{
|
||||
ref var sleepState = ref house.GetSleepStateRW();
|
||||
sleepState.AllocatedWorkerCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IProductStorageCommonLogic
|
||||
{
|
||||
bool ContainsAll(in ProductStorage storage, List<ProductAmountAuthoring> products);
|
||||
|
||||
bool TakeAllOrNothing(ref ProductStorage storage, List<ProductAmountAuthoring> products);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IProductStorageEntity : IEntity
|
||||
{
|
||||
ref ProductStorage GetStorageRW();
|
||||
|
||||
ref ProductStoragePolicyState GetProductStoragePolicyRW();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IProductStorageManager
|
||||
{
|
||||
ref ProductStorage InitializeProductStorage(IProductStorageEntity entity, int capacity);
|
||||
|
||||
bool TryGetProductStorageEntity(int id, out IProductStorageEntity entity);
|
||||
}
|
||||
}
|
||||
185
Source/Riversong/Game/World/Production/Storage/ProductStorage.cs
Normal file
185
Source/Riversong/Game/World/Production/Storage/ProductStorage.cs
Normal file
@@ -0,0 +1,185 @@
|
||||
using Unity.Collections;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public unsafe struct ProductStorage
|
||||
{
|
||||
private FixedList512Bytes<Slot> _slots;
|
||||
|
||||
public int TotalCount;
|
||||
|
||||
public int TotalPutReservations;
|
||||
|
||||
public int TotalTakeReservations;
|
||||
|
||||
public int Capacity;
|
||||
|
||||
public readonly void Get(int productHandle, out int count, out int putReservations, out int takeReservations)
|
||||
{
|
||||
var slot = _slots[productHandle];
|
||||
count = slot.Count;
|
||||
putReservations = slot.PutReservations;
|
||||
takeReservations = slot.TakeReservations;
|
||||
}
|
||||
|
||||
public readonly int AvailableNow(int productHandle)
|
||||
{
|
||||
var slot = _slots[productHandle];
|
||||
return slot.Count - slot.TakeReservations;
|
||||
}
|
||||
|
||||
public readonly int CountIncludingReservations(int productHandle)
|
||||
{
|
||||
var slot = _slots[productHandle];
|
||||
return slot.Count + slot.PutReservations - slot.TakeReservations;
|
||||
}
|
||||
|
||||
public readonly int FreeSpace()
|
||||
{
|
||||
return Capacity - TotalCount - TotalPutReservations;
|
||||
}
|
||||
|
||||
public bool Put(int productHandle, int amount)
|
||||
{
|
||||
if (amount > Capacity - TotalCount) return false;
|
||||
|
||||
var slot = _slots[productHandle];
|
||||
slot.Count += (ushort)amount;
|
||||
_slots[productHandle] = slot;
|
||||
|
||||
TotalCount += amount;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Take(int productHandle, int amount)
|
||||
{
|
||||
var slot = _slots[productHandle];
|
||||
if (amount > slot.Count) return false;
|
||||
slot.Count -= (ushort)amount;
|
||||
_slots[productHandle] = slot;
|
||||
|
||||
TotalCount -= amount;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ReservePutting(int productHandle, int amount)
|
||||
{
|
||||
if (amount > FreeSpace()) return false;
|
||||
|
||||
var slot = _slots[productHandle];
|
||||
slot.PutReservations += (ushort)amount;
|
||||
_slots[productHandle] = slot;
|
||||
|
||||
TotalPutReservations += amount;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ReleasePutReservation(int productHandle, int amount)
|
||||
{
|
||||
var slot = _slots[productHandle];
|
||||
if (amount > slot.PutReservations) return false;
|
||||
slot.PutReservations -= (ushort)amount;
|
||||
_slots[productHandle] = slot;
|
||||
|
||||
TotalPutReservations -= amount;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ReserveTaking(int productHandle, int amount)
|
||||
{
|
||||
if (amount > AvailableNow(productHandle)) return false;
|
||||
|
||||
var slot = _slots[productHandle];
|
||||
slot.TakeReservations += (ushort)amount;
|
||||
_slots[productHandle] = slot;
|
||||
|
||||
TotalTakeReservations += amount;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool ReleaseTakeReservation(int productHandle, int amount)
|
||||
{
|
||||
var slot = _slots[productHandle];
|
||||
if (amount > slot.TakeReservations) return false;
|
||||
amount = math.min(amount, slot.TakeReservations);
|
||||
slot.TakeReservations -= (ushort)amount;
|
||||
_slots[productHandle] = slot;
|
||||
|
||||
TotalTakeReservations -= amount;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool FulfillPut(int productHandle, int amount)
|
||||
{
|
||||
return ReleasePutReservation(productHandle, amount) && Put(productHandle, amount);
|
||||
}
|
||||
|
||||
public bool FulfillTake(int productHandle, int amount)
|
||||
{
|
||||
return ReleaseTakeReservation(productHandle, amount) && Take(productHandle, amount);
|
||||
}
|
||||
|
||||
public static ProductStorage Create(int slotCount, int capacity)
|
||||
{
|
||||
var result = new ProductStorage { Capacity = capacity };
|
||||
result._slots.Length = slotCount;
|
||||
return result;
|
||||
}
|
||||
|
||||
private struct Slot
|
||||
{
|
||||
public ushort Count;
|
||||
|
||||
public ushort PutReservations;
|
||||
|
||||
public ushort TakeReservations;
|
||||
}
|
||||
|
||||
public struct Enumerator
|
||||
{
|
||||
private ProductStorage* _storage;
|
||||
|
||||
private int _productHandle;
|
||||
|
||||
internal Enumerator(ProductStorage* storage)
|
||||
{
|
||||
_storage = storage;
|
||||
_productHandle = -1;
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
while (++_productHandle < _storage->_slots.Length)
|
||||
{
|
||||
var slot = _storage->_slots[_productHandle];
|
||||
if (slot.Count > 0) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public (int, int) Current
|
||||
{
|
||||
get
|
||||
{
|
||||
var slot = _storage->_slots[_productHandle];
|
||||
return (_productHandle, slot.Count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Enumerator GetEnumerator()
|
||||
{
|
||||
fixed (ProductStorage* ptr = &this)
|
||||
{
|
||||
return new Enumerator(ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[Service(typeof(IProductStorageManager))]
|
||||
[Service(typeof(IProductStorageCommonLogic))]
|
||||
public class ProductStorageManager : GameSystem, IInitializable, IDisposable, IProductStorageManager, IProductStorageCommonLogic
|
||||
{
|
||||
[InjectService]
|
||||
private IProductCatalog _productCatalog;
|
||||
|
||||
[InjectService]
|
||||
private IEntityCollection _entityCollection;
|
||||
|
||||
private Dictionary<int, IProductStorageEntity> _productStorageEntities = new();
|
||||
|
||||
public ProductStorageManager(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public UniTask InitializeAsync()
|
||||
{
|
||||
_entityCollection.On<Entity>().Removed += OnEntityRemoved;
|
||||
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_entityCollection.On<Entity>().Removed -= OnEntityRemoved;
|
||||
}
|
||||
|
||||
public ref ProductStorage InitializeProductStorage(IProductStorageEntity entity, int capacity)
|
||||
{
|
||||
ref var storage = ref entity.GetStorageRW();
|
||||
storage = ProductStorage.Create(_productCatalog.ProductTypeCount, capacity);
|
||||
ref var productStoragePolicy = ref entity.GetProductStoragePolicyRW();
|
||||
productStoragePolicy = ProductStoragePolicyState.Create(_productCatalog.ProductTypeCount);
|
||||
|
||||
_productStorageEntities.Add(entity.Id, entity);
|
||||
|
||||
return ref storage;
|
||||
}
|
||||
|
||||
public bool TryGetProductStorageEntity(int id, out IProductStorageEntity entity)
|
||||
{
|
||||
return _productStorageEntities.TryGetValue(id, out entity);
|
||||
}
|
||||
|
||||
private void OnEntityRemoved(Entity entity)
|
||||
{
|
||||
_productStorageEntities.Remove(entity.Id);
|
||||
}
|
||||
|
||||
public bool ContainsAll(in ProductStorage storage, List<ProductAmountAuthoring> products)
|
||||
{
|
||||
foreach (var (product, amount) in products)
|
||||
{
|
||||
var productHandle = _productCatalog.GetHandle(product);
|
||||
if (storage.AvailableNow(productHandle) < amount) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TakeAllOrNothing(ref ProductStorage storage, List<ProductAmountAuthoring> products)
|
||||
{
|
||||
if (!ContainsAll(storage, products)) return false;
|
||||
|
||||
foreach (var (product, amount) in products)
|
||||
{
|
||||
var productHandle = _productCatalog.GetHandle(product);
|
||||
storage.Take(productHandle, amount);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using Unity.Collections;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct ProductStoragePolicyState
|
||||
{
|
||||
private FixedList512Bytes<Slot> _slots;
|
||||
|
||||
public int NextProductToFetch;
|
||||
|
||||
public readonly int Length => _slots.Length;
|
||||
|
||||
public readonly void Get(int productHandle, out bool isTakingAllowed, out bool canFulfillRequests, out int requestedAmount)
|
||||
{
|
||||
var slot = _slots[productHandle];
|
||||
isTakingAllowed = slot.IsTakingAllowed;
|
||||
canFulfillRequests = slot.CanFulfillRequests;
|
||||
requestedAmount = slot.RequestedAmount;
|
||||
}
|
||||
|
||||
public readonly bool IsTakingAllowed(int productHandle)
|
||||
{
|
||||
return _slots[productHandle].IsTakingAllowed;
|
||||
}
|
||||
|
||||
public void SetAllowTaking(int productHandle, bool value)
|
||||
{
|
||||
var slot = _slots[productHandle];
|
||||
slot.IsTakingAllowed = value;
|
||||
_slots[productHandle] = slot;
|
||||
}
|
||||
|
||||
public readonly bool CanFulfillRequests(int productHandle)
|
||||
{
|
||||
return _slots[productHandle].CanFulfillRequests;
|
||||
}
|
||||
|
||||
public void SetCanFulfillRequests(int productHandle, bool value)
|
||||
{
|
||||
var slot = _slots[productHandle];
|
||||
slot.CanFulfillRequests = value;
|
||||
_slots[productHandle] = slot;
|
||||
}
|
||||
|
||||
public readonly int GetRequestedAmount(int productHandle)
|
||||
{
|
||||
return _slots[productHandle].RequestedAmount;
|
||||
}
|
||||
|
||||
public void SetRequestedAmount(int productHandle, int value)
|
||||
{
|
||||
var slot = _slots[productHandle];
|
||||
slot.RequestedAmount = value;
|
||||
_slots[productHandle] = slot;
|
||||
}
|
||||
|
||||
public static ProductStoragePolicyState Create(int slotCount)
|
||||
{
|
||||
var result = new ProductStoragePolicyState { NextProductToFetch = 0 };
|
||||
|
||||
result._slots.Length = slotCount;
|
||||
|
||||
for (var i = 0; i < slotCount; i++)
|
||||
result._slots[i] = new Slot
|
||||
{
|
||||
IsTakingAllowed = true,
|
||||
CanFulfillRequests = true
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private struct Slot
|
||||
{
|
||||
public bool IsTakingAllowed;
|
||||
|
||||
public bool CanFulfillRequests;
|
||||
|
||||
public int RequestedAmount;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class WorldProductionState
|
||||
{
|
||||
public float ProductionTickTimer;
|
||||
|
||||
public int LaborSupply;
|
||||
|
||||
public int LaborDemand;
|
||||
|
||||
public float LaborFulfillmentRatio;
|
||||
|
||||
public LaborTier LaborTier;
|
||||
|
||||
public float LaborEfficiencyModifier;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user