using SepCore.Entity; using UnityEngine; namespace SepCore.Simulation { public sealed partial class SimulationWorld { #region Simulation State Lifecycle public void ClearSimulationState() { _enemies.Clear(); _projectiles.Clear(); _pickups.Clear(); _projectileRecycleEntityIds.Clear(); _projectileResolvedEntityIds.Clear(); _areaCollisionRequests.Clear(); _areaCollisionHitEvents.Clear(); _areaCollisionHitDedupKeys.Clear(); _fixedStepAccumulator = 0d; ClearJobDataChannels(); ClearPlayerMovementState(); } #endregion #region Enemy Simulation State public int EnemyCount => _enemies.Count; public bool TryGetEnemySnapshot(int entityId, out EnemySimData snapshot) { if (!_enemies.TryGetSimulationIndex(entityId, out int index)) { snapshot = default; return false; } EnemyTransformSim transform = _enemies.Transform[index]; EnemyMoveInputSim moveInput = _enemies.MoveInput[index]; EnemyMoveConfigSim moveConfig = _enemies.MoveConfig[index]; EnemyAttackSim attack = _enemies.Attack[index]; EnemyBehaviorSim behavior = _enemies.Behavior[index]; snapshot = new EnemySimData { EntityId = entityId, Position = ToVector3(transform.Position), Forward = ToVector3(transform.Forward), Rotation = ToQuaternion(transform.Rotation), Speed = moveInput.Speed, AttackRange = attack.AttackRange, AvoidEnemyOverlap = moveConfig.AvoidOverlap, EnemyBodyRadius = moveConfig.BodyRadius, SeparationIterations = moveConfig.SeparationIterations, State = behavior.State }; return true; } private int AddEnemy(in EnemySimData simData) { InitializeJobDataChannels(); int index = _enemies.Add( new EnemyIdentitySim { EntityId = simData.EntityId }, new EnemyTransformSim { Position = ToFloat3(simData.Position), Forward = ToFloat3(simData.Forward), Rotation = ToQuaternion(simData.Rotation) }, new EnemyMoveInputSim { Speed = simData.Speed }, new EnemyMoveConfigSim { AvoidOverlap = simData.AvoidEnemyOverlap, BodyRadius = simData.EnemyBodyRadius, SeparationIterations = simData.SeparationIterations }, new EnemyAttackSim { AttackRange = simData.AttackRange }, new EnemyBehaviorSim { State = simData.State }); MarkEnemyTargetSpatialIndexDirty(); return index; } internal int UpsertEnemy(in EnemySimData simData) { if (!_enemies.TryGetSimulationIndex(simData.EntityId, out int index)) { return AddEnemy(in simData); } _enemies.Identity[index] = new EnemyIdentitySim { EntityId = simData.EntityId }; _enemies.Transform[index] = new EnemyTransformSim { Position = ToFloat3(simData.Position), Forward = ToFloat3(simData.Forward), Rotation = ToQuaternion(simData.Rotation) }; _enemies.MoveInput[index] = new EnemyMoveInputSim { Speed = simData.Speed }; _enemies.MoveConfig[index] = new EnemyMoveConfigSim { AvoidOverlap = simData.AvoidEnemyOverlap, BodyRadius = simData.EnemyBodyRadius, SeparationIterations = simData.SeparationIterations }; _enemies.Attack[index] = new EnemyAttackSim { AttackRange = simData.AttackRange }; _enemies.Behavior[index] = new EnemyBehaviorSim { State = simData.State }; MarkEnemyTargetSpatialIndexDirty(); return index; } private bool RemoveEnemyByEntityId(int entityId) { if (!_enemies.RemoveByEntityId(entityId)) { return false; } MarkEnemyTargetSpatialIndexDirty(); return true; } private void RegisterEnemyLifecycle(EnemyBase enemy, object userData) { if (enemy == null || enemy.CachedTransform == null) { return; } EnemyData enemyData = userData as EnemyData; UpsertEnemy(CreateEnemyInitialSimData(enemy, enemyData)); } private void UnregisterEnemyLifecycle(int entityId) { RemoveEnemyByEntityId(entityId); } public void SyncEnemyMovementInput(int entityId, bool isMoving, in Vector3 direction, float speed, bool avoidEnemyOverlap, float enemyBodyRadius, int separationIterations) { if (!_enemies.TryGetSimulationIndex(entityId, out int index)) { return; } _enemies.MoveInput[index] = new EnemyMoveInputSim { Speed = isMoving ? Mathf.Max(0f, speed) : 0f }; _enemies.MoveConfig[index] = new EnemyMoveConfigSim { AvoidOverlap = avoidEnemyOverlap, BodyRadius = Mathf.Max(0.01f, enemyBodyRadius), SeparationIterations = Mathf.Max(1, separationIterations) }; Vector3 planarDirection = direction; planarDirection.y = 0f; if (planarDirection.sqrMagnitude > Mathf.Epsilon) { planarDirection.Normalize(); EnemyTransformSim transform = _enemies.Transform[index]; transform.Forward = ToFloat3(planarDirection); _enemies.Transform[index] = transform; } } #endregion #region Projectile Simulation State public int ProjectileCount => _projectiles.Count; public bool TryGetProjectileSnapshot(int entityId, out ProjectileSimData snapshot) { if (!_projectiles.TryGetSimulationIndex(entityId, out int index)) { snapshot = default; return false; } ProjectileIdentitySim identity = _projectiles.Identity[index]; ProjectileTransformSim transform = _projectiles.Transform[index]; ProjectileVelocitySim velocity = _projectiles.Velocity[index]; ProjectileLifeSim life = _projectiles.Life[index]; snapshot = new ProjectileSimData { EntityId = identity.EntityId, OwnerEntityId = identity.OwnerEntityId, Position = ToVector3(transform.Position), Forward = ToVector3(transform.Forward), Velocity = ToVector3(velocity.Velocity), LifeTime = life.LifeTime, Age = life.Age, Active = life.Active }; return true; } private int AddProjectile(in ProjectileSimData simData) { InitializeJobDataChannels(); return _projectiles.Add( new ProjectileIdentitySim { EntityId = simData.EntityId, OwnerEntityId = simData.OwnerEntityId }, new ProjectileTransformSim { Position = ToFloat3(simData.Position), Forward = ToFloat3(simData.Forward) }, new ProjectileVelocitySim { Velocity = ToFloat3(simData.Velocity) }, new ProjectileLifeSim { Age = simData.Age, LifeTime = simData.LifeTime, Active = simData.Active }); } internal int UpsertProjectile(in ProjectileSimData simData) { if (!_projectiles.TryGetSimulationIndex(simData.EntityId, out int index)) { return AddProjectile(in simData); } _projectiles.Identity[index] = new ProjectileIdentitySim { EntityId = simData.EntityId, OwnerEntityId = simData.OwnerEntityId }; _projectiles.Transform[index] = new ProjectileTransformSim { Position = ToFloat3(simData.Position), Forward = ToFloat3(simData.Forward) }; _projectiles.Velocity[index] = new ProjectileVelocitySim { Velocity = ToFloat3(simData.Velocity) }; _projectiles.Life[index] = new ProjectileLifeSim { Age = simData.Age, LifeTime = simData.LifeTime, Active = simData.Active }; return index; } private bool RemoveProjectileByEntityId(int entityId) { return _projectiles.RemoveByEntityId(entityId); } private void RegisterProjectileLifecycle(EntityBase projectileEntity, object userData) { if (projectileEntity == null || projectileEntity.CachedTransform == null) { return; } UpsertProjectile(CreateProjectileInitialSimData(projectileEntity, userData)); } private void UnregisterProjectileLifecycle(int entityId) { RemoveProjectileByEntityId(entityId); } #endregion #region Pickup Simulation State public int PickupCount => _pickups.Count; public bool TryGetPickupSnapshot(int entityId, out PickupSimData snapshot) { if (!_pickups.TryGetSimulationIndex(entityId, out int index)) { snapshot = default; return false; } PickupTransformSim transform = _pickups.Transform[index]; PickupRadiusSim radius = _pickups.Radius[index]; PickupStateSim state = _pickups.State[index]; snapshot = new PickupSimData { EntityId = entityId, Position = ToVector3(transform.Position), PickupRadius = radius.Radius, State = state.State }; return true; } private int AddPickup(in PickupSimData simData) { InitializeJobDataChannels(); return _pickups.Add( new PickupIdentitySim { EntityId = simData.EntityId }, new PickupTransformSim { Position = ToFloat3(simData.Position) }, new PickupRadiusSim { Radius = simData.PickupRadius }, new PickupStateSim { State = simData.State }); } internal int UpsertPickup(in PickupSimData simData) { if (!_pickups.TryGetSimulationIndex(simData.EntityId, out int index)) { return AddPickup(in simData); } _pickups.Identity[index] = new PickupIdentitySim { EntityId = simData.EntityId }; _pickups.Transform[index] = new PickupTransformSim { Position = ToFloat3(simData.Position) }; _pickups.Radius[index] = new PickupRadiusSim { Radius = simData.PickupRadius }; _pickups.State[index] = new PickupStateSim { State = simData.State }; return index; } public void TriggerFullMapMagnet() { int pickupCount = _pickups.Count; for (int i = 0; i < pickupCount; i++) { _pickups.State[i] = new PickupStateSim { State = PickupStateSim.StateAbsorbing }; } } private bool RemovePickupByEntityId(int entityId) { return _pickups.RemoveByEntityId(entityId); } private void RegisterPickupLifecycle(EntityBase pickupEntity) { if (pickupEntity == null || pickupEntity.CachedTransform == null) { return; } UpsertPickup(CreatePickupInitialSimData(pickupEntity)); } private void UnregisterPickupLifecycle(int entityId) { RemovePickupByEntityId(entityId); } #endregion } }