diff --git a/core/src/com/unciv/Constants.kt b/core/src/com/unciv/Constants.kt index 6caa47a2d1..4f2761d5d3 100644 --- a/core/src/com/unciv/Constants.kt +++ b/core/src/com/unciv/Constants.kt @@ -4,6 +4,7 @@ object Constants { const val settler = "Settler" const val eraSpecificUnit = "Era Starting Unit" val all = setOf("All", "all") + const val NO_ID = -1 const val english = "English" diff --git a/core/src/com/unciv/logic/BackwardCompatibility.kt b/core/src/com/unciv/logic/BackwardCompatibility.kt index a359d170df..2310f6454e 100644 --- a/core/src/com/unciv/logic/BackwardCompatibility.kt +++ b/core/src/com/unciv/logic/BackwardCompatibility.kt @@ -200,4 +200,12 @@ object BackwardCompatibility { } 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 + } + } } diff --git a/core/src/com/unciv/logic/GameInfo.kt b/core/src/com/unciv/logic/GameInfo.kt index 6ee8a7972a..93c766a6e7 100644 --- a/core/src/com/unciv/logic/GameInfo.kt +++ b/core/src/com/unciv/logic/GameInfo.kt @@ -6,6 +6,7 @@ import com.unciv.UncivGame import com.unciv.UncivGame.Version import com.unciv.json.json import com.unciv.logic.BackwardCompatibility.convertFortify +import com.unciv.logic.BackwardCompatibility.ensureUnitIds import com.unciv.logic.BackwardCompatibility.guaranteeUnitPromotions import com.unciv.logic.BackwardCompatibility.migrateGreatGeneralPools import com.unciv.logic.BackwardCompatibility.migrateToTileHistory @@ -115,6 +116,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion var currentTurnStartTime = 0L var gameId = UUID.randomUUID().toString() // random string var checksum = "" + var lastUnitId = 0 var victoryData: VictoryData? = null @@ -679,10 +681,9 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion cityDistances.game = this guaranteeUnitPromotions() - migrateToTileHistory() - migrateGreatGeneralPools() + ensureUnitIds() } private fun updateCivilizationState() { diff --git a/core/src/com/unciv/logic/automation/unit/RoadBetweenCitiesAutomation.kt b/core/src/com/unciv/logic/automation/unit/RoadBetweenCitiesAutomation.kt index dd02344106..54f0652ce9 100644 --- a/core/src/com/unciv/logic/automation/unit/RoadBetweenCitiesAutomation.kt +++ b/core/src/com/unciv/logic/automation/unit/RoadBetweenCitiesAutomation.kt @@ -1,6 +1,7 @@ package com.unciv.logic.automation.unit import com.badlogic.gdx.math.Vector2 +import com.unciv.Constants import com.unciv.UncivGame import com.unciv.logic.city.City import com.unciv.logic.civilization.Civilization @@ -82,7 +83,9 @@ class RoadBetweenCitiesAutomation(val civInfo: Civilization, cachedForTurn: Int, return roadsToBuildByCitiesCache[city]!! } // 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() fun rankRoadCapitalPriority(roadStatus: RoadStatus): Float { diff --git a/core/src/com/unciv/logic/battle/BattleUnitCapture.kt b/core/src/com/unciv/logic/battle/BattleUnitCapture.kt index 0345bb5587..776e4d2bcf 100644 --- a/core/src/com/unciv/logic/battle/BattleUnitCapture.kt +++ b/core/src/com/unciv/logic/battle/BattleUnitCapture.kt @@ -219,7 +219,7 @@ object BattleUnitCapture { .firstOrNull { it.isCivilian() && it.getMatchingUniques(UniqueType.BuildImprovements) .any { unique -> unique.params[0] == "Land" } } ?: return null - return capturingCiv.units.placeUnitNearTile(capturedUnit.currentTile.position, workerTypeUnit) + return capturingCiv.units.placeUnitNearTile(capturedUnit.currentTile.position, workerTypeUnit, capturedUnit.id) ?.currentTile?.position } diff --git a/core/src/com/unciv/logic/civilization/managers/UnitManager.kt b/core/src/com/unciv/logic/civilization/managers/UnitManager.kt index 22bacb13d5..d42485ba5b 100644 --- a/core/src/com/unciv/logic/civilization/managers/UnitManager.kt +++ b/core/src/com/unciv/logic/civilization/managers/UnitManager.kt @@ -90,8 +90,8 @@ class UnitManager(val civInfo: Civilization) { * @param baseUnit [BaseUnit] to create and place * @return created [MapUnit] or null if no suitable location was found * */ - fun placeUnitNearTile(location: Vector2, baseUnit: BaseUnit): MapUnit? { - val unit = civInfo.gameInfo.tileMap.placeUnitNearTile(location, baseUnit, civInfo) + fun placeUnitNearTile(location: Vector2, baseUnit: BaseUnit, unitId: Int? = null): MapUnit? { + val unit = civInfo.gameInfo.tileMap.placeUnitNearTile(location, baseUnit, civInfo, unitId) if (unit != null) { 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 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. fun cycleThroughDueUnits(unitToSkip: MapUnit? = null): MapUnit? { if (unitList.none()) return null diff --git a/core/src/com/unciv/logic/map/TileMap.kt b/core/src/com/unciv/logic/map/TileMap.kt index 42fd994e5e..53d0adee22 100644 --- a/core/src/com/unciv/logic/map/TileMap.kt +++ b/core/src/com/unciv/logic/map/TileMap.kt @@ -530,10 +530,11 @@ class TileMap(initialCapacity: Int = 10) : IsPartOfGameInfoSerialization { fun placeUnitNearTile( position: Vector2, unitName: String, - civInfo: Civilization + civInfo: Civilization, + unitId: Int? = null ): MapUnit? { 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] @@ -545,9 +546,10 @@ class TileMap(initialCapacity: Int = 10) : IsPartOfGameInfoSerialization { fun placeUnitNearTile( position: Vector2, baseUnit: BaseUnit, - civInfo: Civilization + civInfo: Civilization, + unitId: Int? = null ): MapUnit? { - val unit = baseUnit.getMapUnit(civInfo) + val unit = baseUnit.getMapUnit(civInfo, unitId) fun getPassableNeighbours(tile: Tile): Set = tile.neighbors.filter { unit.movement.canPassThrough(it) }.toSet() diff --git a/core/src/com/unciv/logic/map/mapunit/MapUnit.kt b/core/src/com/unciv/logic/map/mapunit/MapUnit.kt index 7bd7e4e614..0d62caf178 100644 --- a/core/src/com/unciv/logic/map/mapunit/MapUnit.kt +++ b/core/src/com/unciv/logic/map/mapunit/MapUnit.kt @@ -56,6 +56,7 @@ class MapUnit : IsPartOfGameInfoSerialization { var currentMovement: Float = 0f var health: Int = 100 + var id: Int = Constants.NO_ID // work, automation, fortifying, ... // Connect roads implies automated is true. It is specified by the action type. diff --git a/core/src/com/unciv/logic/map/mapunit/UnitUpgradeManager.kt b/core/src/com/unciv/logic/map/mapunit/UnitUpgradeManager.kt index 6405605cf9..e70fd346d4 100644 --- a/core/src/com/unciv/logic/map/mapunit/UnitUpgradeManager.kt +++ b/core/src/com/unciv/logic/map/mapunit/UnitUpgradeManager.kt @@ -87,7 +87,7 @@ class UnitUpgradeManager(val unit: MapUnit) { unit.destroy(destroyTransportedUnit = false) val civ = unit.civ 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! * The only known cause of this currently is "land units upgrading to water units" which fail to be placed. diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt b/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt index 85794a0fb4..b46f02938d 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt @@ -201,7 +201,7 @@ object UniqueTriggerActivation { civInfo.units.addUnit(civUnit, chosenCity) // Else set the unit at the given tile 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.placeUnitNearTile(civInfo.units.getCivUnits().first().currentTile.position, civUnit) diff --git a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt index dcec209f18..8576282001 100644 --- a/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt +++ b/core/src/com/unciv/models/ruleset/unit/BaseUnit.kt @@ -101,11 +101,12 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction { } } - fun getMapUnit(civInfo: Civilization): MapUnit { + fun getMapUnit(civInfo: Civilization, unitId: Int? = null): MapUnit { val unit = MapUnit() unit.name = name unit.civ = civInfo 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 // 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()) yield(RejectionReasonType.WaterUnitsInCoastalCities.toInstance()) 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()) if (!canUnitEnterTile) yield(RejectionReasonType.NoPlaceToPutUnit.toInstance()) + fakeUnit.destroy() } val civInfo = cityConstructions.city.civ diff --git a/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsFromUniques.kt b/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsFromUniques.kt index a86acb87e8..8c9e1958c7 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsFromUniques.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/unit/actions/UnitActionsFromUniques.kt @@ -370,14 +370,14 @@ object UnitActionsFromUniques { val oldMovement = unit.currentMovement unit.destroy() 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! * The only known cause of this currently is "land units upgrading to water units" which fail to be placed. */ if (newUnit == null) { val resurrectedUnit = - civInfo.units.placeUnitNearTile(unitTile.position, unit.baseUnit)!! + civInfo.units.placeUnitNearTile(unitTile.position, unit.baseUnit, unit.id)!! unit.copyStatisticsTo(resurrectedUnit) } else { // Managed to upgrade unit.copyStatisticsTo(newUnit)