mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-11 00:08:58 +07:00
Unique enum compliance detection (#5226)
* Added basic functionality for uniques enum * Added unique type to Unique class for faster enum comparisons * And Elvis operator for unknown parameter type * Resolved #5162 - AI much less motivated to attack city-states * Whoops, wrong branch * New unique checks, with enum. * Fixed Trog's comments
This commit is contained in:
@ -323,6 +323,19 @@ class Ruleset {
|
|||||||
for (building in buildings.values) {
|
for (building in buildings.values) {
|
||||||
if (building.requiredTech == null && building.cost == 0 && !building.uniques.contains("Unbuildable"))
|
if (building.requiredTech == null && building.cost == 0 && !building.uniques.contains("Unbuildable"))
|
||||||
lines += "${building.name} is buildable and therefore must either have an explicit cost or reference an existing tech!"
|
lines += "${building.name} is buildable and therefore must either have an explicit cost or reference an existing tech!"
|
||||||
|
|
||||||
|
for (unique in building.uniqueObjects) {
|
||||||
|
if (unique.type == null) continue
|
||||||
|
val complianceErrors = unique.type.getComplianceErrors(unique, this)
|
||||||
|
for (complianceError in complianceErrors) {
|
||||||
|
// When not checking the entire ruleset, we can only really detect ruleset-invariant errors
|
||||||
|
if (complianceError.errorSeverity == UniqueType.UniqueComplianceErrorSeverity.RulesetInvariant)
|
||||||
|
lines += "${building.name}'s unique \"${unique.text}\" contains parameter ${complianceError.parameterName}," +
|
||||||
|
" which does not fit parameter type" +
|
||||||
|
" ${complianceError.acceptableParameterTypes.joinToString(" or ") { it.parameterName }} !"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (nation in nations.values) {
|
for (nation in nations.values) {
|
||||||
|
@ -1,26 +1,35 @@
|
|||||||
package com.unciv.models.ruleset
|
package com.unciv.models.ruleset
|
||||||
|
|
||||||
import com.badlogic.gdx.math.Vector2
|
|
||||||
import com.unciv.logic.city.CityInfo
|
|
||||||
import com.unciv.logic.civilization.*
|
|
||||||
import com.unciv.logic.map.MapUnit
|
|
||||||
import com.unciv.logic.map.TileInfo
|
|
||||||
import com.unciv.models.stats.Stat
|
|
||||||
import com.unciv.models.stats.Stats
|
import com.unciv.models.stats.Stats
|
||||||
import com.unciv.models.translations.fillPlaceholders
|
|
||||||
import com.unciv.models.translations.getPlaceholderParameters
|
import com.unciv.models.translations.getPlaceholderParameters
|
||||||
import com.unciv.models.translations.getPlaceholderText
|
import com.unciv.models.translations.getPlaceholderText
|
||||||
import com.unciv.models.translations.hasPlaceholderParameters
|
|
||||||
import com.unciv.ui.worldscreen.unit.UnitActions
|
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
|
|
||||||
// parameterName values should be compliant with autogenerated values in TranslationFileWriter.generateStringsFromJSONs
|
// parameterName values should be compliant with autogenerated values in TranslationFileWriter.generateStringsFromJSONs
|
||||||
// Eventually we'll merge the translation generation to take this as the source of that
|
// Eventually we'll merge the translation generation to take this as the source of that
|
||||||
enum class UniqueParameterType(val parameterName:String, val complianceCheck:(String, Ruleset)->Boolean) {
|
enum class UniqueParameterType(val parameterName:String) {
|
||||||
Number("amount", { s, r -> s.toIntOrNull() != null }),
|
Number("amount") {
|
||||||
UnitFilter("unitType", { s, r -> r.unitTypes.containsKey(s) || unitTypeStrings.contains(s) }),
|
override fun getErrorType(parameterText: String, ruleset: Ruleset):
|
||||||
Unknown("",{s,r -> true});
|
UniqueType.UniqueComplianceErrorSeverity? {
|
||||||
|
return if (parameterText.toIntOrNull() != null) UniqueType.UniqueComplianceErrorSeverity.RulesetInvariant
|
||||||
|
else null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
UnitFilter("unitType") {
|
||||||
|
override fun getErrorType(parameterText: String, ruleset: Ruleset):
|
||||||
|
UniqueType.UniqueComplianceErrorSeverity? {
|
||||||
|
if(ruleset.unitTypes.containsKey(parameterText) || unitTypeStrings.contains(parameterText)) return null
|
||||||
|
return UniqueType.UniqueComplianceErrorSeverity.RulesetSpecific
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Unknown("param") {
|
||||||
|
override fun getErrorType(parameterText: String, ruleset: Ruleset):
|
||||||
|
UniqueType.UniqueComplianceErrorSeverity? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
abstract fun getErrorType(parameterText:String, ruleset: Ruleset): UniqueType.UniqueComplianceErrorSeverity?
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val unitTypeStrings = hashSetOf(
|
val unitTypeStrings = hashSetOf(
|
||||||
@ -42,6 +51,13 @@ enum class UniqueParameterType(val parameterName:String, val complianceCheck:(St
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class UniqueComplianceError(
|
||||||
|
val parameterName: String,
|
||||||
|
val acceptableParameterTypes: List<UniqueParameterType>,
|
||||||
|
val errorSeverity: UniqueType.UniqueComplianceErrorSeverity
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
enum class UniqueType(val text:String) {
|
enum class UniqueType(val text:String) {
|
||||||
|
|
||||||
ConsumesResources("Consumes [amount] [resource]");
|
ConsumesResources("Consumes [amount] [resource]");
|
||||||
@ -61,11 +77,35 @@ enum class UniqueType(val text:String) {
|
|||||||
|
|
||||||
val placeholderText = text.getPlaceholderText()
|
val placeholderText = text.getPlaceholderText()
|
||||||
|
|
||||||
fun checkCompliance(unique: Unique, ruleset: Ruleset): Boolean {
|
/** Ordinal determines severity - ordered from most severe at 0 */
|
||||||
for ((index, param) in unique.params.withIndex())
|
enum class UniqueComplianceErrorSeverity {
|
||||||
if (parameterTypeMap[index].none { it.complianceCheck(param, ruleset) })
|
|
||||||
return false
|
/** This is a problem like "numbers don't parse", "stat isn't stat", "city filter not applicable" */
|
||||||
return true
|
RulesetInvariant,
|
||||||
|
|
||||||
|
/** This is a problem like "unit/resource/tech name doesn't exist in ruleset" - definite bug */
|
||||||
|
RulesetSpecific,
|
||||||
|
|
||||||
|
/** This is for filters that can also potentially accept free text, like UnitFilter and TileFilter */
|
||||||
|
WarningOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Maps uncompliant parameters to their required types */
|
||||||
|
fun getComplianceErrors(
|
||||||
|
unique: Unique,
|
||||||
|
ruleset: Ruleset
|
||||||
|
): List<UniqueComplianceError> {
|
||||||
|
val errorList = ArrayList<UniqueComplianceError>()
|
||||||
|
for ((index, param) in unique.params.withIndex()) {
|
||||||
|
val acceptableParamTypes = parameterTypeMap[index]
|
||||||
|
val errorTypesForAcceptableParameters =
|
||||||
|
acceptableParamTypes.map { it.getErrorType(param, ruleset) }
|
||||||
|
if (errorTypesForAcceptableParameters.any { it == null }) continue // This matches one of the types!
|
||||||
|
val leastSevereWarning =
|
||||||
|
errorTypesForAcceptableParameters.maxByOrNull { it!!.ordinal }!!
|
||||||
|
errorList += UniqueComplianceError(param, acceptableParamTypes, leastSevereWarning)
|
||||||
|
}
|
||||||
|
return errorList
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,8 +126,10 @@ class Unique(val text:String) {
|
|||||||
|
|
||||||
|
|
||||||
fun isOfType(uniqueType: UniqueType) = uniqueType == type
|
fun isOfType(uniqueType: UniqueType) = uniqueType == type
|
||||||
|
|
||||||
|
/** We can't save compliance errors in the unique, since it's ruleset-dependant */
|
||||||
fun matches(uniqueType: UniqueType, ruleset: Ruleset) = isOfType(uniqueType)
|
fun matches(uniqueType: UniqueType, ruleset: Ruleset) = isOfType(uniqueType)
|
||||||
&& uniqueType.checkCompliance(this, ruleset)
|
&& uniqueType.getComplianceErrors(this, ruleset).isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
class UniqueMap:HashMap<String, ArrayList<Unique>>() {
|
class UniqueMap:HashMap<String, ArrayList<Unique>>() {
|
||||||
@ -103,477 +145,4 @@ class UniqueMap:HashMap<String, ArrayList<Unique>>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getAllUniques() = this.asSequence().flatMap { it.value.asSequence() }
|
fun getAllUniques() = this.asSequence().flatMap { it.value.asSequence() }
|
||||||
}
|
|
||||||
|
|
||||||
// Buildings, techs, policies, ancient ruins and promotions can have 'triggered' effects
|
|
||||||
object UniqueTriggerActivation {
|
|
||||||
/** @return boolean whether an action was successfully preformed */
|
|
||||||
fun triggerCivwideUnique(
|
|
||||||
unique: Unique,
|
|
||||||
civInfo: CivilizationInfo,
|
|
||||||
cityInfo: CityInfo? = null,
|
|
||||||
tile: TileInfo? = null,
|
|
||||||
notification: String? = null
|
|
||||||
): Boolean {
|
|
||||||
val chosenCity =
|
|
||||||
if (cityInfo != null) cityInfo
|
|
||||||
else civInfo.cities.firstOrNull { it.isCapital() }
|
|
||||||
val tileBasedRandom =
|
|
||||||
if (tile != null) Random(tile.position.toString().hashCode())
|
|
||||||
else Random(-550) // Very random indeed
|
|
||||||
when (unique.placeholderText) {
|
|
||||||
"Free [] appears" -> {
|
|
||||||
val unitName = unique.params[0]
|
|
||||||
val unit = civInfo.gameInfo.ruleSet.units[unitName]
|
|
||||||
if (chosenCity == null || unit == null || (unit.uniques.contains("Founds a new city") && civInfo.isOneCityChallenger()))
|
|
||||||
return false
|
|
||||||
|
|
||||||
val placedUnit = civInfo.addUnit(unitName, chosenCity)
|
|
||||||
if (notification != null && placedUnit != null) {
|
|
||||||
civInfo.addNotification(
|
|
||||||
notification,
|
|
||||||
placedUnit.getTile().position,
|
|
||||||
placedUnit.name
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
"[] free [] units appear" -> {
|
|
||||||
val unitName = unique.params[1]
|
|
||||||
val unit = civInfo.gameInfo.ruleSet.units[unitName]
|
|
||||||
if (chosenCity == null || unit == null || (unit.uniques.contains("Founds a new city") && civInfo.isOneCityChallenger()))
|
|
||||||
return false
|
|
||||||
|
|
||||||
val tilesUnitsWerePlacedOn: MutableList<Vector2> = mutableListOf()
|
|
||||||
for (i in 1..unique.params[0].toInt()) {
|
|
||||||
val placedUnit = civInfo.addUnit(unitName, chosenCity)
|
|
||||||
if (placedUnit != null)
|
|
||||||
tilesUnitsWerePlacedOn.add(placedUnit.getTile().position)
|
|
||||||
}
|
|
||||||
if (notification != null && tilesUnitsWerePlacedOn.isNotEmpty()) {
|
|
||||||
civInfo.addNotification(
|
|
||||||
notification,
|
|
||||||
LocationAction(tilesUnitsWerePlacedOn),
|
|
||||||
civInfo.getEquivalentUnit(unit).name
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// Differs from "Free [] appears" in that it spawns near the ruins instead of in a city
|
|
||||||
"Free [] found in the ruins" -> {
|
|
||||||
val unit = civInfo.getEquivalentUnit(unique.params[0])
|
|
||||||
val placingTile =
|
|
||||||
tile ?: civInfo.cities.random().getCenterTile()
|
|
||||||
|
|
||||||
val placedUnit = civInfo.placeUnitNearTile(placingTile.position, unit.name)
|
|
||||||
if (notification != null && placedUnit != null) {
|
|
||||||
val notificationText =
|
|
||||||
if (notification.hasPlaceholderParameters())
|
|
||||||
notification.fillPlaceholders(unique.params[0])
|
|
||||||
else notification
|
|
||||||
civInfo.addNotification(
|
|
||||||
notificationText,
|
|
||||||
placedUnit.getTile().position,
|
|
||||||
placedUnit.name
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return placedUnit != null
|
|
||||||
}
|
|
||||||
|
|
||||||
// spectators get all techs at start of game, and if (in a mod) a tech gives a free policy, the game gets stuck on the policy picker screen
|
|
||||||
"Free Social Policy" -> {
|
|
||||||
if (civInfo.isSpectator()) return false
|
|
||||||
civInfo.policies.freePolicies++
|
|
||||||
if (notification != null) {
|
|
||||||
civInfo.addNotification(notification, NotificationIcon.Culture)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
"[] Free Social Policies" -> {
|
|
||||||
if (civInfo.isSpectator()) return false
|
|
||||||
civInfo.policies.freePolicies += unique.params[0].toInt()
|
|
||||||
if (notification != null) {
|
|
||||||
civInfo.addNotification(notification, NotificationIcon.Culture)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
"Empire enters golden age" -> {
|
|
||||||
civInfo.goldenAges.enterGoldenAge()
|
|
||||||
if (notification != null) {
|
|
||||||
civInfo.addNotification(notification, NotificationIcon.Happiness)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
"Free Great Person" -> {
|
|
||||||
if (civInfo.isSpectator()) return false
|
|
||||||
if (civInfo.isPlayerCivilization()) {
|
|
||||||
civInfo.greatPeople.freeGreatPeople++
|
|
||||||
if (notification != null)
|
|
||||||
civInfo.addNotification(notification) // Anyone an idea for a good icon?
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
val greatPeople = civInfo.getGreatPeople()
|
|
||||||
if (greatPeople.isEmpty()) return false
|
|
||||||
var greatPerson = civInfo.getGreatPeople().random()
|
|
||||||
|
|
||||||
val preferredVictoryType = civInfo.victoryType()
|
|
||||||
if (preferredVictoryType == VictoryType.Cultural) {
|
|
||||||
val culturalGP =
|
|
||||||
greatPeople.firstOrNull { it.uniques.contains("Great Person - [Culture]") }
|
|
||||||
if (culturalGP != null) greatPerson = culturalGP
|
|
||||||
}
|
|
||||||
if (preferredVictoryType == VictoryType.Scientific) {
|
|
||||||
val scientificGP =
|
|
||||||
greatPeople.firstOrNull { it.uniques.contains("Great Person - [Science]") }
|
|
||||||
if (scientificGP != null) greatPerson = scientificGP
|
|
||||||
}
|
|
||||||
|
|
||||||
return civInfo.addUnit(greatPerson.name, chosenCity) != null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"[] population []" -> {
|
|
||||||
val citiesWithPopulationChanged: MutableList<Vector2> = mutableListOf()
|
|
||||||
for (city in civInfo.cities) {
|
|
||||||
if (city.matchesFilter(unique.params[1])) {
|
|
||||||
city.population.addPopulation(unique.params[0].toInt())
|
|
||||||
citiesWithPopulationChanged.add(city.location)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (notification != null && citiesWithPopulationChanged.isNotEmpty())
|
|
||||||
civInfo.addNotification(
|
|
||||||
notification,
|
|
||||||
LocationAction(citiesWithPopulationChanged),
|
|
||||||
NotificationIcon.Population
|
|
||||||
)
|
|
||||||
return citiesWithPopulationChanged.isNotEmpty()
|
|
||||||
}
|
|
||||||
"[] population in a random city" -> {
|
|
||||||
if (civInfo.cities.isEmpty()) return false
|
|
||||||
val randomCity = civInfo.cities.random(tileBasedRandom)
|
|
||||||
randomCity.population.addPopulation(unique.params[0].toInt())
|
|
||||||
if (notification != null) {
|
|
||||||
val notificationText =
|
|
||||||
if (notification.hasPlaceholderParameters())
|
|
||||||
notification.fillPlaceholders(randomCity.name)
|
|
||||||
else notification
|
|
||||||
civInfo.addNotification(
|
|
||||||
notificationText,
|
|
||||||
randomCity.location,
|
|
||||||
NotificationIcon.Population
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
"Free Technology" -> {
|
|
||||||
if (civInfo.isSpectator()) return false
|
|
||||||
civInfo.tech.freeTechs += 1
|
|
||||||
if (notification != null) {
|
|
||||||
civInfo.addNotification(notification, NotificationIcon.Science)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
"[] Free Technologies" -> {
|
|
||||||
if (civInfo.isSpectator()) return false
|
|
||||||
civInfo.tech.freeTechs += unique.params[0].toInt()
|
|
||||||
if (notification != null) {
|
|
||||||
civInfo.addNotification(notification, NotificationIcon.Science)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
"[] free random researchable Tech(s) from the []" -> {
|
|
||||||
val researchableTechsFromThatEra = civInfo.gameInfo.ruleSet.technologies.values
|
|
||||||
.filter {
|
|
||||||
(it.column!!.era == unique.params[1] || unique.params[1] == "any era")
|
|
||||||
&& civInfo.tech.canBeResearched(it.name)
|
|
||||||
}
|
|
||||||
if (researchableTechsFromThatEra.isEmpty()) return false
|
|
||||||
|
|
||||||
val techsToResearch = researchableTechsFromThatEra.shuffled(tileBasedRandom)
|
|
||||||
.take(unique.params[0].toInt())
|
|
||||||
for (tech in techsToResearch)
|
|
||||||
civInfo.tech.addTechnology(tech.name)
|
|
||||||
|
|
||||||
if (notification != null) {
|
|
||||||
val notificationText =
|
|
||||||
if (notification.hasPlaceholderParameters())
|
|
||||||
notification.fillPlaceholders(*(techsToResearch.map { it.name }
|
|
||||||
.toTypedArray()))
|
|
||||||
else notification
|
|
||||||
civInfo.addNotification(notificationText, NotificationIcon.Science)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
"Quantity of strategic resources produced by the empire increased by 100%" -> {
|
|
||||||
civInfo.updateDetailedCivResources()
|
|
||||||
if (notification != null) {
|
|
||||||
civInfo.addNotification(
|
|
||||||
notification,
|
|
||||||
NotificationIcon.War
|
|
||||||
) // I'm open for better icons
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
"+[]% attack strength to all [] Units for [] turns" -> {
|
|
||||||
civInfo.temporaryUniques.add(Pair(unique, unique.params[2].toInt()))
|
|
||||||
if (notification != null) {
|
|
||||||
civInfo.addNotification(notification, NotificationIcon.War)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
"Reveals the entire map" -> {
|
|
||||||
if (notification != null) {
|
|
||||||
civInfo.addNotification(notification, "UnitIcons/Scout")
|
|
||||||
}
|
|
||||||
return civInfo.exploredTiles.addAll(
|
|
||||||
civInfo.gameInfo.tileMap.values.asSequence().map { it.position })
|
|
||||||
}
|
|
||||||
|
|
||||||
"[] units gain the [] promotion" -> {
|
|
||||||
val filter = unique.params[0]
|
|
||||||
val promotion = unique.params[1]
|
|
||||||
|
|
||||||
val promotedUnitLocations: MutableList<Vector2> = mutableListOf()
|
|
||||||
for (unit in civInfo.getCivUnits()) {
|
|
||||||
if (unit.matchesFilter(filter)
|
|
||||||
&& civInfo.gameInfo.ruleSet.unitPromotions.values.any {
|
|
||||||
it.name == promotion && unit.type.name in it.unitTypes
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
unit.promotions.addPromotion(promotion, isFree = true)
|
|
||||||
promotedUnitLocations.add(unit.getTile().position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (notification != null) {
|
|
||||||
civInfo.addNotification(
|
|
||||||
notification,
|
|
||||||
LocationAction(promotedUnitLocations),
|
|
||||||
"unitPromotionIcons/${unique.params[1]}"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return promotedUnitLocations.isNotEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
"Allied City-States will occasionally gift Great People" -> {
|
|
||||||
civInfo.addFlag(
|
|
||||||
CivFlags.CityStateGreatPersonGift.name,
|
|
||||||
civInfo.turnsForGreatPersonFromCityState() / 2
|
|
||||||
)
|
|
||||||
if (notification != null) {
|
|
||||||
civInfo.addNotification(notification, NotificationIcon.CityState)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// The mechanics for granting great people are wonky, but basically the following happens:
|
|
||||||
// Based on the game speed, a timer with some amount of turns is set, 40 on regular speed
|
|
||||||
// Every turn, 1 is subtracted from this timer, as long as you have at least 1 city state ally
|
|
||||||
// So no, the number of city-state allies does not matter for this. You have a global timer for all of them combined.
|
|
||||||
// If the timer reaches the amount of city-state allies you have (or 10, whichever is lower), it is reset.
|
|
||||||
// You will then receive a random great person from a random city-state you are allied to
|
|
||||||
// The very first time after acquiring this policy, the timer is set to half of its normal value
|
|
||||||
// This is the basics, and apart from this, there is some randomness in the exact turn count, but I don't know how much
|
|
||||||
|
|
||||||
// There is surprisingly little information findable online about this policy, and the civ 5 source files are
|
|
||||||
// also quite though to search through, so this might all be incorrect.
|
|
||||||
// For now this mechanic seems decent enough that this is fine.
|
|
||||||
|
|
||||||
// Note that the way this is implemented now, this unique does NOT stack
|
|
||||||
// I could parametrize the [Allied], but eh.
|
|
||||||
|
|
||||||
"Gain [] []" -> {
|
|
||||||
if (Stat.values().none { it.name == unique.params[1] }) return false
|
|
||||||
val stat = Stat.valueOf(unique.params[1])
|
|
||||||
|
|
||||||
if (stat !in listOf(Stat.Gold, Stat.Faith, Stat.Science, Stat.Culture)
|
|
||||||
|| unique.params[0].toIntOrNull() == null
|
|
||||||
) return false
|
|
||||||
|
|
||||||
civInfo.addStat(stat, unique.params[0].toInt())
|
|
||||||
if (notification != null)
|
|
||||||
civInfo.addNotification(notification, stat.notificationIcon)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
"Gain []-[] []" -> {
|
|
||||||
if (Stat.values().none { it.name == unique.params[2] }) return false
|
|
||||||
val stat = Stat.valueOf(unique.params[2])
|
|
||||||
|
|
||||||
if (stat !in listOf(Stat.Gold, Stat.Faith, Stat.Science, Stat.Culture)
|
|
||||||
|| unique.params[0].toIntOrNull() == null
|
|
||||||
|| unique.params[1].toIntOrNull() == null
|
|
||||||
) return false
|
|
||||||
|
|
||||||
val foundStatAmount =
|
|
||||||
(tileBasedRandom.nextInt(unique.params[0].toInt(), unique.params[1].toInt()) *
|
|
||||||
civInfo.gameInfo.gameParameters.gameSpeed.modifier
|
|
||||||
).toInt()
|
|
||||||
|
|
||||||
civInfo.addStat(
|
|
||||||
Stat.valueOf(unique.params[2]),
|
|
||||||
foundStatAmount
|
|
||||||
)
|
|
||||||
|
|
||||||
if (notification != null) {
|
|
||||||
val notificationText =
|
|
||||||
if (notification.hasPlaceholderParameters()) {
|
|
||||||
notification.fillPlaceholders(foundStatAmount.toString())
|
|
||||||
} else notification
|
|
||||||
civInfo.addNotification(notificationText, stat.notificationIcon)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
"Gain enough Faith for a Pantheon" -> {
|
|
||||||
if (civInfo.religionManager.religionState != ReligionState.None) return false
|
|
||||||
val gainedFaith = civInfo.religionManager.faithForPantheon(2)
|
|
||||||
if (gainedFaith == 0) return false
|
|
||||||
|
|
||||||
civInfo.addStat(Stat.Faith, gainedFaith)
|
|
||||||
|
|
||||||
if (notification != null) {
|
|
||||||
val notificationText =
|
|
||||||
if (notification.hasPlaceholderParameters())
|
|
||||||
notification.fillPlaceholders(gainedFaith.toString())
|
|
||||||
else notification
|
|
||||||
civInfo.addNotification(notificationText, NotificationIcon.Faith)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
"Gain enough Faith for []% of a Great Prophet" -> {
|
|
||||||
if (civInfo.religionManager.getGreatProphetEquivalent() == null) return false
|
|
||||||
val gainedFaith =
|
|
||||||
(civInfo.religionManager.faithForNextGreatProphet() * (unique.params[0].toFloat() / 100f)).toInt()
|
|
||||||
if (gainedFaith == 0) return false
|
|
||||||
|
|
||||||
civInfo.addStat(Stat.Faith, gainedFaith)
|
|
||||||
|
|
||||||
if (notification != null) {
|
|
||||||
val notificationText =
|
|
||||||
if (notification.hasPlaceholderParameters())
|
|
||||||
notification.fillPlaceholders(gainedFaith.toString())
|
|
||||||
else notification
|
|
||||||
civInfo.addNotification(notificationText, NotificationIcon.Faith)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
"Reveal up to [] [] within a [] tile radius" -> {
|
|
||||||
if (tile == null) return false
|
|
||||||
val nearbyRevealableTiles = tile
|
|
||||||
.getTilesInDistance(unique.params[2].toInt())
|
|
||||||
.filter {
|
|
||||||
!civInfo.exploredTiles.contains(it.position) && it.matchesFilter(
|
|
||||||
unique.params[1]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.map { it.position }
|
|
||||||
if (nearbyRevealableTiles.none()) return false
|
|
||||||
civInfo.exploredTiles.addAll(nearbyRevealableTiles
|
|
||||||
.shuffled(tileBasedRandom)
|
|
||||||
.apply {
|
|
||||||
if (unique.params[0] != "All") this.take(unique.params[0].toInt())
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (notification != null) {
|
|
||||||
civInfo.addNotification(
|
|
||||||
notification,
|
|
||||||
LocationAction(nearbyRevealableTiles.toList())
|
|
||||||
) // We really need a barbarian icon
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
"From a randomly chosen tile [] tiles away from the ruins, reveal tiles up to [] tiles away with []% chance" -> {
|
|
||||||
if (tile == null) return false
|
|
||||||
val revealCenter = tile.getTilesAtDistance(unique.params[0].toInt())
|
|
||||||
.filter { it.position !in civInfo.exploredTiles }
|
|
||||||
.toList()
|
|
||||||
.randomOrNull(tileBasedRandom)
|
|
||||||
if (revealCenter == null) return false
|
|
||||||
val tilesToReveal = revealCenter
|
|
||||||
.getTilesInDistance(unique.params[1].toInt())
|
|
||||||
.map { it.position }
|
|
||||||
.filter { tileBasedRandom.nextFloat() < unique.params[2].toFloat() / 100f }
|
|
||||||
civInfo.exploredTiles.addAll(tilesToReveal)
|
|
||||||
civInfo.updateViewableTiles()
|
|
||||||
if (notification != null)
|
|
||||||
civInfo.addNotification(
|
|
||||||
notification,
|
|
||||||
tile.position,
|
|
||||||
"ImprovementIcons/Ancient ruins"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
"Triggers voting for the Diplomatic Victory" -> {
|
|
||||||
for (civ in civInfo.gameInfo.civilizations)
|
|
||||||
if (!civ.isBarbarian() && !civ.isSpectator())
|
|
||||||
civ.addFlag(
|
|
||||||
CivFlags.TurnsTillNextDiplomaticVote.name,
|
|
||||||
civInfo.getTurnsBetweenDiplomaticVotings()
|
|
||||||
)
|
|
||||||
if (notification != null)
|
|
||||||
civInfo.addNotification(notification, NotificationIcon.Diplomacy)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
"Provides the cheapest [] building in your first [] cities for free",
|
|
||||||
"Provides a [] in your first [] cities for free" ->
|
|
||||||
civInfo.civConstructions.tryAddFreeBuildings()
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return boolean whether an action was successfully preformed */
|
|
||||||
fun triggerUnitwideUnique(
|
|
||||||
unique: Unique,
|
|
||||||
unit: MapUnit,
|
|
||||||
notification: String? = null
|
|
||||||
): Boolean {
|
|
||||||
when (unique.placeholderText) {
|
|
||||||
"Heal this unit by [] HP" -> {
|
|
||||||
unit.healBy(unique.params[0].toInt())
|
|
||||||
if (notification != null)
|
|
||||||
unit.civInfo.addNotification(notification, unit.getTile().position) // Do we have a heal icon?
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
"This Unit gains [] XP" -> {
|
|
||||||
if (!unit.baseUnit.isMilitary()) return false
|
|
||||||
unit.promotions.XP += unique.params[0].toInt()
|
|
||||||
if (notification != null)
|
|
||||||
unit.civInfo.addNotification(notification, unit.getTile().position)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
"This Unit upgrades for free" -> {
|
|
||||||
val upgradeAction = UnitActions.getUpgradeAction(unit, true)
|
|
||||||
?: return false
|
|
||||||
upgradeAction.action!!()
|
|
||||||
if (notification != null)
|
|
||||||
unit.civInfo.addNotification(notification, unit.getTile().position)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
"This Unit upgrades for free including special upgrades" -> {
|
|
||||||
val upgradeAction = UnitActions.getAncientRuinsUpgradeAction(unit)
|
|
||||||
?: return false
|
|
||||||
upgradeAction.action!!()
|
|
||||||
if (notification != null)
|
|
||||||
unit.civInfo.addNotification(notification, unit.getTile().position)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
"This Unit gains the [] promotion" -> {
|
|
||||||
val promotion = unit.civInfo.gameInfo.ruleSet.unitPromotions.keys.firstOrNull { it == unique.params[0] }
|
|
||||||
if (promotion == null) return false
|
|
||||||
unit.promotions.addPromotion(promotion, true)
|
|
||||||
if (notification != null)
|
|
||||||
unit.civInfo.addNotification(notification, unit.name)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
485
core/src/com/unciv/models/ruleset/UniqueTriggerActivation.kt
Normal file
485
core/src/com/unciv/models/ruleset/UniqueTriggerActivation.kt
Normal file
@ -0,0 +1,485 @@
|
|||||||
|
package com.unciv.models.ruleset
|
||||||
|
|
||||||
|
import com.badlogic.gdx.math.Vector2
|
||||||
|
import com.unciv.logic.city.CityInfo
|
||||||
|
import com.unciv.logic.civilization.*
|
||||||
|
import com.unciv.logic.map.MapUnit
|
||||||
|
import com.unciv.logic.map.TileInfo
|
||||||
|
import com.unciv.models.stats.Stat
|
||||||
|
import com.unciv.models.translations.fillPlaceholders
|
||||||
|
import com.unciv.models.translations.hasPlaceholderParameters
|
||||||
|
import com.unciv.ui.worldscreen.unit.UnitActions
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
// Buildings, techs, policies, ancient ruins and promotions can have 'triggered' effects
|
||||||
|
object UniqueTriggerActivation {
|
||||||
|
/** @return boolean whether an action was successfully preformed */
|
||||||
|
fun triggerCivwideUnique(
|
||||||
|
unique: Unique,
|
||||||
|
civInfo: CivilizationInfo,
|
||||||
|
cityInfo: CityInfo? = null,
|
||||||
|
tile: TileInfo? = null,
|
||||||
|
notification: String? = null
|
||||||
|
): Boolean {
|
||||||
|
val chosenCity =
|
||||||
|
if (cityInfo != null) cityInfo
|
||||||
|
else civInfo.cities.firstOrNull { it.isCapital() }
|
||||||
|
val tileBasedRandom =
|
||||||
|
if (tile != null) Random(tile.position.toString().hashCode())
|
||||||
|
else Random(-550) // Very random indeed
|
||||||
|
when (unique.placeholderText) {
|
||||||
|
"Free [] appears" -> {
|
||||||
|
val unitName = unique.params[0]
|
||||||
|
val unit = civInfo.gameInfo.ruleSet.units[unitName]
|
||||||
|
if (chosenCity == null || unit == null || (unit.uniques.contains("Founds a new city") && civInfo.isOneCityChallenger()))
|
||||||
|
return false
|
||||||
|
|
||||||
|
val placedUnit = civInfo.addUnit(unitName, chosenCity)
|
||||||
|
if (notification != null && placedUnit != null) {
|
||||||
|
civInfo.addNotification(
|
||||||
|
notification,
|
||||||
|
placedUnit.getTile().position,
|
||||||
|
placedUnit.name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
"[] free [] units appear" -> {
|
||||||
|
val unitName = unique.params[1]
|
||||||
|
val unit = civInfo.gameInfo.ruleSet.units[unitName]
|
||||||
|
if (chosenCity == null || unit == null || (unit.uniques.contains("Founds a new city") && civInfo.isOneCityChallenger()))
|
||||||
|
return false
|
||||||
|
|
||||||
|
val tilesUnitsWerePlacedOn: MutableList<Vector2> = mutableListOf()
|
||||||
|
for (i in 1..unique.params[0].toInt()) {
|
||||||
|
val placedUnit = civInfo.addUnit(unitName, chosenCity)
|
||||||
|
if (placedUnit != null)
|
||||||
|
tilesUnitsWerePlacedOn.add(placedUnit.getTile().position)
|
||||||
|
}
|
||||||
|
if (notification != null && tilesUnitsWerePlacedOn.isNotEmpty()) {
|
||||||
|
civInfo.addNotification(
|
||||||
|
notification,
|
||||||
|
LocationAction(tilesUnitsWerePlacedOn),
|
||||||
|
civInfo.getEquivalentUnit(unit).name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// Differs from "Free [] appears" in that it spawns near the ruins instead of in a city
|
||||||
|
"Free [] found in the ruins" -> {
|
||||||
|
val unit = civInfo.getEquivalentUnit(unique.params[0])
|
||||||
|
val placingTile =
|
||||||
|
tile ?: civInfo.cities.random().getCenterTile()
|
||||||
|
|
||||||
|
val placedUnit = civInfo.placeUnitNearTile(placingTile.position, unit.name)
|
||||||
|
if (notification != null && placedUnit != null) {
|
||||||
|
val notificationText =
|
||||||
|
if (notification.hasPlaceholderParameters())
|
||||||
|
notification.fillPlaceholders(unique.params[0])
|
||||||
|
else notification
|
||||||
|
civInfo.addNotification(
|
||||||
|
notificationText,
|
||||||
|
placedUnit.getTile().position,
|
||||||
|
placedUnit.name
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return placedUnit != null
|
||||||
|
}
|
||||||
|
|
||||||
|
// spectators get all techs at start of game, and if (in a mod) a tech gives a free policy, the game gets stuck on the policy picker screen
|
||||||
|
"Free Social Policy" -> {
|
||||||
|
if (civInfo.isSpectator()) return false
|
||||||
|
civInfo.policies.freePolicies++
|
||||||
|
if (notification != null) {
|
||||||
|
civInfo.addNotification(notification, NotificationIcon.Culture)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
"[] Free Social Policies" -> {
|
||||||
|
if (civInfo.isSpectator()) return false
|
||||||
|
civInfo.policies.freePolicies += unique.params[0].toInt()
|
||||||
|
if (notification != null) {
|
||||||
|
civInfo.addNotification(notification, NotificationIcon.Culture)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
"Empire enters golden age" -> {
|
||||||
|
civInfo.goldenAges.enterGoldenAge()
|
||||||
|
if (notification != null) {
|
||||||
|
civInfo.addNotification(notification, NotificationIcon.Happiness)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
"Free Great Person" -> {
|
||||||
|
if (civInfo.isSpectator()) return false
|
||||||
|
if (civInfo.isPlayerCivilization()) {
|
||||||
|
civInfo.greatPeople.freeGreatPeople++
|
||||||
|
if (notification != null)
|
||||||
|
civInfo.addNotification(notification) // Anyone an idea for a good icon?
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
val greatPeople = civInfo.getGreatPeople()
|
||||||
|
if (greatPeople.isEmpty()) return false
|
||||||
|
var greatPerson = civInfo.getGreatPeople().random()
|
||||||
|
|
||||||
|
val preferredVictoryType = civInfo.victoryType()
|
||||||
|
if (preferredVictoryType == VictoryType.Cultural) {
|
||||||
|
val culturalGP =
|
||||||
|
greatPeople.firstOrNull { it.uniques.contains("Great Person - [Culture]") }
|
||||||
|
if (culturalGP != null) greatPerson = culturalGP
|
||||||
|
}
|
||||||
|
if (preferredVictoryType == VictoryType.Scientific) {
|
||||||
|
val scientificGP =
|
||||||
|
greatPeople.firstOrNull { it.uniques.contains("Great Person - [Science]") }
|
||||||
|
if (scientificGP != null) greatPerson = scientificGP
|
||||||
|
}
|
||||||
|
|
||||||
|
return civInfo.addUnit(greatPerson.name, chosenCity) != null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"[] population []" -> {
|
||||||
|
val citiesWithPopulationChanged: MutableList<Vector2> = mutableListOf()
|
||||||
|
for (city in civInfo.cities) {
|
||||||
|
if (city.matchesFilter(unique.params[1])) {
|
||||||
|
city.population.addPopulation(unique.params[0].toInt())
|
||||||
|
citiesWithPopulationChanged.add(city.location)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (notification != null && citiesWithPopulationChanged.isNotEmpty())
|
||||||
|
civInfo.addNotification(
|
||||||
|
notification,
|
||||||
|
LocationAction(citiesWithPopulationChanged),
|
||||||
|
NotificationIcon.Population
|
||||||
|
)
|
||||||
|
return citiesWithPopulationChanged.isNotEmpty()
|
||||||
|
}
|
||||||
|
"[] population in a random city" -> {
|
||||||
|
if (civInfo.cities.isEmpty()) return false
|
||||||
|
val randomCity = civInfo.cities.random(tileBasedRandom)
|
||||||
|
randomCity.population.addPopulation(unique.params[0].toInt())
|
||||||
|
if (notification != null) {
|
||||||
|
val notificationText =
|
||||||
|
if (notification.hasPlaceholderParameters())
|
||||||
|
notification.fillPlaceholders(randomCity.name)
|
||||||
|
else notification
|
||||||
|
civInfo.addNotification(
|
||||||
|
notificationText,
|
||||||
|
randomCity.location,
|
||||||
|
NotificationIcon.Population
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
"Free Technology" -> {
|
||||||
|
if (civInfo.isSpectator()) return false
|
||||||
|
civInfo.tech.freeTechs += 1
|
||||||
|
if (notification != null) {
|
||||||
|
civInfo.addNotification(notification, NotificationIcon.Science)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
"[] Free Technologies" -> {
|
||||||
|
if (civInfo.isSpectator()) return false
|
||||||
|
civInfo.tech.freeTechs += unique.params[0].toInt()
|
||||||
|
if (notification != null) {
|
||||||
|
civInfo.addNotification(notification, NotificationIcon.Science)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
"[] free random researchable Tech(s) from the []" -> {
|
||||||
|
val researchableTechsFromThatEra = civInfo.gameInfo.ruleSet.technologies.values
|
||||||
|
.filter {
|
||||||
|
(it.column!!.era == unique.params[1] || unique.params[1] == "any era")
|
||||||
|
&& civInfo.tech.canBeResearched(it.name)
|
||||||
|
}
|
||||||
|
if (researchableTechsFromThatEra.isEmpty()) return false
|
||||||
|
|
||||||
|
val techsToResearch = researchableTechsFromThatEra.shuffled(tileBasedRandom)
|
||||||
|
.take(unique.params[0].toInt())
|
||||||
|
for (tech in techsToResearch)
|
||||||
|
civInfo.tech.addTechnology(tech.name)
|
||||||
|
|
||||||
|
if (notification != null) {
|
||||||
|
val notificationText =
|
||||||
|
if (notification.hasPlaceholderParameters())
|
||||||
|
notification.fillPlaceholders(*(techsToResearch.map { it.name }
|
||||||
|
.toTypedArray()))
|
||||||
|
else notification
|
||||||
|
civInfo.addNotification(notificationText, NotificationIcon.Science)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
"Quantity of strategic resources produced by the empire increased by 100%" -> {
|
||||||
|
civInfo.updateDetailedCivResources()
|
||||||
|
if (notification != null) {
|
||||||
|
civInfo.addNotification(
|
||||||
|
notification,
|
||||||
|
NotificationIcon.War
|
||||||
|
) // I'm open for better icons
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
"+[]% attack strength to all [] Units for [] turns" -> {
|
||||||
|
civInfo.temporaryUniques.add(Pair(unique, unique.params[2].toInt()))
|
||||||
|
if (notification != null) {
|
||||||
|
civInfo.addNotification(notification, NotificationIcon.War)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
"Reveals the entire map" -> {
|
||||||
|
if (notification != null) {
|
||||||
|
civInfo.addNotification(notification, "UnitIcons/Scout")
|
||||||
|
}
|
||||||
|
return civInfo.exploredTiles.addAll(
|
||||||
|
civInfo.gameInfo.tileMap.values.asSequence().map { it.position })
|
||||||
|
}
|
||||||
|
|
||||||
|
"[] units gain the [] promotion" -> {
|
||||||
|
val filter = unique.params[0]
|
||||||
|
val promotion = unique.params[1]
|
||||||
|
|
||||||
|
val promotedUnitLocations: MutableList<Vector2> = mutableListOf()
|
||||||
|
for (unit in civInfo.getCivUnits()) {
|
||||||
|
if (unit.matchesFilter(filter)
|
||||||
|
&& civInfo.gameInfo.ruleSet.unitPromotions.values.any {
|
||||||
|
it.name == promotion && unit.type.name in it.unitTypes
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
unit.promotions.addPromotion(promotion, isFree = true)
|
||||||
|
promotedUnitLocations.add(unit.getTile().position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (notification != null) {
|
||||||
|
civInfo.addNotification(
|
||||||
|
notification,
|
||||||
|
LocationAction(promotedUnitLocations),
|
||||||
|
"unitPromotionIcons/${unique.params[1]}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return promotedUnitLocations.isNotEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
"Allied City-States will occasionally gift Great People" -> {
|
||||||
|
civInfo.addFlag(
|
||||||
|
CivFlags.CityStateGreatPersonGift.name,
|
||||||
|
civInfo.turnsForGreatPersonFromCityState() / 2
|
||||||
|
)
|
||||||
|
if (notification != null) {
|
||||||
|
civInfo.addNotification(notification, NotificationIcon.CityState)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
// The mechanics for granting great people are wonky, but basically the following happens:
|
||||||
|
// Based on the game speed, a timer with some amount of turns is set, 40 on regular speed
|
||||||
|
// Every turn, 1 is subtracted from this timer, as long as you have at least 1 city state ally
|
||||||
|
// So no, the number of city-state allies does not matter for this. You have a global timer for all of them combined.
|
||||||
|
// If the timer reaches the amount of city-state allies you have (or 10, whichever is lower), it is reset.
|
||||||
|
// You will then receive a random great person from a random city-state you are allied to
|
||||||
|
// The very first time after acquiring this policy, the timer is set to half of its normal value
|
||||||
|
// This is the basics, and apart from this, there is some randomness in the exact turn count, but I don't know how much
|
||||||
|
|
||||||
|
// There is surprisingly little information findable online about this policy, and the civ 5 source files are
|
||||||
|
// also quite though to search through, so this might all be incorrect.
|
||||||
|
// For now this mechanic seems decent enough that this is fine.
|
||||||
|
|
||||||
|
// Note that the way this is implemented now, this unique does NOT stack
|
||||||
|
// I could parametrize the [Allied], but eh.
|
||||||
|
|
||||||
|
"Gain [] []" -> {
|
||||||
|
if (Stat.values().none { it.name == unique.params[1] }) return false
|
||||||
|
val stat = Stat.valueOf(unique.params[1])
|
||||||
|
|
||||||
|
if (stat !in listOf(Stat.Gold, Stat.Faith, Stat.Science, Stat.Culture)
|
||||||
|
|| unique.params[0].toIntOrNull() == null
|
||||||
|
) return false
|
||||||
|
|
||||||
|
civInfo.addStat(stat, unique.params[0].toInt())
|
||||||
|
if (notification != null)
|
||||||
|
civInfo.addNotification(notification, stat.notificationIcon)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
"Gain []-[] []" -> {
|
||||||
|
if (Stat.values().none { it.name == unique.params[2] }) return false
|
||||||
|
val stat = Stat.valueOf(unique.params[2])
|
||||||
|
|
||||||
|
if (stat !in listOf(Stat.Gold, Stat.Faith, Stat.Science, Stat.Culture)
|
||||||
|
|| unique.params[0].toIntOrNull() == null
|
||||||
|
|| unique.params[1].toIntOrNull() == null
|
||||||
|
) return false
|
||||||
|
|
||||||
|
val foundStatAmount =
|
||||||
|
(tileBasedRandom.nextInt(unique.params[0].toInt(), unique.params[1].toInt()) *
|
||||||
|
civInfo.gameInfo.gameParameters.gameSpeed.modifier
|
||||||
|
).toInt()
|
||||||
|
|
||||||
|
civInfo.addStat(
|
||||||
|
Stat.valueOf(unique.params[2]),
|
||||||
|
foundStatAmount
|
||||||
|
)
|
||||||
|
|
||||||
|
if (notification != null) {
|
||||||
|
val notificationText =
|
||||||
|
if (notification.hasPlaceholderParameters()) {
|
||||||
|
notification.fillPlaceholders(foundStatAmount.toString())
|
||||||
|
} else notification
|
||||||
|
civInfo.addNotification(notificationText, stat.notificationIcon)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
"Gain enough Faith for a Pantheon" -> {
|
||||||
|
if (civInfo.religionManager.religionState != ReligionState.None) return false
|
||||||
|
val gainedFaith = civInfo.religionManager.faithForPantheon(2)
|
||||||
|
if (gainedFaith == 0) return false
|
||||||
|
|
||||||
|
civInfo.addStat(Stat.Faith, gainedFaith)
|
||||||
|
|
||||||
|
if (notification != null) {
|
||||||
|
val notificationText =
|
||||||
|
if (notification.hasPlaceholderParameters())
|
||||||
|
notification.fillPlaceholders(gainedFaith.toString())
|
||||||
|
else notification
|
||||||
|
civInfo.addNotification(notificationText, NotificationIcon.Faith)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
"Gain enough Faith for []% of a Great Prophet" -> {
|
||||||
|
if (civInfo.religionManager.getGreatProphetEquivalent() == null) return false
|
||||||
|
val gainedFaith =
|
||||||
|
(civInfo.religionManager.faithForNextGreatProphet() * (unique.params[0].toFloat() / 100f)).toInt()
|
||||||
|
if (gainedFaith == 0) return false
|
||||||
|
|
||||||
|
civInfo.addStat(Stat.Faith, gainedFaith)
|
||||||
|
|
||||||
|
if (notification != null) {
|
||||||
|
val notificationText =
|
||||||
|
if (notification.hasPlaceholderParameters())
|
||||||
|
notification.fillPlaceholders(gainedFaith.toString())
|
||||||
|
else notification
|
||||||
|
civInfo.addNotification(notificationText, NotificationIcon.Faith)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
"Reveal up to [] [] within a [] tile radius" -> {
|
||||||
|
if (tile == null) return false
|
||||||
|
val nearbyRevealableTiles = tile
|
||||||
|
.getTilesInDistance(unique.params[2].toInt())
|
||||||
|
.filter {
|
||||||
|
!civInfo.exploredTiles.contains(it.position) && it.matchesFilter(
|
||||||
|
unique.params[1]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.map { it.position }
|
||||||
|
if (nearbyRevealableTiles.none()) return false
|
||||||
|
civInfo.exploredTiles.addAll(nearbyRevealableTiles
|
||||||
|
.shuffled(tileBasedRandom)
|
||||||
|
.apply {
|
||||||
|
if (unique.params[0] != "All") this.take(unique.params[0].toInt())
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (notification != null) {
|
||||||
|
civInfo.addNotification(
|
||||||
|
notification,
|
||||||
|
LocationAction(nearbyRevealableTiles.toList())
|
||||||
|
) // We really need a barbarian icon
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
"From a randomly chosen tile [] tiles away from the ruins, reveal tiles up to [] tiles away with []% chance" -> {
|
||||||
|
if (tile == null) return false
|
||||||
|
val revealCenter = tile.getTilesAtDistance(unique.params[0].toInt())
|
||||||
|
.filter { it.position !in civInfo.exploredTiles }
|
||||||
|
.toList()
|
||||||
|
.randomOrNull(tileBasedRandom)
|
||||||
|
if (revealCenter == null) return false
|
||||||
|
val tilesToReveal = revealCenter
|
||||||
|
.getTilesInDistance(unique.params[1].toInt())
|
||||||
|
.map { it.position }
|
||||||
|
.filter { tileBasedRandom.nextFloat() < unique.params[2].toFloat() / 100f }
|
||||||
|
civInfo.exploredTiles.addAll(tilesToReveal)
|
||||||
|
civInfo.updateViewableTiles()
|
||||||
|
if (notification != null)
|
||||||
|
civInfo.addNotification(
|
||||||
|
notification,
|
||||||
|
tile.position,
|
||||||
|
"ImprovementIcons/Ancient ruins"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"Triggers voting for the Diplomatic Victory" -> {
|
||||||
|
for (civ in civInfo.gameInfo.civilizations)
|
||||||
|
if (!civ.isBarbarian() && !civ.isSpectator())
|
||||||
|
civ.addFlag(
|
||||||
|
CivFlags.TurnsTillNextDiplomaticVote.name,
|
||||||
|
civInfo.getTurnsBetweenDiplomaticVotings()
|
||||||
|
)
|
||||||
|
if (notification != null)
|
||||||
|
civInfo.addNotification(notification, NotificationIcon.Diplomacy)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
"Provides the cheapest [] building in your first [] cities for free",
|
||||||
|
"Provides a [] in your first [] cities for free" ->
|
||||||
|
civInfo.civConstructions.tryAddFreeBuildings()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return boolean whether an action was successfully preformed */
|
||||||
|
fun triggerUnitwideUnique(
|
||||||
|
unique: Unique,
|
||||||
|
unit: MapUnit,
|
||||||
|
notification: String? = null
|
||||||
|
): Boolean {
|
||||||
|
when (unique.placeholderText) {
|
||||||
|
"Heal this unit by [] HP" -> {
|
||||||
|
unit.healBy(unique.params[0].toInt())
|
||||||
|
if (notification != null)
|
||||||
|
unit.civInfo.addNotification(notification, unit.getTile().position) // Do we have a heal icon?
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
"This Unit gains [] XP" -> {
|
||||||
|
if (!unit.baseUnit.isMilitary()) return false
|
||||||
|
unit.promotions.XP += unique.params[0].toInt()
|
||||||
|
if (notification != null)
|
||||||
|
unit.civInfo.addNotification(notification, unit.getTile().position)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
"This Unit upgrades for free" -> {
|
||||||
|
val upgradeAction = UnitActions.getUpgradeAction(unit, true)
|
||||||
|
?: return false
|
||||||
|
upgradeAction.action!!()
|
||||||
|
if (notification != null)
|
||||||
|
unit.civInfo.addNotification(notification, unit.getTile().position)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
"This Unit upgrades for free including special upgrades" -> {
|
||||||
|
val upgradeAction = UnitActions.getAncientRuinsUpgradeAction(unit)
|
||||||
|
?: return false
|
||||||
|
upgradeAction.action!!()
|
||||||
|
if (notification != null)
|
||||||
|
unit.civInfo.addNotification(notification, unit.getTile().position)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
"This Unit gains the [] promotion" -> {
|
||||||
|
val promotion = unit.civInfo.gameInfo.ruleSet.unitPromotions.keys.firstOrNull { it == unique.params[0] }
|
||||||
|
if (promotion == null) return false
|
||||||
|
unit.promotions.addPromotion(promotion, true)
|
||||||
|
if (notification != null)
|
||||||
|
unit.civInfo.addNotification(notification, unit.name)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user