using System.Collections.Generic;
using UnityEngine;
using Ashwild.Inventory;
namespace Ashwild.Player
{
///
/// Keeps a rig's Animator loaded with the clips of whatever item the player is holding. It owns
/// exactly one concern — *which clips are in the controller right now* — while PlayerAnimatorDriver
/// owns *which parameters are set*. Splitting them is what lets a new weapon ship without touching
/// either: the driver keeps firing the same "Attack" trigger, the binder just points that slot at a
/// different clip.
///
/// It never assigns a fresh controller per item. Assigning runtimeAnimatorController rebinds the
/// Animator, which resets the state machine to Entry and wipes the parameters — the player would
/// visibly snap to idle every time he scrolled the hotbar. Instead one AnimatorOverrideController is
/// built in Awake and only its overrides are mutated afterwards, which leaves the current state and
/// all parameters untouched. That same property is what keeps FishNet's NetworkAnimator working on
/// the third-person body: an override controller preserves the base controller's parameter list, so
/// replication never sees the swap.
///
[DisallowMultipleComponent]
public class PlayerAnimationSetBinder : MonoBehaviour
{
#region Serialized Fields
[Header("Target")]
[SerializeField] private Animator animator;
[Tooltip("Which rig this binder drives — it picks the matching set off the held ItemData.")]
[SerializeField] private PlayerAnimationRig rig = PlayerAnimationRig.Arms;
[Header("Sets")]
[Tooltip("Set used with bare hands, and as the fallback for any slot the held item leaves empty.")]
[SerializeField] private PlayerAnimationSet defaultSet;
#endregion
#region State
private AnimatorOverrideController overrideController;
private AnimationClip[] placeholders;
private readonly List> overrides = new List>();
private PlayerAnimationSet currentSet;
private bool ready;
#endregion
#region Unity Lifecycle
///
/// Wraps the authored controller in a single override controller and caches its placeholder
/// clips, whose names are the slot keys every set is matched against. Done in Awake so the
/// rebind happens once, before any driver seeds a parameter.
///
private void Awake()
{
if (animator == null)
{
Debug.LogError($"[PlayerAnimationSetBinder] '{name}' has no Animator assigned — clips will never swap.", this);
return;
}
RuntimeAnimatorController master = animator.runtimeAnimatorController;
if (master == null)
{
Debug.LogError($"[PlayerAnimationSetBinder] '{name}' — the Animator has no controller assigned.", this);
return;
}
overrideController = new AnimatorOverrideController(master) { name = $"{master.name} (Runtime)" };
overrideController.GetOverrides(overrides);
placeholders = new AnimationClip[overrides.Count];
for (int i = 0; i < overrides.Count; i++)
placeholders[i] = overrides[i].Key;
animator.runtimeAnimatorController = overrideController;
ready = true;
ApplySet(defaultSet);
}
///
/// Subscribes to the held-item bus. HotbarController re-raises HeldItemChanged on its own first
/// resolve, so a rig enabled after the player already drew an item still lands on the right set.
///
private void OnEnable()
{
PlayerEvents.HeldItemChanged += HandleHeldItemChanged;
}
///
/// Unsubscribes — mirrors OnEnable exactly.
///
private void OnDisable()
{
PlayerEvents.HeldItemChanged -= HandleHeldItemChanged;
}
#endregion
#region Event Handlers
///
/// Loads the set the newly drawn item authors for this rig (or the bare-hand set when the player
/// empties his hands).
///
private void HandleHeldItemChanged(ItemData item)
{
ApplySet(item != null ? item.GetAnimationSet(rig) : defaultSet);
}
#endregion
#region Internal Helpers
///
/// Rewrites every slot in one ApplyOverrides call rather than one indexer assignment per clip,
/// because each individual assignment forces the controller to regenerate. Skips redundant work
/// when the set has not actually changed — several items can legitimately share one set.
///
private void ApplySet(PlayerAnimationSet set)
{
if (!ready) return;
if (set == currentSet) return;
currentSet = set;
for (int i = 0; i < placeholders.Length; i++)
{
AnimationClip placeholder = placeholders[i];
overrides[i] = new KeyValuePair(placeholder, Resolve(placeholder.name, set));
}
overrideController.ApplyOverrides(overrides);
}
///
/// Picks the clip for a slot: the held item's set first, then the default set, then null — which
/// means "no override" and leaves the master controller's own placeholder playing. That last
/// step is the safety net: a slot nobody authored still animates instead of freezing the rig.
///
private AnimationClip Resolve(string slotName, PlayerAnimationSet set)
{
AnimationClip clip = set != null ? set.GetClip(slotName) : null;
if (clip == null && defaultSet != null) clip = defaultSet.GetClip(slotName);
return clip;
}
#endregion
}
}