Files
Nitrox/NitroxServer/GameLogic/Entities/EntitySimulation.cs
2025-07-06 00:23:46 +02:00

163 lines
6.7 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using NitroxModel.DataStructures;
using NitroxModel.DataStructures.GameLogic;
using NitroxModel.DataStructures.GameLogic.Entities;
using NitroxModel.Packets;
namespace NitroxServer.GameLogic.Entities;
public class EntitySimulation
{
private const SimulationLockType DEFAULT_ENTITY_SIMULATION_LOCKTYPE = SimulationLockType.TRANSIENT;
private readonly EntityRegistry entityRegistry;
private readonly WorldEntityManager worldEntityManager;
private readonly PlayerManager playerManager;
private readonly ISimulationWhitelist simulationWhitelist;
private readonly SimulationOwnershipData simulationOwnershipData;
public EntitySimulation(EntityRegistry entityRegistry, WorldEntityManager worldEntityManager, SimulationOwnershipData simulationOwnershipData, PlayerManager playerManager, ISimulationWhitelist simulationWhitelist)
{
this.entityRegistry = entityRegistry;
this.worldEntityManager = worldEntityManager;
this.simulationOwnershipData = simulationOwnershipData;
this.playerManager = playerManager;
this.simulationWhitelist = simulationWhitelist;
}
public List<SimulatedEntity> GetSimulationChangesForCell(Player player, AbsoluteEntityCell cell)
{
List<WorldEntity> entities = worldEntityManager.GetEntities(cell);
List<WorldEntity> addedEntities = FilterSimulatableEntities(player, entities);
List<SimulatedEntity> ownershipChanges = new();
foreach (WorldEntity entity in addedEntities)
{
bool doesEntityMove = ShouldSimulateEntityMovement(entity);
ownershipChanges.Add(new SimulatedEntity(entity.Id, player.Id, doesEntityMove, DEFAULT_ENTITY_SIMULATION_LOCKTYPE));
}
return ownershipChanges;
}
public void FillWithRemovedCells(Player player, AbsoluteEntityCell removedCell, List<SimulatedEntity> ownershipChanges)
{
List<WorldEntity> entities = worldEntityManager.GetEntities(removedCell);
IEnumerable<WorldEntity> revokedEntities = entities.Where(entity => !player.CanSee(entity) && simulationOwnershipData.RevokeIfOwner(entity.Id, player));
AssignEntitiesToOtherPlayers(player, revokedEntities, ownershipChanges);
}
public void BroadcastSimulationChanges(List<SimulatedEntity> ownershipChanges)
{
if (ownershipChanges.Count > 0)
{
SimulationOwnershipChange ownershipChange = new(ownershipChanges);
playerManager.SendPacketToAllPlayers(ownershipChange);
}
}
public List<SimulatedEntity> CalculateSimulationChangesFromPlayerDisconnect(Player player)
{
List<SimulatedEntity> ownershipChanges = new();
List<NitroxId> revokedEntityIds = simulationOwnershipData.RevokeAllForOwner(player);
List<Entity> revokedEntities = entityRegistry.GetEntities(revokedEntityIds);
AssignEntitiesToOtherPlayers(player, revokedEntities, ownershipChanges);
return ownershipChanges;
}
public SimulatedEntity AssignNewEntityToPlayer(Entity entity, Player player, bool shouldEntityMove = true)
{
if (simulationOwnershipData.TryToAcquire(entity.Id, player, DEFAULT_ENTITY_SIMULATION_LOCKTYPE))
{
bool doesEntityMove = shouldEntityMove && entity is WorldEntity worldEntity && ShouldSimulateEntityMovement(worldEntity);
return new SimulatedEntity(entity.Id, player.Id, doesEntityMove, DEFAULT_ENTITY_SIMULATION_LOCKTYPE);
}
throw new Exception($"New entity was already being simulated by someone else: {entity.Id}");
}
public List<SimulatedEntity> AssignGlobalRootEntitiesAndGetData(Player player)
{
List<SimulatedEntity> simulatedEntities = new();
foreach (GlobalRootEntity entity in worldEntityManager.GetGlobalRootEntities())
{
simulationOwnershipData.TryToAcquire(entity.Id, player, SimulationLockType.TRANSIENT);
if (!simulationOwnershipData.TryGetLock(entity.Id, out SimulationOwnershipData.PlayerLock playerLock))
{
continue;
}
bool doesEntityMove = ShouldSimulateEntityMovement(entity);
SimulatedEntity simulatedEntity = new(entity.Id, playerLock.Player.Id, doesEntityMove, playerLock.LockType);
simulatedEntities.Add(simulatedEntity);
}
return simulatedEntities;
}
private void AssignEntitiesToOtherPlayers(Player oldPlayer, IEnumerable<Entity> entities, List<SimulatedEntity> ownershipChanges)
{
List<Player> otherPlayers = playerManager.GetConnectedPlayersExcept(oldPlayer);
foreach (Entity entity in entities)
{
if (TryAssignEntityToPlayers(otherPlayers, entity, out SimulatedEntity simulatedEntity))
{
ownershipChanges.Add(simulatedEntity);
}
}
}
public bool TryAssignEntityToPlayers(List<Player> players, Entity entity, out SimulatedEntity simulatedEntity)
{
NitroxId id = entity.Id;
foreach (Player player in players)
{
if (player.CanSee(entity) && simulationOwnershipData.TryToAcquire(id, player, DEFAULT_ENTITY_SIMULATION_LOCKTYPE))
{
bool doesEntityMove = entity is WorldEntity worldEntity && ShouldSimulateEntityMovement(worldEntity);
Log.Verbose($"Player {player.Name} has taken over simulating {id}");
simulatedEntity = new(id, player.Id, doesEntityMove, DEFAULT_ENTITY_SIMULATION_LOCKTYPE);
return true;
}
}
simulatedEntity = null;
return false;
}
private List<WorldEntity> FilterSimulatableEntities(Player player, List<WorldEntity> entities)
{
return entities.Where(entity => {
bool isEligibleForSimulation = player.CanSee(entity) && ShouldSimulateEntity(entity);
return isEligibleForSimulation && simulationOwnershipData.TryToAcquire(entity.Id, player, DEFAULT_ENTITY_SIMULATION_LOCKTYPE);
}).ToList();
}
public bool ShouldSimulateEntity(WorldEntity entity)
{
return simulationWhitelist.UtilityWhitelist.Contains(entity.TechType) || ShouldSimulateEntityMovement(entity);
}
public bool ShouldSimulateEntityMovement(WorldEntity entity)
{
return !entity.SpawnedByServer || simulationWhitelist.MovementWhitelist.Contains(entity.TechType);
}
public bool ShouldSimulateEntityMovement(NitroxId entityId)
{
return entityRegistry.TryGetEntityById(entityId, out WorldEntity worldEntity) && ShouldSimulateEntityMovement(worldEntity);
}
public void EntityDestroyed(NitroxId id)
{
simulationOwnershipData.RevokeOwnerOfId(id);
}
}