Added new stanok
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 1d3dd768e3784bc6bc270a1ce57ce345
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
[CreateAssetMenu(
|
||||||
|
fileName = "ItemProcessingRecipe",
|
||||||
|
menuName = "FORT-BO/Workstations/Item Processing Recipe")]
|
||||||
|
public sealed class ItemProcessingRecipe : ScriptableObject
|
||||||
|
{
|
||||||
|
[SerializeField] private string recipeId;
|
||||||
|
[SerializeField] private ItemDefinition inputItem;
|
||||||
|
[SerializeField] private ItemDefinition outputItem;
|
||||||
|
[SerializeField, Min(0.05f)] private float processingDuration = 3f;
|
||||||
|
|
||||||
|
public string RecipeId => recipeId;
|
||||||
|
public ItemDefinition InputItem => inputItem;
|
||||||
|
public ItemDefinition OutputItem => outputItem;
|
||||||
|
public float ProcessingDuration => processingDuration;
|
||||||
|
|
||||||
|
public bool Accepts(NetworkItem item)
|
||||||
|
{
|
||||||
|
if (item == null || item.Definition == null || inputItem == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return item.Definition == inputItem ||
|
||||||
|
(!string.IsNullOrEmpty(inputItem.ItemId) && item.ItemId == inputItem.ItemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnValidate() =>
|
||||||
|
recipeId = recipeId?.Trim().ToLowerInvariant();
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: ea07d1502f6f4abc96ea949c7e688857
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Events;
|
||||||
|
|
||||||
|
[DisallowMultipleComponent]
|
||||||
|
public class ItemProcessingWorkstationView : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] private NetworkItemProcessingWorkstation workstation;
|
||||||
|
|
||||||
|
[Header("State Events")]
|
||||||
|
[SerializeField] private UnityEvent onEmpty;
|
||||||
|
[SerializeField] private UnityEvent onProcessingStarted;
|
||||||
|
[SerializeField] private UnityEvent onOutputReady;
|
||||||
|
|
||||||
|
[Header("Continuous Progress")]
|
||||||
|
[SerializeField] private UnityEvent<float> onProcessingProgress;
|
||||||
|
|
||||||
|
private ItemProcessingWorkstationState previousState;
|
||||||
|
private bool hasAppliedState;
|
||||||
|
|
||||||
|
public NetworkItemProcessingWorkstation Workstation => workstation;
|
||||||
|
|
||||||
|
protected virtual void Reset() =>
|
||||||
|
workstation = GetComponentInParent<NetworkItemProcessingWorkstation>();
|
||||||
|
|
||||||
|
protected virtual void Awake()
|
||||||
|
{
|
||||||
|
if (workstation == null)
|
||||||
|
workstation = GetComponentInParent<NetworkItemProcessingWorkstation>();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnEnable()
|
||||||
|
{
|
||||||
|
if (workstation != null)
|
||||||
|
{
|
||||||
|
workstation.Changed += HandleWorkstationChanged;
|
||||||
|
ApplyState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Start() => ApplyState();
|
||||||
|
|
||||||
|
protected virtual void OnDisable()
|
||||||
|
{
|
||||||
|
if (workstation != null)
|
||||||
|
workstation.Changed -= HandleWorkstationChanged;
|
||||||
|
|
||||||
|
hasAppliedState = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Update()
|
||||||
|
{
|
||||||
|
if (workstation != null &&
|
||||||
|
workstation.State == ItemProcessingWorkstationState.Processing)
|
||||||
|
{
|
||||||
|
onProcessingProgress?.Invoke(workstation.ProcessProgress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void HandleStateApplied(ItemProcessingWorkstationState state)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
[ContextMenu("Preview/Empty")]
|
||||||
|
private void PreviewEmpty()
|
||||||
|
{
|
||||||
|
onProcessingProgress?.Invoke(0f);
|
||||||
|
onEmpty?.Invoke();
|
||||||
|
HandleStateApplied(ItemProcessingWorkstationState.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ContextMenu("Preview/Processing Started")]
|
||||||
|
private void PreviewProcessingStarted()
|
||||||
|
{
|
||||||
|
onProcessingProgress?.Invoke(0f);
|
||||||
|
onProcessingStarted?.Invoke();
|
||||||
|
HandleStateApplied(ItemProcessingWorkstationState.Processing);
|
||||||
|
}
|
||||||
|
|
||||||
|
[ContextMenu("Preview/Processing 50 Percent")]
|
||||||
|
private void PreviewProcessingHalfway() => onProcessingProgress?.Invoke(0.5f);
|
||||||
|
|
||||||
|
[ContextMenu("Preview/Output Ready")]
|
||||||
|
private void PreviewOutputReady()
|
||||||
|
{
|
||||||
|
onProcessingProgress?.Invoke(1f);
|
||||||
|
onOutputReady?.Invoke();
|
||||||
|
HandleStateApplied(ItemProcessingWorkstationState.OutputReady);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleWorkstationChanged() => ApplyState();
|
||||||
|
|
||||||
|
private void ApplyState()
|
||||||
|
{
|
||||||
|
if (workstation == null || !workstation.IsSpawned)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ItemProcessingWorkstationState currentState = workstation.State;
|
||||||
|
if (hasAppliedState && currentState == previousState)
|
||||||
|
return;
|
||||||
|
|
||||||
|
previousState = currentState;
|
||||||
|
hasAppliedState = true;
|
||||||
|
|
||||||
|
switch (currentState)
|
||||||
|
{
|
||||||
|
case ItemProcessingWorkstationState.Processing:
|
||||||
|
onProcessingStarted?.Invoke();
|
||||||
|
onProcessingProgress?.Invoke(workstation.ProcessProgress);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ItemProcessingWorkstationState.OutputReady:
|
||||||
|
onProcessingProgress?.Invoke(1f);
|
||||||
|
onOutputReady?.Invoke();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
onProcessingProgress?.Invoke(0f);
|
||||||
|
onEmpty?.Invoke();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
HandleStateApplied(currentState);
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: fddbd9f473234f27b85054ee55b89f69
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
[DisallowMultipleComponent]
|
||||||
|
[RequireComponent(typeof(Collider))]
|
||||||
|
public sealed class NetworkItemInputSlot : MonoBehaviour
|
||||||
|
{
|
||||||
|
[SerializeField] private NetworkItemProcessingWorkstation workstation;
|
||||||
|
|
||||||
|
private readonly Dictionary<NetworkItem, HashSet<Collider>> overlaps = new();
|
||||||
|
|
||||||
|
private void Reset()
|
||||||
|
{
|
||||||
|
workstation = GetComponentInParent<NetworkItemProcessingWorkstation>();
|
||||||
|
GetComponent<Collider>().isTrigger = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Awake()
|
||||||
|
{
|
||||||
|
if (workstation == null)
|
||||||
|
workstation = GetComponentInParent<NetworkItemProcessingWorkstation>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDisable() => overlaps.Clear();
|
||||||
|
|
||||||
|
private void OnTriggerEnter(Collider other)
|
||||||
|
{
|
||||||
|
RegisterOverlap(other);
|
||||||
|
TryAccept(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTriggerStay(Collider other)
|
||||||
|
{
|
||||||
|
RegisterOverlap(other);
|
||||||
|
TryAccept(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTriggerExit(Collider other)
|
||||||
|
{
|
||||||
|
if (workstation == null || !workstation.IsServer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
NetworkItem item = other.GetComponentInParent<NetworkItem>();
|
||||||
|
if (item == null || !overlaps.TryGetValue(item, out HashSet<Collider> colliders))
|
||||||
|
return;
|
||||||
|
|
||||||
|
colliders.Remove(other);
|
||||||
|
if (colliders.Count > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
overlaps.Remove(item);
|
||||||
|
workstation.NotifyItemExited(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TryAccept(Collider other)
|
||||||
|
{
|
||||||
|
if (workstation == null || !workstation.IsServer || !workstation.IsSpawned)
|
||||||
|
return;
|
||||||
|
|
||||||
|
NetworkItem item = other.GetComponentInParent<NetworkItem>();
|
||||||
|
if (item != null)
|
||||||
|
workstation.TryAcceptItem(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegisterOverlap(Collider other)
|
||||||
|
{
|
||||||
|
NetworkItem item = other.GetComponentInParent<NetworkItem>();
|
||||||
|
if (item == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!overlaps.TryGetValue(item, out HashSet<Collider> colliders))
|
||||||
|
{
|
||||||
|
colliders = new HashSet<Collider>();
|
||||||
|
overlaps.Add(item, colliders);
|
||||||
|
}
|
||||||
|
|
||||||
|
colliders.Add(other);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 67504197a13e4569ab17c8dcefaf8293
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
+236
@@ -0,0 +1,236 @@
|
|||||||
|
using System;
|
||||||
|
using Unity.Netcode;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public enum ItemProcessingWorkstationState : byte
|
||||||
|
{
|
||||||
|
Empty,
|
||||||
|
Processing,
|
||||||
|
OutputReady
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class NetworkItemProcessingWorkstation : NetworkWorkstationController
|
||||||
|
{
|
||||||
|
[Header("Recipe")]
|
||||||
|
[SerializeField] private ItemProcessingRecipe recipe;
|
||||||
|
|
||||||
|
[Header("Slot")]
|
||||||
|
[SerializeField] private Transform inputPoint;
|
||||||
|
|
||||||
|
private readonly NetworkVariable<ItemProcessingWorkstationState> state = new(
|
||||||
|
ItemProcessingWorkstationState.Empty,
|
||||||
|
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);
|
||||||
|
|
||||||
|
private NetworkItem currentItem;
|
||||||
|
|
||||||
|
public override bool IsOperating => State == ItemProcessingWorkstationState.Processing;
|
||||||
|
public ItemProcessingWorkstationState State => state.Value;
|
||||||
|
public ItemProcessingRecipe Recipe => recipe;
|
||||||
|
public NetworkItem CurrentItem => currentItem;
|
||||||
|
public bool CanAcceptItem => IsServer && IsSpawned &&
|
||||||
|
State == ItemProcessingWorkstationState.Empty &&
|
||||||
|
recipe != null && recipe.InputItem != null &&
|
||||||
|
recipe.OutputItem != null && recipe.OutputItem.WorldPrefab != null;
|
||||||
|
|
||||||
|
public float ProcessProgress
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (State == ItemProcessingWorkstationState.OutputReady)
|
||||||
|
return 1f;
|
||||||
|
|
||||||
|
if (!IsOperating)
|
||||||
|
return 0f;
|
||||||
|
|
||||||
|
double duration = processEndsAt.Value - processStartedAt.Value;
|
||||||
|
if (duration <= 0d)
|
||||||
|
return 1f;
|
||||||
|
|
||||||
|
return Mathf.Clamp01((float)((GetNetworkTime() - processStartedAt.Value) / duration));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float RemainingSeconds => !IsOperating
|
||||||
|
? 0f
|
||||||
|
: Mathf.Max(0f, (float)(processEndsAt.Value - GetNetworkTime()));
|
||||||
|
|
||||||
|
public event Action Changed;
|
||||||
|
|
||||||
|
public override void OnNetworkSpawn()
|
||||||
|
{
|
||||||
|
state.OnValueChanged += HandleStateChanged;
|
||||||
|
processStartedAt.OnValueChanged += HandleProcessTimeChanged;
|
||||||
|
processEndsAt.OnValueChanged += HandleProcessTimeChanged;
|
||||||
|
|
||||||
|
if (IsServer)
|
||||||
|
ResetWorkstationOnServer();
|
||||||
|
|
||||||
|
Changed?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnNetworkDespawn()
|
||||||
|
{
|
||||||
|
state.OnValueChanged -= HandleStateChanged;
|
||||||
|
processStartedAt.OnValueChanged -= HandleProcessTimeChanged;
|
||||||
|
processEndsAt.OnValueChanged -= HandleProcessTimeChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update()
|
||||||
|
{
|
||||||
|
if (!IsServer || !IsSpawned)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (State == ItemProcessingWorkstationState.Processing)
|
||||||
|
{
|
||||||
|
if (currentItem == null || !currentItem.IsSpawned)
|
||||||
|
{
|
||||||
|
ResetWorkstationOnServer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GetNetworkTime() >= processEndsAt.Value)
|
||||||
|
CompleteProcessingOnServer();
|
||||||
|
}
|
||||||
|
else if (State == ItemProcessingWorkstationState.OutputReady &&
|
||||||
|
(currentItem == null || !currentItem.IsSpawned))
|
||||||
|
{
|
||||||
|
ResetWorkstationOnServer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LateUpdate()
|
||||||
|
{
|
||||||
|
if (!IsServer || !IsSpawned ||
|
||||||
|
State != ItemProcessingWorkstationState.Processing ||
|
||||||
|
currentItem == null || inputPoint == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SnapToInputPoint(currentItem.transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryAcceptItem(NetworkItem item)
|
||||||
|
{
|
||||||
|
if (!CanAcceptItem || item == null || !item.IsSpawned || !recipe.Accepts(item))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
NetworkCarryableInteractable carryable =
|
||||||
|
item.GetComponent<NetworkCarryableInteractable>();
|
||||||
|
if (carryable == null || !carryable.TryAcquireExternalControl())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
currentItem = item;
|
||||||
|
SnapToInputPoint(item.transform);
|
||||||
|
|
||||||
|
double now = GetNetworkTime();
|
||||||
|
processStartedAt.Value = now;
|
||||||
|
processEndsAt.Value = now + recipe.ProcessingDuration;
|
||||||
|
state.Value = ItemProcessingWorkstationState.Processing;
|
||||||
|
Changed?.Invoke();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void NotifyItemExited(NetworkItem item)
|
||||||
|
{
|
||||||
|
if (!IsServer || !IsSpawned ||
|
||||||
|
State != ItemProcessingWorkstationState.OutputReady ||
|
||||||
|
item == null || item != currentItem)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResetWorkstationOnServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CompleteProcessingOnServer()
|
||||||
|
{
|
||||||
|
if (currentItem == null || recipe == null ||
|
||||||
|
recipe.OutputItem == null || recipe.OutputItem.WorldPrefab == null)
|
||||||
|
{
|
||||||
|
ResetWorkstationOnServer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Transform slot = inputPoint != null ? inputPoint : transform;
|
||||||
|
Vector3 outputPosition = slot.position;
|
||||||
|
Quaternion outputRotation = slot.rotation;
|
||||||
|
|
||||||
|
NetworkObject inputObject = currentItem.NetworkObject;
|
||||||
|
currentItem = null;
|
||||||
|
|
||||||
|
if (inputObject != null && inputObject.IsSpawned)
|
||||||
|
inputObject.Despawn(true);
|
||||||
|
|
||||||
|
NetworkObject outputObject = Instantiate(
|
||||||
|
recipe.OutputItem.WorldPrefab,
|
||||||
|
outputPosition,
|
||||||
|
outputRotation);
|
||||||
|
outputObject.Spawn();
|
||||||
|
|
||||||
|
currentItem = outputObject.GetComponent<NetworkItem>();
|
||||||
|
if (currentItem == null ||
|
||||||
|
outputObject.GetComponent<NetworkCarryableInteractable>() == null)
|
||||||
|
{
|
||||||
|
Debug.LogError(
|
||||||
|
$"{name} cannot produce '{recipe.OutputItem.DisplayName}': " +
|
||||||
|
"the output prefab must contain NetworkItem and " +
|
||||||
|
"NetworkCarryableInteractable.",
|
||||||
|
this);
|
||||||
|
outputObject.Despawn(true);
|
||||||
|
currentItem = null;
|
||||||
|
ResetWorkstationOnServer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
processStartedAt.Value = 0d;
|
||||||
|
processEndsAt.Value = 0d;
|
||||||
|
state.Value = ItemProcessingWorkstationState.OutputReady;
|
||||||
|
Changed?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResetWorkstationOnServer()
|
||||||
|
{
|
||||||
|
if (!IsServer)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (State == ItemProcessingWorkstationState.Processing && currentItem != null &&
|
||||||
|
currentItem.TryGetComponent(out NetworkCarryableInteractable carryable))
|
||||||
|
{
|
||||||
|
carryable.TryReleaseExternalControl();
|
||||||
|
}
|
||||||
|
|
||||||
|
currentItem = null;
|
||||||
|
processStartedAt.Value = 0d;
|
||||||
|
processEndsAt.Value = 0d;
|
||||||
|
state.Value = ItemProcessingWorkstationState.Empty;
|
||||||
|
Changed?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SnapToInputPoint(Transform itemTransform)
|
||||||
|
{
|
||||||
|
Transform slot = inputPoint != null ? inputPoint : transform;
|
||||||
|
itemTransform.SetPositionAndRotation(slot.position, slot.rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double GetNetworkTime() => NetworkManager != null
|
||||||
|
? NetworkManager.ServerTime.Time
|
||||||
|
: Time.timeAsDouble;
|
||||||
|
|
||||||
|
private void HandleStateChanged(
|
||||||
|
ItemProcessingWorkstationState _,
|
||||||
|
ItemProcessingWorkstationState __) => Changed?.Invoke();
|
||||||
|
|
||||||
|
private void HandleProcessTimeChanged(double _, double __) => Changed?.Invoke();
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 90c3cd35b32140ac86dec106c4bb5035
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Reference in New Issue
Block a user