Initial import
This commit is contained in:
197
Assets/Scripts/Systems/BuildingSystems.cs
Normal file
197
Assets/Scripts/Systems/BuildingSystems.cs
Normal file
@@ -0,0 +1,197 @@
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
using Unity.Transforms;
|
||||
using Unity.NetCode;
|
||||
using EE2Clone.Components;
|
||||
using EE2Clone.Core;
|
||||
|
||||
namespace EE2Clone.Systems
|
||||
{
|
||||
/// <summary>
|
||||
/// Server-side: processes PlaceBuildingRpc — validates and spawns building entities.
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
public partial struct PlaceBuildingCommandSystem : ISystem
|
||||
{
|
||||
[BurstCompile]
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
var ecb = new EntityCommandBuffer(Allocator.Temp);
|
||||
|
||||
foreach (var (cmd, source, entity) in
|
||||
SystemAPI.Query<RefRO<PlaceBuildingRpc>, RefRO<ReceiveRpcCommandRequest>>()
|
||||
.WithEntityAccess())
|
||||
{
|
||||
// TODO: Validate resources, territory, collision
|
||||
// TODO: Lookup building prefab entity from BuildingType
|
||||
// TODO: Instantiate the prefab and place it
|
||||
|
||||
// For now just log
|
||||
UnityEngine.Debug.Log($"[Server] PlaceBuilding request: {cmd.ValueRO.Type} at {cmd.ValueRO.Position}");
|
||||
|
||||
ecb.DestroyEntity(entity);
|
||||
}
|
||||
|
||||
ecb.Playback(state.EntityManager);
|
||||
ecb.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server-side: processes BuildRepairCommandRpc — assigns citizens to build/repair buildings.
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
public partial struct BuildRepairCommandSystem : ISystem
|
||||
{
|
||||
[BurstCompile]
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
var ecb = new EntityCommandBuffer(Allocator.Temp);
|
||||
|
||||
foreach (var (cmd, source, entity) in
|
||||
SystemAPI.Query<RefRO<BuildRepairCommandRpc>, RefRO<ReceiveRpcCommandRequest>>()
|
||||
.WithEntityAccess())
|
||||
{
|
||||
var citizen = cmd.ValueRO.CitizenEntity;
|
||||
var building = cmd.ValueRO.BuildingEntity;
|
||||
|
||||
if (state.EntityManager.Exists(citizen) && state.EntityManager.Exists(building) &&
|
||||
state.EntityManager.HasComponent<CitizenStateComponent>(citizen))
|
||||
{
|
||||
state.EntityManager.SetComponentData(citizen, new BuildTarget { Target = building });
|
||||
state.EntityManager.SetComponentData(citizen, new CitizenStateComponent { Value = CitizenState.MovingToBuild });
|
||||
|
||||
var buildingPos = state.EntityManager.GetComponentData<LocalTransform>(building).Position;
|
||||
state.EntityManager.SetComponentData(citizen, new MoveTarget
|
||||
{
|
||||
Position = buildingPos,
|
||||
IsActive = true
|
||||
});
|
||||
}
|
||||
|
||||
ecb.DestroyEntity(entity);
|
||||
}
|
||||
|
||||
ecb.Playback(state.EntityManager);
|
||||
ecb.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server-side: ticks construction progress for buildings with assigned citizens.
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
[UpdateAfter(typeof(BuildRepairCommandSystem))]
|
||||
public partial struct ConstructionSystem : ISystem
|
||||
{
|
||||
private float _timer;
|
||||
|
||||
public void OnCreate(ref SystemState state)
|
||||
{
|
||||
_timer = 0f;
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
float dt = SystemAPI.Time.DeltaTime;
|
||||
_timer += dt;
|
||||
if (_timer < GameConstants.ConstructionTickInterval) return;
|
||||
_timer -= GameConstants.ConstructionTickInterval;
|
||||
|
||||
var ecb = new EntityCommandBuffer(Allocator.Temp);
|
||||
|
||||
// Count builders per building
|
||||
// Simple approach: iterate citizens in Building state
|
||||
foreach (var (citizenState, buildTarget, transform, moveTarget) in
|
||||
SystemAPI.Query<RefRW<CitizenStateComponent>, RefRO<BuildTarget>,
|
||||
RefRO<LocalTransform>, RefRO<MoveTarget>>()
|
||||
.WithAll<CitizenTag>())
|
||||
{
|
||||
if (citizenState.ValueRO.Value == CitizenState.MovingToBuild && !moveTarget.ValueRO.IsActive)
|
||||
{
|
||||
citizenState.ValueRW.Value = CitizenState.Building;
|
||||
}
|
||||
|
||||
if (citizenState.ValueRO.Value != CitizenState.Building) continue;
|
||||
|
||||
var buildingEntity = buildTarget.ValueRO.Target;
|
||||
if (buildingEntity == Entity.Null || !state.EntityManager.Exists(buildingEntity)) continue;
|
||||
|
||||
if (!state.EntityManager.HasComponent<ConstructionProgress>(buildingEntity)) continue;
|
||||
|
||||
var progress = state.EntityManager.GetComponentData<ConstructionProgress>(buildingEntity);
|
||||
float increment = GameConstants.ConstructionTickInterval / progress.BuildTime;
|
||||
progress.Progress = math.min(progress.Progress + increment, 1f);
|
||||
state.EntityManager.SetComponentData(buildingEntity, progress);
|
||||
|
||||
// Complete construction
|
||||
if (progress.Progress >= 1f)
|
||||
{
|
||||
ecb.RemoveComponent<UnderConstructionTag>(buildingEntity);
|
||||
ecb.RemoveComponent<ConstructionProgress>(buildingEntity);
|
||||
|
||||
// Set health to max
|
||||
var health = state.EntityManager.GetComponentData<Health>(buildingEntity);
|
||||
health.Current = health.Max;
|
||||
ecb.SetComponent(buildingEntity, health);
|
||||
|
||||
// Citizen becomes idle
|
||||
citizenState.ValueRW.Value = CitizenState.Idle;
|
||||
}
|
||||
}
|
||||
|
||||
ecb.Playback(state.EntityManager);
|
||||
ecb.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server-side: processes production queues on buildings and spawns units.
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
public partial struct ProductionSystem : ISystem
|
||||
{
|
||||
[BurstCompile]
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
float dt = SystemAPI.Time.DeltaTime;
|
||||
var ecb = new EntityCommandBuffer(Allocator.Temp);
|
||||
|
||||
foreach (var (productionQueue, rallyPoint, owner, transform, entity) in
|
||||
SystemAPI.Query<DynamicBuffer<ProductionQueueElement>, RefRO<RallyPoint>,
|
||||
RefRO<OwnerPlayer>, RefRO<LocalTransform>>()
|
||||
.WithAll<BuildingTag>()
|
||||
.WithNone<UnderConstructionTag>()
|
||||
.WithEntityAccess())
|
||||
{
|
||||
if (productionQueue.Length == 0) continue;
|
||||
|
||||
var front = productionQueue[0];
|
||||
front.TimeRemaining -= dt;
|
||||
|
||||
if (front.TimeRemaining <= 0)
|
||||
{
|
||||
// TODO: Instantiate unit prefab based on front.UnitDataId
|
||||
// For now just log
|
||||
UnityEngine.Debug.Log($"[Server] Unit produced: DataId={front.UnitDataId} at {rallyPoint.ValueRO.Position}");
|
||||
|
||||
productionQueue.RemoveAt(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
productionQueue[0] = front;
|
||||
}
|
||||
}
|
||||
|
||||
ecb.Playback(state.EntityManager);
|
||||
ecb.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Systems/BuildingSystems.cs.meta
Normal file
2
Assets/Scripts/Systems/BuildingSystems.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e4a7a8d853e94f941b762acafa1de53d
|
||||
207
Assets/Scripts/Systems/CombatSystems.cs
Normal file
207
Assets/Scripts/Systems/CombatSystems.cs
Normal file
@@ -0,0 +1,207 @@
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
using Unity.Transforms;
|
||||
using Unity.NetCode;
|
||||
using EE2Clone.Components;
|
||||
using EE2Clone.Core;
|
||||
|
||||
namespace EE2Clone.Systems
|
||||
{
|
||||
/// <summary>
|
||||
/// Server-side: processes AttackCommandRpc — sets combat target on units.
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
public partial struct AttackCommandSystem : ISystem
|
||||
{
|
||||
[BurstCompile]
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
var ecb = new EntityCommandBuffer(Allocator.Temp);
|
||||
|
||||
foreach (var (cmd, source, entity) in
|
||||
SystemAPI.Query<RefRO<AttackCommandRpc>, RefRO<ReceiveRpcCommandRequest>>()
|
||||
.WithEntityAccess())
|
||||
{
|
||||
var attacker = cmd.ValueRO.AttackerEntity;
|
||||
var target = cmd.ValueRO.TargetEntity;
|
||||
|
||||
if (state.EntityManager.Exists(attacker) && state.EntityManager.Exists(target) &&
|
||||
state.EntityManager.HasComponent<CombatTarget>(attacker))
|
||||
{
|
||||
state.EntityManager.SetComponentData(attacker, new CombatTarget { Target = target });
|
||||
state.EntityManager.SetComponentData(attacker, new UnitStateComponent { Value = UnitState.Attacking });
|
||||
}
|
||||
|
||||
ecb.DestroyEntity(entity);
|
||||
}
|
||||
|
||||
ecb.Playback(state.EntityManager);
|
||||
ecb.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server-side: units with a CombatTarget move toward target, then deal damage when in range.
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
[UpdateAfter(typeof(AttackCommandSystem))]
|
||||
public partial struct CombatSystem : ISystem
|
||||
{
|
||||
[BurstCompile]
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
float dt = SystemAPI.Time.DeltaTime;
|
||||
|
||||
foreach (var (transform, attack, combatTarget, speed, unitState, unitClass, entity) in
|
||||
SystemAPI.Query<RefRW<LocalTransform>, RefRW<AttackData>, RefRW<CombatTarget>,
|
||||
RefRO<MovementSpeed>, RefRW<UnitStateComponent>, RefRO<UnitClassComponent>>()
|
||||
.WithAll<UnitTag>()
|
||||
.WithEntityAccess())
|
||||
{
|
||||
// Tick cooldown
|
||||
if (attack.ValueRO.CooldownRemaining > 0)
|
||||
attack.ValueRW.CooldownRemaining -= dt;
|
||||
|
||||
var target = combatTarget.ValueRO.Target;
|
||||
if (target == Entity.Null || !state.EntityManager.Exists(target))
|
||||
{
|
||||
// Clear invalid target
|
||||
if (target != Entity.Null)
|
||||
{
|
||||
combatTarget.ValueRW.Target = Entity.Null;
|
||||
if (unitState.ValueRO.Value == UnitState.Attacking)
|
||||
unitState.ValueRW.Value = UnitState.Idle;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!state.EntityManager.HasComponent<Health>(target))
|
||||
{
|
||||
combatTarget.ValueRW.Target = Entity.Null;
|
||||
unitState.ValueRW.Value = UnitState.Idle;
|
||||
continue;
|
||||
}
|
||||
|
||||
var targetTransform = state.EntityManager.GetComponentData<LocalTransform>(target);
|
||||
float3 toTarget = targetTransform.Position - transform.ValueRO.Position;
|
||||
toTarget.y = 0;
|
||||
float distance = math.length(toTarget);
|
||||
|
||||
if (distance > attack.ValueRO.Range)
|
||||
{
|
||||
// Move toward target
|
||||
float3 moveDir = math.normalize(toTarget);
|
||||
float moveAmount = math.min(speed.ValueRO.Value * dt, distance - attack.ValueRO.Range * 0.9f);
|
||||
transform.ValueRW.Position += moveDir * moveAmount;
|
||||
transform.ValueRW.Rotation = quaternion.LookRotationSafe(moveDir, math.up());
|
||||
}
|
||||
else if (attack.ValueRO.CooldownRemaining <= 0)
|
||||
{
|
||||
// Attack
|
||||
float damage = attack.ValueRO.Damage;
|
||||
|
||||
// Apply counter bonus
|
||||
if (state.EntityManager.HasComponent<UnitClassComponent>(target))
|
||||
{
|
||||
var targetClass = state.EntityManager.GetComponentData<UnitClassComponent>(target).Value;
|
||||
float counterMult = GetCounterMultiplier(unitClass.ValueRO.Value, targetClass);
|
||||
damage *= counterMult;
|
||||
}
|
||||
|
||||
// Apply armor
|
||||
if (state.EntityManager.HasComponent<ArmorData>(target))
|
||||
{
|
||||
float armor = state.EntityManager.GetComponentData<ArmorData>(target).Value;
|
||||
damage = math.max(damage - armor, GameConstants.MinDamage);
|
||||
}
|
||||
|
||||
var targetHealth = state.EntityManager.GetComponentData<Health>(target);
|
||||
targetHealth.Current -= damage;
|
||||
state.EntityManager.SetComponentData(target, targetHealth);
|
||||
|
||||
attack.ValueRW.CooldownRemaining = attack.ValueRO.AttackCooldown;
|
||||
|
||||
// Face target
|
||||
if (distance > 0.01f)
|
||||
transform.ValueRW.Rotation = quaternion.LookRotationSafe(math.normalize(toTarget), math.up());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static float GetCounterMultiplier(UnitClass attacker, UnitClass target)
|
||||
{
|
||||
// Rock-paper-scissors: Infantry > Ranged > Cavalry > Infantry
|
||||
if (attacker == UnitClass.Infantry && target == UnitClass.Ranged) return GameConstants.CounterBonusStrong;
|
||||
if (attacker == UnitClass.Ranged && target == UnitClass.Cavalry) return GameConstants.CounterBonusStrong;
|
||||
if (attacker == UnitClass.Cavalry && target == UnitClass.Infantry) return GameConstants.CounterBonusStrong;
|
||||
|
||||
if (attacker == UnitClass.Infantry && target == UnitClass.Cavalry) return GameConstants.CounterBonusWeak;
|
||||
if (attacker == UnitClass.Ranged && target == UnitClass.Infantry) return GameConstants.CounterBonusWeak;
|
||||
if (attacker == UnitClass.Cavalry && target == UnitClass.Ranged) return GameConstants.CounterBonusWeak;
|
||||
|
||||
// Siege bonus vs buildings (handled via BuildingTag check in caller if needed)
|
||||
return GameConstants.CounterBonusNeutral;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server-side: destroys entities that have 0 or less HP after a brief delay.
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
[UpdateAfter(typeof(CombatSystem))]
|
||||
public partial struct DeathSystem : ISystem
|
||||
{
|
||||
[BurstCompile]
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
var ecb = new EntityCommandBuffer(Allocator.Temp);
|
||||
float dt = SystemAPI.Time.DeltaTime;
|
||||
|
||||
// Tag entities with <=0 HP for destruction
|
||||
foreach (var (health, entity) in
|
||||
SystemAPI.Query<RefRO<Health>>()
|
||||
.WithNone<DeathTimer, DestroyEntityTag>()
|
||||
.WithEntityAccess())
|
||||
{
|
||||
if (health.ValueRO.Current <= 0)
|
||||
{
|
||||
ecb.AddComponent(entity, new DeathTimer { TimeRemaining = 1.0f });
|
||||
|
||||
if (state.EntityManager.HasComponent<UnitStateComponent>(entity))
|
||||
{
|
||||
ecb.SetComponent(entity, new UnitStateComponent { Value = UnitState.Dying });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tick death timers
|
||||
foreach (var (deathTimer, entity) in
|
||||
SystemAPI.Query<RefRW<DeathTimer>>()
|
||||
.WithNone<DestroyEntityTag>()
|
||||
.WithEntityAccess())
|
||||
{
|
||||
deathTimer.ValueRW.TimeRemaining -= dt;
|
||||
if (deathTimer.ValueRO.TimeRemaining <= 0)
|
||||
{
|
||||
ecb.AddComponent<DestroyEntityTag>(entity);
|
||||
}
|
||||
}
|
||||
|
||||
// Destroy tagged entities
|
||||
foreach (var (tag, entity) in
|
||||
SystemAPI.Query<RefRO<DestroyEntityTag>>()
|
||||
.WithEntityAccess())
|
||||
{
|
||||
ecb.DestroyEntity(entity);
|
||||
}
|
||||
|
||||
ecb.Playback(state.EntityManager);
|
||||
ecb.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Systems/CombatSystems.cs.meta
Normal file
2
Assets/Scripts/Systems/CombatSystems.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1163cc3a2970481429c3bd67549beb78
|
||||
172
Assets/Scripts/Systems/GatheringSystems.cs
Normal file
172
Assets/Scripts/Systems/GatheringSystems.cs
Normal file
@@ -0,0 +1,172 @@
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
using Unity.Transforms;
|
||||
using Unity.NetCode;
|
||||
using EE2Clone.Components;
|
||||
using EE2Clone.Core;
|
||||
|
||||
namespace EE2Clone.Systems
|
||||
{
|
||||
/// <summary>
|
||||
/// Server-side: processes GatherCommandRpc — assigns a gather target to citizens.
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
public partial struct GatherCommandSystem : ISystem
|
||||
{
|
||||
[BurstCompile]
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
var ecb = new EntityCommandBuffer(Allocator.Temp);
|
||||
|
||||
foreach (var (cmd, source, entity) in
|
||||
SystemAPI.Query<RefRO<GatherCommandRpc>, RefRO<ReceiveRpcCommandRequest>>()
|
||||
.WithEntityAccess())
|
||||
{
|
||||
var citizen = cmd.ValueRO.CitizenEntity;
|
||||
var node = cmd.ValueRO.ResourceNodeEntity;
|
||||
|
||||
if (state.EntityManager.Exists(citizen) && state.EntityManager.Exists(node) &&
|
||||
state.EntityManager.HasComponent<CitizenStateComponent>(citizen) &&
|
||||
state.EntityManager.HasComponent<ResourceNode>(node))
|
||||
{
|
||||
state.EntityManager.SetComponentData(citizen, new GatherTarget { Target = node });
|
||||
state.EntityManager.SetComponentData(citizen, new CitizenStateComponent { Value = CitizenState.MovingToGather });
|
||||
|
||||
// Set move target to the resource node's position
|
||||
var nodePos = state.EntityManager.GetComponentData<LocalTransform>(node).Position;
|
||||
state.EntityManager.SetComponentData(citizen, new MoveTarget
|
||||
{
|
||||
Position = nodePos,
|
||||
IsActive = true
|
||||
});
|
||||
}
|
||||
|
||||
ecb.DestroyEntity(entity);
|
||||
}
|
||||
|
||||
ecb.Playback(state.EntityManager);
|
||||
ecb.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server-side: citizen gathering state machine.
|
||||
/// Handles: MovingToGather → Gathering → MovingToDropoff → Depositing → repeat.
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
[UpdateAfter(typeof(GatherCommandSystem))]
|
||||
public partial struct GatheringSystem : ISystem
|
||||
{
|
||||
private float _gatherTimer;
|
||||
|
||||
public void OnCreate(ref SystemState state)
|
||||
{
|
||||
_gatherTimer = 0f;
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
float dt = SystemAPI.Time.DeltaTime;
|
||||
_gatherTimer += dt;
|
||||
bool gatherTick = _gatherTimer >= GameConstants.GatherTickInterval;
|
||||
if (gatherTick) _gatherTimer -= GameConstants.GatherTickInterval;
|
||||
|
||||
foreach (var (citizenState, gatherTarget, carried, transform, moveTarget, owner) in
|
||||
SystemAPI.Query<RefRW<CitizenStateComponent>, RefRW<GatherTarget>,
|
||||
RefRW<CarriedResource>, RefRO<LocalTransform>, RefRW<MoveTarget>, RefRO<OwnerPlayer>>()
|
||||
.WithAll<CitizenTag>())
|
||||
{
|
||||
switch (citizenState.ValueRO.Value)
|
||||
{
|
||||
case CitizenState.MovingToGather:
|
||||
// Check if arrived (MoveTarget will be deactivated by movement system)
|
||||
if (!moveTarget.ValueRO.IsActive)
|
||||
{
|
||||
citizenState.ValueRW.Value = CitizenState.Gathering;
|
||||
}
|
||||
break;
|
||||
|
||||
case CitizenState.Gathering:
|
||||
if (!gatherTick) break;
|
||||
|
||||
var targetEntity = gatherTarget.ValueRO.Target;
|
||||
if (targetEntity == Entity.Null || !state.EntityManager.Exists(targetEntity))
|
||||
{
|
||||
citizenState.ValueRW.Value = CitizenState.Idle;
|
||||
break;
|
||||
}
|
||||
|
||||
var node = state.EntityManager.GetComponentData<ResourceNode>(targetEntity);
|
||||
if (node.RemainingAmount <= 0)
|
||||
{
|
||||
citizenState.ValueRW.Value = CitizenState.Idle;
|
||||
gatherTarget.ValueRW.Target = Entity.Null;
|
||||
break;
|
||||
}
|
||||
|
||||
// Gather one unit
|
||||
int gatherAmount = math.min(1, node.RemainingAmount);
|
||||
gatherAmount = math.min(gatherAmount, carried.ValueRO.MaxCarryCapacity - carried.ValueRO.Amount);
|
||||
|
||||
if (gatherAmount > 0)
|
||||
{
|
||||
node.RemainingAmount -= gatherAmount;
|
||||
state.EntityManager.SetComponentData(targetEntity, node);
|
||||
|
||||
carried.ValueRW.Amount += gatherAmount;
|
||||
carried.ValueRW.Type = node.Type;
|
||||
}
|
||||
|
||||
// If carrying capacity full, go to dropoff
|
||||
if (carried.ValueRO.Amount >= carried.ValueRO.MaxCarryCapacity)
|
||||
{
|
||||
citizenState.ValueRW.Value = CitizenState.MovingToDropoff;
|
||||
// TODO: Find nearest dropoff building and set as move target
|
||||
// For now, move back toward origin as placeholder
|
||||
moveTarget.ValueRW = new MoveTarget
|
||||
{
|
||||
Position = float3.zero,
|
||||
IsActive = true
|
||||
};
|
||||
}
|
||||
break;
|
||||
|
||||
case CitizenState.MovingToDropoff:
|
||||
if (!moveTarget.ValueRO.IsActive)
|
||||
{
|
||||
citizenState.ValueRW.Value = CitizenState.Depositing;
|
||||
}
|
||||
break;
|
||||
|
||||
case CitizenState.Depositing:
|
||||
// Deposit resources into player economy
|
||||
// TODO: Find PlayerResourcesComponent for this owner and add resources
|
||||
carried.ValueRW.Amount = 0;
|
||||
|
||||
// Go back to gather if the node still exists
|
||||
if (gatherTarget.ValueRO.Target != Entity.Null &&
|
||||
state.EntityManager.Exists(gatherTarget.ValueRO.Target))
|
||||
{
|
||||
var nodePos = state.EntityManager.GetComponentData<LocalTransform>(gatherTarget.ValueRO.Target).Position;
|
||||
moveTarget.ValueRW = new MoveTarget
|
||||
{
|
||||
Position = nodePos,
|
||||
IsActive = true
|
||||
};
|
||||
citizenState.ValueRW.Value = CitizenState.MovingToGather;
|
||||
}
|
||||
else
|
||||
{
|
||||
citizenState.ValueRW.Value = CitizenState.Idle;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Systems/GatheringSystems.cs.meta
Normal file
2
Assets/Scripts/Systems/GatheringSystems.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35e43773511035f4bb29174417932b52
|
||||
151
Assets/Scripts/Systems/MovementSystems.cs
Normal file
151
Assets/Scripts/Systems/MovementSystems.cs
Normal file
@@ -0,0 +1,151 @@
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Entities;
|
||||
using Unity.Mathematics;
|
||||
using Unity.Transforms;
|
||||
using Unity.NetCode;
|
||||
using EE2Clone.Components;
|
||||
using EE2Clone.Core;
|
||||
|
||||
namespace EE2Clone.Systems
|
||||
{
|
||||
/// <summary>
|
||||
/// Server-side system that processes MoveCommandRpc and sets MoveTarget on units.
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
public partial struct MoveCommandSystem : ISystem
|
||||
{
|
||||
[BurstCompile]
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
var ecb = new EntityCommandBuffer(Allocator.Temp);
|
||||
|
||||
foreach (var (cmd, source, entity) in
|
||||
SystemAPI.Query<RefRO<MoveCommandRpc>, RefRO<ReceiveRpcCommandRequest>>()
|
||||
.WithEntityAccess())
|
||||
{
|
||||
var unitEntity = cmd.ValueRO.UnitEntity;
|
||||
if (state.EntityManager.Exists(unitEntity) &&
|
||||
state.EntityManager.HasComponent<MoveTarget>(unitEntity))
|
||||
{
|
||||
state.EntityManager.SetComponentData(unitEntity, new MoveTarget
|
||||
{
|
||||
Position = cmd.ValueRO.TargetPosition,
|
||||
IsActive = true
|
||||
});
|
||||
|
||||
// Clear combat target when moving
|
||||
if (state.EntityManager.HasComponent<CombatTarget>(unitEntity))
|
||||
{
|
||||
state.EntityManager.SetComponentData(unitEntity, new CombatTarget
|
||||
{
|
||||
Target = Entity.Null
|
||||
});
|
||||
}
|
||||
|
||||
// Set state to Moving
|
||||
if (state.EntityManager.HasComponent<UnitStateComponent>(unitEntity))
|
||||
{
|
||||
state.EntityManager.SetComponentData(unitEntity, new UnitStateComponent
|
||||
{
|
||||
Value = UnitState.Moving
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ecb.DestroyEntity(entity);
|
||||
}
|
||||
|
||||
ecb.Playback(state.EntityManager);
|
||||
ecb.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server-side system that moves units toward their MoveTarget.
|
||||
/// Placeholder for flow-field pathfinding — currently uses direct steering.
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
[UpdateAfter(typeof(MoveCommandSystem))]
|
||||
public partial struct UnitMovementSystem : ISystem
|
||||
{
|
||||
[BurstCompile]
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
float dt = SystemAPI.Time.DeltaTime;
|
||||
|
||||
foreach (var (transform, moveTarget, speed, unitState) in
|
||||
SystemAPI.Query<RefRW<LocalTransform>, RefRW<MoveTarget>, RefRO<MovementSpeed>, RefRW<UnitStateComponent>>()
|
||||
.WithAll<UnitTag>())
|
||||
{
|
||||
if (!moveTarget.ValueRO.IsActive) continue;
|
||||
|
||||
float3 pos = transform.ValueRO.Position;
|
||||
float3 target = moveTarget.ValueRO.Position;
|
||||
float3 direction = target - pos;
|
||||
direction.y = 0; // Keep on ground plane
|
||||
|
||||
float distance = math.length(direction);
|
||||
|
||||
if (distance < 0.5f)
|
||||
{
|
||||
// Arrived
|
||||
moveTarget.ValueRW.IsActive = false;
|
||||
if (unitState.ValueRO.Value == UnitState.Moving)
|
||||
unitState.ValueRW.Value = UnitState.Idle;
|
||||
}
|
||||
else
|
||||
{
|
||||
float3 moveDir = math.normalize(direction);
|
||||
float moveAmount = speed.ValueRO.Value * dt;
|
||||
moveAmount = math.min(moveAmount, distance);
|
||||
|
||||
var newPos = pos + moveDir * moveAmount;
|
||||
transform.ValueRW.Position = newPos;
|
||||
|
||||
// Face movement direction
|
||||
transform.ValueRW.Rotation = quaternion.LookRotationSafe(moveDir, math.up());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Server-side: processes StopCommandRpc — halts unit movement and clears targets.
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
public partial struct StopCommandSystem : ISystem
|
||||
{
|
||||
[BurstCompile]
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
var ecb = new EntityCommandBuffer(Allocator.Temp);
|
||||
|
||||
foreach (var (cmd, source, entity) in
|
||||
SystemAPI.Query<RefRO<StopCommandRpc>, RefRO<ReceiveRpcCommandRequest>>()
|
||||
.WithEntityAccess())
|
||||
{
|
||||
var unitEntity = cmd.ValueRO.UnitEntity;
|
||||
if (state.EntityManager.Exists(unitEntity))
|
||||
{
|
||||
if (state.EntityManager.HasComponent<MoveTarget>(unitEntity))
|
||||
state.EntityManager.SetComponentData(unitEntity, new MoveTarget { IsActive = false });
|
||||
|
||||
if (state.EntityManager.HasComponent<CombatTarget>(unitEntity))
|
||||
state.EntityManager.SetComponentData(unitEntity, new CombatTarget { Target = Entity.Null });
|
||||
|
||||
if (state.EntityManager.HasComponent<UnitStateComponent>(unitEntity))
|
||||
state.EntityManager.SetComponentData(unitEntity, new UnitStateComponent { Value = UnitState.Idle });
|
||||
}
|
||||
|
||||
ecb.DestroyEntity(entity);
|
||||
}
|
||||
|
||||
ecb.Playback(state.EntityManager);
|
||||
ecb.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Systems/MovementSystems.cs.meta
Normal file
2
Assets/Scripts/Systems/MovementSystems.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ae6e692d140e8c24a9dd56224e814d43
|
||||
67
Assets/Scripts/Systems/PopulationSystem.cs
Normal file
67
Assets/Scripts/Systems/PopulationSystem.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Entities;
|
||||
using Unity.NetCode;
|
||||
using EE2Clone.Components;
|
||||
using EE2Clone.Core;
|
||||
|
||||
namespace EE2Clone.Systems
|
||||
{
|
||||
/// <summary>
|
||||
/// Server-side: sums population-providing buildings per player and updates PopulationMax.
|
||||
/// </summary>
|
||||
[BurstCompile]
|
||||
[WorldSystemFilter(WorldSystemFilterFlags.ServerSimulation)]
|
||||
public partial struct PopulationSystem : ISystem
|
||||
{
|
||||
[BurstCompile]
|
||||
public void OnUpdate(ref SystemState state)
|
||||
{
|
||||
// Build a map of playerId → total pop capacity from buildings
|
||||
var popCapMap = new NativeHashMap<int, int>(GameConstants.MaxPlayers, Allocator.Temp);
|
||||
|
||||
foreach (var (pop, owner) in
|
||||
SystemAPI.Query<RefRO<ProvidesPopulation>, RefRO<OwnerPlayer>>()
|
||||
.WithAll<BuildingTag>()
|
||||
.WithNone<UnderConstructionTag>())
|
||||
{
|
||||
int playerId = owner.ValueRO.PlayerId;
|
||||
if (popCapMap.TryGetValue(playerId, out int current))
|
||||
popCapMap[playerId] = current + pop.ValueRO.Amount;
|
||||
else
|
||||
popCapMap[playerId] = GameConstants.StartingPopulationCap + pop.ValueRO.Amount;
|
||||
}
|
||||
|
||||
// Count current population per player (units)
|
||||
var popCurrentMap = new NativeHashMap<int, int>(GameConstants.MaxPlayers, Allocator.Temp);
|
||||
|
||||
foreach (var owner in SystemAPI.Query<RefRO<OwnerPlayer>>().WithAll<UnitTag>())
|
||||
{
|
||||
int playerId = owner.ValueRO.PlayerId;
|
||||
if (popCurrentMap.TryGetValue(playerId, out int current))
|
||||
popCurrentMap[playerId] = current + 1;
|
||||
else
|
||||
popCurrentMap[playerId] = 1;
|
||||
}
|
||||
|
||||
// Update PlayerStateComponent
|
||||
foreach (var playerState in SystemAPI.Query<RefRW<PlayerStateComponent>>())
|
||||
{
|
||||
int pid = playerState.ValueRO.PlayerId;
|
||||
|
||||
if (popCapMap.TryGetValue(pid, out int cap))
|
||||
playerState.ValueRW.PopulationMax = System.Math.Min(cap, GameConstants.MaxPopulationCap);
|
||||
else
|
||||
playerState.ValueRW.PopulationMax = GameConstants.StartingPopulationCap;
|
||||
|
||||
if (popCurrentMap.TryGetValue(pid, out int current))
|
||||
playerState.ValueRW.PopulationCurrent = current;
|
||||
else
|
||||
playerState.ValueRW.PopulationCurrent = 0;
|
||||
}
|
||||
|
||||
popCapMap.Dispose();
|
||||
popCurrentMap.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/Systems/PopulationSystem.cs.meta
Normal file
2
Assets/Scripts/Systems/PopulationSystem.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e2ca22d7e5c3be04fb7f4d58ea50c36b
|
||||
Reference in New Issue
Block a user