using System.Linq; using Cirno.Scripts.Resources; using Godot; using Godot.Collections; namespace Cirno.Scripts.Interactables; public partial class ItemPickup3D : Interactable3D { private const float GravityAccel = 9.8f; // How far below the item to probe for the floor each physics step. private const float FloorProbeDistance = 10f; [Export] public Array LootTable = []; /// /// Distance from the item's origin to its bottom edge. /// Lifts the item above the floor surface so it doesn't clip into it. /// Default matches the GenericItem3D collision shape: offset (0.065) + half cylinder height (0.269). /// [Export] public float GroundOffset { get; set; } = 0.204f; private bool _autoPickup = false; private bool _simulating = false; private Vector3 _velocity = Vector3.Zero; public bool AutoPickup => _autoPickup; public override void _Ready() { _autoPickup = LootTable.Any(x => x.AutoPickup); } /// /// Applies an initial velocity and enables gravity simulation so the pickup /// arcs through the air and settles on the floor. /// public void Launch(Vector3 velocity) { _velocity = velocity; _simulating = true; } public override void _PhysicsProcess(double delta) { if (!_simulating) return; _velocity.Y -= GravityAccel * (float)delta; var nextPosition = GlobalPosition + _velocity * (float)delta; // Cast downward to detect the floor so we don't sink through it. var spaceState = GetWorld3D()?.DirectSpaceState; var query = PhysicsRayQueryParameters3D.Create( GlobalPosition, GlobalPosition + Vector3.Down * FloorProbeDistance, // Exclude the item's own collision layer (layer 32 per scene) and hit static geometry only. 0b1 ); query.Exclude = [GetRid()]; var hit = spaceState?.IntersectRay(query) ?? []; if (hit.Count > 0) { var floorY = hit["position"].AsVector3().Y; if (nextPosition.Y - GroundOffset <= floorY) { nextPosition.Y = floorY + GroundOffset; _simulating = false; _velocity = Vector3.Zero; } } GlobalPosition = nextPosition; } public override bool Activate(ActivationType activationType = ActivationType.Toggle) { if (!MeetsRequirements()) return false; Collect(); return true; } public void AddItemsToInventory() { var failedItems = new Array(); foreach (var item in LootTable) { if (!InventoryManager.Instance.AddItem(item)) { failedItems.Add(item); } } if (failedItems.Count > 0) { foreach (var failedItem in failedItems) { var dup = this.Duplicate() as ItemPickup; this.AddSibling(dup); dup.LootTable = [failedItem]; } } // Delet This QueueFree(); } /// /// Auto-pickup items are handled exclusively by AutoPickupModule3D. /// Returning false here prevents them from entering the normal /// interaction selector, so they cannot be manually activated by the player. /// public override bool CanActivate() => !_autoPickup; public void Collect() { AddItemsToInventory(); } public void SetSprite(Texture2D sprite) { var spriteNode = GetNodeOrNull("Sprite3D"); if (spriteNode is null) return; spriteNode.Texture = sprite; } }