riversong code showcase

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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