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

244 lines
6.7 KiB
C#

using System.Collections.Generic;
using Game.Features.Interactions.Application;
using Game.Features.Interactions.Contracts;
using Game.Features.Interactions.Domain;
using Game.Features.Items.Contracts;
using Game.Features.Players.Contracts;
using Game.Godot;
using Game.Godot.Exceptions;
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 IInteractionHandler _interactions;
private PlayerId _playerId;
private readonly HashSet<Hand> _dropActions = [];
private readonly ResolvePickupAvailability _resolvePickupAvailability = new();
private ItemUseRequest CreateUseRequest(Hand hand, ItemActionPhase phase)
{
InteractionTargetId? targetId = InteractionRaycaster
.GetTargetOrNull<IInteractionTarget>()
?.InteractionTargetId;
return new ItemUseRequest(_playerId, hand, phase, targetId);
}
private void ProcessHandActionStarted(Hand hand)
{
if (Input.IsActionPressed(InputAction.DropModifier.ActionName()))
{
_items.TryUse(new ItemUseRequest(_playerId, hand, ItemActionPhase.Cancelled));
_items.TryDrop(hand, CalculateDropPosition());
_dropActions.Add(hand);
return;
}
if (!_items.IsHandFree(hand))
{
_items.TryUse(CreateUseRequest(hand, ItemActionPhase.Started));
return;
}
IPickupTarget target = InteractionRaycaster.GetTargetOrNull<IPickupTarget>();
if (target is not null)
{
_items.TryPickup(target.ItemInfo.Id, hand);
}
}
private void ProcessHandActionCompleted(Hand hand)
{
if (_dropActions.Remove(hand))
{
return;
}
_items.TryUse(new ItemUseRequest(_playerId, hand, ItemActionPhase.Completed));
}
private Vector3 CalculateDropPosition()
{
DropRaycaster.ForceRaycastUpdate();
bool isBlocked = DropRaycaster.IsColliding();
DropRayHit hit = new(
Camera.GlobalPosition,
-Camera.GlobalBasis.Z.Normalized(),
DropRaycaster.TargetPosition.Length(),
isBlocked,
isBlocked ? DropRaycaster.GetCollisionPoint() : default
);
return DropPosition.CalculateFrom(hit);
}
private void UpdatePrompt()
{
IPickupTarget pickupTarget = InteractionRaycaster.GetTargetOrNull<IPickupTarget>();
if (pickupTarget is not null)
{
ShowPickupPrompt(pickupTarget.ItemInfo);
return;
}
IInteractionTarget interactionTarget = InteractionRaycaster.GetTargetOrNull<IInteractionTarget>();
if (interactionTarget?.CanInteract == true)
{
PromptController.ShowPrompt($"[E] {interactionTarget.Prompt.Text}");
return;
}
PromptController.Clear();
}
private void ShowPickupPrompt(ItemInfo itemInfo)
{
PickupAvailability availability = _resolvePickupAvailability.Resolve(
itemInfo.HandRequirement,
_items.IsHandFree(Hand.Left),
_items.IsHandFree(Hand.Right)
);
if (availability.RequiresBoth)
{
PromptController.ShowPrompt(
availability.LeftHand
? $"[LPM lub PPM] Podnieś {itemInfo.DisplayName} — obie ręce"
: "Potrzebujesz obu wolnych rąk"
);
return;
}
List<string> actions = [];
if (availability.LeftHand)
{
actions.Add($"[LPM] Podnieś {itemInfo.DisplayName} — lewa ręka");
}
if (availability.RightHand)
{
actions.Add($"[PPM] Podnieś {itemInfo.DisplayName} — prawa ręka");
}
PromptController.ShowPrompt(
actions.Count > 0 ? string.Join("\n", actions) : "Brak wolnej ręki"
);
}
public void Initialize(
PlayerId playerId,
IItemInteractionService items,
IInteractionHandler interactions
)
{
_playerId = playerId;
_items = items;
_interactions = interactions;
}
public Vector3 GetItemSpawnPosition() => CalculateDropPosition();
public override void _Ready()
{
this.AssertRequiredExports();
}
public override void _Process(double delta)
{
if (_items is null || _interactions is null)
{
throw new ControllerNotInitializedException(Name);
}
if (GamePause.IsPaused || !MouseCapture.IsCaptured)
{
PromptController.Clear();
return;
}
UpdatePrompt();
}
public override void _UnhandledInput(InputEvent @event)
{
if (_items is null || _interactions is null)
{
throw new ControllerNotInitializedException(Name);
}
if (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.GetTargetOrNull<IInteractionTarget>();
if (target?.CanInteract == true)
{
_interactions.TryInteract(
new InteractionRequest(_playerId, target.InteractionTargetId)
);
}
GetViewport().SetInputAsHandled();
}
}
}