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; }
|
||||
}
|
||||
}
|
||||
37
Source/Riversong/Game/World/BlockMap/BlockMap.cs
Normal file
37
Source/Riversong/Game/World/BlockMap/BlockMap.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using Unity.Collections;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class BlockMap : NativeGrid<BlockReason>
|
||||
{
|
||||
public BlockMap(int2 size) : base(size, Allocator.Persistent)
|
||||
{
|
||||
}
|
||||
|
||||
public void AddReason(int2 tile, BlockReason reason)
|
||||
{
|
||||
SetValue(tile, GetValue(tile) | reason);
|
||||
}
|
||||
|
||||
public void AddReason(int2 min, int2 max, BlockReason reason)
|
||||
{
|
||||
foreach (var tile in TileRange.From(min, max)) AddReason(tile, reason);
|
||||
}
|
||||
|
||||
public void RemoveReason(int2 tile, BlockReason reason)
|
||||
{
|
||||
SetValue(tile, GetValue(tile) & ~reason);
|
||||
}
|
||||
|
||||
public void RemoveReason(int2 min, int2 max, BlockReason reason)
|
||||
{
|
||||
foreach (var tile in TileRange.From(min, max)) RemoveReason(tile, reason);
|
||||
}
|
||||
|
||||
public bool IsBlocked(int2 tile, BlockReason reason = BlockReason.Any)
|
||||
{
|
||||
return (GetValue(tile) & reason) != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
79
Source/Riversong/Game/World/BlockMap/BlockMapUpdateSystem.cs
Normal file
79
Source/Riversong/Game/World/BlockMap/BlockMapUpdateSystem.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class BlockMapUpdateSystem : GameSystem, IInitializable, IDisposable
|
||||
{
|
||||
[InjectService]
|
||||
private ISignalBus _signalBus;
|
||||
|
||||
[InjectService]
|
||||
private World _world;
|
||||
|
||||
[InjectService]
|
||||
private GameConfig _config;
|
||||
|
||||
public BlockMapUpdateSystem(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public UniTask InitializeAsync()
|
||||
{
|
||||
_signalBus.Subscribe<BuildingPlacementAnimationStartedSignal>(OnBuildingPlacementAnimationStarted);
|
||||
_signalBus.Subscribe<BuildingCreatedSignal>(OnBuildingCreated);
|
||||
_signalBus.Subscribe<BuildingDeleteAnimationCompletedSignal>(OnBuildingDeleteAnimationCompleted);
|
||||
_signalBus.Subscribe<ConstructionSiteDeletedSignal>(OnConstructionSiteDeleted);
|
||||
_signalBus.Subscribe<RoadTileUpdatedSignal>(OnRoadTileUpdated);
|
||||
_signalBus.Subscribe<RawResourcesRemovedSignal>(OnRawResourcesRemoved);
|
||||
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_signalBus.Unsubscribe<BuildingPlacementAnimationStartedSignal>(OnBuildingPlacementAnimationStarted);
|
||||
_signalBus.Unsubscribe<BuildingCreatedSignal>(OnBuildingCreated);
|
||||
_signalBus.Unsubscribe<BuildingDeleteAnimationCompletedSignal>(OnBuildingDeleteAnimationCompleted);
|
||||
_signalBus.Unsubscribe<ConstructionSiteDeletedSignal>(OnConstructionSiteDeleted);
|
||||
_signalBus.Unsubscribe<RoadTileUpdatedSignal>(OnRoadTileUpdated);
|
||||
_signalBus.Unsubscribe<RawResourcesRemovedSignal>(OnRawResourcesRemoved);
|
||||
}
|
||||
|
||||
private void OnBuildingPlacementAnimationStarted(BuildingPlacementAnimationStartedSignal signal)
|
||||
{
|
||||
_world.BlockMap.AddReason(signal.Rect.Min, signal.Rect.Max, BlockReason.Building);
|
||||
}
|
||||
|
||||
private void OnBuildingCreated(BuildingCreatedSignal signal)
|
||||
{
|
||||
var rect = signal.Building.Rect;
|
||||
_world.BlockMap.AddReason(rect.Min, rect.Max, BlockReason.Building);
|
||||
}
|
||||
|
||||
private void OnBuildingDeleteAnimationCompleted(BuildingDeleteAnimationCompletedSignal signal)
|
||||
{
|
||||
var rect = signal.Building.Rect;
|
||||
_world.BlockMap.RemoveReason(rect.Min, rect.Max, BlockReason.Building);
|
||||
}
|
||||
|
||||
private void OnConstructionSiteDeleted(ConstructionSiteDeletedSignal signal)
|
||||
{
|
||||
var rect = signal.ConstructionSite.Rect;
|
||||
_world.BlockMap.RemoveReason(rect.Min, rect.Max, BlockReason.Building);
|
||||
}
|
||||
|
||||
private void OnRoadTileUpdated(RoadTileUpdatedSignal signal)
|
||||
{
|
||||
if (_world.RoadNetwork.IsRoadTile(signal.Tile))
|
||||
_world.BlockMap.AddReason(signal.Tile, BlockReason.Road);
|
||||
else
|
||||
_world.BlockMap.RemoveReason(signal.Tile, BlockReason.Road);
|
||||
}
|
||||
|
||||
private void OnRawResourcesRemoved(RawResourcesRemovedSignal signal)
|
||||
{
|
||||
foreach (var resourceNode in signal.ResourceNodes) _world.BlockMap.RemoveReason(resourceNode.Tile, BlockReason.RawResource);
|
||||
}
|
||||
}
|
||||
}
|
||||
32
Source/Riversong/Game/World/BlockMap/BlockReason.cs
Normal file
32
Source/Riversong/Game/World/BlockMap/BlockReason.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[Flags]
|
||||
public enum BlockReason
|
||||
{
|
||||
None,
|
||||
|
||||
InvalidElevation = 1 << 0,
|
||||
|
||||
RawResource = 1 << 1,
|
||||
|
||||
ProductStack = 1 << 2,
|
||||
|
||||
Building = 1 << 3,
|
||||
|
||||
Road = 1 << 4,
|
||||
|
||||
CannotBuild = InvalidElevation | Building | Road,
|
||||
|
||||
CannotBuildRoad = InvalidElevation | Building,
|
||||
|
||||
ClearGrassMask = ProductStack | Building | Road,
|
||||
|
||||
CannotMakeCamp = RawResource | ProductStack | CannotBuild,
|
||||
|
||||
BlocksAgents = InvalidElevation | Building,
|
||||
|
||||
Any = ~None
|
||||
}
|
||||
}
|
||||
61
Source/Riversong/Game/World/Buildings/Building.cs
Normal file
61
Source/Riversong/Game/World/Buildings/Building.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class Building : Entity, IBuildingShape, IProductStorageEntity, IAgentSourceEntity
|
||||
{
|
||||
private ProductStorage _storage;
|
||||
|
||||
private ProductStoragePolicyState _productStoragePolicy;
|
||||
|
||||
private BuildingProductionState _productionState;
|
||||
|
||||
private AgentSourceState _agentsState;
|
||||
|
||||
private PopulationNeedsState _needsState;
|
||||
|
||||
private BuildingSleepState _sleepState;
|
||||
|
||||
public BuildingDefinition Definition { get; set; }
|
||||
|
||||
public TileRect Rect { get; set; }
|
||||
|
||||
public Directions Orientation { get; set; }
|
||||
|
||||
public int WeekCreated { get; set; }
|
||||
|
||||
public int TierIndex { get; set; }
|
||||
|
||||
public int PopulationCapacity { get; set; }
|
||||
|
||||
public float SpawnCooldown => Definition.SpawnCooldown;
|
||||
|
||||
public ref ProductStorage GetStorageRW()
|
||||
{
|
||||
return ref _storage;
|
||||
}
|
||||
|
||||
public ref ProductStoragePolicyState GetProductStoragePolicyRW()
|
||||
{
|
||||
return ref _productStoragePolicy;
|
||||
}
|
||||
|
||||
public ref BuildingProductionState GetProductionStateRW()
|
||||
{
|
||||
return ref _productionState;
|
||||
}
|
||||
|
||||
public ref AgentSourceState GetAgentSourceStateRW()
|
||||
{
|
||||
return ref _agentsState;
|
||||
}
|
||||
|
||||
public ref PopulationNeedsState GetNeedsStateRW()
|
||||
{
|
||||
return ref _needsState;
|
||||
}
|
||||
|
||||
public ref BuildingSleepState GetSleepStateRW()
|
||||
{
|
||||
return ref _sleepState;
|
||||
}
|
||||
}
|
||||
}
|
||||
55
Source/Riversong/Game/World/Buildings/BuildingAoESystem.cs
Normal file
55
Source/Riversong/Game/World/Buildings/BuildingAoESystem.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using UnityEngine.Pool;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[UpdateAfter(typeof(EditingStateGameSystem))]
|
||||
public class BuildingAoESystem : GameSystem, IUpdatable
|
||||
{
|
||||
[InjectService]
|
||||
private EditingState _editingState;
|
||||
|
||||
[InjectService]
|
||||
private UIState _uiState;
|
||||
|
||||
[InjectService]
|
||||
private IBuildingSpatialQuery _buildingSpatialQuery;
|
||||
|
||||
[InjectService]
|
||||
private IAoERenderingService _aoeRenderingService;
|
||||
|
||||
public BuildingAoESystem(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
var tool = _editingState.ActiveTool;
|
||||
if (tool is BuildTool buildTool && buildTool.Preview.IsVisible) AddAoE(buildTool.Building, buildTool.BuildingRect);
|
||||
|
||||
var selectedBuilding = _uiState.SelectedBuilding;
|
||||
if (selectedBuilding != null) AddAoE(selectedBuilding.Definition, selectedBuilding.Rect);
|
||||
}
|
||||
|
||||
private void AddAoE(BuildingDefinition definition, TileRect rect)
|
||||
{
|
||||
var range = definition.Range;
|
||||
if (range > 0) _aoeRenderingService.Add(definition.AoELayer, rect.Inflate(range));
|
||||
|
||||
if (definition.IsHouse)
|
||||
{
|
||||
using var providersScope = ListPool<Building>.Get(out var providers);
|
||||
_buildingSpatialQuery.FindProvidersForHouse(rect, providers);
|
||||
|
||||
foreach (var provider in providers) _aoeRenderingService.Add(provider.Definition.AoELayer, provider.Rect.Inflate(provider.Definition.Range));
|
||||
}
|
||||
|
||||
if (definition.IsStorage)
|
||||
{
|
||||
using var providersScope = ListPool<Building>.Get(out var providers);
|
||||
_buildingSpatialQuery.FindProvidersForStorage(rect, providers);
|
||||
|
||||
foreach (var provider in providers) _aoeRenderingService.Add(provider.Definition.AoELayer, provider.Rect.Inflate(provider.Definition.Range));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct BuildingCreatedSignal
|
||||
{
|
||||
public Building Building;
|
||||
|
||||
public BuildingCreatedSignal(Building building)
|
||||
{
|
||||
Building = building;
|
||||
}
|
||||
}
|
||||
}
|
||||
115
Source/Riversong/Game/World/Buildings/BuildingDefinition.cs
Normal file
115
Source/Riversong/Game/World/Buildings/BuildingDefinition.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AddressableAssets;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[CreateAssetMenu(fileName = "BuildingDefinition", menuName = "Riversong Code Showcase/Building Definition")]
|
||||
public class BuildingDefinition : GameDataAsset, IComparable<BuildingDefinition>
|
||||
{
|
||||
public int UIOrder;
|
||||
|
||||
[PreviewField]
|
||||
public Sprite Icon;
|
||||
|
||||
public string BuildingName = "Building";
|
||||
|
||||
[TextArea(5, 10)]
|
||||
public string BuildingDescription = "Building";
|
||||
|
||||
public AssetReferenceGameObject Visualization;
|
||||
|
||||
public List<ProductAmountAuthoring> BuildingMaterials;
|
||||
|
||||
public int Width = 1;
|
||||
|
||||
public int Height = 1;
|
||||
|
||||
public int Range;
|
||||
|
||||
[LabelText("AoE Layer")]
|
||||
public int AoELayer;
|
||||
|
||||
public bool CanBeDeleted = true;
|
||||
|
||||
[TitleGroup("Workers")]
|
||||
public int WorkerCount;
|
||||
|
||||
public float SpawnCooldown;
|
||||
|
||||
[TitleGroup("Storage")]
|
||||
public int StorageCapacity = 1000;
|
||||
|
||||
[LabelText("Show Storage Tooltip?")]
|
||||
public bool ShowStorageTooltip;
|
||||
|
||||
[TitleGroup("Harvester")]
|
||||
[LabelText("Resource")]
|
||||
public ResourceNodeDefinition HarvestedResource;
|
||||
|
||||
[TitleGroup("Hunter")]
|
||||
public CritterDefinition TargetCritter;
|
||||
|
||||
[TitleGroup("Farm")]
|
||||
public bool IsFarm;
|
||||
|
||||
[LabelText("Product")]
|
||||
public ProductDefinition FarmProduct;
|
||||
|
||||
[HorizontalGroup("Farm/Dropped Amount")]
|
||||
[LabelText("Amt. Min")]
|
||||
public int MinDroppedAmount = 1;
|
||||
|
||||
[HorizontalGroup("Farm/Dropped Amount")]
|
||||
[LabelText("Amt. Max")]
|
||||
public int MaxDroppedAmount = 1;
|
||||
|
||||
[TitleGroup("Producer")]
|
||||
public RecipeDefinition Recipe;
|
||||
|
||||
[LabelText("Delivers Output?")]
|
||||
public bool DeliversOutput = true;
|
||||
|
||||
[TitleGroup("Provider")]
|
||||
[LabelText("Products")]
|
||||
public List<ProductAmountAuthoring> ProvidedProducts;
|
||||
|
||||
[LabelText("Fetches Products?")]
|
||||
public bool FetchesProducts = true;
|
||||
|
||||
[TitleGroup("House")]
|
||||
[LabelText("Is House?")]
|
||||
public bool IsHouse;
|
||||
|
||||
[ShowIf("IsHouse")]
|
||||
public List<HouseTierAuthoring> HouseTiers;
|
||||
|
||||
[TitleGroup("Storage")]
|
||||
[LabelText("Is Storage?")]
|
||||
public bool IsStorage;
|
||||
|
||||
[TitleGroup("Placement Rules")]
|
||||
[LabelText("Near Water?")]
|
||||
public bool NearWater;
|
||||
|
||||
[LabelText("Requires Fertile Tile?")]
|
||||
public bool RequiresFertileTile;
|
||||
|
||||
public int CompareTo(BuildingDefinition other)
|
||||
{
|
||||
return UIOrder.CompareTo(other.UIOrder);
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class HouseTierAuthoring
|
||||
{
|
||||
public int Capacity = 1;
|
||||
|
||||
public List<ProductAmountAuthoring> UpgradeMaterials;
|
||||
|
||||
public List<PopulationNeedAuthoring> Needs;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct BuildingDeletedSignal
|
||||
{
|
||||
public Building Building;
|
||||
|
||||
public DeleteBuildingOptions Options;
|
||||
|
||||
public BuildingDeletedSignal(Building building, DeleteBuildingOptions options)
|
||||
{
|
||||
Building = building;
|
||||
Options = options;
|
||||
}
|
||||
}
|
||||
}
|
||||
323
Source/Riversong/Game/World/Buildings/BuildingManagerSystem.cs
Normal file
323
Source/Riversong/Game/World/Buildings/BuildingManagerSystem.cs
Normal file
@@ -0,0 +1,323 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine.Pool;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[Service(typeof(IBuildingFactory))]
|
||||
[Service(typeof(IDeleteBuildingService))]
|
||||
[Service(typeof(IBuildingSpatialQuery))]
|
||||
public class BuildingManagerSystem : GameSystem, IInitializable, IDisposable, IBuildingFactory, IDeleteBuildingService, IBuildingSpatialQuery
|
||||
{
|
||||
[InjectService]
|
||||
private ISignalBus _signalBus;
|
||||
|
||||
[InjectService]
|
||||
private IEntityCollection _entityCollection;
|
||||
|
||||
[InjectService]
|
||||
private IProductStorageManager _productStorageManager;
|
||||
|
||||
[InjectService]
|
||||
private IProductCatalog _productCatalog;
|
||||
|
||||
[InjectService]
|
||||
private IProductStackFactory _productStackFactory;
|
||||
|
||||
[InjectService]
|
||||
private IEntityCache _entityCache;
|
||||
|
||||
[InjectService]
|
||||
private IAgentFactory _agentFactory;
|
||||
|
||||
[InjectService]
|
||||
private IFailedPathCache _failedPathCache;
|
||||
|
||||
[InjectService]
|
||||
private World _world;
|
||||
|
||||
[InjectService]
|
||||
private GameConfig _config;
|
||||
|
||||
private Func<Building, int, bool> _fetchValidator;
|
||||
|
||||
private Func<Building, int, bool> _deliveryValidator;
|
||||
|
||||
private Func<Building, int, bool> _storageRequestValidator;
|
||||
|
||||
private Func<Building, int, int> _sourceRangeFunc;
|
||||
|
||||
private Func<Building, int, int> _candidateRangeFunc;
|
||||
|
||||
private Func<Building, int, int> _infiniteRangeFunc;
|
||||
|
||||
public BuildingManagerSystem(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
_fetchValidator = FetchValidator;
|
||||
_deliveryValidator = DeliveryValidator;
|
||||
_storageRequestValidator = StorageRequestValidator;
|
||||
_sourceRangeFunc = SourceRange;
|
||||
_candidateRangeFunc = CandidateRange;
|
||||
_infiniteRangeFunc = InfiniteRange;
|
||||
}
|
||||
|
||||
public UniTask InitializeAsync()
|
||||
{
|
||||
_signalBus.Subscribe<ConstructionCompletedSignal>(OnConstructionCompleted);
|
||||
_signalBus.Subscribe<DoDeleteToolSignal>(OnDoDeleteTool);
|
||||
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_signalBus.Unsubscribe<ConstructionCompletedSignal>(OnConstructionCompleted);
|
||||
_signalBus.Unsubscribe<DoDeleteToolSignal>(OnDoDeleteTool);
|
||||
}
|
||||
|
||||
private void OnConstructionCompleted(ConstructionCompletedSignal signal)
|
||||
{
|
||||
var constructionSite = signal.ConstructionSite;
|
||||
Create(constructionSite.Building, constructionSite.Rect, constructionSite.Orientation);
|
||||
}
|
||||
|
||||
public Building Create(BuildingDefinition definition, TileRect rect, Directions orientation)
|
||||
{
|
||||
var building = _entityCollection.Create<Building>();
|
||||
|
||||
building.Definition = definition;
|
||||
building.Rect = rect;
|
||||
building.Orientation = orientation;
|
||||
building.WeekCreated = _world.TimeState.TotalWeeks;
|
||||
InitializeStorage(building);
|
||||
InitializeProductionState(building);
|
||||
InitializeAgentSource(building);
|
||||
InitializeHouse(building);
|
||||
InitializeSleepState(building);
|
||||
|
||||
_entityCollection.Add(building);
|
||||
|
||||
_signalBus.Raise(new BuildingCreatedSignal(building));
|
||||
|
||||
return building;
|
||||
}
|
||||
|
||||
private void InitializeStorage(Building building)
|
||||
{
|
||||
var capacity = building.Definition.StorageCapacity;
|
||||
if (capacity <= 0) return;
|
||||
|
||||
_productStorageManager.InitializeProductStorage(building, capacity);
|
||||
}
|
||||
|
||||
private void InitializeProductionState(Building building)
|
||||
{
|
||||
var recipeDefinition = building.Definition.Recipe;
|
||||
if (!recipeDefinition) return;
|
||||
|
||||
ref var productionState = ref building.GetProductionStateRW();
|
||||
|
||||
productionState.State = ProducerState.NotWorking;
|
||||
|
||||
productionState.Recipe.Inputs.Length = recipeDefinition.Inputs.Count;
|
||||
for (var i = 0; i < recipeDefinition.Inputs.Count; i++)
|
||||
{
|
||||
var input = recipeDefinition.Inputs[i];
|
||||
var productHandle = _productCatalog.GetHandle(input.Product);
|
||||
productionState.Recipe.Inputs[i] = (productHandle, input.Amount);
|
||||
}
|
||||
|
||||
productionState.Recipe.OutputProductHandle = _productCatalog.GetHandle(recipeDefinition.Output.Product);
|
||||
productionState.Recipe.OutputAmount = recipeDefinition.Output.Amount;
|
||||
|
||||
productionState.Recipe.ProductionTime = recipeDefinition.ProductionTime;
|
||||
}
|
||||
|
||||
private void InitializeAgentSource(Building building)
|
||||
{
|
||||
_agentFactory.InitializeAgentSource(building);
|
||||
}
|
||||
|
||||
private void InitializeHouse(Building building)
|
||||
{
|
||||
if (!building.Definition.IsHouse) return;
|
||||
|
||||
var tiers = building.Definition.HouseTiers;
|
||||
building.PopulationCapacity = tiers.Count > 0 ? tiers[0].Capacity : 0;
|
||||
|
||||
InitializeHouseNeeds(building);
|
||||
}
|
||||
|
||||
private void InitializeHouseNeeds(Building building)
|
||||
{
|
||||
ref var needsState = ref building.GetNeedsStateRW();
|
||||
|
||||
var tiers = building.Definition.HouseTiers;
|
||||
for (var i = 0; i < tiers.Count; i++)
|
||||
foreach (var need in tiers[i].Needs)
|
||||
needsState.Needs.Add(
|
||||
new PopulationNeed
|
||||
{
|
||||
TierIndex = (byte)i,
|
||||
Type = need.Type,
|
||||
ProductHandle = _productCatalog.GetHandle(need.Product),
|
||||
HappinessScore = (ushort)need.HappinessScore,
|
||||
ConsumptionRate = (byte)need.ConsumptionRate,
|
||||
FetchThreshold = (byte)need.FetchThreshold,
|
||||
YieldOnFetch = (ushort)need.YieldOnFetch
|
||||
});
|
||||
|
||||
needsState.UpgradeState = tiers.Count > 1 ? TierUpgradeState.NotReady : TierUpgradeState.MaxedOut;
|
||||
|
||||
needsState.Happiness = _config.Population.InitialHouseHappiness;
|
||||
}
|
||||
|
||||
private void InitializeSleepState(Building building)
|
||||
{
|
||||
ref var sleepState = ref building.GetSleepStateRW();
|
||||
sleepState.HasHomelessWorkers = false;
|
||||
sleepState.EfficiencyModifier = _config.Economy.RestedWorkersEfficiencyModifier;
|
||||
}
|
||||
|
||||
private void OnDoDeleteTool(DoDeleteToolSignal signal)
|
||||
{
|
||||
using var toDeleteScope = HashSetPool<int>.Get(out var toDelete);
|
||||
|
||||
foreach (var tile in TileRange.From(signal.Rect))
|
||||
foreach (Building building in _entityCollection.GetInternalEntityList(typeof(Building)))
|
||||
{
|
||||
if (!building.Definition.CanBeDeleted || !building.Rect.Contains(tile)) continue;
|
||||
|
||||
toDelete.Add(building.Id);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (var id in toDelete)
|
||||
{
|
||||
var building = _entityCollection.Get<Building>(id);
|
||||
Delete(building, DeleteBuildingOptions.WithFeedback);
|
||||
}
|
||||
}
|
||||
|
||||
public void Delete(Building building, DeleteBuildingOptions options)
|
||||
{
|
||||
_entityCollection.Remove(building.Id);
|
||||
|
||||
_productStackFactory.CreateProductStacksFrom(building);
|
||||
|
||||
_signalBus.Raise(new BuildingDeletedSignal(building, options));
|
||||
}
|
||||
|
||||
public bool FindStorageForFetch(int sourceId, TileRect sourceRect, int productHandle, out Building building)
|
||||
{
|
||||
return FindReachableBuilding(sourceId, sourceRect, 0, productHandle, _entityCache.GetStorageBuildings(), _fetchValidator, _infiniteRangeFunc, out building);
|
||||
}
|
||||
|
||||
public bool FindStorageForFetch(int sourceId, TileRect sourceRect, int sourceRange, int productHandle, out Building building)
|
||||
{
|
||||
return FindReachableBuilding(sourceId, sourceRect, sourceRange, productHandle, _entityCache.GetStorageBuildings(), _fetchValidator, _sourceRangeFunc, out building);
|
||||
}
|
||||
|
||||
public bool FindStorageForDelivery(int sourceId, TileRect sourceRect, int productHandle, out Building building)
|
||||
{
|
||||
return FindReachableBuilding(sourceId, sourceRect, 0, productHandle, _entityCache.GetStorageBuildings(), _deliveryValidator, _infiniteRangeFunc, out building);
|
||||
}
|
||||
|
||||
public bool FindStorageForRequest(int sourceId, TileRect sourceRect, int sourceRange, int productHandle, out Building building)
|
||||
{
|
||||
return FindReachableBuilding(
|
||||
sourceId,
|
||||
sourceRect,
|
||||
sourceRange,
|
||||
productHandle,
|
||||
_entityCache.GetStorageRequestBuildings(),
|
||||
_storageRequestValidator,
|
||||
_infiniteRangeFunc,
|
||||
out building);
|
||||
}
|
||||
|
||||
public bool FindProviderForFetch(int sourceId, TileRect sourceRect, int productHandle, out Building building)
|
||||
{
|
||||
return FindReachableBuilding(sourceId, sourceRect, 0, productHandle, _entityCache.GetProviders(), _fetchValidator, _candidateRangeFunc, out building);
|
||||
}
|
||||
|
||||
public void FindProvidersForHouse(TileRect sourceRect, List<Building> providers)
|
||||
{
|
||||
foreach (var provider in _entityCache.GetProviders())
|
||||
if (TileMath.StepCount(provider.Rect, sourceRect) <= provider.Definition.Range)
|
||||
providers.Add(provider);
|
||||
}
|
||||
|
||||
public void FindProvidersForStorage(TileRect sourceRect, List<Building> providers)
|
||||
{
|
||||
foreach (var provider in _entityCache.GetProviders())
|
||||
if (provider.Definition.FetchesProducts && TileMath.StepCount(provider.Rect, sourceRect) <= provider.Definition.Range)
|
||||
providers.Add(provider);
|
||||
}
|
||||
|
||||
private bool FindReachableBuilding(int sourceId,
|
||||
TileRect sourceRect,
|
||||
int sourceRange,
|
||||
int productHandle,
|
||||
List<Building> candidates,
|
||||
Func<Building, int, bool> validator,
|
||||
Func<Building, int, int> rangeFunc,
|
||||
out Building building)
|
||||
{
|
||||
building = null;
|
||||
var best = int.MaxValue;
|
||||
|
||||
foreach (var candidate in candidates)
|
||||
{
|
||||
if (_failedPathCache.IsKnownFailedPath(sourceId, candidate.Id)) continue;
|
||||
|
||||
var stepCount = TileMath.StepCount(sourceRect, candidate.Rect);
|
||||
if (stepCount >= best || stepCount > rangeFunc.Invoke(candidate, sourceRange)) continue;
|
||||
|
||||
if (!validator.Invoke(candidate, productHandle)) continue;
|
||||
|
||||
best = stepCount;
|
||||
building = candidate;
|
||||
}
|
||||
|
||||
return best < int.MaxValue;
|
||||
}
|
||||
|
||||
private bool FetchValidator(Building candidate, int productHandle)
|
||||
{
|
||||
ref var storage = ref candidate.GetStorageRW();
|
||||
return storage.AvailableNow(productHandle) > 0;
|
||||
}
|
||||
|
||||
private bool DeliveryValidator(Building candidate, int productHandle)
|
||||
{
|
||||
ref var storage = ref candidate.GetStorageRW();
|
||||
ref var storagePolicy = ref candidate.GetProductStoragePolicyRW();
|
||||
return storage.FreeSpace() > 0 && storagePolicy.IsTakingAllowed(productHandle);
|
||||
}
|
||||
|
||||
private bool StorageRequestValidator(Building candidate, int productHandle)
|
||||
{
|
||||
ref var storage = ref candidate.GetStorageRW();
|
||||
ref var storagePolicy = ref candidate.GetProductStoragePolicyRW();
|
||||
return storage.CountIncludingReservations(productHandle) > storagePolicy.GetRequestedAmount(productHandle) && storagePolicy.CanFulfillRequests(productHandle);
|
||||
}
|
||||
|
||||
private int SourceRange(Building candidate, int sourceRange)
|
||||
{
|
||||
return sourceRange;
|
||||
}
|
||||
|
||||
private int CandidateRange(Building candidate, int sourceRange)
|
||||
{
|
||||
return candidate.Definition.Range;
|
||||
}
|
||||
|
||||
private int InfiniteRange(Building candidate, int sourceRange)
|
||||
{
|
||||
return int.MaxValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
110
Source/Riversong/Game/World/Buildings/BuildingSelectionSystem.cs
Normal file
110
Source/Riversong/Game/World/Buildings/BuildingSelectionSystem.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[UpdateAfter(typeof(EditingStateGameSystem))]
|
||||
public class BuildingSelectionSystem : GameSystem, IInitializable, IUpdatable
|
||||
{
|
||||
[InjectService]
|
||||
private GameConfig _gameConfig;
|
||||
|
||||
[InjectService]
|
||||
private IPointerService _pointerService;
|
||||
|
||||
[InjectService]
|
||||
private UIState _uiState;
|
||||
|
||||
[InjectService]
|
||||
private ITileSpace _tileSpace;
|
||||
|
||||
[InjectService]
|
||||
private IEntityCollection _entityCollection;
|
||||
|
||||
[InjectService]
|
||||
private ICancelAction _cancelAction;
|
||||
|
||||
[InjectService]
|
||||
private EditingState _editingState;
|
||||
|
||||
[InjectService]
|
||||
private ISignalBus _signalBus;
|
||||
|
||||
[InjectService]
|
||||
private IBuildingVisualizationCollection _buildingVisualizationCollection;
|
||||
|
||||
[InjectService]
|
||||
private World _world;
|
||||
|
||||
[InjectService]
|
||||
private MaterialReplacementCache _materialReplacementCache;
|
||||
|
||||
public BuildingSelectionSystem(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public UniTask InitializeAsync()
|
||||
{
|
||||
_cancelAction.AddHandler(
|
||||
(int)CancelActions.CancelSelection,
|
||||
_ =>
|
||||
{
|
||||
if (_uiState.SelectedBuilding == null) return false;
|
||||
SetSelectedBuilding(null);
|
||||
return true;
|
||||
});
|
||||
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
if (_uiState.SelectedBuilding != null && !_entityCollection.Exists(_uiState.SelectedBuilding.Id))
|
||||
{
|
||||
SetSelectedBuilding(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_editingState.ActiveTool != null)
|
||||
{
|
||||
SetSelectedBuilding(null);
|
||||
return;
|
||||
}
|
||||
|
||||
var buildingUnderPointer = GetBuildingUnderPointer();
|
||||
if (_pointerService.TryConsumeLeftClick()) SetSelectedBuilding(buildingUnderPointer);
|
||||
|
||||
HighlightBuilding(buildingUnderPointer);
|
||||
HighlightBuilding(_uiState.SelectedBuilding);
|
||||
}
|
||||
|
||||
private Building GetBuildingUnderPointer()
|
||||
{
|
||||
if (_pointerService.IsPointerOverUI) return null;
|
||||
if (!_pointerService.TryGetPositionOnTerrain(out var position)) return null;
|
||||
|
||||
var tile = _tileSpace.WorldToTile(position);
|
||||
ref var entityIdMapValue = ref _world.EntityIdMap.GetValueRW(tile);
|
||||
if (entityIdMapValue.BuildingId == Entity.InvalidId) return null;
|
||||
|
||||
return _entityCollection.TryGet<Building>(entityIdMapValue.BuildingId, out var building) ? building : null;
|
||||
}
|
||||
|
||||
private void HighlightBuilding(Building building)
|
||||
{
|
||||
if (building == null) return;
|
||||
if (!_buildingVisualizationCollection.TryGetVisualization(building.Id, out var visualization)) return;
|
||||
|
||||
_materialReplacementCache.ReplaceMaterials(visualization.gameObject, _gameConfig.UI.HighlightedGameObjectsMaterial);
|
||||
}
|
||||
|
||||
private void SetSelectedBuilding(Building selectedBuilding)
|
||||
{
|
||||
if (_uiState.SelectedBuilding == selectedBuilding) return;
|
||||
|
||||
var oldSelection = _uiState.SelectedBuilding;
|
||||
_uiState.SelectedBuilding = selectedBuilding;
|
||||
|
||||
_signalBus.Raise(new SelectedBuildingChangedSignal(oldSelection, _uiState.SelectedBuilding));
|
||||
}
|
||||
}
|
||||
}
|
||||
13
Source/Riversong/Game/World/Buildings/BuildingSleepState.cs
Normal file
13
Source/Riversong/Game/World/Buildings/BuildingSleepState.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct BuildingSleepState
|
||||
{
|
||||
public int RestedWorkerCount;
|
||||
|
||||
public int AllocatedWorkerCount;
|
||||
|
||||
public bool HasHomelessWorkers;
|
||||
|
||||
public float EfficiencyModifier;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct ConstructionCompletedSignal
|
||||
{
|
||||
public ConstructionSite ConstructionSite;
|
||||
|
||||
public ConstructionCompletedSignal(ConstructionSite constructionSite)
|
||||
{
|
||||
ConstructionSite = constructionSite;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class ConstructionSite : Entity, IBuildingShape, IProductStorageEntity, IAgentSourceEntity
|
||||
{
|
||||
private ProductStorage _storage;
|
||||
|
||||
private ProductStoragePolicyState _productStoragePolicy;
|
||||
|
||||
private AgentSourceState _agentSourceState;
|
||||
|
||||
public BuildingDefinition Building { get; set; }
|
||||
|
||||
public TileRect Rect { get; set; }
|
||||
|
||||
public Directions Orientation { get; set; }
|
||||
|
||||
public float SpawnCooldown => 0;
|
||||
|
||||
public ref ProductStorage GetStorageRW()
|
||||
{
|
||||
return ref _storage;
|
||||
}
|
||||
|
||||
public ref ProductStoragePolicyState GetProductStoragePolicyRW()
|
||||
{
|
||||
return ref _productStoragePolicy;
|
||||
}
|
||||
|
||||
public ref AgentSourceState GetAgentSourceStateRW()
|
||||
{
|
||||
return ref _agentSourceState;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct ConstructionSiteDeletedSignal
|
||||
{
|
||||
public ConstructionSite ConstructionSite;
|
||||
|
||||
public ConstructionSiteDeletedSignal(ConstructionSite constructionSite)
|
||||
{
|
||||
ConstructionSite = constructionSite;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Pool;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class ConstructionSiteManager : GameSystem, IInitializable, IDisposable, IUpdatable
|
||||
{
|
||||
private const int SpatialLookupCellSize = 5;
|
||||
|
||||
private const int StorageCapacity = 100;
|
||||
|
||||
[InjectService]
|
||||
private GameConfig _config;
|
||||
|
||||
[InjectService]
|
||||
private IScene _scene;
|
||||
|
||||
[InjectService]
|
||||
private ISignalBus _signalBus;
|
||||
|
||||
[InjectService]
|
||||
private IEntityCollection _entityCollection;
|
||||
|
||||
[InjectService]
|
||||
private ITileSpace _tileSpace;
|
||||
|
||||
[InjectService]
|
||||
private IProductStorageManager _productStorageManager;
|
||||
|
||||
[InjectService]
|
||||
private IProductCatalog _productCatalog;
|
||||
|
||||
[InjectService]
|
||||
private IProductStackFactory _productStackFactory;
|
||||
|
||||
[InjectService]
|
||||
private IAgentFactory _agentFactory;
|
||||
|
||||
private Dictionary<int, GameObject> _visualizations = new();
|
||||
|
||||
private SpatialLookup<(ConstructionSite, GameObject)> _spatialLookup = new(SpatialLookupCellSize);
|
||||
|
||||
private MaterialPropertyBlock _materialPropertyBlock;
|
||||
|
||||
public ConstructionSiteManager(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public UniTask InitializeAsync()
|
||||
{
|
||||
_signalBus.Subscribe<BuildingPlacementAnimationCompletedSignal>(OnBuildingPlaced);
|
||||
_signalBus.Subscribe<CollectDeletedGameObjectsSignal>(OnCollectDeletedGameObjects);
|
||||
_signalBus.Subscribe<DoDeleteToolSignal>(OnDoDeleteTool);
|
||||
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_signalBus.Unsubscribe<BuildingPlacementAnimationCompletedSignal>(OnBuildingPlaced);
|
||||
_signalBus.Unsubscribe<CollectDeletedGameObjectsSignal>(OnCollectDeletedGameObjects);
|
||||
_signalBus.Unsubscribe<DoDeleteToolSignal>(OnDoDeleteTool);
|
||||
}
|
||||
|
||||
private void OnBuildingPlaced(BuildingPlacementAnimationCompletedSignal signal)
|
||||
{
|
||||
var constructionSite = _entityCollection.Create<ConstructionSite>();
|
||||
|
||||
constructionSite.Building = signal.Building;
|
||||
constructionSite.Rect = signal.Rect;
|
||||
constructionSite.Orientation = signal.Orientation;
|
||||
_productStorageManager.InitializeProductStorage(constructionSite, StorageCapacity);
|
||||
_agentFactory.InitializeAgentSource(constructionSite);
|
||||
|
||||
_entityCollection.Add(constructionSite);
|
||||
|
||||
_ = CreateVisualizationAsync(constructionSite);
|
||||
}
|
||||
|
||||
private async UniTask CreateVisualizationAsync(ConstructionSite constructionSite)
|
||||
{
|
||||
var position = _tileSpace.GetRectWorldCenter(constructionSite.Rect);
|
||||
var rotation = constructionSite.Orientation.ToQuaternion();
|
||||
var folder = _scene.SceneFolders.Buildings;
|
||||
|
||||
var visualization = await constructionSite.Building.Visualization.InstantiateAsync(position, rotation, folder);
|
||||
ReplaceMaterials(visualization);
|
||||
|
||||
_visualizations.Add(constructionSite.Id, visualization);
|
||||
_spatialLookup.Add((constructionSite, visualization), constructionSite.Rect, constructionSite.Id);
|
||||
}
|
||||
|
||||
private void ReplaceMaterials(GameObject gameObject)
|
||||
{
|
||||
var material = _config.Buildings.ConstructionSiteMaterial;
|
||||
|
||||
using var renderersScope = ListPool<Renderer>.Get(out var renderers);
|
||||
gameObject.GetComponentsInChildren(renderers);
|
||||
|
||||
_materialPropertyBlock ??= new MaterialPropertyBlock();
|
||||
|
||||
foreach (var renderer in renderers) MaterialReplacementCache.ReplaceRendererMaterials(renderer, material, _materialPropertyBlock);
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
using var toCompleteScope = ListPool<ConstructionSite>.Get(out var toComplete);
|
||||
|
||||
foreach (ConstructionSite constructionSite in _entityCollection.GetInternalEntityList(typeof(ConstructionSite)))
|
||||
if (_visualizations.ContainsKey(constructionSite.Id) && AllBuildingMaterialsFetched(constructionSite))
|
||||
toComplete.Add(constructionSite);
|
||||
|
||||
foreach (var constructionSite in toComplete)
|
||||
{
|
||||
_entityCollection.Remove(constructionSite.Id);
|
||||
|
||||
_visualizations.Remove(constructionSite.Id, out var visualization);
|
||||
_spatialLookup.Remove(constructionSite.Rect, constructionSite.Id);
|
||||
|
||||
constructionSite.Building.Visualization.ReleaseInstance(visualization);
|
||||
|
||||
_signalBus.Raise(new ConstructionCompletedSignal(constructionSite));
|
||||
}
|
||||
}
|
||||
|
||||
private bool AllBuildingMaterialsFetched(ConstructionSite constructionSite)
|
||||
{
|
||||
ref var storage = ref constructionSite.GetStorageRW();
|
||||
|
||||
foreach (var (product, amount) in constructionSite.Building.BuildingMaterials)
|
||||
{
|
||||
var handle = _productCatalog.GetHandle(product);
|
||||
if (storage.AvailableNow(handle) < amount) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void OnCollectDeletedGameObjects(CollectDeletedGameObjectsSignal signal)
|
||||
{
|
||||
if ((signal.Filter & DeletedGameObjectsFilter.Buildings) == 0) return;
|
||||
|
||||
using var lookupResultScope = ListPool<(ConstructionSite, GameObject)>.Get(out var lookupResult);
|
||||
_spatialLookup.Find(signal.Rect, lookupResult);
|
||||
|
||||
foreach (var (_, visualization) in lookupResult) signal.GameObjects.Add(visualization);
|
||||
}
|
||||
|
||||
private void OnDoDeleteTool(DoDeleteToolSignal signal)
|
||||
{
|
||||
using var lookupResultScope = ListPool<(ConstructionSite, GameObject)>.Get(out var lookupResult);
|
||||
_spatialLookup.Find(signal.Rect, lookupResult);
|
||||
|
||||
foreach (var (constructionSite, visualization) in lookupResult)
|
||||
{
|
||||
_productStackFactory.CreateProductStacksFrom(constructionSite);
|
||||
|
||||
_entityCollection.Remove(constructionSite.Id);
|
||||
_visualizations.Remove(constructionSite.Id);
|
||||
_spatialLookup.Remove(constructionSite.Rect, constructionSite.Id);
|
||||
|
||||
constructionSite.Building.Visualization.ReleaseInstance(visualization);
|
||||
|
||||
_signalBus.Raise(new ConstructionSiteDeletedSignal(constructionSite));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public enum DeleteBuildingOptions
|
||||
{
|
||||
WithFeedback,
|
||||
|
||||
Silent
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IBuildingFactory
|
||||
{
|
||||
Building Create(BuildingDefinition definition, TileRect rect, Directions orientation);
|
||||
}
|
||||
}
|
||||
7
Source/Riversong/Game/World/Buildings/IBuildingShape.cs
Normal file
7
Source/Riversong/Game/World/Buildings/IBuildingShape.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IBuildingShape
|
||||
{
|
||||
TileRect Rect { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IBuildingSpatialQuery
|
||||
{
|
||||
bool FindStorageForFetch(int sourceId, TileRect sourceRect, int productHandle, out Building building);
|
||||
|
||||
bool FindStorageForFetch(int sourceId, TileRect sourceRect, int sourceRange, int productHandle, out Building building);
|
||||
|
||||
bool FindStorageForDelivery(int sourceId, TileRect sourceRect, int productHandle, out Building building);
|
||||
|
||||
bool FindStorageForRequest(int sourceId, TileRect sourceRect, int sourceRange, int productHandle, out Building building);
|
||||
|
||||
bool FindProviderForFetch(int sourceId, TileRect sourceRect, int productHandle, out Building building);
|
||||
|
||||
void FindProvidersForHouse(TileRect sourceRect, List<Building> providers);
|
||||
|
||||
void FindProvidersForStorage(TileRect sourceRect, List<Building> providers);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public interface IDeleteBuildingService
|
||||
{
|
||||
void Delete(Building building, DeleteBuildingOptions options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct SelectedBuildingChangedSignal
|
||||
{
|
||||
public Building OldSelection;
|
||||
|
||||
public Building NewSelection;
|
||||
|
||||
public SelectedBuildingChangedSignal(Building oldSelection, Building newSelection)
|
||||
{
|
||||
OldSelection = oldSelection;
|
||||
NewSelection = newSelection;
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Source/Riversong/Game/World/Buildings/TentRemovalSystem.cs
Normal file
44
Source/Riversong/Game/World/Buildings/TentRemovalSystem.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using Cysharp.Threading.Tasks;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class TentRemovalSystem : GameSystem, IInitializable, IDisposable
|
||||
{
|
||||
[InjectService]
|
||||
private ISignalBus _signalBus;
|
||||
|
||||
[InjectService]
|
||||
private IDeleteBuildingService _deleteBuildingService;
|
||||
|
||||
[InjectService]
|
||||
private IEntityCache _entityCache;
|
||||
|
||||
public TentRemovalSystem(IServiceLocator serviceLocator) : base(serviceLocator)
|
||||
{
|
||||
}
|
||||
|
||||
public UniTask InitializeAsync()
|
||||
{
|
||||
_signalBus.Subscribe<DayStartedSignal>(OnDayStarted);
|
||||
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_signalBus.Unsubscribe<DayStartedSignal>(OnDayStarted);
|
||||
}
|
||||
|
||||
private void OnDayStarted(DayStartedSignal signal)
|
||||
{
|
||||
var tents = _entityCache.GetTentBuildings();
|
||||
|
||||
for (var i = tents.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var tent = tents[i];
|
||||
_deleteBuildingService.Delete(tent, DeleteBuildingOptions.Silent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
using UnityEngine.VFX;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public class BuildingDeleteAnimation : MonoBehaviour
|
||||
{
|
||||
private static readonly int IntensityPropertyID = Shader.PropertyToID("Intensity");
|
||||
|
||||
private static readonly int SizePropertyID = Shader.PropertyToID("Size");
|
||||
|
||||
[SerializeField]
|
||||
[ReadOnly]
|
||||
private float _sinkingHeight;
|
||||
|
||||
[SerializeField]
|
||||
private float _sinkingSpeed = 1;
|
||||
|
||||
[SerializeField]
|
||||
private AnimationCurve _sinkingSpeedCurve = AnimationCurve.Constant(0, 1, 1);
|
||||
|
||||
[SerializeField]
|
||||
private float _shakeFrequency = 10;
|
||||
|
||||
[SerializeField]
|
||||
private float _shakeAmplitude = 0.1f;
|
||||
|
||||
[SerializeField]
|
||||
private VisualEffect _vfxPrefab;
|
||||
|
||||
[SerializeField]
|
||||
private int _vfxCount = 1;
|
||||
|
||||
[SerializeField]
|
||||
private float _vfxInterval = 1;
|
||||
|
||||
[SerializeField]
|
||||
private float _vfxIntensityDecay = 0.5f;
|
||||
|
||||
private void ComputeSinkingHeight()
|
||||
{
|
||||
var bounds = default(Bounds);
|
||||
|
||||
var renderers = GetComponentsInChildren<Renderer>();
|
||||
foreach (var renderer in renderers) bounds.Encapsulate(renderer.bounds);
|
||||
|
||||
_sinkingHeight = bounds.size.y;
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_sinkingHeight == 0) ComputeSinkingHeight();
|
||||
}
|
||||
|
||||
public async UniTask PlayAsync(Building building)
|
||||
{
|
||||
var initialPosition = transform.position;
|
||||
var animatedPosition = transform.position;
|
||||
var angularFrequency = 2 * Mathf.PI * _shakeFrequency;
|
||||
var shakeWeights = new Vector3(Random.Range(-1, 1), 0, Random.Range(-1, 1));
|
||||
|
||||
for (var i = 0; i < _vfxCount; i++)
|
||||
{
|
||||
var delay = i * _vfxInterval;
|
||||
var intensity = Mathf.Pow(_vfxIntensityDecay, i);
|
||||
_ = PlayVfxAsync(building, initialPosition, delay, intensity);
|
||||
}
|
||||
|
||||
var t = 0f;
|
||||
while (animatedPosition.y > initialPosition.y - _sinkingHeight)
|
||||
{
|
||||
var dt = Time.deltaTime;
|
||||
|
||||
t += dt;
|
||||
|
||||
var sinkingSpeed = _sinkingSpeed * _sinkingSpeedCurve.Evaluate(t);
|
||||
animatedPosition += Vector3.down * (sinkingSpeed * dt);
|
||||
|
||||
var shake = Mathf.Sin(t * angularFrequency) * _shakeAmplitude * _sinkingSpeed;
|
||||
transform.position = animatedPosition + shake * shakeWeights;
|
||||
|
||||
await UniTask.NextFrame();
|
||||
}
|
||||
}
|
||||
|
||||
private async UniTask PlayVfxAsync(Building building, Vector3 position, float delay, float intensity)
|
||||
{
|
||||
if (delay > 0) await UniTask.WaitForSeconds(delay);
|
||||
|
||||
var vfx = Instantiate(_vfxPrefab, position, Quaternion.identity);
|
||||
vfx.SetFloat(IntensityPropertyID, intensity);
|
||||
vfx.SetVector2(SizePropertyID, new Vector2(building.Rect.Width, building.Rect.Height));
|
||||
}
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
ComputeSinkingHeight();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct BuildingDeleteAnimationCompletedSignal
|
||||
{
|
||||
public Building Building;
|
||||
|
||||
public BuildingDeleteAnimationCompletedSignal(Building building)
|
||||
{
|
||||
Building = building;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
using System.Collections.Generic;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Animations;
|
||||
using UnityEngine.Playables;
|
||||
using UnityEngine.VFX;
|
||||
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
[RequireComponent(typeof(Animator))]
|
||||
public class BuildingPlacementAnimation : MonoBehaviour, IAnimationClipSource
|
||||
{
|
||||
[SerializeField]
|
||||
private AnimationClip _animationClip;
|
||||
|
||||
[SerializeField]
|
||||
private float _playbackSpeed = 1;
|
||||
|
||||
[SerializeField]
|
||||
private Vector3 _positionLerpWeights = new(1.5f, 1, 1.5f);
|
||||
|
||||
[SerializeField]
|
||||
private float _rotationLerpWeight = 1.5f;
|
||||
|
||||
[SerializeField]
|
||||
private VisualEffect _impactVfxPrefab;
|
||||
|
||||
[SerializeField]
|
||||
private float _impactIntensityDecay = 0.75f;
|
||||
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
private float _animationProgress;
|
||||
|
||||
[SerializeField]
|
||||
[HideInInspector]
|
||||
private float _bounce;
|
||||
|
||||
private PlayableGraph _playableGraph;
|
||||
|
||||
private AnimationClipPlayable _playableClip;
|
||||
|
||||
private BuildingDefinition _building;
|
||||
|
||||
private Vector3 _startPosition;
|
||||
|
||||
private Vector3 _finalPosition;
|
||||
|
||||
private Quaternion _finalRotation;
|
||||
|
||||
public bool IsPlaying { get; private set; }
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_playableGraph = PlayableGraph.Create();
|
||||
_playableGraph.SetTimeUpdateMode(DirectorUpdateMode.Manual);
|
||||
|
||||
_playableClip = AnimationClipPlayable.Create(_playableGraph, _animationClip);
|
||||
_playableClip.SetSpeed(_playbackSpeed);
|
||||
|
||||
var animator = GetComponent<Animator>();
|
||||
var output = AnimationPlayableOutput.Create(_playableGraph, nameof(BuildingPlacementAnimation), animator);
|
||||
output.SetSourcePlayable(_playableClip);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
_playableGraph.Destroy();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!IsPlaying) return;
|
||||
|
||||
_playableGraph.Evaluate(Time.unscaledDeltaTime);
|
||||
|
||||
var position = transform.position;
|
||||
position.x = Mathf.Lerp(_startPosition.x, _finalPosition.x, _positionLerpWeights.x * _animationProgress);
|
||||
position.y = Mathf.Lerp(_startPosition.y, _finalPosition.y, _positionLerpWeights.y * _bounce);
|
||||
position.z = Mathf.Lerp(_startPosition.z, _finalPosition.z, _positionLerpWeights.z * _animationProgress);
|
||||
transform.position = position;
|
||||
|
||||
transform.rotation = Quaternion.Lerp(transform.rotation, _finalRotation, _rotationLerpWeight * _animationProgress);
|
||||
}
|
||||
|
||||
public async UniTask PlayAsync(BuildingDefinition building, Vector3 startPosition, Vector3 finalPosition, Quaternion finalRotation)
|
||||
{
|
||||
IsPlaying = true;
|
||||
|
||||
_building = building;
|
||||
_startPosition = startPosition;
|
||||
_finalPosition = finalPosition;
|
||||
_finalRotation = finalRotation;
|
||||
|
||||
_playableClip.SetTime(0);
|
||||
_playableClip.SetTime(0);
|
||||
_playableGraph.Play();
|
||||
|
||||
_animationProgress = 0;
|
||||
while (_animationProgress < 1) await UniTask.NextFrame();
|
||||
|
||||
_playableGraph.Stop();
|
||||
|
||||
transform.SetPositionAndRotation(finalPosition, finalRotation);
|
||||
|
||||
IsPlaying = false;
|
||||
}
|
||||
|
||||
public void GetAnimationClips(List<AnimationClip> results)
|
||||
{
|
||||
if (_animationClip) results.Add(_animationClip);
|
||||
}
|
||||
|
||||
private void OnImpact(int impact)
|
||||
{
|
||||
var impactVfx = Instantiate(_impactVfxPrefab, transform.position, transform.rotation);
|
||||
|
||||
var intensity = Mathf.Pow(_impactIntensityDecay, impact);
|
||||
var size = new Vector2(_building.Width, _building.Height);
|
||||
DustVfxProperties.Setup(impactVfx, intensity, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace DanieleMarotta.RiversongCodeShowcase
|
||||
{
|
||||
public struct BuildingPlacementAnimationCompletedSignal
|
||||
{
|
||||
public BuildingDefinition Building;
|
||||
|
||||
public TileRect Rect;
|
||||
|
||||
public Directions Orientation;
|
||||
|
||||
public BuildingPlacementAnimationCompletedSignal(BuildingDefinition building, TileRect rect, Directions orientation)
|
||||
{
|
||||
Building = building;
|
||||
Rect = rect;
|
||||
Orientation = orientation;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user