mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-15 02:09:21 +07:00
Improvement picker fixes (#11801)
* 🎵 A little linting 🎶 * Prevent queueing unresearched removals by adding their problem reports on top * More linting and bugfixes * Hide "too advanced" Improvements in the PickerScreen * ImprovementPickerScreen fully Civilopedia-linked * Fix dumb mistake * Slightly improve PickerPane descriptionLabel layout * Slightly improve ImprovementPickerScreen top padding
This commit is contained in:
@ -94,7 +94,7 @@ class TileImprovementFunctions(val tile: Tile) {
|
||||
yield(ImprovementBuildingProblem.Other)
|
||||
}
|
||||
|
||||
/** Without regards to what CivInfo it is, a lot of the checks are just for the improvement on the tile.
|
||||
/** Without regards to what CivInfo it is (so no tech requirement check), a lot of the checks are just for the improvement on the tile.
|
||||
* Doubles as a check for the map editor.
|
||||
*/
|
||||
internal fun canImprovementBeBuiltHere(
|
||||
@ -105,7 +105,7 @@ class TileImprovementFunctions(val tile: Tile) {
|
||||
isNormalizeCheck: Boolean = false
|
||||
): Boolean {
|
||||
|
||||
fun TileImprovement.canBeBuildOnThisUnbuildableTerrain(
|
||||
fun TileImprovement.canBeBuiltOnThisUnbuildableTerrain(
|
||||
knownFeatureRemovals: List<TileImprovement>? = null,
|
||||
): Boolean {
|
||||
val topTerrain = tile.lastTerrain
|
||||
@ -121,7 +121,7 @@ class TileImprovementFunctions(val tile: Tile) {
|
||||
if (featureRemovals.any { it !in knownFeatureRemovals }) return false
|
||||
val clonedTile = tile.clone()
|
||||
clonedTile.setTerrainFeatures(tile.terrainFeatures.filterNot {
|
||||
feature -> featureRemovals.any{ it.name.removePrefix(Constants.remove) == feature } })
|
||||
feature -> featureRemovals.any { it.name.removePrefix(Constants.remove) == feature } })
|
||||
return clonedTile.improvementFunctions.canImprovementBeBuiltHere(improvement, resourceIsVisible, knownFeatureRemovals, stateForConditionals)
|
||||
}
|
||||
|
||||
@ -142,13 +142,13 @@ class TileImprovementFunctions(val tile: Tile) {
|
||||
RoadStatus.values().any { it.name == improvement.name } -> !tile.isWater
|
||||
&& RoadStatus.valueOf(improvement.name) > tile.roadStatus
|
||||
|
||||
// Then we check if there is any reason to not allow this improvement to be build
|
||||
// Then we check if there is any reason to not allow this improvement to be built
|
||||
|
||||
// Can't build if there is already an irremovable improvement here
|
||||
tile.improvement != null && tile.getTileImprovement()!!.hasUnique(UniqueType.Irremovable, stateForConditionals) -> false
|
||||
|
||||
// Can't build if this terrain is unbuildable, except when we are specifically allowed to
|
||||
tile.lastTerrain.unbuildable && !improvement.canBeBuildOnThisUnbuildableTerrain(knownFeatureRemovals) -> false
|
||||
tile.lastTerrain.unbuildable && !improvement.canBeBuiltOnThisUnbuildableTerrain(knownFeatureRemovals) -> false
|
||||
|
||||
// Can't build if any terrain specifically prevents building this improvement
|
||||
tile.getTerrainMatchingUniques(UniqueType.RestrictedBuildableImprovements, stateForConditionals).any {
|
||||
|
@ -98,11 +98,13 @@ class ModConstants {
|
||||
|
||||
// Espionage
|
||||
var maxSpyRank = 3
|
||||
// How much of a skill bonus each rank gives.
|
||||
// Rank 0 is 100%, rank 1 is 130%, and so on for stealing technology.
|
||||
// How much of a skill bonus each rank gives.
|
||||
// Rank 0 is 100%, rank 1 is 130%, and so on for stealing technology.
|
||||
// Half as much for a coup.
|
||||
var spyRankSkillPercentBonus = 30
|
||||
|
||||
// UI: If set >= 0, ImprovementPicker will silently skip improvements whose tech requirement is more advanced than your current Era + this
|
||||
var maxImprovementTechErasForward = -1
|
||||
|
||||
fun merge(other: ModConstants) {
|
||||
for (field in this::class.java.declaredFields) {
|
||||
|
@ -33,6 +33,8 @@ class Technology: RulesetObject() {
|
||||
override fun getCivilopediaTextLines(ruleset: Ruleset) =
|
||||
TechnologyDescriptions.getCivilopediaTextLines(this, ruleset)
|
||||
|
||||
override fun era(ruleset: Ruleset) = ruleset.eras[era()]
|
||||
|
||||
fun matchesFilter(filter: String): Boolean {
|
||||
return when (filter) {
|
||||
in Constants.all -> true
|
||||
|
@ -27,6 +27,7 @@ class TileImprovement : RulesetStatsObject() {
|
||||
// This is the base cost. A cost of 0 means created instead of buildable.
|
||||
var turnsToBuild: Int = -1
|
||||
|
||||
override fun legacyRequiredTechs() = if (techRequired == null) emptySequence() else sequenceOf(techRequired!!)
|
||||
|
||||
fun getTurnsToBuild(civInfo: Civilization, unit: MapUnit): Int {
|
||||
val state = StateForConditionals(civInfo, unit = unit)
|
||||
|
@ -68,14 +68,14 @@ interface IHasUniques : INamed {
|
||||
fun requiredTechs(): Sequence<String> = legacyRequiredTechs() + techsRequiredByUniques()
|
||||
|
||||
fun requiredTechnologies(ruleset: Ruleset): Sequence<Technology?> =
|
||||
requiredTechs().map{ ruleset.technologies[it] }
|
||||
requiredTechs().map { ruleset.technologies[it] }
|
||||
|
||||
fun era(ruleset: Ruleset): Era? =
|
||||
requiredTechnologies(ruleset).map{ it?.era() }.map{ ruleset.eras[it] }.maxByOrNull{ it?.eraNumber ?: 0 }
|
||||
requiredTechnologies(ruleset).map { it?.era() }.map { ruleset.eras[it] }.maxByOrNull { it?.eraNumber ?: 0 }
|
||||
// This will return null only if requiredTechnologies() is empty or all required techs have no eraNumber
|
||||
|
||||
fun techColumn(ruleset: Ruleset): TechColumn? =
|
||||
requiredTechnologies(ruleset).map{ it?.column }.filterNotNull().maxByOrNull{ it.columnNumber }
|
||||
requiredTechnologies(ruleset).map { it?.column }.filterNotNull().maxByOrNull { it.columnNumber }
|
||||
// This will return null only if *all* required techs have null TechColumn.
|
||||
|
||||
fun availableInEra(ruleset: Ruleset, requestedEra: String): Boolean {
|
||||
|
@ -150,11 +150,10 @@ class ModManagementScreen private constructor(
|
||||
// Replace the PickerScreen's descriptionLabel
|
||||
val labelWrapper = Table()
|
||||
labelWrapper.defaults().top().left().growX()
|
||||
val labelScroll = descriptionLabel.parent as ScrollPane
|
||||
descriptionLabel.remove()
|
||||
labelWrapper.row()
|
||||
labelWrapper.add(modDescriptionLabel).row()
|
||||
labelScroll.actor = labelWrapper
|
||||
descriptionScroll.actor = labelWrapper
|
||||
|
||||
isPortrait = isNarrowerThan4to3()
|
||||
if (isPortrait) initPortrait()
|
||||
|
@ -2,7 +2,10 @@ package com.unciv.ui.screens.pickerscreens
|
||||
|
||||
import com.badlogic.gdx.graphics.Color
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor
|
||||
import com.badlogic.gdx.scenes.scene2d.InputEvent
|
||||
import com.badlogic.gdx.scenes.scene2d.Touchable
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.badlogic.gdx.scenes.scene2d.utils.ClickListener
|
||||
import com.badlogic.gdx.utils.Align
|
||||
import com.unciv.Constants
|
||||
import com.unciv.logic.map.mapunit.MapUnit
|
||||
@ -46,6 +49,7 @@ class ImprovementPickerScreen(
|
||||
// Support for UniqueType.CreatesOneImprovement
|
||||
private val tileMarkedForCreatesOneImprovement = tile.isMarkedForCreatesOneImprovement()
|
||||
private val tileWithoutLastTerrain: Tile
|
||||
private val maxErasForward = ruleset.modOptions.constants.maxImprovementTechErasForward.takeUnless { it < 0 } ?: Int.MAX_VALUE
|
||||
|
||||
private fun getRequiredTechColumn(improvement: TileImprovement) =
|
||||
ruleset.technologies[improvement.techRequired]?.column?.columnNumber ?: -1
|
||||
@ -75,6 +79,11 @@ class ImprovementPickerScreen(
|
||||
accept(selectedImprovement)
|
||||
}
|
||||
|
||||
descriptionLabel.onClick {
|
||||
val link = selectedImprovement?.makeLink()
|
||||
if (!link.isNullOrEmpty()) openCivilopedia(link)
|
||||
}
|
||||
|
||||
val regularImprovements = Table()
|
||||
regularImprovements.defaults().pad(5f)
|
||||
|
||||
@ -95,106 +104,26 @@ class ImprovementPickerScreen(
|
||||
if (!unit.canBuildImprovement(improvement)) continue
|
||||
val problemReport = getProblemReport(improvement) ?: continue
|
||||
|
||||
val image = ImageGetter.getImprovementPortrait(improvement.name, 30f)
|
||||
|
||||
// allow multiple key mappings to technologically supersede each other
|
||||
var shortcutKey = improvement.shortcutKey
|
||||
if (shortcutKey != null) {
|
||||
val techLevel = getRequiredTechColumn(improvement)
|
||||
val isSuperseded = ruleset.tileImprovements.values.asSequence()
|
||||
// *other* improvements with same shortcutKey
|
||||
.filter { it.shortcutKey == improvement.shortcutKey && it != improvement }
|
||||
// civ can build it (checks tech researched)
|
||||
.filter { tile.improvementFunctions.canBuildImprovement(it, currentPlayerCiv) }
|
||||
// is technologically more advanced
|
||||
.filter { getRequiredTechColumn(it) > techLevel }
|
||||
.any()
|
||||
// another supersedes this - ignore key binding
|
||||
if (isSuperseded) shortcutKey = null
|
||||
}
|
||||
|
||||
var labelText = improvement.name.tr(true)
|
||||
val turnsToBuild = if (tile.improvementInProgress == improvement.name) tile.turnsToImprovement
|
||||
else improvement.getTurnsToBuild(currentPlayerCiv, unit)
|
||||
|
||||
if (turnsToBuild > 0) labelText += " - $turnsToBuild${Fonts.turn}"
|
||||
val provideResource = tile.hasViewableResource(currentPlayerCiv) && tile.tileResource.isImprovedBy(improvement.name)
|
||||
if (provideResource) labelText += "\n" + "Provides [${tile.resource}]".tr()
|
||||
val removeImprovement = (!improvement.isRoad()
|
||||
&& !improvement.name.startsWith(Constants.remove)
|
||||
&& improvement.name != Constants.cancelImprovementOrder)
|
||||
if (tile.improvement != null && removeImprovement) labelText += "\n" + "Replaces [${tile.improvement}]".tr()
|
||||
|
||||
val statIcons = getStatIconsTable(provideResource, removeImprovement)
|
||||
|
||||
// get benefits of the new improvement
|
||||
val stats = tile.stats.getStatDiffForImprovement(
|
||||
improvement,
|
||||
currentPlayerCiv,
|
||||
tile.getCity(),
|
||||
cityUniqueCache
|
||||
)
|
||||
|
||||
//Warn when the current improvement will increase a stat for the tile,
|
||||
// but the tile is outside of the range (> 3 tiles from any city center) that can be
|
||||
// worked by a city's population
|
||||
if (tile.owningCity != null
|
||||
&& !improvement.isRoad()
|
||||
&& stats.values.any { it > 0f }
|
||||
&& !improvement.name.startsWith(Constants.remove)
|
||||
&& !tile.getTilesInDistance(currentPlayerCiv.modConstants.cityWorkRange)
|
||||
.any { it.isCityCenter() && it.getCity()!!.civ == currentPlayerCiv }
|
||||
)
|
||||
labelText += "\n" + "Not in city work range".tr()
|
||||
|
||||
val statsTable = getStatsTable(stats)
|
||||
statIcons.add(statsTable).padLeft(13f)
|
||||
|
||||
regularImprovements.add(statIcons).align(Align.right)
|
||||
|
||||
val improvementButton = PickerPane.getPickerOptionButton(image, labelText)
|
||||
// This is onClick without ActivationTypes.Keystroke equivalence - keys should select *and* close:
|
||||
improvementButton.onActivation(type = ActivationTypes.Tap, noEquivalence = true) {
|
||||
selectedImprovement = improvement
|
||||
pick(improvement.name.tr())
|
||||
descriptionLabel.setText(improvement.getDescription(ruleset))
|
||||
}
|
||||
|
||||
improvementButton.onDoubleClick { accept(improvement) }
|
||||
|
||||
when {
|
||||
improvement.name == tile.improvementInProgress ->
|
||||
improvementButton.color = Color.GREEN
|
||||
problemReport.isQueueable() ->
|
||||
// TODO should be a skin ButtonStyle, this mixes with the style override from disable() below - which is a very wrong approach anyway
|
||||
improvementButton.setColor(0.625f, 1f, 0.625f, 1f) // #a0ffa0 - brightened GREEN
|
||||
}
|
||||
|
||||
if (!problemReport.isEmpty() || tileMarkedForCreatesOneImprovement) {
|
||||
improvementButton.disable()
|
||||
} else if (shortcutKey != null) {
|
||||
// Shortcut keys trigger what onDoubleClick does, not equivalent to single Click:
|
||||
improvementButton.keyShortcuts.add(shortcutKey) { accept(improvement) }
|
||||
improvementButton.addTooltip(shortcutKey)
|
||||
}
|
||||
|
||||
regularImprovements.add(improvementButton)
|
||||
regularImprovements.add(getExplanationActor(improvement, problemReport)).padLeft(10f)
|
||||
regularImprovements.row()
|
||||
regularImprovements.addImprovementRow(improvement, problemReport, cityUniqueCache)
|
||||
}
|
||||
|
||||
val ownerTable = Table()
|
||||
if (tile.getOwner() == null) {
|
||||
ownerTable.add("Unowned tile".toLabel())
|
||||
ownerTable.add("Unowned tile".toLabel()).pad(10f)
|
||||
} else if (tile.getOwner()!!.isCurrentPlayer()) {
|
||||
val button = tile.getCity()!!.name.toTextButton(hideIcons = true)
|
||||
button.onClick {
|
||||
this.game.pushScreen(CityScreen(tile.getCity()!!, null, tile))
|
||||
}
|
||||
ownerTable.add("Tile owned by [${tile.getOwner()!!.civName}] (You)".toLabel()).padLeft(10f)
|
||||
val label = "Tile owned by [${tile.getOwner()!!.civName}] (You)".toLabel()
|
||||
label.onClick { openCivilopedia(tile.getOwner()!!.nation.makeLink()) }
|
||||
ownerTable.add(label)
|
||||
ownerTable.add(button).padLeft(20f)
|
||||
ownerTable.padTop(2.5f) // The button causes the label to have ample padding, just unglue the button from the window border a little
|
||||
} else {
|
||||
ownerTable.add("Tile owned by [${tile.getOwner()!!.civName}] - [${tile.getCity()!!.name}]".toLabel()).padLeft(10f)
|
||||
val label = "Tile owned by [${tile.getOwner()!!.civName}] - [${tile.getCity()!!.name}]".toLabel()
|
||||
label.onClick { openCivilopedia(tile.getOwner()!!.nation.makeLink()) }
|
||||
ownerTable.add(label).pad(10f)
|
||||
}
|
||||
|
||||
topTable.add(ownerTable)
|
||||
@ -202,12 +131,120 @@ class ImprovementPickerScreen(
|
||||
topTable.add(regularImprovements)
|
||||
}
|
||||
|
||||
private fun Table.addImprovementRow(improvement: TileImprovement, problemReport: ProblemReport, cityUniqueCache: LocalUniqueCache) {
|
||||
val image = ImageGetter.getImprovementPortrait(improvement.name, 30f)
|
||||
|
||||
// allow multiple key mappings to technologically supersede each other
|
||||
var shortcutKey = improvement.shortcutKey
|
||||
if (shortcutKey != null) {
|
||||
val techLevel = getRequiredTechColumn(improvement)
|
||||
val isSuperseded = ruleset.tileImprovements.values.asSequence()
|
||||
// *other* improvements with same shortcutKey
|
||||
.filter { it.shortcutKey == improvement.shortcutKey && it != improvement }
|
||||
// civ can build it (checks tech researched)
|
||||
.filter { tile.improvementFunctions.canBuildImprovement(it, currentPlayerCiv) }
|
||||
// is technologically more advanced
|
||||
.filter { getRequiredTechColumn(it) > techLevel }
|
||||
.any()
|
||||
// another supersedes this - ignore key binding
|
||||
if (isSuperseded) shortcutKey = null
|
||||
}
|
||||
|
||||
var labelText = improvement.name.tr(true)
|
||||
val turnsToBuild = if (tile.improvementInProgress == improvement.name) tile.turnsToImprovement
|
||||
else improvement.getTurnsToBuild(currentPlayerCiv, unit)
|
||||
|
||||
if (turnsToBuild > 0) labelText += " - $turnsToBuild${Fonts.turn}"
|
||||
val provideResource = tile.hasViewableResource(currentPlayerCiv) && tile.tileResource.isImprovedBy(improvement.name)
|
||||
if (provideResource) labelText += "\n" + "Provides [${tile.resource}]".tr()
|
||||
val removeImprovement = (!improvement.isRoad()
|
||||
&& !improvement.name.startsWith(Constants.remove)
|
||||
&& improvement.name != Constants.cancelImprovementOrder)
|
||||
if (tile.improvement != null && removeImprovement) labelText += "\n" + "Replaces [${tile.improvement}]".tr()
|
||||
|
||||
val statIcons = getStatIconsTable(provideResource, removeImprovement)
|
||||
|
||||
// get benefits of the new improvement
|
||||
val stats = tile.stats.getStatDiffForImprovement(
|
||||
improvement,
|
||||
currentPlayerCiv,
|
||||
tile.getCity(),
|
||||
cityUniqueCache
|
||||
)
|
||||
|
||||
//Warn when the current improvement will increase a stat for the tile,
|
||||
// but the tile is outside of the range (> 3 tiles from any city center) that can be
|
||||
// worked by a city's population
|
||||
if (tile.owningCity != null
|
||||
&& !improvement.isRoad()
|
||||
&& stats.values.any { it > 0f }
|
||||
&& !improvement.name.startsWith(Constants.remove)
|
||||
&& !tile.getTilesInDistance(currentPlayerCiv.modConstants.cityWorkRange)
|
||||
.any { it.isCityCenter() && it.getCity()!!.civ == currentPlayerCiv }
|
||||
)
|
||||
labelText += "\n" + "Not in city work range".tr()
|
||||
|
||||
val statsTable = getStatsTable(stats)
|
||||
statIcons.add(statsTable).padLeft(13f)
|
||||
|
||||
add(statIcons).align(Align.right)
|
||||
|
||||
val improvementButton = PickerPane.getPickerOptionButton(image, labelText)
|
||||
// This is onClick without ActivationTypes.Keystroke equivalence - keys should select *and* close:
|
||||
improvementButton.onActivation(type = ActivationTypes.Tap, noEquivalence = true) {
|
||||
setDescription(improvement, Color.WHITE)
|
||||
pick(improvement.name.tr())
|
||||
}
|
||||
|
||||
when {
|
||||
improvement.name == tile.improvementInProgress ->
|
||||
improvementButton.color = Color.GREEN
|
||||
problemReport.isQueueable() ->
|
||||
// TODO should be a skin ButtonStyle, this mixes with the style override from disable() below - which is a very wrong approach anyway
|
||||
improvementButton.setColor(0.625f, 1f, 0.625f, 1f) // #a0ffa0 - brightened GREEN
|
||||
}
|
||||
|
||||
if (!problemReport.isEmpty() || tileMarkedForCreatesOneImprovement) {
|
||||
improvementButton.disable()
|
||||
// Now a little backhanded trick: We want to allow access to information on a disabled improvement
|
||||
// isDisabled still prevents the Button class from firing its event, but our own ClickListener bypasses that and is fired from Actor code
|
||||
improvementButton.touchable = Touchable.enabled
|
||||
improvementButton.addListener(object : ClickListener() {
|
||||
override fun clicked(event: InputEvent?, x: Float, y: Float) {
|
||||
setDescription(improvement, Color.LIGHT_GRAY)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
improvementButton.onDoubleClick { accept(improvement) }
|
||||
if (shortcutKey != null) {
|
||||
// Shortcut keys trigger what onDoubleClick does, not equivalent to single Click:
|
||||
improvementButton.keyShortcuts.add(shortcutKey) { accept(improvement) }
|
||||
improvementButton.addTooltip(shortcutKey)
|
||||
}
|
||||
}
|
||||
|
||||
add(improvementButton)
|
||||
add(getExplanationActor(improvement, problemReport)).padLeft(10f)
|
||||
row()
|
||||
}
|
||||
|
||||
/** Sets the PickerPane's description and where in Civilopedia a click on it should go - but not the right side button */
|
||||
private fun setDescription(improvement: TileImprovement, color: Color) {
|
||||
selectedImprovement = improvement
|
||||
descriptionLabel.setText(improvement.getDescription(ruleset))
|
||||
descriptionLabel.color = color
|
||||
}
|
||||
|
||||
private fun getStatIconsTable(provideResource: Boolean, removeImprovement: Boolean): Table {
|
||||
val statIcons = Table()
|
||||
|
||||
// icon for adding the resource by improvement
|
||||
if (provideResource)
|
||||
statIcons.add(ImageGetter.getResourcePortrait(tile.resource.toString(), 30f)).pad(3f)
|
||||
if (provideResource) {
|
||||
val resourceIcon = ImageGetter.getResourcePortrait(tile.resource!!, 30f) // `!!` is covered by provideResource
|
||||
val link = ruleset.tileResources[tile.resource]?.makeLink()
|
||||
if (!link.isNullOrEmpty()) resourceIcon.onClick { openCivilopedia(link) }
|
||||
statIcons.add(resourceIcon).pad(3f)
|
||||
}
|
||||
|
||||
// icon for removing the resource by replacing improvement
|
||||
if (removeImprovement && tile.hasViewableResource(currentPlayerCiv) && tile.improvement != null && tile.tileResource.isImprovedBy(tile.improvement!!)) {
|
||||
@ -237,16 +274,17 @@ class ImprovementPickerScreen(
|
||||
private class ProblemReport {
|
||||
var suggestRemoval = false
|
||||
var removalImprovement: TileImprovement? = null
|
||||
val proposedSolutions = mutableListOf<String>()
|
||||
/** `first` is the text, `second` the Civilopedia link */
|
||||
val proposedSolutions = mutableSetOf<Pair<String, String?>>()
|
||||
fun isEmpty() = proposedSolutions.isEmpty()
|
||||
fun isQueueable() = removalImprovement != null && proposedSolutions.size == 1
|
||||
fun toLabel() = proposedSolutions.joinToString("}\n{", "{", "}").toLabel()
|
||||
}
|
||||
|
||||
private fun getProblemReport(improvement: TileImprovement): ProblemReport? {
|
||||
private fun getProblemReport(improvement: TileImprovement) = getProblemReport(tile, tileWithoutLastTerrain, improvement)
|
||||
private fun getProblemReport(tile: Tile, tileWithoutLastTerrain: Tile?, improvement: TileImprovement): ProblemReport? {
|
||||
val report = ProblemReport()
|
||||
var unbuildableBecause = tile.improvementFunctions.getImprovementBuildingProblems(improvement, currentPlayerCiv).toSet()
|
||||
if (!canReport(unbuildableBecause)) {
|
||||
if (!canReport(unbuildableBecause) && tileWithoutLastTerrain != null) {
|
||||
// Try after pretending to have removed the top terrain layer.
|
||||
unbuildableBecause = tileWithoutLastTerrain.improvementFunctions.getImprovementBuildingProblems(improvement, currentPlayerCiv).toSet()
|
||||
if (!canReport(unbuildableBecause)) return null
|
||||
@ -257,20 +295,31 @@ class ImprovementPickerScreen(
|
||||
if (suggestRemoval) {
|
||||
val removalName = Constants.remove + tile.lastTerrain.name
|
||||
removalImprovement = ruleset.tileImprovements[removalName]
|
||||
if (removalImprovement != null)
|
||||
proposedSolutions.add("${Constants.remove}[${tile.lastTerrain.name}] first")
|
||||
if (removalImprovement != null) {
|
||||
val cannotRemoveReport = getProblemReport(tileWithoutLastTerrain!!, null, removalImprovement!!)
|
||||
?: return null
|
||||
proposedSolutions.addAll(cannotRemoveReport.proposedSolutions)
|
||||
proposedSolutions.add("${Constants.remove}[${tile.lastTerrain.name}] first" to removalImprovement!!.makeLink())
|
||||
}
|
||||
}
|
||||
|
||||
if (ImprovementBuildingProblem.MissingTech in unbuildableBecause)
|
||||
proposedSolutions.add("Research [${improvement.techRequired}] first")
|
||||
if (ImprovementBuildingProblem.MissingTech in unbuildableBecause) {
|
||||
val maxEraNumber = currentPlayerCiv.getEraNumber() + maxErasForward
|
||||
for (tech in improvement.requiredTechnologies(ruleset)) {
|
||||
val techEra = tech?.era(ruleset) ?: continue
|
||||
if (techEra.eraNumber > maxEraNumber) return null
|
||||
proposedSolutions.add("Research [${tech.name}] first" to tech.makeLink())
|
||||
}
|
||||
}
|
||||
if (ImprovementBuildingProblem.NotJustOutsideBorders in unbuildableBecause)
|
||||
proposedSolutions.add("Have this tile close to your borders")
|
||||
proposedSolutions.add("Have this tile close to your borders" to null)
|
||||
if (ImprovementBuildingProblem.OutsideBorders in unbuildableBecause)
|
||||
proposedSolutions.add("Have this tile inside your empire")
|
||||
proposedSolutions.add("Have this tile inside your empire" to null)
|
||||
if (ImprovementBuildingProblem.MissingResources in unbuildableBecause) {
|
||||
proposedSolutions.addAll(improvement.getMatchingUniques(UniqueType.ConsumesResources).filter {
|
||||
currentPlayerCiv.getResourceAmount(it.params[1]) < it.params[0].toInt()
|
||||
}.map { "Acquire more [$it]" })
|
||||
val resources = improvement.getMatchingUniques(UniqueType.ConsumesResources)
|
||||
.filter { currentPlayerCiv.getResourceAmount(it.params[1]) < it.params[0].toInt() }
|
||||
.map { "Acquire more [${it.params[1]}]" to ruleset.tileResources[it.params[1]]?.makeLink() }
|
||||
proposedSolutions.addAll(resources)
|
||||
}
|
||||
}
|
||||
return report
|
||||
@ -286,13 +335,15 @@ class ImprovementPickerScreen(
|
||||
else -> getPickNowButton { accept(improvement) }
|
||||
}
|
||||
|
||||
val label = report.toLabel()
|
||||
if (!report.isQueueable()) return label
|
||||
|
||||
return Table().apply {
|
||||
defaults().center()
|
||||
add(label).padBottom(5f).row()
|
||||
add(getPickNowButton { accept(report.removalImprovement, improvement) })
|
||||
for ((text, link) in report.proposedSolutions) {
|
||||
val label = text.toLabel()
|
||||
if (!link.isNullOrEmpty()) label.onClick { openCivilopedia(link) }
|
||||
add(label).padBottom(5f).row()
|
||||
}
|
||||
if (report.isQueueable())
|
||||
add(getPickNowButton { accept(report.removalImprovement, improvement) }).padTop(5f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package com.unciv.ui.screens.pickerscreens
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.Actor
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Button
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Label
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.SplitPane
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup
|
||||
@ -23,7 +24,9 @@ class PickerPane(
|
||||
* Note if you don't use that helper, you'll need to do both click and keyboard support yourself. */
|
||||
val closeButton = Constants.close.toTextButton()
|
||||
/** A scrollable wrapped Label you can use to show descriptions in the [bottomTable], starts empty */
|
||||
val descriptionLabel = "".toLabel()
|
||||
val descriptionLabel = DescriptionLabel()
|
||||
/** Wraps descriptionLabel - a subclass may replace the content, but beware: zero padding around this */
|
||||
internal val descriptionScroll: AutoScrollPane
|
||||
/** A wrapper containing [rightSideButton]. You can add buttons, they will be arranged vertically */
|
||||
val rightSideGroup = VerticalGroup()
|
||||
/** A button on the lower right of [bottomTable] you can use for a "OK"-type action, starts disabled */
|
||||
@ -48,8 +51,10 @@ class PickerPane(
|
||||
bottomTable.add(closeButton).pad(10f)
|
||||
|
||||
descriptionLabel.wrap = true
|
||||
val labelScroll = AutoScrollPane(descriptionLabel, BaseScreen.skin)
|
||||
bottomTable.add(labelScroll).pad(5f).fill().expand()
|
||||
val descriptionWithPad = Table()
|
||||
descriptionWithPad.add(descriptionLabel).pad(10f).grow()
|
||||
descriptionScroll = AutoScrollPane(descriptionWithPad, BaseScreen.skin)
|
||||
bottomTable.add(descriptionScroll).grow()
|
||||
|
||||
rightSideButton.disable()
|
||||
rightSideGroup.addActor(rightSideButton)
|
||||
@ -72,12 +77,28 @@ class PickerPane(
|
||||
rightSideButton.isEnabled = enabled
|
||||
}
|
||||
|
||||
/** Sets the text of the [rightSideButton] and enables it if it's the player's turn */
|
||||
/** Sets the text of the [rightSideButton] (does not autotranslate) and enables it if it's the player's turn */
|
||||
fun pick(rightButtonText: String) {
|
||||
if (GUI.isMyTurn()) rightSideButton.enable()
|
||||
rightSideButton.setText(rightButtonText)
|
||||
}
|
||||
|
||||
/** This Label adjusts the Y scroll of its ascendant ScrollPane when its text is set:
|
||||
* - short texts stay top-aligned with [closeButton]
|
||||
* - taller texts scroll some of the wrapper top padding out
|
||||
* - still taller texts scroll most but not all of the top padding out
|
||||
*
|
||||
* This also ensures vertical scroll is reset when changing description
|
||||
*/
|
||||
inner class DescriptionLabel : Label("", BaseScreen.skin) {
|
||||
override fun setText(newText: CharSequence?) {
|
||||
super.setText(newText)
|
||||
descriptionScroll.validate()
|
||||
descriptionScroll.scrollY = (prefHeight + 10f - descriptionScroll.height).coerceIn(0f, 8f)
|
||||
descriptionScroll.updateVisualScroll()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/** Icon size used in [getPickerOptionButton]. */
|
||||
const val pickerOptionIconSize = 30f
|
||||
|
@ -13,6 +13,8 @@ open class PickerScreen(disableScroll: Boolean = false) : BaseScreen() {
|
||||
val closeButton by pickerPane::closeButton
|
||||
/** @see PickerPane.descriptionLabel */
|
||||
val descriptionLabel by pickerPane::descriptionLabel
|
||||
/** @see PickerPane.descriptionScroll */
|
||||
protected val descriptionScroll by pickerPane::descriptionScroll
|
||||
/** @see PickerPane.rightSideGroup */
|
||||
val rightSideGroup by pickerPane::rightSideGroup
|
||||
/** @see PickerPane.rightSideButton */
|
||||
|
@ -32,7 +32,6 @@ abstract class ReligionPickerScreenCommon(
|
||||
protected val ruleset = gameInfo.ruleset
|
||||
|
||||
private val descriptionTable = Table(skin)
|
||||
private val descriptionScroll = descriptionLabel.parent as ScrollPane
|
||||
|
||||
protected class Selection {
|
||||
var button: Button? = null
|
||||
|
@ -217,6 +217,7 @@ and city distance in another. In case of conflicts, there is no guarantee which
|
||||
| minimumWarDuration | Int | 10 | [^P] |
|
||||
| baseTurnsUntilRevolt | Int | 4 | [^Q] |
|
||||
| cityStateElectionTurns | Int | 15 | [^R] |
|
||||
| maxImprovementTechErasForward | Int | None | [^S] |
|
||||
|
||||
Legend:
|
||||
|
||||
@ -255,6 +256,7 @@ Legend:
|
||||
- [^P]: The number of turns a civ has to wait before negotiating for peace
|
||||
- [^Q]: The number of turns before a revolt is spawned
|
||||
- [^R]: The number of turns between city-state elections
|
||||
- [^S]: If set, the Improvement picker will silently skip improvements whose tech requirement is more advanced than your current Era + this value. Example: With a 0, Trade posts will not show until the Medieval Era, with a 1 they will already show in the CLassical Era.
|
||||
|
||||
#### UnitUpgradeCost
|
||||
|
||||
|
Reference in New Issue
Block a user