mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-30 14:48:56 +07:00
chore: move unit upgrade functions to separate class
This commit is contained in:
@ -116,7 +116,7 @@ object UnitAutomation {
|
||||
|
||||
internal fun tryUpgradeUnit(unit: MapUnit): Boolean {
|
||||
if (unit.baseUnit.upgradesTo == null) return false
|
||||
val upgradedUnit = unit.getUnitToUpgradeTo()
|
||||
val upgradedUnit = unit.upgrade.getUnitToUpgradeTo()
|
||||
if (!upgradedUnit.isBuildable(unit.civInfo)) return false // for resource reasons, usually
|
||||
|
||||
if (upgradedUnit.getResourceRequirements().keys.any { !unit.baseUnit.requiresResource(it) }) {
|
||||
|
@ -9,8 +9,6 @@ import com.unciv.logic.automation.unit.WorkerAutomation
|
||||
import com.unciv.logic.battle.Battle
|
||||
import com.unciv.logic.battle.MapUnitCombatant
|
||||
import com.unciv.logic.city.CityInfo
|
||||
import com.unciv.logic.city.RejectionReason
|
||||
import com.unciv.logic.city.RejectionReasons
|
||||
import com.unciv.logic.civilization.CivilizationInfo
|
||||
import com.unciv.logic.civilization.LocationAction
|
||||
import com.unciv.logic.civilization.NotificationCategory
|
||||
@ -52,6 +50,9 @@ class MapUnit : IsPartOfGameInfoSerialization {
|
||||
@Transient
|
||||
val movement = UnitMovementAlgorithms(this)
|
||||
|
||||
@Transient
|
||||
val upgrade = UnitUpgradeManager(this)
|
||||
|
||||
@Transient
|
||||
var isDestroyed = false
|
||||
|
||||
@ -496,119 +497,6 @@ class MapUnit : IsPartOfGameInfoSerialization {
|
||||
}
|
||||
|
||||
|
||||
/** Returns FULL upgrade path, without checking what we can or cannot build currently.
|
||||
* Does not contain current baseunit, so will be empty if no upgrades. */
|
||||
fun getUpgradePath(): List<BaseUnit>{
|
||||
var currentUnit = baseUnit
|
||||
val upgradeList = arrayListOf<BaseUnit>()
|
||||
while (currentUnit.upgradesTo != null){
|
||||
val nextUpgrade = civInfo.getEquivalentUnit(currentUnit.upgradesTo!!)
|
||||
currentUnit = nextUpgrade
|
||||
upgradeList.add(currentUnit)
|
||||
}
|
||||
return upgradeList
|
||||
}
|
||||
|
||||
/** Get the base unit this map unit could upgrade to, respecting researched tech and nation uniques only.
|
||||
* Note that if the unit can't upgrade, the current BaseUnit is returned.
|
||||
*/
|
||||
// Used from UnitAutomation, UI action, canUpgrade
|
||||
fun getUnitToUpgradeTo(): BaseUnit {
|
||||
val upgradePath = getUpgradePath()
|
||||
|
||||
fun isInvalidUpgradeDestination(baseUnit: BaseUnit): Boolean{
|
||||
if (baseUnit.requiredTech != null && !civInfo.tech.isResearched(baseUnit.requiredTech!!))
|
||||
return true
|
||||
if (baseUnit.getMatchingUniques(UniqueType.OnlyAvailableWhen).any {
|
||||
!it.conditionalsApply(StateForConditionals(civInfo, unit = this ))
|
||||
}) return true
|
||||
return false
|
||||
}
|
||||
|
||||
for (baseUnit in upgradePath.reversed()){
|
||||
if (isInvalidUpgradeDestination(baseUnit)) continue
|
||||
return baseUnit
|
||||
}
|
||||
return baseUnit
|
||||
}
|
||||
|
||||
/** Check whether this unit can upgrade to [unitToUpgradeTo]. This does not check or follow the
|
||||
* normal upgrade chain defined by [BaseUnit.upgradesTo], unless [unitToUpgradeTo] is left at default.
|
||||
* @param ignoreRequirements Ignore possible tech/policy/building requirements (e.g. resource requirements still count).
|
||||
* Used for upgrading units via ancient ruins.
|
||||
* @param ignoreResources Ignore resource requirements (tech still counts)
|
||||
* Used to display disabled Upgrade button
|
||||
*/
|
||||
fun canUpgrade(
|
||||
unitToUpgradeTo: BaseUnit = getUnitToUpgradeTo(),
|
||||
ignoreRequirements: Boolean = false,
|
||||
ignoreResources: Boolean = false
|
||||
): Boolean {
|
||||
if (name == unitToUpgradeTo.name) return false
|
||||
|
||||
// We need to remove the unit from the civ for this check,
|
||||
// because if the unit requires, say, horses, and so does its upgrade,
|
||||
// and the civ currently has 0 horses, we need to see if the upgrade will be buildable
|
||||
// WHEN THE CURRENT UNIT IS NOT HERE
|
||||
// TODO redesign without kludge: Inform getRejectionReasons about 'virtually available' resources somehow
|
||||
civInfo.units.removeUnit(this)
|
||||
val rejectionReasons = unitToUpgradeTo.getRejectionReasons(civInfo)
|
||||
civInfo.units.addUnit(this)
|
||||
|
||||
var relevantRejectionReasons = rejectionReasons.asSequence().filterNot { it.rejectionReason == RejectionReason.Unbuildable }
|
||||
if (ignoreRequirements)
|
||||
relevantRejectionReasons = relevantRejectionReasons.filterNot { it.rejectionReason in RejectionReasons.techPolicyEraWonderRequirements }
|
||||
if (ignoreResources)
|
||||
relevantRejectionReasons = relevantRejectionReasons.filterNot { it.rejectionReason == RejectionReason.ConsumesResources }
|
||||
return relevantRejectionReasons.none()
|
||||
}
|
||||
|
||||
/** Determine gold cost of a Unit Upgrade, potentially over several steps.
|
||||
* @param unitToUpgradeTo the final BaseUnit. Must be reachable via normal upgrades or else
|
||||
* the function will return the cost to upgrade to the last possible and researched normal upgrade.
|
||||
* @return Gold cost in increments of 5, never negative. Will return 0 for invalid inputs (unit can't upgrade or is is already a [unitToUpgradeTo])
|
||||
* @see <a href="https://github.com/dmnd/CvGameCoreSource/blob/6501d2398113a5100ffa854c146fb6f113992898/CvGameCoreDLL_Expansion1/CvUnit.cpp#L7728">CvUnit::upgradePrice</a>
|
||||
*/
|
||||
// Only one use from getUpgradeAction at the moment, so AI-specific rules omitted
|
||||
//todo Does the AI never buy upgrades???
|
||||
fun getCostOfUpgrade(unitToUpgradeTo: BaseUnit): Int {
|
||||
// Source rounds to int every step, we don't
|
||||
//TODO From the source, this should apply _Production_ modifiers (Temple of Artemis? GameSpeed! StartEra!), at the moment it doesn't
|
||||
|
||||
var goldCostOfUpgrade = 0
|
||||
|
||||
val ruleset = civInfo.gameInfo.ruleSet
|
||||
val constants = ruleset.modOptions.constants.unitUpgradeCost
|
||||
// apply modifiers: Wonders (Pentagon), Policies (Professional Army). Cached outside loop despite
|
||||
// the UniqueType being allowed on a BaseUnit - we don't have a MapUnit in the loop.
|
||||
// Actually instantiating every intermediate to support such mods: todo
|
||||
var civModifier = 1f
|
||||
val stateForConditionals = StateForConditionals(civInfo, unit = this)
|
||||
for (unique in civInfo.getMatchingUniques(UniqueType.UnitUpgradeCost, stateForConditionals))
|
||||
civModifier *= unique.params[0].toPercent()
|
||||
|
||||
val upgradePath = getUpgradePath()
|
||||
var currentUnit = baseUnit
|
||||
for (baseUnit in upgradePath) {
|
||||
// do clamping and rounding here so upgrading stepwise costs the same as upgrading far down the chain
|
||||
var stepCost = constants.base
|
||||
stepCost += (constants.perProduction * (baseUnit.cost - currentUnit.cost)).coerceAtLeast(0f)
|
||||
val era = ruleset.eras[ruleset.technologies[baseUnit.requiredTech]?.era()]
|
||||
if (era != null)
|
||||
stepCost *= (1f + era.eraNumber * constants.eraMultiplier)
|
||||
stepCost = (stepCost * civModifier).pow(constants.exponent)
|
||||
stepCost *= civInfo.gameInfo.speed.modifier
|
||||
goldCostOfUpgrade += (stepCost / constants.roundTo).toInt() * constants.roundTo
|
||||
if (baseUnit == unitToUpgradeTo)
|
||||
break // stop at requested BaseUnit to upgrade to
|
||||
currentUnit = baseUnit
|
||||
}
|
||||
|
||||
|
||||
return goldCostOfUpgrade
|
||||
}
|
||||
|
||||
|
||||
fun canFortify(): Boolean {
|
||||
if (baseUnit.isWaterUnit()) return false
|
||||
if (isCivilian()) return false
|
||||
|
126
core/src/com/unciv/logic/map/mapunit/UnitUpgradeManager.kt
Normal file
126
core/src/com/unciv/logic/map/mapunit/UnitUpgradeManager.kt
Normal file
@ -0,0 +1,126 @@
|
||||
package com.unciv.logic.map.mapunit
|
||||
|
||||
import com.unciv.logic.city.RejectionReason
|
||||
import com.unciv.logic.city.RejectionReasons
|
||||
import com.unciv.models.ruleset.unique.StateForConditionals
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.models.ruleset.unit.BaseUnit
|
||||
import com.unciv.ui.utils.extensions.toPercent
|
||||
import kotlin.math.pow
|
||||
|
||||
class UnitUpgradeManager(val unit:MapUnit) {
|
||||
|
||||
/** Returns FULL upgrade path, without checking what we can or cannot build currently.
|
||||
* Does not contain current baseunit, so will be empty if no upgrades. */
|
||||
private fun getUpgradePath(): List<BaseUnit>{
|
||||
var currentUnit = unit.baseUnit
|
||||
val upgradeList = arrayListOf<BaseUnit>()
|
||||
while (currentUnit.upgradesTo != null){
|
||||
val nextUpgrade = unit.civInfo.getEquivalentUnit(currentUnit.upgradesTo!!)
|
||||
currentUnit = nextUpgrade
|
||||
upgradeList.add(currentUnit)
|
||||
}
|
||||
return upgradeList
|
||||
}
|
||||
|
||||
/** Get the base unit this map unit could upgrade to, respecting researched tech and nation uniques only.
|
||||
* Note that if the unit can't upgrade, the current BaseUnit is returned.
|
||||
*/
|
||||
// Used from UnitAutomation, UI action, canUpgrade
|
||||
fun getUnitToUpgradeTo(): BaseUnit {
|
||||
val upgradePath = getUpgradePath()
|
||||
|
||||
fun isInvalidUpgradeDestination(baseUnit: BaseUnit): Boolean{
|
||||
if (baseUnit.requiredTech != null && !unit.civInfo.tech.isResearched(baseUnit.requiredTech!!))
|
||||
return true
|
||||
if (baseUnit.getMatchingUniques(UniqueType.OnlyAvailableWhen).any {
|
||||
!it.conditionalsApply(StateForConditionals(unit.civInfo, unit = unit ))
|
||||
}) return true
|
||||
return false
|
||||
}
|
||||
|
||||
for (baseUnit in upgradePath.reversed()){
|
||||
if (isInvalidUpgradeDestination(baseUnit)) continue
|
||||
return baseUnit
|
||||
}
|
||||
return unit.baseUnit
|
||||
}
|
||||
|
||||
/** Check whether this unit can upgrade to [unitToUpgradeTo]. This does not check or follow the
|
||||
* normal upgrade chain defined by [BaseUnit.upgradesTo], unless [unitToUpgradeTo] is left at default.
|
||||
* @param ignoreRequirements Ignore possible tech/policy/building requirements (e.g. resource requirements still count).
|
||||
* Used for upgrading units via ancient ruins.
|
||||
* @param ignoreResources Ignore resource requirements (tech still counts)
|
||||
* Used to display disabled Upgrade button
|
||||
*/
|
||||
fun canUpgrade(
|
||||
unitToUpgradeTo: BaseUnit = getUnitToUpgradeTo(),
|
||||
ignoreRequirements: Boolean = false,
|
||||
ignoreResources: Boolean = false
|
||||
): Boolean {
|
||||
if (unit.name == unitToUpgradeTo.name) return false
|
||||
|
||||
// We need to remove the unit from the civ for this check,
|
||||
// because if the unit requires, say, horses, and so does its upgrade,
|
||||
// and the civ currently has 0 horses, we need to see if the upgrade will be buildable
|
||||
// WHEN THE CURRENT UNIT IS NOT HERE
|
||||
// TODO redesign without kludge: Inform getRejectionReasons about 'virtually available' resources somehow
|
||||
unit.civInfo.units.removeUnit(unit)
|
||||
val rejectionReasons = unitToUpgradeTo.getRejectionReasons(unit.civInfo)
|
||||
unit.civInfo.units.addUnit(unit)
|
||||
|
||||
var relevantRejectionReasons = rejectionReasons.asSequence().filterNot { it.rejectionReason == RejectionReason.Unbuildable }
|
||||
if (ignoreRequirements)
|
||||
relevantRejectionReasons = relevantRejectionReasons.filterNot { it.rejectionReason in RejectionReasons.techPolicyEraWonderRequirements }
|
||||
if (ignoreResources)
|
||||
relevantRejectionReasons = relevantRejectionReasons.filterNot { it.rejectionReason == RejectionReason.ConsumesResources }
|
||||
return relevantRejectionReasons.none()
|
||||
}
|
||||
|
||||
/** Determine gold cost of a Unit Upgrade, potentially over several steps.
|
||||
* @param unitToUpgradeTo the final BaseUnit. Must be reachable via normal upgrades or else
|
||||
* the function will return the cost to upgrade to the last possible and researched normal upgrade.
|
||||
* @return Gold cost in increments of 5, never negative. Will return 0 for invalid inputs (unit can't upgrade or is is already a [unitToUpgradeTo])
|
||||
* @see <a href="https://github.com/dmnd/CvGameCoreSource/blob/6501d2398113a5100ffa854c146fb6f113992898/CvGameCoreDLL_Expansion1/CvUnit.cpp#L7728">CvUnit::upgradePrice</a>
|
||||
*/
|
||||
// Only one use from getUpgradeAction at the moment, so AI-specific rules omitted
|
||||
//todo Does the AI never buy upgrades???
|
||||
fun getCostOfUpgrade(unitToUpgradeTo: BaseUnit): Int {
|
||||
// Source rounds to int every step, we don't
|
||||
//TODO From the source, this should apply _Production_ modifiers (Temple of Artemis? GameSpeed! StartEra!), at the moment it doesn't
|
||||
|
||||
var goldCostOfUpgrade = 0
|
||||
|
||||
val ruleset = unit.civInfo.gameInfo.ruleSet
|
||||
val constants = ruleset.modOptions.constants.unitUpgradeCost
|
||||
// apply modifiers: Wonders (Pentagon), Policies (Professional Army). Cached outside loop despite
|
||||
// the UniqueType being allowed on a BaseUnit - we don't have a MapUnit in the loop.
|
||||
// Actually instantiating every intermediate to support such mods: todo
|
||||
var civModifier = 1f
|
||||
val stateForConditionals = StateForConditionals(unit.civInfo, unit = unit)
|
||||
for (unique in unit.civInfo.getMatchingUniques(UniqueType.UnitUpgradeCost, stateForConditionals))
|
||||
civModifier *= unique.params[0].toPercent()
|
||||
|
||||
val upgradePath = getUpgradePath()
|
||||
var currentUnit = unit.baseUnit
|
||||
for (baseUnit in upgradePath) {
|
||||
// do clamping and rounding here so upgrading stepwise costs the same as upgrading far down the chain
|
||||
var stepCost = constants.base
|
||||
stepCost += (constants.perProduction * (baseUnit.cost - currentUnit.cost)).coerceAtLeast(0f)
|
||||
val era = ruleset.eras[ruleset.technologies[baseUnit.requiredTech]?.era()]
|
||||
if (era != null)
|
||||
stepCost *= (1f + era.eraNumber * constants.eraMultiplier)
|
||||
stepCost = (stepCost * civModifier).pow(constants.exponent)
|
||||
stepCost *= unit.civInfo.gameInfo.speed.modifier
|
||||
goldCostOfUpgrade += (stepCost / constants.roundTo).toInt() * constants.roundTo
|
||||
if (baseUnit == unitToUpgradeTo)
|
||||
break // stop at requested BaseUnit to upgrade to
|
||||
currentUnit = baseUnit
|
||||
}
|
||||
|
||||
|
||||
return goldCostOfUpgrade
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -229,10 +229,10 @@ class UnitOverviewTab(
|
||||
add(promotionsTable)
|
||||
|
||||
// Upgrade column
|
||||
if (unit.canUpgrade()) {
|
||||
if (unit.upgrade.canUpgrade()) {
|
||||
val unitAction = UnitActions.getUpgradeAction(unit)
|
||||
val enable = unitAction?.action != null
|
||||
val upgradeIcon = ImageGetter.getUnitIcon(unit.getUnitToUpgradeTo().name,
|
||||
val upgradeIcon = ImageGetter.getUnitIcon(unit.upgrade.getUnitToUpgradeTo().name,
|
||||
if (enable) Color.GREEN else Color.GREEN.darken(0.5f))
|
||||
if (enable) upgradeIcon.onClick {
|
||||
SoundPlayer.play(unitAction!!.uncivSound)
|
||||
|
@ -414,10 +414,10 @@ object UnitActions {
|
||||
val upgradedUnit = when {
|
||||
isSpecial && specialUpgradesTo != null -> civInfo.getEquivalentUnit(specialUpgradesTo)
|
||||
(isFree || isSpecial) && upgradesTo != null -> civInfo.getEquivalentUnit(upgradesTo) // Only get DIRECT upgrade
|
||||
else -> unit.getUnitToUpgradeTo() // Get EVENTUAL upgrade, all the way up the chain
|
||||
else -> unit.upgrade.getUnitToUpgradeTo() // Get EVENTUAL upgrade, all the way up the chain
|
||||
}
|
||||
|
||||
if (!unit.canUpgrade(unitToUpgradeTo = upgradedUnit, ignoreRequirements = isFree, ignoreResources = true))
|
||||
if (!unit.upgrade.canUpgrade(unitToUpgradeTo = upgradedUnit, ignoreRequirements = isFree, ignoreResources = true))
|
||||
return null
|
||||
|
||||
// Check _new_ resource requirements (display only - yes even for free or special upgrades)
|
||||
@ -431,7 +431,7 @@ object UnitActions {
|
||||
.filter { it.value > 0 }
|
||||
.joinToString { "${it.value} {${it.key}}".tr() }
|
||||
|
||||
val goldCostOfUpgrade = if (isFree) 0 else unit.getCostOfUpgrade(upgradedUnit)
|
||||
val goldCostOfUpgrade = if (isFree) 0 else unit.upgrade.getCostOfUpgrade(upgradedUnit)
|
||||
|
||||
// No string for "FREE" variants, these are never shown to the user.
|
||||
// The free actions are only triggered via OneTimeUnitUpgrade or OneTimeUnitSpecialUpgrade in UniqueTriggerActivation.
|
||||
@ -461,7 +461,7 @@ object UnitActions {
|
||||
unit.civInfo.gold >= goldCostOfUpgrade
|
||||
&& unit.currentMovement > 0
|
||||
&& !unit.isEmbarked()
|
||||
&& unit.canUpgrade(unitToUpgradeTo = upgradedUnit)
|
||||
&& unit.upgrade.canUpgrade(unitToUpgradeTo = upgradedUnit)
|
||||
)
|
||||
}
|
||||
)
|
||||
@ -494,7 +494,7 @@ object UnitActions {
|
||||
StateForConditionals(unit = unit, civInfo = civInfo, tile = unitTile))) {
|
||||
val upgradedUnit = civInfo.getEquivalentUnit(unique.params[0])
|
||||
// don't show if haven't researched/is obsolete
|
||||
if (!unit.canUpgrade(unitToUpgradeTo = upgradedUnit)) continue
|
||||
if (!unit.upgrade.canUpgrade(unitToUpgradeTo = upgradedUnit)) continue
|
||||
|
||||
// Check _new_ resource requirements
|
||||
// Using Counter to aggregate is a bit exaggerated, but - respect the mad modder.
|
||||
@ -530,7 +530,7 @@ object UnitActions {
|
||||
}.takeIf {
|
||||
unit.currentMovement > 0
|
||||
&& !unit.isEmbarked()
|
||||
&& unit.canUpgrade(unitToUpgradeTo = upgradedUnit)
|
||||
&& unit.upgrade.canUpgrade(unitToUpgradeTo = upgradedUnit)
|
||||
}
|
||||
) )
|
||||
}
|
||||
|
Reference in New Issue
Block a user