350 lines
13 KiB
C#
350 lines
13 KiB
C#
![]() |
using System;
|
||
|
using System.Collections;
|
||
|
using System.Collections.Concurrent;
|
||
|
using System.Collections.Generic;
|
||
|
using System.Linq;
|
||
|
using System.Text;
|
||
|
using BITKit;
|
||
|
using BITKit.Entities;
|
||
|
using BITKit.UX.Hotkey;
|
||
|
using Lightbug.CharacterControllerPro.Core;
|
||
|
using Microsoft.Extensions.DependencyInjection;
|
||
|
using Microsoft.Extensions.Logging;
|
||
|
using Net.Project.B.Health;
|
||
|
using Net.Project.B.Mark;
|
||
|
using NodeCanvas.Framework;
|
||
|
using Project.B.CharacterController;
|
||
|
using Project.B.Player;
|
||
|
using UnityEngine;
|
||
|
using UnityEngine.Animations;
|
||
|
using UnityEngine.InputSystem;
|
||
|
using UnityEngine.InputSystem.Interactions;
|
||
|
using Object = UnityEngine.Object;
|
||
|
|
||
|
namespace Net.Project.B.Interaction
|
||
|
{
|
||
|
public class PlayerInteractionController:IDisposable,IHotkeyCollection
|
||
|
{
|
||
|
|
||
|
private Transform CameraTransform => _cameraTransform ? _cameraTransform : _cameraTransform = Camera.main!.transform;
|
||
|
private Transform _cameraTransform;
|
||
|
|
||
|
private readonly Transform _transform;
|
||
|
|
||
|
private readonly IMarkService _markService;
|
||
|
private readonly IEntitiesService _entitiesService;
|
||
|
private readonly IKnockedService _knockedService;
|
||
|
private readonly IEntity _entity;
|
||
|
private readonly ILogger<PlayerInteractionController> _logger;
|
||
|
private readonly IWorldInteractionService _interactionService;
|
||
|
private readonly InputActionGroup _inputActionGroup;
|
||
|
private readonly IBlackboard _blackBoard;
|
||
|
private readonly IMainTicker _ticker;
|
||
|
private readonly IFixedTicker _fixedTicker;
|
||
|
private readonly ICharacterController _characterController;
|
||
|
private readonly CharacterActor _characterActor;
|
||
|
private readonly LayerMask _interactionLayer;
|
||
|
private IWorldInteractable _currentInteractable;
|
||
|
private IWorldInteractable _triggerInteractable;
|
||
|
|
||
|
private readonly ConcurrentDictionary<int,IWorldInteractable> _interactions = new();
|
||
|
private readonly ConcurrentDictionary<int, IHotkeyProvider> _hotkeyProviders = new();
|
||
|
public IEnumerable<IHotkeyProvider> Hotkeys => _hotkeyProviders.Values;
|
||
|
|
||
|
private bool AllowInteraction => _inputActionGroup.allowInput.Allow &&
|
||
|
_characterController.CurrentState is not null or ICharacterSeating;
|
||
|
|
||
|
private bool _markNextFrame;
|
||
|
|
||
|
private readonly GameObject _myMark;
|
||
|
private readonly IEntity _markEntity;
|
||
|
|
||
|
public PlayerInteractionController(IBlackboard blackBoard, IMainTicker ticker, IWorldInteractionService interactionService, InputActionGroup inputActionGroup, IPlayerKeyMap<InputAction> playerKeyMap, ICharacterController characterController, CharacterActor characterActor, ILogger<PlayerInteractionController> logger, IEntity entity, IKnockedService knockedService, IFixedTicker fixedTicker, IEntitiesService entitiesService, IMarkService markService, Transform transform)
|
||
|
{
|
||
|
_blackBoard = blackBoard;
|
||
|
_ticker = ticker;
|
||
|
_interactionService = interactionService;
|
||
|
_inputActionGroup = inputActionGroup;
|
||
|
_characterController = characterController;
|
||
|
_characterActor = characterActor;
|
||
|
_logger = logger;
|
||
|
_entity = entity;
|
||
|
_knockedService = knockedService;
|
||
|
_fixedTicker = fixedTicker;
|
||
|
_entitiesService = entitiesService;
|
||
|
_markService = markService;
|
||
|
_transform = transform;
|
||
|
|
||
|
_fixedTicker.Add(OnTick);
|
||
|
|
||
|
_interactionLayer = blackBoard.GetVariable<LayerMask>("layer_interaction").GetValue();
|
||
|
|
||
|
inputActionGroup.RegisterCallback(playerKeyMap.InteractiveKey, OnInteraction);
|
||
|
inputActionGroup.RegisterCallback(playerKeyMap.MarkKey, OnMark);
|
||
|
|
||
|
_myMark = new GameObject()
|
||
|
{
|
||
|
hideFlags = HideFlags.HideAndDontSave
|
||
|
};
|
||
|
|
||
|
_markEntity = new Entity();
|
||
|
_markEntity.ServiceCollection.AddSingleton(_myMark.transform);
|
||
|
_markEntity.ServiceCollection.AddSingleton(_myMark);
|
||
|
_markEntity.ServiceCollection.AddSingleton<IMarkComponent,PositionMark>();
|
||
|
_markEntity.ServiceCollection.AddSingleton<PositionMark>();
|
||
|
_markEntity.ServiceCollection.AddSingleton<OwnedByLocalPlayer>();
|
||
|
|
||
|
_entitiesService.Register(_markEntity);
|
||
|
}
|
||
|
|
||
|
private void OnMark(InputAction.CallbackContext obj)
|
||
|
{
|
||
|
if(BITAppForUnity.AllowCursor.Allow)return;
|
||
|
if (obj.JustPressed())
|
||
|
{
|
||
|
_markNextFrame = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void OnInteraction(InputAction.CallbackContext obj)
|
||
|
{
|
||
|
var interactable = _currentInteractable?? _triggerInteractable;
|
||
|
|
||
|
if(interactable is null)return;
|
||
|
|
||
|
switch (obj)
|
||
|
{
|
||
|
case { interaction: PressInteraction, performed: true }:
|
||
|
{
|
||
|
_interactionService.Interaction(_entity, interactable);
|
||
|
}
|
||
|
break;
|
||
|
case { interaction: PressInteraction, canceled: true }:
|
||
|
{
|
||
|
_interactionService.Interaction(_entity, interactable,WorldInteractionProcess.Cancel);
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void OnTick(float obj)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
if (_markService.InMarking.Contains(_markEntity.Id))
|
||
|
{
|
||
|
if (Vector3.Distance(_transform.position, _myMark.transform.position) < 1)
|
||
|
{
|
||
|
_markService.CancelMark(_markEntity.Id);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var releaseTrigger = true;
|
||
|
|
||
|
if (AllowInteraction)
|
||
|
{
|
||
|
foreach (var trigger in _characterActor.Triggers)
|
||
|
{
|
||
|
if (!trigger.collider3D)
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
var id = trigger.collider3D.gameObject.GetInstanceID();
|
||
|
|
||
|
if (_entitiesService.TryGetEntity(id, out var triggerEntity) is false) continue;
|
||
|
|
||
|
if (triggerEntity.ServiceProvider.QueryComponents(
|
||
|
out IWorldInteractable worldInteractable) is false) continue;
|
||
|
|
||
|
worldInteractable.WorldObject = trigger.gameObject;
|
||
|
worldInteractable.Id = id;
|
||
|
|
||
|
if (_currentInteractable == worldInteractable)
|
||
|
{
|
||
|
releaseTrigger = false;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
_triggerInteractable = worldInteractable;
|
||
|
|
||
|
_interactionService.Interaction(_entity, _triggerInteractable, WorldInteractionProcess.Hover);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (releaseTrigger || AllowInteraction is false)
|
||
|
{
|
||
|
if (_triggerInteractable is not null)
|
||
|
{
|
||
|
_interactionService.Interaction(_entity, _triggerInteractable, WorldInteractionProcess.None);
|
||
|
_triggerInteractable = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (AllowInteraction is false)
|
||
|
{
|
||
|
DisposeCurrentInteractable();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (Physics.Raycast(CameraTransform.position, CameraTransform.forward, out var hit, 512,
|
||
|
_interactionLayer) is false)
|
||
|
{
|
||
|
DisposeCurrentInteractable();
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (_markNextFrame && hit.transform)
|
||
|
{
|
||
|
var id = hit.transform.gameObject.GetInstanceID();
|
||
|
|
||
|
var colliderId = hit.collider.gameObject.GetInstanceID();
|
||
|
|
||
|
if (_entitiesService.Entities.ContainsKey(id))
|
||
|
{
|
||
|
|
||
|
}else if (_entitiesService.Entities.ContainsKey(colliderId))
|
||
|
{
|
||
|
id = colliderId;
|
||
|
}else if (hit.collider.GetComponentInParent<IEntity>() is {} parentEntity)
|
||
|
{
|
||
|
id = parentEntity.Id;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
_myMark.transform.position = hit.point;
|
||
|
if (hit.rigidbody)
|
||
|
{
|
||
|
_myMark.transform.SetParentConstraint(hit.transform);
|
||
|
}
|
||
|
else if(_myMark.transform.TryGetComponent<ParentConstraint>(out var parentConstraint))
|
||
|
{
|
||
|
Object.Destroy(parentConstraint);
|
||
|
_myMark.transform.position = hit.point;
|
||
|
}
|
||
|
|
||
|
id = _markEntity.Id;
|
||
|
if (_markService.InMarking.Contains(id))
|
||
|
{
|
||
|
id = -9999;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (id != _characterActor.gameObject.GetInstanceID())
|
||
|
{
|
||
|
if (_markService.InMarking.Contains(id))
|
||
|
{
|
||
|
_markService.CancelMark(id);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
_markService.Mark(id);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (_knockedService.Knocked.Contains(_entity.Id))
|
||
|
{
|
||
|
DisposeCurrentInteractable();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (Vector3.Distance(_characterController.Center, hit.point) > 2)
|
||
|
{
|
||
|
DisposeCurrentInteractable();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
IWorldInteractable newInteractable = null;
|
||
|
|
||
|
try
|
||
|
{
|
||
|
if (_entitiesService.TryGetEntity(hit.transform.gameObject.GetInstanceID(), out var hitEntity))
|
||
|
{
|
||
|
Check(hitEntity);
|
||
|
}
|
||
|
|
||
|
if (_entitiesService.TryGetEntity(hit.collider.gameObject.GetInstanceID(), out hitEntity))
|
||
|
{
|
||
|
Check(hitEntity);
|
||
|
}
|
||
|
|
||
|
if (hit.transform.GetComponentInParent<IEntity>() is { } e)
|
||
|
{
|
||
|
Check(e);
|
||
|
}
|
||
|
|
||
|
DisposeCurrentInteractable();
|
||
|
return;
|
||
|
|
||
|
void Check(IEntity entity)
|
||
|
{
|
||
|
if (!entity.ServiceProvider.QueryComponents(out IWorldInteractable interactable,
|
||
|
out GameObject gameObject)) return;
|
||
|
if(interactable.Enabled is false)return;
|
||
|
newInteractable = interactable;
|
||
|
newInteractable.WorldObject = gameObject;
|
||
|
newInteractable.Id = hit.transform.gameObject.GetInstanceID();
|
||
|
throw new OperationCanceledException();
|
||
|
}
|
||
|
}
|
||
|
catch (OperationCanceledException)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
|
||
|
if (_currentInteractable == newInteractable)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
DisposeCurrentInteractable();
|
||
|
|
||
|
_interactionService.Interaction(_entity, newInteractable, WorldInteractionProcess.Hover);
|
||
|
_currentInteractable = newInteractable;
|
||
|
}
|
||
|
finally
|
||
|
{
|
||
|
_markNextFrame = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public bool IsInteractable(Transform transform, out IWorldInteractable interactable)
|
||
|
|
||
|
{
|
||
|
interactable = null;
|
||
|
return false;
|
||
|
}
|
||
|
public void Dispose()
|
||
|
{
|
||
|
_fixedTicker.Remove(OnTick);
|
||
|
|
||
|
_entitiesService.UnRegister(_markEntity);
|
||
|
}
|
||
|
public void DisposeCurrentInteractable()
|
||
|
{
|
||
|
if (_currentInteractable is not null)
|
||
|
{
|
||
|
_hotkeyProviders.TryRemove(_currentInteractable.Id);
|
||
|
_interactions.TryRemove(_currentInteractable.Id);
|
||
|
|
||
|
_interactionService.Interaction(_entity, _currentInteractable, WorldInteractionProcess.None);
|
||
|
_currentInteractable = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
public void Register(IHotkeyProvider hotkey)
|
||
|
{
|
||
|
throw new NotImplementedException();
|
||
|
}
|
||
|
|
||
|
public void UnRegister(IHotkeyProvider hotkey)
|
||
|
{
|
||
|
throw new NotImplementedException();
|
||
|
}
|
||
|
}
|
||
|
}
|