diff --git a/Prefabs/combinable_item.gd b/Prefabs/combinable_item.gd index 5bc271e..c1f114b 100644 --- a/Prefabs/combinable_item.gd +++ b/Prefabs/combinable_item.gd @@ -17,6 +17,13 @@ func _ready() -> void: if not _food_item: push_error("CombinableItem is missing FoodItem reference. must be a sibling of a FoodItem on ", get_parent().name, ".") + 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 + # 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 @@ -26,40 +33,22 @@ func _ready() -> void: 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 - + # Signals _pickable.picked_up.connect(_on_item_picked_up) _pickable.dropped.connect(_on_item_dropped) combine_area_3d.body_entered.connect(_on_body_entered) -func get_result_for(other_id: StringName) -> PackedScene: - 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) - +# If the other body is a FoodItem, check for a recipe and combine if one exists. func _on_body_entered(body: Node3D) -> void: print("CombinableItem _on_body_entered") if _combining or body == _pickable: @@ -78,12 +67,12 @@ func _on_body_entered(body: Node3D) -> void: _combine(body, result) - -# Transform the base item into [param result], consuming the incoming item. +# Instantiate the result of combination and free the two ingredient items 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 + # Get Snapzone 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.") @@ -96,10 +85,10 @@ func _combine(other_body: Node3D, result: PackedScene) -> void: _pickable.get_tree().current_scene.add_child(result_instance) result_instance.global_transform = base_transform - # Free the base item and consume the incoming item. + # Free the pickable / root of this item and consume the incoming item. snap_zone.drop_object() _pickable.queue_free() - if other_body.has_method("drop_and_free"): + if other_body.has_method("drop_and_free"): # XRToolsPickable has this method other_body.drop_and_free() else: other_body.queue_free() diff --git a/RecipeManager.gd b/RecipeManager.gd index 54b670e..ff3e8c1 100644 --- a/RecipeManager.gd +++ b/RecipeManager.gd @@ -3,7 +3,7 @@ extends Node static var _recipes: Dictionary = {} static var _combining_map: Dictionary = {} -static var _scene_paths: Dictionary = {} +static var _scene_paths: Dictionary static var _loaded: bool = false static func load_recipes() -> void: @@ -20,25 +20,14 @@ static func load_recipes() -> void: var result = ClassDB.class_call_static("YAML", "load_file", recipe_path) if result != null and not result.has_error(): var data = result.get_data() - if typeof(data) == TYPE_DICTIONARY: - _recipes = data - _build_scene_paths() - _build_combining_map() - _loaded = true - return - push_warning("RecipeManager: YAML addon parsed recipes.yaml but returned an unsupported structure; falling back to built-in parser") - else: - push_warning("RecipeManager: YAML addon failed to read %s; falling back to built-in parser: %s" % [recipe_path, result.get_error() if result != null else "unknown error"]) - - var fallback_data = _load_recipes_with_fallback_parser(recipe_path) - if typeof(fallback_data) != TYPE_DICTIONARY: - push_error("RecipeManager: failed to parse recipes.yaml with both YAML addon and built-in fallback") - return - - _recipes = fallback_data - _build_scene_paths() - _build_combining_map() - _loaded = true + if typeof(data) != TYPE_DICTIONARY: + push_warning("RecipeManager: YAML addon parsed recipes.yaml but returned an unsupported structure") + + _recipes = data + _build_scene_paths() + _build_combining_map() + _loaded = true + static func get_combination(first_id: StringName, second_id: StringName) -> PackedScene: load_recipes() @@ -47,6 +36,7 @@ static func get_combination(first_id: StringName, second_id: StringName) -> Pack return null return get_item_scene(result_id) + static func get_item_scene(item_id: StringName) -> PackedScene: load_recipes() var scene_path = _scene_paths.get(str(item_id), "") @@ -60,6 +50,7 @@ static func get_item_scene(item_id: StringName) -> PackedScene: return null return scene as PackedScene + static func print_all_recipes() -> void: load_recipes() if not _loaded: @@ -91,72 +82,6 @@ static func print_all_recipes() -> void: else: print("RecipeManager: invalid combining recipe for '%s'" % result_id) -static func _load_recipes_with_fallback_parser(recipe_path: String) -> Dictionary: - var file := FileAccess.open(recipe_path, FileAccess.READ) - if file == null: - push_error("RecipeManager: could not open %s: %s" % [recipe_path, FileAccess.get_open_error()]) - return {} - - var lines := file.get_as_text().split("\n") - var result := { - "items": {}, - "combining": {} - } - var current_section := "" - var current_result_id := "" - var current_item_id := "" - var current_item_def: Dictionary = {} - - for line in lines: - var trimmed := line.strip_edges() - if trimmed.is_empty() or trimmed.begins_with("#"): - continue - - var indent := line.length() - line.lstrip(" \t").length() - if indent == 0: - current_section = "" - current_result_id = "" - current_item_id = "" - if trimmed == "items:": - current_section = "items" - elif trimmed == "combining:": - current_section = "combining" - continue - - if current_section == "items": - if indent == 2 and trimmed.ends_with(":"): - current_item_id = trimmed.trim_suffix(":") - current_item_def = {} - result["items"][current_item_id] = current_item_def - continue - - if indent >= 4 and not current_item_id.is_empty(): - var parts := trimmed.split(":", true, 1) - if parts.size() == 2: - var key := parts[0].strip_edges() - var value := parts[1].strip_edges() - current_item_def[key] = value - continue - - if current_section == "combining": - if indent == 2 and trimmed.ends_with(":"): - current_result_id = trimmed.trim_suffix(":") - result["combining"][current_result_id] = [] - continue - - if trimmed.begins_with("- ") and not current_result_id.is_empty(): - var item_text := trimmed.substr(2).strip_edges() - if item_text.begins_with("[") and item_text.ends_with("]"): - var inner := item_text.substr(1, item_text.length() - 2).strip_edges() - var parts := inner.split(",", false) - var recipe := [] - for part in parts: - recipe.append(part.strip_edges()) - result["combining"][current_result_id].append(recipe) - else: - result["combining"][current_result_id].append([item_text]) - - return result static func _build_scene_paths() -> void: _scene_paths.clear() @@ -171,6 +96,7 @@ static func _build_scene_paths() -> void: if typeof(scene_path) == TYPE_STRING and not scene_path.is_empty(): _scene_paths[str(item_id)] = scene_path + static func _get_recipe_entries(recipe_value: Variant) -> Array: var recipes: Array = [] if typeof(recipe_value) != TYPE_ARRAY: @@ -186,6 +112,7 @@ static func _get_recipe_entries(recipe_value: Variant) -> Array: recipes.append(recipe_value) return recipes + static func _build_combining_map() -> void: _combining_map.clear() var combining = _recipes.get("combining", {}) @@ -200,6 +127,7 @@ static func _build_combining_map() -> void: var key = _make_pair_key(first, second) _combining_map[key] = result_id + static func _make_pair_key(first_id: StringName, second_id: StringName) -> StringName: if str(first_id) <= str(second_id): return StringName(str(first_id) + "|" + str(second_id)) diff --git a/recipes.yaml b/recipes.yaml index 422c779..1005071 100644 --- a/recipes.yaml +++ b/recipes.yaml @@ -1,19 +1,14 @@ items: hamburger: scene: res://Items/hamburger.tscn - type: meal burger_buns: scene: res://Items/BurgerBuns.tscn - type: ingredient cooked_burger: scene: res://Items/cooked_burger.tscn - type: ingredient cube: scene: res://Items/PickupCube.tscn - type: ingredient charcoal: scene: res://Items/Charcoal.tscn - type: ingredient combining: hamburger: @@ -21,3 +16,4 @@ combining: - [charcoal, charcoal] charcoal: - [cube, cube] +