refactor: refactor stuff
This commit is contained in:
@@ -15,12 +15,12 @@ public sealed class SpawnItemCommand : ICommand
|
|||||||
public string Description => "Spawns an item in front of the player.";
|
public string Description => "Spawns an item in front of the player.";
|
||||||
public IReadOnlyList<CommandArgSpec> Args { get; }
|
public IReadOnlyList<CommandArgSpec> Args { get; }
|
||||||
|
|
||||||
public SpawnItemCommand(IItemSpawner spawner, IItemSpawnPoint spawnPoint)
|
public SpawnItemCommand(IItemSpawner spawner, IItemDefinitionRegistry registry, IItemSpawnPoint spawnPoint)
|
||||||
{
|
{
|
||||||
_spawner = spawner;
|
_spawner = spawner;
|
||||||
_spawnPoint = spawnPoint;
|
_spawnPoint = spawnPoint;
|
||||||
|
|
||||||
string[] definitions = spawner.DefinitionIds
|
string[] definitions = registry.DefinitionIds
|
||||||
.Select(id => id.Value)
|
.Select(id => id.Value)
|
||||||
.OrderBy(id => id, StringComparer.Ordinal)
|
.OrderBy(id => id, StringComparer.Ordinal)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public abstract partial class InteractableController : Node3D, IInteractionTarge
|
|||||||
public const string InteractionGroup = "interactions";
|
public const string InteractionGroup = "interactions";
|
||||||
|
|
||||||
[Export]
|
[Export]
|
||||||
public string Key;
|
public string InstanceKey;
|
||||||
|
|
||||||
[Export]
|
[Export]
|
||||||
public string PromptText = "Wejdź w interakcję";
|
public string PromptText = "Wejdź w interakcję";
|
||||||
@@ -51,7 +51,7 @@ public abstract partial class InteractableController : Node3D, IInteractionTarge
|
|||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
InteractionTargetId = InteractionTargetId.From(this.GetStableInstanceIdFrom(Key));
|
InteractionTargetId = InteractionTargetId.From(this.GetStableInstanceIdFrom(InstanceKey));
|
||||||
AddToGroup(InteractionGroup);
|
AddToGroup(InteractionGroup);
|
||||||
RegisterWithAvailableRegistry();
|
RegisterWithAvailableRegistry();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace Game.Features.Items.Application;
|
|||||||
|
|
||||||
public sealed class ItemInteractionService(
|
public sealed class ItemInteractionService(
|
||||||
HandsInventory hands,
|
HandsInventory hands,
|
||||||
IItemRepository itemRepository,
|
IItemInstanceRegistry itemRegistry,
|
||||||
IHeldItemPresenter presenter
|
IHeldItemPresenter presenter
|
||||||
) : IItemInteractionService
|
) : IItemInteractionService
|
||||||
{
|
{
|
||||||
@@ -20,7 +20,7 @@ public sealed class ItemInteractionService(
|
|||||||
|
|
||||||
public ItemActionResult TryPickup(ItemInstanceId itemId, Hand hand)
|
public ItemActionResult TryPickup(ItemInstanceId itemId, Hand hand)
|
||||||
{
|
{
|
||||||
if (!itemRepository.TryGet(itemId, out IItemInstance item))
|
if (!itemRegistry.TryGet(itemId, out IItemInstance item))
|
||||||
{
|
{
|
||||||
return ItemActionResult.Fail(ItemActionStatus.ItemNotFound, $"Item {itemId} was not found.");
|
return ItemActionResult.Fail(ItemActionStatus.ItemNotFound, $"Item {itemId} was not found.");
|
||||||
}
|
}
|
||||||
@@ -68,7 +68,7 @@ public sealed class ItemInteractionService(
|
|||||||
return ItemActionResult.Fail(ItemActionStatus.HandEmpty, "The requested hand is empty.");
|
return ItemActionResult.Fail(ItemActionStatus.HandEmpty, "The requested hand is empty.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!itemRepository.TryGet(itemId.Value, out IItemInstance item))
|
if (!itemRegistry.TryGet(itemId.Value, out IItemInstance item))
|
||||||
{
|
{
|
||||||
return ItemActionResult.Fail(ItemActionStatus.ItemNotFound, $"Item {itemId} was not found.");
|
return ItemActionResult.Fail(ItemActionStatus.ItemNotFound, $"Item {itemId} was not found.");
|
||||||
}
|
}
|
||||||
@@ -90,7 +90,7 @@ public sealed class ItemInteractionService(
|
|||||||
return ItemActionResult.Fail(ItemActionStatus.HandEmpty, "The requested hand is empty.");
|
return ItemActionResult.Fail(ItemActionStatus.HandEmpty, "The requested hand is empty.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!itemRepository.TryGet(itemId.Value, out IItemInstance item))
|
if (!itemRegistry.TryGet(itemId.Value, out IItemInstance item))
|
||||||
{
|
{
|
||||||
return ItemActionResult.Fail(ItemActionStatus.ItemNotFound, $"Item {itemId} was not found.");
|
return ItemActionResult.Fail(ItemActionStatus.ItemNotFound, $"Item {itemId} was not found.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Game.Features.Items.Contracts;
|
||||||
|
|
||||||
|
public interface IItemDefinitionRegistry
|
||||||
|
{
|
||||||
|
IReadOnlyCollection<ItemDefinitionId> DefinitionIds { get; }
|
||||||
|
bool TryRegister(ItemDefinitionId definitionId, ItemDefinition definition);
|
||||||
|
bool TryGetDefinition(ItemDefinitionId definitionId, out ItemDefinition definition);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://kjka28plyqeo
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Game.Features.Items.Contracts;
|
||||||
|
|
||||||
|
public interface IItemInstanceRegistry
|
||||||
|
{
|
||||||
|
void Register(ItemInstanceId itemId, IItemInstance item);
|
||||||
|
void Unregister(ItemInstanceId itemId);
|
||||||
|
bool TryGet(ItemInstanceId itemId, out IItemInstance item);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://s83yytevpj52
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace Game.Features.Items.Contracts;
|
|
||||||
|
|
||||||
public interface IItemRepository
|
|
||||||
{
|
|
||||||
bool TryGet(ItemInstanceId itemId, out IItemInstance item);
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
uid://e7n1m6v4k9r3q
|
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Godot;
|
using Godot;
|
||||||
|
|
||||||
namespace Game.Features.Items.Contracts;
|
namespace Game.Features.Items.Contracts;
|
||||||
|
|
||||||
public interface IItemSpawner
|
public interface IItemSpawner
|
||||||
{
|
{
|
||||||
IReadOnlyCollection<ItemDefinitionId> DefinitionIds { get; }
|
ItemActionResult TrySpawn(
|
||||||
ItemActionResult TrySpawn(ItemDefinitionId definitionId, Vector3 worldPosition, out ItemInstanceId itemId);
|
ItemDefinitionId definitionId,
|
||||||
|
Vector3 worldPosition,
|
||||||
|
out ItemInstanceId itemId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ using Godot;
|
|||||||
|
|
||||||
namespace Game.Features.Items.Godot.Flashlight;
|
namespace Game.Features.Items.Godot.Flashlight;
|
||||||
|
|
||||||
public partial class FlashlightItemController : WorldItemController
|
public partial class FlashlightItemController : ItemController
|
||||||
{
|
{
|
||||||
[Export] public SpotLight3D WorldLight;
|
[Export] public SpotLight3D WorldLight;
|
||||||
|
|
||||||
|
|||||||
+5
-5
@@ -3,15 +3,15 @@ using Godot;
|
|||||||
|
|
||||||
namespace Game.Features.Items.Godot;
|
namespace Game.Features.Items.Godot;
|
||||||
|
|
||||||
public sealed class PlayerHeldItemPresenter(
|
public sealed class HeldItemPresenter(
|
||||||
ItemWorldController itemWorld,
|
ItemControllerRegistryController itemWorld,
|
||||||
Node3D leftHandAnchor,
|
Node3D leftHandAnchor,
|
||||||
Node3D rightHandAnchor
|
Node3D rightHandAnchor
|
||||||
) : IHeldItemPresenter
|
) : IHeldItemPresenter
|
||||||
{
|
{
|
||||||
public bool TryShowHeld(IItemInstance item, Hand hand)
|
public bool TryShowHeld(IItemInstance item, Hand hand)
|
||||||
{
|
{
|
||||||
if (!itemWorld.TryGetController(item.Info.Id, out WorldItemController controller))
|
if (!itemWorld.TryGetController(item.Info.Id, out ItemController controller))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -23,7 +23,7 @@ public sealed class PlayerHeldItemPresenter(
|
|||||||
|
|
||||||
public bool TryShowDropped(IItemInstance item, Vector3 worldPosition)
|
public bool TryShowDropped(IItemInstance item, Vector3 worldPosition)
|
||||||
{
|
{
|
||||||
if (!itemWorld.TryGetController(item.Info.Id, out WorldItemController controller))
|
if (!itemWorld.TryGetController(item.Info.Id, out ItemController controller))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -34,7 +34,7 @@ public sealed class PlayerHeldItemPresenter(
|
|||||||
|
|
||||||
public void Refresh(IItemInstance item)
|
public void Refresh(IItemInstance item)
|
||||||
{
|
{
|
||||||
if (itemWorld.TryGetController(item.Info.Id, out WorldItemController controller))
|
if (itemWorld.TryGetController(item.Info.Id, out ItemController controller))
|
||||||
{
|
{
|
||||||
controller.ApplyState(item);
|
controller.ApplyState(item);
|
||||||
}
|
}
|
||||||
+34
-35
@@ -6,17 +6,30 @@ using Godot;
|
|||||||
|
|
||||||
namespace Game.Features.Items.Godot;
|
namespace Game.Features.Items.Godot;
|
||||||
|
|
||||||
public partial class WorldItemController : RigidBody3D, IPickupTarget
|
public partial class ItemController : RigidBody3D, IPickupTarget
|
||||||
{
|
{
|
||||||
public const string ItemGroup = "world_items";
|
public const string ItemGroup = "world_items";
|
||||||
|
|
||||||
[Export] public string Key;
|
[Export]
|
||||||
[Export] public string DefinitionId = "item";
|
public string InstanceKey;
|
||||||
[Export] public string DisplayName = "Item";
|
|
||||||
[Export] public HandRequirement HandRequirement = HandRequirement.One;
|
[Export]
|
||||||
[Export] public CollisionShape3D Collision;
|
public string DefinitionId = "item";
|
||||||
[Export] public Node3D WorldVisual;
|
|
||||||
[Export] public PackedScene HeldViewScene;
|
[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 Node3D HeldView { get; private set; }
|
||||||
protected bool IsHeld => HeldView is not null;
|
protected bool IsHeld => HeldView is not null;
|
||||||
@@ -25,7 +38,7 @@ public partial class WorldItemController : RigidBody3D, IPickupTarget
|
|||||||
public ItemDefinition Definition { get; private set; }
|
public ItemDefinition Definition { get; private set; }
|
||||||
public IItemInstance Item { get; private set; }
|
public IItemInstance Item { get; private set; }
|
||||||
|
|
||||||
private ItemWorldController _registry;
|
private ItemControllerRegistryController _registry;
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
@@ -41,16 +54,19 @@ public partial class WorldItemController : RigidBody3D, IPickupTarget
|
|||||||
throw new InvalidOperationException($"{Name}: held view scene is not assigned.");
|
throw new InvalidOperationException($"{Name}: held view scene is not assigned.");
|
||||||
}
|
}
|
||||||
|
|
||||||
Definition = new ItemDefinition(new ItemDefinitionId(DefinitionId), DisplayName, HandRequirement);
|
Definition = new ItemDefinition(
|
||||||
|
new ItemDefinitionId(DefinitionId),
|
||||||
|
DisplayName,
|
||||||
|
HandRequirement
|
||||||
|
);
|
||||||
ItemInfo = new ItemInfo(
|
ItemInfo = new ItemInfo(
|
||||||
ItemInstanceId.From(this.GetStableInstanceIdFrom(Key)),
|
ItemInstanceId.From(this.GetStableInstanceIdFrom(InstanceKey)),
|
||||||
Definition.Id,
|
Definition.Id,
|
||||||
Definition.DisplayName,
|
Definition.DisplayName,
|
||||||
Definition.HandRequirement
|
Definition.HandRequirement
|
||||||
);
|
);
|
||||||
Item = CreateItemInstance(ItemInfo);
|
Item = CreateItemInstance(ItemInfo);
|
||||||
AddToGroup(ItemGroup);
|
AddToGroup(ItemGroup);
|
||||||
RegisterWithAvailableWorld();
|
|
||||||
ApplyState(Item);
|
ApplyState(Item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +75,7 @@ public partial class WorldItemController : RigidBody3D, IPickupTarget
|
|||||||
_registry?.Unregister(ItemInfo.Id);
|
_registry?.Unregister(ItemInfo.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RegisterWith(ItemWorldController registry)
|
public void RegisterWith(ItemControllerRegistryController registry)
|
||||||
{
|
{
|
||||||
_registry = registry;
|
_registry = registry;
|
||||||
}
|
}
|
||||||
@@ -109,29 +125,12 @@ public partial class WorldItemController : RigidBody3D, IPickupTarget
|
|||||||
OnPresentationChanged(item);
|
OnPresentationChanged(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual IItemInstance CreateItemInstance(ItemInfo itemInfo) => new PassiveItemInstance(itemInfo);
|
protected virtual IItemInstance CreateItemInstance(ItemInfo itemInfo) =>
|
||||||
|
new PassiveItemInstance(itemInfo);
|
||||||
|
|
||||||
protected virtual void OnHeldViewCreated(Node3D heldView)
|
protected virtual void OnHeldViewCreated(Node3D heldView) { }
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void OnHeldViewRemoving(Node3D heldView)
|
protected virtual void OnHeldViewRemoving(Node3D heldView) { }
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected virtual void OnPresentationChanged(IItemInstance item)
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Game.Features.Items.Contracts;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Game.Features.Items.Godot;
|
||||||
|
|
||||||
|
public partial class ItemControllerRegistryController : Node
|
||||||
|
{
|
||||||
|
private IItemInstanceRegistry _registry;
|
||||||
|
private readonly Dictionary<ItemInstanceId, ItemController> _items = [];
|
||||||
|
|
||||||
|
public IReadOnlyCollection<ItemController> Items => _items.Values;
|
||||||
|
|
||||||
|
public void UseRegistry(IItemInstanceRegistry registry)
|
||||||
|
{
|
||||||
|
_registry = registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterSceneItems()
|
||||||
|
{
|
||||||
|
foreach (Node node in GetTree().GetNodesInGroup(ItemController.ItemGroup))
|
||||||
|
{
|
||||||
|
if (node is ItemController item)
|
||||||
|
{
|
||||||
|
Register(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Register(ItemController item)
|
||||||
|
{
|
||||||
|
if (_items.TryGetValue(item.ItemInfo.Id, out ItemController existing))
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(existing, item))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException($"Duplicate item instance id: {item.ItemInfo.Id}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_items.Add(item.ItemInfo.Id, item);
|
||||||
|
_registry.Register(item.ItemInfo.Id, item.Item);
|
||||||
|
item.RegisterWith(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Unregister(ItemInstanceId itemId)
|
||||||
|
{
|
||||||
|
_items.Remove(itemId);
|
||||||
|
_registry.Unregister(itemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetController(ItemInstanceId itemId, out ItemController controller)
|
||||||
|
{
|
||||||
|
return _items.TryGetValue(itemId, out controller);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Game.Features.Items.Contracts;
|
||||||
|
using Game.Godot.Extensions;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
|
namespace Game.Features.Items.Godot;
|
||||||
|
|
||||||
|
public partial class ItemSceneRegistryController : Node
|
||||||
|
{
|
||||||
|
[Export]
|
||||||
|
public ItemControllerRegistryController ItemWorld;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public global::Godot.Collections.Array<PackedScene> ItemScenes = [];
|
||||||
|
|
||||||
|
private readonly Dictionary<ItemDefinitionId, PackedScene> _scenes = [];
|
||||||
|
|
||||||
|
private IItemDefinitionRegistry _definitions;
|
||||||
|
|
||||||
|
public void UseRegistry(IItemDefinitionRegistry registry)
|
||||||
|
{
|
||||||
|
_definitions = registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void _Ready()
|
||||||
|
{
|
||||||
|
this.AssertRequiredExports();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
_scenes.Clear();
|
||||||
|
|
||||||
|
foreach (PackedScene scene in ItemScenes)
|
||||||
|
{
|
||||||
|
RegisterItem(scene, throwOnDuplicate: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (ItemController item in ItemWorld.Items)
|
||||||
|
{
|
||||||
|
string scenePath = item.SceneFilePath;
|
||||||
|
if (string.IsNullOrWhiteSpace(scenePath))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
RegisterItem(ResourceLoader.Load<PackedScene>(scenePath), throwOnDuplicate: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_scenes.Count == 0)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("The item spawn catalog is empty.");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (ItemController item in ItemWorld.Items)
|
||||||
|
{
|
||||||
|
ValidateAgainstCatalog(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGetScene(ItemDefinitionId definitionId, out PackedScene scene)
|
||||||
|
{
|
||||||
|
return _scenes.TryGetValue(definitionId, out scene);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegisterItem(PackedScene itemScene, bool throwOnDuplicate)
|
||||||
|
{
|
||||||
|
if (itemScene is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemController prototype = itemScene.Instantiate<ItemController>();
|
||||||
|
ItemDefinitionId definitionId = new(prototype.DefinitionId);
|
||||||
|
ItemDefinition definition = new(
|
||||||
|
definitionId,
|
||||||
|
prototype.DisplayName,
|
||||||
|
prototype.HandRequirement
|
||||||
|
);
|
||||||
|
prototype.Free();
|
||||||
|
|
||||||
|
if (!_definitions.TryRegister(definitionId, definition))
|
||||||
|
{
|
||||||
|
if (throwOnDuplicate)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"Duplicate item spawn definition: {definitionId}."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_scenes.Add(definitionId, itemScene);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ValidateAgainstCatalog(ItemController item)
|
||||||
|
{
|
||||||
|
if (!_definitions.TryGetDefinition(item.Definition.Id, out ItemDefinition canonical))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!item.Definition.Equals(canonical))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"{item.Name}: item definition drifted from the catalog for '{item.Definition.Id}'."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://cft7r2j1f0sav
|
||||||
@@ -1,6 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Game.Features.Items.Contracts;
|
using Game.Features.Items.Contracts;
|
||||||
using Game.Godot.Extensions;
|
using Game.Godot.Extensions;
|
||||||
using Godot;
|
using Godot;
|
||||||
@@ -9,57 +6,29 @@ namespace Game.Features.Items.Godot;
|
|||||||
|
|
||||||
public partial class ItemSpawnerController : Node, IItemSpawner
|
public partial class ItemSpawnerController : Node, IItemSpawner
|
||||||
{
|
{
|
||||||
[Export] public Node3D ItemsRoot;
|
[Export]
|
||||||
[Export] public ItemWorldController ItemWorld;
|
public Node3D ItemsRoot;
|
||||||
[Export] public global::Godot.Collections.Array<PackedScene> ItemScenes = [];
|
|
||||||
|
|
||||||
private readonly Dictionary<ItemDefinitionId, PackedScene> _scenes = [];
|
[Export]
|
||||||
|
public ItemControllerRegistryController ItemWorld;
|
||||||
|
|
||||||
public IReadOnlyCollection<ItemDefinitionId> DefinitionIds => _scenes.Keys.ToArray();
|
[Export]
|
||||||
|
public ItemSceneRegistryController Scenes;
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
this.AssertRequiredExports();
|
this.AssertRequiredExports();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Initialize()
|
|
||||||
{
|
|
||||||
_scenes.Clear();
|
|
||||||
|
|
||||||
foreach (PackedScene scene in ItemScenes)
|
|
||||||
{
|
|
||||||
RegisterScene(scene);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (WorldItemController item in ItemWorld.Items)
|
|
||||||
{
|
|
||||||
string scenePath = item.SceneFilePath;
|
|
||||||
if (string.IsNullOrWhiteSpace(scenePath))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
PackedScene scene = ResourceLoader.Load<PackedScene>(scenePath);
|
|
||||||
if (scene is not null)
|
|
||||||
{
|
|
||||||
_scenes.TryAdd(item.ItemInfo.DefinitionId, scene);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_scenes.Count == 0)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("The item spawn catalog is empty.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ItemActionResult TrySpawn(
|
public ItemActionResult TrySpawn(
|
||||||
ItemDefinitionId definitionId,
|
ItemDefinitionId definitionId,
|
||||||
Vector3 worldPosition,
|
Vector3 worldPosition,
|
||||||
out ItemInstanceId itemId
|
out ItemInstanceId itemId
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
itemId = default;
|
itemId = default;
|
||||||
|
|
||||||
if (!_scenes.TryGetValue(definitionId, out PackedScene scene))
|
if (!Scenes.TryGetScene(definitionId, out PackedScene scene))
|
||||||
{
|
{
|
||||||
return ItemActionResult.Fail(
|
return ItemActionResult.Fail(
|
||||||
ItemActionStatus.UnknownDefinition,
|
ItemActionStatus.UnknownDefinition,
|
||||||
@@ -67,28 +36,11 @@ public partial class ItemSpawnerController : Node, IItemSpawner
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
WorldItemController item = scene.Instantiate<WorldItemController>();
|
ItemController item = scene.Instantiate<ItemController>();
|
||||||
ItemsRoot.AddChild(item);
|
ItemsRoot.AddChild(item);
|
||||||
item.GlobalPosition = worldPosition;
|
item.GlobalPosition = worldPosition;
|
||||||
ItemWorld.Register(item);
|
ItemWorld.Register(item);
|
||||||
itemId = item.ItemInfo.Id;
|
itemId = item.ItemInfo.Id;
|
||||||
return ItemActionResult.Ok();
|
return ItemActionResult.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RegisterScene(PackedScene scene)
|
|
||||||
{
|
|
||||||
if (scene is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
WorldItemController prototype = scene.Instantiate<WorldItemController>();
|
|
||||||
ItemDefinitionId definitionId = new(prototype.DefinitionId);
|
|
||||||
prototype.Free();
|
|
||||||
|
|
||||||
if (!_scenes.TryAdd(definitionId, scene))
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException($"Duplicate item spawn definition: {definitionId}.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using Game.Features.Items.Contracts;
|
|
||||||
using Godot;
|
|
||||||
|
|
||||||
namespace Game.Features.Items.Godot;
|
|
||||||
|
|
||||||
public partial class ItemWorldController : Node, IItemRepository
|
|
||||||
{
|
|
||||||
public const string RegistryGroup = "item_world_registry";
|
|
||||||
|
|
||||||
private readonly Dictionary<ItemInstanceId, WorldItemController> _items = [];
|
|
||||||
|
|
||||||
public IReadOnlyCollection<WorldItemController> Items => _items.Values;
|
|
||||||
|
|
||||||
public override void _EnterTree()
|
|
||||||
{
|
|
||||||
AddToGroup(RegistryGroup);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RegisterSceneItems()
|
|
||||||
{
|
|
||||||
foreach (Node node in GetTree().GetNodesInGroup(WorldItemController.ItemGroup))
|
|
||||||
{
|
|
||||||
if (node is WorldItemController item)
|
|
||||||
{
|
|
||||||
Register(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Register(WorldItemController item)
|
|
||||||
{
|
|
||||||
if (_items.TryGetValue(item.ItemInfo.Id, out WorldItemController existing))
|
|
||||||
{
|
|
||||||
if (ReferenceEquals(existing, item))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new InvalidOperationException($"Duplicate item instance id: {item.ItemInfo.Id}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
_items.Add(item.ItemInfo.Id, item);
|
|
||||||
item.RegisterWith(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Unregister(ItemInstanceId itemId)
|
|
||||||
{
|
|
||||||
_items.Remove(itemId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryGet(ItemInstanceId itemId, out IItemInstance item)
|
|
||||||
{
|
|
||||||
if (_items.TryGetValue(itemId, out WorldItemController controller))
|
|
||||||
{
|
|
||||||
item = controller.Item;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
item = null;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryGetController(ItemInstanceId itemId, out WorldItemController controller)
|
|
||||||
{
|
|
||||||
return _items.TryGetValue(itemId, out controller);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Game.Features.Items.Contracts;
|
||||||
|
|
||||||
|
namespace Game.Features.Items.Infrastructure;
|
||||||
|
|
||||||
|
public sealed class ItemDefinitionRegistry : IItemDefinitionRegistry
|
||||||
|
{
|
||||||
|
private readonly Dictionary<ItemDefinitionId, ItemDefinition> _definitionsById = [];
|
||||||
|
|
||||||
|
public IReadOnlyCollection<ItemDefinitionId> DefinitionIds => _definitionsById.Keys.ToArray();
|
||||||
|
|
||||||
|
public bool TryRegister(ItemDefinitionId definitionId, ItemDefinition definition) =>
|
||||||
|
_definitionsById.TryAdd(definitionId, definition);
|
||||||
|
|
||||||
|
public bool TryGetDefinition(ItemDefinitionId definitionId, out ItemDefinition definition) =>
|
||||||
|
_definitionsById.TryGetValue(definitionId, out definition);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://72g8eg0lvlui
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Game.Features.Items.Contracts;
|
||||||
|
|
||||||
|
namespace Game.Features.Items.Infrastructure;
|
||||||
|
|
||||||
|
public sealed class ItemInstanceRegistry : IItemInstanceRegistry
|
||||||
|
{
|
||||||
|
private readonly Dictionary<ItemInstanceId, IItemInstance> _itemsById = [];
|
||||||
|
|
||||||
|
public void Register(ItemInstanceId itemId, IItemInstance item)
|
||||||
|
{
|
||||||
|
if (_itemsById.TryGetValue(itemId, out IItemInstance existing))
|
||||||
|
{
|
||||||
|
if (ReferenceEquals(existing, item))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException($"Duplicate item instance id: {itemId}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_itemsById.Add(itemId, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Unregister(ItemInstanceId itemId)
|
||||||
|
{
|
||||||
|
_itemsById.Remove(itemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryGet(ItemInstanceId itemId, out IItemInstance item) =>
|
||||||
|
_itemsById.TryGetValue(itemId, out item);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dfbylp383il2v
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dj8kxrwjoyfvi
|
||||||
+47
-17
@@ -2,14 +2,15 @@ using System;
|
|||||||
using Game.Features.Console.Application.Commands;
|
using Game.Features.Console.Application.Commands;
|
||||||
using Game.Features.Console.Godot;
|
using Game.Features.Console.Godot;
|
||||||
using Game.Features.Console.Infrastructure;
|
using Game.Features.Console.Infrastructure;
|
||||||
using Game.Features.Logging.Godot;
|
|
||||||
using Game.Features.Logging.Infrastructure;
|
|
||||||
using Game.Features.Interactions.Application;
|
using Game.Features.Interactions.Application;
|
||||||
using Game.Features.Interactions.Godot;
|
using Game.Features.Interactions.Godot;
|
||||||
using Game.Features.Interactions.Infrastructure;
|
using Game.Features.Interactions.Infrastructure;
|
||||||
using Game.Features.Items.Application;
|
using Game.Features.Items.Application;
|
||||||
using Game.Features.Items.Domain;
|
using Game.Features.Items.Domain;
|
||||||
using Game.Features.Items.Godot;
|
using Game.Features.Items.Godot;
|
||||||
|
using Game.Features.Items.Infrastructure;
|
||||||
|
using Game.Features.Logging.Godot;
|
||||||
|
using Game.Features.Logging.Infrastructure;
|
||||||
using Game.Features.Multiplayer.Godot;
|
using Game.Features.Multiplayer.Godot;
|
||||||
using Game.Features.Players.Contracts;
|
using Game.Features.Players.Contracts;
|
||||||
using Game.Features.Players.Godot;
|
using Game.Features.Players.Godot;
|
||||||
@@ -21,16 +22,33 @@ namespace Game.Godot;
|
|||||||
|
|
||||||
public partial class GameBootstrap : Node
|
public partial class GameBootstrap : Node
|
||||||
{
|
{
|
||||||
[Export] public ConsoleController Console;
|
[Export]
|
||||||
[Export] public PlayerController Player;
|
public ConsoleController Console;
|
||||||
[Export] public PlayerInteractionController PlayerInteraction;
|
|
||||||
[Export] public ItemWorldController ItemWorld;
|
[Export]
|
||||||
[Export] public ItemSpawnerController ItemSpawner;
|
public PlayerController Player;
|
||||||
[Export] public InteractionsRegistryController InteractionWorld;
|
|
||||||
[Export] public MultiplayerController MultiplayerController;
|
[Export]
|
||||||
|
public PlayerInteractionController PlayerInteraction;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public ItemControllerRegistryController ItemWorld;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public ItemSceneRegistryController ItemScenes;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public ItemSpawnerController ItemSpawner;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public InteractionsRegistryController InteractionWorld;
|
||||||
|
|
||||||
|
[Export]
|
||||||
|
public MultiplayerController MultiplayerController;
|
||||||
|
|
||||||
private FileLogger _fileLogger;
|
private FileLogger _fileLogger;
|
||||||
private CompositeLogger _logger;
|
private CompositeLogger _logger;
|
||||||
|
private ItemDefinitionRegistry _itemDefinitions;
|
||||||
|
|
||||||
private void InitializeLogging()
|
private void InitializeLogging()
|
||||||
{
|
{
|
||||||
@@ -38,7 +56,7 @@ public partial class GameBootstrap : Node
|
|||||||
_logger = new CompositeLogger([
|
_logger = new CompositeLogger([
|
||||||
new GodotConsoleLogger(),
|
new GodotConsoleLogger(),
|
||||||
new LogTargetLogger(Console),
|
new LogTargetLogger(Console),
|
||||||
_fileLogger
|
_fileLogger,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +67,7 @@ public partial class GameBootstrap : Node
|
|||||||
registry.Register(new EchoCommand());
|
registry.Register(new EchoCommand());
|
||||||
registry.Register(new ClearCommand());
|
registry.Register(new ClearCommand());
|
||||||
registry.Register(new NoclipCommand(Player));
|
registry.Register(new NoclipCommand(Player));
|
||||||
registry.Register(new SpawnItemCommand(ItemSpawner, PlayerInteraction));
|
registry.Register(new SpawnItemCommand(ItemSpawner, _itemDefinitions, PlayerInteraction));
|
||||||
registry.Register(new HostCommand(MultiplayerController));
|
registry.Register(new HostCommand(MultiplayerController));
|
||||||
registry.Register(new JoinCommand(MultiplayerController));
|
registry.Register(new JoinCommand(MultiplayerController));
|
||||||
|
|
||||||
@@ -59,28 +77,40 @@ public partial class GameBootstrap : Node
|
|||||||
private void InitializeMultiplayer()
|
private void InitializeMultiplayer()
|
||||||
{
|
{
|
||||||
MultiplayerController.UsePeerFactory(new EnetMultiplayerPeerFactory());
|
MultiplayerController.UsePeerFactory(new EnetMultiplayerPeerFactory());
|
||||||
MultiplayerController.Connected += () => _logger.Info("Multiplayer", "Connected successfully.");
|
MultiplayerController.Connected += () =>
|
||||||
MultiplayerController.ConnectionFailed += () => _logger.Warning("Multiplayer", "Connection failed.");
|
_logger.Info("Multiplayer", "Connected successfully.");
|
||||||
MultiplayerController.Disconnected += () => _logger.Warning("Multiplayer", "Server disconnected.");
|
MultiplayerController.ConnectionFailed += () =>
|
||||||
|
_logger.Warning("Multiplayer", "Connection failed.");
|
||||||
|
MultiplayerController.Disconnected += () =>
|
||||||
|
_logger.Warning("Multiplayer", "Server disconnected.");
|
||||||
MultiplayerController.PeerJoined += id => _logger.Info("Multiplayer", $"Peer {id} joined.");
|
MultiplayerController.PeerJoined += id => _logger.Info("Multiplayer", $"Peer {id} joined.");
|
||||||
MultiplayerController.PeerLeft += id => _logger.Info("Multiplayer", $"Peer {id} left.");
|
MultiplayerController.PeerLeft += id => _logger.Info("Multiplayer", $"Peer {id} left.");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeItems()
|
private void InitializeItems()
|
||||||
{
|
{
|
||||||
|
ItemInstanceRegistry itemInstanceRegistry = new();
|
||||||
|
ItemWorld.UseRegistry(itemInstanceRegistry);
|
||||||
ItemWorld.RegisterSceneItems();
|
ItemWorld.RegisterSceneItems();
|
||||||
ItemSpawner.Initialize();
|
|
||||||
|
_itemDefinitions = new ItemDefinitionRegistry();
|
||||||
|
ItemScenes.UseRegistry(_itemDefinitions);
|
||||||
|
ItemScenes.Initialize();
|
||||||
|
|
||||||
InteractionRegistry interactionRegistry = new();
|
InteractionRegistry interactionRegistry = new();
|
||||||
InteractionWorld.UseRegistry(interactionRegistry);
|
InteractionWorld.UseRegistry(interactionRegistry);
|
||||||
InteractionWorld.RegisterSceneTargets();
|
InteractionWorld.RegisterSceneTargets();
|
||||||
|
|
||||||
PlayerHeldItemPresenter presenter = new(
|
HeldItemPresenter presenter = new(
|
||||||
ItemWorld,
|
ItemWorld,
|
||||||
PlayerInteraction.LeftHandAnchor,
|
PlayerInteraction.LeftHandAnchor,
|
||||||
PlayerInteraction.RightHandAnchor
|
PlayerInteraction.RightHandAnchor
|
||||||
);
|
);
|
||||||
ItemInteractionService itemInteractions = new(new HandsInventory(), ItemWorld, presenter);
|
ItemInteractionService itemInteractions = new(
|
||||||
|
new HandsInventory(),
|
||||||
|
itemInstanceRegistry,
|
||||||
|
presenter
|
||||||
|
);
|
||||||
Interact interactions = new(interactionRegistry);
|
Interact interactions = new(interactionRegistry);
|
||||||
PlayerInteraction.Initialize(new PlayerId(Guid.NewGuid()), itemInteractions, interactions);
|
PlayerInteraction.Initialize(new PlayerId(Guid.NewGuid()), itemInteractions, interactions);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,10 @@
|
|||||||
[ext_resource type="Script" uid="uid://drctu5y21ey2y" path="res://src/Godot/GameBootstrap.cs" id="3_bootstrap"]
|
[ext_resource type="Script" uid="uid://drctu5y21ey2y" path="res://src/Godot/GameBootstrap.cs" id="3_bootstrap"]
|
||||||
[ext_resource type="PackedScene" uid="uid://bk0771dvonxh8" path="res://src/Features/Console/Godot/ConsoleController.tscn" id="4_console"]
|
[ext_resource type="PackedScene" uid="uid://bk0771dvonxh8" path="res://src/Features/Console/Godot/ConsoleController.tscn" id="4_console"]
|
||||||
[ext_resource type="Script" uid="uid://drqx0wiw0sbj1" path="res://src/Features/Multiplayer/Godot/MultiplayerController.cs" id="5_multiplayer"]
|
[ext_resource type="Script" uid="uid://drqx0wiw0sbj1" path="res://src/Features/Multiplayer/Godot/MultiplayerController.cs" id="5_multiplayer"]
|
||||||
[ext_resource type="Script" uid="uid://f8n3b6w1k5r9m" path="res://src/Features/Items/Godot/ItemWorldController.cs" id="6_items"]
|
[ext_resource type="Script" uid="uid://f8n3b6w1k5r9m" path="res://src/Features/Items/Godot/ItemControllerRegistryController.cs" id="6_items"]
|
||||||
[ext_resource type="PackedScene" uid="uid://cv8m3q1y6r4nf" path="res://src/Features/Items/Godot/Flashlight/FlashlightItem.tscn" id="7_flashlight"]
|
[ext_resource type="PackedScene" uid="uid://cv8m3q1y6r4nf" path="res://src/Features/Items/Godot/Flashlight/FlashlightItem.tscn" id="7_flashlight"]
|
||||||
[ext_resource type="Script" uid="uid://c3x9m6w2q8r1b" path="res://src/Features/Items/Godot/ItemSpawnerController.cs" id="8_spawner"]
|
[ext_resource type="Script" uid="uid://c3x9m6w2q8r1b" path="res://src/Features/Items/Godot/ItemSpawnerController.cs" id="8_spawner"]
|
||||||
|
[ext_resource type="Script" path="res://src/Features/Items/Godot/ItemSceneRegistryController.cs" id="8b_catalog"]
|
||||||
[ext_resource type="Script" uid="uid://f6q1c9m4w7v2n" path="res://src/Features/Interactions/Godot/InteractionsRegistryController.cs" id="9_interactions"]
|
[ext_resource type="Script" uid="uid://f6q1c9m4w7v2n" path="res://src/Features/Interactions/Godot/InteractionsRegistryController.cs" id="9_interactions"]
|
||||||
|
|
||||||
[sub_resource type="PanoramaSkyMaterial" id="PanoramaSkyMaterial_epykj"]
|
[sub_resource type="PanoramaSkyMaterial" id="PanoramaSkyMaterial_epykj"]
|
||||||
@@ -22,12 +23,13 @@ background_mode = 2
|
|||||||
sky = SubResource("Sky_1kpcd")
|
sky = SubResource("Sky_1kpcd")
|
||||||
glow_enabled = true
|
glow_enabled = true
|
||||||
|
|
||||||
[node name="GameWorld" type="Node" unique_id=222644234 node_paths=PackedStringArray("Console", "Player", "PlayerInteraction", "ItemWorld", "ItemSpawner", "InteractionWorld", "MultiplayerController")]
|
[node name="GameWorld" type="Node" unique_id=222644234 node_paths=PackedStringArray("Console", "Player", "PlayerInteraction", "ItemWorld", "ItemScenes", "ItemSpawner", "InteractionWorld", "MultiplayerController")]
|
||||||
script = ExtResource("3_bootstrap")
|
script = ExtResource("3_bootstrap")
|
||||||
Console = NodePath("Console")
|
Console = NodePath("Console")
|
||||||
Player = NodePath("Player")
|
Player = NodePath("Player")
|
||||||
PlayerInteraction = NodePath("Player/PlayerInteraction")
|
PlayerInteraction = NodePath("Player/PlayerInteraction")
|
||||||
ItemWorld = NodePath("ItemWorld")
|
ItemWorld = NodePath("ItemWorld")
|
||||||
|
ItemScenes = NodePath("ItemScenesRegistry")
|
||||||
ItemSpawner = NodePath("ItemSpawner")
|
ItemSpawner = NodePath("ItemSpawner")
|
||||||
InteractionWorld = NodePath("InteractionWorld")
|
InteractionWorld = NodePath("InteractionWorld")
|
||||||
MultiplayerController = NodePath("Multiplayer")
|
MultiplayerController = NodePath("Multiplayer")
|
||||||
@@ -50,11 +52,16 @@ script = ExtResource("5_multiplayer")
|
|||||||
[node name="ItemWorld" type="Node" parent="."]
|
[node name="ItemWorld" type="Node" parent="."]
|
||||||
script = ExtResource("6_items")
|
script = ExtResource("6_items")
|
||||||
|
|
||||||
[node name="ItemSpawner" type="Node" parent="." node_paths=PackedStringArray("ItemsRoot", "ItemWorld")]
|
[node name="ItemScenesRegistry" type="Node" parent="." node_paths=PackedStringArray("ItemWorld")]
|
||||||
|
script = ExtResource("8b_catalog")
|
||||||
|
ItemWorld = NodePath("../ItemWorld")
|
||||||
|
ItemScenes = Array[PackedScene]([ExtResource("7_flashlight")])
|
||||||
|
|
||||||
|
[node name="ItemSpawner" type="Node" parent="." node_paths=PackedStringArray("ItemsRoot", "ItemWorld", "Scenes")]
|
||||||
script = ExtResource("8_spawner")
|
script = ExtResource("8_spawner")
|
||||||
ItemsRoot = NodePath("../Entities")
|
ItemsRoot = NodePath("../Entities")
|
||||||
ItemWorld = NodePath("../ItemWorld")
|
ItemWorld = NodePath("../ItemWorld")
|
||||||
ItemScenes = Array[PackedScene]([ExtResource("7_flashlight")])
|
Scenes = NodePath("../ItemScenesRegistry")
|
||||||
|
|
||||||
[node name="InteractionWorld" type="Node" parent="."]
|
[node name="InteractionWorld" type="Node" parent="."]
|
||||||
script = ExtResource("9_interactions")
|
script = ExtResource("9_interactions")
|
||||||
|
|||||||
Reference in New Issue
Block a user