diff --git a/core/src/com/unciv/ui/pickerscreens/PolicyPickerScreen.kt b/core/src/com/unciv/ui/pickerscreens/PolicyPickerScreen.kt index ec5cc5f92d..4a0c4f987f 100644 --- a/core/src/com/unciv/ui/pickerscreens/PolicyPickerScreen.kt +++ b/core/src/com/unciv/ui/pickerscreens/PolicyPickerScreen.kt @@ -37,7 +37,7 @@ import java.lang.Integer.max import kotlin.math.abs import kotlin.math.min -object Colors { +object PolicyColors { val policyPickable = colorFromRGB(47,67,92).darken(0.3f) val policyNotPickable = colorFromRGB(20, 20, 20) @@ -48,14 +48,6 @@ object Colors { val branchAdopted = colorFromRGB(100, 90, 10).darken(0.5f) } -object Sizes { - val paddingVertical = 10f - val paddingHorizontal = 20f - val paddingBetweenHor = 10f - val paddingBetweenVer = 20f - val iconSize = 50f -} - fun Policy.isAdopted() : Boolean { val worldScreen = UncivGame.Current.worldScreen ?: return false val viewingCiv = worldScreen.viewingCiv @@ -135,21 +127,21 @@ class PolicyButton(val policy: Policy, size: Float = 30f) : BorderedTable( when { isSelected && isPickable -> { - setBackgroundColor(Colors.policySelected) + setBackgroundColor(PolicyColors.policySelected) } isPickable -> { - setBackgroundColor(Colors.policyPickable) + setBackgroundColor(PolicyColors.policyPickable) } isAdopted -> { - icon.color = Color.GOLD + icon.color = Color.GOLD.cpy() setBackgroundColor(colorFromRGB(10,90,100).darken(0.8f)) } else -> { icon.color.a = 0.2f - setBackgroundColor(Colors.policyNotPickable) + setBackgroundColor(PolicyColors.policyNotPickable) } } @@ -162,6 +154,14 @@ class PolicyButton(val policy: Policy, size: Float = 30f) : BorderedTable( class PolicyPickerScreen(val worldScreen: WorldScreen, civInfo: CivilizationInfo = worldScreen.viewingCiv) : PickerScreen(), RecreateOnResize { + object Sizes { + val paddingVertical = 10f + val paddingHorizontal = 20f + val paddingBetweenHor = 10f + val paddingBetweenVer = 20f + val iconSize = 50f + } + internal val viewingCiv: CivilizationInfo = civInfo private var policyNameToButton = HashMap() @@ -302,8 +302,10 @@ class PolicyPickerScreen(val worldScreen: WorldScreen, civInfo: CivilizationInfo val prefHeight = Sizes.paddingVertical*2 + Sizes.iconSize*maxRow + Sizes.paddingBetweenVer*(maxRow - 1) // Main table - val colorBg = if (branch.isAdopted()) Colors.branchAdopted else Colors.branchNotAdopted - val branchGroup = BorderedTable(innerColor = colorBg) + val colorBg = if (branch.isAdopted()) PolicyColors.branchAdopted else PolicyColors.branchNotAdopted + val branchGroup = BorderedTable( + path="PolicyScree/PolicyBranchBackground", + innerColor = colorBg) // Header val header = getBranchHeader(branch) @@ -343,7 +345,7 @@ class PolicyPickerScreen(val worldScreen: WorldScreen, civInfo: CivilizationInfo for ((k, v) in conditionals) { warning += "• " + k.text.fillPlaceholders(v.joinToString()).tr() + "\n" } - val warningLabel = warning.toLabel(Color.RED, 13) + val warningLabel = warning.toLabel(Color.RED.cpy(), 13) warningLabel.setFillParent(false) warningLabel.setAlignment(Align.topLeft) warningLabel.wrap = true @@ -479,7 +481,7 @@ class PolicyPickerScreen(val worldScreen: WorldScreen, civInfo: CivilizationInfo private fun drawLine(group: Group, policyX: Float, policyY: Float, prereqX: Float, prereqY:Float) { - val lineColor = Color.WHITE + val lineColor = Color.WHITE.cpy() val lineSize = 2f if (policyX != prereqX) { @@ -554,7 +556,9 @@ class PolicyPickerScreen(val worldScreen: WorldScreen, civInfo: CivilizationInfo } private fun getBranchHeader(branch: PolicyBranch): Table { - val header = BorderedTable(innerColor = colorFromRGB(47,90,92), borderSize = 5f) + val header = BorderedTable( + path="PolicyScreen/PolicyBranchHeader", + innerColor = colorFromRGB(47,90,92), borderSize = 5f) header.pad(5f) val table = Table() @@ -582,7 +586,7 @@ class PolicyPickerScreen(val worldScreen: WorldScreen, civInfo: CivilizationInfo val text: String val lockIcon = ImageGetter.getImage("OtherIcons/LockSmall") - .apply { color = Color.WHITE }.toGroup(15f) + .apply { color = Color.WHITE.cpy() }.toGroup(15f) lockIcon.isVisible = false if (viewingCiv.policies.isAdopted(branch.name)) { policy = branch.policies.last() @@ -599,18 +603,19 @@ class PolicyPickerScreen(val worldScreen: WorldScreen, civInfo: CivilizationInfo label.setAlignment(Align.center) val color = when { - policy.isPickable() -> Colors.policyPickable + policy.isPickable() -> PolicyColors.policyPickable viewingCiv.policies.isAdopted(policy.name) -> { label.color = colorFromRGB(150, 70, 40) - Colors.branchCompleted + PolicyColors.branchCompleted } else -> { lockIcon.isVisible = true label.color.a = 0.5f - Colors.policyNotPickable} + PolicyColors.policyNotPickable} } val table = BorderedTable( + path="PolicyScreen/PolicyBranchAdoptButton", defaultInner = skinStrings.roundedEdgeRectangleSmallShape, defaultBorder = skinStrings.roundedEdgeRectangleSmallShape, innerColor = color, borderSize = 2f) diff --git a/core/src/com/unciv/ui/pickerscreens/PromotionPickerScreen.kt b/core/src/com/unciv/ui/pickerscreens/PromotionPickerScreen.kt index fcf67dddaf..06dda976ea 100644 --- a/core/src/com/unciv/ui/pickerscreens/PromotionPickerScreen.kt +++ b/core/src/com/unciv/ui/pickerscreens/PromotionPickerScreen.kt @@ -1,34 +1,153 @@ package com.unciv.ui.pickerscreens import com.badlogic.gdx.graphics.Color +import com.badlogic.gdx.math.Vector2 +import com.badlogic.gdx.scenes.scene2d.Actor +import com.badlogic.gdx.scenes.scene2d.ui.Cell +import com.badlogic.gdx.scenes.scene2d.ui.Image import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.utils.Align import com.unciv.logic.map.MapUnit import com.unciv.models.TutorialTrigger import com.unciv.models.UncivSound -import com.unciv.models.ruleset.unique.UniqueType import com.unciv.models.ruleset.unit.Promotion import com.unciv.models.translations.tr import com.unciv.ui.images.ImageGetter import com.unciv.ui.utils.BaseScreen +import com.unciv.ui.utils.BorderedTable import com.unciv.ui.utils.RecreateOnResize +import com.unciv.ui.utils.extensions.colorFromRGB +import com.unciv.ui.utils.extensions.darken import com.unciv.ui.utils.extensions.isEnabled import com.unciv.ui.utils.extensions.onClick +import com.unciv.ui.utils.extensions.setFontColor import com.unciv.ui.utils.extensions.toLabel import com.unciv.ui.utils.extensions.toTextButton +import com.unciv.utils.Log +import java.lang.Integer.max +import kotlin.math.abs + +class PromotionNode(val promotion: Promotion) { + var maxDepth = 0 + + /** How many level this promotion has */ + var levels = 1 + + val successors: ArrayList = ArrayList() + val predecessors: ArrayList = ArrayList() + + val baseName = getBasePromotionName() + + fun isRoot() : Boolean { + return predecessors.isEmpty() + } + + fun calculateDepth(excludeNodes: ArrayList, currentDepth: Int) { + maxDepth = max(maxDepth, currentDepth) + excludeNodes.add(this) + successors.filter { !excludeNodes.contains(it) }.forEach { it.calculateDepth(excludeNodes,currentDepth+1) } + } + + private fun getBasePromotionName(): String { + val nameWithoutBrackets = promotion.name.replace("[", "").replace("]", "") + val level = when { + nameWithoutBrackets.endsWith(" I") -> 1 + nameWithoutBrackets.endsWith(" II") -> 2 + nameWithoutBrackets.endsWith(" III") -> 3 + else -> 0 + } + return nameWithoutBrackets.dropLast(if (level == 0) 0 else level + 1) + } + + class CustomComparator( + val baseNode: PromotionNode + ) : Comparator { + override fun compare(a: PromotionNode, b: PromotionNode): Int { + val baseName = baseNode.baseName + val aName = a.baseName + val bName = b.baseName + return when (aName) { + baseName -> -1 + bName -> 0 + else -> 1 + } + } + } + +} + +class PromotionButton( + val node: PromotionNode, + val isPickable: Boolean = true, + val isPromoted: Boolean = false + +) : BorderedTable( + path="PromotionScreen/PromotionButton", + defaultInner = BaseScreen.skinStrings.roundedEdgeRectangleMidShape, + defaultBorder = BaseScreen.skinStrings.roundedEdgeRectangleMidBorderShape, + borderColor = Color.WHITE.cpy(), + innerColor = Color.BLACK, + borderSize = 5f +) { + + var isSelected = false + val label = node.promotion.name.toLabel().apply { + wrap = false + setAlignment(Align.left) + setEllipsis(true) + } + + init { + + pad(5f) + align(Align.left) + add(ImageGetter.getPromotionIcon(node.promotion.name)).padRight(10f) + add(label).left().maxWidth(130f) + + updateColor() + } + + fun updateColor() { + + val color = when { + isSelected -> PromotionPickerScreen.Selected + isPickable -> PromotionPickerScreen.Pickable + isPromoted -> PromotionPickerScreen.Promoted + else -> PromotionPickerScreen.Default + } + setBackgroundColor(color) + + val textColor = when { + isSelected -> Color.WHITE + isPromoted -> PromotionPickerScreen.Promoted.cpy().darken(0.8f) + else -> Color.WHITE + } + label.setFontColor(textColor) + } + +} class PromotionPickerScreen(val unit: MapUnit) : PickerScreen(), RecreateOnResize { - private var selectedPromotion: Promotion? = null - private fun acceptPromotion(promotion: Promotion?) { + companion object Colors { + val Default:Color = Color.BLACK + val Selected:Color = colorFromRGB(72, 147, 175) + val Promoted:Color = colorFromRGB(255, 215, 0).darken(0.2f) + val Pickable:Color = colorFromRGB(28, 80, 0) + val Prerequisite:Color = colorFromRGB(14, 92, 86) + } + + private val promotionsTable = Table() + private val promotionToButton = LinkedHashMap() + private var selectedPromotion: PromotionButton? = null + private var lines = ArrayList() + + private fun acceptPromotion(node: PromotionNode?) { // if user managed to click disabled button, still do nothing - if (promotion == null) return + if (node == null) return - unit.promotions.addPromotion(promotion.name) - if (unit.promotions.canBePromoted()) - game.replaceCurrentScreen(recreate()) - else - game.popScreen() + unit.promotions.addPromotion(node.promotion.name) + game.replaceCurrentScreen(recreate()) } init { @@ -36,7 +155,8 @@ class PromotionPickerScreen(val unit: MapUnit) : PickerScreen(), RecreateOnResiz rightSideButton.setText("Pick promotion".tr()) rightSideButton.onClick(UncivSound.Promote) { - acceptPromotion(selectedPromotion) + if (selectedPromotion?.isPickable == true) + acceptPromotion(selectedPromotion?.node) } val canBePromoted = unit.promotions.canBePromoted() @@ -53,8 +173,6 @@ class PromotionPickerScreen(val unit: MapUnit) : PickerScreen(), RecreateOnResiz val promotionsForUnitType = unit.civInfo.gameInfo.ruleSet.unitPromotions.values.filter { it.unitTypes.contains(unitType.name) || unit.promotions.promotions.contains(it.name) } - val unitAvailablePromotions = unit.promotions.getAvailablePromotions() - //Always allow the user to rename the unit as many times as they like. val renameButton = "Choose name for [${unit.name}]".toTextButton() renameButton.isEnabled = true @@ -70,57 +188,288 @@ class PromotionPickerScreen(val unit: MapUnit) : PickerScreen(), RecreateOnResiz ) } availablePromotionsGroup.add(renameButton) - availablePromotionsGroup.row() + topTable.add(availablePromotionsGroup).row() + fillTable(promotionsForUnitType) - val promotionsTable = Table() - val width = promotionsForUnitType.maxOf { it.column } +1 - val height = promotionsForUnitType.maxOf { it.row } +1 - val cellMatrix = ArrayList>() - for (y in 0..height) { + displayTutorial(TutorialTrigger.Experience) + } + + private fun fillTable(promotions: Collection) { + val map = LinkedHashMap() + + val availablePromotions = unit.promotions.getAvailablePromotions() + + val canBePromoted = unit.promotions.canBePromoted() + val canChangeState = game.worldScreen!!.canChangeState + + // Create nodes + // Pass 1 - create nodes for all promotions + for (promotion in promotions) + map[promotion.name] = PromotionNode(promotion) + + // Pass 2 - remove nodes which are unreachable (dependent only on absent promotions) + for (promotion in promotions) { + if (promotion.prerequisites.isNotEmpty()) { + val isReachable = promotion.prerequisites.any { map.containsKey(it) } + if (!isReachable) + map.remove(promotion.name) + } + } + + // Pass 3 - fill nodes successors/predecessors, based on promotions prerequisites + for (node in map.values) { + for (prerequisiteName in node.promotion.prerequisites) { + val prerequisiteNode = map[prerequisiteName] + if (prerequisiteNode != null) { + node.predecessors.add(prerequisiteNode) + prerequisiteNode.successors.add(node) + // Prerequisite has the same base name -> +1 more level + if (prerequisiteNode.baseName == node.baseName) + prerequisiteNode.levels += 1 + } + } + } + + // Traverse each root node tree and calculate max possible depths of each node + for (node in map.values) { + if (node.isRoot()) + node.calculateDepth(arrayListOf(node), 0) + } + + // For each non-root node remove all predecessors except the one with the least max depth. + // This is needed to compactify trees and remove circular dependencies (A -> B -> C -> A) + for (node in map.values) { + if (node.isRoot()) + continue + + // Choose best predecessor - the one with less depth + var best: PromotionNode? = null + for (predecessor in node.predecessors) { + if (best == null) + best = predecessor + else + best = if (predecessor.maxDepth < best.maxDepth) predecessor else best + } + + // Remove everything else, leave only best + for (predecessor in node.predecessors) + predecessor.successors.remove(node) + node.predecessors.clear() + node.predecessors.add(best!!) + best.successors.add(node) + } + + // Sort nodes successors so promotions with same base name go first + for (node in map.values) { + Log.debug("MYTAG: Node ${node.promotion.name} depth: ${node.maxDepth}") + node.successors.sortWith(PromotionNode.CustomComparator(node)) + } + + // Create cell matrix + val maxColumns = map.size + 1 + val maxRows = map.size + 1 + + val cellMatrix = ArrayList>>() + for (y in 0..maxRows) { cellMatrix.add(ArrayList()) - for (x in 0..width*2) { - val cell = promotionsTable.add(Table()) - cellMatrix[y].add(cell.actor) + for (x in 0..maxColumns) { + val cell = promotionsTable.add() + cellMatrix[y].add(cell) } promotionsTable.row() } - for (promotion in promotionsForUnitType) { - if (promotion.hasUnique(UniqueType.OneTimeUnitHeal) && unit.health == 100) continue - val isPromotionAvailable = promotion in unitAvailablePromotions - val unitHasPromotion = unit.promotions.promotions.contains(promotion.name) - - val selectPromotionButton = PickerPane.getPickerOptionButton(ImageGetter.getPromotionIcon(promotion.name), promotion.name) - selectPromotionButton.isEnabled = true - selectPromotionButton.onClick { - val enable = canBePromoted && isPromotionAvailable && !unitHasPromotion && canChangeState - selectedPromotion = if (enable) promotion else null - rightSideButton.isEnabled = enable - rightSideButton.setText(promotion.name.tr()) - - descriptionLabel.setText(updateDescriptionLabel(promotion.getDescription(promotionsForUnitType))) + /** Check whether cell is inhabited by actor already */ + fun isTherePlace(row: Int, col: Int, levels: Int) : Boolean { + for (i in 0 until levels) { + if (cellMatrix[row][col+i].actor != null) + return false } - - val group = cellMatrix[promotion.row][promotion.column*2] - group.pad(5f) - group.add(selectPromotionButton) - - if (canPromoteNow && isPromotionAvailable) { - val pickNow = "Pick now!".toLabel() - pickNow.setAlignment(Align.center) - pickNow.onClick { - acceptPromotion(promotion) - } - cellMatrix[promotion.row][promotion.column*2+1].add(pickNow).padLeft(10f).fillY() - } - else if (unitHasPromotion) selectPromotionButton.color = Color.GREEN - else selectPromotionButton.color= Color.GRAY + return true } - availablePromotionsGroup.add(promotionsTable) - topTable.add(availablePromotionsGroup) - displayTutorial(TutorialTrigger.Experience) + /** Recursively place buttons for node and it's successors into free cells */ + fun placeButton(col: Int, row: Int, node: PromotionNode) : Int { + val name = node.promotion.name + // If promotion button not yet placed + if (promotionToButton[name] == null) { + // If place is free - we place button + if (isTherePlace(row, col, node.levels)) { + val cell = cellMatrix[row][col] + val isPromotionAvailable = node.promotion in availablePromotions + val hasPromotion = unit.promotions.promotions.contains(name) + val isPickable = canBePromoted && isPromotionAvailable && !hasPromotion && canChangeState + val button = getButton(promotions, node, isPickable, hasPromotion) + promotionToButton[name] = button + cell.setActor(button) + cell.pad(5f) + cell.padRight(20f) + cell.minWidth(190f) + cell.maxWidth(190f) + } + // If place is not free - try to find another in the next row + else { + return placeButton(col, row+1, node) + } + } + + // Filter successors who haven't been placed yet (to avoid circular dependencies) + // and try to place them in the next column. + // Return the max row this whole tree ever reached. + return node.successors.filter { + !promotionToButton.containsKey(it.promotion.name) + }.map { + placeButton(col+1, row, it) + }.maxOfOrNull { it }?: row + } + + // Build each tree starting from root nodes + var row = 0 + for (node in map.values) { + if (node.isRoot()) { + row = placeButton(0, row, node) + // Each root tree should start from a completely empty row. + row += 1 + } + } + + topTable.add(promotionsTable) + + addConnectingLines() + + } + + private fun getButton(allPromotions: Collection, node: PromotionNode, + isPickable: Boolean = true, isPromoted: Boolean = false) : PromotionButton { + + val button = PromotionButton( + node = node, + isPromoted = isPromoted, + isPickable = isPickable + ) + + button.onClick { + selectedPromotion?.isSelected = false + selectedPromotion?.updateColor() + selectedPromotion = button + button.isSelected = true + button.updateColor() + + for (btn in promotionToButton.values) + btn.updateColor() + button.node.promotion.prerequisites.forEach { promotionToButton[it]?.apply { + if (!this.isPromoted) + setBackgroundColor(Prerequisite) }} + + rightSideButton.isEnabled = isPickable + rightSideButton.setText(node.promotion.name.tr()) + descriptionLabel.setText(updateDescriptionLabel(node.promotion.getDescription(allPromotions))) + + addConnectingLines() + } + + return button + } + + private fun addConnectingLines() { + promotionsTable.pack() + scrollPane.updateVisualScroll() + + for (line in lines) line.remove() + lines.clear() + + for (button in promotionToButton.values) { + for (prerequisite in button.node.promotion.prerequisites) { + val prerequisiteButton = promotionToButton[prerequisite] ?: continue + + var buttonCoords = Vector2(0f, button.height / 2) + button.localToStageCoordinates(buttonCoords) + promotionsTable.stageToLocalCoordinates(buttonCoords) + + var prerequisiteCoords = Vector2(prerequisiteButton.width, prerequisiteButton.height / 2) + prerequisiteButton.localToStageCoordinates(prerequisiteCoords) + promotionsTable.stageToLocalCoordinates(prerequisiteCoords) + + val lineColor = when { + button.isSelected -> Selected + prerequisiteButton.node.baseName == button.node.baseName -> Color.WHITE.cpy() + else -> Color.CLEAR + } + val lineSize = when { + button.isSelected -> 4f + else -> 2f + } + + if (buttonCoords.x < prerequisiteCoords.x) { + val temp = buttonCoords.cpy() + buttonCoords = prerequisiteCoords + prerequisiteCoords = temp + } + + + if (buttonCoords.y != prerequisiteCoords.y) { + + val deltaX = buttonCoords.x - prerequisiteCoords.x + val deltaY = buttonCoords.y - prerequisiteCoords.y + val halfLength = deltaX / 2f + + val line = ImageGetter.getWhiteDot().apply { + width = halfLength+lineSize/2 + height = lineSize + x = prerequisiteCoords.x + y = prerequisiteCoords.y - lineSize / 2 + } + val line1 = ImageGetter.getWhiteDot().apply { + width = halfLength + lineSize/2 + height = lineSize + x = buttonCoords.x - width + y = buttonCoords.y - lineSize / 2 + } + val line2 = ImageGetter.getWhiteDot().apply { + width = lineSize + height = abs(deltaY) + x = buttonCoords.x - halfLength - lineSize / 2 + y = buttonCoords.y + (if (deltaY > 0f) -height-lineSize/2 else lineSize/2) + } + + line.color = lineColor + line1.color = lineColor + line2.color = lineColor + + promotionsTable.addActor(line) + promotionsTable.addActor(line1) + promotionsTable.addActor(line2) + + line.toBack() + line1.toBack() + line2.toBack() + + lines.add(line) + lines.add(line1) + lines.add(line2) + + } else { + + val line = ImageGetter.getWhiteDot().apply { + width = buttonCoords.x - prerequisiteCoords.x + height = lineSize + x = prerequisiteCoords.x + y = prerequisiteCoords.y - lineSize / 2 + } + line.color = lineColor + promotionsTable.addActor(line) + line.toBack() + lines.add(line) + + } + } + } + + for (line in lines) { + if (line.color == Selected) + line.zIndex = lines.size + } } private fun setScrollY(scrollY: Float) { @@ -130,17 +479,13 @@ class PromotionPickerScreen(val unit: MapUnit) : PickerScreen(), RecreateOnResiz } private fun updateDescriptionLabel(): String { - var newDescriptionText = unit.displayName().tr() - - return newDescriptionText.toString() + return unit.displayName().tr() } private fun updateDescriptionLabel(promotionDescription: String): String { var newDescriptionText = unit.displayName().tr() - newDescriptionText += "\n" + promotionDescription - - return newDescriptionText.toString() + return newDescriptionText } override fun recreate(): BaseScreen { diff --git a/core/src/com/unciv/ui/pickerscreens/TechButton.kt b/core/src/com/unciv/ui/pickerscreens/TechButton.kt index 80780ac7ae..dffd8d1adf 100644 --- a/core/src/com/unciv/ui/pickerscreens/TechButton.kt +++ b/core/src/com/unciv/ui/pickerscreens/TechButton.kt @@ -45,7 +45,7 @@ class TechButton(techName:String, private val techManager: TechManager, isWorldS init { touchable = Touchable.enabled background = BaseScreen.skinStrings.getUiBackground("TechPickerScreen/TechButton", BaseScreen.skinStrings.roundedEdgeRectangleMidShape, - tintColor = Color.WHITE.darken(0.3f)) + tintColor = Color.WHITE.cpy().darken(0.3f)) bg.toBack() addActor(bg) @@ -65,9 +65,9 @@ class TechButton(techName:String, private val techManager: TechManager, isWorldS val percentWillBeComplete = (techCost - (remainingTech-techThisTurn)) / techCost.toFloat() val progressBar = ImageGetter.ProgressBar(2f, 48f, true) .setBackground(Color.WHITE) - .setSemiProgress(Color.BLUE.brighten(0.3f), percentWillBeComplete) - .setProgress(Color.BLUE.darken(0.5f), percentComplete) - add(progressBar.addBorder(1f, Color.GRAY)).padLeft(0f).padRight(5f) + .setSemiProgress(Color.BLUE.cpy().brighten(0.3f), percentWillBeComplete) + .setProgress(Color.BLUE.cpy().darken(0.5f), percentComplete) + add(progressBar.addBorder(1f, Color.GRAY.cpy())).padLeft(0f).padRight(5f) } val rightSide = Table() diff --git a/core/src/com/unciv/ui/pickerscreens/TechPickerScreen.kt b/core/src/com/unciv/ui/pickerscreens/TechPickerScreen.kt index 57a4ea41ef..5b210db509 100644 --- a/core/src/com/unciv/ui/pickerscreens/TechPickerScreen.kt +++ b/core/src/com/unciv/ui/pickerscreens/TechPickerScreen.kt @@ -139,7 +139,7 @@ class TechPickerScreen( val color = when { civTech.era.name == era -> queuedTechColor civInfo.gameInfo.ruleSet.eras[era]!!.eraNumber < civTech.era.eraNumber -> colorFromRGB(255, 175, 0) - else -> Color.BLACK + else -> Color.BLACK.cpy() } val table1 = Table().pad(1f) @@ -197,7 +197,7 @@ class TechPickerScreen( tempTechsToResearch.firstOrNull() == techName && !freeTechPick -> currentTechColor researchableTechs.contains(techName) -> researchableTechColor tempTechsToResearch.contains(techName) -> queuedTechColor - else -> Color.BLACK + else -> Color.BLACK.cpy() }) if (civTech.isResearched(techName) && techName != Constants.futureTech) { @@ -236,7 +236,7 @@ class TechPickerScreen( eraLabel.localToStageCoordinates(coords) techTable.stageToLocalCoordinates(coords) val line = ImageGetter.getLine(coords.x-1f, coords.y, coords.x-1f, coords.y - 1000f, 1f) - line.color = Color.GRAY.apply { a = 0.6f } + line.color = Color.GRAY.cpy().apply { a = 0.6f } line.toBack() techTable.addActor(line) lines.add(line) @@ -263,10 +263,10 @@ class TechPickerScreen( techTable.stageToLocalCoordinates(prerequisiteCoords) val lineColor = when { - civTech.isResearched(tech.name) && !tech.isContinuallyResearchable() -> Color.WHITE + civTech.isResearched(tech.name) && !tech.isContinuallyResearchable() -> Color.WHITE.cpy() civTech.isResearched(prerequisite) -> researchableTechColor tempTechsToResearch.contains(tech.name) -> currentTechColor - else -> Color.WHITE + else -> Color.WHITE.cpy() } val lineSize = when { diff --git a/core/src/com/unciv/ui/utils/BorderedTable.kt b/core/src/com/unciv/ui/utils/BorderedTable.kt index 8ce04ed5ad..89121d6414 100644 --- a/core/src/com/unciv/ui/utils/BorderedTable.kt +++ b/core/src/com/unciv/ui/utils/BorderedTable.kt @@ -13,12 +13,12 @@ open class BorderedTable( val defaultBorder: String = BaseScreen.skinStrings.rectangleWithOutlineShape, val borderColor: Color = Color.WHITE, val innerColor: Color = Color.BLACK, - val borderSize: Float = 5f, + var borderSize: Float = 5f, val borderOnTop: Boolean = false ) : Table() { - var bgBorder: Image = Image(BaseScreen.skinStrings.getUiBackground(path, defaultBorder, borderColor)) - var bgInner: Image = Image(BaseScreen.skinStrings.getUiBackground(path, defaultInner, innerColor)) + var bgInner: Image = Image(BaseScreen.skinStrings.getUiBackground(path, defaultInner, innerColor.cpy())) + var bgBorder: Image = Image(BaseScreen.skinStrings.getUiBackground(path + "Border", defaultBorder, borderColor.cpy())) init { if (borderSize != 0f) @@ -40,7 +40,7 @@ open class BorderedTable( fun setBackgroundColor(color: Color) { bgInner.remove() - bgInner = Image(BaseScreen.skinStrings.getUiBackground(path, defaultInner, color)) + bgInner = Image(BaseScreen.skinStrings.getUiBackground(path, defaultInner, color.cpy())) addActor(bgInner) if (borderSize != 0f) { if (borderOnTop) diff --git a/core/src/com/unciv/ui/worldscreen/unit/UnitTable.kt b/core/src/com/unciv/ui/worldscreen/unit/UnitTable.kt index c50509fad8..b855d02fb3 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/UnitTable.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/UnitTable.kt @@ -189,7 +189,12 @@ class UnitTable(val worldScreen: WorldScreen) : Table() { } if (!unit.isCivilian()) { - unitDescriptionTable.add("XP".tr()) + unitDescriptionTable.add("XP".tr().toLabel().apply { + onClick { + if (selectedUnit == null) return@onClick + worldScreen.game.pushScreen(PromotionPickerScreen(unit)) + } + }) unitDescriptionTable.add(unit.promotions.XP.toString() + "/" + unit.promotions.xpForNextPromotion()) } diff --git a/docs/Modders/Creating-a-UI-skin.md b/docs/Modders/Creating-a-UI-skin.md index d68de26d0f..e3d458b517 100644 --- a/docs/Modders/Creating-a-UI-skin.md +++ b/docs/Modders/Creating-a-UI-skin.md @@ -34,6 +34,7 @@ These shapes are used all over Unciv and can be replaced to make a lot of UI ele | Directory | Name | Default shape | Image | |---|:---:|:---:|---| +| | Border | null | | | CityScreen/ | CityPickerTable | roundedEdgeRectangle | | | CityScreen/CitizenManagementTable/ | AvoidCell | null | | | CityScreen/CitizenManagementTable/ | FocusCell | null | |