Added Furnance and smelting
This commit is contained in:
@@ -0,0 +1,291 @@
|
||||
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();
|
||||
}
|
||||
Reference in New Issue
Block a user