diff --git a/Assets/Scripts/Characters/LockOnCameraStateOverrideTarget.cs b/Assets/Scripts/Characters/LockOnCameraStateOverrideTarget.cs
new file mode 100644
index 000000000..91dc2e470
--- /dev/null
+++ b/Assets/Scripts/Characters/LockOnCameraStateOverrideTarget.cs
@@ -0,0 +1,16 @@
+using UnityEngine;
+
+namespace Beyond
+{
+ ///
+ /// Marks a target that should override the lock-on camera state when close.
+ ///
+ public sealed class LockOnCameraStateOverrideTarget : MonoBehaviour
+ {
+ [Tooltip("Camera state to use while locked on this target.")]
+ public string cameraStateName = "LockOnBig";
+
+ [Tooltip("Max distance to apply the override. Set per enemy as needed.")]
+ public float maxDistance = 6f;
+ }
+}
diff --git a/Assets/Scripts/Characters/LockOnCameraStateOverrideTarget.cs.meta b/Assets/Scripts/Characters/LockOnCameraStateOverrideTarget.cs.meta
new file mode 100644
index 000000000..2c30b9d84
--- /dev/null
+++ b/Assets/Scripts/Characters/LockOnCameraStateOverrideTarget.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: d26a247418ec4c849b93fb72ca6279e0
\ No newline at end of file
diff --git a/Assets/Scripts/Effects/LockOnCameraStateOverride.cs b/Assets/Scripts/Effects/LockOnCameraStateOverride.cs
new file mode 100644
index 000000000..98c1e6cbb
--- /dev/null
+++ b/Assets/Scripts/Effects/LockOnCameraStateOverride.cs
@@ -0,0 +1,156 @@
+using UnityEngine;
+
+namespace Beyond
+{
+ ///
+ /// Overrides camera state while locked-on to specific targets.
+ ///
+ [RequireComponent(typeof(bLockOn))]
+ public sealed class LockOnCameraStateOverride : MonoBehaviour
+ {
+ private bLockOn _lockOn;
+ private bThirdPersonInput _input;
+
+ private bool _isOverriding;
+ private string _overrideState;
+ private Transform _overrideTarget;
+
+ private struct CameraStateSnapshot
+ {
+ public bool changeCameraState;
+ public string customCameraState;
+ public bool smoothCameraState;
+ }
+
+ private CameraStateSnapshot _previousState;
+
+ private void Awake()
+ {
+ _lockOn = GetComponent();
+ _input = GetComponent();
+ }
+
+ private void OnEnable()
+ {
+ if (_lockOn != null)
+ {
+ _lockOn.onLockOnTarget.AddListener(OnLockOn);
+ _lockOn.onUnLockOnTarget.AddListener(OnUnlockOn);
+ }
+ }
+
+ private void OnDisable()
+ {
+ if (_lockOn != null)
+ {
+ _lockOn.onLockOnTarget.RemoveListener(OnLockOn);
+ _lockOn.onUnLockOnTarget.RemoveListener(OnUnlockOn);
+ }
+
+ StopOverrideIfNeeded();
+ }
+
+ private void Update()
+ {
+ if (!_isOverriding)
+ {
+ return;
+ }
+
+ if (_overrideTarget == null)
+ {
+ StopOverrideIfNeeded();
+ return;
+ }
+
+ var targetOverride = _overrideTarget.GetComponent();
+ if (targetOverride == null)
+ {
+ StopOverrideIfNeeded();
+ return;
+ }
+
+ if (!IsWithinDistance(_overrideTarget, targetOverride.maxDistance))
+ {
+ StopOverrideIfNeeded();
+ }
+ }
+
+ private void OnLockOn(Transform target)
+ {
+ if (target == null || _input == null)
+ {
+ return;
+ }
+
+ var targetOverride = target.GetComponent();
+ if (targetOverride == null)
+ {
+ return;
+ }
+
+ if (!IsWithinDistance(target, targetOverride.maxDistance))
+ {
+ return;
+ }
+
+ _previousState = new CameraStateSnapshot
+ {
+ changeCameraState = _input.changeCameraState,
+ customCameraState = _input.customCameraState,
+ smoothCameraState = _input.smoothCameraState
+ };
+
+ _overrideState = targetOverride.cameraStateName;
+ _overrideTarget = target;
+
+ _input.ChangeCameraStateWithLerp(_overrideState);
+ _isOverriding = true;
+ }
+
+ private void OnUnlockOn(Transform target)
+ {
+ StopOverrideIfNeeded();
+ }
+
+ private void StopOverrideIfNeeded()
+ {
+ if (!_isOverriding || _input == null)
+ {
+ _isOverriding = false;
+ _overrideTarget = null;
+ return;
+ }
+
+ // Only restore if we're still on the override state.
+ if (_input.changeCameraState && _input.customCameraState == _overrideState)
+ {
+ if (_previousState.changeCameraState)
+ {
+ _input.changeCameraState = true;
+ _input.customCameraState = _previousState.customCameraState;
+ _input.smoothCameraState = _previousState.smoothCameraState;
+ }
+ else
+ {
+ _input.ResetCameraState();
+ }
+ }
+
+ _isOverriding = false;
+ _overrideTarget = null;
+ _overrideState = null;
+ }
+
+ private bool IsWithinDistance(Transform target, float maxDistance)
+ {
+ if (maxDistance <= 0f)
+ {
+ return true;
+ }
+
+ var delta = target.position - transform.position;
+ return delta.sqrMagnitude <= maxDistance * maxDistance;
+ }
+ }
+}
diff --git a/Assets/Scripts/Effects/LockOnCameraStateOverride.cs.meta b/Assets/Scripts/Effects/LockOnCameraStateOverride.cs.meta
new file mode 100644
index 000000000..3200e073f
--- /dev/null
+++ b/Assets/Scripts/Effects/LockOnCameraStateOverride.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 86ca4effe1f4a4b4dbd243af2bc93a29
\ No newline at end of file
diff --git a/Assets/Scripts/InvectorDerivatives/bSimpleCombatAction.cs b/Assets/Scripts/InvectorDerivatives/bSimpleCombatAction.cs
index 6f910c21f..bc8a5a249 100644
--- a/Assets/Scripts/InvectorDerivatives/bSimpleCombatAction.cs
+++ b/Assets/Scripts/InvectorDerivatives/bSimpleCombatAction.cs
@@ -12,9 +12,20 @@ namespace Beyond
{
if (controller.currentTarget.transform == null) return;
+ var summoner = controller.gameObject.GetComponent();
+ if (summoner != null && !summoner.ShouldEngageMelee())
+ {
+ controller.Stop();
+ return;
+ }
+
if (controller.targetDistance <= controller.attackDistance && !controller.isBlocking)
{
controller.Stop();
+ if (summoner != null)
+ {
+ summoner.NotifyMeleeAttack();
+ }
controller.Attack();
}
else if (!controller.animatorStateInfos.HasAnyTag("Attack", "LockMovement", "CustomAction"))
@@ -26,4 +37,4 @@ namespace Beyond
else controller.Stop();
}
}
-}
\ No newline at end of file
+}