Files
Nitrox/NitroxClient/GameLogic/SimulationOwnership.cs
2025-07-06 00:23:46 +02:00

210 lines
7.6 KiB
C#

using System.Collections.Generic;
using NitroxClient.Communication.Abstract;
using NitroxClient.GameLogic.Simulation;
using NitroxClient.MonoBehaviours;
using NitroxModel.DataStructures;
using NitroxModel.Packets;
using UnityEngine;
namespace NitroxClient.GameLogic
{
public class SimulationOwnership
{
private readonly IMultiplayerSession multiplayerSession;
private readonly IPacketSender packetSender;
private readonly Dictionary<NitroxId, SimulationLockType> simulatedIdsByLockType = [];
private readonly Dictionary<NitroxId, LockRequestBase> lockRequestsById = [];
private readonly Dictionary<NitroxId, SimulatedEntity> newerSimulationById = [];
public SimulationOwnership(IMultiplayerSession muliplayerSession, IPacketSender packetSender)
{
this.multiplayerSession = muliplayerSession;
this.packetSender = packetSender;
}
public bool PlayerHasMinLockType(NitroxId id, SimulationLockType lockType)
{
if (id != null && simulatedIdsByLockType.TryGetValue(id, out SimulationLockType playerLock))
{
return playerLock <= lockType;
}
return false;
}
public bool HasAnyLockType(NitroxId id)
{
return PlayerHasMinLockType(id, SimulationLockType.TRANSIENT);
}
public bool HasExclusiveLock(NitroxId id)
{
return PlayerHasMinLockType(id, SimulationLockType.EXCLUSIVE);
}
public void RequestSimulationLock(NitroxId id, SimulationLockType lockType)
{
SimulationOwnershipRequest ownershipRequest = new SimulationOwnershipRequest(multiplayerSession.Reservation.PlayerId, id, lockType);
packetSender.Send(ownershipRequest);
}
public void RequestSimulationLock(LockRequestBase lockRequest)
{
lockRequestsById[lockRequest.Id] = lockRequest;
RequestSimulationLock(lockRequest.Id, lockRequest.LockType);
}
public void ReceivedSimulationLockResponse(NitroxId id, bool lockAquired, SimulationLockType lockType)
{
Log.Info($"Received lock response, id: {id} {lockAquired} {lockType}");
if (lockAquired)
{
SimulateEntity(id, lockType);
TreatVehicleEntity(id, true, lockType);
}
if (lockRequestsById.TryGetValue(id, out LockRequestBase lockRequest))
{
lockRequest.LockRequestComplete(id, lockAquired);
lockRequestsById.Remove(id);
}
}
public void SimulateEntity(NitroxId id, SimulationLockType lockType)
{
simulatedIdsByLockType[id] = lockType;
}
public void StopSimulatingEntity(NitroxId id)
{
simulatedIdsByLockType.Remove(id);
}
public void TreatSimulatedEntity(SimulatedEntity simulatedEntity)
{
bool isLocalPlayerNewOwner = multiplayerSession.Reservation.PlayerId == simulatedEntity.PlayerId;
if (TreatVehicleEntity(simulatedEntity.Id, isLocalPlayerNewOwner, simulatedEntity.LockType) ||
newerSimulationById.ContainsKey(simulatedEntity.Id))
{
return;
}
if (isLocalPlayerNewOwner)
{
if (simulatedEntity.ChangesPosition)
{
EntityPositionBroadcaster.WatchEntity(simulatedEntity.Id);
}
SimulateEntity(simulatedEntity.Id, simulatedEntity.LockType);
}
else if (HasAnyLockType(simulatedEntity.Id))
{
// The server has forcibly removed this lock from the client. This is generally fine for
// transient locks because it is only broadcasting position. However, exclusive locks may
// need additional cleanup (such as a person piloting a vehicle - they need to be kicked out)
// We can later add a forcibly removed callback but as of right now we have no use-cases for
// forcibly removing an exclusive lock. Just log it if it happens....
if (HasExclusiveLock(simulatedEntity.Id))
{
Log.Warn($"The server has forcibly revoked an exclusive lock - this may cause undefined behaviour. GUID: {simulatedEntity.Id}");
}
StopSimulatingEntity(simulatedEntity.Id);
EntityPositionBroadcaster.StopWatchingEntity(simulatedEntity.Id);
}
// Avoid keeping artifacts of the entity's previous ChangesPosition state
if (!simulatedEntity.ChangesPosition && NitroxEntity.TryGetComponentFrom(simulatedEntity.Id, out RemotelyControlled remotelyControlled))
{
Object.Destroy(remotelyControlled);
}
}
public void DropSimulationFrom(NitroxId entityId)
{
StopSimulatingEntity(entityId);
EntityPositionBroadcaster.StopWatchingEntity(entityId);
if (!NitroxEntity.TryGetObjectFrom(entityId, out GameObject gameObject))
{
return;
}
if (gameObject.TryGetComponent(out RemotelyControlled remotelyControlled))
{
Object.Destroy(remotelyControlled);
}
}
public bool TryGetLockType(NitroxId nitroxId, out SimulationLockType simulationLockType)
{
return simulatedIdsByLockType.TryGetValue(nitroxId, out simulationLockType);
}
public bool TreatVehicleEntity(NitroxId entityId, bool isLocalPlayerNewOwner, SimulationLockType simulationLockType)
{
if (!NitroxEntity.TryGetObjectFrom(entityId, out GameObject gameObject) || !IsVehicle(gameObject))
{
return false;
}
MovementReplicator movementReplicator = gameObject.GetComponent<MovementReplicator>();
if (isLocalPlayerNewOwner)
{
if (movementReplicator)
{
Object.Destroy(movementReplicator);
}
MovementBroadcaster.RegisterWatched(gameObject, entityId);
SimulateEntity(entityId, simulationLockType);
}
else
{
if (!movementReplicator)
{
MovementReplicator.AddReplicatorToObject(gameObject);
}
MovementBroadcaster.UnregisterWatched(entityId);
StopSimulatingEntity(entityId);
}
return true;
}
public bool IsVehicle(GameObject gameObject)
{
if (gameObject.GetComponent<Vehicle>())
{
return true;
}
if (gameObject.TryGetComponent(out SubRoot subRoot) && !subRoot.isBase)
{
return true;
}
return false;
}
public void RegisterNewerSimulation(NitroxId entityId, SimulatedEntity simulatedEntity)
{
newerSimulationById[entityId] = simulatedEntity;
}
public void ApplyNewerSimulation(NitroxId nitroxId)
{
if (newerSimulationById.TryGetValue(nitroxId, out SimulatedEntity simulatedEntity))
{
newerSimulationById.Remove(nitroxId);
TreatSimulatedEntity(simulatedEntity);
}
}
public void ClearNewerSimulations()
{
newerSimulationById.Clear();
}
}
}