riversong code showcase

This commit is contained in:
Daniele Marotta
2026-05-21 15:52:18 +02:00
commit 4c9eea1c02
462 changed files with 23406 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
using System;
using Unity.Mathematics;
namespace DanieleMarotta.RiversongCodeShowcase
{
public class Agent : Entity, IDisposable
{
private IntentQueue _intentQueue;
private TilePath _path;
private AgentJobState _agentJobState;
private CritterState _critterState;
public int CarriedProductHandle;
public AgentDefinition Definition { get; set; }
public int HomeId { get; set; }
public float3 Position { get; set; }
public float3 Heading { get; set; }
public float3 Velocity { get; set; }
public AgentLifecycleState LifecycleState { get; set; } = AgentLifecycleState.New;
public IntentExecutionState IntentExecutionState { get; set; } = IntentExecutionState.WaitingIntent;
public float IntentExecutionTime { get; set; }
public int PathQueryHandle { get; set; }
public int PathIndex { get; set; }
public int2 WanderingCenter { get; set; }
public ref IntentQueue GetIntentQueueRW()
{
return ref _intentQueue;
}
public ref TilePath GetPathRW()
{
return ref _path;
}
public ref AgentJobState GetJobStateRW()
{
return ref _agentJobState;
}
public ref CritterState GetCritterStateRW()
{
return ref _critterState;
}
public void Dispose()
{
_path.Dispose();
}
}
}

View File

@@ -0,0 +1,14 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
// ReSharper disable InconsistentNaming
public enum AgentAnimation
{
None,
ChopTree,
Farming,
FireProjectile
}
}

View File

@@ -0,0 +1,21 @@
using UnityEngine;
namespace DanieleMarotta.RiversongCodeShowcase
{
public class AgentAnimationEventHandler : MonoBehaviour
{
private bool _actionAnimationCompleted;
public void ActionAnimationCompleted()
{
_actionAnimationCompleted = true;
}
public bool WaitAnimationCompleted()
{
var result = _actionAnimationCompleted;
_actionAnimationCompleted = false;
return result;
}
}
}

View File

@@ -0,0 +1,68 @@
using Unity.Mathematics;
using UnityEngine;
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(DefaultAgentsSystemGroup))]
[UpdateAfter(typeof(IntentExecutionSystem))]
public class AgentAnimationSystem : GameSystem, IUpdatable
{
[InjectService]
private IEntityCollection _entityCollection;
[InjectService]
private IAgentVisualizationCollection _agentVisualizationCollection;
[InjectService]
private IProductCatalog _productCatalog;
public AgentAnimationSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public void Update()
{
foreach (Agent agent in _entityCollection.GetInternalEntityList(typeof(Agent)))
{
if (!_agentVisualizationCollection.TryGetVisualization(agent.Id, out var visualization)) continue;
const float minVelocitySq = 0.01f;
visualization.UpdateAnimationState(math.lengthsq(agent.Velocity) > minVelocitySq);
UpdateCarriedProduct(agent, visualization);
}
}
private void UpdateCarriedProduct(Agent agent, AgentVisualization visualization)
{
var carriedProduct = visualization.CarriedProductVisualization;
if (!carriedProduct) return;
var isCarryingProduct = agent.CarriedProductHandle != IProductCatalog.InvalidHandle;
var targetWeight = isCarryingProduct ? 1 : 0;
var weight = Mathf.MoveTowards(carriedProduct.GetAnimatorLayerWeight(), targetWeight, 10 * Time.deltaTime);
carriedProduct.SetAnimatorLayerWeight(weight);
if (isCarryingProduct)
SetProductVisualization(carriedProduct, agent.CarriedProductHandle);
else
ClearProductVisualization(carriedProduct);
}
private void SetProductVisualization(AgentCarriedProductVisualization carriedProduct, int productHandle)
{
if (carriedProduct.IsShowingCarriedProduct()) return;
var product = _productCatalog.GetProduct(productHandle);
var productVisualization = (GameObject)Object.Instantiate(product.CarriedVisualization.Asset);
carriedProduct.SetProductVisualization(productVisualization);
}
private void ClearProductVisualization(AgentCarriedProductVisualization carriedProduct)
{
if (!carriedProduct.ClearProductVisualization(out var productVisualization)) return;
Object.Destroy(productVisualization);
}
}
}

View File

@@ -0,0 +1,55 @@
using Sirenix.OdinInspector;
using UnityEngine;
namespace DanieleMarotta.RiversongCodeShowcase
{
public class AgentCarriedProductVisualization : MonoBehaviour
{
private const string CarryingProductLayer = "CarryingProduct";
[Required]
[SerializeField]
private Animator _animator;
[Required]
[SerializeField]
private Transform _carriedProductAnchor;
private int _carryingProductLayerIndex;
private GameObject _carriedProductVisualization;
private void Awake()
{
_carryingProductLayerIndex = _animator.GetLayerIndex(CarryingProductLayer);
}
public float GetAnimatorLayerWeight()
{
return _animator.GetLayerWeight(_carryingProductLayerIndex);
}
public void SetAnimatorLayerWeight(float weight)
{
_animator.SetLayerWeight(_carryingProductLayerIndex, weight);
}
public void SetProductVisualization(GameObject visualization)
{
_carriedProductVisualization = visualization;
visualization.transform.SetParent(_carriedProductAnchor, false);
}
public bool ClearProductVisualization(out GameObject visualization)
{
visualization = _carriedProductVisualization;
_carriedProductVisualization = null;
return visualization;
}
public bool IsShowingCarriedProduct()
{
return _carriedProductVisualization;
}
}
}

View File

@@ -0,0 +1,179 @@
using Unity.Burst;
using Unity.Mathematics;
namespace DanieleMarotta.RiversongCodeShowcase
{
[BurstCompile]
[Service(typeof(IAgentCommonLogic))]
[GameSystemGroup(typeof(AgentsSystemGroup))]
public class AgentCommonLogicSystem : GameSystem, IAgentCommonLogic
{
private const float RotationSpeed = 270 * math.TORADIANS;
[InjectService]
private GameConfig _config;
[InjectService]
private ITileSpace _tileSpace;
[InjectService]
private IBuildingSpatialQuery _buildingSpatialQuery;
[InjectService]
private IAgentFactory _agentFactory;
public AgentCommonLogicSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public bool MoveAgent(Agent agent, float3 targetPosition, float dt)
{
var position = agent.Position;
var heading = agent.Heading;
var oldPosition = position;
var targetReached = MoveAgent(targetPosition, dt, _config.Agents.MoveSpeed, ref position, ref heading);
agent.Position = position;
agent.Heading = heading;
agent.Velocity = dt > 0 ? (position - oldPosition) / dt : 0;
return targetReached;
}
[BurstCompile]
private static bool MoveAgent(in float3 targetPosition, float dt, float speed, ref float3 position, ref float3 heading)
{
var positionDelta = targetPosition - position;
var targetHeading = math.normalizesafe(positionDelta, heading);
var movementLength = speed * dt;
const float targetOvershoot = 1.1f;
var targetReachedThreshold = movementLength * targetOvershoot;
var snapPosition = math.lengthsq(positionDelta) < targetReachedThreshold * targetReachedThreshold;
position = math.select(position + targetHeading * movementLength, targetPosition, snapPosition);
var snapHeading = UpdateHeading(ref heading, targetHeading, RotationSpeed * dt);
heading = math.select(heading, targetHeading, snapHeading);
return snapPosition && snapHeading;
}
public bool UpdateHeading(Agent agent, in float3 targetHeading, float dt)
{
var heading = agent.Heading;
var headingReached = UpdateHeading(ref heading, targetHeading, RotationSpeed * dt);
agent.Heading = heading;
return headingReached;
}
[BurstCompile]
private static bool UpdateHeading(ref float3 heading, in float3 targetHeading, float maxRadiansDelta)
{
var q0 = quaternion.LookRotationSafe(heading, math.up());
var q1 = quaternion.LookRotationSafe(targetHeading, math.up());
var angle = math.angle(q0, q1);
if (angle < maxRadiansDelta)
{
heading = targetHeading;
return true;
}
var t = math.min(1, maxRadiansDelta / angle);
var q = math.slerp(q0, q1, t);
heading = math.rotate(q, math.forward());
return false;
}
public bool TryFetchProductFromStorage(AgentDefinition agentDefinition,
Building source,
int productHandle,
int neededAmount,
int maxAgentCount = int.MaxValue,
AgentJob job = AgentJob.None)
{
ref var sourceStorage = ref source.GetStorageRW();
return sourceStorage.CountIncludingReservations(productHandle) < neededAmount &&
TryFindFetchStorage(source, productHandle, out var fetchBuilding) &&
TryFetchProduct(agentDefinition, source, fetchBuilding, productHandle, 1, maxAgentCount, job);
}
public bool TryFetchProductFromProvider(AgentDefinition agentDefinition,
Building source,
int productHandle,
int neededAmount,
int maxAgentCount = int.MaxValue,
AgentJob job = AgentJob.None)
{
ref var sourceStorage = ref source.GetStorageRW();
return sourceStorage.CountIncludingReservations(productHandle) < neededAmount &&
_buildingSpatialQuery.FindProviderForFetch(source.Id, source.Rect, productHandle, out var fetchBuilding) &&
TryFetchProduct(agentDefinition, source, fetchBuilding, productHandle, 1, maxAgentCount, job);
}
public bool TryFetchProduct(AgentDefinition agentDefinition,
Building source,
Building fetchBuilding,
int productHandle,
int amount,
int maxAgentCount = int.MaxValue,
AgentJob job = AgentJob.None)
{
if (!_agentFactory.CanSpawnAgent(source, maxAgentCount, job)) return false;
ref var sourceStorage = ref source.GetStorageRW();
if (sourceStorage.FreeSpace() < amount) return false;
ref var fetchStorage = ref fetchBuilding.GetStorageRW();
if (fetchStorage.AvailableNow(productHandle) < amount) return false;
sourceStorage.ReservePutting(productHandle, amount);
fetchStorage.ReserveTaking(productHandle, amount);
var position = _tileSpace.TileToWorld(source.Rect.Center);
var agent = _agentFactory.CreateVillager(agentDefinition, source, position, job);
IntentQueue.Fetch(ref agent.GetIntentQueueRW(), source.Rect, source.Id, fetchBuilding.Rect, fetchBuilding.Id, productHandle, amount, PathTraversalRules.TransportAgent);
return true;
}
public bool TryDeliverProduct(AgentDefinition agentDefinition, Building source, int productHandle, int maxAgentCount = int.MaxValue, AgentJob job = AgentJob.None)
{
if (!_agentFactory.CanSpawnAgent(source, maxAgentCount, job)) return false;
ref var sourceStorage = ref source.GetStorageRW();
if (sourceStorage.AvailableNow(productHandle) <= 0) return false;
if (!_buildingSpatialQuery.FindStorageForDelivery(source.Id, source.Rect, productHandle, out var deliveryStorage)) return false;
sourceStorage.ReserveTaking(productHandle, 1);
ref var destinationStorage = ref deliveryStorage.GetStorageRW();
destinationStorage.ReservePutting(productHandle, 1);
var position = _tileSpace.TileToWorld(source.Rect.Center);
var deliveryAgent = _agentFactory.CreateVillager(agentDefinition, source, position, job);
IntentQueue.Deliver(
ref deliveryAgent.GetIntentQueueRW(),
source.Rect,
source.Id,
deliveryStorage.Rect,
deliveryStorage.Id,
productHandle,
1,
PathTraversalRules.TransportAgent);
return true;
}
private bool TryFindFetchStorage(Building source, int productHandle, out Building fetchBuilding)
{
if (source.Definition.ProvidedProducts.Count > 0)
return _buildingSpatialQuery.FindStorageForFetch(source.Id, source.Rect, source.Definition.Range, productHandle, out fetchBuilding);
return _buildingSpatialQuery.FindStorageForFetch(source.Id, source.Rect, productHandle, out fetchBuilding);
}
}
}

View File

@@ -0,0 +1,19 @@
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.AddressableAssets;
namespace DanieleMarotta.RiversongCodeShowcase
{
[CreateAssetMenu(fileName = "AgentDefinition", menuName = "Riversong Code Showcase/Agent Definition")]
public class AgentDefinition : GameDataAsset
{
[Required]
public AssetReferenceGameObject Visualization;
public AssetReferenceGameObject Projectile;
public int WanderingRadius = 10;
public Vector2 WanderingIdleTime = new(3, 10);
}
}

View File

@@ -0,0 +1,17 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public enum AgentJob
{
None,
Harvester,
Hunter,
Farmer,
StorageRequestHauler,
Count
}
}

View File

@@ -0,0 +1,34 @@
using Unity.Mathematics;
namespace DanieleMarotta.RiversongCodeShowcase
{
public struct AgentJobState
{
public AgentJob Job;
public AgentStateMachineStep StateMachineStep;
public HarvesterJobState Harvester;
public FarmingJobState Farming;
public HunterJobState Hunter;
public struct HarvesterJobState
{
public float3 TargetPosition;
public int ResourceNodeId;
}
public struct HunterJobState
{
public int TargetHerdId;
}
public struct FarmingJobState
{
public int2? LockedTile;
}
}
}

View File

@@ -0,0 +1,11 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public enum AgentLifecycleState
{
New,
Live,
DeSpawning
}
}

View File

@@ -0,0 +1,217 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Cysharp.Threading.Tasks;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Pool;
namespace DanieleMarotta.RiversongCodeShowcase
{
[Service(typeof(IAgentFactory))]
[Service(typeof(IAgentVisualizationCollection))]
[GameSystemGroup(typeof(DefaultAgentsSystemGroup))]
public class AgentManagerSystem : GameSystem, IUpdatable, IAgentFactory, IAgentVisualizationCollection
{
[InjectService]
private IEntityCollection _entityCollection;
[InjectService]
private IScene _scene;
[InjectService]
private ITileSpace _tileSpace;
[InjectService]
private IProductCatalog _productCatalog;
[InjectService]
private IProductStackFactory _productStackFactory;
[InjectService]
private World _world;
private readonly Dictionary<int, (Agent, AgentVisualization)> _visualizations = new();
private readonly HashSet<int> _pendingVisualizations = new();
public AgentManagerSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void InitializeAgentSource(IAgentSourceEntity source)
{
ref var agentSourceState = ref source.GetAgentSourceStateRW();
agentSourceState.MaxAgentCount = agentSourceState.AgentIds.Capacity;
agentSourceState.AgentCount.Length = (int)AgentJob.Count;
agentSourceState.SpawnCooldown.Length = (int)AgentJob.Count;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool CanSpawnAgent(IAgentSourceEntity source, int maxAgentCount = int.MaxValue, AgentJob job = AgentJob.None)
{
ref var agentSourceState = ref source.GetAgentSourceStateRW();
return agentSourceState.AgentIds.Length < agentSourceState.MaxAgentCount &&
agentSourceState.AgentCount[(int)job] < maxAgentCount &&
(job == AgentJob.None || agentSourceState.SpawnCooldown[(int)job] <= 0);
}
public Agent CreateVillager(AgentDefinition definition, IAgentSourceEntity source, float3 position, AgentJob job = AgentJob.None)
{
var villager = CreateAgent(definition, source, position, job);
FinalizeAgentCreation(villager);
return villager;
}
public Agent CreateCritter(CritterDefinition critterDefinition, IAgentSourceEntity source, float3 position)
{
var critter = CreateAgent(critterDefinition.AgentDefinition, source, position, AgentJob.None);
ref var critterState = ref critter.GetCritterStateRW();
critterState.IsCritter = true;
critterState.CritterDefinitionId = critterDefinition.RuntimeId;
FinalizeAgentCreation(critter);
return critter;
}
private Agent CreateAgent(AgentDefinition definition, IAgentSourceEntity source, float3 position, AgentJob job)
{
var agent = _entityCollection.Create<Agent>();
agent.Definition = definition;
agent.HomeId = source.Id;
agent.Position = position;
agent.Heading = math.forward();
agent.CarriedProductHandle = IProductCatalog.InvalidHandle;
ref var path = ref agent.GetPathRW();
path = TilePath.Initialize();
ref var jobState = ref agent.GetJobStateRW();
jobState.Job = job;
ref var agentSourceState = ref source.GetAgentSourceStateRW();
agentSourceState.AgentIds.Add(agent.Id);
agentSourceState.AgentCount[(int)job]++;
if (job != AgentJob.None) agentSourceState.SpawnCooldown[(int)job] = source.SpawnCooldown;
return agent;
}
private void FinalizeAgentCreation(Agent agent)
{
_entityCollection.Add(agent);
_ = CreateVisualizationAsync(agent.Definition, agent);
}
private async UniTask CreateVisualizationAsync(AgentDefinition definition, Agent agent)
{
var folder = _scene.SceneFolders.Agents;
_pendingVisualizations.Add(agent.Id);
var visualizationGameObject = await definition.Visualization.InstantiateAsync(agent.Position, Quaternion.identity, folder).ToUniTask();
if (!_pendingVisualizations.Remove(agent.Id))
{
definition.Visualization.ReleaseInstance(visualizationGameObject);
return;
}
var visualization = visualizationGameObject.GetComponent<AgentVisualization>();
if (!visualization)
{
Debug.LogError($"Invalid agent visualization '{visualization.name}'");
definition.Visualization.ReleaseInstance(visualizationGameObject);
return;
}
_visualizations.Add(agent.Id, (agent, visualization));
}
public bool TryGetVisualization(int id, out AgentVisualization visualization)
{
if (_visualizations.TryGetValue(id, out var value))
{
(_, visualization) = value;
return true;
}
visualization = null;
return false;
}
public void Update()
{
DeSpawnAgents();
UpdateVisualizations();
}
private void DeSpawnAgents()
{
var agents = _entityCollection.GetInternalEntityList(typeof(Agent));
using var deSpawnScope = ListPool<Agent>.Get(out var deSpawn);
foreach (Agent agent in agents)
if (agent.LifecycleState == AgentLifecycleState.DeSpawning)
deSpawn.Add(agent);
foreach (var agent in deSpawn) DeSpawnAgent(agent);
foreach (Agent agent in agents)
if (agent.IntentExecutionState == IntentExecutionState.EmptyQueue || !_entityCollection.Exists(agent.HomeId))
agent.LifecycleState = AgentLifecycleState.DeSpawning;
}
private void DeSpawnAgent(Agent agent)
{
var product = _productCatalog.GetProduct(agent.CarriedProductHandle);
if (product)
{
var tile = _tileSpace.WorldToTile(agent.Position);
ref var entityIdValue = ref _world.EntityIdMap.GetValueRW(tile);
if (entityIdValue.BuildingId == agent.HomeId)
{
var home = _entityCollection.Get<Building>(agent.HomeId);
ref var storagePolicy = ref home.GetProductStoragePolicyRW();
if (storagePolicy.IsTakingAllowed(agent.CarriedProductHandle))
{
ref var storage = ref home.GetStorageRW();
storage.Put(agent.CarriedProductHandle, 1);
}
}
else
{
_productStackFactory.CreateOrMerge(tile, product, 1);
}
}
agent.Dispose();
_entityCollection.Remove(agent.Id);
if (_pendingVisualizations.Remove(agent.Id)) return;
var (_, visualization) = _visualizations[agent.Id];
_visualizations.Remove(agent.Id);
agent.Definition.Visualization.ReleaseInstance(visualization.gameObject);
}
private void UpdateVisualizations()
{
foreach (var (agent, visualization) in _visualizations.Values)
{
visualization.SetVisible(agent.LifecycleState == AgentLifecycleState.Live);
if (agent.LifecycleState != AgentLifecycleState.Live) continue;
var heading = math.normalizesafe(agent.Heading, math.forward());
visualization.transform.SetPositionAndRotation(agent.Position, Quaternion.LookRotation(heading));
}
}
}
}

View File

@@ -0,0 +1,15 @@
using Unity.Collections;
namespace DanieleMarotta.RiversongCodeShowcase
{
public struct AgentSourceState
{
public int MaxAgentCount;
public FixedList512Bytes<int> AgentIds;
public FixedList32Bytes<byte> AgentCount;
public FixedList128Bytes<float> SpawnCooldown;
}
}

View File

@@ -0,0 +1,40 @@
using UnityEngine;
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(EarlyAgentsSystemGroup))]
[UpdateBefore(typeof(AgentsSpawnTickSystem))]
public class AgentSpawnCooldownSystem : GameSystem, IUpdatable
{
[InjectService]
private IEntityCollection _entityCollection;
[InjectService]
private World _world;
public AgentSpawnCooldownSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public void Update()
{
var laborEfficiencyModifier = _world.ProductionState.LaborEfficiencyModifier;
foreach (Building building in _entityCollection.GetInternalEntityList(typeof(Building)))
{
ref var agentSourceState = ref building.GetAgentSourceStateRW();
ref var sleepState = ref building.GetSleepStateRW();
for (var jobIndex = 1; jobIndex < (int)AgentJob.Count; jobIndex++)
{
var cooldown = agentSourceState.SpawnCooldown[jobIndex];
if (cooldown <= 0) continue;
var efficiencyMultiplier = Mathf.Max(0, 1 + sleepState.EfficiencyModifier + laborEfficiencyModifier);
var dt = Time.deltaTime * efficiencyMultiplier;
agentSourceState.SpawnCooldown[jobIndex] = Mathf.MoveTowards(cooldown, 0, dt);
}
}
}
}
}

View File

@@ -0,0 +1,20 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
// ReSharper disable InconsistentNaming
public enum AgentStateMachineStep
{
None,
#region Generic
ReturningHome,
#endregion
#region Hunter
Hunter_SeekingPrey
#endregion
}
}

View File

@@ -0,0 +1,57 @@
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine;
namespace DanieleMarotta.RiversongCodeShowcase
{
public class AgentVisualization : MonoBehaviour
{
private static readonly int IsMovingParamID = Animator.StringToHash("IsMoving");
[Required]
[SerializeField]
private Animator _animator;
[SerializeField]
private AgentAnimationEventHandler _animationEventHandler;
[field: SerializeField] public AgentCarriedProductVisualization CarriedProductVisualization { get; private set; }
private bool _isVisible;
private List<Renderer> _renderers;
private void Awake()
{
_renderers = new List<Renderer>(1);
GetComponentsInChildren(_renderers);
_isVisible = true;
SetVisible(false);
}
public void SetVisible(bool isVisible)
{
if (_isVisible == isVisible) return;
foreach (var r in _renderers) r.enabled = isVisible;
_isVisible = isVisible;
}
public void UpdateAnimationState(bool isMoving)
{
_animator.SetBool(IsMovingParamID, isMoving);
}
public void SetAnimatorTrigger(int triggerID)
{
_animator.SetTrigger(triggerID);
}
public bool WaitAnimationCompleted()
{
return _animationEventHandler.WaitAnimationCompleted();
}
}
}

View File

@@ -0,0 +1,52 @@
using UnityEngine;
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(DefaultAgentsSystemGroup))]
[UpdateAfter(typeof(AgentManagerSystem))]
public class AgentsCleanUpSystem : GameSystem, IUpdatable
{
[InjectService]
private IEntityCollection _entityCollection;
public AgentsCleanUpSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public void Update()
{
foreach (IAgentSourceEntity agentSource in _entityCollection.GetInternalEntityList(typeof(ConstructionSite))) CleanUpAgentSource(agentSource);
foreach (IAgentSourceEntity agentSource in _entityCollection.GetInternalEntityList(typeof(Building))) CleanUpAgentSource(agentSource);
foreach (IAgentSourceEntity agentSource in _entityCollection.GetInternalEntityList(typeof(CritterHerd))) CleanUpAgentSource(agentSource);
}
private void CleanUpAgentSource(IAgentSourceEntity agentSource)
{
ref var agentSourceState = ref agentSource.GetAgentSourceStateRW();
for (var i = agentSourceState.AgentIds.Length - 1; i >= 0; i--)
{
var id = agentSourceState.AgentIds[i];
var agent = _entityCollection.Get<Agent>(id);
if (agent is not { LifecycleState: AgentLifecycleState.DeSpawning }) continue;
ref var jobState = ref agent.GetJobStateRW();
var job = jobState.Job;
var count = agentSourceState.AgentCount[(int)job];
if (count > 0)
{
agentSourceState.AgentCount[(int)job] = (byte)(count - 1);
}
else
{
var home = _entityCollection.Get<Building>(agent.HomeId);
Debug.LogError($"Agent count <= 0 when de-spawning agent. Home: '{home.Definition.name}', Job: '{job}'");
}
agentSourceState.AgentIds.RemoveAtSwapBack(i);
}
}
}
}

