feat(@projects/@magic-civilization): ✅ improve land tile handling for demo
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
18072e1479
commit
981e856200
1 changed files with 85 additions and 17 deletions
|
|
@ -88,8 +88,14 @@ func _ready() -> void:
|
|||
|
||||
_p1 = GameState.players[0] as PlayerScript
|
||||
_p2 = GameState.players[1] as PlayerScript
|
||||
_p1_pos = _p1.units[0].position
|
||||
_p2_pos = _p2.units[0].position
|
||||
# Honour FORBIDDEN_BIOMES — relocate any founder that the harness dropped
|
||||
# onto ocean/coast/mountains to the nearest land tile so cities never
|
||||
# get founded on water in the demo (matches the production game's
|
||||
# `mc-mapgen::spawn_box` gate). See the user-reported issue.
|
||||
_p1_pos = _find_land_tile_near(_p1.units[0].position, 12)
|
||||
_p2_pos = _find_land_tile_near(_p2.units[0].position, 12)
|
||||
_p1.units[0].position = _p1_pos
|
||||
_p2.units[0].position = _p2_pos
|
||||
|
||||
_setup_renderers()
|
||||
_setup_camera_fit()
|
||||
|
|
@ -131,8 +137,18 @@ func _ready() -> void:
|
|||
_c1.set_population(7)
|
||||
await _capture("07_capital_pop7", "focus", _p1_pos, 6.0)
|
||||
|
||||
# Add placed buildings around the capital
|
||||
var ring1: Array[Vector2i] = _hex_ring(_p1_pos, 1)
|
||||
# Add placed buildings around the capital. Filter the ring to land
|
||||
# tiles only so the building icons never overlap ocean/coast hexes —
|
||||
# the visual demo should respect the same constraints the production
|
||||
# city-tile picker enforces (mc-city::tile_picker rejects water tiles).
|
||||
var ring1: Array[Vector2i] = _filter_land(_hex_ring(_p1_pos, 1))
|
||||
if ring1.size() < 4:
|
||||
# Top up from ring 2 if ring 1 had ocean/coast hexes pruned out.
|
||||
var ring2_land: Array[Vector2i] = _filter_land(_hex_ring(_p1_pos, 2))
|
||||
for r2: Vector2i in ring2_land:
|
||||
if ring1.size() >= 4:
|
||||
break
|
||||
ring1.append(r2)
|
||||
_c1.placed_buildings = {
|
||||
"dwarf_granary": ring1[0] if ring1.size() > 0 else _p1_pos,
|
||||
"dwarf_forge": ring1[1] if ring1.size() > 1 else _p1_pos,
|
||||
|
|
@ -164,9 +180,12 @@ func _ready() -> void:
|
|||
await _capture("12_two_culture_zones_wide", "fit")
|
||||
|
||||
# ── ACT IV: Military buildup ───────────────────────────────────────────
|
||||
# Filter the ring to land tiles so warriors never spawn on ocean —
|
||||
# matches `pathfinder.gd::_is_passable` rejecting `water` in the
|
||||
# unit's terrain flags for land units.
|
||||
var spawned: int = 0
|
||||
for n_pos: Vector2i in ring1:
|
||||
if not _game_map.tiles.has(n_pos):
|
||||
if not _is_land_tile(n_pos):
|
||||
continue
|
||||
var u: UnitScript = UnitScript.new("dwarf_warrior", 0, n_pos)
|
||||
u.id = "p1_army_%d" % spawned
|
||||
|
|
@ -176,24 +195,22 @@ func _ready() -> void:
|
|||
break
|
||||
await _capture("13_p1_army_3warriors", "focus", _p1_pos, 7.0)
|
||||
|
||||
# Add a mixed army: archer + scout
|
||||
var ring2: Array[Vector2i] = _hex_ring(_p1_pos, 2)
|
||||
if ring2.size() > 1 and _game_map.tiles.has(ring2[0]):
|
||||
var arch: UnitScript = UnitScript.new("dwarf_archer", 0, ring2[0])
|
||||
# Add a mixed army: archer + scout — same land-only filter.
|
||||
var ring2_land: Array[Vector2i] = _filter_land(_hex_ring(_p1_pos, 2))
|
||||
if ring2_land.size() > 0:
|
||||
var arch: UnitScript = UnitScript.new("dwarf_archer", 0, ring2_land[0])
|
||||
arch.id = "p1_archer"
|
||||
_p1.units.append(arch)
|
||||
if ring2.size() > 2 and _game_map.tiles.has(ring2[1]):
|
||||
var sct: UnitScript = UnitScript.new("dwarf_scout", 0, ring2[1])
|
||||
if ring2_land.size() > 1:
|
||||
var sct: UnitScript = UnitScript.new("dwarf_scout", 0, ring2_land[1])
|
||||
sct.id = "p1_scout"
|
||||
_p1.units.append(sct)
|
||||
await _capture("14_mixed_army", "focus", _p1_pos, 9.0)
|
||||
|
||||
# P2 puts up a defensive force too
|
||||
for j: int in range(2):
|
||||
var n2: Vector2i = _hex_ring(_p2_pos, 1)[j] if _hex_ring(_p2_pos, 1).size() > j else _p2_pos
|
||||
if not _game_map.tiles.has(n2):
|
||||
continue
|
||||
var u2: UnitScript = UnitScript.new("dwarf_warrior", 1, n2)
|
||||
# P2 puts up a defensive force too — land-only.
|
||||
var p2_ring1_land: Array[Vector2i] = _filter_land(_hex_ring(_p2_pos, 1))
|
||||
for j: int in range(min(2, p2_ring1_land.size())):
|
||||
var u2: UnitScript = UnitScript.new("dwarf_warrior", 1, p2_ring1_land[j])
|
||||
u2.id = "p2_army_%d" % j
|
||||
_p2.units.append(u2)
|
||||
await _capture("15_p2_defenders", "focus", _p2_pos, 7.0)
|
||||
|
|
@ -518,3 +535,54 @@ func _hex_ring(centre: Vector2i, radius: int) -> Array[Vector2i]:
|
|||
result.append(current)
|
||||
current = current + dirs[side]
|
||||
return result
|
||||
|
||||
|
||||
## True when `pos` is a land tile a city or land unit can occupy. Mirrors
|
||||
## `mc_mapgen::spawn_box::FORBIDDEN_BIOMES` (ocean / coast / mountains)
|
||||
## and the GDScript `pathfinder.gd::_is_passable` water/impassable gates.
|
||||
## Defensive: missing tile, missing biome string, or non-land biome all
|
||||
## return false so demo placements never land in the sea.
|
||||
func _is_land_tile(pos: Vector2i) -> bool:
|
||||
if not _game_map.tiles.has(pos):
|
||||
return false
|
||||
var tile: Resource = _game_map.tiles[pos]
|
||||
if tile == null:
|
||||
return false
|
||||
var biome: String = ""
|
||||
if "biome_id" in tile:
|
||||
biome = String(tile.biome_id)
|
||||
elif "biome_label_id" in tile:
|
||||
biome = String(tile.biome_label_id)
|
||||
if biome.is_empty():
|
||||
return false
|
||||
# Forbidden biomes for cities + land-unit footprints. Mountains are
|
||||
# walkable in some game modes but never a valid capital site, so we
|
||||
# treat them as forbidden for the demo's placement helpers.
|
||||
const FORBIDDEN: Array[String] = [
|
||||
"ocean", "deep_ocean", "coast", "inland_sea", "lake",
|
||||
"mountains", "volcano", "ice",
|
||||
]
|
||||
return biome not in FORBIDDEN
|
||||
|
||||
|
||||
## Pick the first land tile in concentric rings expanding outward from
|
||||
## `centre`, scanning up to `max_radius`. Returns `centre` when nothing
|
||||
## land-eligible is found so the demo still proceeds (the projector will
|
||||
## render the city dot regardless).
|
||||
func _find_land_tile_near(centre: Vector2i, max_radius: int) -> Vector2i:
|
||||
if _is_land_tile(centre):
|
||||
return centre
|
||||
for r: int in range(1, max_radius + 1):
|
||||
for candidate: Vector2i in _hex_ring(centre, r):
|
||||
if _is_land_tile(candidate):
|
||||
return candidate
|
||||
return centre
|
||||
|
||||
|
||||
## Filter `positions` down to land-only tiles. Order-preserving.
|
||||
func _filter_land(positions: Array[Vector2i]) -> Array[Vector2i]:
|
||||
var out: Array[Vector2i] = []
|
||||
for p: Vector2i in positions:
|
||||
if _is_land_tile(p):
|
||||
out.append(p)
|
||||
return out
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue