Files
night-shift/src/Features/Interactions/Godot/PlayerInteractionController.cs
T

224 lines
6.7 KiB
C#

using System.Collections.Generic;
using Game.Features.Interactions.Contracts;
using Game.Features.Items.Contracts;
using Game.Features.Players.Contracts;
using Game.Godot;
using Game.Godot.Extensions;
using Game.Godot.Mappings;
using Godot;
namespace Game.Features.Interactions.Godot;
public partial class PlayerInteractionController : Node, IItemSpawnPoint
{
[Export] public Camera3D Camera;
[Export] public CharacterBody3D Body;
[Export] public InteractionRaycaster InteractionRaycaster;
[Export] public RayCast3D DropRaycaster;
[Export] public InteractionPromptController PromptController;
[Export] public Node3D LeftHandAnchor;
[Export] public Node3D RightHandAnchor;
private IItemInteractionService _items;
private IInteractionService _interactions;
private PlayerId _playerId;
private readonly HashSet<Hand> _dropActions = [];
private readonly Dictionary<Hand, InteractionTargetId?> _activeUseTargets = [];
public override void _Ready()
{
this.AssertRequiredExports();
}
public void Initialize(
PlayerId playerId,
IItemInteractionService items,
IInteractionService interactions
)
{
_playerId = playerId;
_items = items;
_interactions = interactions;
}
public override void _Process(double delta)
{
if (_items is null || _interactions is null || GamePause.IsPaused || !MouseCapture.IsCaptured)
{
PromptController.Clear();
return;
}
UpdatePrompt();
}
public override void _UnhandledInput(InputEvent @event)
{
if (_items is null || _interactions is null || GamePause.IsPaused || !MouseCapture.IsCaptured)
{
return;
}
if (@event.IsActionPressed(InputAction.UseLeftHand.ActionName()))
{
ProcessHandActionStarted(Hand.Left);
GetViewport().SetInputAsHandled();
return;
}
if (@event.IsActionReleased(InputAction.UseLeftHand.ActionName()))
{
ProcessHandActionCompleted(Hand.Left);
GetViewport().SetInputAsHandled();
return;
}
if (@event.IsActionPressed(InputAction.UseRightHand.ActionName()))
{
ProcessHandActionStarted(Hand.Right);
GetViewport().SetInputAsHandled();
return;
}
if (@event.IsActionReleased(InputAction.UseRightHand.ActionName()))
{
ProcessHandActionCompleted(Hand.Right);
GetViewport().SetInputAsHandled();
return;
}
if (@event.IsActionPressed(InputAction.Interact.ActionName()))
{
IInteractionTarget target = InteractionRaycaster.GetTarget<IInteractionTarget>();
if (target?.CanInteract == true)
{
_interactions.TryInteract(new InteractionRequest(_playerId, target.Id));
}
GetViewport().SetInputAsHandled();
}
}
public Vector3 GetItemSpawnPosition() => CalculateDropPosition();
private void ProcessHandActionStarted(Hand hand)
{
if (Input.IsActionPressed(InputAction.DropModifier.ActionName()))
{
if (_activeUseTargets.Remove(hand, out InteractionTargetId? activeTargetId))
{
_items.TryUse(new ItemUseRequest(
_playerId,
hand,
ItemActionPhase.Cancelled,
activeTargetId
));
}
_items.TryDrop(hand, CalculateDropPosition());
_dropActions.Add(hand);
return;
}
if (!_items.IsHandFree(hand))
{
ItemUseRequest request = CreateUseRequest(hand, ItemActionPhase.Started);
ItemActionResult result = _items.TryUse(request);
if (result.Success)
{
_activeUseTargets[hand] = request.TargetId;
}
return;
}
IPickupTarget target = InteractionRaycaster.GetTarget<IPickupTarget>();
if (target is not null)
{
_items.TryPickup(target.ItemInfo.Id, hand);
}
}
private void ProcessHandActionCompleted(Hand hand)
{
if (_dropActions.Remove(hand))
{
return;
}
if (_activeUseTargets.Remove(hand, out InteractionTargetId? targetId))
{
_items.TryUse(new ItemUseRequest(_playerId, hand, ItemActionPhase.Completed, targetId));
}
}
private ItemUseRequest CreateUseRequest(Hand hand, ItemActionPhase phase)
{
InteractionTargetId? targetId = InteractionRaycaster.GetTarget<IInteractionTarget>()?.Id;
return new ItemUseRequest(_playerId, hand, phase, targetId);
}
private Vector3 CalculateDropPosition()
{
DropRaycaster.ForceRaycastUpdate();
Vector3 origin = Camera.GlobalPosition;
Vector3 direction = -Camera.GlobalBasis.Z.Normalized();
float distance = DropRaycaster.TargetPosition.Length();
if (DropRaycaster.IsColliding())
{
distance = Mathf.Max(0.25f, origin.DistanceTo(DropRaycaster.GetCollisionPoint()) - 0.2f);
}
return origin + direction * distance;
}
private void UpdatePrompt()
{
IPickupTarget pickupTarget = InteractionRaycaster.GetTarget<IPickupTarget>();
if (pickupTarget is not null)
{
ShowPickupPrompt(pickupTarget.ItemInfo);
return;
}
IInteractionTarget interactionTarget = InteractionRaycaster.GetTarget<IInteractionTarget>();
if (interactionTarget?.CanInteract == true)
{
PromptController.ShowPrompt($"[E] {interactionTarget.Prompt.Text}");
return;
}
PromptController.Clear();
}
private void ShowPickupPrompt(ItemInfo itemInfo)
{
if (itemInfo.HandRequirement == HandRequirement.Both)
{
bool bothHandsFree = _items.IsHandFree(Hand.Left) && _items.IsHandFree(Hand.Right);
PromptController.ShowPrompt(bothHandsFree
? $"[LPM lub PPM] Podnieś {itemInfo.DisplayName} — obie ręce"
: "Potrzebujesz obu wolnych rąk");
return;
}
List<string> actions = [];
if (_items.IsHandFree(Hand.Left))
{
actions.Add($"[LPM] Podnieś {itemInfo.DisplayName} — lewa ręka");
}
if (_items.IsHandFree(Hand.Right))
{
actions.Add($"[PPM] Podnieś {itemInfo.DisplayName} — prawa ręka");
}
PromptController.ShowPrompt(actions.Count > 0
? string.Join("\n", actions)
: "Brak wolnej ręki");
}
}