diff --git a/core/src/com/unciv/logic/map/tile/TileImprovementFunctions.kt b/core/src/com/unciv/logic/map/tile/TileImprovementFunctions.kt index a6602fca55..4d60a2607c 100644 --- a/core/src/com/unciv/logic/map/tile/TileImprovementFunctions.kt +++ b/core/src/com/unciv/logic/map/tile/TileImprovementFunctions.kt @@ -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? = 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 { diff --git a/core/src/com/unciv/models/ModConstants.kt b/core/src/com/unciv/models/ModConstants.kt index c6fa8bfa6a..6433c76349 100644 --- a/core/src/com/unciv/models/ModConstants.kt +++ b/core/src/com/unciv/models/ModConstants.kt @@ -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) { diff --git a/core/src/com/unciv/models/ruleset/tech/Technology.kt b/core/src/com/unciv/models/ruleset/tech/Technology.kt index 3380a56005..5968cc02ac 100644 --- a/core/src/com/unciv/models/ruleset/tech/Technology.kt +++ b/core/src/com/unciv/models/ruleset/tech/Technology.kt @@ -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 diff --git a/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt b/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt index 3996d70895..8a99a7bdca 100644 --- a/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt +++ b/core/src/com/unciv/models/ruleset/tile/TileImprovement.kt @@ -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) diff --git a/core/src/com/unciv/models/ruleset/unique/IHasUniques.kt b/core/src/com/unciv/models/ruleset/unique/IHasUniques.kt index 2b49dd23e2..e0e2ab34ee 100644 --- a/core/src/com/unciv/models/ruleset/unique/IHasUniques.kt +++ b/core/src/com/unciv/models/ruleset/unique/IHasUniques.kt @@ -68,14 +68,14 @@ interface IHasUniques : INamed { fun requiredTechs(): Sequence = legacyRequiredTechs() + techsRequiredByUniques() fun requiredTechnologies(ruleset: Ruleset): Sequence = - 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 { diff --git a/core/src/com/unciv/ui/screens/modmanager/ModManagementScreen.kt b/core/src/com/unciv/ui/screens/modmanager/ModManagementScreen.kt index 9a31b79f6b..ebf17012ab 100644 --- a/core/src/com/unciv/ui/screens/modmanager/ModManagementScreen.kt +++ b/core/src/com/unciv/ui/screens/modmanager/ModManagementScreen.kt @@ -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() diff --git a/core/src/com/unciv/ui/screens/pickerscreens/ImprovementPickerScreen.kt b/core/src/com/unciv/ui/screens/pickerscreens/ImprovementPickerScreen.kt index 088b3c942e..3a95d34c29 100644 --- a/core/src/com/unciv/ui/screens/pickerscreens/ImprovementPickerScreen.kt +++ b/core/src/com/unciv/ui/screens/pickerscreens/ImprovementPickerScreen.kt @@ -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() + /** `first` is the text, `second` the Civilopedia link */ + val proposedSolutions = mutableSetOf>() 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) } } } diff --git a/core/src/com/unciv/ui/screens/pickerscreens/PickerPane.kt b/core/src/com/unciv/ui/screens/pickerscreens/PickerPane.kt index 583cfac4a9..478a65bf34 100644 --- a/core/src/com/unciv/ui/screens/pickerscreens/PickerPane.kt +++ b/core/src/com/unciv/ui/screens/pickerscreens/PickerPane.kt @@ -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 diff --git a/core/src/com/unciv/ui/screens/pickerscreens/PickerScreen.kt b/core/src/com/unciv/ui/screens/pickerscreens/PickerScreen.kt index c72ad33cf9..53462461f2 100644 --- a/core/src/com/unciv/ui/screens/pickerscreens/PickerScreen.kt +++ b/core/src/com/unciv/ui/screens/pickerscreens/PickerScreen.kt @@ -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 */ diff --git a/core/src/com/unciv/ui/screens/pickerscreens/ReligionPickerScreenCommon.kt b/core/src/com/unciv/ui/screens/pickerscreens/ReligionPickerScreenCommon.kt index 276eb33730..63dde7e23d 100644 --- a/core/src/com/unciv/ui/screens/pickerscreens/ReligionPickerScreenCommon.kt +++ b/core/src/com/unciv/ui/screens/pickerscreens/ReligionPickerScreenCommon.kt @@ -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 diff --git a/docs/Modders/Mod-file-structure/5-Miscellaneous-JSON-files.md b/docs/Modders/Mod-file-structure/5-Miscellaneous-JSON-files.md index 8fb7c67d93..8732e3e35d 100644 --- a/docs/Modders/Mod-file-structure/5-Miscellaneous-JSON-files.md +++ b/docs/Modders/Mod-file-structure/5-Miscellaneous-JSON-files.md @@ -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