From bb332637d111b15b3318b31cbd29cd06d429f5d7 Mon Sep 17 00:00:00 2001 From: SomeTroglodyte <63000004+SomeTroglodyte@users.noreply.github.com> Date: Tue, 28 Mar 2023 06:35:56 +0200 Subject: [PATCH] Fix exploit allowing promotion with 0 movement (#9055) * Close exploit allowing promotion with 0 movement * PromotionPickerScreen linting --- .../pickerscreens/PromotionPickerScreen.kt | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/core/src/com/unciv/ui/screens/pickerscreens/PromotionPickerScreen.kt b/core/src/com/unciv/ui/screens/pickerscreens/PromotionPickerScreen.kt index a008250282..fd4093deff 100644 --- a/core/src/com/unciv/ui/screens/pickerscreens/PromotionPickerScreen.kt +++ b/core/src/com/unciv/ui/screens/pickerscreens/PromotionPickerScreen.kt @@ -62,7 +62,7 @@ class PromotionNode(val promotion: Promotion) { } class CustomComparator( - val baseNode: PromotionNode + private val baseNode: PromotionNode ) : Comparator { override fun compare(a: PromotionNode, b: PromotionNode): Int { val baseName = baseNode.baseName @@ -78,15 +78,15 @@ class PromotionNode(val promotion: Promotion) { } -class PromotionButton( +private class PromotionButton( val node: PromotionNode, val isPickable: Boolean = true, val isPromoted: Boolean = false - ) : BorderedTable( path="PromotionScreen/PromotionButton", defaultBgShape = BaseScreen.skinStrings.roundedEdgeRectangleMidShape, - defaultBgBorder = BaseScreen.skinStrings.roundedEdgeRectangleMidBorderShape) { + defaultBgBorder = BaseScreen.skinStrings.roundedEdgeRectangleMidBorderShape +) { var isSelected = false val label = node.promotion.name.toLabel().apply { @@ -132,11 +132,11 @@ class PromotionButton( class PromotionPickerScreen(val unit: MapUnit) : PickerScreen(), RecreateOnResize { 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) + 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() @@ -144,28 +144,25 @@ class PromotionPickerScreen(val unit: MapUnit) : PickerScreen(), RecreateOnResiz 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 (node == null) return - - unit.promotions.addPromotion(node.promotion.name) - game.replaceCurrentScreen(recreate()) - } + // [acceptPromotion] will [recreate] the screen, so these are constant for this picker's lifetime + private val canChangeState = GUI.isAllowedChangeState() + private val canBePromoted = unit.promotions.canBePromoted() + private val canPromoteNow = canChangeState && canBePromoted && + unit.currentMovement > 0 && unit.attacksThisTurn == 0 init { setDefaultCloseAction() - rightSideButton.setText("Pick promotion".tr()) - rightSideButton.onClick(UncivSound.Promote) { - if (selectedPromotion?.isPickable == true) - acceptPromotion(selectedPromotion?.node) + if (canPromoteNow) { + rightSideButton.setText("Pick promotion".tr()) + rightSideButton.onClick(UncivSound.Promote) { + if (selectedPromotion?.isPickable == true) + acceptPromotion(selectedPromotion?.node) + } + } else { + rightSideButton.isVisible = false } - val canBePromoted = unit.promotions.canBePromoted() - val canChangeState = GUI.isAllowedChangeState() - val canPromoteNow = canBePromoted && canChangeState - && unit.currentMovement > 0 && unit.attacksThisTurn == 0 - rightSideButton.isEnabled = canPromoteNow descriptionLabel.setText(updateDescriptionLabel()) val availablePromotionsGroup = Table() @@ -197,14 +194,19 @@ class PromotionPickerScreen(val unit: MapUnit) : PickerScreen(), RecreateOnResiz displayTutorial(TutorialTrigger.Experience) } + private fun acceptPromotion(node: PromotionNode?) { + // if user managed to click disabled button, still do nothing + if (node == null) return + + unit.promotions.addPromotion(node.promotion.name) + game.replaceCurrentScreen(recreate()) + } + private fun fillTable(promotions: Collection) { val map = LinkedHashMap() val availablePromotions = unit.promotions.getAvailablePromotions() - val canBePromoted = unit.promotions.canBePromoted() - val canChangeState = GUI.isAllowedChangeState() - // Create nodes // Pass 1 - create nodes for all promotions for (promotion in promotions) @@ -248,10 +250,8 @@ class PromotionPickerScreen(val unit: MapUnit) : PickerScreen(), RecreateOnResiz // Choose best predecessor - the one with less depth var best: PromotionNode? = null for (predecessor in node.predecessors) { - if (best == null) + if (best == null || predecessor.maxDepth < best.maxDepth) best = predecessor - else - best = if (predecessor.maxDepth < best.maxDepth) predecessor else best } // Remove everything else, leave only best @@ -300,7 +300,7 @@ class PromotionPickerScreen(val unit: MapUnit) : PickerScreen(), RecreateOnResiz 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 isPickable = canPromoteNow && isPromotionAvailable && !hasPromotion val button = getButton(promotions, node, isPickable, hasPromotion) promotionToButton[name] = button cell.setActor(button)