Added new stanok

This commit is contained in:
2026-08-13 08:33:12 +03:00
parent badcd41077
commit f41e14971a
9 changed files with 520 additions and 0 deletions
@@ -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);
}
}
@@ -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:
@@ -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();
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 90c3cd35b32140ac86dec106c4bb5035
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: