mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-11 16:29:48 +07:00
Rework of PromotionPicker UI (#8325)
Co-authored-by: tunerzinc@gmail.com <vfylfhby>
This commit is contained in:
@ -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<String, BorderedTable>()
|
||||
@ -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)
|
||||
|
@ -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<PromotionNode> = ArrayList()
|
||||
val predecessors: ArrayList<PromotionNode> = ArrayList()
|
||||
|
||||
val baseName = getBasePromotionName()
|
||||
|
||||
fun isRoot() : Boolean {
|
||||
return predecessors.isEmpty()
|
||||
}
|
||||
|
||||
fun calculateDepth(excludeNodes: ArrayList<PromotionNode>, 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<PromotionNode> {
|
||||
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<String, PromotionButton>()
|
||||
private var selectedPromotion: PromotionButton? = null
|
||||
private var lines = ArrayList<Image>()
|
||||
|
||||
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())
|
||||
unit.promotions.addPromotion(node.promotion.name)
|
||||
game.replaceCurrentScreen(recreate())
|
||||
else
|
||||
game.popScreen()
|
||||
}
|
||||
|
||||
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<ArrayList<Table>>()
|
||||
for (y in 0..height) {
|
||||
displayTutorial(TutorialTrigger.Experience)
|
||||
}
|
||||
|
||||
private fun fillTable(promotions: Collection<Promotion>) {
|
||||
val map = LinkedHashMap<String, PromotionNode>()
|
||||
|
||||
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<ArrayList<Cell<Actor>>>()
|
||||
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
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
val group = cellMatrix[promotion.row][promotion.column*2]
|
||||
group.pad(5f)
|
||||
group.add(selectPromotionButton)
|
||||
/** 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)
|
||||
}
|
||||
}
|
||||
|
||||
if (canPromoteNow && isPromotionAvailable) {
|
||||
val pickNow = "Pick now!".toLabel()
|
||||
pickNow.setAlignment(Align.center)
|
||||
pickNow.onClick {
|
||||
acceptPromotion(promotion)
|
||||
// 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
|
||||
}
|
||||
cellMatrix[promotion.row][promotion.column*2+1].add(pickNow).padLeft(10f).fillY()
|
||||
}
|
||||
else if (unitHasPromotion) selectPromotionButton.color = Color.GREEN
|
||||
else selectPromotionButton.color= Color.GRAY
|
||||
}
|
||||
availablePromotionsGroup.add(promotionsTable)
|
||||
topTable.add(availablePromotionsGroup)
|
||||
|
||||
displayTutorial(TutorialTrigger.Experience)
|
||||
// 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<Promotion>, 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 {
|
||||
|
@ -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()
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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())
|
||||
}
|
||||
|
||||
|
@ -34,6 +34,7 @@ These shapes are used all over Unciv and can be replaced to make a lot of UI ele
|
||||
<!--- DO NOT REMOVE OR MODIFY THIS LINE UI_ELEMENT_TABLE_REGION -->
|
||||
| Directory | Name | Default shape | Image |
|
||||
|---|:---:|:---:|---|
|
||||
| | Border | null | |
|
||||
| CityScreen/ | CityPickerTable | roundedEdgeRectangle | |
|
||||
| CityScreen/CitizenManagementTable/ | AvoidCell | null | |
|
||||
| CityScreen/CitizenManagementTable/ | FocusCell | null | |
|
||||
|
Reference in New Issue
Block a user