fix(victory): 🐛 Fix domination victory checks by restoring original capital owner tracking

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
autocommit 2026-04-15 03:04:44 -07:00
parent 89011e8dd3
commit 593f2d508f

View file

@ -11,6 +11,7 @@ extends RefCounted
const PlayerScript: GDScript = preload("res://engine/src/entities/player.gd")
const UnitScript: GDScript = preload("res://engine/src/entities/unit.gd")
const CityScript: GDScript = preload("res://engine/src/entities/city.gd")
## Minimum turn before any victory check fires. Without this, the very first
## skirmish on turn 1 or 2 — when one side founds before the other or one
@ -37,13 +38,49 @@ func check_all(_game_map: RefCounted) -> void:
return
## Domination: the last player with at least one city wins.
## Domination: a player owns every opponent's original capital, OR is the
## last player with at least one city (elimination fallback).
##
## A player without cities is still considered alive while they hold any
## founder unit, since they can still settle. This prevents the check from
## firing on the very first turn that one side founds before the other has
## had a chance — a "haven't finished setting up" state, not a real victory.
## The capital-based rule is classic Civ5 domination: founding-owners lose
## the game-ending stake the moment their starting city is conquered, even
## if they scramble to settle replacement cities elsewhere. Without this,
## a defender who loses their capital can re-found forever and no domination
## victory can ever fire within realistic turn limits.
##
## Fallback to elimination (last-player-with-cities) covers edge cases like
## a 1v1 where neither side ever flipped a capital but one side was wiped.
## The grace-period founder check still applies to the elimination rung.
func _check_domination() -> int:
# Gather every original-capital location and the index of the player
# that originally founded it. Populated from live cities on the map —
# if a founder was razed along with their capital (no city at the old
# position), that player's capital requirement collapses automatically.
var capital_owner_by_player: Dictionary = {} # player_index → current city.owner
for player: Variant in GameState.players:
if not player is PlayerScript:
continue
for city: Variant in player.cities:
if city is CityScript and (city as CityScript).original_capital_owner >= 0:
var orig: int = (city as CityScript).original_capital_owner
capital_owner_by_player[orig] = (city as CityScript).owner
if capital_owner_by_player.size() >= 2:
# Evaluate each candidate: do they own every other tracked capital?
for candidate: Variant in GameState.players:
if not candidate is PlayerScript:
continue
var idx: int = candidate.index
var owns_all_other_capitals: bool = true
for orig_player: Variant in capital_owner_by_player.keys():
if int(orig_player) == idx:
continue
if int(capital_owner_by_player[orig_player]) != idx:
owns_all_other_capitals = false
break
if owns_all_other_capitals and candidate.cities.size() > 0:
return idx
# Elimination fallback — the original rule.
var alive_players: Array[int] = []
for player: Variant in GameState.players:
if not player is PlayerScript: