Files
gamedev-the-steward/simulation/animals/AnimalFactory.gd
T
2026-08-12 20:50:52 +02:00

118 lines
4.2 KiB
GDScript

class_name AnimalFactory
extends RefCounted
const CONFIGURATION_KEYS := [
"initial_hunger",
"initial_enabled",
"can_npcs_feed",
"can_player_feed",
"debug_label_enabled",
"initial_routine_site_id",
"initial_next_routine_tick",
]
var last_error := ""
var _catalog: AnimalCatalog
func _init(catalog: AnimalCatalog = null) -> void:
_catalog = catalog if catalog != null else AnimalCatalog.create_core()
if _catalog == null:
last_error = "Animal catalog could not be loaded"
elif not _catalog.rebuild().is_empty():
last_error = "; ".join(_catalog.get_errors())
func get_catalog() -> AnimalCatalog:
return _catalog
func create_presentation(
animal_definition_id: StringName,
animal_id: StringName,
display_name: String,
configuration: Dictionary = {}
) -> AnimalNode:
last_error = ""
if _catalog == null or not _catalog.has_definition(animal_definition_id):
return _fail("Unknown animal definition '%s'" % animal_definition_id)
if not AnimalDefinition.is_valid_stable_id(animal_id):
return _fail("animal_id is invalid")
if display_name.strip_edges().is_empty():
return _fail("display_name is empty")
if not _configuration_is_valid(configuration):
return null
var definition := _catalog.get_definition(animal_definition_id)
var presentation_scene := load(definition.presentation_scene_path) as PackedScene
var node := (
presentation_scene.instantiate() as AnimalNode if presentation_scene != null else null
)
if node == null:
return _fail(
"Presentation scene for '%s' must instantiate AnimalNode" % animal_definition_id
)
if not node.apply_animal_definition(definition):
node.free()
return _fail("Presentation rejected definition '%s'" % animal_definition_id)
node.animal_id = animal_id
node.display_name = display_name
_apply_configuration(node, configuration)
return node
func _configuration_is_valid(configuration: Dictionary) -> bool:
for key in configuration:
if (key is not String and key is not StringName) or String(key) not in CONFIGURATION_KEYS:
return _fail_bool("Unknown animal presentation configuration '%s'" % key)
if configuration.has("initial_hunger"):
if (
configuration["initial_hunger"] is not int
and configuration["initial_hunger"] is not float
):
return _fail_bool("initial_hunger must be numeric")
var hunger := float(configuration["initial_hunger"])
if not is_finite(hunger) or hunger < 0.0 or hunger > 100.0:
return _fail_bool("initial_hunger must be between 0 and 100")
if (
configuration.has("initial_routine_site_id")
and not AnimalDefinition.is_valid_stable_id(configuration["initial_routine_site_id"])
):
return _fail_bool("initial_routine_site_id is invalid")
if configuration.has("initial_next_routine_tick"):
if configuration["initial_next_routine_tick"] is not int:
return _fail_bool("initial_next_routine_tick must be an int")
if int(configuration["initial_next_routine_tick"]) < 0:
return _fail_bool("initial_next_routine_tick must not be negative")
for key in ["initial_enabled", "can_npcs_feed", "can_player_feed", "debug_label_enabled"]:
if configuration.has(key) and configuration[key] is not bool:
return _fail_bool("%s must be a bool" % key)
return true
func _apply_configuration(node: AnimalNode, configuration: Dictionary) -> void:
if configuration.has("initial_hunger"):
node.initial_hunger = float(configuration["initial_hunger"])
if configuration.has("initial_enabled"):
node.initial_enabled = bool(configuration["initial_enabled"])
if configuration.has("can_npcs_feed"):
node.can_npcs_feed = bool(configuration["can_npcs_feed"])
if configuration.has("can_player_feed"):
node.can_player_feed = bool(configuration["can_player_feed"])
if configuration.has("debug_label_enabled"):
node.debug_label_enabled = bool(configuration["debug_label_enabled"])
if configuration.has("initial_routine_site_id"):
node.initial_routine_site_id = StringName(configuration["initial_routine_site_id"])
if configuration.has("initial_next_routine_tick"):
node.initial_next_routine_tick = int(configuration["initial_next_routine_tick"])
func _fail(message: String) -> AnimalNode:
last_error = message
return null
func _fail_bool(message: String) -> bool:
last_error = message
return false