mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-27 22:06:05 -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 in capital =
|
||||||
Add to [comment] =
|
Add to [comment] =
|
||||||
Upgrade to [unitType] ([goldCost] gold) =
|
Upgrade to [unitType] ([goldCost] gold) =
|
||||||
|
Upgrade to [unitType]\n([goldCost] gold, [resources]) =
|
||||||
Found city =
|
Found city =
|
||||||
Promote =
|
Promote =
|
||||||
Health =
|
Health =
|
||||||
|
@ -89,8 +89,19 @@ class RejectionReasons: HashSet<RejectionReasonInstance>() {
|
|||||||
|
|
||||||
fun contains(rejectionReason: RejectionReason) = any { it.rejectionReason == rejectionReason }
|
fun contains(rejectionReason: RejectionReason) = any { it.rejectionReason == rejectionReason }
|
||||||
|
|
||||||
fun filterTechPolicyEraWonderRequirements(): List<RejectionReasonInstance> {
|
fun isOKIgnoringRequirements(
|
||||||
return filterNot { it.rejectionReason in techPolicyEraWonderRequirements }
|
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 {
|
fun hasAReasonToBeRemovedFromQueue(): Boolean {
|
||||||
|
@ -501,49 +501,127 @@ class MapUnit {
|
|||||||
return false
|
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 unit = baseUnit()
|
||||||
|
var steps = 0
|
||||||
|
|
||||||
// Go up the upgrade tree until you find the last one which is buildable
|
// Go up the upgrade tree until you find the last one which is buildable
|
||||||
while (unit.upgradesTo != null && unit.getDirectUpgradeUnit(civInfo).requiredTech
|
while(steps < maxSteps) {
|
||||||
.let { it == null || civInfo.tech.isResearched(it) }
|
if (unit.upgradesTo == null) break
|
||||||
)
|
val newUnit = unit.getDirectUpgradeUnit(civInfo)
|
||||||
unit = 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
|
return unit
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param ignoreRequired: Ignore possible tech/policy/building requirements.
|
/** Check if the default upgrade would do more than one step
|
||||||
* Used for upgrading units via ancient ruins.
|
* - to avoid showing both the single step and normal upgrades in UnitActions */
|
||||||
*/
|
fun canUpgradeMultipleSteps(): Boolean {
|
||||||
fun canUpgrade(unitToUpgradeTo: BaseUnit = getUnitToUpgradeTo(), ignoreRequired: Boolean = false): Boolean {
|
return 1 < followUpgradePath(2) { _, _ -> true }
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCostOfUpgrade(): Int {
|
/** Check whether this unit can upgrade to [unitToUpgradeTo]. This does not check or follow the
|
||||||
val unitToUpgradeTo = getUnitToUpgradeTo()
|
* normal upgrade chain defined by [BaseUnit.upgradesTo], unless [unitToUpgradeTo] is left at default.
|
||||||
var goldCostOfUpgrade = (unitToUpgradeTo.cost - baseUnit().cost) * 2f + 10f
|
* @param maxSteps only used for default of [unitToUpgradeTo], ignored otherwise.
|
||||||
for (unique in civInfo.getMatchingUniques(UniqueType.UnitUpgradeCost, StateForConditionals(civInfo, unit=this)))
|
* @param ignoreRequirements Ignore possible tech/policy/building requirements (e.g. resource requirements still count).
|
||||||
goldCostOfUpgrade *= unique.params[0].toPercent()
|
* 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
|
// The resource requirements check above did not consider that the resources
|
||||||
return goldCostOfUpgrade.toInt()
|
// 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 minimalCityDistance = 3
|
||||||
var minimalCityDistanceOnDifferentContinents = 2
|
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.
|
// 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.
|
// 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)
|
// 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.unitSupplyPerPopulation != defaults.unitSupplyPerPopulation) unitSupplyPerPopulation = other.unitSupplyPerPopulation
|
||||||
if (other.minimalCityDistance != defaults.minimalCityDistance) minimalCityDistance = other.minimalCityDistance
|
if (other.minimalCityDistance != defaults.minimalCityDistance) minimalCityDistance = other.minimalCityDistance
|
||||||
if (other.minimalCityDistanceOnDifferentContinents != defaults.minimalCityDistanceOnDifferentContinents) minimalCityDistanceOnDifferentContinents = other.minimalCityDistanceOnDifferentContinents
|
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.naturalWonderCountMultiplier != defaults.naturalWonderCountMultiplier) naturalWonderCountMultiplier = other.naturalWonderCountMultiplier
|
||||||
if (other.naturalWonderCountAddedConstant != defaults.naturalWonderCountAddedConstant) naturalWonderCountAddedConstant = other.naturalWonderCountAddedConstant
|
if (other.naturalWonderCountAddedConstant != defaults.naturalWonderCountAddedConstant) naturalWonderCountAddedConstant = other.naturalWonderCountAddedConstant
|
||||||
if (other.ancientRuinCountMultiplier != defaults.ancientRuinCountMultiplier) ancientRuinCountMultiplier = other.ancientRuinCountMultiplier
|
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.utils.KeyCharAndCode
|
||||||
import com.unciv.ui.images.ImageGetter
|
import com.unciv.ui.images.ImageGetter
|
||||||
import com.unciv.Constants
|
import com.unciv.Constants
|
||||||
import com.unciv.models.translations.equalsPlaceholderText
|
|
||||||
import com.unciv.models.translations.getPlaceholderParameters
|
import com.unciv.models.translations.getPlaceholderParameters
|
||||||
import com.unciv.ui.utils.darken
|
import com.unciv.ui.utils.darken
|
||||||
|
|
||||||
@ -25,31 +24,28 @@ data class UnitAction(
|
|||||||
) {
|
) {
|
||||||
fun getIcon(): Actor {
|
fun getIcon(): Actor {
|
||||||
if (type.imageGetter != null) return type.imageGetter.invoke()
|
if (type.imageGetter != null) return type.imageGetter.invoke()
|
||||||
return when {
|
return when (type) {
|
||||||
type == UnitActionType.Upgrade
|
UnitActionType.Upgrade -> {
|
||||||
&& title.equalsPlaceholderText("Upgrade to [] ([] gold)") -> {
|
|
||||||
ImageGetter.getUnitIcon(title.getPlaceholderParameters()[0])
|
ImageGetter.getUnitIcon(title.getPlaceholderParameters()[0])
|
||||||
}
|
}
|
||||||
type == UnitActionType.Create
|
UnitActionType.Create -> {
|
||||||
&& title.equalsPlaceholderText("Create []") -> {
|
|
||||||
ImageGetter.getImprovementIcon(title.getPlaceholderParameters()[0])
|
ImageGetter.getImprovementIcon(title.getPlaceholderParameters()[0])
|
||||||
}
|
}
|
||||||
type == UnitActionType.SpreadReligion
|
UnitActionType.SpreadReligion -> {
|
||||||
&& title.equalsPlaceholderText("Spread []") -> {
|
|
||||||
val religionName = title.getPlaceholderParameters()[0]
|
val religionName = title.getPlaceholderParameters()[0]
|
||||||
ImageGetter.getReligionImage(
|
ImageGetter.getReligionImage(
|
||||||
if (ImageGetter.religionIconExists(religionName)) religionName
|
if (ImageGetter.religionIconExists(religionName)) religionName
|
||||||
else "Pantheon"
|
else "Pantheon"
|
||||||
).apply { color = Color.BLACK }
|
).apply { color = Color.BLACK }
|
||||||
}
|
}
|
||||||
type == UnitActionType.Fortify || type == UnitActionType.FortifyUntilHealed -> {
|
UnitActionType.Fortify, UnitActionType.FortifyUntilHealed -> {
|
||||||
val match = fortificationRegex.matchEntire(title)
|
val match = fortificationRegex.matchEntire(title)
|
||||||
val percentFortified = match?.groups?.get(1)?.value?.toInt() ?: 0
|
val percentFortified = match?.groups?.get(1)?.value?.toInt() ?: 0
|
||||||
ImageGetter.getImage("OtherIcons/Shield").apply {
|
ImageGetter.getImage("OtherIcons/Shield").apply {
|
||||||
color = Color.GREEN.darken(1f - percentFortified / 80f)
|
color = Color.GREEN.darken(1f - percentFortified / 80f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> ImageGetter.getImage("OtherIcons/Star")
|
else -> ImageGetter.getImage("OtherIcons/Star").apply { color = Color.BLACK }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -17,7 +17,7 @@ import kotlin.random.Random
|
|||||||
|
|
||||||
// Buildings, techs, policies, ancient ruins and promotions can have 'triggered' effects
|
// Buildings, techs, policies, ancient ruins and promotions can have 'triggered' effects
|
||||||
object UniqueTriggerActivation {
|
object UniqueTriggerActivation {
|
||||||
/** @return boolean whether an action was successfully preformed */
|
/** @return boolean whether an action was successfully performed */
|
||||||
fun triggerCivwideUnique(
|
fun triggerCivwideUnique(
|
||||||
unique: Unique,
|
unique: Unique,
|
||||||
civInfo: CivilizationInfo,
|
civInfo: CivilizationInfo,
|
||||||
@ -470,7 +470,7 @@ object UniqueTriggerActivation {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return boolean whether an action was successfully preformed */
|
/** @return boolean whether an action was successfully performed */
|
||||||
fun triggerUnitwideUnique(
|
fun triggerUnitwideUnique(
|
||||||
unique: Unique,
|
unique: Unique,
|
||||||
unit: MapUnit,
|
unit: MapUnit,
|
||||||
|
@ -467,7 +467,7 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
|
|||||||
|
|
||||||
fun isBuildableIgnoringTechs(civInfo: CivilizationInfo): Boolean {
|
fun isBuildableIgnoringTechs(civInfo: CivilizationInfo): Boolean {
|
||||||
val rejectionReasons = getRejectionReasons(civInfo)
|
val rejectionReasons = getRejectionReasons(civInfo)
|
||||||
return rejectionReasons.filterTechPolicyEraWonderRequirements().isEmpty()
|
return rejectionReasons.isOKIgnoringRequirements(ignoreTechPolicyEraWonderRequirements = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun postBuildEvent(cityConstructions: CityConstructions, boughtWith: Stat?): Boolean {
|
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.civilization.diplomacy.DiplomaticModifiers
|
||||||
import com.unciv.logic.map.MapUnit
|
import com.unciv.logic.map.MapUnit
|
||||||
import com.unciv.logic.map.TileInfo
|
import com.unciv.logic.map.TileInfo
|
||||||
|
import com.unciv.models.Counter
|
||||||
import com.unciv.models.UncivSound
|
import com.unciv.models.UncivSound
|
||||||
import com.unciv.models.UnitAction
|
import com.unciv.models.UnitAction
|
||||||
import com.unciv.models.UnitActionType
|
import com.unciv.models.UnitActionType
|
||||||
@ -83,6 +84,9 @@ object UnitActions {
|
|||||||
addSleepActions(actionList, unit, true)
|
addSleepActions(actionList, unit, true)
|
||||||
addFortifyActions(actionList, unit, true)
|
addFortifyActions(actionList, unit, true)
|
||||||
|
|
||||||
|
if (unit.canUpgradeMultipleSteps())
|
||||||
|
addUnitUpgradeAction(unit, actionList, 1)
|
||||||
|
|
||||||
addSwapAction(unit, actionList, worldScreen)
|
addSwapAction(unit, actionList, worldScreen)
|
||||||
addDisbandAction(actionList, unit, worldScreen)
|
addDisbandAction(actionList, unit, worldScreen)
|
||||||
addGiftAction(unit, actionList, tile)
|
addGiftAction(unit, actionList, tile)
|
||||||
@ -301,96 +305,90 @@ object UnitActions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addUnitUpgradeAction(unit: MapUnit, actionList: ArrayList<UnitAction>) {
|
private fun addUnitUpgradeAction(
|
||||||
val upgradeAction = getUpgradeAction(unit)
|
unit: MapUnit,
|
||||||
|
actionList: ArrayList<UnitAction>,
|
||||||
|
maxSteps: Int = Int.MAX_VALUE
|
||||||
|
) {
|
||||||
|
val upgradeAction = getUpgradeAction(unit, maxSteps)
|
||||||
if (upgradeAction != null) actionList += upgradeAction
|
if (upgradeAction != null) actionList += upgradeAction
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUpgradeAction(unit: MapUnit): UnitAction? {
|
/** Common implementation for [getUpgradeAction], [getFreeUpgradeAction] and [getAncientRuinsUpgradeAction] */
|
||||||
val tile = unit.currentTile
|
private fun getUpgradeAction(
|
||||||
|
unit: MapUnit,
|
||||||
|
maxSteps: Int,
|
||||||
|
isFree: Boolean,
|
||||||
|
isSpecial: Boolean
|
||||||
|
): UnitAction? {
|
||||||
if (unit.baseUnit().upgradesTo == null) return null
|
if (unit.baseUnit().upgradesTo == null) return null
|
||||||
if (!unit.canUpgrade()) return null
|
val unitTile = unit.getTile()
|
||||||
if (tile.getOwner() != unit.civInfo) return null
|
val civInfo = unit.civInfo
|
||||||
|
if (!isFree && unitTile.getOwner() != civInfo) return null
|
||||||
|
|
||||||
val upgradedUnit = unit.getUnitToUpgradeTo()
|
val upgradesTo = unit.baseUnit().upgradesTo
|
||||||
val goldCostOfUpgrade = unit.getCostOfUpgrade()
|
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,
|
return UnitAction(UnitActionType.Upgrade,
|
||||||
title = "Upgrade to [${upgradedUnit.name}] ([$goldCostOfUpgrade] gold)",
|
title = title,
|
||||||
action = {
|
action = {
|
||||||
val unitTile = unit.getTile()
|
|
||||||
unit.destroy()
|
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!
|
/** 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 readdedUnit = unit.civInfo.placeUnitNearTile(unitTile.position, unit.name)
|
val resurrectedUnit = civInfo.placeUnitNearTile(unitTile.position, unit.name)!!
|
||||||
unit.copyStatisticsTo(readdedUnit!!)
|
unit.copyStatisticsTo(resurrectedUnit)
|
||||||
} else { // Managed to upgrade
|
} else { // Managed to upgrade
|
||||||
unit.civInfo.addGold(-goldCostOfUpgrade)
|
if (!isFree) civInfo.addGold(-goldCostOfUpgrade)
|
||||||
unit.copyStatisticsTo(newUnit)
|
unit.copyStatisticsTo(newUnit)
|
||||||
newUnit.currentMovement = 0f
|
newUnit.currentMovement = 0f
|
||||||
}
|
}
|
||||||
}.takeIf {
|
}.takeIf {
|
||||||
unit.civInfo.gold >= goldCostOfUpgrade
|
isFree || (
|
||||||
&& unit.currentMovement > 0
|
unit.civInfo.gold >= goldCostOfUpgrade
|
||||||
&& !unit.isEmbarked()
|
&& unit.currentMovement > 0
|
||||||
|
&& !unit.isEmbarked()
|
||||||
|
&& unit.canUpgrade(unitToUpgradeTo = upgradedUnit)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFreeUpgradeAction(unit: MapUnit): UnitAction? {
|
fun getUpgradeAction(unit: MapUnit, maxSteps: Int = Int.MAX_VALUE) =
|
||||||
if (unit.baseUnit().upgradesTo == null) return null
|
getUpgradeAction(unit, maxSteps, isFree = false, isSpecial = false)
|
||||||
val upgradedUnit = unit.civInfo.getEquivalentUnit(unit.baseUnit().upgradesTo!!)
|
fun getFreeUpgradeAction(unit: MapUnit) =
|
||||||
if (!unit.canUpgrade(upgradedUnit, true)) return null
|
getUpgradeAction(unit, 1, isFree = true, isSpecial = false)
|
||||||
|
fun getAncientRuinsUpgradeAction(unit: MapUnit) =
|
||||||
return UnitAction(UnitActionType.Upgrade,
|
getUpgradeAction(unit, 1, isFree = true, isSpecial = true)
|
||||||
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
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addBuildingImprovementsAction(unit: MapUnit, actionList: ArrayList<UnitAction>, tile: TileInfo, worldScreen: WorldScreen, unitTable: UnitTable) {
|
private fun addBuildingImprovementsAction(unit: MapUnit, actionList: ArrayList<UnitAction>, tile: TileInfo, worldScreen: WorldScreen, unitTable: UnitTable) {
|
||||||
if (!unit.hasUniqueToBuildImprovements) return
|
if (!unit.hasUniqueToBuildImprovements) return
|
||||||
|
@ -89,8 +89,10 @@ The file can have the following attributes, including the values Unciv sets (no
|
|||||||
### ModConstants
|
### ModConstants
|
||||||
|
|
||||||
Stored in ModOptions.constants, this is a collection of constants used internally in Unciv.
|
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] |
|
| maxXPfromBarbarians | Int | 30 | [^A] |
|
||||||
| cityStrengthBase| Float | 8.0 | [^B] |
|
| 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] |
|
| unitSupplyPerPopulation| Float | 0.5 | [^C] |
|
||||||
| minimalCityDistance| Int | 3 | [^D] |
|
| minimalCityDistance| Int | 3 | [^D] |
|
||||||
| minimalCityDistanceOnDifferentContinents| Int | 2 | [^D] |
|
| minimalCityDistanceOnDifferentContinents| Int | 2 | [^D] |
|
||||||
|
| unitUpgradeCost | Object | see below | [^J] |
|
||||||
| naturalWonderCountMultiplier| Float | 0.124 | [^E] |
|
| naturalWonderCountMultiplier| Float | 0.124 | [^E] |
|
||||||
| naturalWonderCountAddedConstant| Float | 0.1 | [^E] |
|
| naturalWonderCountAddedConstant| Float | 0.1 | [^E] |
|
||||||
| ancientRuinCountMultiplier| Float | 0.02 | [^F] |
|
| ancientRuinCountMultiplier| Float | 0.02 | [^F] |
|
||||||
@ -134,6 +137,26 @@ Legend:
|
|||||||
- [^F]: MapGenerator.spreadAncientRuins: number of ruins = suitable tile count * this
|
- [^F]: MapGenerator.spreadAncientRuins: number of ruins = suitable tile count * this
|
||||||
- [^H]: MapGenerator.spawnLakesAndCoasts: Water bodies up to this tile count become Lakes
|
- [^H]: MapGenerator.spawnLakesAndCoasts: Water bodies up to this tile count become Lakes
|
||||||
- [^I]: RiverGenerator: river frequency and length bounds
|
- [^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
|
## VictoryTypes.json
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user