360 lines
13 KiB
GDScript
360 lines
13 KiB
GDScript
class_name RecipeManager
|
|
extends Node
|
|
|
|
static var _recipes: Dictionary
|
|
static var _combining_map: Dictionary
|
|
static var _cooking_map: Dictionary
|
|
static var _chopping_map: Dictionary
|
|
static var _rolling_map: Dictionary
|
|
static var _augmenting_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:
|
|
push_warning("RecipeManager: YAML addon parsed recipes.yaml but returned an unsupported structure")
|
|
|
|
_recipes = data
|
|
_build_scene_paths()
|
|
_build_combining_map()
|
|
_build_cooking_map()
|
|
_build_chopping_map()
|
|
_build_rolling_map()
|
|
_build_augmenting_map()
|
|
_loaded = true
|
|
|
|
|
|
static func get_combination_result(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_cooking_result(ingredient_id: StringName) -> String:
|
|
load_recipes()
|
|
for cooked_id in _cooking_map.keys():
|
|
var cook_defs = _cooking_map[cooked_id]
|
|
# cook_defs may be an Array of recipe dicts (new) or a single dict (backwards compat)
|
|
if typeof(cook_defs) == TYPE_ARRAY:
|
|
for cook_def in cook_defs:
|
|
if cook_def["ingredient"] == ingredient_id:
|
|
return cooked_id
|
|
else:
|
|
var cook_def = cook_defs
|
|
if cook_def["ingredient"] == ingredient_id:
|
|
return cooked_id
|
|
return ""
|
|
|
|
|
|
static func get_cooking_time(ingredient_id: StringName) -> float:
|
|
load_recipes()
|
|
for cooked_id in _cooking_map.keys():
|
|
var cook_defs = _cooking_map[cooked_id]
|
|
if typeof(cook_defs) == TYPE_ARRAY:
|
|
for cook_def in cook_defs:
|
|
if cook_def["ingredient"] == ingredient_id:
|
|
return cook_def["time"]
|
|
else:
|
|
var cook_def = cook_defs
|
|
if cook_def["ingredient"] == ingredient_id:
|
|
return cook_def["time"]
|
|
return 0.0
|
|
|
|
|
|
static func get_chopping_result(ingredient_id: StringName) -> String:
|
|
load_recipes()
|
|
for chopped_id in _chopping_map.keys():
|
|
var chop_def = _chopping_map[chopped_id]
|
|
if chop_def["ingredient"] == ingredient_id:
|
|
return chopped_id
|
|
return ""
|
|
|
|
|
|
static func get_chopping_work(ingredient_id: StringName) -> float:
|
|
load_recipes()
|
|
for chopped_id in _chopping_map.keys():
|
|
var chop_def = _chopping_map[chopped_id]
|
|
if chop_def["ingredient"] == ingredient_id:
|
|
return chop_def["work"]
|
|
return 0.0
|
|
|
|
|
|
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:
|
|
SweetLogger.warning("could not load recipes.yaml; no recipes printed.", [], "RecipeManager.gd", "print_all_recipes")
|
|
return
|
|
|
|
if _combining_map.is_empty():
|
|
SweetLogger.warning("no combining recipes found", [], "RecipeManager.gd", "print_all_recipes")
|
|
return
|
|
|
|
SweetLogger.info("#### RecipeManager: Loaded recipes ####", [], "RecipeManager.gd", "print_all_recipes")
|
|
_print_combining_recipes()
|
|
SweetLogger.info("", [], "RecipeManager.gd", "print_all_recipes")
|
|
_print_cooking_recipes()
|
|
SweetLogger.info("", [], "RecipeManager.gd", "print_all_recipes")
|
|
_print_chopping_recipes()
|
|
SweetLogger.info("", [], "RecipeManager.gd", "print_all_recipes")
|
|
_print_rolling_recipes()
|
|
SweetLogger.info("", [], "RecipeManager.gd", "print_all_recipes")
|
|
_print_augmenting_recipes()
|
|
|
|
static func _print_combining_recipes() -> void:
|
|
for pair_key in _combining_map.keys():
|
|
var result_id = _combining_map[pair_key]
|
|
var key_str = str(pair_key)
|
|
var separator_index = key_str.find("|")
|
|
if separator_index == -1:
|
|
SweetLogger.warning("invalid combining map key '{0}'", [key_str], "RecipeManager.gd", "_print_combining_recipes")
|
|
continue
|
|
|
|
var first = key_str.substr(0, separator_index)
|
|
var second = key_str.substr(separator_index + 1, key_str.length() - separator_index - 1)
|
|
SweetLogger.info(" {0} <-combine-- {1} + {2}", [result_id, first, second], "RecipeManager.gd", "_print_combining_recipes")
|
|
|
|
|
|
static func _print_cooking_recipes() -> void:
|
|
for cooked_id in _cooking_map.keys():
|
|
var cook_defs = _cooking_map[cooked_id]
|
|
if typeof(cook_defs) == TYPE_ARRAY:
|
|
for cook_def in cook_defs:
|
|
SweetLogger.info(" {0} <-cook-- {1} - time: {2}", [cooked_id, cook_def["ingredient"], cook_def["time"]], "RecipeManager.gd", "_print_cooking_recipes")
|
|
else:
|
|
var cook_def = cook_defs
|
|
SweetLogger.info(" {0} <-cook-- {1} - time: {2}", [cooked_id, cook_def["ingredient"], cook_def["time"]], "RecipeManager.gd", "_print_cooking_recipes")
|
|
|
|
|
|
static func _print_chopping_recipes() -> void:
|
|
for chopped_id in _chopping_map.keys():
|
|
var chop_def = _chopping_map[chopped_id]
|
|
SweetLogger.info(" {0} <-chop-- {1} - work: {2}", [chopped_id, chop_def["ingredient"], chop_def["work"]], "RecipeManager.gd", "_print_chopping_recipes")
|
|
|
|
|
|
static func _print_rolling_recipes() -> void:
|
|
for rolled_id in _rolling_map.keys():
|
|
var roll_def = _rolling_map[rolled_id]
|
|
SweetLogger.info(" {0} <-roll-- {1} - work: {2}", [rolled_id, roll_def["ingredient"], roll_def["work"]], "RecipeManager.gd", "_print_rolling_recipes")
|
|
|
|
|
|
static func _print_augmenting_recipes() -> void:
|
|
for target_id in _augmenting_map.keys():
|
|
var augment_def = _augmenting_map[target_id]
|
|
for ingredient_id in augment_def.keys():
|
|
var attr_key = augment_def[ingredient_id]
|
|
SweetLogger.info(" {0} <-augment-- {1} adds attribute '{2}'", [target_id, ingredient_id, attr_key], "RecipeManager.gd", "_print_augmenting_recipes")
|
|
|
|
|
|
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
|
|
|
|
|
|
# Reads strucure into _combining_map:
|
|
# combining:
|
|
# hamburger:
|
|
# - [cooked_burger, burger_buns]
|
|
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 _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 _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))
|
|
|
|
|
|
# Reads structure into _cooking_map:
|
|
# cooking:
|
|
# cooked_burger:
|
|
# ingredient: raw_burger
|
|
# time: 3
|
|
# or allowing multiple recipes for the same cooked item:
|
|
# charcoal:
|
|
# - ingredient: cube
|
|
# time: 1
|
|
# - ingredient: some_other
|
|
# time: 2
|
|
static func _build_cooking_map() -> void:
|
|
_cooking_map.clear()
|
|
var cooking = _recipes.get("cooking", {})
|
|
if typeof(cooking) != TYPE_DICTIONARY:
|
|
return
|
|
for cooked_id in cooking.keys():
|
|
var cook_val = cooking[cooked_id]
|
|
var entries: Array = []
|
|
if typeof(cook_val) == TYPE_ARRAY:
|
|
for entry in cook_val:
|
|
if typeof(entry) != TYPE_DICTIONARY:
|
|
push_error("RecipeManager: cooking recipe for '%s' contains a non-dictionary entry" % cooked_id)
|
|
continue
|
|
entries.append(entry)
|
|
elif typeof(cook_val) == TYPE_DICTIONARY:
|
|
entries.append(cook_val)
|
|
else:
|
|
push_error("RecipeManager: cooking recipe for '%s' must be a dictionary or array of dictionaries" % cooked_id)
|
|
continue
|
|
|
|
var parsed_entries: Array = []
|
|
for cook_def in entries:
|
|
var ingredient = cook_def.get("ingredient", "")
|
|
if typeof(ingredient) != TYPE_STRING or str(ingredient).is_empty():
|
|
push_error("RecipeManager: cooking recipe '%s' missing a valid 'ingredient' field" % cooked_id)
|
|
continue
|
|
var time_val = cook_def.get("time", null)
|
|
if typeof(time_val) != TYPE_INT and typeof(time_val) != TYPE_FLOAT:
|
|
push_error("RecipeManager: cooking recipe '%s' missing a valid 'time' field" % cooked_id)
|
|
continue
|
|
parsed_entries.append({"ingredient": StringName(str(ingredient)), "time": float(time_val)})
|
|
|
|
if not parsed_entries.is_empty():
|
|
_cooking_map[StringName(cooked_id)] = parsed_entries
|
|
|
|
|
|
# Reads structure into _chopping_map:
|
|
# chopping:
|
|
# chopped_onion:
|
|
# ingredient: onion
|
|
# work: 2
|
|
static func _build_chopping_map() -> void:
|
|
_chopping_map.clear()
|
|
var chopping = _recipes.get("chopping", {})
|
|
if typeof(chopping) != TYPE_DICTIONARY:
|
|
return
|
|
for chopped_id in chopping.keys():
|
|
var chop_def = chopping[chopped_id]
|
|
if typeof(chop_def) != TYPE_DICTIONARY:
|
|
push_error("RecipeManager: chopping recipe for '%s' must be a dictionary" % chopped_id)
|
|
continue
|
|
var ingredient = chop_def.get("ingredient", "")
|
|
if typeof(ingredient) != TYPE_STRING or str(ingredient).is_empty():
|
|
push_error("RecipeManager: chopping recipe '%s' missing a valid 'ingredient' field" % chopped_id)
|
|
continue
|
|
var work_val = chop_def.get("work", null)
|
|
if typeof(work_val) != TYPE_INT and typeof(work_val) != TYPE_FLOAT:
|
|
push_error("RecipeManager: chopping recipe '%s' missing a valid 'work' field" % chopped_id)
|
|
continue
|
|
# store chopped item -> { ingredient: StringName, work: float }
|
|
_chopping_map[StringName(chopped_id)] = {"ingredient": StringName(str(ingredient)), "work": float(work_val)}
|
|
|
|
|
|
# Reads structure into _rolling_map:
|
|
# rolling:
|
|
# pie_base:
|
|
# ingredient: dough
|
|
# work: 80
|
|
static func _build_rolling_map() -> void:
|
|
_rolling_map.clear()
|
|
var rolling = _recipes.get("rolling", {})
|
|
if typeof(rolling) != TYPE_DICTIONARY:
|
|
return
|
|
for rolled_id in rolling.keys():
|
|
var roll_def = rolling[rolled_id]
|
|
if typeof(roll_def) != TYPE_DICTIONARY:
|
|
push_error("RecipeManager: rolling recipe for '%s' must be a dictionary" % rolled_id)
|
|
continue
|
|
var ingredient = roll_def.get("ingredient", "")
|
|
if typeof(ingredient) != TYPE_STRING or str(ingredient).is_empty():
|
|
push_error("RecipeManager: rolling recipe '%s' missing a valid 'ingredient' field" % rolled_id)
|
|
continue
|
|
var work_val = roll_def.get("work", null)
|
|
if typeof(work_val) != TYPE_INT and typeof(work_val) != TYPE_FLOAT:
|
|
push_error("RecipeManager: rolling recipe '%s' missing a valid 'work' field" % rolled_id)
|
|
continue
|
|
# store rolled item -> { ingredient: StringName, work: float }
|
|
_rolling_map[StringName(rolled_id)] = {"ingredient": StringName(str(ingredient)), "work": float(work_val)}
|
|
|
|
|
|
# Reads structure into _augmenting_map:
|
|
# augmenting: # Process of adding an item onto another without changing it's type, but toggling attributes.
|
|
# cooked_burger:
|
|
# has_tomato: sliced_tomato
|
|
# has_cheese: cheese
|
|
static func _build_augmenting_map() -> void:
|
|
_augmenting_map.clear()
|
|
var augmenting = _recipes.get("augmenting", {})
|
|
if typeof(augmenting) != TYPE_DICTIONARY:
|
|
return
|
|
for target_id in augmenting.keys():
|
|
var augment_def = augmenting[target_id]
|
|
if typeof(augment_def) != TYPE_DICTIONARY:
|
|
push_error("RecipeManager: augmenting recipe for '%s' must be a dictionary" % target_id)
|
|
continue
|
|
var map: Dictionary = {}
|
|
for attr_key in augment_def.keys():
|
|
var ingredient = augment_def[attr_key]
|
|
if typeof(ingredient) != TYPE_STRING or str(ingredient).is_empty():
|
|
push_error("RecipeManager: augmenting recipe '%s' has invalid ingredient for '%s'" % [target_id, attr_key])
|
|
continue
|
|
# store ingredient -> attribute_key (e.g. sliced_tomato -> has_tomato)
|
|
map[StringName(str(ingredient))] = StringName(str(attr_key))
|
|
if not map.is_empty():
|
|
_augmenting_map[StringName(target_id)] = map
|
|
|
|
|