Add Recipemanager that loads recipes from YAML

This commit is contained in:
JonShard
2026-07-17 13:54:14 +02:00
parent 840908e9ff
commit 4d06e72bc1
133 changed files with 9121 additions and 175 deletions
+107 -13
View File
@@ -1,24 +1,118 @@
class_name CombinableItem
extends Node
## Identity of this item, referenced by other items' recipes.
@export var id: StringName
const RECIPE_MANAGER = preload("res://RecipeManager.gd")
## Recipes describing what the item holding this component turns into when
## another item is combined into it while snapped. Recipes are directional:
## only the snapped (base) item's recipes are consulted.
@export var recipes: Array[CombineRecipe] = []
@onready var combine_area_3d: Area3D = $Area3d
@onready var _pickable: XRToolsPickable = get_parent() as XRToolsPickable
# Guard so a combine only fires once.
var _combining: bool = false
var _food_item: FoodItem
func _ready() -> void:
if id.is_empty():
push_error("CombinableItem on ", get_parent().name, " has no 'id' set.")
print("CombinableItem _ready(): ", _pickable.name)
_food_item = get_parent().get_node_or_null("FoodItem") as FoodItem
if not _food_item:
push_error("CombinableItem is missing FoodItem reference. must be a sibling of a FoodItem on ", get_parent().name, ".")
# Detect items whether held (layer 17 "Held Objects") or loose
# (layer 3 "Pickable Objects"). Don't advertise a layer of our own.
combine_area_3d.collision_mask = 0b0000_0000_0000_0001_0000_0000_0000_0100
combine_area_3d.collision_layer = 0
# Deferred: _ready can run during a physics signal flush (when a combine
# spawns the result), where toggling monitoring directly is forbidden.
set_deferred("monitoring", false)
set_deferred("monitorable", false)
if not _pickable:
push_error("CombineZone must be a grand child of an XRToolsPickable.")
return
if not _food_item:
push_error("CombineZone requires a FoodItem sibling on ", _pickable.name, ".")
return
_pickable.picked_up.connect(_on_item_picked_up)
_pickable.dropped.connect(_on_item_dropped)
combine_area_3d.body_entered.connect(_on_body_entered)
## Returns the scene this item becomes when combined with [param other_id],
## or null if there is no matching recipe.
func get_result_for(other_id: StringName) -> PackedScene:
for recipe in recipes:
if recipe and recipe.ingredient_id == other_id:
return recipe.result
return RECIPE_MANAGER.get_combination(_food_item.id, other_id)
## Trigger area that lets a snapped item combine with another item pushed into
## it. The zone only monitors while its owning item is held by an
## [XRToolsSnapZone]; when an item carrying a matching [FoodItem.id]
## enters, the snapped (base) item transforms into the recipe result and the
## incoming item is consumed.
# Enable the trigger only while snapped into a snap zone (not hand-held).
func _on_item_picked_up(_item: Node3D) -> void:
var by := _pickable.get_picked_up_by()
var snapped: bool = by != null and by.has_method("is_xr_class") and by.is_xr_class("XRToolsSnapZone")
set_deferred("monitoring", snapped)
func _on_item_dropped(_item: Node3D) -> void:
set_deferred("monitoring", false)
func _on_body_entered(body: Node3D) -> void:
print("CombinableItem _on_body_entered")
if _combining or body == _pickable:
print("CombinableItem _on_body_entered: other is our own pickable")
return
var other := _find_food_item(body)
if not other:
print("CombinableItem _on_body_entered: other is not a foodItem")
return
var result: PackedScene = RecipeManager.get_combination(_food_item.id, other.id)
if not result:
print("CombinableItem _on_body_entered: There is no recipe for %s + %s" % [_food_item.id, other.id])
return
_combine(body, result)
# Transform the base item into [param result], consuming the incoming item.
func _combine(other_body: Node3D, result: PackedScene) -> void:
print("CombinableItem _combine: combining %s + %s into %s" % [_food_item.id, _find_food_item(other_body).id, result.resource_path])
_combining = true
var snap_zone := _pickable.get_picked_up_by()
if not snap_zone or not snap_zone.has_method("pick_up_object"):
push_warning("CombineZone: base item is not held by a snap zone; cannot combine.")
_combining = false
return
# Spawn the result at the base item's location, in world space.
var base_transform := _pickable.global_transform
var result_instance: Node3D = result.instantiate()
_pickable.get_tree().current_scene.add_child(result_instance)
result_instance.global_transform = base_transform
# Free the base item and consume the incoming item.
snap_zone.drop_object()
_pickable.queue_free()
if other_body.has_method("drop_and_free"):
other_body.drop_and_free()
else:
other_body.queue_free()
# Snap the result into the now-empty zone.
snap_zone.pick_up_object(result_instance)
# Find a FoodItem among the direct children of [param node].
func _find_food_item(node: Node) -> FoodItem:
if not node:
return null
for child in node.get_children():
if child is FoodItem:
return child
return null
+2 -7
View File
@@ -1,19 +1,14 @@
[gd_scene format=3 uid="uid://3lr2dhy62rhk"]
[ext_resource type="Script" uid="uid://dounic663dn5q" path="res://Prefabs/combinable_item.gd" id="1_q21ti"]
[ext_resource type="Script" uid="uid://coidnv8b2yvxr" path="res://Prefabs/combine_recipe.gd" id="2_anbu7"]
[ext_resource type="Script" uid="uid://e3im02nq5cye" path="res://Prefabs/combine_zone.gd" id="4_0om48"]
[sub_resource type="BoxShape3D" id="BoxShape3D_combine"]
size = Vector3(0.16, 0.16, 0.16)
[node name="CombinableItem" type="Node3D" unique_id=996936271]
script = ExtResource("1_q21ti")
id = &"some_food_item"
recipes = Array[ExtResource("2_anbu7")]([null])
[node name="CombineZone" type="Area3D" parent="." unique_id=1514178016]
script = ExtResource("4_0om48")
[node name="Area3d" type="Area3D" parent="." unique_id=1514178016]
[node name="CollisionShape3D" type="CollisionShape3D" parent="CombineZone" unique_id=706892516]
[node name="CollisionShape3D" type="CollisionShape3D" parent="Area3d" unique_id=706892516]
shape = SubResource("BoxShape3D_combine")
-103
View File
@@ -1,103 +0,0 @@
class_name CombineZone
extends Area3D
## Trigger area that lets a snapped item combine with another item pushed into
## it. The zone only monitors while its owning item is held by an
## [XRToolsSnapZone]; when an item carrying a matching [CombinableItem.id]
## enters, the snapped (base) item transforms into the recipe result and the
## incoming item is consumed.
# The pickable this zone belongs to (its parent).
@onready var _item: XRToolsPickable = get_parent().get_parent() as XRToolsPickable
# The base item's recipe data.
@onready var _combinable: CombinableItem = _find_combinable(_item)
# Guard so a combine only fires once.
var _combining: bool = false
func _ready() -> void:
# Detect items whether held (layer 17 "Held Objects") or loose
# (layer 3 "Pickable Objects"). Don't advertise a layer of our own.
collision_mask = 0b0000_0000_0000_0001_0000_0000_0000_0100
collision_layer = 0
# Deferred: _ready can run during a physics signal flush (when a combine
# spawns the result), where toggling monitoring directly is forbidden.
set_deferred("monitoring", false)
set_deferred("monitorable", false)
if not _item:
push_error("CombineZone must be a grand child of an XRToolsPickable.")
return
if not _combinable:
push_error("CombineZone requires a CombinableItem sibling on ", _item.name, ".")
return
_item.picked_up.connect(_on_item_picked_up)
_item.dropped.connect(_on_item_dropped)
body_entered.connect(_on_body_entered)
# Enable the trigger only while snapped into a snap zone (not hand-held).
func _on_item_picked_up(_pickable: Node3D) -> void:
var by := _item.get_picked_up_by()
var snapped: bool = by != null and by.has_method("is_xr_class") and by.is_xr_class("XRToolsSnapZone")
set_deferred("monitoring", snapped)
func _on_item_dropped(_pickable: Node3D) -> void:
set_deferred("monitoring", false)
func _on_body_entered(body: Node3D) -> void:
if _combining or body == _item:
return
var other := _find_combinable(body)
if not other:
return
var result: PackedScene = _combinable.get_result_for(other.id)
if not result:
return
_combine(body, result)
# Transform the base item into [param result], consuming the incoming item.
func _combine(other_body: Node3D, result: PackedScene) -> void:
_combining = true
var snap_zone := _item.get_picked_up_by()
if not snap_zone or not snap_zone.has_method("pick_up_object"):
push_warning("CombineZone: base item is not held by a snap zone; cannot combine.")
_combining = false
return
# Spawn the result at the base item's location, in world space.
var base_transform := _item.global_transform
var result_instance: Node3D = result.instantiate()
_item.get_tree().current_scene.add_child(result_instance)
result_instance.global_transform = base_transform
# Free the base item and consume the incoming item.
snap_zone.drop_object()
_item.queue_free()
if other_body.has_method("drop_and_free"):
other_body.drop_and_free()
else:
other_body.queue_free()
# Snap the result into the now-empty zone.
snap_zone.pick_up_object(result_instance)
# Find a CombinableItem among the direct children of [param node].
func _find_combinable(node: Node) -> CombinableItem:
if not node:
return null
for child in node.get_children():
if child is CombinableItem:
return child
return null
-1
View File
@@ -1 +0,0 @@
uid://e3im02nq5cye