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
+206
View File
@@ -0,0 +1,206 @@
class_name RecipeManager
extends Node
static var _recipes: Dictionary = {}
static var _combining_map: Dictionary = {}
static var _scene_paths: Dictionary = {}
static var _loaded: bool = false
static func load_recipes() -> void:
if _loaded:
return
var recipe_path := "res://recipes.yaml"
if not FileAccess.file_exists(recipe_path):
push_error("RecipeManager: recipes.yaml was not found at %s" % recipe_path)
return
var yaml_available := ClassDB.class_exists("YAML")
if yaml_available:
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
static func get_combination(first_id: StringName, second_id: StringName) -> PackedScene:
load_recipes()
var result_id = _combining_map.get(_make_pair_key(first_id, second_id))
if result_id == null:
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), "")
if typeof(scene_path) != TYPE_STRING or scene_path.is_empty():
push_error("RecipeManager: no scene mapping found for item id '%s'" % item_id)
return null
var scene = ResourceLoader.load(scene_path)
if not scene:
push_error("RecipeManager: failed to load scene '%s' for item '%s'" % [scene_path, item_id])
return null
return scene as PackedScene
static func print_all_recipes() -> void:
load_recipes()
if not _loaded:
print("RecipeManager: could not load recipes.yaml; no recipes printed.")
return
print("RecipeManager: Loaded recipes")
var combining = _recipes.get("combining", {})
if typeof(combining) != TYPE_DICTIONARY:
print("RecipeManager: combining recipes are not available in recipes.yaml")
return
if combining.is_empty():
print("RecipeManager: no combining recipes found")
return
for result_id in combining.keys():
var recipe_entries = _get_recipe_entries(combining[result_id])
if recipe_entries.is_empty():
print("RecipeManager: invalid combining recipe for '%s'" % result_id)
continue
for recipe in recipe_entries:
if typeof(recipe) == TYPE_ARRAY and recipe.size() == 2:
var first = str(recipe[0])
var second = str(recipe[1])
print("%s + %s -> %s" % [first, second, result_id])
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()
var items = _recipes.get("items", {})
if typeof(items) != TYPE_DICTIONARY:
return
for item_id in items.keys():
var item_def = items[item_id]
if typeof(item_def) != TYPE_DICTIONARY:
continue
var scene_path = item_def.get("scene", "")
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:
return recipes
if recipe_value.is_empty():
return recipes
if typeof(recipe_value[0]) == TYPE_ARRAY:
for recipe in recipe_value:
if typeof(recipe) == TYPE_ARRAY and recipe.size() == 2:
recipes.append(recipe)
else:
if recipe_value.size() == 2:
recipes.append(recipe_value)
return recipes
static func _build_combining_map() -> void:
_combining_map.clear()
var combining = _recipes.get("combining", {})
for result_id in combining.keys():
var recipe_entries = _get_recipe_entries(combining[result_id])
if recipe_entries.is_empty():
push_error("RecipeManager: combining recipe '%s' must contain at least one valid recipe" % result_id)
continue
for recipe in recipe_entries:
var first = StringName(recipe[0])
var second = StringName(recipe[1])
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))
return StringName(str(second_id) + "|" + str(first_id))