From c6caf0cecd9f456e5e1a602dc845d442eb6b2246 Mon Sep 17 00:00:00 2001 From: Yair Morgenstern Date: Sat, 16 Jun 2018 22:50:11 +0300 Subject: [PATCH] Added promotions mechanism! --- android/assets/jsons/UnitPromotions.json | 6 +- .../unciv/logic/automation/UnitAutomation.kt | 3 +- core/src/com/unciv/logic/battle/Battle.kt | 119 +++--------------- .../com/unciv/logic/battle/BattleDamage.kt | 104 +++++++++++++++ .../unciv/logic/battle/MapUnitCombatant.kt | 4 +- core/src/com/unciv/logic/map/MapUnit.kt | 1 + .../src/com/unciv/logic/map/UnitPromotions.kt | 16 +++ .../com/unciv/models/gamebasics/GameBasics.kt | 6 +- .../unit/{UnitPromotion.kt => Promotion.kt} | 4 +- .../com/unciv/ui/cityscreen/CityStatsTable.kt | 2 +- .../unciv/ui/pickerscreens/PickerScreen.kt | 13 +- .../ui/pickerscreens/PromotionPickerScreen.kt | 27 ++-- .../ui/worldscreen/bottombar/BattleTable.kt | 13 +- .../unciv/ui/worldscreen/unit/UnitActions.kt | 18 ++- .../ui/worldscreen/unit/UnitActionsTable.kt | 1 + .../unciv/ui/worldscreen/unit/UnitTable.kt | 17 ++- 16 files changed, 203 insertions(+), 151 deletions(-) create mode 100644 core/src/com/unciv/logic/battle/BattleDamage.kt create mode 100644 core/src/com/unciv/logic/map/UnitPromotions.kt rename core/src/com/unciv/models/gamebasics/unit/{UnitPromotion.kt => Promotion.kt} (67%) diff --git a/android/assets/jsons/UnitPromotions.json b/android/assets/jsons/UnitPromotions.json index daad5567..d7ea2b5f 100644 --- a/android/assets/jsons/UnitPromotions.json +++ b/android/assets/jsons/UnitPromotions.json @@ -71,18 +71,18 @@ { name:"Scouting I", effect:"+1 Visibility Range", - unitTypes:["Melee","Mounted"] + unitTypes:["Scout"] } { name:"Scouting II", prerequisites:["Scouting I"], effect:"+1 Visibility Range", - unitTypes:["Melee","Mounted"] + unitTypes:["Scout"] } { name:"Scouting III", prerequisites:["Scouting II"], effect:"+1 Visibility Range", - unitTypes:["Melee","Mounted"] + unitTypes:["Scout"] } ] \ No newline at end of file diff --git a/core/src/com/unciv/logic/automation/UnitAutomation.kt b/core/src/com/unciv/logic/automation/UnitAutomation.kt index 48e5a586..996af3d8 100644 --- a/core/src/com/unciv/logic/automation/UnitAutomation.kt +++ b/core/src/com/unciv/logic/automation/UnitAutomation.kt @@ -2,6 +2,7 @@ package com.unciv.logic.automation import com.unciv.UnCivGame import com.unciv.logic.battle.Battle +import com.unciv.logic.battle.BattleDamage import com.unciv.logic.battle.MapUnitCombatant import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.map.MapUnit @@ -90,7 +91,7 @@ class UnitAutomation{ if(setupAction!=null) setupAction.action() val enemy = Battle().getMapCombatantOfTile(enemyTileToAttack)!! - val damageToAttacker = Battle(unit.civInfo.gameInfo).calculateDamageToAttacker(MapUnitCombatant(unit), enemy) + val damageToAttacker = BattleDamage().calculateDamageToAttacker(MapUnitCombatant(unit), enemy) if (damageToAttacker < unit.health) { // don't attack if we'll die from the attack if(MapUnitCombatant(unit).isMelee()) diff --git a/core/src/com/unciv/logic/battle/Battle.kt b/core/src/com/unciv/logic/battle/Battle.kt index 8b025f18..db57b521 100644 --- a/core/src/com/unciv/logic/battle/Battle.kt +++ b/core/src/com/unciv/logic/battle/Battle.kt @@ -8,116 +8,17 @@ import com.unciv.logic.map.TileInfo import com.unciv.models.gamebasics.unit.UnitType import com.unciv.ui.utils.tr import java.util.* -import kotlin.collections.HashMap import kotlin.math.max /** * Damage calculations according to civ v wiki and https://steamcommunity.com/sharedfiles/filedetails/?id=170194443 */ class Battle(val gameInfo:GameInfo=UnCivGame.Current.gameInfo) { - - private fun getGeneralModifiers(combatant: ICombatant, enemy: ICombatant): HashMap { - val modifiers = HashMap() - if (combatant is MapUnitCombatant) { - val uniques = combatant.unit.getBaseUnit().uniques - if (uniques != null) { - // This beut allows us to have generic unit uniques: "Bonus vs City 75%", "Penatly vs Mounted 25%" etc. - for (unique in uniques) { - val regexResult = Regex("""(Bonus|Penalty) vs (\S*) (\d*)%""").matchEntire(unique) - if (regexResult == null) continue - val vsType = UnitType.valueOf(regexResult.groups[2]!!.value) - val modificationAmount = regexResult.groups[3]!!.value.toFloat() / 100 // if it says 15%, that's 0.15f in modification - if (enemy.getUnitType() == vsType) { - if (regexResult.groups[1]!!.value == "Bonus") - modifiers["Bonus vs $vsType"] = modificationAmount - else modifiers["Penalty vs $vsType"] = -modificationAmount - } - } - } - - if(enemy.getCivilization().isBarbarianCivilization()) - modifiers["vs Barbarians"] = 0.33f - - if(combatant.getCivilization().happiness<0) - modifiers["Unhappiness"] = 0.02f * combatant.getCivilization().happiness //https://www.carlsguides.com/strategy/civilization5/war/combatbonuses.php - } - - return modifiers - } - - fun getAttackModifiers(attacker: ICombatant, defender: ICombatant): HashMap { - val modifiers = getGeneralModifiers(attacker, defender) - if (attacker.isMelee()) { - val numberOfAttackersSurroundingDefender = defender.getTile().neighbors.count { - it.militaryUnit != null - && it.militaryUnit!!.owner == attacker.getCivilization().civName - && MapUnitCombatant(it.militaryUnit!!).isMelee() - } - if (numberOfAttackersSurroundingDefender > 1) - modifiers["Flanking"] = 0.1f * (numberOfAttackersSurroundingDefender-1) //https://www.carlsguides.com/strategy/civilization5/war/combatbonuses.php - } - - return modifiers - } - - fun getDefenceModifiers(attacker: ICombatant, defender: ICombatant): HashMap { - val modifiers = getGeneralModifiers(defender, attacker) - if (!(defender is MapUnitCombatant && defender.unit.hasUnique("No defensive terrain bonus"))) { - val tileDefenceBonus = defender.getTile().getDefensiveBonus() - if (tileDefenceBonus > 0) modifiers["Terrain"] = tileDefenceBonus - } - if(defender is MapUnitCombatant && defender.unit.isFortified()) - modifiers["Fortification"]=0.2f*defender.unit.getFortificationTurns() - return modifiers - } - - private fun modifiersToMultiplicationBonus(modifiers: HashMap): Float { - // modifiers are like 0.1 for a 10% bonus, -0.1 for a 10% loss - var modifier = 1f - for (m in modifiers.values) modifier *= (1 + m) - return modifier - } - - private fun getHealthDependantDamageRatio(combatant: ICombatant): Float { - if (combatant.getUnitType() == UnitType.City) return 1f - return 1/2f + combatant.getHealth()/200f // Each point of health reduces damage dealt by 0.5% - } - - - /** - * Includes attack modifiers - */ - fun getAttackingStrength(attacker: ICombatant, defender: ICombatant): Float { - val attackModifier = modifiersToMultiplicationBonus(getAttackModifiers(attacker,defender)) - return attacker.getAttackingStrength(defender) * attackModifier - } - - - /** - * Includes defence modifiers - */ - fun getDefendingStrength(attacker: ICombatant, defender: ICombatant): Float { - val defenceModifier = modifiersToMultiplicationBonus(getDefenceModifiers(attacker,defender)) - return defender.getDefendingStrength(attacker) * defenceModifier - } - - fun calculateDamageToAttacker(attacker: ICombatant, defender: ICombatant): Int { - if(attacker.isRanged()) return 0 - if(defender.getUnitType()== UnitType.Civilian) return 0 - val ratio = getDefendingStrength(attacker,defender) / getAttackingStrength(attacker,defender) - return (ratio * 30 * getHealthDependantDamageRatio(defender)).toInt() - } - - fun calculateDamageToDefender(attacker: ICombatant, defender: ICombatant): Int { - val ratio = getAttackingStrength(attacker,defender) / getDefendingStrength(attacker,defender) - return (ratio * 30 * getHealthDependantDamageRatio(attacker)).toInt() - } - fun attack(attacker: ICombatant, defender: ICombatant) { val attackedTile = defender.getTile() - var damageToDefender = calculateDamageToDefender(attacker,defender) - var damageToAttacker = calculateDamageToAttacker(attacker,defender) + var damageToDefender = BattleDamage().calculateDamageToDefender(attacker,defender) + var damageToAttacker = BattleDamage().calculateDamageToAttacker(attacker,defender) if(defender.getUnitType() == UnitType.Civilian && attacker.isMelee()){ captureCivilianUnit(attacker,defender) @@ -182,6 +83,19 @@ class Battle(val gameInfo:GameInfo=UnCivGame.Current.gameInfo) { unit.attacksThisTurn+=1 if(unit.isFortified()) attacker.unit.action=null // but not, for instance, if it's Set Up - then it should definitely keep the action! } + + // XP! + if(attacker.isMelee()){ + if(defender.getCivilization() != attacker.getCivilization()) // unit was not captured but actually attacked + { + if (attacker is MapUnitCombatant) attacker.unit.promotions.XP += 5 + if (defender is MapUnitCombatant) defender.unit.promotions.XP += 4 + } + } + else{ // ranged attack + if(attacker is MapUnitCombatant) attacker.unit.promotions.XP += 2 + if(defender is MapUnitCombatant) defender.unit.promotions.XP += 2 + } } private fun conquerCity(city: CityInfo, attacker: ICombatant) { @@ -235,4 +149,5 @@ class Battle(val gameInfo:GameInfo=UnCivGame.Current.gameInfo) { capturedUnit.civInfo = attacker.getCivilization() capturedUnit.owner = capturedUnit.civInfo.civName } -} \ No newline at end of file +} + diff --git a/core/src/com/unciv/logic/battle/BattleDamage.kt b/core/src/com/unciv/logic/battle/BattleDamage.kt new file mode 100644 index 00000000..3ac2ebea --- /dev/null +++ b/core/src/com/unciv/logic/battle/BattleDamage.kt @@ -0,0 +1,104 @@ +package com.unciv.logic.battle + +import com.unciv.models.gamebasics.unit.UnitType + + +class BattleDamage{ + + private fun getGeneralModifiers(combatant: ICombatant, enemy: ICombatant): HashMap { + val modifiers = HashMap() + if (combatant is MapUnitCombatant) { + val uniques = combatant.unit.getBaseUnit().uniques + if (uniques != null) { + // This beut allows us to have generic unit uniques: "Bonus vs City 75%", "Penatly vs Mounted 25%" etc. + for (unique in uniques) { + val regexResult = Regex("""(Bonus|Penalty) vs (\S*) (\d*)%""").matchEntire(unique) + if (regexResult == null) continue + val vsType = UnitType.valueOf(regexResult.groups[2]!!.value) + val modificationAmount = regexResult.groups[3]!!.value.toFloat() / 100 // if it says 15%, that's 0.15f in modification + if (enemy.getUnitType() == vsType) { + if (regexResult.groups[1]!!.value == "Bonus") + modifiers["Bonus vs $vsType"] = modificationAmount + else modifiers["Penalty vs $vsType"] = -modificationAmount + } + } + } + + if(enemy.getCivilization().isBarbarianCivilization()) + modifiers["vs Barbarians"] = 0.33f + + if(combatant.getCivilization().happiness<0) + modifiers["Unhappiness"] = 0.02f * combatant.getCivilization().happiness //https://www.carlsguides.com/strategy/civilization5/war/combatbonuses.php + } + + return modifiers + } + + fun getAttackModifiers(attacker: ICombatant, defender: ICombatant): HashMap { + val modifiers = getGeneralModifiers(attacker, defender) + if (attacker.isMelee()) { + val numberOfAttackersSurroundingDefender = defender.getTile().neighbors.count { + it.militaryUnit != null + && it.militaryUnit!!.owner == attacker.getCivilization().civName + && MapUnitCombatant(it.militaryUnit!!).isMelee() + } + if (numberOfAttackersSurroundingDefender > 1) + modifiers["Flanking"] = 0.1f * (numberOfAttackersSurroundingDefender-1) //https://www.carlsguides.com/strategy/civilization5/war/combatbonuses.php + } + + return modifiers + } + + fun getDefenceModifiers(attacker: ICombatant, defender: ICombatant): HashMap { + val modifiers = getGeneralModifiers(defender, attacker) + if (!(defender is MapUnitCombatant && defender.unit.hasUnique("No defensive terrain bonus"))) { + val tileDefenceBonus = defender.getTile().getDefensiveBonus() + if (tileDefenceBonus > 0) modifiers["Terrain"] = tileDefenceBonus + } + if(defender is MapUnitCombatant && defender.unit.isFortified()) + modifiers["Fortification"]=0.2f*defender.unit.getFortificationTurns() + return modifiers + } + + private fun modifiersToMultiplicationBonus(modifiers: HashMap): Float { + // modifiers are like 0.1 for a 10% bonus, -0.1 for a 10% loss + var modifier = 1f + for (m in modifiers.values) modifier *= (1 + m) + return modifier + } + + private fun getHealthDependantDamageRatio(combatant: ICombatant): Float { + if (combatant.getUnitType() == UnitType.City) return 1f + return 1/2f + combatant.getHealth()/200f // Each point of health reduces damage dealt by 0.5% + } + + + /** + * Includes attack modifiers + */ + fun getAttackingStrength(attacker: ICombatant, defender: ICombatant): Float { + val attackModifier = modifiersToMultiplicationBonus(getAttackModifiers(attacker,defender)) + return attacker.getAttackingStrength(defender) * attackModifier + } + + + /** + * Includes defence modifiers + */ + fun getDefendingStrength(attacker: ICombatant, defender: ICombatant): Float { + val defenceModifier = modifiersToMultiplicationBonus(getDefenceModifiers(attacker,defender)) + return defender.getDefendingStrength(attacker) * defenceModifier + } + + fun calculateDamageToAttacker(attacker: ICombatant, defender: ICombatant): Int { + if(attacker.isRanged()) return 0 + if(defender.getUnitType()== UnitType.Civilian) return 0 + val ratio = getDefendingStrength(attacker,defender) / getAttackingStrength(attacker,defender) + return (ratio * 30 * getHealthDependantDamageRatio(defender)).toInt() + } + + fun calculateDamageToDefender(attacker: ICombatant, defender: ICombatant): Int { + val ratio = getAttackingStrength(attacker,defender) / getDefendingStrength(attacker,defender) + return (ratio * 30 * getHealthDependantDamageRatio(attacker)).toInt() + } +} \ No newline at end of file diff --git a/core/src/com/unciv/logic/battle/MapUnitCombatant.kt b/core/src/com/unciv/logic/battle/MapUnitCombatant.kt index 06ce483b..f20f9cdc 100644 --- a/core/src/com/unciv/logic/battle/MapUnitCombatant.kt +++ b/core/src/com/unciv/logic/battle/MapUnitCombatant.kt @@ -17,12 +17,12 @@ class MapUnitCombatant(val unit: MapUnit) : ICombatant { if(isDefeated()) unit.removeFromTile() } - override fun getAttackingStrength(defender: ICombatant): Int { + override fun getAttackingStrength(defender: ICombatant): Int { // todo remove defender if (isRanged()) return unit.getBaseUnit().rangedStrength else return unit.getBaseUnit().strength } - override fun getDefendingStrength(attacker: ICombatant): Int { + override fun getDefendingStrength(attacker: ICombatant): Int { // todo remove attacker return unit.getBaseUnit().strength } diff --git a/core/src/com/unciv/logic/map/MapUnit.kt b/core/src/com/unciv/logic/map/MapUnit.kt index 831b6699..3fa57602 100644 --- a/core/src/com/unciv/logic/map/MapUnit.kt +++ b/core/src/com/unciv/logic/map/MapUnit.kt @@ -19,6 +19,7 @@ class MapUnit { var health:Int = 100 var action: String? = null // work, automation, fortifying, I dunno what. var attacksThisTurn = 0 + var promotions = UnitPromotions() fun getBaseUnit(): Unit = GameBasics.Units[name]!! fun getMovementString(): String = DecimalFormat("0.#").format(currentMovement.toDouble()) + "/" + maxMovement diff --git a/core/src/com/unciv/logic/map/UnitPromotions.kt b/core/src/com/unciv/logic/map/UnitPromotions.kt new file mode 100644 index 00000000..400cff4f --- /dev/null +++ b/core/src/com/unciv/logic/map/UnitPromotions.kt @@ -0,0 +1,16 @@ +package com.unciv.logic.map + +class UnitPromotions{ + var XP=0 + var promotions = HashSet() + var numberOfPromotions = 0 // The number of times this unit has been promoted - some promotions don't come from being promoted but from other things! + + fun xpForNextPromotion() = (numberOfPromotions+1)*10 + fun canBePromoted() = XP >= xpForNextPromotion() + + fun addPromotion(promotionName:String){ + XP -= xpForNextPromotion() + promotions.add(promotionName) + numberOfPromotions++ + } +} \ No newline at end of file diff --git a/core/src/com/unciv/models/gamebasics/GameBasics.kt b/core/src/com/unciv/models/gamebasics/GameBasics.kt index 5459b1d3..caaa660b 100644 --- a/core/src/com/unciv/models/gamebasics/GameBasics.kt +++ b/core/src/com/unciv/models/gamebasics/GameBasics.kt @@ -6,7 +6,7 @@ import com.unciv.models.gamebasics.tile.Terrain import com.unciv.models.gamebasics.tile.TileImprovement import com.unciv.models.gamebasics.tile.TileResource import com.unciv.models.gamebasics.unit.Unit -import com.unciv.models.gamebasics.unit.UnitPromotion +import com.unciv.models.gamebasics.unit.Promotion import com.unciv.models.stats.INamed import kotlin.collections.set @@ -18,7 +18,7 @@ object GameBasics { val Technologies = LinkedHashMap() val Helps = LinkedHashMap() val Units = LinkedHashMap() - val UnitPromotions = LinkedHashMap() + val UnitPromotions = LinkedHashMap() val Civilizations = LinkedHashMap() val PolicyBranches = LinkedHashMap() val Tutorials = LinkedHashMap>() @@ -43,7 +43,7 @@ object GameBasics { TileImprovements += createHashmap(getFromJson(Array::class.java, "TileImprovements")) Helps += createHashmap(getFromJson(Array::class.java, "BasicHelp")) Units += createHashmap(getFromJson(Array::class.java, "Units")) - UnitPromotions += createHashmap(getFromJson(Array::class.java, "UnitPromotions")) + UnitPromotions += createHashmap(getFromJson(Array::class.java, "UnitPromotions")) PolicyBranches += createHashmap(getFromJson(Array::class.java, "Policies")) Civilizations += createHashmap(getFromJson(Array::class.java, "Civilizations")) diff --git a/core/src/com/unciv/models/gamebasics/unit/UnitPromotion.kt b/core/src/com/unciv/models/gamebasics/unit/Promotion.kt similarity index 67% rename from core/src/com/unciv/models/gamebasics/unit/UnitPromotion.kt rename to core/src/com/unciv/models/gamebasics/unit/Promotion.kt index 35dccc8e..74930ddf 100644 --- a/core/src/com/unciv/models/gamebasics/unit/UnitPromotion.kt +++ b/core/src/com/unciv/models/gamebasics/unit/Promotion.kt @@ -3,7 +3,7 @@ package com.unciv.models.gamebasics.unit import com.unciv.models.gamebasics.ICivilopedia import com.unciv.models.stats.INamed -class UnitPromotion : ICivilopedia, INamed{ +class Promotion : ICivilopedia, INamed{ override lateinit var name: String override val description: String get(){ @@ -11,5 +11,5 @@ class UnitPromotion : ICivilopedia, INamed{ } var prerequisites = listOf() lateinit var effect:String; - var unitTypes = listOf() + var unitTypes = listOf() // The json parser woulddn't agree to deserialize this as a list of UnitTypes. =( } \ No newline at end of file diff --git a/core/src/com/unciv/ui/cityscreen/CityStatsTable.kt b/core/src/com/unciv/ui/cityscreen/CityStatsTable.kt index 91f7daeb..c0727e48 100644 --- a/core/src/com/unciv/ui/cityscreen/CityStatsTable.kt +++ b/core/src/com/unciv/ui/cityscreen/CityStatsTable.kt @@ -61,7 +61,7 @@ class CityStatsTable(val cityScreen: CityScreen) : Table(){ !(construction is Building && construction.isWonder)) { row() val buildingGoldCost = construction.getGoldCost(city.civInfo.policies.getAdoptedPolicies()) - val buildingBuyButton = TextButton("Buy for".tr()+"\r\n$buildingGoldCost gold", CameraStageBaseScreen.skin) + val buildingBuyButton = TextButton("Buy for".tr()+"\r\n$buildingGoldCost "+"Gold".tr(), CameraStageBaseScreen.skin) buildingBuyButton.addClickListener { city.cityConstructions.purchaseBuilding(city.cityConstructions.currentConstruction) update() diff --git a/core/src/com/unciv/ui/pickerscreens/PickerScreen.kt b/core/src/com/unciv/ui/pickerscreens/PickerScreen.kt index 888cfe03..d8f742de 100644 --- a/core/src/com/unciv/ui/pickerscreens/PickerScreen.kt +++ b/core/src/com/unciv/ui/pickerscreens/PickerScreen.kt @@ -1,13 +1,8 @@ package com.unciv.ui.pickerscreens -import com.badlogic.gdx.graphics.Color -import com.badlogic.gdx.scenes.scene2d.Touchable import com.badlogic.gdx.scenes.scene2d.ui.* import com.badlogic.gdx.utils.Align -import com.unciv.ui.utils.CameraStageBaseScreen -import com.unciv.ui.utils.addClickListener -import com.unciv.ui.utils.disable -import com.unciv.ui.utils.tr +import com.unciv.ui.utils.* open class PickerScreen : CameraStageBaseScreen() { @@ -53,9 +48,7 @@ open class PickerScreen : CameraStageBaseScreen() { } protected fun pick(rightButtonText: String) { - rightSideButton.touchable = Touchable.enabled - rightSideButton.color = Color.WHITE + rightSideButton.enable() rightSideButton.setText(rightButtonText) } -} - +} \ No newline at end of file diff --git a/core/src/com/unciv/ui/pickerscreens/PromotionPickerScreen.kt b/core/src/com/unciv/ui/pickerscreens/PromotionPickerScreen.kt index 157f4ef8..02dedc8e 100644 --- a/core/src/com/unciv/ui/pickerscreens/PromotionPickerScreen.kt +++ b/core/src/com/unciv/ui/pickerscreens/PromotionPickerScreen.kt @@ -6,37 +6,44 @@ import com.badlogic.gdx.scenes.scene2d.ui.Label import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup import com.unciv.logic.map.MapUnit import com.unciv.models.gamebasics.GameBasics -import com.unciv.models.gamebasics.unit.UnitPromotion -import com.unciv.ui.utils.ImageGetter -import com.unciv.ui.utils.addClickListener -import com.unciv.ui.utils.setFontColor +import com.unciv.models.gamebasics.unit.Promotion +import com.unciv.ui.utils.* class PromotionPickerScreen(mapUnit: MapUnit) : PickerScreen() { - private var selectedPromotion: UnitPromotion? = null + private var selectedPromotion: Promotion? = null + init { rightSideButton.setText("Pick promotion") rightSideButton.addClickListener { - // todo add promotion to unit and decrease XP + mapUnit.promotions.addPromotion(selectedPromotion!!.name) game.setWorldScreen() dispose() } val availablePromotions = VerticalGroup() availablePromotions.space(10f) + val unitType = mapUnit.getBaseUnit().unitType for (promotion in GameBasics.UnitPromotions.values) { - if (!promotion.unitTypes.contains(mapUnit.getBaseUnit().unitType)) continue + if (!promotion.unitTypes.contains(unitType.toString())) continue + val isPromotionAvailable = promotion.prerequisites.all { mapUnit.promotions.promotions.contains(it) } + val unitHasPromotion = mapUnit.promotions.promotions.contains(promotion.name) val promotionButton = Button(skin) + if(!isPromotionAvailable) promotionButton.color = Color.GRAY promotionButton.add(ImageGetter.getPromotionIcon(promotion.name)).size(30f).pad(10f) - promotionButton.add(Label(promotion.name, skin) .setFontColor(Color.WHITE)).pad(10f) + if(unitHasPromotion) promotionButton.color = Color.GREEN promotionButton.addClickListener { selectedPromotion = promotion - pick(promotion.name) - descriptionLabel.setText(promotion.effect) + rightSideButton.setText(promotion.name) + if(isPromotionAvailable && !unitHasPromotion) rightSideButton.enable() + else rightSideButton.disable() + var descriptionText = promotion.effect + if(promotion.prerequisites.isNotEmpty()) descriptionText +="\nRequires: "+promotion.prerequisites.joinToString() + descriptionLabel.setText(descriptionText) } availablePromotions.addActor(promotionButton) } diff --git a/core/src/com/unciv/ui/worldscreen/bottombar/BattleTable.kt b/core/src/com/unciv/ui/worldscreen/bottombar/BattleTable.kt index b981c6a0..84a686dd 100644 --- a/core/src/com/unciv/ui/worldscreen/bottombar/BattleTable.kt +++ b/core/src/com/unciv/ui/worldscreen/bottombar/BattleTable.kt @@ -5,6 +5,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.TextButton import com.unciv.logic.automation.UnitAutomation import com.unciv.logic.battle.Battle +import com.unciv.logic.battle.BattleDamage import com.unciv.logic.battle.ICombatant import com.unciv.logic.battle.MapUnitCombatant import com.unciv.models.gamebasics.unit.UnitType @@ -19,7 +20,7 @@ class BattleTable(val worldScreen: WorldScreen): Table() { skin = CameraStageBaseScreen.skin background = ImageGetter.getDrawable(ImageGetter.WhiteDot) .tint(ImageGetter.getBlue()) - pad(10f) + pad(5f) } fun hide(){ @@ -66,17 +67,17 @@ class BattleTable(val worldScreen: WorldScreen): Table() { add("Strength: "+defender.getDefendingStrength(attacker)) row().pad(5f) - val attackerModifiers = battle.getAttackModifiers(attacker,defender) .map { it.key+": "+(if(it.value>0)"+" else "")+(it.value*100).toInt()+"%" } - val defenderModifiers = battle.getDefenceModifiers(attacker, defender).map { it.key+": "+(if(it.value>0)"+" else "")+(it.value*100).toInt()+"%" } + val attackerModifiers = BattleDamage().getAttackModifiers(attacker,defender) .map { it.key+": "+(if(it.value>0)"+" else "")+(it.value*100).toInt()+"%" } + val defenderModifiers = BattleDamage().getDefenceModifiers(attacker, defender).map { it.key+": "+(if(it.value>0)"+" else "")+(it.value*100).toInt()+"%" } for(i in 0..max(attackerModifiers.size,defenderModifiers.size)){ if (attackerModifiers.size > i) add(attackerModifiers[i]) else add() if (defenderModifiers.size > i) add(defenderModifiers[i]) else add() - row().pad(5f) + row().pad(2f) } - var damageToDefender = battle.calculateDamageToDefender(attacker,defender) - var damageToAttacker = battle.calculateDamageToAttacker(attacker,defender) + var damageToDefender = BattleDamage().calculateDamageToDefender(attacker,defender) + var damageToAttacker = BattleDamage().calculateDamageToAttacker(attacker,defender) if (damageToAttacker>attacker.getHealth() && damageToDefender>defender.getHealth()) // when damage exceeds health, we don't want to show negative health numbers diff --git a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt index 93b5d41a..2669e316 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/UnitActions.kt @@ -1,11 +1,13 @@ package com.unciv.ui.worldscreen.unit +import com.unciv.UnCivGame import com.unciv.logic.automation.WorkerAutomation import com.unciv.logic.map.MapUnit import com.unciv.models.gamebasics.Building import com.unciv.models.gamebasics.GameBasics import com.unciv.models.gamebasics.unit.UnitType import com.unciv.ui.pickerscreens.ImprovementPickerScreen +import com.unciv.ui.pickerscreens.PromotionPickerScreen import com.unciv.ui.pickerscreens.TechPickerScreen import com.unciv.ui.worldscreen.WorldScreen import java.util.* @@ -34,7 +36,6 @@ class UnitActions { unitTable.currentlyExecutingAction = "moveTo" }, unit.currentMovement != 0f ) } - else { actionList += UnitAction("Stop movement", { @@ -43,9 +44,15 @@ class UnitActions { },true) } - if(unit.getBaseUnit().unitType!= UnitType.Civilian && !unit.hasUnique("No defensive terrain bonus")){ - if(!unit.isFortified()) - actionList += UnitAction("Fortify",{unit.action="Fortify 0"}, unit.currentMovement != 0f) + if(unit.getBaseUnit().unitType!= UnitType.Civilian + && !unit.hasUnique("No defensive terrain bonus") && !unit.isFortified()) { + actionList += UnitAction("Fortify", { unit.action = "Fortify 0" }, unit.currentMovement != 0f) + } + + if(unit.promotions.canBePromoted()){ + actionList += UnitAction("Promote", + {UnCivGame.Current.screen = PromotionPickerScreen(unit)}, + unit.currentMovement != 0f) } if(unit.getBaseUnit().upgradesTo!=null) { @@ -158,5 +165,4 @@ class UnitActions { return actionList } -} - +} \ No newline at end of file diff --git a/core/src/com/unciv/ui/worldscreen/unit/UnitActionsTable.kt b/core/src/com/unciv/ui/worldscreen/unit/UnitActionsTable.kt index 508f9913..686a5ed0 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/UnitActionsTable.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/UnitActionsTable.kt @@ -21,6 +21,7 @@ class UnitActionsTable(val worldScreen: WorldScreen) : Table(){ "Move unit" -> return ImageGetter.getStatIcon("Movement") "Stop movement"-> return ImageGetter.getStatIcon("Movement").apply { color= Color.RED } "Fortify" -> return ImageGetter.getImage("OtherIcons/Shield.png").apply { color= Color.BLACK } + "Promote" -> return ImageGetter.getImage("OtherIcons/Star.png").apply { color= Color.GOLD } "Construct improvement" -> return ImageGetter.getUnitIcon("Worker") "Automate" -> return ImageGetter.getUnitIcon("Great Engineer") "Stop automation" -> return ImageGetter.getImage("OtherIcons/Stop.png") diff --git a/core/src/com/unciv/ui/worldscreen/unit/UnitTable.kt b/core/src/com/unciv/ui/worldscreen/unit/UnitTable.kt index fe16442b..a7c6a179 100644 --- a/core/src/com/unciv/ui/worldscreen/unit/UnitTable.kt +++ b/core/src/com/unciv/ui/worldscreen/unit/UnitTable.kt @@ -6,6 +6,7 @@ import com.unciv.logic.map.MapUnit import com.unciv.logic.map.TileInfo import com.unciv.models.gamebasics.unit.UnitType import com.unciv.ui.utils.CameraStageBaseScreen +import com.unciv.ui.utils.ImageGetter import com.unciv.ui.utils.addClickListener import com.unciv.ui.utils.tr import com.unciv.ui.worldscreen.WorldScreen @@ -14,26 +15,27 @@ class UnitTable(val worldScreen: WorldScreen) : Table(){ private val prevIdleUnitButton = IdleUnitButton(this,worldScreen.tileMapHolder,true) private val nextIdleUnitButton = IdleUnitButton(this,worldScreen.tileMapHolder,false) private val unitNameLabel = Label("",CameraStageBaseScreen.skin) + private val promotionsTable = Table() private val unitDescriptionLabel = Label("",CameraStageBaseScreen.skin) var selectedUnit : MapUnit? = null var currentlyExecutingAction : String? = null init { - - pad(20f) + pad(5f) add(Table().apply { add(prevIdleUnitButton) - add(unitNameLabel).pad(10f) + add(unitNameLabel).pad(5f) add(nextIdleUnitButton) - }).colspan(2) - row() + }).colspan(2).row() + add(promotionsTable).row() add(unitDescriptionLabel) } fun update() { prevIdleUnitButton.update() nextIdleUnitButton.update() + promotionsTable.clear() unitDescriptionLabel.clearListeners() if(selectedUnit!=null) @@ -58,6 +60,9 @@ class UnitTable(val worldScreen: WorldScreen) : Table(){ if(unit.health<100) nameLabelText+=" ("+unit.health+")" unitNameLabel.setText(nameLabelText) + for(promotion in unit.promotions.promotions) + promotionsTable.add(ImageGetter.getPromotionIcon(promotion)).size(20f) + var unitLabelText = "Movement".tr()+": " + unit.getMovementString() if (unit.getBaseUnit().unitType != UnitType.Civilian) { unitLabelText += "\n"+"Strength".tr()+": " + unit.getBaseUnit().strength @@ -65,6 +70,8 @@ class UnitTable(val worldScreen: WorldScreen) : Table(){ if (unit.getBaseUnit().rangedStrength!=0) unitLabelText += "\n"+"Ranged strength".tr()+": "+unit.getBaseUnit().rangedStrength + unitLabelText += "\n"+"XP".tr()+": "+unit.promotions.XP + if(unit.isFortified() && unit.getFortificationTurns()>0) unitLabelText+="\n+"+unit.getFortificationTurns()*20+"% fortification"