Updated promotions - make more generalizable, update to G&K (#4292)

* Generalized the "Heal Instantly" promotion

* Extended "Indirect Fire" to WaterRanged units, conform the main game

* Generalized Extend Range, Operational Range

* Generalized "logistics"

* Typo

* Generalized the healing from "Medic"

* Implemented requested changes

* Generalized "[amount] Movement"; "[amount] Visibility Range"

* Added survavalism promotions

* Updated Boarding Party strength bonus values to G&K

* Implemented requested changes
This commit is contained in:
Xander Lenstra 2021-06-30 16:09:02 +02:00 committed by GitHub
parent 8b7804b19a
commit a0cf30831c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 112 additions and 53 deletions

View File

@ -2,7 +2,7 @@
{
"name": "Heal Instantly",
"effect": "Heal this Unit by 50 HP; Doing so will consume this opportunity to choose a Promotion",
"uniques": ["Heal this Unit by [50] HP", "Doing so will consume this opportunity to choose a Promotion"],
"unitTypes": ["Melee","Mounted","Scout","Siege","Ranged","Armor","WaterMelee","WaterRanged","WaterSubmarine"]
},
@ -52,14 +52,14 @@
{
"name": "Extended Range",
"prerequisites": ["Accuracy III","Barrage III","Targeting II","Bombardment II", "Wolfpack II"],
"effect": "+1 Range",
"effect": "[+1] Range",
"unitTypes": ["Ranged","Siege","WaterRanged","WaterSubmarine"]
},
{
"name": "Indirect Fire",
"prerequisites": ["Accuracy III","Barrage III"],
"prerequisites": ["Accuracy III", "Barrage III", "Bombardment II", "Targeting II"],
"effect": "Ranged attacks may be performed over obstacles",
"unitTypes": ["Ranged","Siege"]
"unitTypes": ["Ranged","Siege","WaterRanged"]
},
// Melee, Mounted+Armor
@ -112,7 +112,7 @@
},
{
"name": "Formation I",
"prerequisites": ["Shock II","Drill II"],
"prerequisites": ["Shock II","Drill II"], // G&K also has Accuracy II & Barrage II as possible prerequisites for this, but I couldn't find a source for the unittypes
"effect": "+[33]% Strength vs [Mounted]",
"unitTypes": ["Melee","Mounted"]
},
@ -126,13 +126,15 @@
{
"name": "Blitz",
"prerequisites": ["Shock III","Drill III"],
"effect": "1 additional attack per turn",
"effect": "[1] additional attacks per turn",
"unitTypes": ["Melee","Mounted","Armor"]
},
{
"name": "Woodsman",
"prerequisites": ["Shock III","Drill III"],
"effect": "Double movement rate through Forest and Jungle",
"effect": "Double movement rate through Forest and Jungle",
// This could be generalized: ["-[50]% movement costs through [Forest] tiles", "-[50]% movement costs through [Jungle] tiles"],
// but with how getMovementCostBetweenAdjacentTiles() is optimized, that's difficult to implement.
"unitTypes": ["Melee"]
},
{
@ -143,33 +145,49 @@
},
{
"name": "Medic",
"prerequisites": ["Shock I", "Drill I", "Scouting II"],
"effect": "This unit and all others in adjacent tiles heal 5 additional HP per turn",
"prerequisites": ["Shock I", "Drill I", "Scouting II", "Survivalism II"],
"uniques": ["[+5] HP when healing", "All adjacent units heal [5] HP when healing"],
"unitTypes": ["Melee","Mounted","Scout"]
},
{
"name": "Medic II",
"prerequisites": ["Medic"],
"effect": "This unit and all others in adjacent tiles heal 5 additional HP. This unit heals 5 additional HP outside of friendly territory.",
"uniques": ["[+5] HP when healing", "All adjacent units heal [5] HP when healing",
"[+5] HP when healing in [Foreign Land] tiles"],
"unitTypes": ["Melee","Mounted","Scout"]
},
// Scout
{
"name": "Scouting I",
"effect": "+1 Visibility Range",
"effect": "[+1] Visibility Range",
"unitTypes": ["Scout"]
},
{
"name": "Scouting II",
"prerequisites": ["Scouting I"],
"effect": "+1 Movement",
"prerequisites": ["Scouting II"],
"effect": "[+1] Visibility Range",
"unitTypes": ["Scout"]
},
{
"name": "Scouting III",
"prerequisites": ["Scouting II"],
"effect": "+1 Visibility Range",
"prerequisites": ["Scouting I"],
"effect": "[+1] Movement",
"unitTypes": ["Scout"]
},
{
"name": "Survivalism I",
"uniques": ["[+5] HP when healing in [Foreign Land] tiles", "+[25]% Strength when defending"],
"unitTypes": ["Scout"]
},
{
"name": "Survivalism II",
"uniques": ["[+5] HP when healing in [Foreign Land] tiles", "+[25]% Strength when defending"],
"unitTypes": ["Scout"]
},
{
"name": "Survivalism III",
"uniques": ["Unit will heal every turn, even if it performs an action", "May withdraw before melee ([75]%)"], // This number is not based on any source
"unitTypes": ["Scout"]
},
@ -177,19 +195,19 @@
// Water melee
{
"name": "Boarding Party I",
"effect": "+[33]% Strength vs [water units]",
"effect": "+[15]% Strength vs [water units]",
"unitTypes": ["WaterMelee"]
},
{
"name": "Boarding Party II",
"prerequisites": ["Boarding Party I"],
"effect": "+[33]% Strength vs [water units]",
"effect": "+[15]% Strength vs [water units]",
"unitTypes": ["WaterMelee"]
},
{
"name": "Boarding Party III",
"prerequisites": ["Boarding Party II"],
"effect": "+[33]% Strength vs [water units]",
"effect": "+[15]% Strength vs [water units]",
"unitTypes": ["WaterMelee"]
},
@ -339,7 +357,7 @@
{
"name": "Operational Range",
"prerequisites": ["Interception I", /*"Dogfighting I",*/ "Siege I", "Bombardment I"],
"effect": "+2 Range",
"effect": "[+2] Range",
"unitTypes": ["Fighter","Bomber"]
},
{
@ -372,13 +390,13 @@
"name": "Mobility",
"prerequisites": ["Shock II","Drill II","Targeting I",
"Bombardment I","Boarding Party I", "Coastal Raider I", "Wolfpack I"],
"effect": "+1 Movement",
"effect": "+[1] Movement",
"unitTypes": ["Mounted","WaterMelee","WaterRanged","Armor","WaterSubmarine"]
},
{
"name": "Sentry",
"prerequisites": ["Accuracy I","Barrage I","Shock II","Drill II","Bombardment I","Targeting I","Boarding Party I","Coastal Raider I"],
"effect": "+1 Visibility Range",
"effect": "[+1] Visibility Range",
"unitTypes": ["Melee","Mounted","WaterRanged","Armor","WaterMelee"]
},
{

View File

@ -251,7 +251,7 @@
"requiredTech": "Bronze Working",
"obsoleteTech": "Civil Service",
"upgradesTo": "Pikeman",
"uniques": ["+[50]% Strength vs [Mounted]","+10 HP when healing"],
"uniques": ["+[50]% Strength vs [Mounted]","+[10] HP when healing"],
"attackSound": "metalhit"
},
/*
@ -493,7 +493,7 @@
"requiredResource": "Horses",
"upgradesTo": "Cavalry",
"obsoleteTech": "Military Science",
"uniques": ["Can move after attacking","No defensive terrain bonus", "Founds a new city", "+2 Visibility Range", "Defense bonus when embarked"],
"uniques": ["Can move after attacking","No defensive terrain bonus", "Founds a new city", "[+2] Visibility Range", "Defense bonus when embarked"],
"attackSound": "horse"
//Conquistador should have no penalty attacking cities
//Ability to found new cities can only be used on a foreign continent that does not contain the Spanish capital.
@ -674,7 +674,7 @@
"requiredTech": "Astronomy",
"upgradesTo": "Ironclad",
"obsoleteTech": "Combustion",
"uniques": ["+1 Visibility Range","May withdraw before melee ([80]%)"],
"uniques": ["[+1] Visibility Range", "May withdraw before melee ([80]%)"],
"hurryCostModifier": 30
},
{
@ -781,7 +781,7 @@
"rangedStrength": 35,
"cost": 185,
"requiredResource": "Iron",
"uniques": ["+1 Visibility Range"],
"uniques": ["[+1] Visibility Range"],
"requiredTech": "Navigation",
"obsoleteTech": "Electronics",
"upgradesTo": "Battleship",
@ -812,7 +812,7 @@
"requiredTech": "Metallurgy",
"requiredResource": "Horses",
"uniques": ["Can move after attacking","No defensive terrain bonus","Penalty vs City 33%",
"+1 Visibility Range", "No movement cost to pillage"],
"[+1] Visibility Range", "No movement cost to pillage"],
"promotions": ["Formation I"],
"upgradesTo": "Anti-Tank Gun",
"obsoleteTech": "Combined Arms",
@ -1289,7 +1289,7 @@
"cost": 425,
"requiredTech": "Telecommunications",
"uniques": ["+[75]% Strength when attacking", "Invisible to others", "Can only attack water",
"Can attack submarines", "Can enter ice tiles", "+1 Visibility Range", "Can carry [2] [Missile] units"]
"Can attack submarines", "Can enter ice tiles", "[+1] Visibility Range", "Can carry [2] [Missile] units"]
},
{
"name": "Mechanized Infantry",

View File

@ -6,7 +6,6 @@ import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.*
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.RoadStatus
import com.unciv.logic.map.TileInfo
import com.unciv.models.AttackableTile
@ -15,7 +14,6 @@ import com.unciv.models.ruleset.unit.UnitType
import com.unciv.models.stats.Stat
import com.unciv.models.translations.tr
import java.util.*
import kotlin.math.min
import kotlin.math.max
/**
@ -250,8 +248,7 @@ object Battle {
private fun reduceAttackerMovementPointsAndAttacks(attacker: ICombatant, defender: ICombatant) {
if (attacker is MapUnitCombatant) {
val unit = attacker.unit
if (unit.hasUnique("Can move after attacking")
|| (unit.hasUnique("1 additional attack per turn") && unit.attacksThisTurn == 0)) {
if (unit.hasUnique("Can move after attacking") || unit.maxAttacksPerTurn() > unit.attacksThisTurn) {
// if it was a melee attack and we won, then the unit ALREADY got movement points deducted,
// for the movement to the enemy's tile!
// and if it's an air unit, it only has 1 movement anyway, so...

View File

@ -141,8 +141,11 @@ class MapUnit {
if (isEmbarked()) return getEmbarkedMovement()
var movement = baseUnit.movement
movement += getUniques().count { it.text == "+1 Movement" }
movement += getMatchingUniques("[] Movement").sumBy { it.params[0].toInt() }
// Deprecated since 3.15.6
movement += getUniques().count { it.text == "+1 Movement" }
//
// Deprecated since 3.14.17
if (type.isMilitary() && type.isWaterUnit() && civInfo.hasUnique("All military naval units receive +1 movement and +1 sight")) {
movement += 1
@ -206,15 +209,21 @@ class MapUnit {
*/
private fun getVisibilityRange(): Int {
var visibilityRange = 2
visibilityRange += getUniques().count { it.text == "+1 Visibility Range" }
for (unique in civInfo.getMatchingUniques("+[] Sight for all [] units"))
if (matchesFilter(unique.params[1]))
visibilityRange += unique.params[0].toInt()
if (hasUnique("+2 Visibility Range")) visibilityRange += 2 // This shouldn't be stackable
visibilityRange += getMatchingUniques("[] Visibility Range").sumBy { it.params[0].toInt() }
if (hasUnique("Limited Visibility")) visibilityRange -= 1
// Deprecated since 3.15.6
visibilityRange += getUniques().count { it.text == "+1 Visibility Range" }
if (hasUnique("+2 Visibility Range")) visibilityRange += 2 // This shouldn't be stackable
//
// Deprecated since 3.15.1
if (civInfo.hasUnique("+1 Sight for all land military units") && type.isMilitary() && type.isLandUnit())
visibilityRange += 1
if (civInfo.hasUnique("+1 Sight for all land military units") && type.isMilitary() && type.isLandUnit())
visibilityRange += 1
//
// Deprecated since 3.14.17
if (type.isMilitary() && type.isWaterUnit() && civInfo.hasUnique("All military naval units receive +1 movement and +1 sight"))
@ -273,18 +282,28 @@ class MapUnit {
return true
}
fun maxAttacksPerTurn(): Int {
var maxAttacksPerTurn = 1 + getMatchingUniques("[] additional attacks per turn").sumBy { it.params[0].toInt() }
// Deprecated since 3.15.6
if (hasUnique("+1 additional attack per turn"))
maxAttacksPerTurn++
//
return maxAttacksPerTurn
}
fun canAttack(): Boolean {
if (currentMovement == 0f) return false
if (attacksThisTurn > 0 && !hasUnique("1 additional attack per turn")) return false
if (attacksThisTurn > 1) return false
return true
return attacksThisTurn < maxAttacksPerTurn()
}
fun getRange(): Int {
if (type.isMelee()) return 1
var range = baseUnit().range
if (hasUnique("+1 Range")) range++
if (hasUnique("+2 Range")) range += 2
// Deprecated since 3.15.6
if (hasUnique("+1 Range")) range++
if (hasUnique("+2 Range")) range += 2
//
range += getMatchingUniques("[] Range").sumBy { it.params[0].toInt() }
return range
}
@ -375,8 +394,11 @@ class MapUnit {
private fun adjacentHealingBonus(): Int {
var healingBonus = 0
if (hasUnique("This unit and all others in adjacent tiles heal 5 additional HP per turn")) healingBonus += 5
if (hasUnique("This unit and all others in adjacent tiles heal 5 additional HP. This unit heals 5 additional HP outside of friendly territory.")) healingBonus += 5
healingBonus += getMatchingUniques("All adjacent units heal [] HP when healing").sumBy { it.params[0].toInt() }
// Deprecated since 3.15.6
if (hasUnique("This unit and all others in adjacent tiles heal 5 additional HP per turn")) healingBonus += 5
if (hasUnique("This unit and all others in adjacent tiles heal 5 additional HP. This unit heals 5 additional HP outside of friendly territory.")) healingBonus += 5
//
return healingBonus
}
@ -507,7 +529,11 @@ class MapUnit {
var amountToHealBy = rankTileForHealing(getTile())
if (amountToHealBy == 0) return
if (hasUnique("+10 HP when healing")) amountToHealBy += 10
// Deprecated since 3.15.6
if (hasUnique("+10 HP when healing")) amountToHealBy += 10
//
amountToHealBy += getMatchingUniques("[] HP when healing").sumBy { it.params[0].toInt() }
val maxAdjacentHealingBonus = currentTile.getTilesInDistance(1)
.flatMap { it.getUnits().asSequence() }.map { it.adjacentHealingBonus() }.maxOrNull()
if (maxAdjacentHealingBonus != null)
@ -535,11 +561,20 @@ class MapUnit {
else -> 5 // Enemy territory
}
if (hasUnique("This unit and all others in adjacent tiles heal 5 additional HP. This unit heals 5 additional HP outside of friendly territory.")
&& !isFriendlyTerritory
&& healing > 0
)// Additional healing from medic is only applied when the unit is able to heal
healing += 5
// Deprecated since 3.15.6
if (hasUnique("This unit and all others in adjacent tiles heal 5 additional HP. This unit heals 5 additional HP outside of friendly territory.")
&& !isFriendlyTerritory
&& healing > 0
)// Additional healing from medic is only applied when the unit is able to heal
healing += 5
//
if (healing > 0) {
for (unique in getMatchingUniques("[] HP when healing in [] tiles")) {
if (tileInfo.matchesFilter(unique.params[1])) {
healing += unique.params[0].toInt()
}
}
}
return healing
}

View File

@ -415,8 +415,8 @@ open class TileInfo {
naturalWonder -> true
"Open terrain" -> !isRoughTerrain()
"Rough terrain" -> isRoughTerrain()
"Foreign Land" -> observingCiv != null && !isFriendlyTerritory(observingCiv)
"Friendly Land" -> observingCiv != null && isFriendlyTerritory(observingCiv)
"Foreign Land", "Foreign" -> observingCiv != null && !isFriendlyTerritory(observingCiv)
"Friendly Land", "Friendly" -> observingCiv != null && isFriendlyTerritory(observingCiv)
resource -> observingCiv != null && hasViewableResource(observingCiv)
else -> {
if (terrainFeatures.contains(filter)) return true

View File

@ -26,8 +26,12 @@ class UnitPromotions{
numberOfPromotions++
}
if(promotionName=="Heal Instantly") unit.healBy(50)
else promotions.add(promotionName)
val promotion = unit.civInfo.gameInfo.ruleSet.unitPromotions[promotionName]!!
doDirectPromotionEffects(promotion)
// This usage of a promotion name as its identifier is deprecated since 3.15.6
if (promotionName != "Heal Instantly" && promotion.uniqueObjects.none { it.placeholderText == "Doing so will consume this opportunity to choose a Promotion" })
promotions.add(promotionName)
unit.updateUniques()
@ -36,6 +40,11 @@ class UnitPromotions{
// So, if the addPromotion was triggered from there, simply don't update
unit.updateVisibleTiles() // some promotions/uniques give the unit bonus sight
}
fun doDirectPromotionEffects(promotion: Promotion) {
for (unique in promotion.uniqueObjects.filter { it.placeholderText == "Heal this unit by [] HP"})
unit.healBy(unique.params[0].toInt())
}
fun getAvailablePromotions(): List<Promotion> {
return unit.civInfo.gameInfo.ruleSet.unitPromotions.values