Files
BABA_YAGA/Assets/Scripts/Trap/TrapBase.cs
2026-07-02 08:59:23 +07:00

206 lines
6.0 KiB
C#

using Fusion;
using UnityEngine;
public enum TrapState
{
Arming, // Newly placed, arming delay active
Armed, // Active and scanning for targets
Triggered, // Tripped by a target, applying effects
Despawning // Mark for cleanup/destruction
}
[RequireComponent(typeof(NetworkObject))]
public abstract class TrapBase : NetworkBehaviour
{
[Header("Trap Config")]
[SerializeField] protected TrapDataSO trapData;
[Networked] public PlayerRef Owner { get; set; }
[Networked, OnChangedRender(nameof(OnStateChanged))] public TrapState State { get; set; }
[Networked] protected TickTimer ArmingTimer { get; set; }
[Networked] protected TickTimer LifetimeTimer { get; set; }
protected Collider trapCollider;
protected Animator animator;
protected AudioSource audioSource;
public override void Spawned()
{
trapCollider = GetComponent<Collider>();
animator = GetComponentInChildren<Animator>();
audioSource = GetComponent<AudioSource>();
if (Object.HasStateAuthority)
{
State = TrapState.Arming;
ArmingTimer = TickTimer.CreateFromSeconds(Runner, trapData.ArmingDelay);
LifetimeTimer = TickTimer.CreateFromSeconds(Runner, trapData.Lifetime);
}
ConfigureVisibility();
}
public override void FixedUpdateNetwork()
{
if (!Object.HasStateAuthority) return;
// Count down the arming delay
if (State == TrapState.Arming && ArmingTimer.Expired(Runner))
{
State = TrapState.Armed;
}
// Auto-despawn when lifetime timer expires
if (LifetimeTimer.Expired(Runner))
{
DespawnTrap();
}
}
protected virtual void OnTriggerEnter(Collider other)
{
// Physics collision triggers are only processed on Server/Host (State Authority)
if (!Object.HasStateAuthority) return;
if (State != TrapState.Armed) return;
if (IsTargetValid(other.gameObject))
{
TriggerTrap(other.gameObject);
}
}
protected bool IsTargetValid(GameObject target)
{
// Verify target has a NetworkObject and check role through PlayerData
var netObj = target.GetComponent<NetworkObject>();
if (netObj != null)
{
var playerData = target.GetComponent<PlayerData>();
if (playerData != null && playerData.PlayerRole == _Role.Seeker)
{
return true;
}
}
return false;
}
protected void TriggerTrap(GameObject victim)
{
State = TrapState.Triggered;
OnTriggered(victim);
}
// Abstract methods to be implemented by specific traps
protected abstract void OnTriggered(GameObject victim);
protected virtual void ConfigureVisibility()
{
var localPlayer = Runner.LocalPlayer;
PlayerDataManager pdm = PlayerDataManager.Instance;
if (pdm != null && pdm.TryGetPlayerMetaData(localPlayer, out var meta))
{
if (meta.Role == _Role.Trapper)
{
// Trapper sees their traps clearly
gameObject.layer = LayerMask.NameToLayer("Default");
SetTrapAlpha(0.8f);
}
else
{
// Seeker: place in stealth layer if exists, otherwise disable MeshRenderers as fallback
int stealthLayer = LayerMask.NameToLayer("StealthTrap");
if (stealthLayer != -1)
{
SetLayerRecursively(gameObject, stealthLayer);
}
else
{
SetRenderersEnabled(false);
}
}
}
}
private void SetLayerRecursively(GameObject obj, int newLayer)
{
if (obj == null) return;
obj.layer = newLayer;
foreach (Transform child in obj.transform)
{
SetLayerRecursively(child.gameObject, newLayer);
}
}
private void SetTrapAlpha(float alpha)
{
Renderer[] renderers = GetComponentsInChildren<Renderer>();
foreach (var r in renderers)
{
foreach (var mat in r.materials)
{
Color col = mat.color;
col.a = alpha;
mat.color = col;
}
}
}
private void SetRenderersEnabled(bool isEnabled)
{
Renderer[] renderers = GetComponentsInChildren<Renderer>();
foreach (var r in renderers)
{
r.enabled = isEnabled;
}
}
public void OnStateChanged()
{
HandleStateVisuals();
}
protected virtual void HandleStateVisuals()
{
switch (State)
{
case TrapState.Arming:
// Play placement sound effect or visual cue locally
if (audioSource != null && trapData.PlaceSFX != null)
{
audioSource.PlayOneShot(trapData.PlaceSFX);
}
break;
case TrapState.Armed:
// Visual indicators of trap active state (e.g. flashing light)
break;
case TrapState.Triggered:
// When triggered, reveal the trap mesh to everyone
SetRenderersEnabled(true);
if (animator != null)
{
animator.SetTrigger("Trigger");
}
if (audioSource != null && trapData.TriggerSFX != null)
{
audioSource.PlayOneShot(trapData.TriggerSFX);
}
if (trapData.TriggerVFX != null)
{
Instantiate(trapData.TriggerVFX, transform.position, Quaternion.identity);
}
break;
}
}
protected void DespawnTrap()
{
if (Object != null && Object.IsValid && Object.HasStateAuthority)
{
State = TrapState.Despawning;
Runner.Despawn(Object);
}
}
}