refactor(engine-specific): ♻️ Restructure autoloaded engine systems to optimize initialization and interaction between DataLoader, EventBus, GameState, TurnManager, and SpriteManifest
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
d8934e0a66
commit
8148272564
5 changed files with 524 additions and 11 deletions
|
|
@ -23,6 +23,7 @@ const DATA_CATEGORIES: Array[String] = [
|
|||
"victories",
|
||||
"villages",
|
||||
"wilds",
|
||||
"npc_buildings",
|
||||
"difficulty",
|
||||
"setup",
|
||||
"ai_personalities",
|
||||
|
|
@ -33,6 +34,11 @@ const DATA_CATEGORIES: Array[String] = [
|
|||
"ley_line_params",
|
||||
"social_policies",
|
||||
"events",
|
||||
"world_biomes",
|
||||
"world_flora",
|
||||
"world_fauna",
|
||||
"world_ecosystem",
|
||||
"world_traits",
|
||||
]
|
||||
|
||||
## Categories stored as raw parsed Dictionaries rather than ID-keyed entries.
|
||||
|
|
@ -44,12 +50,43 @@ const RAW_CATEGORIES: Array[String] = [
|
|||
"ley_line_params",
|
||||
"social_policies",
|
||||
"events",
|
||||
"world_biomes",
|
||||
"world_flora",
|
||||
"world_fauna",
|
||||
"world_ecosystem",
|
||||
"world_traits",
|
||||
]
|
||||
|
||||
## Mapping from world_* categories to their data subdirectory names.
|
||||
const _WORLD_DIR_MAP: Dictionary = {
|
||||
"world_biomes": "world/biomes",
|
||||
"world_flora": "world/flora",
|
||||
"world_fauna": "world/fauna",
|
||||
"world_ecosystem": "world/ecosystem",
|
||||
"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")
|
||||
|
||||
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
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
|
|
@ -57,6 +94,16 @@ func _ready() -> void:
|
|||
_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:
|
||||
|
|
@ -65,18 +112,22 @@ 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:
|
||||
var dir_path: String = "%s/%s" % [base_path, category]
|
||||
var file_path: String = "%s/%s.json" % [base_path, category]
|
||||
# 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()
|
||||
_log_load_summary()
|
||||
|
||||
|
||||
|
|
@ -386,22 +437,22 @@ func get_terrains_by_flag(flag: String) -> Array:
|
|||
return results
|
||||
|
||||
|
||||
func get_resources_for_terrain(terrain_id: String) -> Array:
|
||||
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 terrain_id in terrains:
|
||||
if biome_id in terrains:
|
||||
results.append(entry)
|
||||
return results
|
||||
|
||||
|
||||
func get_improvements_for_terrain(terrain_id: String) -> Array:
|
||||
func get_improvements_for_terrain(biome_id: String) -> Array:
|
||||
var results: Array = []
|
||||
for entry: Dictionary in _data["improvements"].values():
|
||||
var valid: Variant = entry.get("valid_terrain", [])
|
||||
if valid is String and valid == "any_land" and terrain_id != "ocean":
|
||||
if valid is String and valid == "any_land" and biome_id != "ocean":
|
||||
results.append(entry)
|
||||
elif valid is Array and terrain_id in valid:
|
||||
elif valid is Array and biome_id in valid:
|
||||
results.append(entry)
|
||||
return results
|
||||
|
||||
|
|
@ -463,6 +514,126 @@ 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 --
|
||||
|
||||
|
||||
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", {})
|
||||
|
||||
|
||||
# -- Internal helpers --
|
||||
|
||||
|
||||
|
|
@ -483,3 +654,106 @@ 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)
|
||||
|
|
|
|||
|
|
@ -100,8 +100,6 @@ signal tile_culture_flipped(tile_pos: Vector2i, old_owner: int, new_owner: int)
|
|||
# -- Climate signals --
|
||||
signal terrain_transformed(tile: Variant, old_type: String, new_type: String)
|
||||
signal quality_changed(tile: Variant, old_quality: int, new_quality: int)
|
||||
signal corruption_spread(tile: Variant)
|
||||
signal corruption_healed(tile: Variant)
|
||||
signal wind_recalculated
|
||||
signal weather_spell_cast(spell_id: String, position: Vector2i)
|
||||
signal weather_spell_expired(spell_id: String)
|
||||
|
|
@ -146,7 +144,7 @@ signal tile_clicked(axial: Vector2i)
|
|||
signal climate_phase_changed(new_label: String)
|
||||
## Request the map renderer to switch its active heatmap overlay.
|
||||
## mode: "none" | "temperature" | "moisture" | "wind_heatmap" | "weather"
|
||||
## | "land_value" | "water" | "elevation" | "corruption"
|
||||
## | "land_value" | "water" | "elevation"
|
||||
signal map_overlay_changed(mode: String)
|
||||
## Refresh weather footprint highlights; effects from weather.get_active_effects().
|
||||
signal weather_effects_updated(effects: Array)
|
||||
|
|
@ -172,6 +170,22 @@ signal marine_creature_spawned(tile_pos: Vector2i, creature_type: String)
|
|||
## A corrupted marine creature was removed from a tile.
|
||||
signal marine_creature_cleared(tile_pos: Vector2i, creature_type: String)
|
||||
|
||||
# -- Ecology signals --
|
||||
## A lair's habitat degraded below threshold for too long — converted to ruin.
|
||||
signal lair_abandoned(pos: Vector2i)
|
||||
## A lair's habitat is thriving — creatures quality-up faster.
|
||||
signal lair_thriving(pos: Vector2i, new_tier: int)
|
||||
## Global ecosystem health score updated.
|
||||
signal ecosystem_health_updated(score: float)
|
||||
## Tile's biome reclassified by BiomeClassifier.
|
||||
signal biome_changed(pos: Vector2i, old_biome: String, new_biome: String)
|
||||
## An individual creature died (from age, starvation, predation, or player).
|
||||
signal creature_died(pos: Vector2i, species_name: String, quality: int)
|
||||
## A new creature was born via reproduction.
|
||||
signal creature_born(pos: Vector2i, species_name: String)
|
||||
## Tile crossed Q4 threshold — natural wonder emerged from ecology.
|
||||
signal landmark_formed(pos: Vector2i, name: String, quality: int)
|
||||
|
||||
# -- Natural event signals --
|
||||
signal natural_event_spawned(event_type: String, position: Vector2i, intensity: float)
|
||||
signal natural_event_moved(event_type: String, from_pos: Vector2i, to_pos: Vector2i)
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ extends Node
|
|||
|
||||
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",
|
||||
|
|
@ -80,6 +81,11 @@ var ley_anchors: Array = []
|
|||
## Array of LeyNetwork.LeyEdge objects — used by renderer for visualization.
|
||||
var ley_edges: Array = []
|
||||
|
||||
## NPC buildings on the world map (lairs, villages, ruins). Array of Building.
|
||||
var npc_buildings: Array = []
|
||||
## Spatial index: "col,row" -> Array[Building] for quick tile lookups.
|
||||
var _npc_buildings_by_tile: Dictionary = {}
|
||||
|
||||
|
||||
func initialize_game(settings: Dictionary) -> void:
|
||||
game_settings = DEFAULT_SETTINGS.duplicate()
|
||||
|
|
@ -95,6 +101,8 @@ func initialize_game(settings: Dictionary) -> void:
|
|||
ley_anchors = []
|
||||
ley_edges = []
|
||||
diplomacy = {}
|
||||
npc_buildings = []
|
||||
_npc_buildings_by_tile = {}
|
||||
|
||||
# Create primary map layer (index 0)
|
||||
(
|
||||
|
|
@ -183,6 +191,46 @@ func get_game_map() -> RefCounted: # Returns GameMap
|
|||
return null
|
||||
|
||||
|
||||
## -- NPC building management --
|
||||
|
||||
|
||||
func add_npc_building(building: RefCounted) -> void:
|
||||
npc_buildings.append(building)
|
||||
var key: String = "%d,%d" % [building.position.x, building.position.y]
|
||||
if not _npc_buildings_by_tile.has(key):
|
||||
_npc_buildings_by_tile[key] = []
|
||||
_npc_buildings_by_tile[key].append(building)
|
||||
|
||||
|
||||
func remove_npc_building(building: RefCounted) -> void:
|
||||
npc_buildings.erase(building)
|
||||
var key: String = "%d,%d" % [building.position.x, building.position.y]
|
||||
if _npc_buildings_by_tile.has(key):
|
||||
_npc_buildings_by_tile[key].erase(building)
|
||||
|
||||
|
||||
func get_npc_buildings_at(pos: Vector2i) -> Array:
|
||||
var key: String = "%d,%d" % [pos.x, pos.y]
|
||||
return _npc_buildings_by_tile.get(key, [])
|
||||
|
||||
|
||||
func get_npc_building_at(pos: Vector2i, type_filter: String = "") -> Variant:
|
||||
## Returns the first NPC building at pos, optionally filtered by type_id. Null if none.
|
||||
var buildings: Array = get_npc_buildings_at(pos)
|
||||
for b: Variant in buildings:
|
||||
if type_filter == "" or b.type_id == type_filter:
|
||||
return b
|
||||
return null
|
||||
|
||||
|
||||
func get_all_npc_buildings_of_type(type_id: String) -> Array:
|
||||
var result: Array = []
|
||||
for b: Variant in npc_buildings:
|
||||
if b.type_id == type_id:
|
||||
result.append(b)
|
||||
return result
|
||||
|
||||
|
||||
func serialize() -> Dictionary:
|
||||
var data: Dictionary = {
|
||||
"current_theme": current_theme,
|
||||
|
|
@ -195,6 +243,7 @@ func serialize() -> Dictionary:
|
|||
"layers": [],
|
||||
"transit_nodes": transit_nodes,
|
||||
"ley_anchors": _serialize_ley_anchors(),
|
||||
"npc_buildings": _serialize_npc_buildings(),
|
||||
}
|
||||
|
||||
# Serialize ascension rituals: {player_index_str -> ritual_dict}
|
||||
|
|
@ -224,6 +273,7 @@ func deserialize(data: Dictionary) -> void:
|
|||
transit_nodes = data.get("transit_nodes", [])
|
||||
wonders_built = data.get("wonders_built", {}).duplicate()
|
||||
_deserialize_ley_anchors(data.get("ley_anchors", []))
|
||||
_deserialize_npc_buildings(data.get("npc_buildings", []))
|
||||
|
||||
# Deserialize ascension rituals
|
||||
var AscensionRitualScript: GDScript = preload(
|
||||
|
|
@ -358,6 +408,23 @@ func _deserialize_ley_anchors(raw: Array) -> void:
|
|||
)
|
||||
|
||||
|
||||
func _serialize_npc_buildings() -> Array:
|
||||
var result: Array = []
|
||||
for b: Variant in npc_buildings:
|
||||
if b is BuildingScript:
|
||||
result.append(b.to_dict())
|
||||
return result
|
||||
|
||||
|
||||
func _deserialize_npc_buildings(raw: Array) -> void:
|
||||
npc_buildings = []
|
||||
_npc_buildings_by_tile = {}
|
||||
for entry: Variant in raw:
|
||||
if entry is Dictionary:
|
||||
var b: RefCounted = BuildingScript.from_dict(entry)
|
||||
add_npc_building(b)
|
||||
|
||||
|
||||
func rebuild_layer_references() -> void:
|
||||
## After deserialization, rebuild the primary layer's unit list
|
||||
## from all player unit arrays.
|
||||
|
|
|
|||
148
engine/src/autoloads/sprite_manifest.gd
Normal file
148
engine/src/autoloads/sprite_manifest.gd
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
extends Node
|
||||
## SQLite-backed sprite manifest for ecology assets.
|
||||
## Opens sprites.db (read-only) and provides lookup by entity/quality/variant.
|
||||
## Registered as autoload — warns but does not crash if DB is missing.
|
||||
|
||||
var _db: Variant = null # SQLiteDatabase instance (nullable)
|
||||
var _db_path: String = ""
|
||||
var _db_available: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
pass
|
||||
|
||||
|
||||
func open_db(theme_id: String) -> void:
|
||||
## Open the sprite database for the given game pack.
|
||||
## Called after DataLoader.load_theme() to know the active pack.
|
||||
close_db()
|
||||
_db_path = "res://games/%s/data/sprites.db" % theme_id
|
||||
|
||||
if not FileAccess.file_exists(_db_path):
|
||||
push_warning(
|
||||
"SpriteManifest: sprites.db not found at %s — sprite lookups will return empty"
|
||||
% _db_path
|
||||
)
|
||||
return
|
||||
|
||||
# SQLite GDExtension: open read-only
|
||||
var sqlite_class: Variant = ClassDB.instantiate("SQLite")
|
||||
if sqlite_class == null:
|
||||
push_warning(
|
||||
"SpriteManifest: SQLite GDExtension not available — sprite lookups disabled"
|
||||
)
|
||||
return
|
||||
|
||||
_db = sqlite_class
|
||||
_db.path = _db_path
|
||||
_db.read_only = true
|
||||
if not _db.open_db():
|
||||
push_warning("SpriteManifest: Failed to open %s" % _db_path)
|
||||
_db = null
|
||||
return
|
||||
|
||||
_db_available = true
|
||||
print("SpriteManifest: Opened %s" % _db_path)
|
||||
|
||||
|
||||
func close_db() -> void:
|
||||
if _db != null and _db_available:
|
||||
_db.close_db()
|
||||
_db = null
|
||||
_db_available = false
|
||||
|
||||
|
||||
func is_available() -> bool:
|
||||
return _db_available
|
||||
|
||||
|
||||
func get_sprite(
|
||||
entity_type: String,
|
||||
entity_id: String,
|
||||
quality: int = -1,
|
||||
variant: int = 0,
|
||||
) -> String:
|
||||
## Returns the asset path for a sprite, or "" if not found.
|
||||
## quality = -1 means quality-agnostic lookup (e.g. substrate sprites).
|
||||
if not _db_available:
|
||||
return ""
|
||||
|
||||
var query: String
|
||||
var params: Array
|
||||
if quality < 0:
|
||||
query = (
|
||||
"SELECT path FROM sprites WHERE entity_type = ? AND entity_id = ? AND variant = ? LIMIT 1"
|
||||
)
|
||||
params = [entity_type, entity_id, variant]
|
||||
else:
|
||||
query = (
|
||||
"SELECT path FROM sprites WHERE entity_type = ? AND entity_id = ? AND quality = ? AND variant = ? LIMIT 1"
|
||||
)
|
||||
params = [entity_type, entity_id, quality, variant]
|
||||
|
||||
if not _db.query_with_bindings(query, params):
|
||||
return ""
|
||||
|
||||
var results: Array = _db.query_result
|
||||
if results.size() == 0:
|
||||
return ""
|
||||
return results[0].get("path", "")
|
||||
|
||||
|
||||
func has_sprite(
|
||||
entity_type: String, entity_id: String, quality: int = -1
|
||||
) -> bool:
|
||||
## Returns true if at least one sprite exists for the given entity.
|
||||
if not _db_available:
|
||||
return false
|
||||
|
||||
var query: String
|
||||
var params: Array
|
||||
if quality < 0:
|
||||
query = (
|
||||
"SELECT 1 FROM sprites WHERE entity_type = ? AND entity_id = ? LIMIT 1"
|
||||
)
|
||||
params = [entity_type, entity_id]
|
||||
else:
|
||||
query = (
|
||||
"SELECT 1 FROM sprites WHERE entity_type = ? AND entity_id = ? AND quality = ? LIMIT 1"
|
||||
)
|
||||
params = [entity_type, entity_id, quality]
|
||||
|
||||
if not _db.query_with_bindings(query, params):
|
||||
return false
|
||||
return _db.query_result.size() > 0
|
||||
|
||||
|
||||
func validate_sprites() -> Array[String]:
|
||||
## Pre-flight validation: checks all substrates, biomes x quality, species.
|
||||
## Returns array of missing sprite descriptions. Empty = all good.
|
||||
var missing: Array[String] = []
|
||||
|
||||
if not _db_available:
|
||||
missing.append("sprites.db not available at %s" % _db_path)
|
||||
return missing
|
||||
|
||||
# Validate substrate sprites
|
||||
var substrates: Array = DataLoader.get_all_substrates()
|
||||
for sub: Variant in substrates:
|
||||
if not has_sprite("substrate", sub.id):
|
||||
missing.append("substrate:%s" % sub.id)
|
||||
|
||||
# Validate biome sprites across quality ranges
|
||||
var biomes: Array = DataLoader.get_all_biomes()
|
||||
for biome: Variant in biomes:
|
||||
var qr: Vector2i = biome.quality_range
|
||||
for q in range(qr.x, qr.y + 1):
|
||||
if not has_sprite("biome", biome.id, q):
|
||||
missing.append("biome:%s:q%d" % [biome.id, q])
|
||||
|
||||
if missing.size() > 0:
|
||||
push_error(
|
||||
(
|
||||
"SpriteManifest: %d missing sprites:\n %s"
|
||||
% [missing.size(), "\n ".join(missing)]
|
||||
)
|
||||
)
|
||||
|
||||
return missing
|
||||
|
|
@ -28,6 +28,12 @@ const MarineHarvestScript: GDScript = preload(
|
|||
const TurnProcessorScript: GDScript = preload(
|
||||
"res://engine/src/modules/management/turn_processor.gd"
|
||||
)
|
||||
const EcosystemScript: GDScript = preload(
|
||||
"res://engine/src/modules/ecology/ecosystem.gd"
|
||||
)
|
||||
const EcologyDBScript: GDScript = preload(
|
||||
"res://engine/src/modules/ecology/ecology_db.gd"
|
||||
)
|
||||
|
||||
enum Phase {
|
||||
NONE,
|
||||
|
|
@ -56,6 +62,8 @@ var climate: RefCounted = ClimateScript.new() # Climate — per-tile physics
|
|||
var climate_effects: RefCounted = ClimateEffectsScript.new() # ClimateEffects — unit damage
|
||||
var diplomacy: RefCounted = DiplomacyScript.new() # Diplomacy — relationship state
|
||||
var marine_harvest: RefCounted = MarineHarvestScript.new() # MarineHarvest — ocean ecology
|
||||
var ecosystem: RefCounted = EcosystemScript.new() # EcosystemOrchestrator — flora+fauna+quality
|
||||
var ecology_db: RefCounted = EcologyDBScript.new() # EcologyDB — SQLite creature storage
|
||||
var _processor: RefCounted = null # TurnProcessor — wired in _ready
|
||||
|
||||
|
||||
|
|
@ -71,6 +79,8 @@ func _ready() -> void:
|
|||
proc.climate = climate
|
||||
proc.climate_effects = climate_effects
|
||||
proc.marine_harvest = marine_harvest
|
||||
proc.ecosystem = ecosystem
|
||||
proc.ecology_db = ecology_db
|
||||
|
||||
|
||||
func _on_tech_research_started(tech_id: String, player_index: int) -> void:
|
||||
|
|
@ -220,7 +230,7 @@ func next_player() -> void:
|
|||
# Diplomacy tick: decay timed modifiers and agreements once per full turn.
|
||||
(diplomacy as DiplomacyScript).process_turn()
|
||||
# Protection building effects: write mitigation fields onto city tiles
|
||||
# BEFORE climate runs so aerosol_mitigation and corruption_resistance are active.
|
||||
# BEFORE climate runs so aerosol_mitigation is active.
|
||||
var game_map_for_climate: RefCounted = GameState.get_game_map()
|
||||
if game_map_for_climate != null:
|
||||
EconomyScript.apply_protection_effects(game_map_for_climate, GameState.players)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue