mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-27 13:55:54 -04:00
Show required resource for upgrades, rework upgrade logic (#6849)
* Show required resource for upgrades, rework upgrade logic * Show required resource for upgrades - reviews
This commit is contained in:
parent
0461d9d7fd
commit
0f63000ac8
@ -843,6 +843,7 @@ Paradrop =
|
||||
Add in capital =
|
||||
Add to [comment] =
|
||||
Upgrade to [unitType] ([goldCost] gold) =
|
||||
Upgrade to [unitType]\n([goldCost] gold, [resources]) =
|
||||
Found city =
|
||||
Promote =
|
||||
Health =
|
||||
|
@ -89,8 +89,19 @@ class RejectionReasons: HashSet<RejectionReasonInstance>() {
|
||||
|
||||
fun contains(rejectionReason: RejectionReason) = any { it.rejectionReason == rejectionReason }
|
||||
|
||||
fun filterTechPolicyEraWonderRequirements(): List<RejectionReasonInstance> {
|
||||
return filterNot { it.rejectionReason in techPolicyEraWonderRequirements }
|
||||
fun isOKIgnoringRequirements(
|
||||
ignoreTechPolicyEraWonderRequirements: Boolean = false,
|
||||
ignoreResources: Boolean = false
|
||||
): Boolean {
|
||||
if (!ignoreTechPolicyEraWonderRequirements && !ignoreResources) return isEmpty()
|
||||
if (!ignoreTechPolicyEraWonderRequirements)
|
||||
return all { it.rejectionReason == RejectionReason.ConsumesResources }
|
||||
if (!ignoreResources)
|
||||
return all { it.rejectionReason in techPolicyEraWonderRequirements }
|
||||
return all {
|
||||
it.rejectionReason == RejectionReason.ConsumesResources ||
|
||||
it.rejectionReason in techPolicyEraWonderRequirements
|
||||
}
|
||||
}
|
||||
|
||||
fun hasAReasonToBeRemovedFromQueue(): Boolean {
|
||||
|
@ -501,49 +501,127 @@ class MapUnit {
|
||||
return false
|
||||
}
|
||||
|
||||
fun getUnitToUpgradeTo(): BaseUnit {
|
||||
/**
|
||||
* Follow the upgrade chain, stopping when there is no [BaseUnit.upgradesTo] or a tech is not researched.
|
||||
* @param [actionAllowStep] Will be called for each upgrade allowed by tech and has a double purpose:
|
||||
* Side effects, e.g. for aggregation, are allowed, and
|
||||
* returning `false` will abort the upgrade chain and not include the step in the final count.
|
||||
* @return Number of allowed upgrade steps
|
||||
*/
|
||||
private fun followUpgradePath(
|
||||
maxSteps: Int = Int.MAX_VALUE,
|
||||
actionAllowStep: (oldUnit: BaseUnit, newUnit: BaseUnit)->Boolean
|
||||
): Int {
|
||||
var unit = baseUnit()
|
||||
var steps = 0
|
||||
|
||||
// Go up the upgrade tree until you find the last one which is buildable
|
||||
while (unit.upgradesTo != null && unit.getDirectUpgradeUnit(civInfo).requiredTech
|
||||
.let { it == null || civInfo.tech.isResearched(it) }
|
||||
)
|
||||
unit = unit.getDirectUpgradeUnit(civInfo)
|
||||
while(steps < maxSteps) {
|
||||
if (unit.upgradesTo == null) break
|
||||
val newUnit = unit.getDirectUpgradeUnit(civInfo)
|
||||
val techName = newUnit.requiredTech
|
||||
if (techName != null && !civInfo.tech.isResearched(techName)) break
|
||||
if (!actionAllowStep(unit, newUnit)) break
|
||||
unit = newUnit
|
||||
steps++
|
||||
}
|
||||
return steps
|
||||
}
|
||||
|
||||
/** 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.
|
||||
* @param maxSteps follow the upgrade chain only this far. Useful values are default (directly upgrade to what tech ultimately allows) or 1 (Civ5 behaviour)
|
||||
*/
|
||||
// Used from UnitAutomation, UI action, canUpgrade
|
||||
fun getUnitToUpgradeTo(maxSteps: Int = Int.MAX_VALUE): BaseUnit {
|
||||
var unit = baseUnit()
|
||||
followUpgradePath(maxSteps) { _, newUnit ->
|
||||
unit = newUnit
|
||||
true
|
||||
}
|
||||
return unit
|
||||
}
|
||||
|
||||
/** @param ignoreRequired: Ignore possible tech/policy/building requirements.
|
||||
* Used for upgrading units via ancient ruins.
|
||||
*/
|
||||
fun canUpgrade(unitToUpgradeTo: BaseUnit = getUnitToUpgradeTo(), ignoreRequired: Boolean = false): Boolean {
|
||||
if (name == unitToUpgradeTo.name) return false
|
||||
val rejectionReasons = unitToUpgradeTo.getRejectionReasons(civInfo)
|
||||
if (rejectionReasons.isEmpty()) return true
|
||||
if (ignoreRequired && rejectionReasons.filterTechPolicyEraWonderRequirements().isEmpty()) return true
|
||||
|
||||
if (rejectionReasons.contains(RejectionReason.ConsumesResources)) {
|
||||
// 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
|
||||
civInfo.removeUnit(this)
|
||||
val canUpgrade =
|
||||
if (ignoreRequired) unitToUpgradeTo.isBuildableIgnoringTechs(civInfo)
|
||||
else unitToUpgradeTo.isBuildable(civInfo)
|
||||
civInfo.addUnit(this)
|
||||
return canUpgrade
|
||||
}
|
||||
return false
|
||||
/** Check if the default upgrade would do more than one step
|
||||
* - to avoid showing both the single step and normal upgrades in UnitActions */
|
||||
fun canUpgradeMultipleSteps(): Boolean {
|
||||
return 1 < followUpgradePath(2) { _, _ -> true }
|
||||
}
|
||||
|
||||
fun getCostOfUpgrade(): Int {
|
||||
val unitToUpgradeTo = getUnitToUpgradeTo()
|
||||
var goldCostOfUpgrade = (unitToUpgradeTo.cost - baseUnit().cost) * 2f + 10f
|
||||
for (unique in civInfo.getMatchingUniques(UniqueType.UnitUpgradeCost, StateForConditionals(civInfo, unit=this)))
|
||||
goldCostOfUpgrade *= unique.params[0].toPercent()
|
||||
/** 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 maxSteps only used for default of [unitToUpgradeTo], ignored otherwise.
|
||||
* @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(
|
||||
maxSteps: Int = Int.MAX_VALUE,
|
||||
unitToUpgradeTo: BaseUnit = getUnitToUpgradeTo(maxSteps),
|
||||
ignoreRequirements: Boolean = false,
|
||||
ignoreResources: Boolean = false
|
||||
): Boolean {
|
||||
if (name == unitToUpgradeTo.name) return false
|
||||
val rejectionReasons = unitToUpgradeTo.getRejectionReasons(civInfo)
|
||||
if (rejectionReasons.isOKIgnoringRequirements(ignoreRequirements, ignoreResources)) return true
|
||||
|
||||
if (goldCostOfUpgrade < 0) return 0 // For instance, Landsknecht costs less than Spearman, so upgrading would cost negative gold
|
||||
return goldCostOfUpgrade.toInt()
|
||||
// The resource requirements check above did not consider that the resources
|
||||
// this unit currently "consumes" are available for an upgrade too - if that's one of the
|
||||
// reasons, repeat the check with those resources in the pool.
|
||||
if (!rejectionReasons.contains(RejectionReason.ConsumesResources))
|
||||
return false
|
||||
|
||||
//TODO redesign without kludge: Inform getRejectionReasons about 'virtually available' resources somehow
|
||||
|
||||
// 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
|
||||
civInfo.removeUnit(this)
|
||||
val canUpgrade = unitToUpgradeTo.getRejectionReasons(civInfo)
|
||||
.isOKIgnoringRequirements(ignoreTechPolicyEraWonderRequirements = ignoreRequirements)
|
||||
civInfo.addUnit(this)
|
||||
return canUpgrade
|
||||
}
|
||||
|
||||
/** 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()
|
||||
|
||||
followUpgradePath(actionAllowStep = fun(oldUnit: BaseUnit, newUnit: BaseUnit): Boolean {
|
||||
// do clamping and rounding here so upgrading stepwise costs the same as upgrading far down the chain
|
||||
var stepCost = constants.base
|
||||
stepCost += (constants.perProduction * (newUnit.cost - oldUnit.cost)).coerceAtLeast(0f)
|
||||
val era = ruleset.eras[ruleset.technologies[newUnit.requiredTech]?.era()]
|
||||
if (era != null)
|
||||
stepCost *= (1f + era.eraNumber * constants.eraMultiplier)
|
||||
stepCost = (stepCost * civModifier).pow(constants.exponent)
|
||||
goldCostOfUpgrade += (stepCost / constants.roundTo).toInt() * constants.roundTo
|
||||
return newUnit != unitToUpgradeTo // stop at requested BaseUnit to upgrade to
|
||||
})
|
||||
|
||||
return goldCostOfUpgrade
|
||||
}
|
||||
|
||||
|
||||
|
@ -34,6 +34,16 @@ class ModConstants {
|
||||
var minimalCityDistance = 3
|
||||
var minimalCityDistanceOnDifferentContinents = 2
|
||||
|
||||
// Constants used to calculate Unit Upgrade gold Cost (can only be modded all-or-nothing)
|
||||
class UnitUpgradeCost {
|
||||
val base = 10f
|
||||
val perProduction = 2f
|
||||
val eraMultiplier = 0f // 0.3 in Civ5 cpp sources but 0 in xml
|
||||
val exponent = 1f
|
||||
val roundTo = 5
|
||||
}
|
||||
var unitUpgradeCost = UnitUpgradeCost()
|
||||
|
||||
// NaturalWonderGenerator uses these to determine the number of Natural Wonders to spawn for a given map size.
|
||||
// With these values, radius * mul + add gives a 1-2-3-4-5 progression for Unciv predefined map sizes and a 2-3-4-5-6-7 progression for the original Civ5 map sizes.
|
||||
// 0.124 = (Civ5.Huge.getHexagonalRadiusForArea(w*h) - Civ5.Duel.getHexagonalRadiusForArea(w*h)) / 5 (if you do not round in the radius function)
|
||||
@ -63,6 +73,7 @@ class ModConstants {
|
||||
if (other.unitSupplyPerPopulation != defaults.unitSupplyPerPopulation) unitSupplyPerPopulation = other.unitSupplyPerPopulation
|
||||
if (other.minimalCityDistance != defaults.minimalCityDistance) minimalCityDistance = other.minimalCityDistance
|
||||
if (other.minimalCityDistanceOnDifferentContinents != defaults.minimalCityDistanceOnDifferentContinents) minimalCityDistanceOnDifferentContinents = other.minimalCityDistanceOnDifferentContinents
|
||||
if (other.unitUpgradeCost != defaults.unitUpgradeCost) unitUpgradeCost = other.unitUpgradeCost
|
||||
if (other.naturalWonderCountMultiplier != defaults.naturalWonderCountMultiplier) naturalWonderCountMultiplier = other.naturalWonderCountMultiplier
|
||||
if (other.naturalWonderCountAddedConstant != defaults.naturalWonderCountAddedConstant) naturalWonderCountAddedConstant = other.naturalWonderCountAddedConstant
|
||||
if (other.ancientRuinCountMultiplier != defaults.ancientRuinCountMultiplier) ancientRuinCountMultiplier = other.ancientRuinCountMultiplier
|
||||
|
@ -7,7 +7,6 @@ import com.badlogic.gdx.utils.Align
|
||||
import com.unciv.ui.utils.KeyCharAndCode
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.Constants
|
||||
import com.unciv.models.translations.equalsPlaceholderText
|
||||
import com.unciv.models.translations.getPlaceholderParameters
|
||||
import com.unciv.ui.utils.darken
|
||||
|
||||
@ -25,31 +24,28 @@ data class UnitAction(
|
||||
) {
|
||||
fun getIcon(): Actor {
|
||||
if (type.imageGetter != null) return type.imageGetter.invoke()
|
||||
return when {
|
||||
type == UnitActionType.Upgrade
|
||||
&& title.equalsPlaceholderText("Upgrade to [] ([] gold)") -> {
|
||||
return when (type) {
|
||||
UnitActionType.Upgrade -> {
|
||||
ImageGetter.getUnitIcon(title.getPlaceholderParameters()[0])
|
||||
}
|
||||
type == UnitActionType.Create
|
||||
&& title.equalsPlaceholderText("Create []") -> {
|
||||
UnitActionType.Create -> {
|
||||
ImageGetter.getImprovementIcon(title.getPlaceholderParameters()[0])
|
||||
}
|
||||
type == UnitActionType.SpreadReligion
|
||||
&& title.equalsPlaceholderText("Spread []") -> {
|
||||
UnitActionType.SpreadReligion -> {
|
||||
val religionName = title.getPlaceholderParameters()[0]
|
||||
ImageGetter.getReligionImage(
|
||||
if (ImageGetter.religionIconExists(religionName)) religionName
|
||||
else "Pantheon"
|
||||
).apply { color = Color.BLACK }
|
||||
}
|
||||
type == UnitActionType.Fortify || type == UnitActionType.FortifyUntilHealed -> {
|
||||
UnitActionType.Fortify, UnitActionType.FortifyUntilHealed -> {
|
||||
val match = fortificationRegex.matchEntire(title)
|
||||
val percentFortified = match?.groups?.get(1)?.value?.toInt() ?: 0
|
||||
ImageGetter.getImage("OtherIcons/Shield").apply {
|
||||
color = Color.GREEN.darken(1f - percentFortified / 80f)
|
||||
}
|
||||
}
|
||||
else -> ImageGetter.getImage("OtherIcons/Star")
|
||||
else -> ImageGetter.getImage("OtherIcons/Star").apply { color = Color.BLACK }
|
||||
}
|
||||
}
|
||||
companion object {
|
||||
|
@ -17,7 +17,7 @@ import kotlin.random.Random
|
||||
|
||||
// Buildings, techs, policies, ancient ruins and promotions can have 'triggered' effects
|
||||
object UniqueTriggerActivation {
|
||||
/** @return boolean whether an action was successfully preformed */
|
||||
/** @return boolean whether an action was successfully performed */
|
||||
fun triggerCivwideUnique(
|
||||
unique: Unique,
|
||||
civInfo: CivilizationInfo,
|
||||
@ -470,7 +470,7 @@ object UniqueTriggerActivation {
|
||||
return false
|
||||
}
|
||||
|
||||
/** @return boolean whether an action was successfully preformed */
|
||||
/** @return boolean whether an action was successfully performed */
|
||||
fun triggerUnitwideUnique(
|
||||
unique: Unique,
|
||||
unit: MapUnit,
|
||||
|
@ -467,7 +467,7 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
|
||||
|
||||
fun isBuildableIgnoringTechs(civInfo: CivilizationInfo): Boolean {
|
||||
val rejectionReasons = getRejectionReasons(civInfo)
|
||||
return rejectionReasons.filterTechPolicyEraWonderRequirements().isEmpty()
|
||||
return rejectionReasons.isOKIgnoringRequirements(ignoreTechPolicyEraWonderRequirements = true)
|
||||
}
|
||||
|
||||
override fun postBuildEvent(cityConstructions: CityConstructions, boughtWith: Stat?): Boolean {
|
||||
|
@ -12,6 +12,7 @@ import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
|
||||
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
|
||||
import com.unciv.logic.map.MapUnit
|
||||
import com.unciv.logic.map.TileInfo
|
||||
import com.unciv.models.Counter
|
||||
import com.unciv.models.UncivSound
|
||||
import com.unciv.models.UnitAction
|
||||
import com.unciv.models.UnitActionType
|
||||
@ -83,6 +84,9 @@ object UnitActions {
|
||||
addSleepActions(actionList, unit, true)
|
||||
addFortifyActions(actionList, unit, true)
|
||||
|
||||
if (unit.canUpgradeMultipleSteps())
|
||||
addUnitUpgradeAction(unit, actionList, 1)
|
||||
|
||||
addSwapAction(unit, actionList, worldScreen)
|
||||
addDisbandAction(actionList, unit, worldScreen)
|
||||
addGiftAction(unit, actionList, tile)
|
||||
@ -301,96 +305,90 @@ object UnitActions {
|
||||
}
|
||||
}
|
||||
|
||||
private fun addUnitUpgradeAction(unit: MapUnit, actionList: ArrayList<UnitAction>) {
|
||||
val upgradeAction = getUpgradeAction(unit)
|
||||
private fun addUnitUpgradeAction(
|
||||
unit: MapUnit,
|
||||
actionList: ArrayList<UnitAction>,
|
||||
maxSteps: Int = Int.MAX_VALUE
|
||||
) {
|
||||
val upgradeAction = getUpgradeAction(unit, maxSteps)
|
||||
if (upgradeAction != null) actionList += upgradeAction
|
||||
}
|
||||
|
||||
fun getUpgradeAction(unit: MapUnit): UnitAction? {
|
||||
val tile = unit.currentTile
|
||||
/** Common implementation for [getUpgradeAction], [getFreeUpgradeAction] and [getAncientRuinsUpgradeAction] */
|
||||
private fun getUpgradeAction(
|
||||
unit: MapUnit,
|
||||
maxSteps: Int,
|
||||
isFree: Boolean,
|
||||
isSpecial: Boolean
|
||||
): UnitAction? {
|
||||
if (unit.baseUnit().upgradesTo == null) return null
|
||||
if (!unit.canUpgrade()) return null
|
||||
if (tile.getOwner() != unit.civInfo) return null
|
||||
val unitTile = unit.getTile()
|
||||
val civInfo = unit.civInfo
|
||||
if (!isFree && unitTile.getOwner() != civInfo) return null
|
||||
|
||||
val upgradedUnit = unit.getUnitToUpgradeTo()
|
||||
val goldCostOfUpgrade = unit.getCostOfUpgrade()
|
||||
val upgradesTo = unit.baseUnit().upgradesTo
|
||||
val specialUpgradesTo = unit.baseUnit().specialUpgradesTo
|
||||
val upgradedUnit = when {
|
||||
isSpecial && specialUpgradesTo != null -> civInfo.getEquivalentUnit (specialUpgradesTo)
|
||||
isFree && upgradesTo != null -> civInfo.getEquivalentUnit(upgradesTo) // getUnitToUpgradeTo can't ignore tech
|
||||
else -> unit.getUnitToUpgradeTo(maxSteps)
|
||||
}
|
||||
if (!unit.canUpgrade(unitToUpgradeTo = upgradedUnit, ignoreRequirements = isFree, ignoreResources = true))
|
||||
return null
|
||||
|
||||
// Check _new_ resource requirements (display only - yes even for free or special upgrades)
|
||||
// Using Counter to aggregate is a bit exaggerated, but - respect the mad modder.
|
||||
val resourceRequirementsDelta = Counter<String>()
|
||||
for ((resource, amount) in unit.baseUnit().getResourceRequirements())
|
||||
resourceRequirementsDelta.add(resource, -amount)
|
||||
for ((resource, amount) in upgradedUnit.getResourceRequirements())
|
||||
resourceRequirementsDelta.add(resource, amount)
|
||||
val newResourceRequirementsString = resourceRequirementsDelta.entries
|
||||
.filter { it.value > 0 }
|
||||
.joinToString { "${it.value} {${it.key}}".tr() }
|
||||
|
||||
val goldCostOfUpgrade = if (isFree) 0 else unit.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.
|
||||
val title = if (newResourceRequirementsString.isEmpty())
|
||||
"Upgrade to [${upgradedUnit.name}] ([$goldCostOfUpgrade] gold)"
|
||||
else "Upgrade to [${upgradedUnit.name}]\n([$goldCostOfUpgrade] gold, [$newResourceRequirementsString])"
|
||||
|
||||
return UnitAction(UnitActionType.Upgrade,
|
||||
title = "Upgrade to [${upgradedUnit.name}] ([$goldCostOfUpgrade] gold)",
|
||||
title = title,
|
||||
action = {
|
||||
val unitTile = unit.getTile()
|
||||
unit.destroy()
|
||||
val newUnit = unit.civInfo.placeUnitNearTile(unitTile.position, upgradedUnit.name)
|
||||
val newUnit = civInfo.placeUnitNearTile(unitTile.position, upgradedUnit.name)
|
||||
|
||||
/** 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 readdedUnit = unit.civInfo.placeUnitNearTile(unitTile.position, unit.name)
|
||||
unit.copyStatisticsTo(readdedUnit!!)
|
||||
val resurrectedUnit = civInfo.placeUnitNearTile(unitTile.position, unit.name)!!
|
||||
unit.copyStatisticsTo(resurrectedUnit)
|
||||
} else { // Managed to upgrade
|
||||
unit.civInfo.addGold(-goldCostOfUpgrade)
|
||||
if (!isFree) civInfo.addGold(-goldCostOfUpgrade)
|
||||
unit.copyStatisticsTo(newUnit)
|
||||
newUnit.currentMovement = 0f
|
||||
}
|
||||
}.takeIf {
|
||||
unit.civInfo.gold >= goldCostOfUpgrade
|
||||
&& unit.currentMovement > 0
|
||||
&& !unit.isEmbarked()
|
||||
isFree || (
|
||||
unit.civInfo.gold >= goldCostOfUpgrade
|
||||
&& unit.currentMovement > 0
|
||||
&& !unit.isEmbarked()
|
||||
&& unit.canUpgrade(unitToUpgradeTo = upgradedUnit)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun getFreeUpgradeAction(unit: MapUnit): UnitAction? {
|
||||
if (unit.baseUnit().upgradesTo == null) return null
|
||||
val upgradedUnit = unit.civInfo.getEquivalentUnit(unit.baseUnit().upgradesTo!!)
|
||||
if (!unit.canUpgrade(upgradedUnit, true)) return null
|
||||
|
||||
return UnitAction(UnitActionType.Upgrade,
|
||||
title = "Upgrade to [${upgradedUnit.name}] (FREE)",
|
||||
action = {
|
||||
val unitTile = unit.getTile()
|
||||
unit.destroy()
|
||||
val newUnit = unit.civInfo.placeUnitNearTile(unitTile.position, upgradedUnit.name)
|
||||
|
||||
/** 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 readdedUnit = unit.civInfo.placeUnitNearTile(unitTile.position, unit.name)
|
||||
unit.copyStatisticsTo(readdedUnit!!)
|
||||
} else { // Managed to upgrade
|
||||
unit.copyStatisticsTo(newUnit)
|
||||
newUnit.currentMovement = 0f
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun getAncientRuinsUpgradeAction(unit: MapUnit): UnitAction? {
|
||||
val upgradedUnitName =
|
||||
when {
|
||||
unit.baseUnit.specialUpgradesTo != null -> unit.baseUnit.specialUpgradesTo
|
||||
unit.baseUnit.upgradesTo != null -> unit.baseUnit.upgradesTo
|
||||
else -> return null
|
||||
}
|
||||
val upgradedUnit =
|
||||
unit.civInfo.getEquivalentUnit(unit.civInfo.gameInfo.ruleSet.units[upgradedUnitName]!!)
|
||||
|
||||
if (!unit.canUpgrade(upgradedUnit,true)) return null
|
||||
|
||||
return UnitAction(UnitActionType.Upgrade,
|
||||
title = "Upgrade to [${upgradedUnit.name}] (free)",
|
||||
action = {
|
||||
val unitTile = unit.getTile()
|
||||
unit.destroy()
|
||||
val newUnit = unit.civInfo.placeUnitNearTile(unitTile.position, upgradedUnit.name)!!
|
||||
unit.copyStatisticsTo(newUnit)
|
||||
|
||||
newUnit.currentMovement = 0f
|
||||
}
|
||||
)
|
||||
}
|
||||
fun getUpgradeAction(unit: MapUnit, maxSteps: Int = Int.MAX_VALUE) =
|
||||
getUpgradeAction(unit, maxSteps, isFree = false, isSpecial = false)
|
||||
fun getFreeUpgradeAction(unit: MapUnit) =
|
||||
getUpgradeAction(unit, 1, isFree = true, isSpecial = false)
|
||||
fun getAncientRuinsUpgradeAction(unit: MapUnit) =
|
||||
getUpgradeAction(unit, 1, isFree = true, isSpecial = true)
|
||||
|
||||
private fun addBuildingImprovementsAction(unit: MapUnit, actionList: ArrayList<UnitAction>, tile: TileInfo, worldScreen: WorldScreen, unitTable: UnitTable) {
|
||||
if (!unit.hasUniqueToBuildImprovements) return
|
||||
|
@ -89,8 +89,10 @@ The file can have the following attributes, including the values Unciv sets (no
|
||||
### ModConstants
|
||||
|
||||
Stored in ModOptions.constants, this is a collection of constants used internally in Unciv.
|
||||
This is the only structure that is _merged_ field by field from mods, not overwritten, so you can change XP from Barbarians in one mod
|
||||
and city distance in another. In case of conflicts, there is no guarantee which mod wins, only that _default_ values are ignored.
|
||||
|
||||
| Attribute | Type | Optional | Notes |
|
||||
| Attribute | Type | Default | Notes |
|
||||
| --------- | ---- | -------- | ----- |
|
||||
| maxXPfromBarbarians | Int | 30 | [^A] |
|
||||
| cityStrengthBase| Float | 8.0 | [^B] |
|
||||
@ -102,6 +104,7 @@ Stored in ModOptions.constants, this is a collection of constants used internall
|
||||
| unitSupplyPerPopulation| Float | 0.5 | [^C] |
|
||||
| minimalCityDistance| Int | 3 | [^D] |
|
||||
| minimalCityDistanceOnDifferentContinents| Int | 2 | [^D] |
|
||||
| unitUpgradeCost | Object | see below | [^J] |
|
||||
| naturalWonderCountMultiplier| Float | 0.124 | [^E] |
|
||||
| naturalWonderCountAddedConstant| Float | 0.1 | [^E] |
|
||||
| ancientRuinCountMultiplier| Float | 0.02 | [^F] |
|
||||
@ -134,6 +137,26 @@ Legend:
|
||||
- [^F]: MapGenerator.spreadAncientRuins: number of ruins = suitable tile count * this
|
||||
- [^H]: MapGenerator.spawnLakesAndCoasts: Water bodies up to this tile count become Lakes
|
||||
- [^I]: RiverGenerator: river frequency and length bounds
|
||||
- [^J]: A [UnitUpgradeCost](#UnitUpgradeCost) sub-structure.
|
||||
|
||||
#### UnitUpgradeCost
|
||||
|
||||
These values are not merged individually, only the entire sub-structure is.
|
||||
|
||||
| Attribute | Type | Default | Notes |
|
||||
| --------- | ---- | -------- | ----- |
|
||||
| base | Float | 10 | |
|
||||
| perProduction | Float | 2 | |
|
||||
| eraMultiplier | Float | 0 | |
|
||||
| exponent | Float | 1 | |
|
||||
| roundTo | Int | 5 | |
|
||||
|
||||
The formula for the gold cost of a unit upgrade is (rounded down to a multiple of `roundTo`):
|
||||
( max((`base` + `perProduction` * (new_unit_cost - old_unit_cost)), 0)
|
||||
* (1 + eraNumber * `eraMultiplier`) * `civModifier`
|
||||
) ^ `exponent`
|
||||
With `civModifier` being the multiplicative aggregate of ["\[relativeAmount\]% Gold cost of upgrading"](../uniques.md#global_uniques) uniques that apply.
|
||||
|
||||
|
||||
## VictoryTypes.json
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user