Fixed
This commit is contained in:
@@ -0,0 +1,388 @@
|
||||
using System;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
|
||||
public enum CannonState : byte
|
||||
{
|
||||
Empty,
|
||||
Loaded,
|
||||
Countdown
|
||||
}
|
||||
|
||||
public struct CannonShotState : INetworkSerializable, IEquatable<CannonShotState>
|
||||
{
|
||||
public uint Sequence;
|
||||
public MissileType MissileType;
|
||||
|
||||
public CannonShotState(uint sequence, MissileType missileType)
|
||||
{
|
||||
Sequence = sequence;
|
||||
MissileType = missileType;
|
||||
}
|
||||
|
||||
public void NetworkSerialize<T>(BufferSerializer<T> serializer)
|
||||
where T : IReaderWriter
|
||||
{
|
||||
serializer.SerializeValue(ref Sequence);
|
||||
serializer.SerializeValue(ref MissileType);
|
||||
}
|
||||
|
||||
public bool Equals(CannonShotState other) =>
|
||||
Sequence == other.Sequence && MissileType == other.MissileType;
|
||||
}
|
||||
|
||||
[DisallowMultipleComponent]
|
||||
[RequireComponent(typeof(NetworkObject))]
|
||||
public sealed class NetworkCannonController : NetworkBehaviour
|
||||
{
|
||||
private const ulong EmptyRocketId = ulong.MaxValue;
|
||||
|
||||
[Header("Configuration")]
|
||||
[SerializeField] private CannonConfig config;
|
||||
|
||||
[Header("Rocket Slot")]
|
||||
[SerializeField] private CannonRocketSlot rocketSlot;
|
||||
[SerializeField] private Transform rocketSnapPoint;
|
||||
[SerializeField, Min(0.5f)] private float maximumTakeDistance = 4f;
|
||||
[SerializeField, Min(0f)] private float minimumReinsertBlockDuration = 0.1f;
|
||||
|
||||
[Header("Output")]
|
||||
[SerializeField] private MapController mapController;
|
||||
[SerializeField] private NetworkShockwaveEmitter shotShockwave;
|
||||
|
||||
private readonly NetworkVariable<CannonState> state = new(
|
||||
CannonState.Empty,
|
||||
NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server);
|
||||
private readonly NetworkVariable<ulong> loadedRocketId = new(
|
||||
EmptyRocketId,
|
||||
NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server);
|
||||
private readonly NetworkVariable<float> selectedDelay = new(
|
||||
5f,
|
||||
NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server);
|
||||
private readonly NetworkVariable<double> fireAt = new(
|
||||
0d,
|
||||
NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server);
|
||||
private readonly NetworkVariable<CannonShotState> lastShot = new(
|
||||
default,
|
||||
NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server);
|
||||
|
||||
private NetworkRocketItem loadedRocket;
|
||||
private IWorkstationItemControl loadedRocketControl;
|
||||
private ulong blockedRocketId = EmptyRocketId;
|
||||
private Collider[] blockedRocketColliders;
|
||||
private double unblockAfter;
|
||||
|
||||
public CannonState State => state.Value;
|
||||
public CannonConfig Config => config;
|
||||
public NetworkRocketItem LoadedRocket => loadedRocket != null
|
||||
? loadedRocket
|
||||
: ResolveLoadedRocket();
|
||||
public float SelectedDelay => selectedDelay.Value;
|
||||
public double FireAt => fireAt.Value;
|
||||
public float RemainingSeconds => State == CannonState.Countdown && NetworkManager != null
|
||||
? Mathf.Max(0f, (float)(fireAt.Value - NetworkManager.ServerTime.Time))
|
||||
: 0f;
|
||||
public bool CanTakeRocket => IsSpawned && State == CannonState.Loaded &&
|
||||
loadedRocketId.Value != EmptyRocketId;
|
||||
public CannonShotState LastShot => lastShot.Value;
|
||||
|
||||
public event Action Changed;
|
||||
public event Action<CannonShotState> ShotFired;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (rocketSnapPoint == null && rocketSlot != null)
|
||||
rocketSnapPoint = rocketSlot.SnapPoint;
|
||||
rocketSlot?.Configure(this);
|
||||
}
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
state.OnValueChanged += HandleStateChanged;
|
||||
loadedRocketId.OnValueChanged += HandleRocketIdChanged;
|
||||
selectedDelay.OnValueChanged += HandleDelayChanged;
|
||||
fireAt.OnValueChanged += HandleFireAtChanged;
|
||||
lastShot.OnValueChanged += HandleShotChanged;
|
||||
|
||||
rocketSlot?.Configure(this);
|
||||
if (IsServer)
|
||||
{
|
||||
state.Value = CannonState.Empty;
|
||||
loadedRocketId.Value = EmptyRocketId;
|
||||
selectedDelay.Value = config != null ? config.DefaultDelay : 5f;
|
||||
fireAt.Value = 0d;
|
||||
}
|
||||
Changed?.Invoke();
|
||||
}
|
||||
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
state.OnValueChanged -= HandleStateChanged;
|
||||
loadedRocketId.OnValueChanged -= HandleRocketIdChanged;
|
||||
selectedDelay.OnValueChanged -= HandleDelayChanged;
|
||||
fireAt.OnValueChanged -= HandleFireAtChanged;
|
||||
lastShot.OnValueChanged -= HandleShotChanged;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!IsServer || !IsSpawned)
|
||||
return;
|
||||
|
||||
UpdateReinsertBlock();
|
||||
if (State == CannonState.Countdown &&
|
||||
NetworkManager != null && NetworkManager.ServerTime.Time >= fireAt.Value)
|
||||
{
|
||||
FireOnServer();
|
||||
}
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (!IsServer || !IsSpawned || LoadedRocket == null ||
|
||||
rocketSnapPoint == null)
|
||||
return;
|
||||
|
||||
loadedRocket.transform.SetPositionAndRotation(
|
||||
rocketSnapPoint.position,
|
||||
rocketSnapPoint.rotation);
|
||||
}
|
||||
|
||||
public bool TryInsertRocket(NetworkRocketItem rocket)
|
||||
{
|
||||
if (!IsServer || !IsSpawned || State != CannonState.Empty ||
|
||||
rocket == null || !rocket.IsSpawned || rocket.Definition == null ||
|
||||
rocket.NetworkObjectId == blockedRocketId ||
|
||||
(config != null && !config.AcceptDefectiveRockets && rocket.IsDefective) ||
|
||||
(config != null && !config.AcceptCriticallyDamagedRockets &&
|
||||
rocket.IsCriticallyDamaged) ||
|
||||
!WorkstationItemControlUtility.TryGetControl(
|
||||
rocket.GetComponent<NetworkItem>(),
|
||||
out IWorkstationItemControl control) ||
|
||||
!control.TryAcquireExternalControl())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
loadedRocket = rocket;
|
||||
loadedRocketControl = control;
|
||||
loadedRocketId.Value = rocket.NetworkObjectId;
|
||||
state.Value = CannonState.Loaded;
|
||||
SnapLoadedRocket();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void RequestTakeRocket()
|
||||
{
|
||||
if (CanTakeRocket)
|
||||
RequestTakeRocketRpc();
|
||||
}
|
||||
|
||||
public void RequestSetShotDelay(float seconds)
|
||||
{
|
||||
if (!IsSpawned)
|
||||
return;
|
||||
if (IsServer)
|
||||
SetShotDelayOnServer(seconds);
|
||||
else
|
||||
RequestSetShotDelayRpc(seconds);
|
||||
}
|
||||
|
||||
public void RequestAdjustShotDelay(float delta)
|
||||
{
|
||||
RequestSetShotDelay(SelectedDelay + delta);
|
||||
}
|
||||
|
||||
public void RequestIncreaseShotDelay()
|
||||
{
|
||||
RequestAdjustShotDelay(config != null ? config.AdjustmentStep : 1f);
|
||||
}
|
||||
|
||||
public void RequestDecreaseShotDelay()
|
||||
{
|
||||
RequestAdjustShotDelay(-(config != null ? config.AdjustmentStep : 1f));
|
||||
}
|
||||
|
||||
public void RequestStartCountdown()
|
||||
{
|
||||
if (!IsSpawned)
|
||||
return;
|
||||
if (IsServer)
|
||||
TryStartCountdownOnServer();
|
||||
else
|
||||
RequestStartCountdownRpc();
|
||||
}
|
||||
|
||||
public void SetFireRequested(bool isActivated)
|
||||
{
|
||||
if (isActivated && IsServer && IsSpawned)
|
||||
TryStartCountdownOnServer();
|
||||
}
|
||||
|
||||
[ContextMenu("Debug/Start Countdown (Server Play Mode)")]
|
||||
private void StartCountdownFromContextMenu()
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
TryStartCountdownOnServer();
|
||||
}
|
||||
|
||||
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
||||
private void RequestSetShotDelayRpc(float seconds) => SetShotDelayOnServer(seconds);
|
||||
|
||||
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
||||
private void RequestStartCountdownRpc() => TryStartCountdownOnServer();
|
||||
|
||||
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
||||
private void RequestTakeRocketRpc(RpcParams rpcParams = default)
|
||||
{
|
||||
if (!CanTakeRocket || rocketSlot == null ||
|
||||
!TryGetPlayerObject(rpcParams.Receive.SenderClientId, out NetworkObject player))
|
||||
return;
|
||||
|
||||
float maxDistanceSquared = maximumTakeDistance * maximumTakeDistance;
|
||||
if ((player.transform.position - rocketSlot.InteractionPosition).sqrMagnitude >
|
||||
maxDistanceSquared)
|
||||
return;
|
||||
|
||||
NetworkRocketItem rocket = LoadedRocket;
|
||||
IWorkstationItemControl control = loadedRocketControl ?? ResolveLoadedControl();
|
||||
if (rocket == null || control == null ||
|
||||
!control.TryReleaseExternalControlToPlayer(rpcParams.Receive.SenderClientId))
|
||||
return;
|
||||
|
||||
blockedRocketId = rocket.NetworkObjectId;
|
||||
blockedRocketColliders = rocket.GetComponentsInChildren<Collider>(true);
|
||||
unblockAfter = GetNetworkTime() + minimumReinsertBlockDuration;
|
||||
ClearRocketSlot();
|
||||
}
|
||||
|
||||
private void SetShotDelayOnServer(float seconds)
|
||||
{
|
||||
if (!IsServer || State == CannonState.Countdown)
|
||||
return;
|
||||
selectedDelay.Value = config != null
|
||||
? config.ClampDelay(seconds)
|
||||
: Mathf.Clamp(seconds, 0f, 60f);
|
||||
}
|
||||
|
||||
private bool TryStartCountdownOnServer()
|
||||
{
|
||||
if (!IsServer || !IsSpawned || State != CannonState.Loaded ||
|
||||
LoadedRocket == null || NetworkManager == null)
|
||||
return false;
|
||||
|
||||
fireAt.Value = NetworkManager.ServerTime.Time + selectedDelay.Value;
|
||||
state.Value = CannonState.Countdown;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void FireOnServer()
|
||||
{
|
||||
NetworkRocketItem rocket = LoadedRocket;
|
||||
if (rocket == null || rocket.Definition == null)
|
||||
{
|
||||
ClearRocketSlot();
|
||||
return;
|
||||
}
|
||||
|
||||
MissileType missileType = rocket.Definition.MissileType;
|
||||
mapController?.RequestShot(missileType);
|
||||
shotShockwave?.EmitShockwave();
|
||||
lastShot.Value = new CannonShotState(lastShot.Value.Sequence + 1, missileType);
|
||||
|
||||
NetworkObject rocketObject = rocket.NetworkObject;
|
||||
loadedRocket = null;
|
||||
loadedRocketControl = null;
|
||||
loadedRocketId.Value = EmptyRocketId;
|
||||
fireAt.Value = 0d;
|
||||
state.Value = CannonState.Empty;
|
||||
|
||||
if (rocketObject != null && rocketObject.IsSpawned)
|
||||
rocketObject.Despawn(true);
|
||||
}
|
||||
|
||||
private void ClearRocketSlot()
|
||||
{
|
||||
loadedRocket = null;
|
||||
loadedRocketControl = null;
|
||||
loadedRocketId.Value = EmptyRocketId;
|
||||
fireAt.Value = 0d;
|
||||
state.Value = CannonState.Empty;
|
||||
}
|
||||
|
||||
private void SnapLoadedRocket()
|
||||
{
|
||||
if (loadedRocket != null && rocketSnapPoint != null)
|
||||
{
|
||||
loadedRocket.transform.SetPositionAndRotation(
|
||||
rocketSnapPoint.position,
|
||||
rocketSnapPoint.rotation);
|
||||
}
|
||||
}
|
||||
|
||||
private NetworkRocketItem ResolveLoadedRocket()
|
||||
{
|
||||
if (loadedRocketId.Value == EmptyRocketId || NetworkManager == null ||
|
||||
NetworkManager.SpawnManager == null ||
|
||||
!NetworkManager.SpawnManager.SpawnedObjects.TryGetValue(
|
||||
loadedRocketId.Value, out NetworkObject networkObject))
|
||||
return null;
|
||||
|
||||
loadedRocket = networkObject.GetComponent<NetworkRocketItem>();
|
||||
return loadedRocket;
|
||||
}
|
||||
|
||||
private IWorkstationItemControl ResolveLoadedControl()
|
||||
{
|
||||
NetworkRocketItem rocket = LoadedRocket;
|
||||
WorkstationItemControlUtility.TryGetControl(
|
||||
rocket != null ? rocket.GetComponent<NetworkItem>() : null,
|
||||
out loadedRocketControl);
|
||||
return loadedRocketControl;
|
||||
}
|
||||
|
||||
private bool TryGetPlayerObject(ulong clientId, out NetworkObject player)
|
||||
{
|
||||
player = null;
|
||||
return NetworkManager != null &&
|
||||
NetworkManager.ConnectedClients.TryGetValue(clientId, out NetworkClient client) &&
|
||||
(player = client.PlayerObject) != null;
|
||||
}
|
||||
|
||||
private void UpdateReinsertBlock()
|
||||
{
|
||||
if (blockedRocketId == EmptyRocketId || rocketSlot == null ||
|
||||
GetNetworkTime() < unblockAfter ||
|
||||
rocketSlot.OverlapsAny(blockedRocketColliders, 0.05f))
|
||||
return;
|
||||
|
||||
blockedRocketId = EmptyRocketId;
|
||||
blockedRocketColliders = null;
|
||||
}
|
||||
|
||||
private double GetNetworkTime() => NetworkManager != null
|
||||
? NetworkManager.ServerTime.Time
|
||||
: Time.timeAsDouble;
|
||||
|
||||
private void HandleStateChanged(CannonState _, CannonState __) => Changed?.Invoke();
|
||||
private void HandleRocketIdChanged(ulong _, ulong __)
|
||||
{
|
||||
loadedRocket = null;
|
||||
loadedRocketControl = null;
|
||||
Changed?.Invoke();
|
||||
}
|
||||
private void HandleDelayChanged(float _, float __) => Changed?.Invoke();
|
||||
private void HandleFireAtChanged(double _, double __) => Changed?.Invoke();
|
||||
private void HandleShotChanged(CannonShotState previous, CannonShotState current)
|
||||
{
|
||||
if (current.Sequence == previous.Sequence)
|
||||
return;
|
||||
ShotFired?.Invoke(current);
|
||||
Changed?.Invoke();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user