refactor(data-loading): ♻️ Implement modular async data loading patterns for DataLoader, GameState, and BiomeRegistry to improve performance and maintainability
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
a84ce19982
commit
1d3bcd95b7
5 changed files with 553 additions and 405 deletions
81
engine/src/autoloads/biome_registry.gd
Normal file
81
engine/src/autoloads/biome_registry.gd
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
extends Node
|
||||
## Tag-based biome query registry. Loaded from the active environment's biomes.json.
|
||||
## The engine queries tags (e.g., "is_water") instead of matching biome ID strings.
|
||||
##
|
||||
## Architecture: Each environment (world/plane) has its own biome set defined in
|
||||
## biomes.json with semantic tags. The registry builds a cache on load_theme() and
|
||||
## exposes O(1) tag lookups. See engine/docs/ENVIRONMENT_ARCHITECTURE.md.
|
||||
##
|
||||
## Usage:
|
||||
## BiomeRegistry.has_tag("ocean", "is_water") # -> true
|
||||
## BiomeRegistry.get_biomes_with_tag("is_water") # -> ["ocean", "coast", ...]
|
||||
## BiomeRegistry.get_tags("volcano") # -> ["is_elevated", "is_volcanic"]
|
||||
|
||||
|
||||
## biome_id -> PackedStringArray of tags
|
||||
var _tag_cache: Dictionary = {}
|
||||
|
||||
## tag -> PackedStringArray of biome_ids
|
||||
var _reverse_cache: Dictionary = {}
|
||||
|
||||
## Whether the cache has been built at least once.
|
||||
var _loaded: bool = false
|
||||
|
||||
|
||||
func rebuild_from_data() -> void:
|
||||
## Rebuild tag caches from DataLoader's biome models.
|
||||
## Called after DataLoader.load_theme() completes.
|
||||
_tag_cache.clear()
|
||||
_reverse_cache.clear()
|
||||
|
||||
for biome: Variant in DataLoader.get_all_biomes():
|
||||
if biome == null:
|
||||
continue
|
||||
var biome_id: String = biome.id
|
||||
var tags: Array[String] = biome.tags
|
||||
_tag_cache[biome_id] = tags
|
||||
for tag: String in tags:
|
||||
if not _reverse_cache.has(tag):
|
||||
_reverse_cache[tag] = [] as Array[String]
|
||||
_reverse_cache[tag].append(biome_id)
|
||||
|
||||
_loaded = true
|
||||
|
||||
|
||||
func has_tag(biome_id: String, tag: String) -> bool:
|
||||
## Returns true if the biome has the given semantic tag.
|
||||
var tags: Variant = _tag_cache.get(biome_id)
|
||||
if tags == null:
|
||||
return false
|
||||
return tag in tags
|
||||
|
||||
|
||||
func get_tags(biome_id: String) -> Array[String]:
|
||||
## Returns all tags for a biome. Empty array if biome not found.
|
||||
var tags: Variant = _tag_cache.get(biome_id)
|
||||
if tags == null:
|
||||
return [] as Array[String]
|
||||
return tags
|
||||
|
||||
|
||||
func get_biomes_with_tag(tag: String) -> Array[String]:
|
||||
## Returns all biome IDs that have the given tag.
|
||||
var biomes: Variant = _reverse_cache.get(tag)
|
||||
if biomes == null:
|
||||
return [] as Array[String]
|
||||
return biomes
|
||||
|
||||
|
||||
func register_runtime_biome(biome_id: String, tags: Array[String]) -> void:
|
||||
## Register a dynamically-created biome (e.g., runtime terrain assignments).
|
||||
## Safe to call multiple times — overwrites previous tags for the same biome_id.
|
||||
_tag_cache[biome_id] = tags
|
||||
for tag: String in tags:
|
||||
if not _reverse_cache.has(tag):
|
||||
_reverse_cache[tag] = [] as Array[String]
|
||||
if biome_id not in _reverse_cache[tag]:
|
||||
_reverse_cache[tag].append(biome_id)
|
||||
|
||||
|
||||
func is_loaded() -> bool:
|
||||
return _loaded
|
||||
|
|
@ -2,62 +2,26 @@
|
|||
extends Node
|
||||
## Loads all JSON data files from the active theme pack.
|
||||
## Supports both single files (terrain.json) and split directories (units/).
|
||||
## Provides typed lookup functions for every data category.
|
||||
## Ecology accessors are delegated to _ecology (data_loader_ecology.gd).
|
||||
|
||||
const DATA_CATEGORIES: Array[String] = [
|
||||
"terrain",
|
||||
"units",
|
||||
"buildings",
|
||||
"techs",
|
||||
"spells",
|
||||
"races",
|
||||
"resources",
|
||||
"keywords",
|
||||
"improvements",
|
||||
"items",
|
||||
"promotions",
|
||||
"magical_promotions",
|
||||
"governments",
|
||||
"disciplines",
|
||||
"eras",
|
||||
"victories",
|
||||
"villages",
|
||||
"wilds",
|
||||
"npc_buildings",
|
||||
"difficulty",
|
||||
"setup",
|
||||
"ai_personalities",
|
||||
"map_types",
|
||||
"climate_params",
|
||||
"climate_spec",
|
||||
"hydrology_params",
|
||||
"ley_line_params",
|
||||
"social_policies",
|
||||
"events",
|
||||
"world_biomes",
|
||||
"world_flora",
|
||||
"world_fauna",
|
||||
"world_ecosystem",
|
||||
"world_traits",
|
||||
"terrain", "units", "buildings", "techs", "spells", "races",
|
||||
"resources", "keywords", "improvements", "items", "promotions",
|
||||
"magical_promotions", "governments", "disciplines", "eras", "victories",
|
||||
"villages", "wilds", "npc_buildings", "difficulty", "setup",
|
||||
"ai_personalities", "map_types", "climate_params", "climate_spec",
|
||||
"hydrology_params", "ley_line_params", "social_policies", "events",
|
||||
"world_biomes", "world_flora", "world_fauna", "world_ecosystem",
|
||||
"world_traits", "seed_easter_eggs", "throne_room",
|
||||
]
|
||||
|
||||
## Categories stored as raw parsed Dictionaries rather than ID-keyed entries.
|
||||
const RAW_CATEGORIES: Array[String] = [
|
||||
"setup",
|
||||
"climate_params",
|
||||
"climate_spec",
|
||||
"hydrology_params",
|
||||
"ley_line_params",
|
||||
"social_policies",
|
||||
"events",
|
||||
"world_biomes",
|
||||
"world_flora",
|
||||
"world_fauna",
|
||||
"world_ecosystem",
|
||||
"world_traits",
|
||||
"setup", "climate_params", "climate_spec", "hydrology_params",
|
||||
"ley_line_params", "social_policies", "events", "world_biomes",
|
||||
"world_flora", "world_fauna", "world_ecosystem", "world_traits",
|
||||
"seed_easter_eggs",
|
||||
]
|
||||
|
||||
## Mapping from world_* categories to their data subdirectory names.
|
||||
const _WORLD_DIR_MAP: Dictionary = {
|
||||
"world_biomes": "world/biomes",
|
||||
"world_flora": "world/flora",
|
||||
|
|
@ -66,45 +30,24 @@ const _WORLD_DIR_MAP: Dictionary = {
|
|||
"world_traits": "world/traits",
|
||||
}
|
||||
|
||||
# Preload model classes for typed deserialization
|
||||
const BiomeModelScript = preload("res://engine/src/models/world/biome.gd")
|
||||
const FloraProfileScript = preload("res://engine/src/models/world/flora_profile.gd")
|
||||
const TraitSetScript = preload("res://engine/src/models/world/species_traits.gd")
|
||||
const SubstrateTypeScript = preload("res://engine/src/models/world/substrate.gd")
|
||||
const MarineFaunaScript = preload("res://engine/src/models/world/marine_fauna.gd")
|
||||
const LandFaunaScript = preload("res://engine/src/models/world/land_fauna.gd")
|
||||
const AirFaunaScript = preload("res://engine/src/models/world/air_fauna.gd")
|
||||
const DataLoaderEcologyScript = preload(
|
||||
"res://engine/src/autoloads/data_loader_ecology.gd"
|
||||
)
|
||||
const DataLoaderWorldsScript = preload(
|
||||
"res://engine/src/autoloads/data_loader_worlds.gd"
|
||||
)
|
||||
|
||||
var _active_theme: String = ""
|
||||
var _data: Dictionary = {}
|
||||
## Raw parsed data for non-ID-keyed categories (e.g. setup, climate_params).
|
||||
var _raw: Dictionary = {}
|
||||
## Typed ecology model instances keyed by id.
|
||||
var _biomes: Dictionary = {} # id -> BiomeModel
|
||||
var _flora_profiles: Dictionary = {} # biome_id -> FloraProfile
|
||||
var _substrates: Dictionary = {} # id -> SubstrateType
|
||||
## Typed fauna parameter models (single instances, not collections).
|
||||
var _marine_fauna: Variant = null # MarineFaunaModel
|
||||
var _land_fauna: Variant = null # LandFaunaModel
|
||||
var _air_fauna: Variant = null # AirFaunaModel
|
||||
|
||||
var _ecology: DataLoaderEcologyScript = DataLoaderEcologyScript.new()
|
||||
var _worlds: DataLoaderWorldsScript = DataLoaderWorldsScript.new()
|
||||
|
||||
func _ready() -> void:
|
||||
for category: String in DATA_CATEGORIES:
|
||||
_data[category] = {}
|
||||
for category: String in RAW_CATEGORIES:
|
||||
_raw[category] = {}
|
||||
_clear_typed_caches()
|
||||
|
||||
|
||||
func _clear_typed_caches() -> void:
|
||||
_biomes.clear()
|
||||
_flora_profiles.clear()
|
||||
_substrates.clear()
|
||||
_marine_fauna = null
|
||||
_land_fauna = null
|
||||
_air_fauna = null
|
||||
|
||||
|
||||
func load_theme(theme_id: String) -> void:
|
||||
_active_theme = theme_id
|
||||
|
|
@ -112,57 +55,52 @@ func load_theme(theme_id: String) -> void:
|
|||
_data[category] = {}
|
||||
for category: String in RAW_CATEGORIES:
|
||||
_raw[category] = {}
|
||||
_clear_typed_caches()
|
||||
|
||||
var base_path: String = "res://games/%s/data" % theme_id
|
||||
|
||||
for category: String in DATA_CATEGORIES:
|
||||
# world_* categories use nested subdirectory paths
|
||||
var subdir: String = _WORLD_DIR_MAP.get(category, category)
|
||||
var dir_path: String = "%s/%s" % [base_path, subdir]
|
||||
var file_path: String = "%s/%s.json" % [base_path, subdir]
|
||||
|
||||
if DirAccess.dir_exists_absolute(dir_path):
|
||||
_load_category_dir(category, dir_path)
|
||||
elif FileAccess.file_exists(file_path):
|
||||
_load_json_file(category, file_path)
|
||||
|
||||
_deserialize_ecology()
|
||||
_ecology.deserialize(_raw)
|
||||
BiomeRegistry.rebuild_from_data()
|
||||
_log_load_summary()
|
||||
|
||||
func load_world(world_id: String) -> void:
|
||||
## Load a world definition from engine/src/worlds/{world_id}/.
|
||||
## Delegates to _worlds (data_loader_worlds.gd).
|
||||
_worlds.load_world(world_id, _ecology)
|
||||
|
||||
func get_world_manifest() -> Dictionary:
|
||||
return _worlds.manifest
|
||||
|
||||
func get_active_world() -> String:
|
||||
return _worlds.active_world
|
||||
|
||||
func _load_category_dir(category: String, dir_path: String) -> void:
|
||||
## Load all .json files from a category directory.
|
||||
## For raw categories (events, etc.): merge into a single dict keyed by filename.
|
||||
## For array categories (units, spells, etc.): merge by ID as before.
|
||||
if category in RAW_CATEGORIES:
|
||||
_load_raw_category_dir(category, dir_path)
|
||||
return
|
||||
|
||||
var dir: DirAccess = DirAccess.open(dir_path)
|
||||
if dir == null:
|
||||
push_warning("DataLoader: Cannot open directory %s" % dir_path)
|
||||
return
|
||||
|
||||
dir.list_dir_begin()
|
||||
var file_name: String = dir.get_next()
|
||||
while file_name != "":
|
||||
if file_name.ends_with(".json") and not dir.current_is_dir():
|
||||
var full_path: String = "%s/%s" % [dir_path, file_name]
|
||||
_load_json_file(category, full_path)
|
||||
_load_json_file(category, "%s/%s" % [dir_path, file_name])
|
||||
file_name = dir.get_next()
|
||||
dir.list_dir_end()
|
||||
|
||||
|
||||
func _load_raw_category_dir(category: String, dir_path: String) -> void:
|
||||
## Load a raw category from a directory: each file becomes a key in the merged dict.
|
||||
## e.g. events/volcanic.json → _raw["events"]["volcanic"] = {...}
|
||||
var merged: Dictionary = {}
|
||||
var dir: DirAccess = DirAccess.open(dir_path)
|
||||
if dir == null:
|
||||
push_warning("DataLoader: Cannot open raw directory %s" % dir_path)
|
||||
return
|
||||
|
||||
dir.list_dir_begin()
|
||||
var file_name: String = dir.get_next()
|
||||
while file_name != "":
|
||||
|
|
@ -172,50 +110,33 @@ func _load_raw_category_dir(category: String, dir_path: String) -> void:
|
|||
if file != null:
|
||||
var json: JSON = JSON.new()
|
||||
if json.parse(file.get_as_text()) == OK and json.data is Dictionary:
|
||||
var key: String = file_name.get_basename()
|
||||
merged[key] = json.data
|
||||
merged[file_name.get_basename()] = json.data
|
||||
file.close()
|
||||
file_name = dir.get_next()
|
||||
dir.list_dir_end()
|
||||
_raw[category] = merged
|
||||
|
||||
|
||||
func _load_json_file(category: String, file_path: String) -> void:
|
||||
var file: FileAccess = FileAccess.open(file_path, FileAccess.READ)
|
||||
if file == null:
|
||||
push_warning(
|
||||
(
|
||||
"DataLoader: Failed to open %s: %s"
|
||||
% [file_path, error_string(FileAccess.get_open_error())]
|
||||
)
|
||||
)
|
||||
push_warning("DataLoader: Failed to open %s: %s" % [
|
||||
file_path, error_string(FileAccess.get_open_error())])
|
||||
return
|
||||
|
||||
var json_text: String = file.get_as_text()
|
||||
file.close()
|
||||
|
||||
var json: JSON = JSON.new()
|
||||
var parse_result: Error = json.parse(json_text)
|
||||
if parse_result != OK:
|
||||
push_error(
|
||||
(
|
||||
"DataLoader: JSON parse error in %s line %d: %s"
|
||||
% [file_path, json.get_error_line(), json.get_error_message()]
|
||||
)
|
||||
)
|
||||
if json.parse(json_text) != OK:
|
||||
push_error("DataLoader: JSON parse error in %s line %d: %s" % [
|
||||
file_path, json.get_error_line(), json.get_error_message()])
|
||||
return
|
||||
|
||||
if category in RAW_CATEGORIES:
|
||||
if json.data is Dictionary:
|
||||
_raw[category] = json.data
|
||||
return
|
||||
|
||||
var entries: Array = _extract_entries(json.data, category)
|
||||
for entry: Variant in entries:
|
||||
for entry: Variant in _extract_entries(json.data, category):
|
||||
if entry is Dictionary and entry.has("id"):
|
||||
_data[category][entry["id"]] = entry
|
||||
|
||||
|
||||
func _extract_entries(parsed: Variant, category: String) -> Array:
|
||||
if parsed is Array:
|
||||
return parsed
|
||||
|
|
@ -226,7 +147,6 @@ func _extract_entries(parsed: Variant, category: String) -> Array:
|
|||
var val: Variant = parsed[key]
|
||||
if val is Array:
|
||||
return val
|
||||
# Nested dict where each key is an entry id (e.g. ai_personalities format)
|
||||
if val is Dictionary:
|
||||
return _extract_entries(val, "")
|
||||
if parsed.has("entries") and parsed["entries"] is Array:
|
||||
|
|
@ -240,7 +160,6 @@ func _extract_entries(parsed: Variant, category: String) -> Array:
|
|||
results.append(entry)
|
||||
return results
|
||||
|
||||
|
||||
func _log_load_summary() -> void:
|
||||
var total: int = 0
|
||||
for category: String in DATA_CATEGORIES:
|
||||
|
|
@ -249,203 +168,188 @@ func _log_load_summary() -> void:
|
|||
total += count
|
||||
print("DataLoader: Loaded %d entries from theme '%s'" % [total, _active_theme])
|
||||
|
||||
|
||||
# -- Single-item lookups --
|
||||
|
||||
|
||||
func get_terrain(id: String) -> Dictionary:
|
||||
return _get_entry("terrain", id)
|
||||
|
||||
|
||||
func get_unit(id: String) -> Dictionary:
|
||||
return _get_entry("units", id)
|
||||
|
||||
|
||||
func get_building(id: String) -> Dictionary:
|
||||
return _get_entry("buildings", id)
|
||||
|
||||
|
||||
func get_tech(id: String) -> Dictionary:
|
||||
return _get_entry("techs", id)
|
||||
|
||||
|
||||
func get_spell(id: String) -> Dictionary:
|
||||
return _get_entry("spells", id)
|
||||
|
||||
|
||||
func get_race(id: String) -> Dictionary:
|
||||
return _get_entry("races", id)
|
||||
|
||||
|
||||
func get_ai_personality(race_id: String) -> Dictionary:
|
||||
return _get_entry("ai_personalities", race_id)
|
||||
|
||||
|
||||
func get_resource(id: String) -> Dictionary:
|
||||
return _get_entry("resources", id)
|
||||
|
||||
|
||||
func get_keyword(id: String) -> Dictionary:
|
||||
return _get_entry("keywords", id)
|
||||
|
||||
|
||||
func get_improvement(id: String) -> Dictionary:
|
||||
return _get_entry("improvements", id)
|
||||
|
||||
|
||||
func get_item(id: String) -> Dictionary:
|
||||
return _get_entry("items", id)
|
||||
|
||||
|
||||
func get_promotion(id: String) -> Dictionary:
|
||||
return _get_entry("promotions", id)
|
||||
|
||||
|
||||
func get_government(id: String) -> Dictionary:
|
||||
return _get_entry("governments", id)
|
||||
|
||||
|
||||
func get_magical_promotion(id: String) -> Dictionary:
|
||||
return _get_entry("magical_promotions", id)
|
||||
|
||||
|
||||
func get_magical_promotion_tree(school: String) -> Dictionary:
|
||||
## Return the infusion tree data for a given school (life, death, chaos, nature, aether).
|
||||
for entry: Dictionary in _data["magical_promotions"].values():
|
||||
if entry.get("school", "") == school:
|
||||
return entry
|
||||
return {}
|
||||
|
||||
|
||||
func get_magical_promotion_config() -> Dictionary:
|
||||
## Return the shared infusion config (_config.json).
|
||||
return _get_entry("magical_promotions", "_config")
|
||||
|
||||
func get_throne_room_decoration(id: String) -> Dictionary:
|
||||
return _get_entry("throne_room", id)
|
||||
|
||||
func get_all_throne_room_decorations() -> Array:
|
||||
return _data["throne_room"].values()
|
||||
|
||||
func get_seed_easter_egg(seed: int) -> Dictionary:
|
||||
## Returns the easter egg entry for the given seed, or {} if none.
|
||||
return _raw.get("seed_easter_eggs", {}).get(str(seed), {})
|
||||
|
||||
func get_map_type(id: String) -> Dictionary:
|
||||
return _get_entry("map_types", id)
|
||||
|
||||
func get_wilds_config() -> Dictionary:
|
||||
return _get_entry("wilds", "wilds")
|
||||
|
||||
func get_villages_config() -> Dictionary:
|
||||
return _get_entry("villages", "villages")
|
||||
|
||||
func get_npc_building_type(type_id: String) -> Dictionary:
|
||||
return _get_entry("npc_buildings", type_id)
|
||||
|
||||
func get_all_npc_building_types() -> Array:
|
||||
return _data.get("npc_buildings", {}).values()
|
||||
|
||||
func get_entry_count(category: String) -> int:
|
||||
if _data.has(category):
|
||||
return _data[category].size()
|
||||
return 0
|
||||
|
||||
func get_setup_entry(key: String) -> Variant:
|
||||
## Returns a top-level key from setup.json (e.g. "defaults", "map_sizes").
|
||||
if not _raw.has("setup"):
|
||||
return {}
|
||||
return _raw["setup"].get(key, {})
|
||||
|
||||
# -- Collection lookups --
|
||||
|
||||
|
||||
func get_all_terrains() -> Array:
|
||||
return _data["terrain"].values()
|
||||
|
||||
|
||||
func get_all_units() -> Array:
|
||||
return _data["units"].values()
|
||||
|
||||
|
||||
func get_all_buildings() -> Array:
|
||||
return _data["buildings"].values()
|
||||
|
||||
|
||||
func get_all_techs() -> Array:
|
||||
return _data["techs"].values()
|
||||
|
||||
|
||||
func get_all_spells() -> Array:
|
||||
return _data["spells"].values()
|
||||
|
||||
|
||||
func get_all_races() -> Array:
|
||||
return _data["races"].values()
|
||||
|
||||
|
||||
func get_all_resources() -> Array:
|
||||
return _data["resources"].values()
|
||||
|
||||
|
||||
func get_all_keywords() -> Array:
|
||||
return _data["keywords"].values()
|
||||
|
||||
|
||||
func get_all_improvements() -> Array:
|
||||
return _data["improvements"].values()
|
||||
|
||||
|
||||
func get_all_items() -> Array:
|
||||
return _data["items"].values()
|
||||
|
||||
|
||||
func get_all_promotions() -> Array:
|
||||
return _data["promotions"].values()
|
||||
|
||||
|
||||
func get_all_governments() -> Array:
|
||||
return _data["governments"].values()
|
||||
|
||||
|
||||
func get_all_magical_promotions() -> Array:
|
||||
return _data["magical_promotions"].values()
|
||||
|
||||
|
||||
func get_magical_promotions_by_school(school: String) -> Array:
|
||||
return _filter_by_field("magical_promotions", "school", school)
|
||||
|
||||
|
||||
func get_all_map_types() -> Array:
|
||||
return _data["map_types"].values()
|
||||
|
||||
|
||||
# -- Filtered lookups --
|
||||
|
||||
|
||||
func get_units_by_race(race_id: String) -> Array:
|
||||
return _filter_by_field("units", "race_required", race_id)
|
||||
|
||||
|
||||
func get_units_by_school(school: String) -> Array:
|
||||
return _filter_by_field("units", "school", school)
|
||||
|
||||
|
||||
func get_buildings_by_category(category: String) -> Array:
|
||||
return _filter_by_field("buildings", "category", category)
|
||||
|
||||
|
||||
func get_techs_by_pillar(pillar: String) -> Array:
|
||||
return _filter_by_field("techs", "pillar", pillar)
|
||||
|
||||
|
||||
func get_spells_by_school(school: String) -> Array:
|
||||
return _filter_by_field("spells", "school", school)
|
||||
|
||||
|
||||
func get_units_by_type(combat_type: String) -> Array:
|
||||
return _filter_by_field("units", "combat_type", combat_type)
|
||||
|
||||
|
||||
func get_buildings_by_school(school: String) -> Array:
|
||||
return _filter_by_field("buildings", "school", school)
|
||||
|
||||
|
||||
func get_terrains_by_climate_zone(zone: String) -> Array:
|
||||
return _filter_by_field("terrain", "climate_zone", zone)
|
||||
|
||||
|
||||
func get_items_by_category(category: String) -> Array:
|
||||
return _filter_by_field("items", "category", category)
|
||||
|
||||
|
||||
func get_items_by_school(school: String) -> Array:
|
||||
return _filter_by_field("items", "school", school)
|
||||
|
||||
|
||||
func get_terrains_by_flag(flag: String) -> Array:
|
||||
var results: Array = []
|
||||
for entry: Dictionary in _data["terrain"].values():
|
||||
var flags: Array = entry.get("flags", [])
|
||||
if flag in flags:
|
||||
if flag in entry.get("flags", []):
|
||||
results.append(entry)
|
||||
return results
|
||||
|
||||
|
||||
func get_resources_for_terrain(biome_id: String) -> Array:
|
||||
var results: Array = []
|
||||
for entry: Dictionary in _data["resources"].values():
|
||||
var terrains: Array = entry.get("terrains", [])
|
||||
if biome_id in terrains:
|
||||
if biome_id in entry.get("terrains", []):
|
||||
results.append(entry)
|
||||
return results
|
||||
|
||||
|
||||
func get_improvements_for_terrain(biome_id: String) -> Array:
|
||||
var results: Array = []
|
||||
for entry: Dictionary in _data["improvements"].values():
|
||||
|
|
@ -456,187 +360,91 @@ func get_improvements_for_terrain(biome_id: String) -> Array:
|
|||
results.append(entry)
|
||||
return results
|
||||
|
||||
|
||||
func get_spells_by_category(category: String) -> Array:
|
||||
return _filter_by_field("spells", "category", category)
|
||||
|
||||
|
||||
func get_climate_params() -> Dictionary:
|
||||
return _raw.get("climate_params", {})
|
||||
|
||||
|
||||
func get_climate_spec() -> Dictionary:
|
||||
return _raw.get("climate_spec", {})
|
||||
|
||||
|
||||
func get_ecological_events() -> Dictionary:
|
||||
## Returns the merged events dictionary keyed by category name.
|
||||
## e.g. {"volcanic": {...}, "impact": {...}, "seismic": {...}, ...}
|
||||
return _raw.get("events", {})
|
||||
|
||||
|
||||
func get_hydrology_params() -> Dictionary:
|
||||
return _raw.get("hydrology_params", {})
|
||||
|
||||
|
||||
func get_ley_line_params() -> Dictionary:
|
||||
return _raw.get("ley_line_params", {})
|
||||
|
||||
|
||||
func get_social_policies() -> Dictionary:
|
||||
## Return the full social policies data object (contains "trees" array).
|
||||
return _raw.get("social_policies", {})
|
||||
|
||||
|
||||
func get_entry_count(category: String) -> int:
|
||||
if _data.has(category):
|
||||
return _data[category].size()
|
||||
return 0
|
||||
|
||||
|
||||
## Returns a top-level key from setup.json (e.g. "defaults", "map_sizes").
|
||||
## setup.json is a flat object, not ID-keyed, so this reads from _raw.
|
||||
func get_setup_entry(key: String) -> Variant:
|
||||
if not _raw.has("setup"):
|
||||
return {}
|
||||
return _raw["setup"].get(key, {})
|
||||
|
||||
|
||||
func get_map_type(id: String) -> Dictionary:
|
||||
return _get_entry("map_types", id)
|
||||
|
||||
|
||||
func get_wilds_config() -> Dictionary:
|
||||
return _get_entry("wilds", "wilds")
|
||||
|
||||
|
||||
func get_villages_config() -> Dictionary:
|
||||
return _get_entry("villages", "villages")
|
||||
|
||||
|
||||
func get_npc_building_type(type_id: String) -> Dictionary:
|
||||
return _get_entry("npc_buildings", type_id)
|
||||
|
||||
|
||||
func get_all_npc_building_types() -> Array:
|
||||
return _data.get("npc_buildings", {}).values()
|
||||
|
||||
|
||||
# -- Ecology: typed accessors --
|
||||
|
||||
# -- Ecology: typed accessors (delegated to _ecology) --
|
||||
|
||||
func get_biome(id: String) -> Variant:
|
||||
## Returns a BiomeModel instance or null if not found.
|
||||
return _biomes.get(id)
|
||||
|
||||
return _ecology.get_biome(id)
|
||||
|
||||
func get_all_biomes() -> Array:
|
||||
## Returns all BiomeModel instances.
|
||||
return _biomes.values()
|
||||
|
||||
return _ecology.get_all_biomes()
|
||||
|
||||
func get_flora_profile(biome_id: String) -> Variant:
|
||||
## Returns a FloraProfile for the given biome or null if not found.
|
||||
return _flora_profiles.get(biome_id)
|
||||
|
||||
return _ecology.get_flora_profile(biome_id)
|
||||
|
||||
func get_substrate(id: String) -> Variant:
|
||||
## Returns a SubstrateType instance or null if not found.
|
||||
return _substrates.get(id)
|
||||
|
||||
return _ecology.get_substrate(id)
|
||||
|
||||
func get_all_substrates() -> Array:
|
||||
## Returns all SubstrateType instances.
|
||||
return _substrates.values()
|
||||
|
||||
return _ecology.get_all_substrates()
|
||||
|
||||
func get_marine_fauna_params() -> Variant:
|
||||
## Returns the MarineFaunaModel or null.
|
||||
return _marine_fauna
|
||||
|
||||
return _ecology.get_marine_fauna_params()
|
||||
|
||||
func get_land_fauna_params() -> Variant:
|
||||
## Returns the LandFaunaModel or null.
|
||||
return _land_fauna
|
||||
|
||||
return _ecology.get_land_fauna_params()
|
||||
|
||||
func get_air_fauna_params() -> Variant:
|
||||
## Returns the AirFaunaModel or null.
|
||||
return _air_fauna
|
||||
|
||||
return _ecology.get_air_fauna_params()
|
||||
|
||||
func get_trait_definitions() -> Dictionary:
|
||||
## Returns trait_definitions from world/traits/trait_definitions.json.
|
||||
return _raw.get("world_traits", {}).get("trait_definitions", {})
|
||||
|
||||
return _ecology.get_trait_definitions()
|
||||
|
||||
func get_trait_constraints() -> Array:
|
||||
## Returns constraint array from world/traits/trait_constraints.json.
|
||||
var tc: Variant = _raw.get("world_traits", {}).get("trait_constraints", [])
|
||||
if tc is Array:
|
||||
return tc
|
||||
return []
|
||||
|
||||
return _ecology.get_trait_constraints()
|
||||
|
||||
func get_biome_trait_weights(biome_id: String) -> Dictionary:
|
||||
## Returns per-biome trait probability weights for species generation.
|
||||
var all_weights: Dictionary = _raw.get("world_traits", {}).get(
|
||||
"biome_trait_weights", {}
|
||||
)
|
||||
return all_weights.get(biome_id, {})
|
||||
|
||||
return _ecology.get_biome_trait_weights(biome_id)
|
||||
|
||||
func get_food_web_rules() -> Dictionary:
|
||||
## Returns food web rules from world/traits/food_web_rules.json.
|
||||
return _raw.get("world_traits", {}).get("food_web_rules", {})
|
||||
|
||||
return _ecology.get_food_web_rules()
|
||||
|
||||
func get_spawn_rules() -> Array:
|
||||
## Returns spawn rules array from world/traits/spawn_rules.json.
|
||||
var sr: Variant = _raw.get("world_traits", {}).get("spawn_rules", [])
|
||||
if sr is Array:
|
||||
return sr
|
||||
return []
|
||||
|
||||
return _ecology.get_spawn_rules()
|
||||
|
||||
func get_flavor_tables() -> Dictionary:
|
||||
## Returns flavor tables from world/traits/flavor.json.
|
||||
return _raw.get("world_traits", {}).get("flavor", {})
|
||||
|
||||
return _ecology.get_flavor_tables()
|
||||
|
||||
func get_ecosystem_health_params() -> Dictionary:
|
||||
## Returns ecosystem health weights and thresholds.
|
||||
return _raw.get("world_ecosystem", {}).get("health", {})
|
||||
|
||||
return _ecology.get_ecosystem_health_params()
|
||||
|
||||
func get_food_yield_params() -> Dictionary:
|
||||
## Returns quality-to-yield curves.
|
||||
return _raw.get("world_ecosystem", {}).get("food_yield", {})
|
||||
|
||||
return _ecology.get_food_yield_params()
|
||||
|
||||
func get_ecosystem_stability_params() -> Dictionary:
|
||||
## Returns reclassification thresholds.
|
||||
return _raw.get("world_ecosystem", {}).get("stability", {})
|
||||
|
||||
return _ecology.get_ecosystem_stability_params()
|
||||
|
||||
func get_vegetation_params() -> Dictionary:
|
||||
## Returns flora vegetation growth/decay params.
|
||||
return _raw.get("world_flora", {}).get("vegetation", {})
|
||||
|
||||
return _ecology.get_vegetation_params()
|
||||
|
||||
func get_succession_params() -> Dictionary:
|
||||
## Returns flora succession params.
|
||||
return _raw.get("world_flora", {}).get("succession", {})
|
||||
|
||||
return _ecology.get_succession_params()
|
||||
|
||||
func get_desertification_params() -> Dictionary:
|
||||
## Returns desertification thresholds.
|
||||
return _raw.get("world_flora", {}).get("desertification", {})
|
||||
|
||||
return _ecology.get_desertification_params()
|
||||
|
||||
# -- Internal helpers --
|
||||
|
||||
|
||||
func _get_entry(category: String, id: String) -> Dictionary:
|
||||
if not _data.has(category):
|
||||
push_warning("DataLoader: Unknown category '%s'" % category)
|
||||
|
|
@ -645,7 +453,6 @@ func _get_entry(category: String, id: String) -> Dictionary:
|
|||
return {}
|
||||
return _data[category][id]
|
||||
|
||||
|
||||
func _filter_by_field(category: String, field: String, value: String) -> Array:
|
||||
var results: Array = []
|
||||
if not _data.has(category):
|
||||
|
|
@ -654,106 +461,3 @@ func _filter_by_field(category: String, field: String, value: String) -> Array:
|
|||
if entry.get(field, "") == value:
|
||||
results.append(entry)
|
||||
return results
|
||||
|
||||
|
||||
func _deserialize_ecology() -> void:
|
||||
## Convert raw ecology JSON into typed model instances.
|
||||
_deserialize_biomes()
|
||||
_deserialize_flora_profiles()
|
||||
_deserialize_substrates()
|
||||
_deserialize_fauna_params()
|
||||
|
||||
|
||||
func _deserialize_biomes() -> void:
|
||||
var raw_biomes: Dictionary = _raw.get("world_biomes", {})
|
||||
# biomes.json is loaded as raw category — may have "biomes" key or be flat
|
||||
var biomes_data: Variant = raw_biomes.get("biomes", raw_biomes)
|
||||
if biomes_data is Dictionary:
|
||||
# Could be a dict of biome entries keyed by id
|
||||
for key: String in biomes_data:
|
||||
var entry: Variant = biomes_data[key]
|
||||
if entry is Dictionary:
|
||||
if not entry.has("id"):
|
||||
entry["id"] = key
|
||||
_biomes[entry.get("id", key)] = BiomeModelScript.from_dict(entry)
|
||||
elif biomes_data is Array:
|
||||
for entry: Variant in biomes_data:
|
||||
if entry is Dictionary and entry.has("id"):
|
||||
_biomes[entry["id"]] = BiomeModelScript.from_dict(entry)
|
||||
if _biomes.size() > 0:
|
||||
print("DataLoader: Deserialized %d biome models" % _biomes.size())
|
||||
|
||||
|
||||
func _deserialize_flora_profiles() -> void:
|
||||
# Flora profiles may come from biomes data (flora_climax fields) or separate files
|
||||
# First check for explicit flora profile data
|
||||
var raw_flora: Dictionary = _raw.get("world_flora", {})
|
||||
|
||||
# Check each biome for embedded flora profile data
|
||||
for biome_id: String in _biomes:
|
||||
var biome: Variant = _biomes[biome_id]
|
||||
var climax: Dictionary = biome.flora_climax
|
||||
if climax.get("canopy", 0.0) > 0.0 or climax.get("undergrowth", 0.0) > 0.0:
|
||||
var profile_data: Dictionary = {
|
||||
"biome_id": biome_id,
|
||||
"canopy_climax": climax.get("canopy", 0.0),
|
||||
"undergrowth_climax": climax.get("undergrowth", 0.0),
|
||||
"fungi_climax": climax.get("fungi", 0.0),
|
||||
}
|
||||
# Merge with vegetation params if available
|
||||
var veg: Dictionary = raw_flora.get("vegetation", {})
|
||||
if veg.size() > 0:
|
||||
profile_data["growth_rate"] = veg.get("growth_rate", 0.02)
|
||||
profile_data["shade_cap"] = veg.get("shade_cap", 0.7)
|
||||
profile_data["fungi_undergrowth_threshold"] = veg.get(
|
||||
"fungi_undergrowth_threshold", 0.3
|
||||
)
|
||||
profile_data["fungi_regrowth_bonus"] = veg.get(
|
||||
"fungi_regrowth_bonus_cap", 2.0
|
||||
)
|
||||
_flora_profiles[biome_id] = FloraProfileScript.from_dict(profile_data)
|
||||
|
||||
if _flora_profiles.size() > 0:
|
||||
print(
|
||||
"DataLoader: Deserialized %d flora profiles" % _flora_profiles.size()
|
||||
)
|
||||
|
||||
|
||||
func _deserialize_substrates() -> void:
|
||||
# Substrates may be in world_biomes raw data under "substrates" key
|
||||
var raw_biomes: Dictionary = _raw.get("world_biomes", {})
|
||||
var substrates_data: Variant = raw_biomes.get("substrates", null)
|
||||
if substrates_data == null:
|
||||
return
|
||||
|
||||
if substrates_data is Dictionary:
|
||||
for key: String in substrates_data:
|
||||
var entry: Variant = substrates_data[key]
|
||||
if entry is Dictionary:
|
||||
if not entry.has("id"):
|
||||
entry["id"] = key
|
||||
_substrates[entry.get("id", key)] = SubstrateTypeScript.from_dict(
|
||||
entry
|
||||
)
|
||||
elif substrates_data is Array:
|
||||
for entry: Variant in substrates_data:
|
||||
if entry is Dictionary and entry.has("id"):
|
||||
_substrates[entry["id"]] = SubstrateTypeScript.from_dict(entry)
|
||||
|
||||
if _substrates.size() > 0:
|
||||
print("DataLoader: Deserialized %d substrate types" % _substrates.size())
|
||||
|
||||
|
||||
func _deserialize_fauna_params() -> void:
|
||||
var raw_fauna: Dictionary = _raw.get("world_fauna", {})
|
||||
var marine_data: Variant = raw_fauna.get("marine", {})
|
||||
if marine_data is Dictionary and marine_data.size() > 0:
|
||||
_marine_fauna = MarineFaunaScript.from_dict(marine_data)
|
||||
|
||||
var land_data: Variant = raw_fauna.get("land", {})
|
||||
if land_data is Dictionary and land_data.size() > 0:
|
||||
_land_fauna = LandFaunaScript.from_dict(land_data)
|
||||
|
||||
var air_data: Variant = raw_fauna.get("air", {})
|
||||
if air_data is Dictionary and air_data.size() > 0:
|
||||
_air_fauna = AirFaunaScript.from_dict(air_data)
|
||||
|
|
|
|||
277
engine/src/autoloads/data_loader_ecology.gd
Normal file
277
engine/src/autoloads/data_loader_ecology.gd
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
extends RefCounted
|
||||
## Ecology deserialisation and typed accessor helpers for DataLoader.
|
||||
## Owns all world_biomes / world_flora / world_fauna / world_traits / world_ecosystem
|
||||
## deserialization and their public getter surface.
|
||||
##
|
||||
## DataLoader holds one instance of this class and delegates ecology calls to it.
|
||||
## The _raw, _biomes, _flora_profiles, _substrates, _marine_fauna, _land_fauna,
|
||||
## _air_fauna state all live here so the split stays clean.
|
||||
|
||||
const BiomeModelScript = preload("res://engine/src/models/world/biome.gd")
|
||||
const FloraProfileScript = preload("res://engine/src/models/world/flora_profile.gd")
|
||||
const TraitSetScript = preload("res://engine/src/models/world/species_traits.gd")
|
||||
const SubstrateTypeScript = preload("res://engine/src/models/world/substrate.gd")
|
||||
const MarineFaunaScript = preload("res://engine/src/models/world/marine_fauna.gd")
|
||||
const LandFaunaScript = preload("res://engine/src/models/world/land_fauna.gd")
|
||||
const AirFaunaScript = preload("res://engine/src/models/world/air_fauna.gd")
|
||||
|
||||
## Back-reference to _raw on the parent DataLoader (set by DataLoader after construction).
|
||||
var _raw: Dictionary = {}
|
||||
|
||||
var _biomes: Dictionary = {} # id -> BiomeModel
|
||||
var _flora_profiles: Dictionary = {} # biome_id -> FloraProfile
|
||||
var _substrates: Dictionary = {} # id -> SubstrateType
|
||||
var _marine_fauna: Variant = null # MarineFaunaModel
|
||||
var _land_fauna: Variant = null # LandFaunaModel
|
||||
var _air_fauna: Variant = null # AirFaunaModel
|
||||
|
||||
|
||||
func clear() -> void:
|
||||
_biomes.clear()
|
||||
_flora_profiles.clear()
|
||||
_substrates.clear()
|
||||
_marine_fauna = null
|
||||
_land_fauna = null
|
||||
_air_fauna = null
|
||||
|
||||
|
||||
func deserialize(raw: Dictionary) -> void:
|
||||
## Run full ecology deserialisation from the provided raw data dict.
|
||||
_raw = raw
|
||||
clear()
|
||||
_deserialize_biomes()
|
||||
_deserialize_flora_profiles()
|
||||
_deserialize_substrates()
|
||||
_deserialize_fauna_params()
|
||||
|
||||
|
||||
# -- Typed accessors --
|
||||
|
||||
|
||||
func get_biome(id: String) -> Variant:
|
||||
## Returns a BiomeModel instance or null if not found.
|
||||
return _biomes.get(id)
|
||||
|
||||
|
||||
func get_all_biomes() -> Array:
|
||||
## Returns all BiomeModel instances.
|
||||
return _biomes.values()
|
||||
|
||||
|
||||
func get_flora_profile(biome_id: String) -> Variant:
|
||||
## Returns a FloraProfile for the given biome or null if not found.
|
||||
return _flora_profiles.get(biome_id)
|
||||
|
||||
|
||||
func get_substrate(id: String) -> Variant:
|
||||
## Returns a SubstrateType instance or null if not found.
|
||||
return _substrates.get(id)
|
||||
|
||||
|
||||
func get_all_substrates() -> Array:
|
||||
## Returns all SubstrateType instances.
|
||||
return _substrates.values()
|
||||
|
||||
|
||||
func get_marine_fauna_params() -> Variant:
|
||||
## Returns the MarineFaunaModel or null.
|
||||
return _marine_fauna
|
||||
|
||||
|
||||
func get_land_fauna_params() -> Variant:
|
||||
## Returns the LandFaunaModel or null.
|
||||
return _land_fauna
|
||||
|
||||
|
||||
func get_air_fauna_params() -> Variant:
|
||||
## Returns the AirFaunaModel or null.
|
||||
return _air_fauna
|
||||
|
||||
|
||||
func get_trait_definitions() -> Dictionary:
|
||||
## Returns trait_definitions from world/traits/trait_definitions.json.
|
||||
return _raw.get("world_traits", {}).get("trait_definitions", {})
|
||||
|
||||
|
||||
func get_trait_constraints() -> Array:
|
||||
## Returns constraint array from world/traits/trait_constraints.json.
|
||||
var tc: Variant = _raw.get("world_traits", {}).get("trait_constraints", [])
|
||||
if tc is Array:
|
||||
return tc
|
||||
return []
|
||||
|
||||
|
||||
func get_biome_trait_weights(biome_id: String) -> Dictionary:
|
||||
## Returns per-biome trait probability weights for species generation.
|
||||
var all_weights: Dictionary = _raw.get("world_traits", {}).get(
|
||||
"biome_trait_weights", {}
|
||||
)
|
||||
return all_weights.get(biome_id, {})
|
||||
|
||||
|
||||
func get_food_web_rules() -> Dictionary:
|
||||
## Returns food web rules from world/traits/food_web_rules.json.
|
||||
return _raw.get("world_traits", {}).get("food_web_rules", {})
|
||||
|
||||
|
||||
func get_spawn_rules() -> Array:
|
||||
## Returns spawn rules array from world/traits/spawn_rules.json.
|
||||
var sr: Variant = _raw.get("world_traits", {}).get("spawn_rules", [])
|
||||
if sr is Array:
|
||||
return sr
|
||||
return []
|
||||
|
||||
|
||||
func get_flavor_tables() -> Dictionary:
|
||||
## Returns flavor tables from world/traits/flavor.json.
|
||||
return _raw.get("world_traits", {}).get("flavor", {})
|
||||
|
||||
|
||||
func get_ecosystem_health_params() -> Dictionary:
|
||||
## Returns ecosystem health weights and thresholds.
|
||||
return _raw.get("world_ecosystem", {}).get("health", {})
|
||||
|
||||
|
||||
func get_food_yield_params() -> Dictionary:
|
||||
## Returns quality-to-yield curves.
|
||||
return _raw.get("world_ecosystem", {}).get("food_yield", {})
|
||||
|
||||
|
||||
func get_ecosystem_stability_params() -> Dictionary:
|
||||
## Returns reclassification thresholds.
|
||||
return _raw.get("world_ecosystem", {}).get("stability", {})
|
||||
|
||||
|
||||
func get_vegetation_params() -> Dictionary:
|
||||
## Returns flora vegetation growth/decay params.
|
||||
return _raw.get("world_flora", {}).get("vegetation", {})
|
||||
|
||||
|
||||
func get_succession_params() -> Dictionary:
|
||||
## Returns flora succession params.
|
||||
return _raw.get("world_flora", {}).get("succession", {})
|
||||
|
||||
|
||||
func get_desertification_params() -> Dictionary:
|
||||
## Returns desertification thresholds.
|
||||
return _raw.get("world_flora", {}).get("desertification", {})
|
||||
|
||||
|
||||
func register_collection_substrate(substrate_data: Dictionary) -> void:
|
||||
## Register a substrate from a world collection JSON entry.
|
||||
var substrate_id: String = substrate_data.get("id", "")
|
||||
if substrate_id.is_empty() or _substrates.has(substrate_id):
|
||||
return
|
||||
_substrates[substrate_id] = SubstrateTypeScript.from_dict(substrate_data)
|
||||
|
||||
|
||||
func register_collection_biome(biome_data: Dictionary) -> void:
|
||||
## Register a biome from a world collection JSON entry.
|
||||
## Merges into the existing biome set — does not replace existing biomes.
|
||||
var biome_id: String = biome_data.get("id", "")
|
||||
if biome_id.is_empty():
|
||||
return
|
||||
if _biomes.has(biome_id):
|
||||
return # game-pack biome takes precedence over collection
|
||||
_biomes[biome_id] = BiomeModelScript.from_dict(biome_data)
|
||||
# Build flora profile if this biome has vegetation
|
||||
var climax: Dictionary = biome_data.get("flora_climax", {})
|
||||
if climax.get("canopy", 0.0) > 0.0 or climax.get("undergrowth", 0.0) > 0.0:
|
||||
var profile_data: Dictionary = {
|
||||
"biome_id": biome_id,
|
||||
"canopy_climax": climax.get("canopy", 0.0),
|
||||
"undergrowth_climax": climax.get("undergrowth", 0.0),
|
||||
"fungi_climax": climax.get("fungi", 0.0),
|
||||
}
|
||||
var veg: Dictionary = _raw.get("world_flora", {}).get("vegetation", {})
|
||||
if veg.size() > 0:
|
||||
profile_data["growth_rate"] = veg.get("growth_rate", 0.02)
|
||||
profile_data["shade_cap"] = veg.get("shade_cap", 0.7)
|
||||
profile_data["fungi_undergrowth_threshold"] = veg.get(
|
||||
"fungi_undergrowth_threshold", 0.3
|
||||
)
|
||||
profile_data["fungi_regrowth_bonus"] = veg.get(
|
||||
"fungi_regrowth_bonus_cap", 2.0
|
||||
)
|
||||
_flora_profiles[biome_id] = FloraProfileScript.from_dict(profile_data)
|
||||
|
||||
|
||||
# -- Internal deserialisation --
|
||||
|
||||
|
||||
func _deserialize_biomes() -> void:
|
||||
var raw_biomes: Dictionary = _raw.get("world_biomes", {})
|
||||
var biomes_data: Variant = raw_biomes.get("biomes", raw_biomes)
|
||||
if biomes_data is Dictionary:
|
||||
for key: String in biomes_data:
|
||||
var entry: Variant = biomes_data[key]
|
||||
if entry is Dictionary:
|
||||
if not entry.has("id"):
|
||||
entry["id"] = key
|
||||
_biomes[entry.get("id", key)] = BiomeModelScript.from_dict(entry)
|
||||
elif biomes_data is Array:
|
||||
for entry: Variant in biomes_data:
|
||||
if entry is Dictionary and entry.has("id"):
|
||||
_biomes[entry["id"]] = BiomeModelScript.from_dict(entry)
|
||||
if _biomes.size() > 0:
|
||||
print("DataLoader: Deserialized %d biome models" % _biomes.size())
|
||||
|
||||
|
||||
func _deserialize_flora_profiles() -> void:
|
||||
var raw_flora: Dictionary = _raw.get("world_flora", {})
|
||||
for biome_id: String in _biomes:
|
||||
var biome: Variant = _biomes[biome_id]
|
||||
var climax: Dictionary = biome.flora_climax
|
||||
if climax.get("canopy", 0.0) > 0.0 or climax.get("undergrowth", 0.0) > 0.0:
|
||||
var profile_data: Dictionary = {
|
||||
"biome_id": biome_id,
|
||||
"canopy_climax": climax.get("canopy", 0.0),
|
||||
"undergrowth_climax": climax.get("undergrowth", 0.0),
|
||||
"fungi_climax": climax.get("fungi", 0.0),
|
||||
}
|
||||
var veg: Dictionary = raw_flora.get("vegetation", {})
|
||||
if veg.size() > 0:
|
||||
profile_data["growth_rate"] = veg.get("growth_rate", 0.02)
|
||||
profile_data["shade_cap"] = veg.get("shade_cap", 0.7)
|
||||
profile_data["fungi_undergrowth_threshold"] = veg.get(
|
||||
"fungi_undergrowth_threshold", 0.3
|
||||
)
|
||||
profile_data["fungi_regrowth_bonus"] = veg.get(
|
||||
"fungi_regrowth_bonus_cap", 2.0
|
||||
)
|
||||
_flora_profiles[biome_id] = FloraProfileScript.from_dict(profile_data)
|
||||
if _flora_profiles.size() > 0:
|
||||
print("DataLoader: Deserialized %d flora profiles" % _flora_profiles.size())
|
||||
|
||||
|
||||
func _deserialize_substrates() -> void:
|
||||
var raw_biomes: Dictionary = _raw.get("world_biomes", {})
|
||||
var substrates_data: Variant = raw_biomes.get("substrates", null)
|
||||
if substrates_data == null:
|
||||
return
|
||||
if substrates_data is Dictionary:
|
||||
for key: String in substrates_data:
|
||||
var entry: Variant = substrates_data[key]
|
||||
if entry is Dictionary:
|
||||
if not entry.has("id"):
|
||||
entry["id"] = key
|
||||
_substrates[entry.get("id", key)] = SubstrateTypeScript.from_dict(entry)
|
||||
elif substrates_data is Array:
|
||||
for entry: Variant in substrates_data:
|
||||
if entry is Dictionary and entry.has("id"):
|
||||
_substrates[entry["id"]] = SubstrateTypeScript.from_dict(entry)
|
||||
if _substrates.size() > 0:
|
||||
print("DataLoader: Deserialized %d substrate types" % _substrates.size())
|
||||
|
||||
|
||||
func _deserialize_fauna_params() -> void:
|
||||
var raw_fauna: Dictionary = _raw.get("world_fauna", {})
|
||||
var marine_data: Variant = raw_fauna.get("marine", {})
|
||||
if marine_data is Dictionary and marine_data.size() > 0:
|
||||
_marine_fauna = MarineFaunaScript.from_dict(marine_data)
|
||||
var land_data: Variant = raw_fauna.get("land", {})
|
||||
if land_data is Dictionary and land_data.size() > 0:
|
||||
_land_fauna = LandFaunaScript.from_dict(land_data)
|
||||
var air_data: Variant = raw_fauna.get("air", {})
|
||||
if air_data is Dictionary and air_data.size() > 0:
|
||||
_air_fauna = AirFaunaScript.from_dict(air_data)
|
||||
73
engine/src/autoloads/data_loader_worlds.gd
Normal file
73
engine/src/autoloads/data_loader_worlds.gd
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
extends RefCounted
|
||||
## World loading delegate for DataLoader.
|
||||
## Reads world manifests from engine/src/worlds/, loads biome/substrate collections,
|
||||
## and registers runtime biomes into BiomeRegistry.
|
||||
|
||||
const WORLDS_BASE: String = "res://engine/src/worlds"
|
||||
|
||||
var manifest: Dictionary = {}
|
||||
var active_world: String = ""
|
||||
|
||||
|
||||
func load_world(world_id: String, ecology: Variant) -> void:
|
||||
active_world = world_id
|
||||
var world_path: String = "%s/%s" % [WORLDS_BASE, world_id]
|
||||
|
||||
# Load manifest
|
||||
manifest = _load_json("%s/manifest.json" % world_path)
|
||||
if manifest.is_empty():
|
||||
push_error("DataLoader: Cannot load world manifest for '%s'" % world_id)
|
||||
return
|
||||
|
||||
# Load subscribed biome collections
|
||||
var biome_count: int = 0
|
||||
for collection_id: Variant in manifest.get("subscribes_biomes", []):
|
||||
var entries: Array = _load_collection_entries(str(collection_id), "biomes")
|
||||
for entry: Dictionary in entries:
|
||||
ecology.register_collection_biome(entry)
|
||||
biome_count += 1
|
||||
|
||||
# Load subscribed substrate collections
|
||||
var substrate_count: int = 0
|
||||
for collection_id: Variant in manifest.get("subscribes_substrates", []):
|
||||
var entries: Array = _load_collection_entries(str(collection_id), "substrates")
|
||||
for entry: Dictionary in entries:
|
||||
ecology.register_collection_substrate(entry)
|
||||
substrate_count += 1
|
||||
|
||||
# Load runtime biomes
|
||||
var runtime_data: Dictionary = _load_json("%s/runtime_biomes.json" % world_path)
|
||||
for entry: Variant in runtime_data.get("runtime_biomes", []):
|
||||
if entry is Dictionary and entry.has("id"):
|
||||
var rt_tags: Array[String] = []
|
||||
for tag: Variant in entry.get("tags", []):
|
||||
rt_tags.append(str(tag))
|
||||
BiomeRegistry.register_runtime_biome(str(entry["id"]), rt_tags)
|
||||
|
||||
BiomeRegistry.rebuild_from_data()
|
||||
print("DataLoader: World '%s' — %d biomes, %d substrates" % [
|
||||
world_id, biome_count, substrate_count])
|
||||
|
||||
|
||||
func _load_collection_entries(collection_id: String, key: String) -> Array:
|
||||
var path: String = "%s/collections/%s/%s.json" % [WORLDS_BASE, collection_id, key]
|
||||
var data: Dictionary = _load_json(path)
|
||||
var entries: Array = data.get(key, [])
|
||||
if entries.is_empty():
|
||||
push_warning("DataLoader: Empty or missing collection %s/%s" % [collection_id, key])
|
||||
return entries
|
||||
|
||||
|
||||
func _load_json(path: String) -> Dictionary:
|
||||
if not FileAccess.file_exists(path):
|
||||
return {}
|
||||
var file: FileAccess = FileAccess.open(path, FileAccess.READ)
|
||||
if file == null:
|
||||
return {}
|
||||
var json: JSON = JSON.new()
|
||||
if json.parse(file.get_as_text()) != OK:
|
||||
push_warning("DataLoader: JSON error in %s: %s" % [path, json.get_error_message()])
|
||||
return {}
|
||||
if json.data is Dictionary:
|
||||
return json.data
|
||||
return {}
|
||||
|
|
@ -6,13 +6,9 @@ const PlayerScript: GDScript = preload("res://engine/src/entities/player.gd")
|
|||
const GameMapScript: GDScript = preload("res://engine/src/map/game_map.gd")
|
||||
const BuildingScript: GDScript = preload("res://engine/src/entities/building.gd")
|
||||
|
||||
const ERA_NAMES: Array[String] = [
|
||||
"age_of_founding",
|
||||
"age_of_discovery",
|
||||
"age_of_magic",
|
||||
"age_of_empires",
|
||||
"age_of_mastery",
|
||||
]
|
||||
## Era definitions loaded from the game pack's eras.json via DataLoader.
|
||||
## The engine defines no era names — all era content is game-pack-driven.
|
||||
var era_data: Array = []
|
||||
|
||||
const DEFAULT_SETTINGS: Dictionary = {
|
||||
"map_size": "small",
|
||||
|
|
@ -23,6 +19,7 @@ const DEFAULT_SETTINGS: Dictionary = {
|
|||
"num_players": 4,
|
||||
"turn_limit": 150,
|
||||
"mana_density": "normal",
|
||||
"era_difficulty_correlation": true,
|
||||
}
|
||||
|
||||
## Player colors for up to 12 players
|
||||
|
|
@ -94,6 +91,8 @@ func initialize_game(settings: Dictionary) -> void:
|
|||
|
||||
turn_number = 1
|
||||
era = 0
|
||||
era_data = DataLoader.get_data("eras").values() if DataLoader.get_data("eras") is Dictionary else []
|
||||
era_data.sort_custom(func(a: Dictionary, b: Dictionary) -> bool: return a.get("id", "") < b.get("id", ""))
|
||||
current_player_index = 0
|
||||
players = []
|
||||
layers = []
|
||||
|
|
@ -156,13 +155,27 @@ func create_player(
|
|||
|
||||
|
||||
func get_era_name() -> String:
|
||||
if era >= 0 and era < ERA_NAMES.size():
|
||||
return ERA_NAMES[era]
|
||||
if era >= 0 and era < era_data.size():
|
||||
return era_data[era].get("name", "unknown")
|
||||
return "unknown"
|
||||
|
||||
|
||||
func get_era_count() -> int:
|
||||
return era_data.size()
|
||||
|
||||
|
||||
func get_max_event_tier() -> int:
|
||||
## Returns the max event tier allowed in the current era.
|
||||
## When era_difficulty_correlation is disabled, returns 10 (uncapped).
|
||||
if not game_settings.get("era_difficulty_correlation", true):
|
||||
return 10
|
||||
if era >= 0 and era < era_data.size():
|
||||
return era_data[era].get("max_event_tier", 10)
|
||||
return 10
|
||||
|
||||
|
||||
func advance_era() -> void:
|
||||
if era < ERA_NAMES.size() - 1:
|
||||
if era < era_data.size() - 1:
|
||||
era += 1
|
||||
EventBus.era_changed.emit(era, current_player_index)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue