fix(@projects/@magic-civilization): 🐛 update auto-play logic for new map type and unit behavior
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
c0246cf37e
commit
30921934d8
1 changed files with 8 additions and 171 deletions
|
|
@ -397,11 +397,7 @@ func _process(_delta: float) -> void:
|
|||
# Force Pangaea so all players share one landmass (no water barriers)
|
||||
GameState.game_settings["map_type"] = "pangaea"
|
||||
GameState.game_settings["num_players"] = 2
|
||||
var diff_env: String = EnvConfig.get_var("AI_DIFFICULTY", "")
|
||||
if not diff_env.is_empty():
|
||||
GameState.game_settings["difficulty"] = diff_env
|
||||
print("AutoPlay: AI_DIFFICULTY=%s applied" % diff_env)
|
||||
GameState.apply_ai_difficulty()
|
||||
GameState.game_settings["map_type"] = "pangaea"
|
||||
_state = "wait_loading"
|
||||
_frame = 0
|
||||
if _frame > 120:
|
||||
|
|
@ -671,37 +667,7 @@ func _play_turn() -> void:
|
|||
if player.researching.is_empty():
|
||||
_pick_research(player)
|
||||
|
||||
# Refresh attack-phase signals and stack-sustain telemetry for this turn.
|
||||
# _attack_commitment_turns reflects prior-turn commitment; rush-buy and
|
||||
# building scoring both key off it so they respond mid-siege.
|
||||
_in_attack_phase = _attack_commitment_turns > 0 and _locked_target != Vector2i(-1, -1)
|
||||
_active_attack_mil_count = 0
|
||||
if _in_attack_phase:
|
||||
for u_stk: RefCounted in player.units:
|
||||
if not u_stk.is_alive():
|
||||
continue
|
||||
if u_stk.get("can_found_city") == true:
|
||||
continue
|
||||
if u_stk.get("can_build_improvements") == true:
|
||||
continue
|
||||
if HexUtilsScript.hex_distance(u_stk.position, _locked_target) <= 8:
|
||||
_active_attack_mil_count += 1
|
||||
if _turn_count % 10 == 0:
|
||||
print(
|
||||
(
|
||||
" [STACK] turn=%d at_target=%d locked=%s commit=%d"
|
||||
% [
|
||||
_turn_count,
|
||||
_active_attack_mil_count,
|
||||
str(_locked_target),
|
||||
_attack_commitment_turns,
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
# 0b. Gold rush-buy warriors — spawn at city nearest to attack target.
|
||||
# During active siege, lower the threshold so we can replace losses fast.
|
||||
# Stack critical (<=1 near target) drops the threshold further.
|
||||
# 0b. Gold rush-buy warriors — spawn at city nearest to attack target
|
||||
var mil_pre: int = 0
|
||||
for u_pre: RefCounted in player.units:
|
||||
if u_pre.is_alive() and u_pre.get("can_found_city") != true:
|
||||
|
|
@ -858,7 +824,7 @@ func _play_turn() -> void:
|
|||
u.fortified_turns = 0
|
||||
_move_toward(u, target, game_map)
|
||||
else:
|
||||
# BUILD PHASE: garrison at city, don't engage. Only scouts explore.
|
||||
# BUILD PHASE (<3 military): rally at city, attack adjacent enemies
|
||||
for u: Variant in units_snapshot:
|
||||
if not u.is_alive() or u.movement_remaining <= 0:
|
||||
continue
|
||||
|
|
@ -869,7 +835,11 @@ func _play_turn() -> void:
|
|||
if u.type_id == "dwarf_scout" and _turn_count <= 20:
|
||||
_explore(u, player, game_map)
|
||||
else:
|
||||
# Fortify at city — do NOT attack or chase enemies
|
||||
# Always attack adjacent enemies first
|
||||
_try_attack_adjacent(u, game_map)
|
||||
if not u.is_alive() or u.movement_remaining <= 0:
|
||||
continue
|
||||
# Rally at city — no fortifying, stay mobile
|
||||
if u.position != city_pos:
|
||||
_move_toward(u, city_pos, game_map)
|
||||
elif not u.is_fortified:
|
||||
|
|
@ -1207,139 +1177,6 @@ func _command_unit(unit: Variant, player: RefCounted, game_map: RefCounted) -> v
|
|||
_explore(unit, player, game_map)
|
||||
|
||||
|
||||
func _decide_founder(unit: Variant, player: RefCounted, game_map: RefCounted) -> void:
|
||||
## State-based founding: score reachable sites, move to the best; only found at a local max.
|
||||
const MIN_FOUND_SCORE: float = 4.0
|
||||
const MAX_WAIT_TURNS: int = 40
|
||||
const LOOK_AHEAD_MULT: int = 3
|
||||
var water: Array = ["ocean", "coast", "deep_ocean", "lake"]
|
||||
var reach: int = max(1, int(unit.max_movement) * LOOK_AHEAD_MULT)
|
||||
var reachable: Dictionary = PathfinderScript.movement_range(game_map, unit.position, reach, "land")
|
||||
var best_pos: Vector2i = unit.position
|
||||
var best_score: float = _score_site(unit.position, game_map)
|
||||
var current_score: float = best_score
|
||||
for pos: Vector2i in reachable:
|
||||
var tile: Resource = game_map.get_tile(pos)
|
||||
if tile == null or tile.biome_id in water:
|
||||
continue
|
||||
var too_close: bool = false
|
||||
for c: Variant in player.cities:
|
||||
if HexUtilsScript.hex_distance(pos, c.position) < 5:
|
||||
too_close = true
|
||||
break
|
||||
if too_close:
|
||||
continue
|
||||
var s: float = _score_site(pos, game_map)
|
||||
if s > best_score:
|
||||
best_score = s
|
||||
best_pos = pos
|
||||
print("[SITE] turn=%d current=%.1f best=%.1f chosen=%s" % [
|
||||
_turn_count, current_score, best_score, str(best_pos)
|
||||
])
|
||||
var current_valid: bool = true
|
||||
var cur_tile: Resource = game_map.get_tile(unit.position)
|
||||
if cur_tile == null or cur_tile.biome_id in water:
|
||||
current_valid = false
|
||||
for c: Variant in player.cities:
|
||||
if HexUtilsScript.hex_distance(unit.position, c.position) < 5:
|
||||
current_valid = false
|
||||
break
|
||||
if best_pos == unit.position and current_valid:
|
||||
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 (score %.1f)" % [unit.position, current_score])
|
||||
return
|
||||
var waiting: bool = current_score < MIN_FOUND_SCORE and _turn_count < MAX_WAIT_TURNS
|
||||
var should_wait: bool = (not current_valid) or waiting
|
||||
if best_pos != unit.position and should_wait:
|
||||
_move_toward(unit, best_pos, game_map)
|
||||
return
|
||||
if current_valid:
|
||||
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 (good-enough, score %.1f)" % [unit.position, current_score])
|
||||
else:
|
||||
_explore(unit, player, game_map)
|
||||
|
||||
|
||||
func _command_worker(unit: Variant, player: RefCounted, game_map: RefCounted) -> void:
|
||||
## Build an improvement on the current tile if possible;
|
||||
## else walk toward the nearest owned unimproved tile; else toward a city.
|
||||
if _improvement_manager == null:
|
||||
return
|
||||
var buildable: Array[Dictionary] = _improvement_manager.get_buildable_improvements(
|
||||
unit, game_map, player
|
||||
)
|
||||
if not buildable.is_empty():
|
||||
var tile: Resource = game_map.get_tile(unit.position)
|
||||
var preferred: String = "farm"
|
||||
if tile != null and tile.biome_id in ["hills", "mountains"]:
|
||||
preferred = "mine"
|
||||
elif tile != null and tile.biome_id in ["forest", "boreal_forest", "jungle", "tundra"]:
|
||||
preferred = "hunting_grounds"
|
||||
var pick: String = str(buildable[0].get("id", ""))
|
||||
for entry: Dictionary in buildable:
|
||||
if entry.get("id", "") == preferred:
|
||||
pick = preferred
|
||||
break
|
||||
if not pick.is_empty():
|
||||
_improvement_manager.start_improvement(unit, pick, player)
|
||||
return
|
||||
# Seek the nearest buildable tile within 4 hexes: owned or unclaimed, non-water,
|
||||
# unimproved, no pending build. Unclaimed tiles work — they convert on build.
|
||||
var best_target: Vector2i = Vector2i(-1, -1)
|
||||
var best_dist: int = 9999
|
||||
for dq: int in range(-4, 5):
|
||||
for dr: int in range(-4, 5):
|
||||
var tpos: Vector2i = unit.position + Vector2i(dq, dr)
|
||||
var d: int = HexUtilsScript.hex_distance(unit.position, tpos)
|
||||
if d == 0 or d > 4 or d >= best_dist:
|
||||
continue
|
||||
var t: Resource = game_map.get_tile(tpos)
|
||||
if t == null or t.is_water() or str(t.improvement) != "":
|
||||
continue
|
||||
if t.owner != -1 and t.owner != player.index:
|
||||
continue
|
||||
if _improvement_manager.get_pending_at(tpos, player).size() > 0:
|
||||
continue
|
||||
best_dist = d
|
||||
best_target = tpos
|
||||
if best_target != Vector2i(-1, -1):
|
||||
_worker_step_toward(unit, best_target, game_map)
|
||||
return
|
||||
if not player.cities.is_empty():
|
||||
_worker_step_toward(unit, player.cities[0].position, game_map)
|
||||
else:
|
||||
_explore(unit, player, game_map)
|
||||
|
||||
|
||||
func _worker_step_toward(unit: Variant, target: Vector2i, game_map: RefCounted) -> void:
|
||||
# Workers must step ONTO the target tile to build on it — do not use the
|
||||
# attack-adjacent shortcut in _move_toward which skips movement entirely.
|
||||
if unit.position == target:
|
||||
return
|
||||
var reachable: Dictionary = PathfinderScript.movement_range(
|
||||
game_map, unit.position, unit.movement_remaining, "land"
|
||||
)
|
||||
if reachable.is_empty():
|
||||
return
|
||||
var best_pos: Vector2i = unit.position
|
||||
var best_dist: int = HexUtilsScript.hex_distance(unit.position, target)
|
||||
for pos: Vector2i in reachable:
|
||||
if pos == unit.position:
|
||||
continue
|
||||
var dist: int = HexUtilsScript.hex_distance(pos, target)
|
||||
if dist < best_dist:
|
||||
best_dist = dist
|
||||
best_pos = pos
|
||||
if best_pos != unit.position:
|
||||
_do_move(unit, best_pos, game_map)
|
||||
|
||||
|
||||
func _nearest_city_to_target(player: RefCounted) -> RefCounted:
|
||||
## Return the player's city closest to the locked attack target.
|
||||
## Falls back to city[0] if no target is locked.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue