"Free great person" can no longer grant great people that are unique to another civ

Unified Great Person recognition
Better Great Person AI detection for unique units
This commit is contained in:
Yair Morgenstern
2020-12-27 11:05:11 +02:00
parent 713086849a
commit 87c6819462
7 changed files with 61 additions and 49 deletions

View File

@ -279,7 +279,7 @@ object UnitAutomation {
.firstOrNull { .firstOrNull {
val tile = it.currentTile val tile = it.currentTile
it.type == UnitType.Civilian && it.type == UnitType.Civilian &&
(it.hasUnique(Constants.settlerUnique) || unit.name in GreatPersonManager().statToGreatPersonMapping.values) (it.hasUnique(Constants.settlerUnique) || unit.isGreatPerson())
&& tile.militaryUnit == null && unit.movement.canMoveTo(tile) && unit.movement.canReach(tile) && tile.militaryUnit == null && unit.movement.canMoveTo(tile) && unit.movement.canReach(tile)
} }
if (settlerOrGreatPersonToAccompany == null) return false if (settlerOrGreatPersonToAccompany == null) return false

View File

@ -235,7 +235,7 @@ class CivilizationInfo {
//region Units //region Units
fun getCivUnits(): Sequence<MapUnit> = units.asSequence() fun getCivUnits(): Sequence<MapUnit> = units.asSequence()
fun getCivGreatPeople(): Sequence<MapUnit> = getCivUnits().filter { mapUnit -> mapUnit.hasUnique("Great Person - []") } fun getCivGreatPeople(): Sequence<MapUnit> = getCivUnits().filter { mapUnit -> mapUnit.isGreatPerson() }
fun addUnit(mapUnit: MapUnit, updateCivInfo: Boolean = true) { fun addUnit(mapUnit: MapUnit, updateCivInfo: Boolean = true) {
val newList = ArrayList(units) val newList = ArrayList(units)
@ -402,6 +402,10 @@ class CivilizationInfo {
} }
} }
fun getGreatPeople() = gameInfo.ruleSet.units.values.asSequence()
.filter { it.isGreatPerson() }.map { getEquivalentUnit(it.name) }.toHashSet()
//endregion //endregion
//region state-changing functions //region state-changing functions
@ -559,7 +563,7 @@ class CivilizationInfo {
if (!gameInfo.ruleSet.units.containsKey(unitName)) return if (!gameInfo.ruleSet.units.containsKey(unitName)) return
val unit = getEquivalentUnit(unitName) val unit = getEquivalentUnit(unitName)
placeUnitNearTile(cityToAddTo.location, unit.name) placeUnitNearTile(cityToAddTo.location, unit.name)
if (unit.uniques.any { it.equalsPlaceholderText("Great Person - []") }) if (unit.isGreatPerson())
addNotification("A [${unit.name}] has been born in [${cityToAddTo.name}]!", cityToAddTo.location, Color.GOLD) addNotification("A [${unit.name}] has been born in [${cityToAddTo.name}]!", cityToAddTo.location, Color.GOLD)
} }

View File

