refactor(ecology): ♻️ Restructure fauna tracking and optimize water body detection with improved database handling
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
da7a90f646
commit
c78443fa4b
3 changed files with 109 additions and 3 deletions
|
|
@ -94,6 +94,7 @@ func add_species(data: Dictionary) -> int:
|
|||
"maturity_age": data.get("maturity_age", 5),
|
||||
"max_age": data.get("max_age", 50),
|
||||
"quality_up_threshold": data.get("quality_up_threshold", 20),
|
||||
"migration_pattern": data.get("migration_pattern", ""),
|
||||
}
|
||||
|
||||
_species[id] = row
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ const LV_SUBSTEPS: int = 3
|
|||
const PERTURBATION: float = 0.05
|
||||
const MIN_VIABLE_POPULATION: int = 2
|
||||
const QUALITY_MISMATCH_TIERS: int = 2
|
||||
## Seasonal migration occurs every N turns (approximate season length).
|
||||
const SEASONAL_MIGRATION_INTERVAL: int = 12
|
||||
const LOOT_DROP_CHANCE: Dictionary = {1: 0.0, 2: 0.0, 3: 0.25, 4: 0.60, 5: 1.0}
|
||||
const GRAZING_BY_SIZE: Dictionary = {
|
||||
"tiny": 0.005, "small": 0.01, "medium": 0.02, "large": 0.04, "huge": 0.08,
|
||||
|
|
@ -24,7 +26,9 @@ func invalidate_region_cache() -> void:
|
|||
_biome_regions.clear()
|
||||
|
||||
|
||||
func process_turn(game_map: Variant, ecology_db: Variant, seed: int) -> void:
|
||||
func process_turn(
|
||||
game_map: Variant, ecology_db: Variant, seed: int, turn_number: int = 0
|
||||
) -> void:
|
||||
rng.seed = seed
|
||||
var lp: Variant = DataLoader.get_land_fauna_params()
|
||||
var mp: Variant = DataLoader.get_marine_fauna_params()
|
||||
|
|
@ -37,6 +41,8 @@ func process_turn(game_map: Variant, ecology_db: Variant, seed: int) -> void:
|
|||
_process_herbivore_grazing(game_map, ecology_db)
|
||||
_enforce_carrying_capacity(ecology_db, mp)
|
||||
_process_migration(game_map, ecology_db, mp)
|
||||
if turn_number > 0 and turn_number % SEASONAL_MIGRATION_INTERVAL == 0:
|
||||
_process_seasonal_migration(game_map, ecology_db)
|
||||
_process_lair_habitat(game_map, ecology_db, lp)
|
||||
|
||||
|
||||
|
|
@ -333,7 +339,11 @@ func _find_target(
|
|||
if not game_map.tiles.has(c):
|
||||
continue
|
||||
var ct: Variant = game_map.tiles[c]
|
||||
if not ct.is_water() and ct.biome_id != ob:
|
||||
# Amphibious species can cross land/water boundaries
|
||||
var habitat: String = sp.get("habitat", "terrestrial")
|
||||
if habitat == "amphibious":
|
||||
pass # amphibious can go anywhere (land or water)
|
||||
elif not ct.is_water() and ct.biome_id != ob:
|
||||
continue
|
||||
if _is_whale(sp) and mp != null:
|
||||
if not ct.is_water() or ct.depth_from_coast < mp.whale_min_depth or ct.depth_from_coast > mp.whale_max_depth or ct.quality < mp.whale_min_quality:
|
||||
|
|
@ -387,6 +397,58 @@ func _abandon_lair(pos: Vector2i, tile: Variant, ecology_db: Variant) -> void:
|
|||
EventBus.lair_abandoned.emit(pos)
|
||||
|
||||
|
||||
# -- Seasonal migration (aerial herd/swarm move to warmer biome) --
|
||||
|
||||
func _process_seasonal_migration(game_map: Variant, ecology_db: Variant) -> void:
|
||||
## Seasonal migrants move toward warmer tiles within migration_range.
|
||||
## Simulates flying herds/swarms following temperature gradients.
|
||||
var migrated: Dictionary = {} # creature_id -> true (prevent double-move)
|
||||
for pos: Vector2i in game_map.tiles:
|
||||
var creatures: Array = ecology_db.get_creatures_on_tile(pos.x, pos.y)
|
||||
for c: Dictionary in creatures:
|
||||
var cid: int = c.get("id", -1)
|
||||
if migrated.has(cid):
|
||||
continue
|
||||
var sp: Dictionary = ecology_db.get_species(c.get("species_id", -1))
|
||||
if sp.is_empty():
|
||||
continue
|
||||
if sp.get("migration_pattern", "") != "seasonal":
|
||||
continue
|
||||
var mrange: int = sp.get("migration_range", 3)
|
||||
var target: Vector2i = _find_seasonal_target(pos, sp, game_map, ecology_db, mrange)
|
||||
if target != Vector2i(-1, -1) and target != pos:
|
||||
ecology_db.update_creature(cid, {
|
||||
"tile_col": target.x, "tile_row": target.y,
|
||||
})
|
||||
migrated[cid] = true
|
||||
|
||||
|
||||
func _find_seasonal_target(
|
||||
origin: Vector2i, sp: Dictionary, game_map: Variant,
|
||||
ecology_db: Variant, search_range: int,
|
||||
) -> Vector2i:
|
||||
## Find the warmest tile within range that has capacity for this species.
|
||||
var otile: Variant = game_map.tiles.get(origin)
|
||||
if otile == null:
|
||||
return Vector2i(-1, -1)
|
||||
var best: Vector2i = Vector2i(-1, -1)
|
||||
var best_temp: float = otile.temperature
|
||||
for r: int in range(1, search_range + 1):
|
||||
for c: Vector2i in HexUtilsScript.hex_ring(origin, r):
|
||||
if not game_map.tiles.has(c):
|
||||
continue
|
||||
var ct: Variant = game_map.tiles[c]
|
||||
if ct.temperature <= best_temp:
|
||||
continue
|
||||
var count: int = ecology_db.get_creature_count_on_tile(c.x, c.y)
|
||||
var cap: float = sp.get("carrying_capacity", 10.0)
|
||||
if float(count) >= cap:
|
||||
continue
|
||||
best_temp = ct.temperature
|
||||
best = c
|
||||
return best
|
||||
|
||||
|
||||
# -- Biome region cache (flood-fill connected same-biome tiles) --
|
||||
|
||||
func _rebuild_biome_regions(game_map: Variant) -> void:
|
||||
|
|
|
|||
|
|
@ -75,15 +75,19 @@ static func identify_water_bodies(
|
|||
# Store in ecology_db
|
||||
ecology_db.add_water_body(body.id, body.type, body.size, body.tiles.size())
|
||||
|
||||
# Set water_body_id on each tile in this body
|
||||
# Set water_body_id and water_body_type on each tile
|
||||
for tile_pos: Vector2i in body.tiles:
|
||||
var tile: Variant = game_map.tiles.get(tile_pos)
|
||||
if tile != null:
|
||||
tile.water_body_id = body.id
|
||||
tile.water_body_type = body.type
|
||||
|
||||
# Compute depth_from_coast for all water tiles
|
||||
_compute_depth_from_coast(game_map, water_bodies, ecology_db)
|
||||
|
||||
# Detect river mouths: ocean tiles at depth <= 1 adjacent to river tiles
|
||||
_detect_river_mouths(game_map, water_bodies)
|
||||
|
||||
return water_bodies
|
||||
|
||||
|
||||
|
|
@ -152,3 +156,42 @@ static func _compute_depth_from_coast(
|
|||
|
||||
# Store in ecology_db
|
||||
ecology_db.add_water_body_tile(body_id, pos.x, pos.y, depth)
|
||||
|
||||
|
||||
## Detect river mouths: ocean/sea tiles at depth <= 1 with adjacent river tiles.
|
||||
## Sets is_river_mouth = true on qualifying tiles.
|
||||
static func _detect_river_mouths(
|
||||
game_map: Variant,
|
||||
water_bodies: Array,
|
||||
) -> void:
|
||||
# Build set of river tile positions
|
||||
var river_positions: Dictionary = {}
|
||||
for wb: Variant in water_bodies:
|
||||
if wb.type == "river":
|
||||
for pos: Vector2i in wb.tiles:
|
||||
river_positions[pos] = true
|
||||
|
||||
# Also consider land tiles with river_edges as river-adjacent
|
||||
for pos: Vector2i in game_map.tiles:
|
||||
var tile: Variant = game_map.tiles[pos]
|
||||
if not tile.river_edges.is_empty():
|
||||
river_positions[pos] = true
|
||||
|
||||
if river_positions.is_empty():
|
||||
return
|
||||
|
||||
# Check ocean tiles at depth <= 1 for river adjacency
|
||||
for wb: Variant in water_bodies:
|
||||
if wb.type != "ocean":
|
||||
continue
|
||||
for pos: Vector2i in wb.tiles:
|
||||
var tile: Variant = game_map.tiles.get(pos)
|
||||
if tile == null or tile.depth_from_coast > 1:
|
||||
continue
|
||||
var nbs: Array[Vector2i] = (
|
||||
game_map.get_valid_neighbor_positions(pos)
|
||||
)
|
||||
for nb: Vector2i in nbs:
|
||||
if nb in river_positions:
|
||||
tile.is_river_mouth = true
|
||||
break
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue