Added RocketAssemblyStation and ReadyRocket
This commit is contained in:
@@ -23,7 +23,7 @@ public sealed class ItemCatalog : ScriptableObject
|
||||
return false;
|
||||
}
|
||||
|
||||
return itemsById.TryGetValue(itemId.Trim().ToLowerInvariant(), out definition);
|
||||
return itemsById.TryGetValue(NormalizeId(itemId), out definition);
|
||||
}
|
||||
|
||||
private void RebuildIndex()
|
||||
@@ -39,4 +39,7 @@ public sealed class ItemCatalog : ScriptableObject
|
||||
Debug.LogError($"Duplicate item id '{definition.ItemId}' in {name}.", this);
|
||||
}
|
||||
}
|
||||
|
||||
private static string NormalizeId(string value) =>
|
||||
value.Trim().ToLowerInvariant().Replace(' ', '-');
|
||||
}
|
||||
|
||||
@@ -1,33 +1,23 @@
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
|
||||
public enum ItemCategory
|
||||
{
|
||||
Scrap,
|
||||
SmeltedMaterial,
|
||||
RocketPart,
|
||||
Tool,
|
||||
Consumable
|
||||
}
|
||||
|
||||
[CreateAssetMenu(fileName = "ItemDefinition", menuName = "FORT-BO/Items/Item Definition")]
|
||||
public sealed class ItemDefinition : ScriptableObject
|
||||
public class ItemDefinition : ScriptableObject
|
||||
{
|
||||
[SerializeField] private string itemId;
|
||||
[SerializeField] private string displayName;
|
||||
[SerializeField] private ItemCategory category;
|
||||
[SerializeField] private bool isAssemblyReady;
|
||||
[SerializeField] private NetworkObject worldPrefab;
|
||||
|
||||
public string ItemId => itemId;
|
||||
public string DisplayName => string.IsNullOrWhiteSpace(displayName) ? name : displayName;
|
||||
public ItemCategory Category => category;
|
||||
public bool IsAssemblyReady => isAssemblyReady;
|
||||
public NetworkObject WorldPrefab => worldPrefab;
|
||||
|
||||
private void OnValidate()
|
||||
protected virtual void OnValidate()
|
||||
{
|
||||
itemId = itemId?.Trim().ToLowerInvariant();
|
||||
itemId = NormalizeId(itemId);
|
||||
displayName = displayName?.Trim();
|
||||
}
|
||||
|
||||
protected static string NormalizeId(string value) =>
|
||||
value?.Trim().ToLowerInvariant().Replace(' ', '-');
|
||||
}
|
||||
|
||||
@@ -10,5 +10,11 @@ public sealed class NetworkItem : NetworkBehaviour
|
||||
public ItemDefinition Definition => definition;
|
||||
public string ItemId => definition != null ? definition.ItemId : string.Empty;
|
||||
public string DisplayName => definition != null ? definition.DisplayName : name;
|
||||
public ItemCategory Category => definition != null ? definition.Category : default;
|
||||
|
||||
public bool TryGetDefinition<TDefinition>(out TDefinition typedDefinition)
|
||||
where TDefinition : ItemDefinition
|
||||
{
|
||||
typedDefinition = definition as TDefinition;
|
||||
return typedDefinition != null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1e6d4d81ab34d2e9ee3a1bfa201f100
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
using UnityEngine;
|
||||
|
||||
[CreateAssetMenu(
|
||||
fileName = "MaterialItemDefinition",
|
||||
menuName = "FORT-BO/Items/Definitions/Material")]
|
||||
public sealed class MaterialItemDefinition : ItemDefinition
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1e6d4d81ab34d2e9ee3a1bfa201f101
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,29 @@
|
||||
using UnityEngine;
|
||||
|
||||
public enum PartProcessingState : byte
|
||||
{
|
||||
Raw,
|
||||
Ready
|
||||
}
|
||||
|
||||
[CreateAssetMenu(
|
||||
fileName = "PartItemDefinition",
|
||||
menuName = "FORT-BO/Items/Definitions/Part")]
|
||||
public sealed class PartItemDefinition : ItemDefinition
|
||||
{
|
||||
[Tooltip("Data-driven part family id, for example rocket-engine or rocket-cone-standard.")]
|
||||
[SerializeField] private string partTypeId;
|
||||
[SerializeField] private PartProcessingState processingState;
|
||||
|
||||
public string PartTypeId => string.IsNullOrWhiteSpace(partTypeId)
|
||||
? ItemId
|
||||
: partTypeId;
|
||||
public PartProcessingState ProcessingState => processingState;
|
||||
public bool IsReadyForAssembly => processingState == PartProcessingState.Ready;
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
partTypeId = NormalizeId(partTypeId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1e6d4d81ab34d2e9ee3a1bfa201f102
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,22 @@
|
||||
using UnityEngine;
|
||||
|
||||
[CreateAssetMenu(
|
||||
fileName = "RocketItemDefinition",
|
||||
menuName = "FORT-BO/Items/Definitions/Rocket")]
|
||||
public sealed class RocketItemDefinition : ItemDefinition
|
||||
{
|
||||
[Tooltip("Stable rocket family id used by launchers and orders.")]
|
||||
[SerializeField] private string rocketTypeId;
|
||||
[SerializeField, Min(0.1f)] private float criticalImpactSpeed = 7f;
|
||||
|
||||
public string RocketTypeId => string.IsNullOrWhiteSpace(rocketTypeId)
|
||||
? ItemId
|
||||
: rocketTypeId;
|
||||
public float CriticalImpactSpeed => criticalImpactSpeed;
|
||||
|
||||
protected override void OnValidate()
|
||||
{
|
||||
base.OnValidate();
|
||||
rocketTypeId = NormalizeId(rocketTypeId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1e6d4d81ab34d2e9ee3a1bfa201f103
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
using UnityEngine;
|
||||
|
||||
[CreateAssetMenu(
|
||||
fileName = "UsableItemDefinition",
|
||||
menuName = "FORT-BO/Items/Definitions/Usable")]
|
||||
public sealed class UsableItemDefinition : ItemDefinition
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1e6d4d81ab34d2e9ee3a1bfa201f104
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1e6d4d81ab34d2e9ee3a1bfa201f110
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,168 @@
|
||||
using System;
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
|
||||
public enum RocketAssemblyQuality : byte
|
||||
{
|
||||
Normal,
|
||||
Defective
|
||||
}
|
||||
|
||||
public struct RocketCriticalImpactState : INetworkSerializable,
|
||||
IEquatable<RocketCriticalImpactState>
|
||||
{
|
||||
public uint Sequence;
|
||||
public Vector3 Point;
|
||||
public float Speed;
|
||||
|
||||
public RocketCriticalImpactState(uint sequence, Vector3 point, float speed)
|
||||
{
|
||||
Sequence = sequence;
|
||||
Point = point;
|
||||
Speed = speed;
|
||||
}
|
||||
|
||||
public void NetworkSerialize<T>(BufferSerializer<T> serializer)
|
||||
where T : IReaderWriter
|
||||
{
|
||||
serializer.SerializeValue(ref Sequence);
|
||||
serializer.SerializeValue(ref Point);
|
||||
serializer.SerializeValue(ref Speed);
|
||||
}
|
||||
|
||||
public bool Equals(RocketCriticalImpactState other) =>
|
||||
Sequence == other.Sequence && Point == other.Point && Speed.Equals(other.Speed);
|
||||
}
|
||||
|
||||
[DisallowMultipleComponent]
|
||||
[RequireComponent(typeof(NetworkItem), typeof(Rigidbody))]
|
||||
public sealed class NetworkRocketItem : NetworkBehaviour
|
||||
{
|
||||
[SerializeField] private NetworkItem networkItem;
|
||||
[SerializeField] private Rigidbody physicsBody;
|
||||
|
||||
private readonly NetworkVariable<RocketAssemblyQuality> assemblyQuality = new(
|
||||
RocketAssemblyQuality.Normal,
|
||||
NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server);
|
||||
|
||||
private readonly NetworkVariable<RocketCriticalImpactState> criticalImpactState = new(
|
||||
default,
|
||||
NetworkVariableReadPermission.Everyone,
|
||||
NetworkVariableWritePermission.Server);
|
||||
|
||||
public RocketItemDefinition Definition =>
|
||||
networkItem != null && networkItem.TryGetDefinition(out RocketItemDefinition definition)
|
||||
? definition
|
||||
: null;
|
||||
public string RocketTypeId => Definition != null ? Definition.RocketTypeId : string.Empty;
|
||||
public RocketAssemblyQuality AssemblyQuality => assemblyQuality.Value;
|
||||
public bool IsDefective => AssemblyQuality == RocketAssemblyQuality.Defective;
|
||||
public bool IsCriticallyDamaged => criticalImpactState.Value.Sequence > 0;
|
||||
public uint CriticalImpactSequence => criticalImpactState.Value.Sequence;
|
||||
public Vector3 LastCriticalImpactPoint => criticalImpactState.Value.Point;
|
||||
public float LastCriticalImpactSpeed => criticalImpactState.Value.Speed;
|
||||
|
||||
public event Action Changed;
|
||||
public event Action CriticalImpact;
|
||||
|
||||
private void Reset() => ResolveReferences();
|
||||
|
||||
private void Awake() => ResolveReferences();
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
assemblyQuality.OnValueChanged += HandleAssemblyQualityChanged;
|
||||
criticalImpactState.OnValueChanged += HandleCriticalImpactChanged;
|
||||
|
||||
if (Definition == null)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"{name} has NetworkRocketItem but its NetworkItem does not reference " +
|
||||
"a RocketItemDefinition.",
|
||||
this);
|
||||
}
|
||||
|
||||
Changed?.Invoke();
|
||||
}
|
||||
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
assemblyQuality.OnValueChanged -= HandleAssemblyQualityChanged;
|
||||
criticalImpactState.OnValueChanged -= HandleCriticalImpactChanged;
|
||||
}
|
||||
|
||||
private void OnCollisionEnter(Collision collision)
|
||||
{
|
||||
if (!IsServer || !IsSpawned || IsCriticallyDamaged || Definition == null)
|
||||
return;
|
||||
|
||||
float impactSpeed = collision.relativeVelocity.magnitude;
|
||||
if (impactSpeed < Definition.CriticalImpactSpeed)
|
||||
return;
|
||||
|
||||
Vector3 impactPoint = collision.contactCount > 0
|
||||
? collision.GetContact(0).point
|
||||
: transform.position;
|
||||
TryTriggerCriticalImpact(impactPoint, impactSpeed);
|
||||
}
|
||||
|
||||
public bool TryTriggerCriticalImpact(Vector3 impactPoint, float impactSpeed)
|
||||
{
|
||||
if (!IsServer || !IsSpawned || IsCriticallyDamaged || Definition == null ||
|
||||
impactSpeed < Definition.CriticalImpactSpeed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
criticalImpactState.Value = new RocketCriticalImpactState(
|
||||
criticalImpactState.Value.Sequence + 1,
|
||||
impactPoint,
|
||||
impactSpeed);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryInitializeAssemblyQuality(bool isDefectiveResult)
|
||||
{
|
||||
if (!IsServer || !IsSpawned)
|
||||
return false;
|
||||
|
||||
assemblyQuality.Value = isDefectiveResult
|
||||
? RocketAssemblyQuality.Defective
|
||||
: RocketAssemblyQuality.Normal;
|
||||
return true;
|
||||
}
|
||||
|
||||
[ContextMenu("Debug/Trigger Critical Impact (Server Play Mode)")]
|
||||
private void TriggerCriticalImpactFromContextMenu()
|
||||
{
|
||||
if (Application.isPlaying && IsServer && IsSpawned && Definition != null)
|
||||
{
|
||||
TryTriggerCriticalImpact(
|
||||
transform.position,
|
||||
Definition.CriticalImpactSpeed);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResolveReferences()
|
||||
{
|
||||
if (networkItem == null)
|
||||
networkItem = GetComponent<NetworkItem>();
|
||||
|
||||
if (physicsBody == null)
|
||||
physicsBody = GetComponent<Rigidbody>();
|
||||
}
|
||||
|
||||
private void HandleAssemblyQualityChanged(
|
||||
RocketAssemblyQuality _,
|
||||
RocketAssemblyQuality __) => Changed?.Invoke();
|
||||
|
||||
private void HandleCriticalImpactChanged(
|
||||
RocketCriticalImpactState previous,
|
||||
RocketCriticalImpactState current)
|
||||
{
|
||||
Changed?.Invoke();
|
||||
if (current.Sequence != previous.Sequence)
|
||||
CriticalImpact?.Invoke();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1e6d4d81ab34d2e9ee3a1bfa201f111
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,102 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class RocketItemView : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private NetworkRocketItem rocket;
|
||||
|
||||
[Header("State")]
|
||||
[SerializeField] private UnityEvent onNormalQualityApplied;
|
||||
[SerializeField] private UnityEvent onDefectiveQualityApplied;
|
||||
[SerializeField] private UnityEvent onDamagedStateApplied;
|
||||
|
||||
[Header("Critical Impact")]
|
||||
[SerializeField] private UnityEvent onCriticalImpact;
|
||||
[SerializeField] private UnityEvent<Vector3> onCriticalImpactPoint;
|
||||
[SerializeField] private UnityEvent<float> onCriticalImpactSpeed;
|
||||
|
||||
private uint observedImpactSequence;
|
||||
private RocketAssemblyQuality observedQuality;
|
||||
private bool hasAppliedQuality;
|
||||
private bool hasAppliedDamagedState;
|
||||
|
||||
private void Reset() => rocket = GetComponentInParent<NetworkRocketItem>();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (rocket == null)
|
||||
rocket = GetComponentInParent<NetworkRocketItem>();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (rocket == null)
|
||||
return;
|
||||
|
||||
observedImpactSequence = rocket.CriticalImpactSequence;
|
||||
observedQuality = rocket.AssemblyQuality;
|
||||
hasAppliedQuality = false;
|
||||
hasAppliedDamagedState = false;
|
||||
rocket.Changed += ApplyState;
|
||||
rocket.CriticalImpact += HandleCriticalImpact;
|
||||
ApplyState();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (rocket == null)
|
||||
return;
|
||||
|
||||
rocket.Changed -= ApplyState;
|
||||
rocket.CriticalImpact -= HandleCriticalImpact;
|
||||
}
|
||||
|
||||
[ContextMenu("Preview/Critical Impact")]
|
||||
private void PreviewCriticalImpact()
|
||||
{
|
||||
onCriticalImpactPoint?.Invoke(transform.position);
|
||||
onCriticalImpactSpeed?.Invoke(7f);
|
||||
onCriticalImpact?.Invoke();
|
||||
onDamagedStateApplied?.Invoke();
|
||||
}
|
||||
|
||||
[ContextMenu("Preview/Normal Quality")]
|
||||
private void PreviewNormalQuality() => onNormalQualityApplied?.Invoke();
|
||||
|
||||
[ContextMenu("Preview/Defective Quality")]
|
||||
private void PreviewDefectiveQuality() => onDefectiveQualityApplied?.Invoke();
|
||||
|
||||
private void ApplyState()
|
||||
{
|
||||
if (rocket == null)
|
||||
return;
|
||||
|
||||
if (!hasAppliedQuality || observedQuality != rocket.AssemblyQuality)
|
||||
{
|
||||
observedQuality = rocket.AssemblyQuality;
|
||||
hasAppliedQuality = true;
|
||||
if (observedQuality == RocketAssemblyQuality.Defective)
|
||||
onDefectiveQualityApplied?.Invoke();
|
||||
else
|
||||
onNormalQualityApplied?.Invoke();
|
||||
}
|
||||
|
||||
if (rocket.IsCriticallyDamaged && !hasAppliedDamagedState)
|
||||
{
|
||||
hasAppliedDamagedState = true;
|
||||
onDamagedStateApplied?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleCriticalImpact()
|
||||
{
|
||||
if (rocket == null || rocket.CriticalImpactSequence == observedImpactSequence)
|
||||
return;
|
||||
|
||||
observedImpactSequence = rocket.CriticalImpactSequence;
|
||||
onCriticalImpactPoint?.Invoke(rocket.LastCriticalImpactPoint);
|
||||
onCriticalImpactSpeed?.Invoke(rocket.LastCriticalImpactSpeed);
|
||||
onCriticalImpact?.Invoke();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1e6d4d81ab34d2e9ee3a1bfa201f112
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1e6d4d81ab34d2e9ee3a1bfa201f130
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,6 @@
|
||||
using Unity.Netcode;
|
||||
|
||||
public interface IHandItemUseAction
|
||||
{
|
||||
bool TryUseOnServer(NetworkObject playerObject);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1e6d4d81ab34d2e9ee3a1bfa201f131
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,49 @@
|
||||
using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
|
||||
[DisallowMultipleComponent]
|
||||
public sealed class NetworkUsableHandItemInteractable : NetworkHandItemInteractable
|
||||
{
|
||||
[Tooltip("Component on this prefab implementing IHandItemUseAction.")]
|
||||
[SerializeField] private MonoBehaviour useActionComponent;
|
||||
|
||||
private IHandItemUseAction UseAction => useActionComponent as IHandItemUseAction;
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
ResolveUseAction();
|
||||
}
|
||||
|
||||
private void OnValidate() => ResolveUseAction();
|
||||
|
||||
protected override void UseOnServer(NetworkObject playerObject)
|
||||
{
|
||||
if (UseAction == null)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"{name} was used but has no IHandItemUseAction component assigned.",
|
||||
this);
|
||||
return;
|
||||
}
|
||||
|
||||
UseAction.TryUseOnServer(playerObject);
|
||||
}
|
||||
|
||||
private void ResolveUseAction()
|
||||
{
|
||||
if (useActionComponent is IHandItemUseAction)
|
||||
return;
|
||||
|
||||
foreach (MonoBehaviour component in GetComponents<MonoBehaviour>())
|
||||
{
|
||||
if (component is IHandItemUseAction)
|
||||
{
|
||||
useActionComponent = component;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
useActionComponent = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1e6d4d81ab34d2e9ee3a1bfa201f132
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user