mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-12 08:49:22 +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.abs
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
object Colors {
|
object PolicyColors {
|
||||||
|
|
||||||
val policyPickable = colorFromRGB(47,67,92).darken(0.3f)
|
val policyPickable = colorFromRGB(47,67,92).darken(0.3f)
|
||||||
val policyNotPickable = colorFromRGB(20, 20, 20)
|
val policyNotPickable = colorFromRGB(20, 20, 20)
|
||||||
@ -48,14 +48,6 @@ object Colors {
|
|||||||
val branchAdopted = colorFromRGB(100, 90, 10).darken(0.5f)
|
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 {
|
fun Policy.isAdopted() : Boolean {
|
||||||
val worldScreen = UncivGame.Current.worldScreen ?: return false
|
val worldScreen = UncivGame.Current.worldScreen ?: return false
|
||||||
val viewingCiv = worldScreen.viewingCiv
|
val viewingCiv = worldScreen.viewingCiv
|
||||||
@ -135,21 +127,21 @@ class PolicyButton(val policy: Policy, size: Float = 30f) : BorderedTable(
|
|||||||
when {
|
when {
|
||||||
|
|
||||||
isSelected && isPickable -> {
|
isSelected && isPickable -> {
|
||||||
setBackgroundColor(Colors.policySelected)
|
setBackgroundColor(PolicyColors.policySelected)
|
||||||
}
|
}
|
||||||
|
|
||||||
isPickable -> {
|
isPickable -> {
|
||||||
setBackgroundColor(Colors.policyPickable)
|
setBackgroundColor(PolicyColors.policyPickable)
|
||||||
}
|
}
|
||||||
|
|
||||||
isAdopted -> {
|
isAdopted -> {
|
||||||
icon.color = Color.GOLD
|
icon.color = Color.GOLD.cpy()
|
||||||
setBackgroundColor(colorFromRGB(10,90,100).darken(0.8f))
|
setBackgroundColor(colorFromRGB(10,90,100).darken(0.8f))
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
icon.color.a = 0.2f
|
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)
|
class PolicyPickerScreen(val worldScreen: WorldScreen, civInfo: CivilizationInfo = worldScreen.viewingCiv)
|
||||||
: PickerScreen(), RecreateOnResize {
|
: PickerScreen(), RecreateOnResize {
|
||||||
|
|
||||||
|
object Sizes {
|
||||||
|
val paddingVertical = 10f
|
||||||
|
val paddingHorizontal = 20f
|
||||||
|
val paddingBetweenHor = 10f
|
||||||
|
val paddingBetweenVer = 20f
|
||||||
|
val iconSize = 50f
|
||||||
|
}
|
||||||
|
|
||||||
internal val viewingCiv: CivilizationInfo = civInfo
|
internal val viewingCiv: CivilizationInfo = civInfo
|
||||||
|
|
||||||
private var policyNameToButton = HashMap<String, BorderedTable>()
|
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)
|
val prefHeight = Sizes.paddingVertical*2 + Sizes.iconSize*maxRow + Sizes.paddingBetweenVer*(maxRow - 1)
|
||||||
|
|
||||||
// Main table
|
// Main table
|
||||||
val colorBg = if (branch.isAdopted()) Colors.branchAdopted else Colors.branchNotAdopted
|
val colorBg = if (branch.isAdopted()) PolicyColors.branchAdopted else PolicyColors.branchNotAdopted
|
||||||
val branchGroup = BorderedTable(innerColor = colorBg)
|
val branchGroup = BorderedTable(
|
||||||
|
path="PolicyScree/PolicyBranchBackground",
|
||||||
|
innerColor = colorBg)
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
val header = getBranchHeader(branch)
|
val header = getBranchHeader(branch)
|
||||||
@ -343,7 +345,7 @@ class PolicyPickerScreen(val worldScreen: WorldScreen, civInfo: CivilizationInfo
|
|||||||
for ((k, v) in conditionals) {
|
for ((k, v) in conditionals) {
|
||||||
warning += "• " + k.text.fillPlaceholders(v.joinToString()).tr() + "\n"
|
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.setFillParent(false)
|
||||||
warningLabel.setAlignment(Align.topLeft)
|
warningLabel.setAlignment(Align.topLeft)
|
||||||
warningLabel.wrap = true
|
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) {
|
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
|
val lineSize = 2f
|
||||||
|
|
||||||
if (policyX != prereqX) {
|
if (policyX != prereqX) {
|
||||||
@ -554,7 +556,9 @@ class PolicyPickerScreen(val worldScreen: WorldScreen, civInfo: CivilizationInfo
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getBranchHeader(branch: PolicyBranch): Table {
|
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)
|
header.pad(5f)
|
||||||
|
|
||||||
val table = Table()
|
val table = Table()
|
||||||
@ -582,7 +586,7 @@ class PolicyPickerScreen(val worldScreen: WorldScreen, civInfo: CivilizationInfo
|
|||||||
val text: String
|
val text: String
|
||||||
|
|
||||||
val lockIcon = ImageGetter.getImage("OtherIcons/LockSmall")
|
val lockIcon = ImageGetter.getImage("OtherIcons/LockSmall")
|
||||||
.apply { color = Color.WHITE }.toGroup(15f)
|
.apply { color = Color.WHITE.cpy() }.toGroup(15f)
|
||||||
lockIcon.isVisible = false
|
lockIcon.isVisible = false
|
||||||
if (viewingCiv.policies.isAdopted(branch.name)) {
|
if (viewingCiv.policies.isAdopted(branch.name)) {
|
||||||
policy = branch.policies.last()
|
policy = branch.policies.last()
|
||||||
@ -599,18 +603,19 @@ class PolicyPickerScreen(val worldScreen: WorldScreen, civInfo: CivilizationInfo
|
|||||||
label.setAlignment(Align.center)
|
label.setAlignment(Align.center)
|
||||||
|
|
||||||
val color = when {
|
val color = when {
|
||||||
policy.isPickable() -> Colors.policyPickable
|
policy.isPickable() -> PolicyColors.policyPickable
|
||||||
viewingCiv.policies.isAdopted(policy.name) -> {
|
viewingCiv.policies.isAdopted(policy.name) -> {
|
||||||
label.color = colorFromRGB(150, 70, 40)
|
label.color = colorFromRGB(150, 70, 40)
|
||||||
Colors.branchCompleted
|
PolicyColors.branchCompleted
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
lockIcon.isVisible = true
|
lockIcon.isVisible = true
|
||||||
label.color.a = 0.5f
|
label.color.a = 0.5f
|
||||||
Colors.policyNotPickable}
|
PolicyColors.policyNotPickable}
|
||||||
}
|
}
|
||||||
|
|
||||||
val table = BorderedTable(
|
val table = BorderedTable(
|
||||||
|
path="PolicyScreen/PolicyBranchAdoptButton",
|
||||||
defaultInner = skinStrings.roundedEdgeRectangleSmallShape,
|
defaultInner = skinStrings.roundedEdgeRectangleSmallShape,
|
||||||
defaultBorder = skinStrings.roundedEdgeRectangleSmallShape,
|
defaultBorder = skinStrings.roundedEdgeRectangleSmallShape,
|
||||||
innerColor = color, borderSize = 2f)
|
innerColor = color, borderSize = 2f)
|
||||||
|
@ -1,34 +1,153 @@
|
|||||||
package com.unciv.ui.pickerscreens
|
package com.unciv.ui.pickerscreens
|
||||||
|
|
||||||
import com.badlogic.gdx.graphics.Color
|
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.scenes.scene2d.ui.Table
|
||||||
import com.badlogic.gdx.utils.Align
|
import com.badlogic.gdx.utils.Align
|
||||||
import com.unciv.logic.map.MapUnit
|
import com.unciv.logic.map.MapUnit
|
||||||
import com.unciv.models.TutorialTrigger
|
import com.unciv.models.TutorialTrigger
|
||||||
import com.unciv.models.UncivSound
|
import com.unciv.models.UncivSound
|
||||||
import com.unciv.models.ruleset.unique.UniqueType
|
|
||||||
import com.unciv.models.ruleset.unit.Promotion
|
import com.unciv.models.ruleset.unit.Promotion
|
||||||
import com.unciv.models.translations.tr
|
import com.unciv.models.translations.tr
|
||||||
import com.unciv.ui.images.ImageGetter
|
import com.unciv.ui.images.ImageGetter
|
||||||
import com.unciv.ui.utils.BaseScreen
|
import com.unciv.ui.utils.BaseScreen
|
||||||
|
import com.unciv.ui.utils.BorderedTable
|
||||||
import com.unciv.ui.utils.RecreateOnResize
|
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.isEnabled
|
||||||
import com.unciv.ui.utils.extensions.onClick
|
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.toLabel
|
||||||
import com.unciv.ui.utils.extensions.toTextButton
|
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 {
|
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 user managed to click disabled button, still do nothing
|
||||||
if (promotion == null) return
|
if (node == null) return
|
||||||
|
|
||||||
unit.promotions.addPromotion(promotion.name)
|
unit.promotions.addPromotion(node.promotion.name)
|
||||||
if (unit.promotions.canBePromoted())
|
game.replaceCurrentScreen(recreate())
|
||||||
game.replaceCurrentScreen(recreate())
|
|
||||||
else
|
|
||||||
game.popScreen()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
@ -36,7 +155,8 @@ class PromotionPickerScreen(val unit: MapUnit) : PickerScreen(), RecreateOnResiz
|
|||||||
|
|
||||||
rightSideButton.setText("Pick promotion".tr())
|
rightSideButton.setText("Pick promotion".tr())
|
||||||
rightSideButton.onClick(UncivSound.Promote) {
|
rightSideButton.onClick(UncivSound.Promote) {
|
||||||
acceptPromotion(selectedPromotion)
|
if (selectedPromotion?.isPickable == true)
|
||||||
|
acceptPromotion(selectedPromotion?.node)
|
||||||
}
|
}
|
||||||
|
|
||||||
val canBePromoted = unit.promotions.canBePromoted()
|
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 {
|
val promotionsForUnitType = unit.civInfo.gameInfo.ruleSet.unitPromotions.values.filter {
|
||||||
it.unitTypes.contains(unitType.name) || unit.promotions.promotions.contains(it.name)
|
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.
|
//Always allow the user to rename the unit as many times as they like.
|
||||||
val renameButton = "Choose name for [${unit.name}]".toTextButton()
|
val renameButton = "Choose name for [${unit.name}]".toTextButton()
|
||||||
renameButton.isEnabled = true
|
renameButton.isEnabled = true
|
||||||
@ -70,57 +188,288 @@ class PromotionPickerScreen(val unit: MapUnit) : PickerScreen(), RecreateOnResiz
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
availablePromotionsGroup.add(renameButton)
|
availablePromotionsGroup.add(renameButton)
|
||||||
availablePromotionsGroup.row()
|
|
||||||
|
|
||||||
|
topTable.add(availablePromotionsGroup).row()
|
||||||
|
fillTable(promotionsForUnitType)
|
||||||
|
|
||||||
val promotionsTable = Table()
|
displayTutorial(TutorialTrigger.Experience)
|
||||||
val width = promotionsForUnitType.maxOf { it.column } +1
|
}
|
||||||
val height = promotionsForUnitType.maxOf { it.row } +1
|
|
||||||
val cellMatrix = ArrayList<ArrayList<Table>>()
|
private fun fillTable(promotions: Collection<Promotion>) {
|
||||||
for (y in 0..height) {
|
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())
|
cellMatrix.add(ArrayList())
|
||||||
for (x in 0..width*2) {
|
for (x in 0..maxColumns) {
|
||||||
val cell = promotionsTable.add(Table())
|
val cell = promotionsTable.add()
|
||||||
cellMatrix[y].add(cell.actor)
|
cellMatrix[y].add(cell)
|
||||||
}
|
}
|
||||||
promotionsTable.row()
|
promotionsTable.row()
|
||||||
}
|
}
|
||||||
|
|
||||||
for (promotion in promotionsForUnitType) {
|
/** Check whether cell is inhabited by actor already */
|
||||||
if (promotion.hasUnique(UniqueType.OneTimeUnitHeal) && unit.health == 100) continue
|
fun isTherePlace(row: Int, col: Int, levels: Int) : Boolean {
|
||||||
val isPromotionAvailable = promotion in unitAvailablePromotions
|
for (i in 0 until levels) {
|
||||||
val unitHasPromotion = unit.promotions.promotions.contains(promotion.name)
|
if (cellMatrix[row][col+i].actor != null)
|
||||||
|
return false
|
||||||
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)))
|
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
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
|
|
||||||
}
|
}
|
||||||
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<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) {
|
private fun setScrollY(scrollY: Float) {
|
||||||
@ -130,17 +479,13 @@ class PromotionPickerScreen(val unit: MapUnit) : PickerScreen(), RecreateOnResiz
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun updateDescriptionLabel(): String {
|
private fun updateDescriptionLabel(): String {
|
||||||
var newDescriptionText = unit.displayName().tr()
|
return unit.displayName().tr()
|
||||||
|
|
||||||
return newDescriptionText.toString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateDescriptionLabel(promotionDescription: String): String {
|
private fun updateDescriptionLabel(promotionDescription: String): String {
|
||||||
var newDescriptionText = unit.displayName().tr()
|
var newDescriptionText = unit.displayName().tr()
|
||||||
|
|
||||||
newDescriptionText += "\n" + promotionDescription
|
newDescriptionText += "\n" + promotionDescription
|
||||||
|
return newDescriptionText
|
||||||
return newDescriptionText.toString()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun recreate(): BaseScreen {
|
override fun recreate(): BaseScreen {
|
||||||
|
@ -45,7 +45,7 @@ class TechButton(techName:String, private val techManager: TechManager, isWorldS
|
|||||||
init {
|
init {
|
||||||
touchable = Touchable.enabled
|
touchable = Touchable.enabled
|
||||||
background = BaseScreen.skinStrings.getUiBackground("TechPickerScreen/TechButton", BaseScreen.skinStrings.roundedEdgeRectangleMidShape,
|
background = BaseScreen.skinStrings.getUiBackground("TechPickerScreen/TechButton", BaseScreen.skinStrings.roundedEdgeRectangleMidShape,
|
||||||
tintColor = Color.WHITE.darken(0.3f))
|
tintColor = Color.WHITE.cpy().darken(0.3f))
|
||||||
|
|
||||||
bg.toBack()
|
bg.toBack()
|
||||||
addActor(bg)
|
addActor(bg)
|
||||||
@ -65,9 +65,9 @@ class TechButton(techName:String, private val techManager: TechManager, isWorldS
|
|||||||
val percentWillBeComplete = (techCost - (remainingTech-techThisTurn)) / techCost.toFloat()
|
val percentWillBeComplete = (techCost - (remainingTech-techThisTurn)) / techCost.toFloat()
|
||||||
val progressBar = ImageGetter.ProgressBar(2f, 48f, true)
|
val progressBar = ImageGetter.ProgressBar(2f, 48f, true)
|
||||||
.setBackground(Color.WHITE)
|
.setBackground(Color.WHITE)
|
||||||
.setSemiProgress(Color.BLUE.brighten(0.3f), percentWillBeComplete)
|
.setSemiProgress(Color.BLUE.cpy().brighten(0.3f), percentWillBeComplete)
|
||||||
.setProgress(Color.BLUE.darken(0.5f), percentComplete)
|
.setProgress(Color.BLUE.cpy().darken(0.5f), percentComplete)
|
||||||
add(progressBar.addBorder(1f, Color.GRAY)).padLeft(0f).padRight(5f)
|
add(progressBar.addBorder(1f, Color.GRAY.cpy())).padLeft(0f).padRight(5f)
|
||||||
}
|
}
|
||||||
|
|
||||||
val rightSide = Table()
|
val rightSide = Table()
|
||||||
|
@ -139,7 +139,7 @@ class TechPickerScreen(
|
|||||||
val color = when {
|
val color = when {
|
||||||
civTech.era.name == era -> queuedTechColor
|
civTech.era.name == era -> queuedTechColor
|
||||||
civInfo.gameInfo.ruleSet.eras[era]!!.eraNumber < civTech.era.eraNumber -> colorFromRGB(255, 175, 0)
|
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)
|
val table1 = Table().pad(1f)
|
||||||
@ -197,7 +197,7 @@ class TechPickerScreen(
|
|||||||
tempTechsToResearch.firstOrNull() == techName && !freeTechPick -> currentTechColor
|
tempTechsToResearch.firstOrNull() == techName && !freeTechPick -> currentTechColor
|
||||||
researchableTechs.contains(techName) -> researchableTechColor
|
researchableTechs.contains(techName) -> researchableTechColor
|
||||||
tempTechsToResearch.contains(techName) -> queuedTechColor
|
tempTechsToResearch.contains(techName) -> queuedTechColor
|
||||||
else -> Color.BLACK
|
else -> Color.BLACK.cpy()
|
||||||
})
|
})
|
||||||
|
|
||||||
if (civTech.isResearched(techName) && techName != Constants.futureTech) {
|
if (civTech.isResearched(techName) && techName != Constants.futureTech) {
|
||||||
@ -236,7 +236,7 @@ class TechPickerScreen(
|
|||||||
eraLabel.localToStageCoordinates(coords)
|
eraLabel.localToStageCoordinates(coords)
|
||||||
techTable.stageToLocalCoordinates(coords)
|
techTable.stageToLocalCoordinates(coords)
|
||||||
val line = ImageGetter.getLine(coords.x-1f, coords.y, coords.x-1f, coords.y - 1000f, 1f)
|
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()
|
line.toBack()
|
||||||
techTable.addActor(line)
|
techTable.addActor(line)
|
||||||
lines.add(line)
|
lines.add(line)
|
||||||
@ -263,10 +263,10 @@ class TechPickerScreen(
|
|||||||
techTable.stageToLocalCoordinates(prerequisiteCoords)
|
techTable.stageToLocalCoordinates(prerequisiteCoords)
|
||||||
|
|
||||||
val lineColor = when {
|
val lineColor = when {
|
||||||
civTech.isResearched(tech.name) && !tech.isContinuallyResearchable() -> Color.WHITE
|
civTech.isResearched(tech.name) && !tech.isContinuallyResearchable() -> Color.WHITE.cpy()
|
||||||
civTech.isResearched(prerequisite) -> researchableTechColor
|
civTech.isResearched(prerequisite) -> researchableTechColor
|
||||||
tempTechsToResearch.contains(tech.name) -> currentTechColor
|
tempTechsToResearch.contains(tech.name) -> currentTechColor
|
||||||
else -> Color.WHITE
|
else -> Color.WHITE.cpy()
|
||||||
}
|
}
|
||||||
|
|
||||||
val lineSize = when {
|
val lineSize = when {
|
||||||
|
@ -13,12 +13,12 @@ open class BorderedTable(
|
|||||||
val defaultBorder: String = BaseScreen.skinStrings.rectangleWithOutlineShape,
|
val defaultBorder: String = BaseScreen.skinStrings.rectangleWithOutlineShape,
|
||||||
val borderColor: Color = Color.WHITE,
|
val borderColor: Color = Color.WHITE,
|
||||||
val innerColor: Color = Color.BLACK,
|
val innerColor: Color = Color.BLACK,
|
||||||
val borderSize: Float = 5f,
|
var borderSize: Float = 5f,
|
||||||
val borderOnTop: Boolean = false
|
val borderOnTop: Boolean = false
|
||||||
) : Table() {
|
) : Table() {
|
||||||
|
|
||||||
var bgBorder: Image = Image(BaseScreen.skinStrings.getUiBackground(path, defaultBorder, borderColor))
|
var bgInner: Image = Image(BaseScreen.skinStrings.getUiBackground(path, defaultInner, innerColor.cpy()))
|
||||||
var bgInner: Image = Image(BaseScreen.skinStrings.getUiBackground(path, defaultInner, innerColor))
|
var bgBorder: Image = Image(BaseScreen.skinStrings.getUiBackground(path + "Border", defaultBorder, borderColor.cpy()))
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (borderSize != 0f)
|
if (borderSize != 0f)
|
||||||
@ -40,7 +40,7 @@ open class BorderedTable(
|
|||||||
|
|
||||||
fun setBackgroundColor(color: Color) {
|
fun setBackgroundColor(color: Color) {
|
||||||
bgInner.remove()
|
bgInner.remove()
|
||||||
bgInner = Image(BaseScreen.skinStrings.getUiBackground(path, defaultInner, color))
|
bgInner = Image(BaseScreen.skinStrings.getUiBackground(path, defaultInner, color.cpy()))
|
||||||
addActor(bgInner)
|
addActor(bgInner)
|
||||||
if (borderSize != 0f) {
|
if (borderSize != 0f) {
|
||||||
if (borderOnTop)
|
if (borderOnTop)
|
||||||
|
@ -189,7 +189,12 @@ class UnitTable(val worldScreen: WorldScreen) : Table() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!unit.isCivilian()) {
|
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())
|
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 -->
|
<!--- DO NOT REMOVE OR MODIFY THIS LINE UI_ELEMENT_TABLE_REGION -->
|
||||||
| Directory | Name | Default shape | Image |
|
| Directory | Name | Default shape | Image |
|
||||||
|---|:---:|:---:|---|
|
|---|:---:|:---:|---|
|
||||||
|
| | Border | null | |
|
||||||
| CityScreen/ | CityPickerTable | roundedEdgeRectangle | |
|
| CityScreen/ | CityPickerTable | roundedEdgeRectangle | |
|
||||||
| CityScreen/CitizenManagementTable/ | AvoidCell | null | |
|
| CityScreen/CitizenManagementTable/ | AvoidCell | null | |
|
||||||
| CityScreen/CitizenManagementTable/ | FocusCell | null | |
|
| CityScreen/CitizenManagementTable/ | FocusCell | null | |
|
||||||
|
Reference in New Issue
Block a user