292 lines
9.3 KiB
C#
292 lines
9.3 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using Unity.Netcode;
|
|
using UnityEngine;
|
|
|
|
public enum FurnaceState : byte
|
|
{
|
|
Idle,
|
|
Smelting
|
|
}
|
|
|
|
public sealed class FurnaceController : NetworkWorkstationController
|
|
{
|
|
[Header("Storage")]
|
|
[SerializeField, Min(1f)] private float metalCapacity = 100f;
|
|
|
|
[Header("Production")]
|
|
[SerializeField] private Transform outputPoint;
|
|
[SerializeField] private List<SmeltingRecipe> recipes = new();
|
|
[SerializeField, Min(0f)] private float outputSpacing = 0.35f;
|
|
|
|
private readonly NetworkVariable<float> storedMetal = new(
|
|
0f,
|
|
NetworkVariableReadPermission.Everyone,
|
|
NetworkVariableWritePermission.Server);
|
|
|
|
private readonly NetworkVariable<int> selectedRecipeIndex = new(
|
|
-1,
|
|
NetworkVariableReadPermission.Everyone,
|
|
NetworkVariableWritePermission.Server);
|
|
|
|
private readonly NetworkVariable<FurnaceState> furnaceState = new(
|
|
FurnaceState.Idle,
|
|
NetworkVariableReadPermission.Everyone,
|
|
NetworkVariableWritePermission.Server);
|
|
|
|
private readonly NetworkVariable<double> processStartedAt = new(
|
|
0d,
|
|
NetworkVariableReadPermission.Everyone,
|
|
NetworkVariableWritePermission.Server);
|
|
|
|
private readonly NetworkVariable<double> processEndsAt = new(
|
|
0d,
|
|
NetworkVariableReadPermission.Everyone,
|
|
NetworkVariableWritePermission.Server);
|
|
|
|
public override bool IsOperating => furnaceState.Value == FurnaceState.Smelting;
|
|
public FurnaceState State => furnaceState.Value;
|
|
public float StoredMetal => storedMetal.Value;
|
|
public float MetalCapacity => metalCapacity;
|
|
public float MetalNormalized => metalCapacity > 0f ? storedMetal.Value / metalCapacity : 0f;
|
|
public int SelectedRecipeIndex => selectedRecipeIndex.Value;
|
|
public IReadOnlyList<SmeltingRecipe> Recipes => recipes;
|
|
public SmeltingRecipe SelectedRecipe => TryGetRecipe(selectedRecipeIndex.Value, out SmeltingRecipe recipe)
|
|
? recipe
|
|
: null;
|
|
public bool CanStartSelectedRecipe =>
|
|
!IsOperating &&
|
|
SelectedRecipe != null &&
|
|
SelectedRecipe.OutputItem != null &&
|
|
SelectedRecipe.OutputItem.WorldPrefab != null &&
|
|
storedMetal.Value >= SelectedRecipe.MetalCost;
|
|
public float ProcessProgress
|
|
{
|
|
get
|
|
{
|
|
if (!IsOperating)
|
|
return 0f;
|
|
|
|
double duration = processEndsAt.Value - processStartedAt.Value;
|
|
if (duration <= 0d)
|
|
return 1f;
|
|
|
|
double now = NetworkManager != null
|
|
? NetworkManager.ServerTime.Time
|
|
: Time.timeAsDouble;
|
|
return Mathf.Clamp01((float)((now - processStartedAt.Value) / duration));
|
|
}
|
|
}
|
|
public float RemainingSeconds
|
|
{
|
|
get
|
|
{
|
|
if (!IsOperating)
|
|
return 0f;
|
|
|
|
double now = NetworkManager != null
|
|
? NetworkManager.ServerTime.Time
|
|
: Time.timeAsDouble;
|
|
return Mathf.Max(0f, (float)(processEndsAt.Value - now));
|
|
}
|
|
}
|
|
|
|
public event Action Changed;
|
|
|
|
public override void OnNetworkSpawn()
|
|
{
|
|
storedMetal.OnValueChanged += HandleStoredMetalChanged;
|
|
selectedRecipeIndex.OnValueChanged += HandleSelectedRecipeChanged;
|
|
furnaceState.OnValueChanged += HandleStateChanged;
|
|
processStartedAt.OnValueChanged += HandleProcessTimeChanged;
|
|
processEndsAt.OnValueChanged += HandleProcessTimeChanged;
|
|
|
|
if (IsServer)
|
|
{
|
|
storedMetal.Value = Mathf.Clamp(storedMetal.Value, 0f, metalCapacity);
|
|
selectedRecipeIndex.Value = recipes.Count > 0 ? 0 : -1;
|
|
furnaceState.Value = FurnaceState.Idle;
|
|
processStartedAt.Value = 0d;
|
|
processEndsAt.Value = 0d;
|
|
}
|
|
|
|
Changed?.Invoke();
|
|
}
|
|
|
|
public override void OnNetworkDespawn()
|
|
{
|
|
storedMetal.OnValueChanged -= HandleStoredMetalChanged;
|
|
selectedRecipeIndex.OnValueChanged -= HandleSelectedRecipeChanged;
|
|
furnaceState.OnValueChanged -= HandleStateChanged;
|
|
processStartedAt.OnValueChanged -= HandleProcessTimeChanged;
|
|
processEndsAt.OnValueChanged -= HandleProcessTimeChanged;
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (!IsServer || !IsSpawned || !IsOperating)
|
|
return;
|
|
|
|
if (NetworkManager.ServerTime.Time >= processEndsAt.Value)
|
|
CompleteSmeltingOnServer();
|
|
}
|
|
|
|
public bool TryConsumeMetalSource(FurnaceMetalSource source)
|
|
{
|
|
if (!IsServer || !IsSpawned || source == null || storedMetal.Value >= metalCapacity)
|
|
return false;
|
|
|
|
NetworkItem item = source.GetComponent<NetworkItem>();
|
|
if (item == null || !item.IsSpawned || item.NetworkObject == null)
|
|
return false;
|
|
|
|
if (source.TryGetComponent(out NetworkHeldInteractable heldObject) && heldObject.IsHeld)
|
|
return false;
|
|
|
|
if (source.TryGetComponent(out NetworkHandItemInteractable handItem) && handItem.IsHeld)
|
|
return false;
|
|
|
|
if (!source.TryMarkConsumed())
|
|
return false;
|
|
|
|
storedMetal.Value = Mathf.Min(metalCapacity, storedMetal.Value + source.MetalAmount);
|
|
item.NetworkObject.Despawn(true);
|
|
return true;
|
|
}
|
|
|
|
public bool TryAddMetal(float amount)
|
|
{
|
|
if (!IsServer || !IsSpawned || amount <= 0f || storedMetal.Value >= metalCapacity)
|
|
return false;
|
|
|
|
storedMetal.Value = Mathf.Min(metalCapacity, storedMetal.Value + amount);
|
|
return true;
|
|
}
|
|
|
|
public void SelectRecipe(int recipeIndex)
|
|
{
|
|
if (!IsSpawned)
|
|
return;
|
|
|
|
if (IsServer)
|
|
SelectRecipeOnServer(recipeIndex);
|
|
else
|
|
RequestSelectRecipeRpc(recipeIndex);
|
|
}
|
|
|
|
public void StartSmelting()
|
|
{
|
|
if (!IsSpawned)
|
|
return;
|
|
|
|
if (IsServer)
|
|
TryStartSmeltingOnServer();
|
|
else
|
|
RequestStartSmeltingRpc();
|
|
}
|
|
|
|
public void SelectAndStartRecipe(int recipeIndex)
|
|
{
|
|
if (!IsSpawned)
|
|
return;
|
|
|
|
if (IsServer)
|
|
{
|
|
if (SelectRecipeOnServer(recipeIndex))
|
|
TryStartSmeltingOnServer();
|
|
}
|
|
else
|
|
{
|
|
RequestSelectAndStartRecipeRpc(recipeIndex);
|
|
}
|
|
}
|
|
|
|
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
|
private void RequestSelectRecipeRpc(int recipeIndex) =>
|
|
SelectRecipeOnServer(recipeIndex);
|
|
|
|
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
|
private void RequestStartSmeltingRpc() =>
|
|
TryStartSmeltingOnServer();
|
|
|
|
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
|
|
private void RequestSelectAndStartRecipeRpc(int recipeIndex)
|
|
{
|
|
if (SelectRecipeOnServer(recipeIndex))
|
|
TryStartSmeltingOnServer();
|
|
}
|
|
|
|
private bool SelectRecipeOnServer(int recipeIndex)
|
|
{
|
|
if (!IsServer || IsOperating || !TryGetRecipe(recipeIndex, out _))
|
|
return false;
|
|
|
|
selectedRecipeIndex.Value = recipeIndex;
|
|
return true;
|
|
}
|
|
|
|
private bool TryStartSmeltingOnServer()
|
|
{
|
|
if (!IsServer || !CanStartSelectedRecipe)
|
|
return false;
|
|
|
|
SmeltingRecipe recipe = SelectedRecipe;
|
|
storedMetal.Value -= recipe.MetalCost;
|
|
double now = NetworkManager.ServerTime.Time;
|
|
processStartedAt.Value = now;
|
|
processEndsAt.Value = now + recipe.ProcessingDuration;
|
|
furnaceState.Value = FurnaceState.Smelting;
|
|
return true;
|
|
}
|
|
|
|
private void CompleteSmeltingOnServer()
|
|
{
|
|
if (!TryGetRecipe(selectedRecipeIndex.Value, out SmeltingRecipe recipe) ||
|
|
recipe.OutputItem == null ||
|
|
recipe.OutputItem.WorldPrefab == null)
|
|
{
|
|
Debug.LogError($"{name} cannot complete smelting: output prefab is missing.", this);
|
|
StopProcessingOnServer();
|
|
return;
|
|
}
|
|
|
|
Transform spawnTransform = outputPoint != null ? outputPoint : transform;
|
|
for (int index = 0; index < recipe.OutputAmount; index++)
|
|
{
|
|
Vector3 sideOffset = spawnTransform.right * outputSpacing * index;
|
|
NetworkObject output = Instantiate(
|
|
recipe.OutputItem.WorldPrefab,
|
|
spawnTransform.position + sideOffset,
|
|
spawnTransform.rotation);
|
|
output.Spawn();
|
|
}
|
|
|
|
StopProcessingOnServer();
|
|
}
|
|
|
|
private void StopProcessingOnServer()
|
|
{
|
|
furnaceState.Value = FurnaceState.Idle;
|
|
processStartedAt.Value = 0d;
|
|
processEndsAt.Value = 0d;
|
|
}
|
|
|
|
private bool TryGetRecipe(int index, out SmeltingRecipe recipe)
|
|
{
|
|
bool isValid = index >= 0 && index < recipes.Count && recipes[index] != null;
|
|
recipe = isValid ? recipes[index] : null;
|
|
return isValid;
|
|
}
|
|
|
|
private void HandleStoredMetalChanged(float _, float __) => Changed?.Invoke();
|
|
private void HandleSelectedRecipeChanged(int _, int __) => Changed?.Invoke();
|
|
private void HandleStateChanged(FurnaceState _, FurnaceState __) => Changed?.Invoke();
|
|
private void HandleProcessTimeChanged(double _, double __) => Changed?.Invoke();
|
|
|
|
[ContextMenu("Debug/Add 25 Metal (Server Play Mode)")]
|
|
private void DebugAddMetal() => TryAddMetal(25f);
|
|
|
|
[ContextMenu("Debug/Start Selected Recipe (Server Play Mode)")]
|
|
private void DebugStartSelectedRecipe() => StartSmelting();
|
|
}
|