using System; using System.Threading; using Animancer; using BITKit; using BITKit.Entities; using BITKit.Mod; using Cysharp.Threading.Tasks; using Net.Project.B.Dialogue; using Net.Project.B.Emoji; using Net.Project.B.Health; using NodeCanvas.Framework; using Project.B.Animation; using Project.B.CharacterController; using Unity.Mathematics; using UnityEngine; using UnityEngine.AI; using Random = UnityEngine.Random; namespace Net.Project.B.AI { public enum NpcAnimationState { } public class NpcAnimancerController:IDisposable { private readonly IHealthComponent _healthComponent; private readonly IFixedTicker _ticker; private readonly IBlackboard _blackboard; private readonly IHealthService _healthService; private readonly Transform _transform; private readonly NavMeshAgent _navMeshAgent; private readonly ICharacterController _characterController; private readonly IHumanoidAnimationFactory _humanoidAnimationFactory; private readonly IHumanoidTransitionAnimationFactory _transitionFactory; private readonly IDialogueService _dialogueService; private readonly AnimancerComponent _animancerComponent; private readonly IEntity _entity; private readonly IWrapper _idleWrapper; private readonly IWrapper _walkWrapper; private readonly IWrapper _idleToWalkWrapper; private readonly IWrapper _walkToIdleWrapper; private readonly IWrapper _idleTurnWrapper; private readonly IWrapper _hitAnimation; public CancellationTokenSource StateChangeCancellationTokenSource { get;private set; } private Vector3 TargetPosition => _blackboard.GetVariableValue("target_position"); private bool _isDisposed; public NpcAnimancerController(AnimancerComponent animancerComponent, IDialogueService dialogueService, ICharacterController characterController, IEntity entity, IHumanoidTransitionAnimationFactory transitionFactory, NavMeshAgent navMeshAgent, Transform transform, IFixedTicker ticker, IBlackboard blackboard, IHumanoidAnimationFactory humanoidAnimationFactory, IHealthService healthService, IHealthComponent healthComponent) { _animancerComponent = animancerComponent; _dialogueService = dialogueService; _characterController = characterController; _entity = entity; _transitionFactory = transitionFactory; _navMeshAgent = navMeshAgent; _transform = transform; _ticker = ticker; _blackboard = blackboard; _humanoidAnimationFactory = humanoidAnimationFactory; _healthService = healthService; _healthComponent = healthComponent; _dialogueService.OnDialogueStart += OnDialogueStartAsync; _dialogueService.OnDynamicDialogueStart += OnDialogueStart; _characterController.OnStateChanged += OnStateChanged; _idleWrapper = _humanoidAnimationFactory.CreateIdleAnimation(); _walkWrapper = _humanoidAnimationFactory.CreateWalkAnimation(); _idleToWalkWrapper = _transitionFactory.CreateIdleToWalk(); _walkToIdleWrapper = _transitionFactory.CreateWalkToIdle(); _idleTurnWrapper = _transitionFactory.CreateIdleTurn(); _hitAnimation = _humanoidAnimationFactory.CreateHitAnimation(); _ticker.Add(OnTick); _healthComponent.OnHealthChanged += OnHealthChanged; } private CancellationTokenSource _hitCancellationTokenSource; private async void OnHealthChanged(int prevHp, int newHp) { _hitCancellationTokenSource?.Cancel(); _hitCancellationTokenSource = new CancellationTokenSource(); if(newHp>=0)return; var layer = _animancerComponent.Layers[2]; layer.IsAdditive = true; try { var task = layer.Play(_hitAnimation.Obj as AnimancerState); task.Time = 0; _hitAnimation.Value = Random.insideUnitCircle; await task.WithCancellation(_hitCancellationTokenSource.Token); if (_animancerComponent) layer.Stop(); _hitCancellationTokenSource?.Cancel(); } catch (OperationCanceledException) { } } private void OnTick(float obj) { if(!_transform.gameObject.activeInHierarchy)return; if (StateChangeCancellationTokenSource is not { IsCancellationRequested: true }) return; switch (_characterController.CurrentState) { case ICharacterStateWalk: { if (_navMeshAgent.remainingDistance > _navMeshAgent.stoppingDistance) { var corner = _navMeshAgent.path.corners; if(corner.Length < 2)break; var position = corner[1]; var dir = _transform.InverseTransformPoint(position)*8; if (Vector3.Distance(position, _transform.position) > 1) { _walkWrapper.Value = new(dir.x, dir.z); } else { _walkWrapper.Value = new(0, 1); } } } break; } } private async void OnStateChanged(ICharacterState arg1, ICharacterState arg2) { StateChangeCancellationTokenSource?.Cancel(); if(_isDisposed)return; if(!_animancerComponent)return; StateChangeCancellationTokenSource = new(); switch (arg1,arg2) { case (ICharacterStateWalk, ICharacterStateIdle): { try { _characterController.AllowMovement.AddDisableElements(this); if (_walkToIdleWrapper is not null) { await _animancerComponent.Play(_walkToIdleWrapper.Obj as AnimancerState, 0.2f).WithCancellation(StateChangeCancellationTokenSource.Token); } if (_idleTurnWrapper is not null) { var dir = _transform.InverseTransformPoint(TargetPosition).normalized; _idleTurnWrapper.Value = new (dir.x, dir.z); await _animancerComponent.Play(_idleTurnWrapper.Obj as AnimancerState, 0.2f).WithCancellation(StateChangeCancellationTokenSource.Token); } } catch (OperationCanceledException) { _characterController.AllowMovement.RemoveDisableElements(this); return; } _characterController.AllowMovement.RemoveDisableElements(this); _animancerComponent.Play(_idleWrapper.Obj as AnimancerState, 0.2f); } break; case (ICharacterStateIdle, ICharacterStateWalk): { var corners = _navMeshAgent.path.corners; if (corners.Length < 2) { _walkWrapper.Value = new(0, 1); _animancerComponent.Play(_walkWrapper.Obj as AnimancerState, 0.2f); break; } var direction = corners[1]; direction = Vector3.ProjectOnPlane(_transform.InverseTransformPoint(direction), Vector3.up).normalized*8 ; _idleToWalkWrapper.Value = new(direction.x, direction.z); try { var walkState = _animancerComponent.Play(_idleToWalkWrapper.Obj as AnimancerState, 0.2f); walkState.Time = 0.2f; await walkState.WithCancellation(StateChangeCancellationTokenSource.Token); } catch (OperationCanceledException) { return; } _walkWrapper.Value = new(0, 1); _animancerComponent.Play(_walkWrapper.Obj as AnimancerState, 0.2f); } break; case (_,ICharacterStateIdle): { _animancerComponent.Play(_idleWrapper.Obj as AnimancerState, 0.2f); } break; case (_,ICharacterStateWalk): { _animancerComponent.Play(_walkWrapper.Obj as AnimancerState, 0.2f); _walkWrapper.Value = new(0, 1); } break; } StateChangeCancellationTokenSource?.Cancel(); } private void OnDialogueStart(IDialogueData arg) { OnDialogueStartAsync(arg).Forget(); } private async UniTask OnDialogueStartAsync(IDialogueData arg) { if (arg.ActorIdentity != _entity.Id) return; foreach (var scriptableEmoji in await ModService.LoadAssets("dialogue")) { _characterController.TransitionState(); var emoji = scriptableEmoji.GetEmojis.Random(); var layer = _animancerComponent.Layers[_animancerComponent.Layers.Count + 1]; await layer.Play(emoji.Clip,0.2f); if (_animancerComponent) { layer.StartFade(0); _characterController.TransitionState(); } return; } } public void Dispose() { _isDisposed = true; StateChangeCancellationTokenSource.Cancel(); _ticker.Remove(OnTick); _dialogueService.OnDialogueStart -= OnDialogueStartAsync; _dialogueService.OnDynamicDialogueStart -= OnDialogueStart; _characterController.OnStateChanged -= OnStateChanged; } } }