Created Patronage policy branch (#4186)

* Created Patronage policy branch -- draft

* Patronage branch is now functional

* Added images for the policies

* Temporarily bandaged backwards compatability, added incompatabilities

* Implemented recommended changes

* Fixed acquirement of 'patronage complete' not being saved

* Reverted change I was unhappy with

* Implemented requested changes

* Fixed build errors

* Implemented recommended changes

* City States can now give any great person, including unique ones, conform Ravignirs tests
This commit is contained in:
Xander Lenstra
2021-06-22 16:25:29 +02:00
committed by GitHub
parent a025660fe0
commit 7f88844d82
15 changed files with 579 additions and 385 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 988 KiB

After

Width:  |  Height:  |  Size: 998 KiB

View File

@ -135,7 +135,7 @@
},{ },{
"name": "Piety", "name": "Piety",
"era": "Classical era", "era": "Classical era",
"uniques": ["+[15]% Production when constructing [Culture] buildings"], "uniques": ["+[15]% Production when constructing [Culture] buildings", "Incompatible with [Rationalism]"],
"policies": [ "policies": [
{ {
"name": "Organized Religion", "name": "Organized Religion",
@ -177,8 +177,15 @@
"uniques": ["Culture cost of adopting new Policies reduced by [10]%"] "uniques": ["Culture cost of adopting new Policies reduced by [10]%"]
} }
] ]
},/*{ },
"name": "Patronage", {
"name": "Patronage ",
// Yes, there is a space behind this word, and yes, this is necessary
// This is, because at the time of writing there existed another policy called 'Patronage' that was recently deprecated
// It would, however, still be possible for save-files to contain this policy
// Therefore, we had to differentiate between these two, and this was the least intrusive way to do so
// NOTE: If you remove the space here, also remove the extra space between 'patronage' and 'complete' in the 'patronage complete' policy.
// Otherwise, weird stuff will happen.
"era": "Classical era", "era": "Classical era",
"uniques": ["City-State Influence degrades [25]% slower"], "uniques": ["City-State Influence degrades [25]% slower"],
"policies": [ "policies": [
@ -196,14 +203,15 @@
}, },
{ {
"name": "Scholasticism", "name": "Scholasticism",
"uniques":["Allied City-States provide Science equal to [25]% of what they produce for themselves"], "uniques":["Allied City-States provide [Science] equal to [25]% of what they produce for themselves"],
"requires": ["Philantropy"], "requires": ["Philantropy"],
"row": 2, "row": 2,
"column": 2 "column": 2
}, },
{ {
"name": "Cultural Diplomacy", "name": "Cultural Diplomacy",
"uniques": ["Quantity of Resources gifted by City-States increased by [100]%"], "uniques": ["Quantity of Resources gifted by City-States increased by [100]%",
"Happiness from Luxury Resources gifted by City-States increased by [50]%"],
"requires": ["Scholasticism"], "requires": ["Scholasticism"],
"row": 3, "row": 3,
"column": 2 "column": 2
@ -211,14 +219,19 @@
{ {
"name": "Educated Elite", "name": "Educated Elite",
"requires": ["Scholasticism","Aesthetics"], "requires": ["Scholasticism","Aesthetics"],
"uniques": ["Allied City-States will occasionally gift Great People"],
"row": 3, "row": 3,
"column": 4 "column": 4
}, },
{ {
"name": "Patronage Complete" "name": "Patronage Complete",
} // This extra space is intentional, see above.
"uniques": ["Influence of all other civilizations with all city-states degrades [33]% faster",
"Triggers the following global alert: [Our influence with City-States has started dropping faster!]"]
// This triggers a global alert in the G&K game also, based on my memory of playing it
}
] ]
},*/ },
{ {
"name": "Commerce", "name": "Commerce",
"uniques": ["+[25]% [Gold] [in capital]"], "uniques": ["+[25]% [Gold] [in capital]"],
@ -269,7 +282,7 @@
{ {
"name": "Rationalism", "name": "Rationalism",
"era": "Renaissance era", "era": "Renaissance era",
"uniques": ["+[15]% [Science] while the empire is happy"], "uniques": ["+[15]% [Science] while the empire is happy", "Incompatible with [Piety]"],
"policies": [ "policies": [
{ {
"name": "Secularism", "name": "Secularism",

View File

@ -483,6 +483,9 @@ Clearing a [forest] has created [amount] Production for [cityName] =
The resistance in [cityName] has ended! = The resistance in [cityName] has ended! =
Our [name] took [tileDamage] tile damage and was destroyed = Our [name] took [tileDamage] tile damage and was destroyed =
Our [name] took [tileDamage] tile damage = Our [name] took [tileDamage] tile damage =
[civName] has adopted the [policyName] policy =
An unknown civilization has adopted the [policyName] policy =
Our influence with City-States has started dropping faster! =
# World Screen UI # World Screen UI

View File

@ -103,13 +103,26 @@ class CivInfoStats(val civInfo: CivilizationInfo) {
statMap.add("City-States", cultureBonus) statMap.add("City-States", cultureBonus)
} }
if (otherCiv.isCityState())
if (otherCiv.isCityState() && otherCiv.getDiplomacyManager(civInfo.civName).relationshipLevel() >= RelationshipLevel.Ally) { for (unique in civInfo.getMatchingUniques("Allied City-States provide [] equal to []% of what they produce for themselves")) {
val sciencePercentage = civInfo if (otherCiv.diplomacy[civInfo.civName]!!.matchesCityStateRelationshipFilter(unique.params[0]) && otherCiv.cities.isNotEmpty()) {
.getMatchingUniques("Allied City-States provide Science equal to []% of what they produce for themselves") statMap.add(
.sumBy { it.params[0].toInt() } "City-States",
statMap.add("City-States", Stats().apply { science = otherCiv.statsForNextTurn.science * (sciencePercentage / 100f) }) Stats().add(
} Stat.valueOf(unique.params[1]),
otherCiv.statsForNextTurn.get(Stat.valueOf(unique.params[1])) * unique.params[2].toFloat() / 100f
)
)
}
}
// Deprecated since 3.15.1
if (otherCiv.isCityState() && otherCiv.getDiplomacyManager(civInfo.civName).relationshipLevel() >= RelationshipLevel.Ally) {
val sciencePercentage = civInfo
.getMatchingUniques("Allied City-States provide Science equal to []% of what they produce for themselves")
.sumBy { it.params[0].toInt() }
statMap.add("City-States", Stats().apply { science = otherCiv.statsForNextTurn.science * (sciencePercentage / 100f) })
}
//
} }
statMap["Transportation upkeep"] = Stats().apply { gold = -getTransportationUpkeep().toFloat() } statMap["Transportation upkeep"] = Stats().apply { gold = -getTransportationUpkeep().toFloat() }
@ -147,9 +160,19 @@ class CivInfoStats(val civInfo: CivilizationInfo) {
for (unique in civInfo.getMatchingUniques("+1 happiness from each type of luxury resource")) for (unique in civInfo.getMatchingUniques("+1 happiness from each type of luxury resource"))
happinessPerUniqueLuxury += 1 happinessPerUniqueLuxury += 1
// //
statMap["Luxury resources"] = civInfo.getCivResources().map { it.resource } statMap["Luxury resources"] = civInfo.getCivResources().map { it.resource }
.count { it.resourceType === ResourceType.Luxury } * happinessPerUniqueLuxury .count { it.resourceType === ResourceType.Luxury } * happinessPerUniqueLuxury
val happinessBonusForCityStateProvidedLuxuries =
civInfo.getMatchingUniques("Happiness from Luxury Resources gifted by City-States increased by []%")
.map { it.params[0].toFloat() / 100f }.sum()
val luxuriesProvidedByCityStates =
civInfo.getKnownCivs().filter { it.isCityState() && it.getAllyCiv() == civInfo.civName }
.map { it.getCivResources().map { res -> res.resource } }.flatten().count { it.resourceType === ResourceType.Luxury }
statMap["City-State Luxuries"] = happinessBonusForCityStateProvidedLuxuries * luxuriesProvidedByCityStates * happinessPerUniqueLuxury
for (city in civInfo.cities) { for (city in civInfo.cities) {
// There appears to be a concurrency problem? In concurrent thread in ConstructionsTable.getConstructionButtonDTOs // There appears to be a concurrency problem? In concurrent thread in ConstructionsTable.getConstructionButtonDTOs

View File

@ -14,6 +14,7 @@ import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
import com.unciv.logic.trade.TradeEvaluation import com.unciv.logic.trade.TradeEvaluation
import com.unciv.logic.trade.TradeRequest import com.unciv.logic.trade.TradeRequest
import com.unciv.models.metadata.GameSpeed
import com.unciv.models.ruleset.* import com.unciv.models.ruleset.*
import com.unciv.models.ruleset.tile.ResourceSupplyList import com.unciv.models.ruleset.tile.ResourceSupplyList
import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.tile.ResourceType
@ -27,6 +28,7 @@ import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.collections.HashMap import kotlin.collections.HashMap
import kotlin.math.roundToInt import kotlin.math.roundToInt
import kotlin.math.min
class CivilizationInfo { class CivilizationInfo {
@ -93,7 +95,7 @@ class CivilizationInfo {
/** See DiplomacyManager.flagsCountdown to why not eEnum */ /** See DiplomacyManager.flagsCountdown to why not eEnum */
private var flagsCountdown = HashMap<String, Int>() private var flagsCountdown = HashMap<String, Int>()
/** Arraylist instead of HashMap as there might be doubles /** Arraylist instead of HashMap as there might be doubles
* Pairs of Uniques and the amount of turns they are still active * Pairs of Uniques and the amount of turns they are still active
* If the counter reaches 0 at the end of a turn, it is removed immediately * If the counter reaches 0 at the end of a turn, it is removed immediately
*/ */
@ -227,7 +229,7 @@ class CivilizationInfo {
if (resource.resourceType == ResourceType.Strategic) { if (resource.resourceType == ResourceType.Strategic) {
resourceModifier *= 1f + getMatchingUniques("Quantity of strategic resources produced by the empire +[]%") resourceModifier *= 1f + getMatchingUniques("Quantity of strategic resources produced by the empire +[]%")
.map { it.params[0].toFloat() / 100f }.sum() .map { it.params[0].toFloat() / 100f }.sum()
// Deprecated since 3.15 // Deprecated since 3.15
if (hasUnique("Quantity of strategic resources produced by the empire increased by 100%")) { if (hasUnique("Quantity of strategic resources produced by the empire increased by 100%")) {
resourceModifier *= 2f resourceModifier *= 2f
@ -386,7 +388,7 @@ class CivilizationInfo {
* Returns a civilization caption suitable for greetings including player type info: * Returns a civilization caption suitable for greetings including player type info:
* Like "Milan" if the nation is a city state, "Caesar of Rome" otherwise, with an added * Like "Milan" if the nation is a city state, "Caesar of Rome" otherwise, with an added
* " (AI)", " (Human - Hotseat)", or " (Human - Multiplayer)" if the game is multiplayer. * " (AI)", " (Human - Hotseat)", or " (Human - Multiplayer)" if the game is multiplayer.
*/ */
fun getLeaderDisplayName(): String { fun getLeaderDisplayName(): String {
val severalHumans = gameInfo.civilizations.count { it.playerType == PlayerType.Human } > 1 val severalHumans = gameInfo.civilizations.count { it.playerType == PlayerType.Human } > 1
val online = gameInfo.gameParameters.isOnlineMultiplayer val online = gameInfo.gameParameters.isOnlineMultiplayer
@ -510,6 +512,7 @@ class CivilizationInfo {
updateViewableTiles() // adds explored tiles so that the units will be able to perform automated actions better updateViewableTiles() // adds explored tiles so that the units will be able to perform automated actions better
transients().updateCitiesConnectedToCapital() transients().updateCitiesConnectedToCapital()
turnStartFlags()
for (city in cities) city.startTurn() for (city in cities) city.startTurn()
for (unit in getCivUnits()) unit.startTurn() for (unit in getCivUnits()) unit.startTurn()
@ -561,7 +564,7 @@ class CivilizationInfo {
for (city in cities.toList()) { // a city can be removed while iterating (if it's being razed) so we need to iterate over a copy for (city in cities.toList()) { // a city can be removed while iterating (if it's being razed) so we need to iterate over a copy
city.endTurn() city.endTurn()
} }
// Update turn counter for temporary uniques // Update turn counter for temporary uniques
for (unique in temporaryUniques.toList()) { for (unique in temporaryUniques.toList()) {
temporaryUniques.remove(unique) temporaryUniques.remove(unique)
@ -575,8 +578,34 @@ class CivilizationInfo {
updateHasActiveGreatWall() updateHasActiveGreatWall()
} }
private fun turnStartFlags() {
// This function may be too abstracted for what it currently does (only managing a single flag)
// But eh, it works.
for (flag in flagsCountdown.keys.toList()) {
if (flag == CivFlags.cityStateGreatPersonGift.name) {
val cityStateAllies = getKnownCivs().filter { it.isCityState() && it.getAllyCiv() == civName }.count()
if (cityStateAllies >= 1) flagsCountdown[flag] = flagsCountdown[flag]!! - 1
if (flagsCountdown[flag]!! < min(cityStateAllies, 10)) {
gainGreatPersonFromCityState()
flagsCountdown[flag] = turnsForGreatPersonFromCityState()
}
continue
}
if (flagsCountdown[flag]!! > 0)
flagsCountdown[flag] = flagsCountdown[flag]!! - 1
}
}
fun addFlag(flag: String, count: Int) { flagsCountdown[flag] = count }
/** Modify gold by a given amount making sure it does neither overflow nor underflow. /** Modify gold by a given amount making sure it does neither overflow nor underflow.
* @param delta the amount to add (can be negative) * @param delta the amount to add (can be negative)
*/ */
fun addGold(delta: Int) { fun addGold(delta: Int) {
// not using Long.coerceIn - this stays in 32 bits // not using Long.coerceIn - this stays in 32 bits
@ -586,7 +615,7 @@ class CivilizationInfo {
else -> gold + delta else -> gold + delta
} }
} }
fun addStat(stat: Stat, amount: Int) { fun addStat(stat: Stat, amount: Int) {
when (stat) { when (stat) {
Stat.Culture -> policies.addCulture(amount) Stat.Culture -> policies.addCulture(amount)
@ -673,8 +702,8 @@ class CivilizationInfo {
fun influenceGainedByGift(cityState: CivilizationInfo, giftAmount: Int): Int { fun influenceGainedByGift(cityState: CivilizationInfo, giftAmount: Int): Int {
var influenceGained = giftAmount / 10f var influenceGained = giftAmount / 10f
for (unique in cityState.getMatchingUniques("Gifts of Gold to City-States generate []% more Influence")) for (unique in getMatchingUniques("Gifts of Gold to City-States generate []% more Influence"))
influenceGained *= (100f + unique.params[0].toInt()) / 100 influenceGained *= 1f + unique.params[0].toFloat() / 100f
return influenceGained.toInt() return influenceGained.toInt()
} }
@ -713,6 +742,22 @@ class CivilizationInfo {
addNotification("[${otherCiv.civName}] gave us a [${militaryUnit.name}] as gift near [${city.name}]!", locations, otherCiv.civName, militaryUnit.name) addNotification("[${otherCiv.civName}] gave us a [${militaryUnit.name}] as gift near [${city.name}]!", locations, otherCiv.civName, militaryUnit.name)
} }
/** Gain a random great person from a random city state */
private fun gainGreatPersonFromCityState() {
val givingCityState = getKnownCivs().filter { it.isCityState() && it.getAllyCiv() == civName}.random()
val giftedUnit = gameInfo.ruleSet.units.values.filter { it.isGreatPerson() }.random()
val cities = NextTurnAutomation.getClosestCities(this, givingCityState)
val placedUnit = placeUnitNearTile(cities.city1.location, giftedUnit.name)
if (placedUnit == null) return
val locations = LocationAction(listOf(placedUnit.getTile().position, cities.city2.location))
addNotification( "[${givingCityState.civName}] gave us a [${giftedUnit.name}] as a gift!", locations, givingCityState.civName, giftedUnit.name)
}
fun turnsForGreatPersonFromCityState(): Int = (40 + -2 + Random().nextInt(5)) * gameInfo.gameParameters.gameSpeed.modifier.toInt()
// There seems to be some randomness in the amount of turns between receiving each great person,
// but I have no idea what the actual lower and upper bound are, so this is just an approximation
fun getAllyCiv() = allyCivName fun getAllyCiv() = allyCivName
fun getProtectorCivs() : List<CivilizationInfo> { fun getProtectorCivs() : List<CivilizationInfo> {
@ -786,3 +831,7 @@ class CivilizationInfoPreview {
var playerId = "" var playerId = ""
fun isPlayerCivilization() = playerType == PlayerType.Human fun isPlayerCivilization() = playerType == PlayerType.Human
} }
enum class CivFlags {
cityStateGreatPersonGift
}

View File

@ -4,6 +4,8 @@ import com.unciv.models.ruleset.Policy
import com.unciv.models.ruleset.Unique import com.unciv.models.ruleset.Unique
import com.unciv.models.ruleset.UniqueMap import com.unciv.models.ruleset.UniqueMap
import com.unciv.models.ruleset.UniqueTriggerActivation import com.unciv.models.ruleset.UniqueTriggerActivation
import com.unciv.models.translations.equalsPlaceholderText
import com.unciv.models.translations.getPlaceholderParameters
import kotlin.math.min import kotlin.math.min
import kotlin.math.pow import kotlin.math.pow
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -168,6 +170,14 @@ class PolicyManager {
} }
} }
for (unique in policy.uniques) {
if (unique == "Triggers a global alert") {
triggerGlobalAlerts(policy)
} else if (unique.equalsPlaceholderText("Triggers the following global alert: []")) {
triggerGlobalAlerts(policy, unique.getPlaceholderParameters()[0])
}
}
for (unique in policy.uniqueObjects) for (unique in policy.uniqueObjects)
UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo) UniqueTriggerActivation.triggerCivwideUnique(unique, civInfo)
@ -238,4 +248,21 @@ class PolicyManager {
} }
return freeBuildings return freeBuildings
} }
private fun triggerGlobalAlerts(policy: Policy, extraNotificationText: String = "") {
var extraNotificationTextCopy = extraNotificationText
if (extraNotificationText != "") {
extraNotificationTextCopy = "\n${extraNotificationText}"
}
for (civ in civInfo.gameInfo.civilizations) {
if (civ == civInfo) continue
val defaultNotificationText =
if (civ.getKnownCivs().contains(civInfo)) {
"[${civInfo.civName}] has adopted the [${policy.name}] policy"
} else {
"An unknown civilization has adopted the [${policy.name}] policy"
}
civ.addNotification("${defaultNotificationText}${extraNotificationTextCopy}", NotificationIcon.Culture)
}
}
} }

View File

@ -176,13 +176,25 @@ class DiplomacyManager() {
} }
return 0 return 0
} }
fun matchesCityStateRelationshipFilter(filter: String): Boolean {
val relationshipLevel = relationshipLevel()
return when (filter) {
"Allied" -> relationshipLevel == RelationshipLevel.Ally
"Friendly" -> relationshipLevel == RelationshipLevel.Friend
"Enemy" -> relationshipLevel == RelationshipLevel.Enemy
"Unforgiving" -> relationshipLevel == RelationshipLevel.Unforgivable
"Neutral" -> relationshipLevel == RelationshipLevel.Neutral
else -> false
}
}
// To be run from City-State DiplomacyManager, which holds the influence. Resting point for every major civ can be different. // To be run from City-State DiplomacyManager, which holds the influence. Resting point for every major civ can be different.
fun getCityStateInfluenceRestingPoint(): Float { fun getCityStateInfluenceRestingPoint(): Float {
var restingPoint = 0f var restingPoint = 0f
for (unique in otherCiv().getMatchingUniques("Resting point for Influence with City-States is increased by []")) for (unique in otherCiv().getMatchingUniques("Resting point for Influence with City-States is increased by []"))
restingPoint += unique.params[0].toInt() restingPoint += unique.params[0].toInt()
if(diplomaticStatus == DiplomaticStatus.Protector) restingPoint += 5 if (diplomaticStatus == DiplomaticStatus.Protector) restingPoint += 5
return restingPoint return restingPoint
} }
@ -203,7 +215,13 @@ class DiplomacyManager() {
} }
for (unique in otherCiv().getMatchingUniques("City-State Influence degrades []% slower")) for (unique in otherCiv().getMatchingUniques("City-State Influence degrades []% slower"))
modifier *= (100f - unique.params[0].toInt()) / 100 modifier *= 1f - unique.params[0].toFloat() / 100f
for (civ in civInfo.gameInfo.civilizations.filter { it.isMajorCiv() && it != otherCiv()}) {
for (unique in civ.getMatchingUniques("Influence of all other civilizations with all city-states degrades []% faster")) {
modifier *= 1f + unique.params[0].toFloat() / 100f
}
}
return max(0f, decrement) * max(0f, modifier) return max(0f, decrement) * max(0f, modifier)
} }
@ -243,7 +261,7 @@ class DiplomacyManager() {
return goldPerTurnForUs return goldPerTurnForUs
} }
fun sciencefromResearchAgreement() { fun scienceFromResearchAgreement() {
// https://forums.civfanatics.com/resources/research-agreements-bnw.25568/ // https://forums.civfanatics.com/resources/research-agreements-bnw.25568/
val scienceFromResearchAgreement = min(totalOfScienceDuringRA, otherCivDiplomacy().totalOfScienceDuringRA) val scienceFromResearchAgreement = min(totalOfScienceDuringRA, otherCivDiplomacy().totalOfScienceDuringRA)
civInfo.tech.scienceFromResearchAgreements += scienceFromResearchAgreement civInfo.tech.scienceFromResearchAgreements += scienceFromResearchAgreement
@ -288,7 +306,7 @@ class DiplomacyManager() {
//endregion //endregion
//region state-changing functions //region state-changing functions
fun removeUntenebleTrades() { fun removeUntenableTrades() {
for (trade in trades.toList()) { for (trade in trades.toList()) {
@ -329,7 +347,7 @@ class DiplomacyManager() {
fun nextTurn() { fun nextTurn() {
nextTurnTrades() nextTurnTrades()
removeUntenebleTrades() removeUntenableTrades()
updateHasOpenBorders() updateHasOpenBorders()
nextTurnDiplomaticModifiers() nextTurnDiplomaticModifiers()
nextTurnFlags() nextTurnFlags()
@ -403,7 +421,7 @@ class DiplomacyManager() {
when (flag) { when (flag) {
DiplomacyFlags.ResearchAgreement.name -> { DiplomacyFlags.ResearchAgreement.name -> {
if (!otherCivDiplomacy().hasFlag(DiplomacyFlags.ResearchAgreement)) if (!otherCivDiplomacy().hasFlag(DiplomacyFlags.ResearchAgreement))
sciencefromResearchAgreement() scienceFromResearchAgreement()
} }
// This is confusingly named - in fact, the civ that has the flag set is the MAJOR civ // This is confusingly named - in fact, the civ that has the flag set is the MAJOR civ
DiplomacyFlags.ProvideMilitaryUnit.name -> { DiplomacyFlags.ProvideMilitaryUnit.name -> {

View File

@ -1,6 +1,7 @@
package com.unciv.models.ruleset package com.unciv.models.ruleset
import com.unciv.logic.city.CityInfo import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.CivFlags
import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.models.stats.Stats import com.unciv.models.stats.Stats
import com.unciv.models.translations.getPlaceholderParameters import com.unciv.models.translations.getPlaceholderParameters
@ -104,6 +105,23 @@ object UniqueTriggerActivation {
unit.promotions.addPromotion(promotion, isFree = true) unit.promotions.addPromotion(promotion, isFree = true)
} }
} }
"Allied City-States will occasionally gift Great People" ->
civInfo.addFlag(CivFlags.cityStateGreatPersonGift.name, civInfo.turnsForGreatPersonFromCityState() / 2)
// 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.
} }
} }
} }

View File

@ -320,6 +320,14 @@ Unless otherwise specified, all the following are from [the Noun Project](https:
* [Religion](https://thenounproject.com/term/religion/1307794/) By Ben Avery for Free Religion * [Religion](https://thenounproject.com/term/religion/1307794/) By Ben Avery for Free Religion
* [Flame](https://thenounproject.com/term/flame/633228/) By Ian Shoobridge for Mandate Of Heaven * [Flame](https://thenounproject.com/term/flame/633228/) By Ian Shoobridge for Mandate Of Heaven
### Patronage
* Adapted from [Gold](https://thenounproject.com/term/gold/842351) by Aneeque Ahmed for Philantropy
* [Ornament](https://thenounproject.com/term/ornament/3945298) by Tommy Suhartomo for Aesthetics
* [Book Gift](https://thenounproject.com/term/book-gift/671626) by Wolf Böse for Scholasticism
* [agreement](https://thenounproject.com/term/agreement/1828960) by RomanP for Cultural Diplomacy
* [professor](https://thenounproject.com/term/professor/232239) by Andrew Doane for Educated Elite
### Commerce ### Commerce
* [Trade](https://thenounproject.com/term/trade/686718/) By Gregor Cresnar for Trade Unions * [Trade](https://thenounproject.com/term/trade/686718/) By Gregor Cresnar for Trade Unions