using System; using Game.Features.Items.Contracts; using Game.Features.Items.Domain; using Game.Godot.Extensions; using Godot; namespace Game.Features.Items.Godot; public partial class WorldItemController : RigidBody3D, IPickupTarget { public const string ItemGroup = "world_items"; [Export] public string Key; [Export] public string DefinitionId = "item"; [Export] public string DisplayName = "Item"; [Export] public HandRequirement HandRequirement = HandRequirement.One; [Export] public CollisionShape3D Collision; [Export] public Node3D WorldVisual; [Export] public PackedScene HeldViewScene; protected Node3D HeldView { get; private set; } protected bool IsHeld => HeldView is not null; public ItemInfo ItemInfo { get; private set; } public ItemDefinition Definition { get; private set; } public IItemInstance Item { get; private set; } private ItemWorldController _registry; public override void _Ready() { this.AssertRequiredExports(); if (string.IsNullOrWhiteSpace(DefinitionId)) { throw new InvalidOperationException($"{Name}: item definition id cannot be empty."); } if (HeldViewScene is null) { throw new InvalidOperationException($"{Name}: held view scene is not assigned."); } Definition = new ItemDefinition(new ItemDefinitionId(DefinitionId), DisplayName, HandRequirement); ItemInfo = new ItemInfo( ItemInstanceId.From(this.GetStableInstanceIdFrom(Key)), Definition.Id, Definition.DisplayName, Definition.HandRequirement ); Item = CreateItemInstance(ItemInfo); AddToGroup(ItemGroup); RegisterWithAvailableWorld(); ApplyState(Item); } public override void _ExitTree() { _registry?.Unregister(ItemInfo.Id); } public void RegisterWith(ItemWorldController registry) { _registry = registry; } public void SetHeld(Node3D anchor, IItemInstance item) { if (IsHeld) { return; } Freeze = true; Sleeping = true; LinearVelocity = Vector3.Zero; AngularVelocity = Vector3.Zero; Collision.SetDeferred(CollisionShape3D.PropertyName.Disabled, true); WorldVisual.Visible = false; HeldView = HeldViewScene.Instantiate(); anchor.AddChild(HeldView); HeldView.Transform = Transform3D.Identity; OnHeldViewCreated(HeldView); ApplyState(item); } public void SetDropped(Vector3 worldPosition, IItemInstance item) { if (HeldView is not null) { OnHeldViewRemoving(HeldView); HeldView.QueueFree(); HeldView = null; } GlobalPosition = worldPosition; LinearVelocity = Vector3.Zero; AngularVelocity = Vector3.Zero; WorldVisual.Visible = true; Collision.SetDeferred(CollisionShape3D.PropertyName.Disabled, false); Freeze = false; Sleeping = false; ApplyState(item); } public void ApplyState(IItemInstance item) { OnPresentationChanged(item); } protected virtual IItemInstance CreateItemInstance(ItemInfo itemInfo) => new PassiveItemInstance(itemInfo); protected virtual void OnHeldViewCreated(Node3D heldView) { } protected virtual void OnHeldViewRemoving(Node3D heldView) { } protected virtual void OnPresentationChanged(IItemInstance item) { } private void RegisterWithAvailableWorld() { foreach (Node node in GetTree().GetNodesInGroup(ItemWorldController.RegistryGroup)) { if (node is ItemWorldController registry) { registry.Register(this); return; } } } }