refactor: restructure interaction system into clean layers
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
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.Logging.Contracts;
|
||||
using Game.Features.Players.Contracts;
|
||||
using Game.Godot;
|
||||
using Game.Godot.Extensions;
|
||||
@@ -11,39 +14,177 @@ 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;
|
||||
[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 const string LogCategory = "PlayerInteraction";
|
||||
private IItemInteractionService _items;
|
||||
private IInteractionService _interactions;
|
||||
private IInteractionHandler _interactions;
|
||||
private ILogger _logger;
|
||||
private PlayerId _playerId;
|
||||
private readonly HashSet<Hand> _dropActions = [];
|
||||
private readonly Dictionary<Hand, InteractionTargetId?> _activeUseTargets = [];
|
||||
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,
|
||||
ILogger logger
|
||||
)
|
||||
{
|
||||
_playerId = playerId;
|
||||
_items = items;
|
||||
_interactions = interactions;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public Vector3 GetItemSpawnPosition() => CalculateDropPosition();
|
||||
|
||||
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)
|
||||
if (_items is null || _interactions is null)
|
||||
{
|
||||
_logger?.Warning(LogCategory, "Skipped frame: controller not yet initialized.");
|
||||
PromptController.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (GamePause.IsPaused || !MouseCapture.IsCaptured)
|
||||
{
|
||||
PromptController.Clear();
|
||||
return;
|
||||
@@ -54,7 +195,13 @@ public partial class PlayerInteractionController : Node, IItemSpawnPoint
|
||||
|
||||
public override void _UnhandledInput(InputEvent @event)
|
||||
{
|
||||
if (_items is null || _interactions is null || GamePause.IsPaused || !MouseCapture.IsCaptured)
|
||||
if (_items is null || _interactions is null)
|
||||
{
|
||||
_logger?.Warning(LogCategory, "Ignored input: controller not yet initialized.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (GamePause.IsPaused || !MouseCapture.IsCaptured)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -89,135 +236,15 @@ public partial class PlayerInteractionController : Node, IItemSpawnPoint
|
||||
|
||||
if (@event.IsActionPressed(InputAction.Interact.ActionName()))
|
||||
{
|
||||
IInteractionTarget target = InteractionRaycaster.GetTarget<IInteractionTarget>();
|
||||
IInteractionTarget target = InteractionRaycaster.GetTargetOrNull<IInteractionTarget>();
|
||||
if (target?.CanInteract == true)
|
||||
{
|
||||
_interactions.TryInteract(new InteractionRequest(_playerId, target.Id));
|
||||
_interactions.TryInteract(
|
||||
new InteractionRequest(_playerId, target.InteractionTargetId)
|
||||
);
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user