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 _dropActions = []; private readonly ResolvePickupAvailability _resolvePickupAvailability = new(); private ItemUseRequest CreateUseRequest(Hand hand, ItemActionPhase phase) { InteractionTargetId? targetId = InteractionRaycaster .GetTargetOrNull() ?.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(); 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(); if (pickupTarget is not null) { ShowPickupPrompt(pickupTarget.ItemInfo); return; } IInteractionTarget interactionTarget = InteractionRaycaster.GetTargetOrNull(); 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 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(); if (target?.CanInteract == true) { _interactions.TryInteract( new InteractionRequest(_playerId, target.InteractionTargetId) ); } GetViewport().SetInputAsHandled(); } } }