mirror of
https://gitlab.com/MaddoScientisto/cirnogodot.git
synced 2026-06-01 09:45:33 +00:00
152 lines
4.5 KiB
C#
152 lines
4.5 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Cirno.Scripts.Interactables;
|
|
using Godot;
|
|
|
|
namespace Cirno.Scripts.Components.FSM._3DPlayer;
|
|
|
|
/// <summary>
|
|
/// Detects nearby <see cref="ItemPickup3D"/> items whose loot table contains at least one
|
|
/// auto-pickup entry and magnets them toward the player. Items are only formally collected
|
|
/// (inventory added, node freed) once they physically reach the player's position.
|
|
///
|
|
/// Radius can be increased at runtime via equipment or upgrade systems — just write to
|
|
/// <see cref="Radius"/> and the underlying collision shape is updated automatically.
|
|
/// </summary>
|
|
public partial class AutoPickupModule3D : Area3D, IModule<PlayerState, CharacterBody3D>
|
|
{
|
|
// Speed at which attracted items move toward the player (units/second).
|
|
private const float AttractionSpeed = 12f;
|
|
|
|
// Squared distance threshold at which an item is considered "collected".
|
|
private const float CollectDistanceSq = 0.15f * 0.15f;
|
|
|
|
[Export]
|
|
private float _radius = 3f;
|
|
|
|
/// <summary>
|
|
/// The radius of the auto-pickup detection sphere.
|
|
/// Assigning a new value resizes the underlying collision shape immediately.
|
|
/// </summary>
|
|
public float Radius
|
|
{
|
|
get => _radius;
|
|
set
|
|
{
|
|
_radius = value;
|
|
UpdateCollisionRadius();
|
|
}
|
|
}
|
|
|
|
private bool _enabled;
|
|
private CollisionShape3D _collisionShape;
|
|
private AudioStreamPlayer3D _pickupSound;
|
|
|
|
// Items currently being attracted toward the player.
|
|
private readonly HashSet<ItemPickup3D> _trackedItems = [];
|
|
|
|
public IStateMachine<PlayerState, CharacterBody3D> StateMachine { get; private set; }
|
|
|
|
public void Init(IStateMachine<PlayerState, CharacterBody3D> machine)
|
|
{
|
|
StateMachine = machine;
|
|
_collisionShape = GetNodeOrNull<CollisionShape3D>("CollisionShape3D") ?? CreateCollisionShape();
|
|
_pickupSound = GetNodeOrNull<AudioStreamPlayer3D>("PickupSound");
|
|
UpdateCollisionRadius();
|
|
}
|
|
|
|
private CollisionShape3D CreateCollisionShape()
|
|
{
|
|
var shape = new CollisionShape3D { Shape = new SphereShape3D { Radius = _radius } };
|
|
AddChild(shape);
|
|
return shape;
|
|
}
|
|
|
|
public void EnterState(PlayerState state)
|
|
{
|
|
_enabled = true;
|
|
AreaEntered += OnAreaEntered;
|
|
AreaExited += OnAreaExited;
|
|
}
|
|
|
|
public void ExitState(PlayerState state)
|
|
{
|
|
_enabled = false;
|
|
AreaEntered -= OnAreaEntered;
|
|
AreaExited -= OnAreaExited;
|
|
_trackedItems.Clear();
|
|
}
|
|
|
|
public void Process(double delta) { }
|
|
|
|
public void PhysicsProcess(double delta)
|
|
{
|
|
if (!_enabled || _trackedItems.Count == 0) return;
|
|
|
|
var playerPos = StateMachine.MainObject.GlobalPosition;
|
|
var toRemove = new List<ItemPickup3D>();
|
|
|
|
foreach (var item in _trackedItems)
|
|
{
|
|
if (!IsInstanceValid(item))
|
|
{
|
|
toRemove.Add(item);
|
|
continue;
|
|
}
|
|
|
|
var direction = playerPos - item.GlobalPosition;
|
|
var distanceSq = direction.LengthSquared();
|
|
|
|
if (distanceSq <= CollectDistanceSq)
|
|
{
|
|
item.Collect();
|
|
_pickupSound?.Play();
|
|
toRemove.Add(item);
|
|
continue;
|
|
}
|
|
|
|
// Move the item toward the player, scaling speed so it feels snappy at any distance.
|
|
item.GlobalPosition += direction.Normalized() * AttractionSpeed * (float)delta;
|
|
}
|
|
|
|
foreach (var item in toRemove)
|
|
{
|
|
_trackedItems.Remove(item);
|
|
}
|
|
}
|
|
|
|
private void OnAreaEntered(Area3D area)
|
|
{
|
|
if (!_enabled) return;
|
|
if (area is not ItemPickup3D pickup) return;
|
|
if (!pickup.AutoPickup) return;
|
|
|
|
// Only attract items whose inventory can still accept them to avoid a
|
|
// looping autopickup situation when the inventory slot is full.
|
|
var canAdd = pickup.LootTable.Aggregate(false,
|
|
(current, item) => current || InventoryManager.Instance.CanAddItem(item.ItemKey));
|
|
|
|
if (canAdd)
|
|
{
|
|
_trackedItems.Add(pickup);
|
|
}
|
|
}
|
|
|
|
private void OnAreaExited(Area3D area)
|
|
{
|
|
if (area is ItemPickup3D pickup)
|
|
{
|
|
_trackedItems.Remove(pickup);
|
|
}
|
|
}
|
|
|
|
private void UpdateCollisionRadius()
|
|
{
|
|
if (_collisionShape?.Shape is SphereShape3D sphere)
|
|
{
|
|
sphere.Radius = _radius;
|
|
}
|
|
}
|
|
}
|
|
|
|
|