@ -502,7 +502,7 @@ class QuestManager {
val greatPeople = ruleSet.units.values val greatPeople = ruleSet.units.values
.asSequence() .asSequence()
.filter { baseUnit -> baseUnit.uniques.any { it.equalsPlaceholderText("Great Person - []") } } .filter { it.isGreatPerson() }
.map { it.getReplacedUnit(ruleSet) } .map { it.getReplacedUnit(ruleSet) }
.distinct() .distinct()
.filter { !challengerGreatPeople.contains(it) && !cityStateGreatPeople.contains(it) } .filter { !challengerGreatPeople.contains(it) && !cityStateGreatPeople.contains(it) }

View File

@ -304,6 +304,8 @@ class MapUnit {
fun canGarrison() = type.isMilitary() && type.isLandUnit() fun canGarrison() = type.isMilitary() && type.isLandUnit()
fun isGreatPerson() = baseUnit.isGreatPerson()
//endregion //endregion
//region state-changing functions //region state-changing functions

View File

@ -1,6 +1,5 @@
package com.unciv.models.ruleset package com.unciv.models.ruleset
import com.unciv.Constants
import com.unciv.logic.city.CityInfo import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.models.stats.Stats import com.unciv.models.stats.Stats
@ -59,14 +58,21 @@ object UniqueTriggerActivation {
"Free Great Person" -> { "Free Great Person" -> {
if (civInfo.isPlayerCivilization()) civInfo.greatPeople.freeGreatPeople++ if (civInfo.isPlayerCivilization()) civInfo.greatPeople.freeGreatPeople++
else { else {
val greatPeople = civInfo.getGreatPeople()
if (greatPeople.isEmpty()) return
var greatPerson = civInfo.getGreatPeople().random()
val preferredVictoryType = civInfo.victoryType() val preferredVictoryType = civInfo.victoryType()
val greatPerson = when (preferredVictoryType) { if (preferredVictoryType == VictoryType.Cultural) {
VictoryType.Cultural -> "Great Artist" val culturalGP = greatPeople.firstOrNull { it.uniques.contains("Great Person - [Culture]") }
VictoryType.Scientific -> "Great Scientist" if (culturalGP != null) greatPerson = culturalGP
else -> civInfo.gameInfo.ruleSet.units.values
.filter { it.uniqueObjects.any { it.placeholderText == "Great Person - []" } }.map { it.name }.random()
} }
civInfo.addUnit(greatPerson, chosenCity) if (preferredVictoryType == VictoryType.Scientific) {
val scientificGP = greatPeople.firstOrNull { it.uniques.contains("Great Person - [Science]") }
if (scientificGP != null) greatPerson = scientificGP
}
civInfo.addUnit(greatPerson.name, chosenCity)
} }
} }
"+1 population in each city" -> "+1 population in each city" ->

View File

@ -23,21 +23,21 @@ class BaseUnit : INamed, IConstruction {
var cost: Int = 0 var cost: Int = 0
var hurryCostModifier: Int = 0 var hurryCostModifier: Int = 0
var movement: Int = 0 var movement: Int = 0
var strength:Int = 0 var strength: Int = 0
var rangedStrength:Int = 0 var rangedStrength: Int = 0
var range:Int = 2 var range: Int = 2
var interceptRange = 0 var interceptRange = 0
lateinit var unitType: UnitType lateinit var unitType: UnitType
var requiredTech:String? = null var requiredTech: String? = null
var requiredResource:String? = null var requiredResource: String? = null
var uniques =HashSet<String>() var uniques = HashSet<String>()
val uniqueObjects: List<Unique> by lazy { uniques.map { Unique(it) } } val uniqueObjects: List<Unique> by lazy { uniques.map { Unique(it) } }
var promotions =HashSet<String>() var promotions = HashSet<String>()
var obsoleteTech:String?=null var obsoleteTech: String? = null
var upgradesTo:String? = null var upgradesTo: String? = null
var replaces:String?=null var replaces: String? = null
var uniqueTo:String?=null var uniqueTo: String? = null
var attackSound:String?=null var attackSound: String? = null
fun getShortDescription(): String { fun getShortDescription(): String {
val infoList = mutableListOf<String>() val infoList = mutableListOf<String>()
@ -51,28 +51,28 @@ class BaseUnit : INamed, IConstruction {
return infoList.joinToString() return infoList.joinToString()
} }
fun getDescription(forPickerScreen:Boolean): String { fun getDescription(forPickerScreen: Boolean): String {
val sb = StringBuilder() val sb = StringBuilder()
if(requiredResource!=null) sb.appendln("Consumes 1 [{$requiredResource}]".tr()) if (requiredResource != null) sb.appendln("Consumes 1 [{$requiredResource}]".tr())
if(!forPickerScreen) { if (!forPickerScreen) {
if(uniqueTo!=null) sb.appendln("Unique to [$uniqueTo], replaces [$replaces]".tr()) if (uniqueTo != null) sb.appendln("Unique to [$uniqueTo], replaces [$replaces]".tr())
else sb.appendln("{Cost}: $cost".tr()) else sb.appendln("{Cost}: $cost".tr())
if(requiredTech!=null) sb.appendln("Required tech: [$requiredTech]".tr()) if (requiredTech != null) sb.appendln("Required tech: [$requiredTech]".tr())
if(upgradesTo!=null) sb.appendln("Upgrades to [$upgradesTo]".tr()) if (upgradesTo != null) sb.appendln("Upgrades to [$upgradesTo]".tr())
if(obsoleteTech!=null) sb.appendln("Obsolete with [$obsoleteTech]".tr()) if (obsoleteTech != null) sb.appendln("Obsolete with [$obsoleteTech]".tr())
} }
if(strength!=0) { if (strength != 0) {
sb.append("$strength${Fonts.strength}, ") sb.append("$strength${Fonts.strength}, ")
if (rangedStrength != 0) sb.append("$rangedStrength${Fonts.rangedStrength}, ") if (rangedStrength != 0) sb.append("$rangedStrength${Fonts.rangedStrength}, ")
if (rangedStrength != 0) sb.append("$range${Fonts.range}, ") if (rangedStrength != 0) sb.append("$range${Fonts.range}, ")
} }
sb.appendln("$movement${Fonts.movement}") sb.appendln("$movement${Fonts.movement}")
for(unique in uniques) for (unique in uniques)
sb.appendln(Translations.translateBonusOrPenalty(unique)) sb.appendln(Translations.translateBonusOrPenalty(unique))
if (promotions.isNotEmpty()) { if (promotions.isNotEmpty()) {
sb.append((if (promotions.size==1) "Free promotion:" else "Free promotions:").tr()) sb.append((if (promotions.size == 1) "Free promotion:" else "Free promotions:").tr())
sb.appendln(promotions.joinToString(", ", " ") { it.tr() }) sb.appendln(promotions.joinToString(", ", " ") { it.tr() })
} }
@ -110,11 +110,11 @@ class BaseUnit : INamed, IConstruction {
return (cost / 10).toInt() * 10 // rounded down o nearest ten return (cost / 10).toInt() * 10 // rounded down o nearest ten
} }
fun getDisbandGold() = getBaseGoldCost().toInt()/20 fun getDisbandGold() = getBaseGoldCost().toInt() / 20
override fun shouldBeDisplayed(construction: CityConstructions): Boolean { override fun shouldBeDisplayed(construction: CityConstructions): Boolean {
val rejectionReason = getRejectionReason(construction) val rejectionReason = getRejectionReason(construction)
return rejectionReason=="" return rejectionReason == ""
|| rejectionReason.startsWith("Requires") || rejectionReason.startsWith("Requires")
|| rejectionReason.startsWith("Consumes") || rejectionReason.startsWith("Consumes")
} }
@ -122,7 +122,7 @@ class BaseUnit : INamed, IConstruction {
fun getRejectionReason(construction: CityConstructions): String { fun getRejectionReason(construction: CityConstructions): String {
if (unitType.isWaterUnit() && !construction.cityInfo.getCenterTile().isCoastalTile()) if (unitType.isWaterUnit() && !construction.cityInfo.getCenterTile().isCoastalTile())
return "Can only build water units in coastal cities" return "Can only build water units in coastal cities"
for (unique in uniqueObjects.filter { it.placeholderText == "Not displayed as an available construction without []"}) { for (unique in uniqueObjects.filter { it.placeholderText == "Not displayed as an available construction without []" }) {
val filter = unique.params[0] val filter = unique.params[0]
if ((filter in construction.cityInfo.civInfo.gameInfo.ruleSet.tileResources && !construction.cityInfo.civInfo.hasResource(filter)) if ((filter in construction.cityInfo.civInfo.gameInfo.ruleSet.tileResources && !construction.cityInfo.civInfo.hasResource(filter))
|| (filter in construction.cityInfo.civInfo.gameInfo.ruleSet.buildings && !construction.containsBuildingOrEquivalent(filter))) || (filter in construction.cityInfo.civInfo.gameInfo.ruleSet.buildings && !construction.containsBuildingOrEquivalent(filter)))
@ -135,10 +135,10 @@ class BaseUnit : INamed, IConstruction {
fun getRejectionReason(civInfo: CivilizationInfo): String { fun getRejectionReason(civInfo: CivilizationInfo): String {
if (uniques.contains("Unbuildable")) return "Unbuildable" if (uniques.contains("Unbuildable")) return "Unbuildable"
if (requiredTech!=null && !civInfo.tech.isResearched(requiredTech!!)) return "$requiredTech not researched" if (requiredTech != null && !civInfo.tech.isResearched(requiredTech!!)) return "$requiredTech not researched"
if (obsoleteTech!=null && civInfo.tech.isResearched(obsoleteTech!!)) return "Obsolete by $obsoleteTech" if (obsoleteTech != null && civInfo.tech.isResearched(obsoleteTech!!)) return "Obsolete by $obsoleteTech"
if (uniqueTo!=null && uniqueTo!=civInfo.civName) return "Unique to $uniqueTo" if (uniqueTo != null && uniqueTo != civInfo.civName) return "Unique to $uniqueTo"
if (civInfo.gameInfo.ruleSet.units.values.any { it.uniqueTo==civInfo.civName && it.replaces==name }) return "Our unique unit replaces this" if (civInfo.gameInfo.ruleSet.units.values.any { it.uniqueTo == civInfo.civName && it.replaces == name }) return "Our unique unit replaces this"
if (!civInfo.gameInfo.gameParameters.nuclearWeaponsEnabled if (!civInfo.gameInfo.gameParameters.nuclearWeaponsEnabled
&& uniques.contains("Nuclear weapon")) return "Disabled by setting" && uniques.contains("Nuclear weapon")) return "Disabled by setting"
for (unique in uniqueObjects.filter { it.placeholderText == "Requires []" }) { for (unique in uniqueObjects.filter { it.placeholderText == "Requires []" }) {
@ -147,13 +147,13 @@ class BaseUnit : INamed, IConstruction {
if (civInfo.cities.none { it.cityConstructions.containsBuildingOrEquivalent(filter) }) return unique.text // Wonder is not built if (civInfo.cities.none { it.cityConstructions.containsBuildingOrEquivalent(filter) }) return unique.text // Wonder is not built
} else if (!civInfo.policies.adoptedPolicies.contains(filter)) return "Policy is not adopted" } else if (!civInfo.policies.adoptedPolicies.contains(filter)) return "Policy is not adopted"
} }
if (requiredResource!=null && !civInfo.hasResource(requiredResource!!) && !civInfo.gameInfo.gameParameters.godMode) return "Consumes 1 [$requiredResource]" if (requiredResource != null && !civInfo.hasResource(requiredResource!!) && !civInfo.gameInfo.gameParameters.godMode) return "Consumes 1 [$requiredResource]"
if (uniques.contains(Constants.settlerUnique) && civInfo.isCityState()) return "No settler for city-states" if (uniques.contains(Constants.settlerUnique) && civInfo.isCityState()) return "No settler for city-states"
if (uniques.contains(Constants.settlerUnique) && civInfo.isOneCityChallenger()) return "No settler for players in One City Challenge" if (uniques.contains(Constants.settlerUnique) && civInfo.isOneCityChallenger()) return "No settler for players in One City Challenge"
return "" return ""
} }
fun isBuildable(civInfo: CivilizationInfo) = getRejectionReason(civInfo)=="" fun isBuildable(civInfo: CivilizationInfo) = getRejectionReason(civInfo) == ""
override fun isBuildable(cityConstructions: CityConstructions): Boolean { override fun isBuildable(cityConstructions: CityConstructions): Boolean {
return getRejectionReason(cityConstructions) == "" return getRejectionReason(cityConstructions) == ""
@ -192,7 +192,7 @@ class BaseUnit : INamed, IConstruction {
} }
// This is to be deprecated and converted to "All newly-trained [] in this city receive the [] promotion" - keeping it here to that mods with this can still work for now // This is to be deprecated and converted to "All newly-trained [] in this city receive the [] promotion" - keeping it here to that mods with this can still work for now
if (unit.type in listOf(UnitType.Melee,UnitType.Mounted,UnitType.Armor) if (unit.type in listOf(UnitType.Melee, UnitType.Mounted, UnitType.Armor)
&& construction.cityInfo.containsBuildingUnique("All newly-trained melee, mounted, and armored units in this city receive the Drill I promotion")) && construction.cityInfo.containsBuildingUnique("All newly-trained melee, mounted, and armored units in this city receive the Drill I promotion"))
unit.promotions.addPromotion("Drill I", isFree = true) unit.promotions.addPromotion("Drill I", isFree = true)
@ -201,7 +201,7 @@ class BaseUnit : INamed, IConstruction {
override fun getResource(): String? = requiredResource override fun getResource(): String? = requiredResource
fun getDirectUpgradeUnit(civInfo: CivilizationInfo):BaseUnit{ fun getDirectUpgradeUnit(civInfo: CivilizationInfo): BaseUnit {
return civInfo.getEquivalentUnit(upgradesTo!!) return civInfo.getEquivalentUnit(upgradesTo!!)
} }
@ -212,7 +212,7 @@ class BaseUnit : INamed, IConstruction {
else ruleset.units[replaces!!]!! else ruleset.units[replaces!!]!!
} }
fun matchesFilter(filter:String):Boolean { fun matchesFilter(filter: String): Boolean {
if (filter == unitType.name) return true if (filter == unitType.name) return true
if (filter == name) return true if (filter == name) return true
if (filter == "All") return true if (filter == "All") return true
@ -223,4 +223,6 @@ class BaseUnit : INamed, IConstruction {
if ((filter == "military" || filter == "Military" || filter == "military units") && unitType.isMilitary()) return true if ((filter == "military" || filter == "Military" || filter == "military units") && unitType.isMilitary()) return true
return false return false
} }
fun isGreatPerson() = uniqueObjects.any { it.placeholderText == "Great Person - []" }
} }

View File

@ -18,9 +18,7 @@ class GreatPersonPickerScreen(val civInfo:CivilizationInfo) : PickerScreen() {
closeButton.isVisible=false closeButton.isVisible=false
rightSideButton.setText("Choose a free great person".tr()) rightSideButton.setText("Choose a free great person".tr())
val greatPersonNames = GreatPersonManager().statToGreatPersonMapping.values val greatPersonUnits = civInfo.getGreatPeople()
.union(listOf("Great General"))
val greatPersonUnits = greatPersonNames.map { civInfo.getEquivalentUnit(it) }
for (unit in greatPersonUnits) for (unit in greatPersonUnits)
{ {
val button = Button(skin) val button = Button(skin)