riversong code showcase
This commit is contained in:
65
Source/Riversong/Game/World/Agents/Agent.cs
Normal file
65
Source/Riversong/Game/World/Agents/Agent.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Source/Riversong/Game/World/Agents/AgentAnimation.cs
Normal file
14
Source/Riversong/Game/World/Agents/AgentAnimation.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
// ReSharper disable InconsistentNaming
|
||||
public enum AgentAnimation
|
||||
{
|
||||
None,
|
||||
|
||||
ChopTree,
|
||||
|
||||
Farming,
|
||||
|
||||
FireProjectile
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
68
Source/Riversong/Game/World/Agents/AgentAnimationSystem.cs
Normal file
68
Source/Riversong/Game/World/Agents/AgentAnimationSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
179
Source/Riversong/Game/World/Agents/AgentCommonLogicSystem.cs
Normal file
179
Source/Riversong/Game/World/Agents/AgentCommonLogicSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Source/Riversong/Game/World/Agents/AgentDefinition.cs
Normal file
19
Source/Riversong/Game/World/Agents/AgentDefinition.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
17
Source/Riversong/Game/World/Agents/AgentJob.cs
Normal file
17
Source/Riversong/Game/World/Agents/AgentJob.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public enum AgentJob
|
||||
{
|
||||
None,
|
||||
|
||||
Harvester,
|
||||
|
||||
Hunter,
|
||||
|
||||
Farmer,
|
||||
|
||||
StorageRequestHauler,
|
||||
|
||||
Count
|
||||
}
|
||||
}
|
||||
34
Source/Riversong/Game/World/Agents/AgentJobState.cs
Normal file
34
Source/Riversong/Game/World/Agents/AgentJobState.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Source/Riversong/Game/World/Agents/AgentLifecycleState.cs
Normal file
11
Source/Riversong/Game/World/Agents/AgentLifecycleState.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public enum AgentLifecycleState
|
||||
{
|
||||
New,
|
||||
|
||||
Live,
|
||||
|
||||
DeSpawning
|
||||
}
|
||||
}
|
||||
217
Source/Riversong/Game/World/Agents/AgentManagerSystem.cs
Normal file
217
Source/Riversong/Game/World/Agents/AgentManagerSystem.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
Source/Riversong/Game/World/Agents/AgentSourceState.cs
Normal file
15
Source/Riversong/Game/World/Agents/AgentSourceState.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Source/Riversong/Game/World/Agents/AgentStateMachineStep.cs
Normal file
20
Source/Riversong/Game/World/Agents/AgentStateMachineStep.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
// ReSharper disable InconsistentNaming
|
||||
public enum AgentStateMachineStep
|
||||
{
|
||||
None,
|
||||
|
||||
#region Generic
|
||||
|
||||
ReturningHome,
|
||||
|
||||
#endregion
|
||||
|
||||
#region Hunter
|
||||
|
||||
Hunter_SeekingPrey
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
57
Source/Riversong/Game/World/Agents/AgentVisualization.cs
Normal file
57
Source/Riversong/Game/World/Agents/AgentVisualization.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
52
Source/Riversong/Game/World/Agents/AgentsCleanUpSystem.cs
Normal file
52
Source/Riversong/Game/World/Agents/AgentsCleanUpSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Source/Riversong/Game/World/Agents/AgentsSpawnTickSystem.cs
Normal file
28
Source/Riversong/Game/World/Agents/AgentsSpawnTickSystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
114
Source/Riversong/Game/World/Agents/ConstructionAgentSystem.cs
Normal file
114
Source/Riversong/Game/World/Agents/ConstructionAgentSystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
24
Source/Riversong/Game/World/Agents/Critters/CritterHerd.cs
Normal file
24
Source/Riversong/Game/World/Agents/Critters/CritterHerd.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Source/Riversong/Game/World/Agents/Critters/CritterState.cs
Normal file
11
Source/Riversong/Game/World/Agents/Critters/CritterState.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct CritterState
|
||||
{
|
||||
public bool IsCritter;
|
||||
|
||||
public int CritterDefinitionId;
|
||||
|
||||
public int LockId;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class WorldCritterHerdsState
|
||||
{
|
||||
public List<int2> EligibleCenters { get; } = new();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
40
Source/Riversong/Game/World/Agents/FarmingAgentSystem.cs
Normal file
40
Source/Riversong/Game/World/Agents/FarmingAgentSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Source/Riversong/Game/World/Agents/FetchType.cs
Normal file
3
Source/Riversong/Game/World/Agents/FetchType.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
}
|
||||
47
Source/Riversong/Game/World/Agents/HarvesterAgentSystem.cs
Normal file
47
Source/Riversong/Game/World/Agents/HarvesterAgentSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
47
Source/Riversong/Game/World/Agents/HouseAgentSystem.cs
Normal file
47
Source/Riversong/Game/World/Agents/HouseAgentSystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
78
Source/Riversong/Game/World/Agents/HunterBehaviorSystem.cs
Normal file
78
Source/Riversong/Game/World/Agents/HunterBehaviorSystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
45
Source/Riversong/Game/World/Agents/HunterSpawnSystem.cs
Normal file
45
Source/Riversong/Game/World/Agents/HunterSpawnSystem.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
35
Source/Riversong/Game/World/Agents/IAgentCommonLogic.cs
Normal file
35
Source/Riversong/Game/World/Agents/IAgentCommonLogic.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
15
Source/Riversong/Game/World/Agents/IAgentFactory.cs
Normal file
15
Source/Riversong/Game/World/Agents/IAgentFactory.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
9
Source/Riversong/Game/World/Agents/IAgentSourceEntity.cs
Normal file
9
Source/Riversong/Game/World/Agents/IAgentSourceEntity.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IAgentSourceEntity : IEntity
|
||||
{
|
||||
float SpawnCooldown { get; }
|
||||
|
||||
ref AgentSourceState GetAgentSourceStateRW();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IAgentVisualizationCollection
|
||||
{
|
||||
bool TryGetVisualization(int id, out AgentVisualization visualization);
|
||||
}
|
||||
}
|
||||
183
Source/Riversong/Game/World/Agents/Intents/AgentIntent.cs
Normal file
183
Source/Riversong/Game/World/Agents/Intents/AgentIntent.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IIntentLogicExecutor
|
||||
{
|
||||
ref IntentQueue CancelRemainingIntents(Agent agent);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public enum IntentExecutionResult
|
||||
{
|
||||
InProgress,
|
||||
|
||||
Failure,
|
||||
|
||||
Success
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public enum IntentExecutionState
|
||||
{
|
||||
WaitingIntent,
|
||||
|
||||
ExecutingIntent,
|
||||
|
||||
EmptyQueue
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
156
Source/Riversong/Game/World/Agents/Intents/IntentQueue.cs
Normal file
156
Source/Riversong/Game/World/Agents/Intents/IntentQueue.cs
Normal 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());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct PathQueryFailedSignal
|
||||
{
|
||||
public PathTraversalRules TraversalRules;
|
||||
|
||||
public PathQueryFailedSignal(PathTraversalRules traversalRules)
|
||||
{
|
||||
TraversalRules = traversalRules;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class WaitForeverIntentExecutionLogic : IntentExecutionLogic
|
||||
{
|
||||
public override IntentExecutionResult Execute(Agent agent, in AgentIntent intent)
|
||||
{
|
||||
return IntentExecutionResult.InProgress;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
36
Source/Riversong/Game/World/Agents/ProviderAgentSystem.cs
Normal file
36
Source/Riversong/Game/World/Agents/ProviderAgentSystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[GameSystemGroup(typeof(EarlyAgentsSystemGroup))]
|
||||
[UpdateAfter(typeof(AgentsSpawnTickSystem))]
|
||||
public class AgentSpawnSystemsGroup : GameSystemGroup
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[RequiresWorldReadyForUpdate]
|
||||
[GameSystemGroup(typeof(DefaultGameSystemGroup))]
|
||||
[UpdateAfter(typeof(EconomySystemGroup))]
|
||||
public class AgentsSystemGroup : GameSystemGroup
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[GameSystemGroup(typeof(AgentsSystemGroup))]
|
||||
[UpdateAfter(typeof(EarlyAgentsSystemGroup))]
|
||||
public class DefaultAgentsSystemGroup : GameSystemGroup
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[GameSystemGroup(typeof(AgentsSystemGroup))]
|
||||
public class EarlyAgentsSystemGroup : GameSystemGroup
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[GameSystemGroup(typeof(AgentsSystemGroup))]
|
||||
[UpdateAfter(typeof(DefaultAgentsSystemGroup))]
|
||||
public class LateAgentsSystemGroup : GameSystemGroup
|
||||
{
|
||||
}
|
||||
}
|
||||
9
Source/Riversong/Game/World/Agents/WorldAgentsState.cs
Normal file
9
Source/Riversong/Game/World/Agents/WorldAgentsState.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class WorldAgentsState
|
||||
{
|
||||
public float SpawnTickTimer { get; set; }
|
||||
|
||||
public bool SpawnTickNow { get; set; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user