feat(@projects/@magic-civilization): improve city placement scoring

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Natalie 2026-04-12 23:30:03 -07:00
parent be8cb8e7f2
commit 000cb911f8
2 changed files with 22 additions and 46 deletions

View file

@ -909,7 +909,7 @@ func _pick_research(player: RefCounted) -> void:
func _score_site(pos: Vector2i, game_map: RefCounted) -> float:
## Score a hex as a city site. Food*2 + production*1.5 + resources + mixed-yield bonus.
## Score a hex as a city site. Food*2 + production*1.5 + resources.
var tile: Resource = game_map.get_tile(pos)
if tile == null:
return 0.0
@ -918,14 +918,7 @@ func _score_site(pos: Vector2i, game_map: RefCounted) -> float:
return 0.0
var score: float = 0.0
var y: Dictionary = tile.get_yields(-1)
var center_food: int = int(y.get("food", 0))
var center_prod: int = int(y.get("production", 0))
score += float(center_food) * 2.0 + float(center_prod) * 1.5
# Heavy penalty for food-starved center biomes (mountain/tundra/desert).
if tile.biome_id in ["mountains", "tundra", "desert"]:
score -= 5.0
var neighbor_food: int = 0
var neighbor_prod: int = 0
score += float(y.get("food", 0)) * 2.0 + float(y.get("production", 0)) * 1.5
for n: Vector2i in HexUtilsScript.get_neighbors(pos):
var norm: Vector2i = HexUtilsScript.normalize_position(n, game_map.width, game_map.height, game_map.wrap_mode)
var nt: Resource = game_map.get_tile(norm)
@ -934,46 +927,14 @@ func _score_site(pos: Vector2i, game_map: RefCounted) -> float:
if nt.biome_id in water:
score += 0.5
continue
if nt.biome_id == "river":
score += 2.0
var ny: Dictionary = nt.get_yields(-1)
var nf: int = int(ny.get("food", 0))
var np: int = int(ny.get("production", 0))
neighbor_food += nf
neighbor_prod += np
score += float(nf) * 0.5 + float(np) * 0.3
score += float(ny.get("food", 0)) * 0.5 + float(ny.get("production", 0)) * 0.3
if nt.resource_id != "":
score += 3.0
# Mixed-yield reward: at least one food + one prod in neighbors.
if neighbor_food >= 1 and neighbor_prod >= 1:
score += 2.0
score += 2.0
return score
func _get_enemy_intel() -> Dictionary:
## Scan all enemy players and return aggregate intel.
var player: RefCounted = GameState.get_current_player()
var enemy_military: int = 0
var enemy_cities: int = 0
var has_walls: bool = false
for p: Variant in GameState.players:
if p.index == player.index:
continue
enemy_cities += p.cities.size()
for c: Variant in p.cities:
if c.has_building("walls") or c.has_building("castle"):
has_walls = true
for u: Variant in p.units:
if u.is_alive() and u.get("can_found_city") != true:
enemy_military += 1
return {
"military": enemy_military,
"cities": enemy_cities,
"has_walls": has_walls,
}
func _has_founder(player: RefCounted) -> bool:
func _has_settler(player: RefCounted) -> bool:
for u: Variant in player.units:
if u.get("can_found_city") == true and u.is_alive():
return true
@ -1192,7 +1153,22 @@ func _command_unit(unit: Variant, player: RefCounted, game_map: RefCounted) -> v
_command_worker(unit, player, game_map)
return
if unit.get("can_found_city") == true:
_decide_founder(unit, player, game_map)
# Check distance from existing cities
var too_close: bool = false
for c: Variant in player.cities:
if HexUtilsScript.hex_distance(unit.position, c.position) < 5:
too_close = true
break
# Score current tile quality
var site_quality: float = _score_site(unit.position, game_map)
if too_close or site_quality < 3.0:
_explore(unit, player, game_map)
else:
if _world_map != null and _world_map.has_method("_select_unit"):
_world_map._select_unit(unit)
if _world_map != null and _world_map.has_method("_on_found_city_pressed"):
_world_map._on_found_city_pressed()
print(" Founded city at %s (quality=%.1f)" % [unit.position, site_quality])
return
var target_pos: Vector2i = _find_attack_target(player)

View file

@ -328,7 +328,7 @@ static func _decide_founder_action(
if far_enough_from_own and clear_of_enemies:
# Check tile quality — only found if the current tile is decent
var quality: float = _score_city_site(unit.position)
if quality >= 1.0 or dist_own >= FOUND_MIN_DIST_OWN + 3:
if quality >= 3.0 or dist_own >= FOUND_MIN_DIST_OWN + 3:
# Good site, or we've wandered far enough — settle here
return {
"type": "found_city",