feat(generation): ✨ Improve water terrain classification, placement, and start position logic by updating biome registry, map generator, and placer to handle legacy water terrains properly
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
ede8a5b969
commit
268c93951f
4 changed files with 27 additions and 79 deletions
|
|
@ -39,6 +39,27 @@ func rebuild_from_data() -> void:
|
|||
_reverse_cache[tag] = [] as Array[String]
|
||||
_reverse_cache[tag].append(biome_id)
|
||||
|
||||
# Fold in legacy water terrains from terrain.json (ocean, coast, lake,
|
||||
# inland_sea) that may not have matching entries in biomes.json. Without
|
||||
# this, map generation code that queries BiomeRegistry for "is_water"
|
||||
# treats water tiles as land and mis-classifies whole maps.
|
||||
for terrain: Dictionary in DataLoader.get_all_terrains():
|
||||
var terrain_id: String = terrain.get("id", "")
|
||||
if terrain_id.is_empty() or _tag_cache.has(terrain_id):
|
||||
continue
|
||||
var flags: Array = terrain.get("flags", [])
|
||||
var derived: Array[String] = []
|
||||
if "water" in flags:
|
||||
derived.append("is_water")
|
||||
if derived.is_empty():
|
||||
continue
|
||||
_tag_cache[terrain_id] = derived
|
||||
for tag: String in derived:
|
||||
if not _reverse_cache.has(tag):
|
||||
_reverse_cache[tag] = [] as Array[String]
|
||||
if terrain_id not in _reverse_cache[tag]:
|
||||
_reverse_cache[tag].append(terrain_id)
|
||||
|
||||
_loaded = true
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -38,9 +38,6 @@ var _settings: Dictionary = {}
|
|||
|
||||
|
||||
func generate(settings: Dictionary) -> RefCounted:
|
||||
print("[mapgen] generate() called with map_type=%s map_size=%s" % [
|
||||
settings.get("map_type", "?"), settings.get("map_size", "?"),
|
||||
])
|
||||
## Generate a complete map from settings.
|
||||
##
|
||||
## Settings keys:
|
||||
|
|
@ -179,28 +176,11 @@ func _generate_terrain(game_map: RefCounted, type_data: Dictionary) -> void:
|
|||
# Stage 7b: Derive substrate_id from elevation + geology
|
||||
_derive_substrates(game_map)
|
||||
|
||||
var _dbg_land_before_patches: int = 0
|
||||
for axial: Vector2i in game_map.tiles:
|
||||
if not BiomeRegistry.has_tag(game_map.tiles[axial].biome_id, "is_water"):
|
||||
_dbg_land_before_patches += 1
|
||||
print("[mapgen] before_patches land=%d" % _dbg_land_before_patches)
|
||||
|
||||
# Stage 8: Terrain patch expansion
|
||||
TerrainRefinerScript.assign_terrain_patches(
|
||||
game_map, type_data, _elevation, _moisture, _temperature, _rng
|
||||
)
|
||||
|
||||
var _dbg_land_after_patches: int = 0
|
||||
var _dbg_biomes: Dictionary = {}
|
||||
for axial: Vector2i in game_map.tiles:
|
||||
var bid: String = game_map.tiles[axial].biome_id
|
||||
_dbg_biomes[bid] = _dbg_biomes.get(bid, 0) + 1
|
||||
if not BiomeRegistry.has_tag(bid, "is_water"):
|
||||
_dbg_land_after_patches += 1
|
||||
print("[mapgen] after_patches land=%d biomes=%s" % [
|
||||
_dbg_land_after_patches, str(_dbg_biomes),
|
||||
])
|
||||
|
||||
# Stage 9: Wind map (full 3-cell atmospheric model) + quality
|
||||
WindCalculatorScript.compute_wind_map(game_map)
|
||||
TerrainRefinerScript.assign_quality(game_map)
|
||||
|
|
@ -346,27 +326,13 @@ func _assign_sea_level(
|
|||
roundi(ocean_target * all_elevs.size()), 0, all_elevs.size() - 1
|
||||
)
|
||||
var sea_level: float = all_elevs[idx]
|
||||
print("[mapgen] sea_level=%.3f ocean_target=%.3f total=%d elev_min=%.3f elev_max=%.3f" % [
|
||||
sea_level, ocean_target, all_elevs.size(),
|
||||
all_elevs[0], all_elevs[all_elevs.size() - 1],
|
||||
])
|
||||
|
||||
var _dbg_land_pre: int = 0
|
||||
for axial: Vector2i in game_map.tiles:
|
||||
var is_land_pre: bool = _elevation.get(axial, 0.0) >= sea_level
|
||||
if is_land_pre:
|
||||
_dbg_land_pre += 1
|
||||
game_map.tiles[axial].biome_id = (
|
||||
"ocean" if _elevation.get(axial, 0.0) < sea_level else "land"
|
||||
)
|
||||
print("[mapgen] pre-smooth land=%d/%d" % [_dbg_land_pre, game_map.tiles.size()])
|
||||
|
||||
TerrainRefinerScript.smooth_coastlines(game_map, gen_params)
|
||||
var _dbg_land_post: int = 0
|
||||
for axial: Vector2i in game_map.tiles:
|
||||
if game_map.tiles[axial].biome_id != "ocean":
|
||||
_dbg_land_post += 1
|
||||
print("[mapgen] post-smooth land=%d/%d" % [_dbg_land_post, game_map.tiles.size()])
|
||||
TerrainRefinerScript.assign_coast_tiles(game_map)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -28,10 +28,8 @@ func place_all(
|
|||
game_map: RefCounted, settings: Dictionary,
|
||||
num_players: int, wonder_count: int, type_data: Dictionary,
|
||||
) -> void:
|
||||
print("[placer] place_all start tiles=%d" % game_map.tiles.size())
|
||||
## Run the full placement pipeline on a terrain-generated map.
|
||||
place_natural_wonders(game_map, wonder_count)
|
||||
print("[placer] after_wonders")
|
||||
|
||||
var resource_mult: float = _get_density_multiplier(
|
||||
settings.get("resource_density", "standard"), "resources"
|
||||
|
|
@ -39,14 +37,6 @@ func place_all(
|
|||
place_resources(game_map, resource_mult)
|
||||
|
||||
var start_strategy: String = settings.get("start_strategy", "")
|
||||
var _dbg_biomes: Dictionary = {}
|
||||
var _dbg_is_land: int = 0
|
||||
for _axial: Vector2i in game_map.tiles:
|
||||
var _bid: String = game_map.tiles[_axial].biome_id
|
||||
_dbg_biomes[_bid] = _dbg_biomes.get(_bid, 0) + 1
|
||||
if game_map.tiles[_axial].is_land():
|
||||
_dbg_is_land += 1
|
||||
print("[placer] biomes=%s is_land_count=%d" % [str(_dbg_biomes), _dbg_is_land])
|
||||
var start_positions: Array[Vector2i] = _start_position.select_start_positions(
|
||||
game_map, num_players, type_data, start_strategy
|
||||
)
|
||||
|
|
|
|||
|
|
@ -21,27 +21,19 @@ func select_start_positions(
|
|||
var prefer_coast: bool = gen_params.get(
|
||||
"start_position_prefer_coast", true
|
||||
)
|
||||
print("[start] dispatch strategy='%s' type_id='%s' num_players=%d min_dist=%d" % [
|
||||
start_strategy, type_data.get("id", ""), num_players,
|
||||
gen_params.get("start_position_min_distance", 10),
|
||||
])
|
||||
|
||||
# Dispatch to specialised strategy if provided
|
||||
var result: Array[Vector2i]
|
||||
match start_strategy:
|
||||
"quadrant":
|
||||
result = _select_starts_quadrant(game_map, num_players, prefer_coast)
|
||||
return _select_starts_quadrant(game_map, num_players, prefer_coast)
|
||||
"per_continent":
|
||||
result = _select_starts_per_continent(game_map, num_players, prefer_coast)
|
||||
return _select_starts_per_continent(game_map, num_players, prefer_coast)
|
||||
"arm_tips":
|
||||
var type_id: String = type_data.get("id", "")
|
||||
result = _select_starts_arm_tips(game_map, num_players, prefer_coast, type_id)
|
||||
_:
|
||||
result = _select_starts_greedy(game_map, num_players, gen_params, prefer_coast)
|
||||
print("[start] result size=%d positions=%s" % [result.size(), str(result)])
|
||||
if result.size() == 2:
|
||||
print("[start] pair_dist=%d" % HexUtilsScript.hex_distance(result[0], result[1]))
|
||||
return result
|
||||
return _select_starts_arm_tips(game_map, num_players, prefer_coast, type_id)
|
||||
|
||||
# Default: greedy score-based with min-distance constraint
|
||||
return _select_starts_greedy(game_map, num_players, gen_params, prefer_coast)
|
||||
|
||||
|
||||
func _select_starts_greedy(
|
||||
|
|
@ -85,13 +77,6 @@ func _select_starts_greedy(
|
|||
if far_enough:
|
||||
selected.append(pos)
|
||||
|
||||
# DEBUG: report selected positions and pair distance for arena investigation
|
||||
if selected.size() == 2:
|
||||
print("[start] greedy min_distance=%d scored_count=%d selected=%s pair_dist=%d" % [
|
||||
min_distance, scored_tiles.size(), str(selected),
|
||||
HexUtilsScript.hex_distance(selected[0], selected[1]),
|
||||
])
|
||||
|
||||
return selected
|
||||
|
||||
|
||||
|
|
@ -287,34 +272,20 @@ func _score_all_start_candidates(
|
|||
) -> Array[Dictionary]:
|
||||
## Score all eligible start tiles and return sorted descending by score.
|
||||
var scored: Array[Dictionary] = []
|
||||
var _dbg_total: int = 0
|
||||
var _dbg_non_land: int = 0
|
||||
var _dbg_nw: int = 0
|
||||
var _dbg_tag: int = 0
|
||||
var _dbg_zero: int = 0
|
||||
for axial: Vector2i in game_map.tiles:
|
||||
_dbg_total += 1
|
||||
var tile: Variant = game_map.tiles[axial]
|
||||
if not tile.is_land():
|
||||
_dbg_non_land += 1
|
||||
continue
|
||||
if tile.is_natural_wonder():
|
||||
_dbg_nw += 1
|
||||
continue
|
||||
if (
|
||||
BiomeRegistry.has_tag(tile.biome_id, "is_elevated")
|
||||
or BiomeRegistry.has_tag(tile.biome_id, "is_water")
|
||||
):
|
||||
_dbg_tag += 1
|
||||
continue
|
||||
var score: float = _score_start_position(game_map, axial, prefer_coast)
|
||||
if score > 0:
|
||||
scored.append({"position": axial, "score": score})
|
||||
else:
|
||||
_dbg_zero += 1
|
||||
print("[start] candidates total=%d non_land=%d nw=%d tag=%d zero=%d qualifying=%d" % [
|
||||
_dbg_total, _dbg_non_land, _dbg_nw, _dbg_tag, _dbg_zero, scored.size()
|
||||
])
|
||||
scored.sort_custom(
|
||||
func(a: Dictionary, b: Dictionary) -> bool:
|
||||
return a["score"] > b["score"]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue