Adds unique IDs to units (#11745)

* Adds unique IDs to units

* Add IDs to units in existing games

* civ.getUnitById function

* Fix 'no units' case
This commit is contained in:
Yair Morgenstern 2024-06-15 22:00:41 +03:00 committed by GitHub
parent 3b40820bf2
commit 255e6c7fc9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 36 additions and 16 deletions

View File

@ -4,6 +4,7 @@ object Constants {
const val settler = "Settler" const val settler = "Settler"
const val eraSpecificUnit = "Era Starting Unit" const val eraSpecificUnit = "Era Starting Unit"
val all = setOf("All", "all") val all = setOf("All", "all")
const val NO_ID = -1
const val english = "English" const val english = "English"

View File

@ -200,4 +200,12 @@ object BackwardCompatibility {
} }
historyStartTurn = turns historyStartTurn = turns
} }
fun GameInfo.ensureUnitIds(){
if (lastUnitId == 0) lastUnitId = tileMap.values.asSequence()
.flatMap { it.getUnits() }.maxOfOrNull { it.id } ?: 0
for (unit in tileMap.values.flatMap { it.getUnits() }) {
if (unit.id == 0) unit.id = ++lastUnitId
}
}
} }

View File

@ -6,6 +6,7 @@ import com.unciv.UncivGame
import com.unciv.UncivGame.Version import com.unciv.UncivGame.Version
import com.unciv.json.json import com.unciv.json.json
import com.unciv.logic.BackwardCompatibility.convertFortify import com.unciv.logic.BackwardCompatibility.convertFortify
import com.unciv.logic.BackwardCompatibility.ensureUnitIds
import com.unciv.logic.BackwardCompatibility.guaranteeUnitPromotions import com.unciv.logic.BackwardCompatibility.guaranteeUnitPromotions
import com.unciv.logic.BackwardCompatibility.migrateGreatGeneralPools import com.unciv.logic.BackwardCompatibility.migrateGreatGeneralPools
import com.unciv.logic.BackwardCompatibility.migrateToTileHistory import com.unciv.logic.BackwardCompatibility.migrateToTileHistory
@ -115,6 +116,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
var currentTurnStartTime = 0L var currentTurnStartTime = 0L
var gameId = UUID.randomUUID().toString() // random string var gameId = UUID.randomUUID().toString() // random string
var checksum = "" var checksum = ""
var lastUnitId = 0
var victoryData: VictoryData? = null var victoryData: VictoryData? = null
@ -679,10 +681,9 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
cityDistances.game = this cityDistances.game = this
guaranteeUnitPromotions() guaranteeUnitPromotions()
migrateToTileHistory() migrateToTileHistory()
migrateGreatGeneralPools() migrateGreatGeneralPools()
ensureUnitIds()
} }
private fun updateCivilizationState() { private fun updateCivilizationState() {

View File

@ -1,6 +1,7 @@
package com.unciv.logic.automation.unit package com.unciv.logic.automation.unit
import com.badlogic.gdx.math.Vector2 import com.badlogic.gdx.math.Vector2
import com.unciv.Constants
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.logic.city.City import com.unciv.logic.city.City
import com.unciv.logic.civilization.Civilization import com.unciv.logic.civilization.Civilization
@ -82,7 +83,9 @@ class RoadBetweenCitiesAutomation(val civInfo: Civilization, cachedForTurn: Int,
return roadsToBuildByCitiesCache[city]!! return roadsToBuildByCitiesCache[city]!!
} }
// TODO: some better worker representative needs to be used here // TODO: some better worker representative needs to be used here
val workerUnit = civInfo.gameInfo.ruleset.units.map { it.value }.firstOrNull { it.hasUnique(UniqueType.BuildImprovements) }?.getMapUnit(civInfo) ?: return listOf() val workerUnit = civInfo.gameInfo.ruleset.units.map { it.value }.firstOrNull { it.hasUnique(UniqueType.BuildImprovements) }
// This is a temporary unit only for AI purposes so it doesn't get a unique ID
?.getMapUnit(civInfo, Constants.NO_ID) ?: return listOf()
val roadToCapitalStatus = city.cityStats.getRoadTypeOfConnectionToCapital() val roadToCapitalStatus = city.cityStats.getRoadTypeOfConnectionToCapital()
fun rankRoadCapitalPriority(roadStatus: RoadStatus): Float { fun rankRoadCapitalPriority(roadStatus: RoadStatus): Float {

View File

@ -219,7 +219,7 @@ object BattleUnitCapture {
.firstOrNull { it.isCivilian() && it.getMatchingUniques(UniqueType.BuildImprovements) .firstOrNull { it.isCivilian() && it.getMatchingUniques(UniqueType.BuildImprovements)
.any { unique -> unique.params[0] == "Land" } } .any { unique -> unique.params[0] == "Land" } }
?: return null ?: return null
return capturingCiv.units.placeUnitNearTile(capturedUnit.currentTile.position, workerTypeUnit) return capturingCiv.units.placeUnitNearTile(capturedUnit.currentTile.position, workerTypeUnit, capturedUnit.id)
?.currentTile?.position ?.currentTile?.position
} }

View File

@ -90,8 +90,8 @@ class UnitManager(val civInfo: Civilization) {
* @param baseUnit [BaseUnit] to create and place * @param baseUnit [BaseUnit] to create and place
* @return created [MapUnit] or null if no suitable location was found * @return created [MapUnit] or null if no suitable location was found
* */ * */
fun placeUnitNearTile(location: Vector2, baseUnit: BaseUnit): MapUnit? { fun placeUnitNearTile(location: Vector2, baseUnit: BaseUnit, unitId: Int? = null): MapUnit? {
val unit = civInfo.gameInfo.tileMap.placeUnitNearTile(location, baseUnit, civInfo) val unit = civInfo.gameInfo.tileMap.placeUnitNearTile(location, baseUnit, civInfo, unitId)
if (unit != null) { if (unit != null) {
val triggerNotificationText = "due to gaining a [${unit.name}]" val triggerNotificationText = "due to gaining a [${unit.name}]"
@ -168,6 +168,8 @@ class UnitManager(val civInfo: Civilization) {
fun shouldGoToDueUnit() = UncivGame.Current.settings.checkForDueUnits && getDueUnits().any() fun shouldGoToDueUnit() = UncivGame.Current.settings.checkForDueUnits && getDueUnits().any()
fun getUnitById(id: Int) = getCivUnits().firstOrNull { it.id == id }
// Return the next due unit, but preferably not 'unitToSkip': this is returned only if it is the only remaining due unit. // Return the next due unit, but preferably not 'unitToSkip': this is returned only if it is the only remaining due unit.
fun cycleThroughDueUnits(unitToSkip: MapUnit? = null): MapUnit? { fun cycleThroughDueUnits(unitToSkip: MapUnit? = null): MapUnit? {
if (unitList.none()) return null if (unitList.none()) return null

View File

@ -530,10 +530,11 @@ class TileMap(initialCapacity: Int = 10) : IsPartOfGameInfoSerialization {
fun placeUnitNearTile( fun placeUnitNearTile(
position: Vector2, position: Vector2,
unitName: String, unitName: String,
civInfo: Civilization civInfo: Civilization,
unitId: Int? = null
): MapUnit? { ): MapUnit? {
val unit = gameInfo.ruleset.units[unitName]!! val unit = gameInfo.ruleset.units[unitName]!!
return placeUnitNearTile(position, unit, civInfo) return placeUnitNearTile(position, unit, civInfo, unitId)
} }
/** Tries to place the [baseUnit] into the [Tile] closest to the given [position] /** Tries to place the [baseUnit] into the [Tile] closest to the given [position]
@ -545,9 +546,10 @@ class TileMap(initialCapacity: Int = 10) : IsPartOfGameInfoSerialization {
fun placeUnitNearTile( fun placeUnitNearTile(
position: Vector2, position: Vector2,
baseUnit: BaseUnit, baseUnit: BaseUnit,
civInfo: Civilization civInfo: Civilization,
unitId: Int? = null
): MapUnit? { ): MapUnit? {
val unit = baseUnit.getMapUnit(civInfo) val unit = baseUnit.getMapUnit(civInfo, unitId)
fun getPassableNeighbours(tile: Tile): Set<Tile> = fun getPassableNeighbours(tile: Tile): Set<Tile> =
tile.neighbors.filter { unit.movement.canPassThrough(it) }.toSet() tile.neighbors.filter { unit.movement.canPassThrough(it) }.toSet()

View File

@ -56,6 +56,7 @@ class MapUnit : IsPartOfGameInfoSerialization {
var currentMovement: Float = 0f var currentMovement: Float = 0f
var health: Int = 100 var health: Int = 100
var id: Int = Constants.NO_ID
// work, automation, fortifying, ... // work, automation, fortifying, ...
// Connect roads implies automated is true. It is specified by the action type. // Connect roads implies automated is true. It is specified by the action type.

View File

@ -87,7 +87,7 @@ class UnitUpgradeManager(val unit: MapUnit) {
unit.destroy(destroyTransportedUnit = false) unit.destroy(destroyTransportedUnit = false)
val civ = unit.civ val civ = unit.civ
val position = unit.currentTile.position val position = unit.currentTile.position
val newUnit = civ.units.placeUnitNearTile(position, upgradedUnit) val newUnit = civ.units.placeUnitNearTile(position, upgradedUnit, unit.id)
/** We were UNABLE to place the new unit, which means that the unit failed to upgrade! /** We were UNABLE to place the new unit, which means that the unit failed to upgrade!
* The only known cause of this currently is "land units upgrading to water units" which fail to be placed. * The only known cause of this currently is "land units upgrading to water units" which fail to be placed.

View File

@ -201,7 +201,7 @@ object UniqueTriggerActivation {
civInfo.units.addUnit(civUnit, chosenCity) civInfo.units.addUnit(civUnit, chosenCity)
// Else set the unit at the given tile // Else set the unit at the given tile
tile != null -> civInfo.units.placeUnitNearTile(tile.position, civUnit) tile != null -> civInfo.units.placeUnitNearTile(tile.position, civUnit)
// Else set unit unit near other units if we have no cities // Else set new unit near other units if we have no cities
civInfo.units.getCivUnits().any() -> civInfo.units.getCivUnits().any() ->
civInfo.units.placeUnitNearTile(civInfo.units.getCivUnits().first().currentTile.position, civUnit) civInfo.units.placeUnitNearTile(civInfo.units.getCivUnits().first().currentTile.position, civUnit)

View File

@ -101,11 +101,12 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
} }
} }
fun getMapUnit(civInfo: Civilization): MapUnit { fun getMapUnit(civInfo: Civilization, unitId: Int? = null): MapUnit {
val unit = MapUnit() val unit = MapUnit()
unit.name = name unit.name = name
unit.civ = civInfo unit.civ = civInfo
unit.owner = civInfo.civName unit.owner = civInfo.civName
unit.id = unitId ?: ++civInfo.gameInfo.lastUnitId
// must be after setting name & civInfo because it sets the baseUnit according to the name // must be after setting name & civInfo because it sets the baseUnit according to the name
// and the civInfo is required for using `hasUnique` when determining its movement options // and the civInfo is required for using `hasUnique` when determining its movement options
@ -174,10 +175,11 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
if (isWaterUnit() && !cityConstructions.city.isCoastal()) if (isWaterUnit() && !cityConstructions.city.isCoastal())
yield(RejectionReasonType.WaterUnitsInCoastalCities.toInstance()) yield(RejectionReasonType.WaterUnitsInCoastalCities.toInstance())
if (isAirUnit()) { if (isAirUnit()) {
val fakeUnit = getMapUnit(cityConstructions.city.civ) val fakeUnit = getMapUnit(cityConstructions.city.civ, Constants.NO_ID)
val canUnitEnterTile = fakeUnit.movement.canMoveTo(cityConstructions.city.getCenterTile()) val canUnitEnterTile = fakeUnit.movement.canMoveTo(cityConstructions.city.getCenterTile())
if (!canUnitEnterTile) if (!canUnitEnterTile)
yield(RejectionReasonType.NoPlaceToPutUnit.toInstance()) yield(RejectionReasonType.NoPlaceToPutUnit.toInstance())
fakeUnit.destroy()
} }
val civInfo = cityConstructions.city.civ val civInfo = cityConstructions.city.civ

View File

@ -370,14 +370,14 @@ object UnitActionsFromUniques {
val oldMovement = unit.currentMovement val oldMovement = unit.currentMovement
unit.destroy() unit.destroy()
val newUnit = val newUnit =
civInfo.units.placeUnitNearTile(unitTile.position, unitToTransformTo) civInfo.units.placeUnitNearTile(unitTile.position, unitToTransformTo, unit.id)
/** We were UNABLE to place the new unit, which means that the unit failed to upgrade! /** We were UNABLE to place the new unit, which means that the unit failed to upgrade!
* The only known cause of this currently is "land units upgrading to water units" which fail to be placed. * The only known cause of this currently is "land units upgrading to water units" which fail to be placed.
*/ */
if (newUnit == null) { if (newUnit == null) {
val resurrectedUnit = val resurrectedUnit =
civInfo.units.placeUnitNearTile(unitTile.position, unit.baseUnit)!! civInfo.units.placeUnitNearTile(unitTile.position, unit.baseUnit, unit.id)!!
unit.copyStatisticsTo(resurrectedUnit) unit.copyStatisticsTo(resurrectedUnit)
} else { // Managed to upgrade } else { // Managed to upgrade
unit.copyStatisticsTo(newUnit) unit.copyStatisticsTo(newUnit)