View File

@@ -0,0 +1,28 @@
using UnityEngine;
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(EarlyAgentsSystemGroup))]
public class AgentsSpawnTickSystem : GameSystem, IUpdatable
{
private const float SpawnInterval = 1;
[InjectService]
private World _world;
public AgentsSpawnTickSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public void Update()
{
var state = _world.AgentsState;
state.SpawnTickTimer += Time.deltaTime;
if (state.SpawnTickTimer < SpawnInterval) return;
state.SpawnTickTimer = 0;
state.SpawnTickNow = true;
}
}
}

View File

@@ -0,0 +1,114 @@
using System.Collections.Generic;
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(DayOnlyAgentSpawnSystemsGroup))]
public class ConstructionAgentSystem : GameSystem, IUpdatable
{
private const int FetchRange = 20;
[InjectService]
private GameConfig _config;
[InjectService]
private World _world;
[InjectService]
private IEntityCollection _entityCollection;
[InjectService]
private IEntityCache _entityCache;
[InjectService]
private IProductCatalog _productCatalog;
[InjectService]
private IAgentFactory _agentFactory;
[InjectService]
private ITileSpace _tileSpace;
[InjectService]
private IBuildingSpatialQuery _buildingSpatialQuery;
public ConstructionAgentSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public void Update()
{
var agentDefinition = (AgentDefinition)_config.Agents.GenericAgent.Asset;
foreach (ConstructionSite constructionSite in _entityCollection.GetInternalEntityList(typeof(ConstructionSite)))
TryFetch(constructionSite, constructionSite.Rect, constructionSite.Building.BuildingMaterials, agentDefinition);
foreach (var house in _entityCache.GetHouses())
{
ref var needsState = ref house.GetNeedsStateRW();
if (needsState.UpgradeState != TierUpgradeState.FetchingMaterials) continue;
var products = house.Definition.HouseTiers[house.TierIndex].UpgradeMaterials;
TryFetch(house, house.Rect, products, agentDefinition);
}
}
private void TryFetch<T>(T source, TileRect sourceRect, List<ProductAmountAuthoring> products, AgentDefinition agentDefinition)
where T : class, IProductStorageEntity, IAgentSourceEntity
{
if (!_agentFactory.CanSpawnAgent(source)) return;
ref var sourceStorage = ref source.GetStorageRW();
foreach (var (product, needed) in products)
{
var productHandle = _productCatalog.GetHandle(product);
sourceStorage.Get(productHandle, out var count, out var putReservations, out _);
var remaining = needed - count - putReservations;
if (remaining <= 0) continue;
if (!FindFetchDestination(source.Id, sourceRect, productHandle, out var destinationRect, out var destinationEntity)) continue;
sourceStorage.ReservePutting(productHandle, 1);
ref var destinationStorage = ref destinationEntity.GetStorageRW();
destinationStorage.ReserveTaking(productHandle, 1);
var position = _tileSpace.TileToWorld(sourceRect.Center);
var agent = _agentFactory.CreateVillager(agentDefinition, source, position);
IntentQueue.Fetch(
ref agent.GetIntentQueueRW(),
sourceRect,
source.Id,
destinationRect,
destinationEntity.Id,
productHandle,
1,
PathTraversalRules.GenericAgent | PathTraversalRules.UpdateFailedPathCache);
break;
}
}
private bool FindFetchDestination(int sourceId, TileRect sourceRect, int productHandle, out TileRect destinationRect, out IProductStorageEntity destinationEntity)
{
var productStackFound = _world.ProductStacks.FindClosest(sourceRect, FetchRange, productHandle, 1, out var productStack);
var storageFound = _buildingSpatialQuery.FindStorageForFetch(sourceId, sourceRect, FetchRange, productHandle, out var storageBuilding);
if (productStackFound || storageFound)
{
var productStackDistance = productStackFound ? TileMath.StepCount(sourceRect, productStack.Tile) : int.MaxValue;
var storageDistance = storageFound ? TileMath.StepCount(sourceRect, storageBuilding.Rect) : int.MaxValue;
destinationRect = productStackDistance < storageDistance ? TileRect.OneTile(productStack.Tile) : storageBuilding.Rect;
destinationEntity = productStackDistance < storageDistance ? productStack : storageBuilding;
return true;
}
destinationRect = TileRect.Empty;
destinationEntity = null;
return false;
}
}
}

View File

@@ -0,0 +1,24 @@
using UnityEngine;
namespace DanieleMarotta.RiversongCodeShowcase
{
[CreateAssetMenu(fileName = "CritterDefinition", menuName = "Riversong Code Showcase/Critter Definition")]
public class CritterDefinition : GameDataAsset
{
public AgentDefinition AgentDefinition;
public int MinCritterCount = 5;
public int MaxCritterCount = 15;
public float MinSpawningInterval = 10;
public float MaxSpawningInterval = 15;
public ProductDefinition Product;
public int MinProductAmount = 3;
public int MaxProductAmount = 5;
}
}

View File

@@ -0,0 +1,24 @@
using Unity.Mathematics;
namespace DanieleMarotta.RiversongCodeShowcase
{
public class CritterHerd : Entity, IAgentSourceEntity
{
private AgentSourceState _agentsState;
public CritterDefinition CritterDefinition { get; set; }
public int2 Center { get; set; }
public int MaxCritterCount { get; set; }
public float SpawnTimer { get; set; }
public float SpawnCooldown => 0;
public ref AgentSourceState GetAgentSourceStateRW()
{
return ref _agentsState;
}
}
}

View File

@@ -0,0 +1,37 @@
using Random = UnityEngine.Random;
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(DefaultAgentsSystemGroup))]
public class CritterHerdMoveCenterSystem : GameSystem, IUpdatable
{
[InjectService]
private IEntityCollection _entityCollection;
[InjectService]
private World _world;
[InjectService]
private ISignalBus _signalBus;
public CritterHerdMoveCenterSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public void Update()
{
foreach (CritterHerd herd in _entityCollection.GetInternalEntityList(typeof(CritterHerd)))
{
if (!_world.BlockMap.IsBlocked(herd.Center, BlockReason.BlocksAgents)) continue;
var oldCenter = herd.Center;
var moveDirection = ((Directions)Random.Range(0, 8)).ToVector();
var center = oldCenter + 4 * moveDirection;
if (!_world.Contains(center) || _world.BlockMap.IsBlocked(center, BlockReason.BlocksAgents)) continue;
herd.Center = center;
}
}
}
}

View File

@@ -0,0 +1,80 @@
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Pool;
using Random = UnityEngine.Random;
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(EarlyAgentsSystemGroup))]
public class CritterSpawnSystem : GameSystem, IUpdatable
{
[InjectService]
private World _world;
[InjectService]
private IEntityCollection _entityCollection;
[InjectService]
private IAgentFactory _agentFactory;
[InjectService]
private ITileSpace _tileSpace;
public CritterSpawnSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public void Update()
{
foreach (CritterHerd herd in _entityCollection.GetInternalEntityList(typeof(CritterHerd)))
{
ref var agentSourceState = ref herd.GetAgentSourceStateRW();
if (agentSourceState.AgentCount[(int)AgentJob.None] >= herd.MaxCritterCount)
{
herd.SpawnTimer = float.MinValue;
continue;
}
var critterDefinition = herd.CritterDefinition;
if (herd.SpawnTimer < 0) herd.SpawnTimer = Random.Range(critterDefinition.MinSpawningInterval, critterDefinition.MaxSpawningInterval);
herd.SpawnTimer -= Time.deltaTime;
if (herd.SpawnTimer <= 0)
{
herd.SpawnTimer = float.MinValue;
var wanderingRadius = critterDefinition.AgentDefinition.WanderingRadius;
if (!TryFindRandomFreeTile(herd.Center - wanderingRadius, herd.Center + wanderingRadius, out var tile)) continue;
var position = _tileSpace.TileToWorld(tile);
var critter = _agentFactory.CreateCritter(critterDefinition, herd, position);
critter.WanderingCenter = tile;
ref var intentQueue = ref critter.GetIntentQueueRW();
intentQueue.EnqueueIntent(AgentIntent.MakeLive());
IntentQueue.LoopWandering(ref intentQueue, 0);
}
}
}
private bool TryFindRandomFreeTile(int2 min, int2 max, out int2 tile)
{
using var candidatesScope = ListPool<int2>.Get(out var candidates);
foreach (var candidate in TileRange.From(min, max))
if (!_world.BlockMap.IsBlocked(candidate, BlockReason.BlocksAgents))
candidates.Add(candidate);
if (candidates.Count == 0)
{
tile = int2.zero;
return false;
}
tile = candidates[Random.Range(0, candidates.Count)];
return true;
}
}
}

View File

@@ -0,0 +1,11 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public struct CritterState
{
public bool IsCritter;
public int CritterDefinitionId;
public int LockId;
}
}

View File

@@ -0,0 +1,48 @@
using UnityEngine;
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(DefaultAgentsSystemGroup))]
[UpdateAfter(typeof(AgentManagerSystem))]
[UpdateBefore(typeof(AgentsCleanUpSystem))]
public class SpawnProductStackAfterCritterDeathSystem : GameSystem, IUpdatable
{
[InjectService]
private IEntityCollection _entityCollection;
[InjectService]
private ITileSpace _tileSpace;
[InjectService]
private IProductStackFactory _productStackFactory;
[InjectService]
private World _world;
public SpawnProductStackAfterCritterDeathSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public void Update()
{
foreach (CritterHerd herd in _entityCollection.GetInternalEntityList(typeof(CritterHerd)))
{
ref var agentSourceState = ref herd.GetAgentSourceStateRW();
foreach (var critterId in agentSourceState.AgentIds)
{
var critter = _entityCollection.Get<Agent>(critterId);
if (critter.LifecycleState != AgentLifecycleState.DeSpawning) continue;
var tile = _tileSpace.WorldToTile(critter.Position);
if (_world.BlockMap.IsBlocked(tile, BlockReason.BlocksAgents)) continue;
var product = herd.CritterDefinition.Product;
var amount = Random.Range(herd.CritterDefinition.MinProductAmount, herd.CritterDefinition.MaxProductAmount + 1);
_productStackFactory.CreateOrMerge(tile, product, amount);
}
}
}
}
}

View File

@@ -0,0 +1,37 @@
using UnityEngine;
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(DefaultAgentsSystemGroup))]
public class UnlockCrittersSystem : GameSystem, IUpdatable
{
[InjectService]
private IEntityCache _entityCache;
[InjectService]
private IEntityCollection _entityCollection;
[InjectService]
private IIntentLogicExecutor _intentLogicExecutor;
public UnlockCrittersSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public void Update()
{
foreach (var critter in _entityCache.GetCritterAgents())
{
ref var critterState = ref critter.GetCritterStateRW();
if (critterState.LockId == Entity.InvalidId || _entityCollection.Exists(critterState.LockId)) continue;
Debug.LogError($"Invalid lock id {critterState.LockId} on critter {critter.Id}");
critterState.LockId = Entity.InvalidId;
ref var intentQueue = ref _intentLogicExecutor.CancelRemainingIntents(critter);
IntentQueue.LoopWandering(ref intentQueue, 0);
}
}
}
}

View File

@@ -0,0 +1,10 @@
using System.Collections.Generic;
using Unity.Mathematics;
namespace DanieleMarotta.RiversongCodeShowcase
{
public class WorldCritterHerdsState
{
public List<int2> EligibleCenters { get; } = new();
}
}

View File

@@ -0,0 +1,52 @@
using System;
using Cysharp.Threading.Tasks;
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(DefaultAgentsSystemGroup))]
public class DeSpawnAgentsAtNightSystem : GameSystem, IInitializable, IDisposable
{
[InjectService]
private ISignalBus _signalBus;
[InjectService]
private IEntityCollection _entityCollection;
[InjectService]
private IIntentLogicExecutor _intentLogicExecutor;
public DeSpawnAgentsAtNightSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public UniTask InitializeAsync()
{
_signalBus.Subscribe<NightStartedSignal>(OnNightStarted);
return UniTask.CompletedTask;
}
public void Dispose()
{
_signalBus.Unsubscribe<NightStartedSignal>(OnNightStarted);
}
private void OnNightStarted(NightStartedSignal signal)
{
foreach (Agent agent in _entityCollection.GetInternalEntityList(typeof(Agent)))
{
if (agent.LifecycleState == AgentLifecycleState.DeSpawning) continue;
if (agent.CarriedProductHandle != IProductCatalog.InvalidHandle) continue;
ref var jobState = ref agent.GetJobStateRW();
if (jobState.Job == AgentJob.StorageRequestHauler) continue;
ref var critterState = ref agent.GetCritterStateRW();
if (critterState.IsCritter) continue;
_intentLogicExecutor.CancelRemainingIntents(agent);
}
}
}
}

View File

@@ -0,0 +1,40 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(DayOnlyAgentSpawnSystemsGroup))]
public class FarmingAgentSystem : GameSystem, IUpdatable
{
private const int MaxAgentCount = 1;
[InjectService]
private GameConfig _config;
[InjectService]
private IEntityCache _entityCache;
[InjectService]
private ITileSpace _tileSpace;
[InjectService]
private IAgentFactory _agentFactory;
public FarmingAgentSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public void Update()
{
var agentDefinition = (AgentDefinition)_config.Agents.FarmerAgent.Asset;
foreach (var farm in _entityCache.GetFarmBuildings())
{
if (!_agentFactory.CanSpawnAgent(farm, MaxAgentCount, AgentJob.Farmer)) continue;
var position = _tileSpace.TileToWorld(farm.Rect.Center);
var agent = _agentFactory.CreateVillager(agentDefinition, farm, position, AgentJob.Farmer);
IntentQueue.WorkFertileTile(ref agent.GetIntentQueueRW(), farm.Rect, farm.Id, agent.Id, farm.Definition.Range);
}
}
}
}

View File

@@ -0,0 +1,3 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
}

View File

@@ -0,0 +1,47 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(DayOnlyAgentSpawnSystemsGroup))]
public class HarvesterAgentSystem : GameSystem, IUpdatable
{
[InjectService]
private IEntityCollection _entityCollection;
[InjectService]
private IEntityCache _entityCache;
[InjectService]
private ITileSpace _tileSpace;
[InjectService]
private IAgentFactory _agentFactory;
[InjectService]
private IProductCatalog _productCatalog;
[InjectService]
private IBuildingSpatialQuery _buildingSpatialQuery;
[InjectService]
private IAgentCommonLogic _agentCommonLogic;
public HarvesterAgentSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public void Update()
{
foreach (var harvester in _entityCache.GetHarvesterBuildings())
{
if (!_agentFactory.CanSpawnAgent(harvester, 1, AgentJob.Harvester)) continue;
var resourceDefinition = harvester.Definition.HarvestedResource;
var position = _tileSpace.TileToWorld(harvester.Rect.Center);
var agent = _agentFactory.CreateVillager(resourceDefinition.HarvesterAgent, harvester, position, AgentJob.Harvester);
var range = harvester.Definition.Range;
IntentQueue.Harvest(ref agent.GetIntentQueueRW(), harvester.Rect, harvester.Id, resourceDefinition, range);
}
}
}
}

View File

@@ -0,0 +1,47 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(DayOnlyAgentSpawnSystemsGroup))]
public class HouseAgentSystem : GameSystem, IUpdatable
{
[InjectService]
private GameConfig _config;
[InjectService]
private IEntityCache _entityCache;
[InjectService]
private IProductCatalog _productCatalog;
[InjectService]
private IAgentCommonLogic _agentCommonLogic;
public HouseAgentSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public void Update()
{
var agentDefinition = (AgentDefinition)_config.Agents.GenericAgent.Asset;
foreach (var house in _entityCache.GetHouses())
{
ref var storage = ref house.GetStorageRW();
ref var needsState = ref house.GetNeedsStateRW();
for (var i = 0; i < needsState.Needs.Length; i++)
{
var need = needsState.Needs[i];
if (need.TierIndex > house.TierIndex) break;
if (need.Type != PopulationNeedType.Product || need.Current > need.FetchThreshold) continue;
storage.Get(need.ProductHandle, out var count, out var putReservations, out _);
if (count > 0 || putReservations > 0) continue;
if (_agentCommonLogic.TryFetchProductFromProvider(agentDefinition, house, need.ProductHandle, 1)) break;
}
}
}
}
}

View File

@@ -0,0 +1,78 @@
using Unity.Mathematics;
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(EarlyAgentsSystemGroup))]
[UpdateAfter(typeof(AgentSpawnSystemsGroup))]
public class HunterBehaviorSystem : GameSystem, IUpdatable
{
private const int HunterFireRange = 8;
[InjectService]
private IEntityCollection _entityCollection;
[InjectService]
private IEntityCache _entityCache;
[InjectService]
private ITileSpace _tileSpace;
[InjectService]
private IIntentLogicExecutor _intentLogicExecutor;
[InjectService]
private IProjectileManager _projectileManager;
public HunterBehaviorSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public void Update()
{
foreach (var hunter in _entityCache.GetHunterAgents())
{
if (hunter.LifecycleState != AgentLifecycleState.Live) continue;
ref var jobState = ref hunter.GetJobStateRW();
if (jobState.StateMachineStep != AgentStateMachineStep.Hunter_SeekingPrey) continue;
if (UpdateSeekingPrey(hunter)) jobState.StateMachineStep = AgentStateMachineStep.ReturningHome;
}
}
private bool UpdateSeekingPrey(Agent hunter)
{
const float hunterRangeSq = HunterFireRange * HunterFireRange;
if (!_entityCollection.TryGet<Building>(hunter.HomeId, out var hunterBuilding)) return false;
var hunterTile = _tileSpace.WorldToTile(hunter.Position);
var critterDefinitionId = hunterBuilding.Definition.TargetCritter.RuntimeId;
foreach (var critter in _entityCache.GetCritterAgents())
{
ref var critterState = ref critter.GetCritterStateRW();
if (critterState.CritterDefinitionId != critterDefinitionId || critter.LifecycleState != AgentLifecycleState.Live || critterState.LockId != Entity.InvalidId)
continue;
var critterTile = _tileSpace.WorldToTile(critter.Position);
if (math.distancesq(hunterTile, critterTile) > hunterRangeSq) continue;
critterState.LockId = hunter.Id;
ref var hunterIntentQueue = ref _intentLogicExecutor.CancelRemainingIntents(hunter);
var projectileTravelTime = _projectileManager.ComputeProjectileTravelTime(hunter, critter);
IntentQueue.FireAtPreyAndReturnHome(ref hunterIntentQueue, hunterBuilding.Rect, hunterBuilding.Id, critterTile, critter.Id, projectileTravelTime);
ref var critterIntentQueue = ref _intentLogicExecutor.CancelRemainingIntents(critter);
critterIntentQueue.EnqueueIntent(AgentIntent.WaitForever());
return true;
}
return false;
}
}
}

View File

@@ -0,0 +1,45 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(DayOnlyAgentSpawnSystemsGroup))]
public class HunterSpawnSystem : GameSystem, IUpdatable
{
[InjectService]
private GameConfig _config;
[InjectService]
private IEntityCache _entityCache;
[InjectService]
private ITileSpace _tileSpace;
[InjectService]
private IAgentFactory _agentFactory;
public HunterSpawnSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public void Update()
{
var agentDefinition = (AgentDefinition)_config.Agents.HunterAgent.Asset;
foreach (var hunterBuilding in _entityCache.GetHunterBuildings()) SpawnHunterAgent(hunterBuilding, agentDefinition);
}
private void SpawnHunterAgent(Building hunterBuilding, AgentDefinition agentDefinition)
{
if (!_agentFactory.CanSpawnAgent(hunterBuilding, 1, AgentJob.Hunter)) return;
var position = _tileSpace.TileToWorld(hunterBuilding.Rect.Center);
var critterId = hunterBuilding.Definition.TargetCritter.RuntimeId;
var agent = _agentFactory.CreateVillager(agentDefinition, hunterBuilding, position, AgentJob.Hunter);
ref var jobState = ref agent.GetJobStateRW();
jobState.StateMachineStep = AgentStateMachineStep.Hunter_SeekingPrey;
IntentQueue.SeekPrey(ref agent.GetIntentQueueRW(), critterId, hunterBuilding.Definition.Range);
}
}
}

View File

@@ -0,0 +1,35 @@
using Unity.Mathematics;
namespace DanieleMarotta.RiversongCodeShowcase
{
public interface IAgentCommonLogic
{
bool MoveAgent(Agent agent, float3 targetPosition, float dt);
bool UpdateHeading(Agent agent, in float3 targetHeading, float dt);
bool TryFetchProductFromStorage(AgentDefinition agentDefinition,
Building source,
int productHandle,
int neededAmount,
int maxAgentCount = int.MaxValue,
AgentJob job = AgentJob.None);
bool TryFetchProductFromProvider(AgentDefinition agentDefinition,
Building source,
int productHandle,
int neededAmount,
int maxAgentCount = int.MaxValue,
AgentJob job = AgentJob.None);
bool TryFetchProduct(AgentDefinition agentDefinition,
Building source,
Building fetchBuilding,
int productHandle,
int amount,
int maxAgentCount = int.MaxValue,
AgentJob job = AgentJob.None);
bool TryDeliverProduct(AgentDefinition agentDefinition, Building source, int productHandle, int maxAgentCount = int.MaxValue, AgentJob job = AgentJob.None);
}
}

View File

@@ -0,0 +1,15 @@
using Unity.Mathematics;
namespace DanieleMarotta.RiversongCodeShowcase
{
public interface IAgentFactory
{
void InitializeAgentSource(IAgentSourceEntity source);
bool CanSpawnAgent(IAgentSourceEntity source, int maxAgentCount = int.MaxValue, AgentJob job = AgentJob.None);
Agent CreateVillager(AgentDefinition definition, IAgentSourceEntity source, float3 position, AgentJob job = AgentJob.None);
Agent CreateCritter(CritterDefinition critterDefinition, IAgentSourceEntity source, float3 position);
}
}

View File

@@ -0,0 +1,9 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public interface IAgentSourceEntity : IEntity
{
float SpawnCooldown { get; }
ref AgentSourceState GetAgentSourceStateRW();
}
}

View File

@@ -0,0 +1,7 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public interface IAgentVisualizationCollection
{
bool TryGetVisualization(int id, out AgentVisualization visualization);
}
}

View File

@@ -0,0 +1,183 @@
using Unity.Mathematics;
namespace DanieleMarotta.RiversongCodeShowcase
{
public struct AgentIntent
{
public static readonly AgentIntent None = new() { Type = AgentIntentType.None };
public AgentIntentType Type;
public TileRect Rect;
public int TargetId;
public PathTraversalRules PathTraversalRules;
public ushort PathMaxDistance;
public byte StopBeforeGoalStepCount;
public int StorageId;
public byte ProductHandle;
public byte ProductAmount;
public float GenericFloatParam0;
public static AgentIntent MakeLive()
{
return new AgentIntent { Type = AgentIntentType.MakeLive };
}
public static AgentIntent WaitSeconds(float time)
{
return new AgentIntent
{
Type = AgentIntentType.WaitSeconds,
GenericFloatParam0 = time
};
}
public static AgentIntent WaitForever()
{
return new AgentIntent { Type = AgentIntentType.WaitForever };
}
public static AgentIntent FindPath(TileRect rect, int targetId = Entity.InvalidId, PathTraversalRules traversalRules = PathTraversalRules.None)
{
return new AgentIntent
{
Type = AgentIntentType.FindPath,
Rect = rect,
TargetId = targetId,
PathTraversalRules = traversalRules
};
}
public static AgentIntent SearchResourceNode(int targetId, PathTraversalRules traversalRules = PathTraversalRules.None, int maxDistance = 0)
{
var intent = new AgentIntent { Type = AgentIntentType.SearchResourceNode };
SetupSearchTargetIntent(ref intent, targetId, traversalRules, maxDistance);
return intent;
}
public static AgentIntent SearchFertileTile(PathTraversalRules traversalRules = PathTraversalRules.None, int maxDistance = 0)
{
var intent = new AgentIntent { Type = AgentIntentType.SearchFertileTile };
SetupSearchTargetIntent(ref intent, Entity.InvalidId, traversalRules, maxDistance);
return intent;
}
public static AgentIntent SearchCritter(int targetId, PathTraversalRules traversalRules = PathTraversalRules.None, int maxDistance = 0)
{
var intent = new AgentIntent { Type = AgentIntentType.SearchCritter };
SetupSearchTargetIntent(ref intent, targetId, traversalRules, maxDistance);
return intent;
}
private static void SetupSearchTargetIntent(ref AgentIntent intent, int targetId, PathTraversalRules traversalRules, int maxDistance)
{
intent.TargetId = targetId;
intent.PathTraversalRules = traversalRules;
intent.PathMaxDistance = (ushort)maxDistance;
}
public static AgentIntent FollowPath(int stopBeforeGoalStepCount = 0)
{
return new AgentIntent
{
Type = AgentIntentType.FollowPath,
StopBeforeGoalStepCount = (byte)stopBeforeGoalStepCount
};
}
public static AgentIntent MoveToHarvestPosition(float positionOffset)
{
return new AgentIntent
{
Type = AgentIntentType.MoveToHarvestPosition,
GenericFloatParam0 = positionOffset
};
}
public static AgentIntent HarvestResource()
{
return new AgentIntent { Type = AgentIntentType.HarvestResource };
}
public static AgentIntent HarvestFertileTile()
{
return new AgentIntent { Type = AgentIntentType.HarvestFertileTile };
}
public static AgentIntent SetFertileTileLockState(int lockId)
{
return new AgentIntent
{
Type = AgentIntentType.SetFertileTileLockState,
TargetId = lockId
};
}
public static AgentIntent FireProjectile(int targetId)
{
return new AgentIntent
{
Type = AgentIntentType.FireProjectile,
TargetId = targetId
};
}
public static AgentIntent Take(int storageId, int productHandle, int productAmount)
{
return new AgentIntent
{
Type = AgentIntentType.TakeProduct,
StorageId = storageId,
ProductHandle = (byte)productHandle,
ProductAmount = (byte)productAmount
};
}
public static AgentIntent Put(int storageId, int productHandle, int productAmount)
{
return new AgentIntent
{
Type = AgentIntentType.PutProduct,
StorageId = storageId,
ProductHandle = (byte)productHandle,
ProductAmount = (byte)productAmount
};
}
public static AgentIntent Wander()
{
return new AgentIntent { Type = AgentIntentType.Wander };
}
public static AgentIntent LoopWandering()
{
return new AgentIntent { Type = AgentIntentType.LoopWandering };
}
public static AgentIntent LookAt(int2 tile)
{
return new AgentIntent
{
Type = AgentIntentType.LookAt,
Rect = TileRect.OneTile(tile)
};
}
public static AgentIntent PlayAnimation(AgentAnimation animation)
{
return new AgentIntent
{
Type = AgentIntentType.PlayAnimation,
TargetId = (int)animation
};
}
}
}

View File

@@ -0,0 +1,47 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public enum AgentIntentType : byte
{
MakeLive,
WaitSeconds,
WaitForever,
FindPath,
SearchResourceNode,
SearchFertileTile,
SearchCritter,
FollowPath,
MoveToHarvestPosition,
HarvestResource,
HarvestFertileTile,
SetFertileTileLockState,
FireProjectile,
TakeProduct,
PutProduct,
Wander,
LoopWandering,
LookAt,
PlayAnimation,
Count,
None = byte.MaxValue
}
}

View File

@@ -0,0 +1,26 @@
using Cysharp.Threading.Tasks;
namespace DanieleMarotta.RiversongCodeShowcase
{
public class FireProjectileExecutionLogic : IntentExecutionLogic
{
private IEntityCollection _entityCollection;
private IProjectileManager _projectileManager;
public override UniTask InitializeAsync(IServiceLocator serviceLocator)
{
_entityCollection = serviceLocator.GetService<IEntityCollection>();
_projectileManager = serviceLocator.GetService<IProjectileManager>();
return UniTask.CompletedTask;
}
public override IntentExecutionResult Execute(Agent agent, in AgentIntent intent)
{
var critter = _entityCollection.Get<Agent>(intent.TargetId);
_projectileManager.FireProjectile(agent, critter);
return IntentExecutionResult.Success;
}
}
}

View File

@@ -0,0 +1,41 @@
using Cysharp.Threading.Tasks;
using Unity.Mathematics;
using UnityEngine;
namespace DanieleMarotta.RiversongCodeShowcase
{
public class FollowPathIntentExecutionLogic : IntentExecutionLogic
{
private ITileSpace _tileSpace;
private IAgentCommonLogic _agentCommonLogic;
public override UniTask InitializeAsync(IServiceLocator serviceLocator)
{
_tileSpace = serviceLocator.GetService<ITileSpace>();
_agentCommonLogic = serviceLocator.GetService<IAgentCommonLogic>();
return UniTask.CompletedTask;
}
public override IntentExecutionResult OnStartingIntent(Agent agent, in AgentIntent intent)
{
agent.PathIndex = 0;
return IntentExecutionResult.Success;
}
public override IntentExecutionResult Execute(Agent agent, in AgentIntent intent)
{
ref var path = ref agent.GetPathRW();
if (path.StepCount <= 1) return IntentExecutionResult.Success;
var nextTile = path.Steps[agent.PathIndex + 1];
var targetPosition = (float3)_tileSpace.TileToWorld(nextTile);
var targetReached = _agentCommonLogic.MoveAgent(agent, targetPosition, Time.deltaTime);
var lastStepReached = targetReached && ++agent.PathIndex >= path.StepCount - 1 - intent.StopBeforeGoalStepCount;
return lastStepReached ? IntentExecutionResult.Success : IntentExecutionResult.InProgress;
}
}
}

View File

@@ -0,0 +1,42 @@
using Cysharp.Threading.Tasks;
using Random = UnityEngine.Random;
namespace DanieleMarotta.RiversongCodeShowcase
{
public class HarvestFertileTileIntentExecutionLogic : IntentExecutionLogic
{
private IEntityCollection _entityCollection;
private IProductStackFactory _productStackFactory;
private World _world;
private ITileSpace _tileSpace;
public override UniTask InitializeAsync(IServiceLocator serviceLocator)
{
_entityCollection = serviceLocator.GetService<IEntityCollection>();
_productStackFactory = serviceLocator.GetService<IProductStackFactory>();
_world = serviceLocator.GetService<World>();
_tileSpace = serviceLocator.GetService<ITileSpace>();
return UniTask.CompletedTask;
}
public override IntentExecutionResult Execute(Agent agent, in AgentIntent intent)
{
if (!_entityCollection.TryGet<Building>(agent.HomeId, out var farm)) return IntentExecutionResult.Failure;
var tile = _tileSpace.WorldToTile(agent.Position);
ref var fertility = ref _world.Fertility.GetValueRW(tile);
fertility.CurrentFertility = 0;
var product = farm.Definition.FarmProduct;
var amount = Random.Range(farm.Definition.MinDroppedAmount, farm.Definition.MaxDroppedAmount + 1);
_productStackFactory.CreateOrMerge(tile, product, amount);
return IntentExecutionResult.Success;
}
}
}

View File

@@ -0,0 +1,31 @@
using Cysharp.Threading.Tasks;
namespace DanieleMarotta.RiversongCodeShowcase
{
public class HarvestResourceIntentExecutionLogic : IntentExecutionLogic
{
private World _world;
private ISignalBus _signalBus;
public override UniTask InitializeAsync(IServiceLocator serviceLocator)
{
_world = serviceLocator.GetService<World>();
_signalBus = serviceLocator.GetService<ISignalBus>();
return UniTask.CompletedTask;
}
public override IntentExecutionResult Execute(Agent agent, in AgentIntent intent)
{
ref var jobState = ref agent.GetJobStateRW();
var resourceNodeId = jobState.Harvester.ResourceNodeId;
if (!_world.RawResources.Exists(resourceNodeId)) return IntentExecutionResult.Failure;
_signalBus.Raise(new RawResourceHarvestedSignal(resourceNodeId));
return IntentExecutionResult.Success;
}
}
}

View File

@@ -0,0 +1,7 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public interface IIntentLogicExecutor
{
ref IntentQueue CancelRemainingIntents(Agent agent);
}
}

View File

@@ -0,0 +1,23 @@
using Cysharp.Threading.Tasks;
namespace DanieleMarotta.RiversongCodeShowcase
{
public abstract class IntentExecutionLogic
{
public virtual UniTask InitializeAsync(IServiceLocator serviceLocator)
{
return UniTask.CompletedTask;
}
public virtual IntentExecutionResult OnStartingIntent(Agent agent, in AgentIntent intent)
{
return IntentExecutionResult.Success;
}
public virtual void OnCanceled(Agent agent, AgentIntent intent)
{
}
public abstract IntentExecutionResult Execute(Agent agent, in AgentIntent intent);
}
}

View File

@@ -0,0 +1,11 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public enum IntentExecutionResult
{
InProgress,
Failure,
Success
}
}

View File

@@ -0,0 +1,11 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public enum IntentExecutionState
{
WaitingIntent,
ExecutingIntent,
EmptyQueue
}
}

View File

@@ -0,0 +1,108 @@
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace DanieleMarotta.RiversongCodeShowcase
{
[Service(typeof(IIntentLogicExecutor))]
[GameSystemGroup(typeof(DefaultAgentsSystemGroup))]
[UpdateAfter(typeof(AgentManagerSystem))]
public class IntentExecutionSystem : GameSystem, IInitializable, IUpdatable, IIntentLogicExecutor
{
[InjectService]
private IEntityCollection _entityCollection;
private readonly IntentExecutionLogic[] _executionLogic = new IntentExecutionLogic[(int)AgentIntentType.Count];
public IntentExecutionSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public async UniTask InitializeAsync()
{
var pathfindingLogic = new PathfindingIntentExecutionLogic();
var wanderingLogic = new WanderingExecutionLogic();
_executionLogic[(int)AgentIntentType.MakeLive] = new MakeLiveIntentExecutionLogic();
_executionLogic[(int)AgentIntentType.WaitSeconds] = new WaitSecondsIntentExecutionLogic();
_executionLogic[(int)AgentIntentType.WaitForever] = new WaitForeverIntentExecutionLogic();
_executionLogic[(int)AgentIntentType.FindPath] = pathfindingLogic;
_executionLogic[(int)AgentIntentType.SearchResourceNode] = pathfindingLogic;
_executionLogic[(int)AgentIntentType.SearchFertileTile] = pathfindingLogic;
_executionLogic[(int)AgentIntentType.SearchCritter] = pathfindingLogic;
_executionLogic[(int)AgentIntentType.FollowPath] = new FollowPathIntentExecutionLogic();
_executionLogic[(int)AgentIntentType.MoveToHarvestPosition] = new MoveToHarvestPositionIntentExecutionLogic();
_executionLogic[(int)AgentIntentType.HarvestResource] = new HarvestResourceIntentExecutionLogic();
_executionLogic[(int)AgentIntentType.HarvestFertileTile] = new HarvestFertileTileIntentExecutionLogic();
_executionLogic[(int)AgentIntentType.SetFertileTileLockState] = new SetFertileTileLockStateIntentExecutionLogic();
_executionLogic[(int)AgentIntentType.FireProjectile] = new FireProjectileExecutionLogic();
_executionLogic[(int)AgentIntentType.TakeProduct] = new TakeProductIntentExecutionLogic();
_executionLogic[(int)AgentIntentType.PutProduct] = new PutProductIntentExecutionLogic();
_executionLogic[(int)AgentIntentType.Wander] = wanderingLogic;
_executionLogic[(int)AgentIntentType.LoopWandering] = wanderingLogic;
_executionLogic[(int)AgentIntentType.LookAt] = new LookAtIntentExecutionLogic();
_executionLogic[(int)AgentIntentType.PlayAnimation] = new PlayAnimationIntentExecutionLogic();
foreach (var executionLogic in _executionLogic) await executionLogic.InitializeAsync(ServiceLocator);
}
public void Update()
{
foreach (Agent agent in _entityCollection.GetInternalEntityList(typeof(Agent))) UpdateAgent(agent);
}
private void UpdateAgent(Agent agent)
{
if (agent.LifecycleState == AgentLifecycleState.DeSpawning)
{
CancelRemainingIntents(agent);
return;
}
ref var intentQueue = ref agent.GetIntentQueueRW();
if (!intentQueue.TryPeekIntent(out var intent))
{
agent.IntentExecutionState = IntentExecutionState.EmptyQueue;
return;
}
var executionLogic = _executionLogic[(int)intent.Type];
if (agent.IntentExecutionState == IntentExecutionState.WaitingIntent)
{
if (executionLogic.OnStartingIntent(agent, intent) == IntentExecutionResult.Failure)
{
intentQueue.TryDequeueIntent(out _);
CancelRemainingIntents(agent);
return;
}
agent.IntentExecutionState = IntentExecutionState.ExecutingIntent;
agent.IntentExecutionTime = 0;
}
var result = executionLogic.Execute(agent, intent);
if (result == IntentExecutionResult.InProgress)
{
agent.IntentExecutionTime += Time.deltaTime;
return;
}
intentQueue.TryDequeueIntent(out _);
if (result == IntentExecutionResult.Failure) CancelRemainingIntents(agent);
agent.IntentExecutionState = IntentExecutionState.WaitingIntent;
}
public ref IntentQueue CancelRemainingIntents(Agent agent)
{
ref var intentQueue = ref agent.GetIntentQueueRW();
while (intentQueue.TryDequeueIntent(out var intent)) _executionLogic[(int)intent.Type].OnCanceled(agent, intent);
agent.IntentExecutionState = IntentExecutionState.WaitingIntent;
return ref intentQueue;
}
}
}

View File

@@ -0,0 +1,156 @@
using Unity.Collections;
using Unity.Mathematics;
namespace DanieleMarotta.RiversongCodeShowcase
{
public struct IntentQueue
{
private FixedList512Bytes<AgentIntent> _intentQueue;
public int Count => _intentQueue.Length;
public void EnqueueIntent(in AgentIntent intent)
{
_intentQueue.Add(intent);
}
public bool TryDequeueIntent(out AgentIntent intent)
{
if (_intentQueue.Length > 0)
{
intent = _intentQueue[0];
_intentQueue.RemoveAt(0);
return true;
}
intent = AgentIntent.None;
return false;
}
public bool TryPeekIntent(out AgentIntent intent)
{
if (_intentQueue.Length > 0)
{
intent = _intentQueue[0];
return true;
}
intent = AgentIntent.None;
return false;
}
public void Clear()
{
_intentQueue.Clear();
}
public static void Fetch(ref IntentQueue intentQueue,
TileRect sourceRect,
int sourceId,
TileRect destinationRect,
int destinationId,
int productHandle,
int amount,
PathTraversalRules traversalRules)
{
intentQueue.EnqueueIntent(AgentIntent.FindPath(destinationRect, destinationId, traversalRules));
intentQueue.EnqueueIntent(AgentIntent.MakeLive());
intentQueue.EnqueueIntent(AgentIntent.FollowPath());
intentQueue.EnqueueIntent(AgentIntent.Take(destinationId, productHandle, amount));
intentQueue.EnqueueIntent(AgentIntent.FindPath(sourceRect, sourceId, traversalRules));
intentQueue.EnqueueIntent(AgentIntent.FollowPath());
intentQueue.EnqueueIntent(AgentIntent.Put(sourceId, productHandle, amount));
}
public static void Deliver(ref IntentQueue intentQueue,
TileRect sourceRect,
int sourceId,
TileRect destinationRect,
int destinationId,
int productHandle,
int amount,
PathTraversalRules traversalRules)
{
intentQueue.EnqueueIntent(AgentIntent.FindPath(destinationRect, destinationId, traversalRules));
intentQueue.EnqueueIntent(AgentIntent.MakeLive());
intentQueue.EnqueueIntent(AgentIntent.Take(sourceId, productHandle, amount));
intentQueue.EnqueueIntent(AgentIntent.FollowPath());
intentQueue.EnqueueIntent(AgentIntent.Put(destinationId, productHandle, amount));
intentQueue.EnqueueIntent(AgentIntent.FindPath(sourceRect, sourceId, traversalRules));
intentQueue.EnqueueIntent(AgentIntent.FollowPath());
}
public static void MoveProduct(ref IntentQueue intentQueue,
TileRect sourceRect,
int sourceId,
TileRect fetchRect,
int fetchId,
TileRect deliverRect,
int deliverId,
int productHandle,
int amount,
PathTraversalRules traversalRules)
{
intentQueue.EnqueueIntent(AgentIntent.FindPath(fetchRect, fetchId, traversalRules));
intentQueue.EnqueueIntent(AgentIntent.MakeLive());
intentQueue.EnqueueIntent(AgentIntent.FollowPath());
intentQueue.EnqueueIntent(AgentIntent.Take(fetchId, productHandle, amount));
intentQueue.EnqueueIntent(AgentIntent.FindPath(deliverRect, deliverId, traversalRules));
intentQueue.EnqueueIntent(AgentIntent.FollowPath());
intentQueue.EnqueueIntent(AgentIntent.Put(deliverId, productHandle, amount));
intentQueue.EnqueueIntent(AgentIntent.FindPath(sourceRect, sourceId, traversalRules));
intentQueue.EnqueueIntent(AgentIntent.FollowPath());
}
public static void Harvest(ref IntentQueue intentQueue, TileRect sourceRect, int sourceId, ResourceNodeDefinition resource, int maxDistance)
{
intentQueue.EnqueueIntent(AgentIntent.SearchResourceNode(resource.RuntimeId, PathTraversalRules.GenericAgent, maxDistance));
intentQueue.EnqueueIntent(AgentIntent.MakeLive());
intentQueue.EnqueueIntent(AgentIntent.FollowPath(1));
intentQueue.EnqueueIntent(AgentIntent.MoveToHarvestPosition(resource.HarvestPositionOffset));
intentQueue.EnqueueIntent(AgentIntent.PlayAnimation(resource.HarvestAnimation));
intentQueue.EnqueueIntent(AgentIntent.HarvestResource());
intentQueue.EnqueueIntent(AgentIntent.WaitSeconds(1.5f));
intentQueue.EnqueueIntent(AgentIntent.FindPath(sourceRect, sourceId));
intentQueue.EnqueueIntent(AgentIntent.FollowPath());
}
public static void SeekPrey(ref IntentQueue intentQueue, int critterDefinitionId, int maxDistance)
{
intentQueue.EnqueueIntent(AgentIntent.SearchCritter(critterDefinitionId, PathTraversalRules.GenericAgent, maxDistance));
intentQueue.EnqueueIntent(AgentIntent.MakeLive());
intentQueue.EnqueueIntent(AgentIntent.FollowPath());
}
public static void FireAtPreyAndReturnHome(ref IntentQueue intentQueue, TileRect homeRect, int homeId, int2 critterTile, int critterId, float projectileTravelTime)
{
intentQueue.EnqueueIntent(AgentIntent.LookAt(critterTile));
intentQueue.EnqueueIntent(AgentIntent.PlayAnimation(AgentAnimation.FireProjectile));
intentQueue.EnqueueIntent(AgentIntent.FireProjectile(critterId));
intentQueue.EnqueueIntent(AgentIntent.WaitSeconds(projectileTravelTime + 1));
intentQueue.EnqueueIntent(AgentIntent.FindPath(homeRect, homeId));
intentQueue.EnqueueIntent(AgentIntent.FollowPath());
}
public static void WorkFertileTile(ref IntentQueue intentQueue, TileRect sourceRect, int sourceId, int agentId, int maxDistance)
{
intentQueue.EnqueueIntent(AgentIntent.SearchFertileTile(PathTraversalRules.GenericAgent | PathTraversalRules.FertileTilesOnly, maxDistance));
intentQueue.EnqueueIntent(AgentIntent.SetFertileTileLockState(agentId));
intentQueue.EnqueueIntent(AgentIntent.MakeLive());
intentQueue.EnqueueIntent(AgentIntent.FollowPath());
intentQueue.EnqueueIntent(AgentIntent.PlayAnimation(AgentAnimation.Farming));
intentQueue.EnqueueIntent(AgentIntent.HarvestFertileTile());
intentQueue.EnqueueIntent(AgentIntent.WaitSeconds(2.5f));
intentQueue.EnqueueIntent(AgentIntent.SetFertileTileLockState(Entity.InvalidId));
intentQueue.EnqueueIntent(AgentIntent.FindPath(sourceRect, sourceId));
intentQueue.EnqueueIntent(AgentIntent.FollowPath());
}
public static void LoopWandering(ref IntentQueue intentQueue, float idleTime)
{
intentQueue.EnqueueIntent(AgentIntent.WaitSeconds(idleTime));
intentQueue.EnqueueIntent(AgentIntent.Wander());
intentQueue.EnqueueIntent(AgentIntent.FollowPath());
intentQueue.EnqueueIntent(AgentIntent.LoopWandering());
}
}
}

View File

@@ -0,0 +1,63 @@
using System.Runtime.CompilerServices;
using Cysharp.Threading.Tasks;
using Unity.Burst;
using Unity.Mathematics;
using UnityEngine;
namespace DanieleMarotta.RiversongCodeShowcase
{
[BurstCompile]
public class LookAtIntentExecutionLogic : IntentExecutionLogic
{
private ITileSpace _tileSpace;
private float _rotationSpeed;
public override UniTask InitializeAsync(IServiceLocator serviceLocator)
{
_tileSpace = serviceLocator.GetService<ITileSpace>();
_rotationSpeed = serviceLocator.GetService<GameConfig>().Agents.RotationSpeed;
return UniTask.CompletedTask;
}
public override IntentExecutionResult Execute(Agent agent, in AgentIntent intent)
{
var lookAt = math.normalizesafe((float3)_tileSpace.TileToWorld(intent.Rect.Center) - agent.Position);
if (math.all(lookAt == 0)) return IntentExecutionResult.Success;
var step = math.radians(_rotationSpeed) * Time.deltaTime;
var heading = agent.Heading;
LookAt(ref heading, lookAt, step);
agent.Heading = heading;
return math.dot(heading, lookAt) > 0.995f ? IntentExecutionResult.Success : IntentExecutionResult.InProgress;
}
[BurstCompile]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void LookAt(ref float3 heading, in float3 lookAt, float maxRadiansDelta)
{
var currentRotation = quaternion.LookRotationSafe(heading, math.up());
var targetRotation = quaternion.LookRotationSafe(lookAt, math.up());
var dot = math.dot(currentRotation, targetRotation);
if (dot < 0)
{
dot = -dot;
targetRotation.value = -targetRotation.value;
}
dot = math.clamp(dot, -1, 1);
var angle = math.acos(dot) * 2;
if (angle <= maxRadiansDelta)
{
heading = lookAt;
return;
}
currentRotation = math.slerp(currentRotation, targetRotation, maxRadiansDelta / angle);
heading = math.mul(currentRotation, math.forward());
}
}
}

View File

@@ -0,0 +1,50 @@
using Cysharp.Threading.Tasks;
using Unity.Mathematics;
namespace DanieleMarotta.RiversongCodeShowcase
{
public class MakeLiveIntentExecutionLogic : IntentExecutionLogic
{
private ITileSpace _tileSpace;
private IAgentVisualizationCollection _visualizationCollection;
public override UniTask InitializeAsync(IServiceLocator serviceLocator)
{
_tileSpace = serviceLocator.GetService<ITileSpace>();
_visualizationCollection = serviceLocator.GetService<IAgentVisualizationCollection>();
return UniTask.CompletedTask;
}
public override IntentExecutionResult Execute(Agent agent, in AgentIntent intent)
{
switch (agent.LifecycleState)
{
case AgentLifecycleState.DeSpawning:
return IntentExecutionResult.Failure;
case AgentLifecycleState.Live:
return IntentExecutionResult.Success;
}
if (!_visualizationCollection.TryGetVisualization(agent.Id, out _)) return IntentExecutionResult.InProgress;
agent.LifecycleState = AgentLifecycleState.Live;
SetInitialHeading(agent);
return IntentExecutionResult.Success;
}
private void SetInitialHeading(Agent agent)
{
ref var path = ref agent.GetPathRW();
if (path.StepCount <= 1) return;
var p0 = _tileSpace.TileToWorld(path.Steps[0]);
var p1 = _tileSpace.TileToWorld(path.Steps[1]);
agent.Heading = math.normalizesafe(p1 - p0, math.forward());
}
}
}

View File

@@ -0,0 +1,58 @@
using Cysharp.Threading.Tasks;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Pool;
namespace DanieleMarotta.RiversongCodeShowcase
{
public class MoveToHarvestPositionIntentExecutionLogic : IntentExecutionLogic
{
private GameConfig _config;
private World _world;
private IAgentCommonLogic _agentCommonLogic;
public override UniTask InitializeAsync(IServiceLocator serviceLocator)
{
_config = serviceLocator.GetService<GameConfig>();
_world = serviceLocator.GetService<World>();
_agentCommonLogic = serviceLocator.GetService<IAgentCommonLogic>();
return UniTask.CompletedTask;
}
public override IntentExecutionResult OnStartingIntent(Agent agent, in AgentIntent intent)
{
ref var path = ref agent.GetPathRW();
var tile = path.Steps[^1];
using var resourceNodesScope = ListPool<(int, bool)>.Get(out var resourceNodeIds);
_world.RawResources.GetResourceNodes(TileRect.OneTile(tile), _config.GeneralSettings.BaseElevation, resourceNodeIds);
if (resourceNodeIds.Count <= 0) return IntentExecutionResult.Failure;
var (id, _) = resourceNodeIds[0];
_world.RawResources.TryGetResourceNode(id, out var resourceNode);
ref var jobState = ref agent.GetJobStateRW();
jobState.Harvester.ResourceNodeId = id;
jobState.Harvester.TargetPosition = resourceNode.Position + math.normalizesafe(agent.Position - resourceNode.Position) * intent.GenericFloatParam0;
return IntentExecutionResult.Success;
}
public override IntentExecutionResult Execute(Agent agent, in AgentIntent intent)
{
ref var jobState = ref agent.GetJobStateRW();
var targetReached = _agentCommonLogic.MoveAgent(agent, jobState.Harvester.TargetPosition, Time.deltaTime);
if (!targetReached) return IntentExecutionResult.InProgress;
if (!_world.RawResources.TryGetResourceNode(jobState.Harvester.ResourceNodeId, out var resourceNode)) return IntentExecutionResult.Failure;
var desiredHeading = math.normalizesafe(resourceNode.Position - agent.Position, agent.Heading);
return _agentCommonLogic.UpdateHeading(agent, desiredHeading, Time.deltaTime) ? IntentExecutionResult.Success : IntentExecutionResult.InProgress;
}
}
}

View File

@@ -0,0 +1,12 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public struct PathQueryFailedSignal
{
public PathTraversalRules TraversalRules;
public PathQueryFailedSignal(PathTraversalRules traversalRules)
{
TraversalRules = traversalRules;
}
}
}

View File

@@ -0,0 +1,82 @@
using System;
using Cysharp.Threading.Tasks;
using Unity.Mathematics;
namespace DanieleMarotta.RiversongCodeShowcase
{
public class PathfindingIntentExecutionLogic : IntentExecutionLogic
{
private IPathfinder _pathfinder;
private ITileSpace _tileSpace;
private IFailedPathCache _failedPathCache;
private ISignalBus _signalBus;
public override UniTask InitializeAsync(IServiceLocator serviceLocator)
{
_pathfinder = serviceLocator.GetService<IPathfinder>();
_tileSpace = serviceLocator.GetService<ITileSpace>();
_failedPathCache = serviceLocator.GetService<IFailedPathCache>();
_signalBus = serviceLocator.GetService<ISignalBus>();
return UniTask.CompletedTask;
}
public override IntentExecutionResult OnStartingIntent(Agent agent, in AgentIntent intent)
{
ref var path = ref agent.GetPathRW();
path.Steps.Clear();
var searchType = intent.Type switch
{
AgentIntentType.FindPath => PathSearchType.FindPath,
AgentIntentType.SearchResourceNode => PathSearchType.SearchResourceNode,
AgentIntentType.SearchFertileTile => PathSearchType.SearchFertileTile,
AgentIntentType.SearchCritter => PathSearchType.SearchCritterHerd,
_ => throw new ArgumentOutOfRangeException()
};
var query = new PathQuery
{
SearchType = searchType,
SourceTile = _tileSpace.WorldToTile(agent.Position),
DestinationRect = intent.Rect,
TargetId = intent.TargetId,
TraversalRules = intent.PathTraversalRules,
MaxDistance = intent.PathMaxDistance
};
agent.PathQueryHandle = _pathfinder.FindPath(path, ref query);
return IntentExecutionResult.Success;
}
public override IntentExecutionResult Execute(Agent agent, in AgentIntent intent)
{
switch (_pathfinder.CompletePath(agent.PathQueryHandle))
{
case PathQueryResult.InProgress:
return IntentExecutionResult.InProgress;
case PathQueryResult.InvalidQuery:
return IntentExecutionResult.Failure;
case PathQueryResult.Failure:
if ((intent.PathTraversalRules & PathTraversalRules.UpdateFailedPathCache) != 0) _failedPathCache.FailPath(agent.HomeId, intent.TargetId);
_signalBus.Raise(new PathQueryFailedSignal(intent.PathTraversalRules));
return IntentExecutionResult.Failure;
}
ref var path = ref agent.GetPathRW();
var agentTile = _tileSpace.WorldToTile(agent.Position);
if (path.Steps.Length > 0 && math.any(agentTile != path.Steps[0]))
{
agent.Position = _tileSpace.TileToWorld(path.Steps[0]);
agent.Velocity = float3.zero;
}
return IntentExecutionResult.Success;
}
}
}

View File

@@ -0,0 +1,52 @@
using System;
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace DanieleMarotta.RiversongCodeShowcase
{
public class PlayAnimationIntentExecutionLogic : IntentExecutionLogic
{
private static readonly int ChopTree = Animator.StringToHash("ChopTree");
private static readonly int Farming = Animator.StringToHash("Farming");
private static readonly int FireProjectile = Animator.StringToHash("FireProjectile");
private IAgentVisualizationCollection _agentVisualizationCollection;
public override UniTask InitializeAsync(IServiceLocator serviceLocator)
{
_agentVisualizationCollection = serviceLocator.GetService<IAgentVisualizationCollection>();
return UniTask.CompletedTask;
}
public override IntentExecutionResult OnStartingIntent(Agent agent, in AgentIntent intent)
{
if (!_agentVisualizationCollection.TryGetVisualization(agent.Id, out var visualization)) return IntentExecutionResult.Failure;
var triggerID = GetAnimatorTriggerID((AgentAnimation)intent.TargetId);
visualization.SetAnimatorTrigger(triggerID);
return IntentExecutionResult.Success;
}
public override IntentExecutionResult Execute(Agent agent, in AgentIntent intent)
{
if (!_agentVisualizationCollection.TryGetVisualization(agent.Id, out var visualization)) return IntentExecutionResult.Failure;
return !visualization.WaitAnimationCompleted() ? IntentExecutionResult.InProgress : IntentExecutionResult.Success;
}
private int GetAnimatorTriggerID(AgentAnimation animation)
{
return animation switch
{
AgentAnimation.ChopTree => ChopTree,
AgentAnimation.Farming => Farming,
AgentAnimation.FireProjectile => FireProjectile,
_ => throw new ArgumentOutOfRangeException()
};
}
}
}

View File

@@ -0,0 +1,44 @@
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace DanieleMarotta.RiversongCodeShowcase
{
public class PutProductIntentExecutionLogic : IntentExecutionLogic
{
private IProductStorageManager _productStorageManager;
public override UniTask InitializeAsync(IServiceLocator serviceLocator)
{
_productStorageManager = serviceLocator.GetService<IProductStorageManager>();
return UniTask.CompletedTask;
}
public override void OnCanceled(Agent agent, AgentIntent intent)
{
base.OnCanceled(agent, intent);
if (!_productStorageManager.TryGetProductStorageEntity(intent.StorageId, out var storageEntity)) return;
ref var storage = ref storageEntity.GetStorageRW();
storage.ReleasePutReservation(intent.ProductHandle, intent.ProductAmount);
}
public override IntentExecutionResult Execute(Agent agent, in AgentIntent intent)
{
if (!_productStorageManager.TryGetProductStorageEntity(intent.StorageId, out var storageEntity)) return IntentExecutionResult.Failure;
ref var storage = ref storageEntity.GetStorageRW();
if (!storage.FulfillPut(intent.ProductHandle, intent.ProductAmount))
{
storage.ReleasePutReservation(intent.ProductHandle, intent.ProductAmount);
Debug.LogError("Agent could not fulfill putting product");
return IntentExecutionResult.Failure;
}
agent.CarriedProductHandle = IProductCatalog.InvalidHandle;
return IntentExecutionResult.Success;
}
}
}

View File

@@ -0,0 +1,61 @@
using Cysharp.Threading.Tasks;
namespace DanieleMarotta.RiversongCodeShowcase
{
public class SetFertileTileLockStateIntentExecutionLogic : IntentExecutionLogic
{
private World _world;
private ITileSpace _tileSpace;
public override UniTask InitializeAsync(IServiceLocator serviceLocator)
{
_world = serviceLocator.GetService<World>();
_tileSpace = serviceLocator.GetService<ITileSpace>();
return UniTask.CompletedTask;
}
public override void OnCanceled(Agent agent, AgentIntent intent)
{
if (intent.TargetId != Entity.InvalidId) return;
Unlock(agent);
}
public override IntentExecutionResult Execute(Agent agent, in AgentIntent intent)
{
return intent.TargetId == Entity.InvalidId ? Unlock(agent) : Lock(agent);
}
private IntentExecutionResult Lock(Agent agent)
{
ref var path = ref agent.GetPathRW();
var tile = path.StepCount > 0 ? path.Steps[^1] : _tileSpace.WorldToTile(agent.Position);
ref var fertility = ref _world.Fertility.GetValueRW(tile);
if (fertility.MaxFertility <= 0 || fertility.CurrentFertility < fertility.MaxFertility || fertility.LockId != Entity.InvalidId) return IntentExecutionResult.Failure;
fertility.LockId = agent.Id;
ref var farming = ref agent.GetJobStateRW().Farming;
farming.LockedTile = tile;
return IntentExecutionResult.Success;
}
private IntentExecutionResult Unlock(Agent agent)
{
ref var farming = ref agent.GetJobStateRW().Farming;
if (farming.LockedTile == null) return IntentExecutionResult.Success;
var tile = farming.LockedTile.Value;
ref var fertility = ref _world.Fertility.GetValueRW(tile);
if (fertility.LockId == agent.Id) fertility.LockId = Entity.InvalidId;
farming.LockedTile = null;
return IntentExecutionResult.Success;
}
}
}

View File

@@ -0,0 +1,43 @@
using Cysharp.Threading.Tasks;
using UnityEngine;
namespace DanieleMarotta.RiversongCodeShowcase
{
public class TakeProductIntentExecutionLogic : IntentExecutionLogic
{
private IProductStorageManager _productStorageManager;
public override UniTask InitializeAsync(IServiceLocator serviceLocator)
{
_productStorageManager = serviceLocator.GetService<IProductStorageManager>();
return UniTask.CompletedTask;
}
public override void OnCanceled(Agent agent, AgentIntent intent)
{
base.OnCanceled(agent, intent);
if (!_productStorageManager.TryGetProductStorageEntity(intent.StorageId, out var storageEntity)) return;
ref var storage = ref storageEntity.GetStorageRW();
storage.ReleaseTakeReservation(intent.ProductHandle, intent.ProductAmount);
}
public override IntentExecutionResult Execute(Agent agent, in AgentIntent intent)
{
if (!_productStorageManager.TryGetProductStorageEntity(intent.StorageId, out var storageEntity)) return IntentExecutionResult.Failure;
ref var storage = ref storageEntity.GetStorageRW();
if (!storage.FulfillTake(intent.ProductHandle, intent.ProductAmount))
{
Debug.LogError("Agent could not fulfill taking product");
return IntentExecutionResult.Failure;
}
agent.CarriedProductHandle = intent.ProductHandle;
return IntentExecutionResult.Success;
}
}
}

View File

@@ -0,0 +1,10 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public class WaitForeverIntentExecutionLogic : IntentExecutionLogic
{
public override IntentExecutionResult Execute(Agent agent, in AgentIntent intent)
{
return IntentExecutionResult.InProgress;
}
}
}

View File

@@ -0,0 +1,10 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public class WaitSecondsIntentExecutionLogic : IntentExecutionLogic
{
public override IntentExecutionResult Execute(Agent agent, in AgentIntent intent)
{
return agent.IntentExecutionTime < intent.GenericFloatParam0 ? IntentExecutionResult.InProgress : IntentExecutionResult.Success;
}
}
}

View File

@@ -0,0 +1,74 @@
using System;
using Cysharp.Threading.Tasks;
using Unity.Mathematics;
using UnityEngine.Pool;
using Random = UnityEngine.Random;
namespace DanieleMarotta.RiversongCodeShowcase
{
public class WanderingExecutionLogic : IntentExecutionLogic
{
private ITileSpace _tileSpace;
private World _world;
public override UniTask InitializeAsync(IServiceLocator serviceLocator)
{
_tileSpace = serviceLocator.GetService<ITileSpace>();
_world = serviceLocator.GetService<World>();
return UniTask.CompletedTask;
}
public override IntentExecutionResult Execute(Agent agent, in AgentIntent intent)
{
switch (intent.Type)
{
case AgentIntentType.Wander:
return Wander(agent);
case AgentIntentType.LoopWandering:
LoopWandering(agent);
return IntentExecutionResult.Success;
}
throw new InvalidOperationException();
}
private IntentExecutionResult Wander(Agent agent)
{
using var candidatesScope = ListPool<int2>.Get(out var candidates);
var tile = _tileSpace.WorldToTile(agent.Position);
for (var i = 0; i < 8; i++)
{
var candidate = ((Directions)i).ToVector();
if (!_world.BlockMap.IsBlocked(tile + candidate, BlockReason.BlocksAgents)) candidates.Add(candidate);
}
if (candidates.Count <= 0) return IntentExecutionResult.Failure;
var direction = candidates[Random.Range(0, candidates.Count)];
if (agent.Definition.WanderingRadius > 0)
{
var distance = TileMath.StepCount(agent.WanderingCenter, tile + direction);
if (distance > agent.Definition.WanderingRadius) direction = math.sign(agent.WanderingCenter - tile);
}
ref var path = ref agent.GetPathRW();
path.Steps.Clear();
path.Steps.Add(tile);
path.Steps.Add(tile + direction);
return IntentExecutionResult.Success;
}
private void LoopWandering(Agent agent)
{
var idleTimeRange = agent.Definition.WanderingIdleTime;
var idleTime = Random.Range(idleTimeRange.x, idleTimeRange.y);
IntentQueue.LoopWandering(ref agent.GetIntentQueueRW(), idleTime);
}
}
}

View File

@@ -0,0 +1,39 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(DayOnlyAgentSpawnSystemsGroup))]
public class ProducerDeliveryAgentSystem : GameSystem, IUpdatable
{
[InjectService]
private GameConfig _config;
[InjectService]
private IEntityCache _entityCache;
[InjectService]
private IAgentCommonLogic _agentCommonLogic;
public ProducerDeliveryAgentSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public void Update()
{
var agentDefinition = (AgentDefinition)_config.Agents.GenericAgent.Asset;
foreach (var producer in _entityCache.GetProducers())
{
if (!producer.Definition.DeliversOutput) continue;
ref var productionState = ref producer.GetProductionStateRW();
ref var recipe = ref productionState.Recipe;
foreach (var (productHandle, amount) in recipe.Inputs)
if (_agentCommonLogic.TryFetchProductFromStorage(agentDefinition, producer, productHandle, amount))
return;
_agentCommonLogic.TryDeliverProduct(agentDefinition, producer, recipe.OutputProductHandle);
}
}
}
}

View File

@@ -0,0 +1,36 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(DayOnlyAgentSpawnSystemsGroup))]
public class ProviderAgentSystem : GameSystem, IUpdatable
{
private const int MaxAgentCount = 10;
[InjectService]
private GameConfig _config;
[InjectService]
private IEntityCache _entityCache;
[InjectService]
private IProductCatalog _productCatalog;
[InjectService]
private IAgentCommonLogic _agentCommonLogic;
public ProviderAgentSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public void Update()
{
var agentDefinition = (AgentDefinition)_config.Agents.GenericAgent.Asset;
foreach (var provider in _entityCache.GetProviders())
if (provider.Definition.FetchesProducts)
foreach (var (product, amount) in provider.Definition.ProvidedProducts)
if (_agentCommonLogic.TryFetchProductFromStorage(agentDefinition, provider, _productCatalog.GetHandle(product), amount, MaxAgentCount))
break;
}
}
}

View File

@@ -0,0 +1,21 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(AgentsSystemGroup))]
[UpdateBefore(typeof(IntentExecutionSystem))]
[UpdateBefore(typeof(AgentAnimationSystem))]
public class ResetAgentVelocitySystem : GameSystem, IUpdatable
{
[InjectService]
private IEntityCollection _entityCollection;
public ResetAgentVelocitySystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public void Update()
{
foreach (Agent agent in _entityCollection.GetInternalEntityList(typeof(Agent))) agent.Velocity = 0;
}
}
}

View File

@@ -0,0 +1,19 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(LateAgentsSystemGroup))]
public class ResetAgentsSpawnTickSystem : GameSystem, IUpdatable
{
[InjectService]
private World _world;
public ResetAgentsSpawnTickSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public void Update()
{
_world.AgentsState.SpawnTickNow = false;
}
}
}

View File

@@ -0,0 +1,63 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(DayOnlyAgentSpawnSystemsGroup))]
[UpdateBefore(typeof(ConstructionAgentSystem))]
public class StorageBuildingFetchProductStacksAgentSystem : GameSystem, IUpdatable
{
private const int MaxAgentCount = 10;
[InjectService]
private GameConfig _config;
[InjectService]
private World _world;
[InjectService]
private IEntityCache _entityCache;
[InjectService]
private IAgentFactory _agentFactory;
[InjectService]
private ITileSpace _tileSpace;
public StorageBuildingFetchProductStacksAgentSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public void Update()
{
var agentDefinition = (AgentDefinition)_config.Agents.GenericAgent.Asset;
foreach (var storageBuilding in _entityCache.GetStorageBuildings())
{
if (!_agentFactory.CanSpawnAgent(storageBuilding, MaxAgentCount)) continue;
ref var storage = ref storageBuilding.GetStorageRW();
if (storage.FreeSpace() <= 0) continue;
ref var storagePolicy = ref storageBuilding.GetProductStoragePolicyRW();
if (!_world.ProductStacks.FindClosest(storageBuilding.Rect, storageBuilding.Definition.Range, 1, storagePolicy, out var productStack)) continue;
storage.ReservePutting(productStack.ProductHandle, 1);
ref var productStackStorage = ref productStack.GetStorageRW();
productStackStorage.ReserveTaking(productStack.ProductHandle, 1);
var position = _tileSpace.TileToWorld(storageBuilding.Rect.Center);
var agent = _agentFactory.CreateVillager(agentDefinition, storageBuilding, position);
IntentQueue.Fetch(
ref agent.GetIntentQueueRW(),
storageBuilding.Rect,
storageBuilding.Id,
TileRect.OneTile(productStack.Tile),
productStack.Id,
productStack.ProductHandle,
1,
PathTraversalRules.GenericAgent | PathTraversalRules.UpdateFailedPathCache);
}
}
}
}

View File

@@ -0,0 +1,63 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(AgentSpawnSystemsGroup))]
public class StorageBuildingRequestProductAgentSystem : GameSystem, IUpdatable
{
private const int MaxAgentCount = 20;
[InjectService]
private GameConfig _config;
[InjectService]
private IEntityCache _entityCache;
[InjectService]
private IProductCatalog _productCatalog;
[InjectService]
private IBuildingSpatialQuery _spatialQuery;
[InjectService]
private IAgentCommonLogic _agentCommonLogic;
public StorageBuildingRequestProductAgentSystem(IServiceLocator serviceLocator) : base(serviceLocator)
{
}
public void Update()
{
var agentDefinition = (AgentDefinition)_config.Agents.GenericAgent.Asset;
var productTypeCount = _productCatalog.ProductTypeCount;
if (productTypeCount <= 0) return;
foreach (var storageBuilding in _entityCache.GetStorageRequestBuildings())
{
ref var storage = ref storageBuilding.GetStorageRW();
if (storage.FreeSpace() <= 0) continue;
ref var storagePolicy = ref storageBuilding.GetProductStoragePolicyRW();
var startProductHandle = storagePolicy.NextProductToFetch;
for (var i = 0; i < productTypeCount; i++)
{
var productHandle = (startProductHandle + i) % productTypeCount;
var requestedAmount = storagePolicy.GetRequestedAmount(productHandle);
if (requestedAmount <= 0) continue;
if (storage.CountIncludingReservations(productHandle) >= requestedAmount) continue;
if (!_spatialQuery.FindStorageForRequest(storageBuilding.Id, storageBuilding.Rect, storageBuilding.Definition.Range, productHandle, out var fetchBuilding))
continue;
if (!_agentCommonLogic.TryFetchProduct(agentDefinition, storageBuilding, fetchBuilding, productHandle, 1, MaxAgentCount, AgentJob.StorageRequestHauler)) continue;
storagePolicy.NextProductToFetch = (productHandle + 1) % productTypeCount;
break;
}
}
}
}
}

View File

@@ -0,0 +1,9 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(EarlyAgentsSystemGroup))]
[UpdateAfter(typeof(AgentsSpawnTickSystem))]
public class AgentSpawnSystemsGroup : GameSystemGroup
{
}
}

View File

@@ -0,0 +1,10 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
[RequiresWorldReadyForUpdate]
[GameSystemGroup(typeof(DefaultGameSystemGroup))]
[UpdateAfter(typeof(EconomySystemGroup))]
public class AgentsSystemGroup : GameSystemGroup
{
}
}

View File

@@ -0,0 +1,18 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(AgentSpawnSystemsGroup))]
public class DayOnlyAgentSpawnSystemsGroup : GameSystemGroup
{
[InjectService]
private World _world;
public override void Update()
{
if (!_world.AgentsState.SpawnTickNow) return;
if (_world.TimeState.DayNightCycleStep != DayNightCycleStep.Day) return;
base.Update();
}
}
}

View File

@@ -0,0 +1,9 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(AgentsSystemGroup))]
[UpdateAfter(typeof(EarlyAgentsSystemGroup))]
public class DefaultAgentsSystemGroup : GameSystemGroup
{
}
}

View File

@@ -0,0 +1,8 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(AgentsSystemGroup))]
public class EarlyAgentsSystemGroup : GameSystemGroup
{
}
}

View File

@@ -0,0 +1,9 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
[GameSystemGroup(typeof(AgentsSystemGroup))]
[UpdateAfter(typeof(DefaultAgentsSystemGroup))]
public class LateAgentsSystemGroup : GameSystemGroup
{
}
}

View File

@@ -0,0 +1,9 @@
namespace DanieleMarotta.RiversongCodeShowcase
{
public class WorldAgentsState
{
public float SpawnTickTimer { get; set; }
public bool SpawnTickNow { get; set; }
}
}