Refactored some code and uniques (#5051)

* Refactored some code and uniques

* Fixed compilation errors, tests and crashes

* Moved influence bounds check from add to set
This commit is contained in:
Xander Lenstra 2021-09-01 18:20:04 +02:00 committed by GitHub
parent cffe8e441e
commit b347366d50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 190 additions and 144 deletions

View File

@ -717,7 +717,7 @@
"culture": 1, "culture": 1,
"isWonder": true, "isWonder": true,
"greatPersonPoints": {"Great Artist": 1}, "greatPersonPoints": {"Great Artist": 1},
"uniques": ["Unhappiness from population decreased by [10]%"], "uniques": ["[-10]% unhappiness from population [in all cities]"],
"requiredTech": "Banking", "requiredTech": "Banking",
"quote": "'Most of us can, as we choose, make of this world either a palace or a prison' - John Lubbock" "quote": "'Most of us can, as we choose, make of this world either a palace or a prison' - John Lubbock"
}, },
@ -969,7 +969,7 @@
"culture": 1, "culture": 1,
"isWonder": true, "isWonder": true,
"greatPersonPoints": {"Great Engineer": 2}, "greatPersonPoints": {"Great Engineer": 2},
"uniques": ["[+1 Production] from every specialist"], "uniques": ["[+1 Production] from every specialist [in all cities]"],
"requiredTech": "Replaceable Parts", "requiredTech": "Replaceable Parts",
"quote": "'Give me your tired, your poor, your huddled masses yearning to breathe free, the wretched refuse of your teeming shore. Send these, the homeless, tempest-tossed to me, I lift my lamp beside the golden door!' - Emma Lazarus" "quote": "'Give me your tired, your poor, your huddled masses yearning to breathe free, the wretched refuse of your teeming shore. Send these, the homeless, tempest-tossed to me, I lift my lamp beside the golden door!' - Emma Lazarus"
}, },
@ -1085,7 +1085,7 @@
"cost": 300, "cost": 300,
"maintenance": 1, "maintenance": 1,
"requiredTech": "Telecommunications", "requiredTech": "Telecommunications",
"uniques": ["Population loss from nuclear attacks -[75]%"] "uniques": ["Population loss from nuclear attacks [-75]% [in this city]"]
}, },
{ {
"name": "SS Cockpit", "name": "SS Cockpit",

View File

@ -314,7 +314,7 @@
"outerColor": [16,126,5], "outerColor": [16,126,5],
"innerColor": [255,153,51], "innerColor": [255,153,51],
"uniqueName": "Population Growth", "uniqueName": "Population Growth",
"uniques": ["Unhappiness from number of Cities doubled", "Unhappiness from population decreased by [50]%"], "uniques": ["Unhappiness from number of Cities doubled", "[-50]% unhappiness from population [in all cities]"],
"cities": ["Delhi","Mumbai","Vijayanagara","Pataliputra","Varanasi","Agra","Calcutta","Lahore","Bangalore","Hyderabad","Madurai","Ahmedabad", "cities": ["Delhi","Mumbai","Vijayanagara","Pataliputra","Varanasi","Agra","Calcutta","Lahore","Bangalore","Hyderabad","Madurai","Ahmedabad",
"Kolhapur","Prayaga","Ayodhya","Indraprastha","Mathura","Ujjain","Gulbarga","Jaunpur","Rajagriha","Sravasti","Tiruchirapalli","Thanjavur", "Kolhapur","Prayaga","Ayodhya","Indraprastha","Mathura","Ujjain","Gulbarga","Jaunpur","Rajagriha","Sravasti","Tiruchirapalli","Thanjavur",
"Bodhgaya","Kushinagar","Amaravati","Gaur","Gwalior","Jaipur","Karachi"] "Bodhgaya","Kushinagar","Amaravati","Gaur","Gwalior","Jaipur","Karachi"]
@ -392,7 +392,7 @@
"outerColor": [26,32,96], "outerColor": [26,32,96],
"innerColor": [255,0,0], "innerColor": [255,0,0],
"uniqueName": "Scholars of the Jade Hall", "uniqueName": "Scholars of the Jade Hall",
"uniques": ["[+2 Science] from every specialist", "[+2 Science] from every [Great Improvement]","Receive a tech boost when scientific buildings/wonders are built in capital"], "uniques": ["[+2 Science] from every specialist [in all cities]", "[+2 Science] from every [Great Improvement]","Receive a tech boost when scientific buildings/wonders are built in capital"],
"cities": ["Seoul","Busan","Jeonju","Daegu","Pyongyang","Kaesong","Suwon","Gwangju","Gangneung","Hamhung","Wonju","Ulsan", "cities": ["Seoul","Busan","Jeonju","Daegu","Pyongyang","Kaesong","Suwon","Gwangju","Gangneung","Hamhung","Wonju","Ulsan",
"Changwon","Andong","Gongju","Haeju","Cheongju","Mokpo","Dongducheon","Geoje","Suncheon","Jinju","Sangju", "Changwon","Andong","Gongju","Haeju","Cheongju","Mokpo","Dongducheon","Geoje","Suncheon","Jinju","Sangju",
"Rason","Gyeongju","Chungju","Sacheon","Gimje","Anju"] "Rason","Gyeongju","Chungju","Sacheon","Gimje","Anju"]

View File

@ -75,7 +75,7 @@
}, },
{ {
"name": "Meritocracy", "name": "Meritocracy",
"uniques": ["[+1 Happiness] [in all cities connected to capital]", "Unhappiness from population decreased by [5]% [in all non-occupied cities]"], "uniques": ["[+1 Happiness] [in all cities connected to capital]", "[-5]% unhappiness from population [in all non-occupied cities]"],
"requires": ["Citizenship"], "requires": ["Citizenship"],
"row": 2, "row": 2,
"column": 5 "column": 5
@ -288,7 +288,7 @@
"policies": [ "policies": [
{ {
"name": "Secularism", "name": "Secularism",
"uniques": ["[+2 Science] from every specialist"], "uniques": ["[+2 Science] from every specialist [in all cities]"],
"row": 1, "row": 1,
"column": 2 "column": 2
}, },
@ -347,7 +347,7 @@
}, },
{ {
"name": "Civil Society", "name": "Civil Society",
"uniques": ["-[50]% food consumption by specialists"], "uniques": ["-[50]% food consumption by specialists [in all cities]"],
"row": 1, "row": 1,
"column": 5 "column": 5
}, },
@ -360,7 +360,7 @@
}, },
{ {
"name": "Democracy", "name": "Democracy",
"uniques": ["Specialists only produce [50]% of normal unhappiness"], "uniques": ["[-50]% unhappiness from specialists [in all cities]"],
"requires": ["Civil Society"], "requires": ["Civil Society"],
"row": 2, "row": 2,
"column": 5 "column": 5

View File

@ -292,22 +292,20 @@ class GameInfo {
for (baseUnit in ruleSet.units.values) for (baseUnit in ruleSet.units.values)
baseUnit.ruleset = ruleSet baseUnit.ruleset = ruleSet
// This needs to go before tileMap.setTransients, as units need to access
// the nation of their civilization when setting transients
for (civInfo in civilizations) civInfo.gameInfo = this
for (civInfo in civilizations) civInfo.setNationTransient()
tileMap.setTransients(ruleSet) tileMap.setTransients(ruleSet)
if (currentPlayer == "") currentPlayer = civilizations.first { it.isPlayerCivilization() }.civName if (currentPlayer == "") currentPlayer = civilizations.first { it.isPlayerCivilization() }.civName
currentPlayerCiv = getCivilization(currentPlayer) currentPlayerCiv = getCivilization(currentPlayer)
// this is separated into 2 loops because when we activate updateVisibleTiles in civ.setTransients,
// we try to find new civs, and we check if civ is barbarian, which we can't know unless the gameInfo is already set.
for (civInfo in civilizations) civInfo.gameInfo = this
difficultyObject = ruleSet.difficulties[difficulty]!! difficultyObject = ruleSet.difficulties[difficulty]!!
for (religion in religions.values) religion.setTransients(this) for (religion in religions.values) religion.setTransients(this)
// This doesn't HAVE to go here, but why not.
for (civInfo in civilizations) civInfo.setNationTransient()
for (civInfo in civilizations) civInfo.setTransients() for (civInfo in civilizations) civInfo.setTransients()
for (civInfo in civilizations) civInfo.updateSightAndResources() for (civInfo in civilizations) civInfo.updateSightAndResources()

View File

@ -484,7 +484,7 @@ object NextTurnAutomation {
val unitsInBorder = otherCiv.getCivUnits().count { !it.isCivilian() && it.getTile().getOwner() == civInfo } val unitsInBorder = otherCiv.getCivUnits().count { !it.isCivilian() && it.getTile().getOwner() == civInfo }
if (unitsInBorder > 0 && diplomacy.relationshipLevel() < RelationshipLevel.Friend) { if (unitsInBorder > 0 && diplomacy.relationshipLevel() < RelationshipLevel.Friend) {
diplomacy.influence -= 10f diplomacy.addInfluence(-10f)
if (!diplomacy.hasFlag(DiplomacyFlags.BorderConflict)) { if (!diplomacy.hasFlag(DiplomacyFlags.BorderConflict)) {
otherCiv.popupAlerts.add(PopupAlert(AlertType.BorderConflict, civInfo.civName)) otherCiv.popupAlerts.add(PopupAlert(AlertType.BorderConflict, civInfo.civName))
diplomacy.setFlag(DiplomacyFlags.BorderConflict, 10) diplomacy.setFlag(DiplomacyFlags.BorderConflict, 10)

View File

@ -6,6 +6,7 @@ import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.* import com.unciv.logic.civilization.*
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.RoadStatus import com.unciv.logic.map.RoadStatus
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
import com.unciv.models.AttackableTile import com.unciv.models.AttackableTile
@ -13,6 +14,7 @@ import com.unciv.models.UnitActionType
import com.unciv.models.ruleset.Unique import com.unciv.models.ruleset.Unique
import com.unciv.models.stats.Stat import com.unciv.models.stats.Stat
import com.unciv.models.stats.Stats import com.unciv.models.stats.Stats
import com.unciv.ui.utils.toPercent
import java.util.* import java.util.*
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
@ -121,10 +123,11 @@ object Battle {
val bonusUniques = ArrayList<Unique>() val bonusUniques = ArrayList<Unique>()
bonusUniques.addAll(civUnit.getCivInfo().getMatchingUniques(bonusUniquePlaceholderText))
if (civUnit is MapUnitCombatant) { if (civUnit is MapUnitCombatant) {
bonusUniques.addAll(civUnit.unit.getMatchingUniques(bonusUniquePlaceholderText)) bonusUniques.addAll(civUnit.getMatchingUniques(bonusUniquePlaceholderText))
} else {
bonusUniques.addAll(civUnit.getCivInfo().getMatchingUniques(bonusUniquePlaceholderText))
} }
bonusUniquePlaceholderText = "Earn []% of [] unit's [] as [] when killed within 4 tiles of a city following this religion" bonusUniquePlaceholderText = "Earn []% of [] unit's [] as [] when killed within 4 tiles of a city following this religion"
@ -386,9 +389,7 @@ object Battle {
if (thisCombatant.getCivInfo().isMajorCiv()) { if (thisCombatant.getCivInfo().isMajorCiv()) {
var greatGeneralPointsModifier = 1f var greatGeneralPointsModifier = 1f
val unitUniques = thisCombatant.unit.getMatchingUniques("[] is earned []% faster") for (unique in thisCombatant.getMatchingUniques("[] is earned []% faster")) {
val civUniques = thisCombatant.getCivInfo().getMatchingUniques("[] is earned []% faster")
for (unique in unitUniques + civUniques) {
val unitName = unique.params[0] val unitName = unique.params[0]
val unit = thisCombatant.getCivInfo().gameInfo.ruleSet.units[unitName] val unit = thisCombatant.getCivInfo().gameInfo.ruleSet.units[unitName]
if (unit != null && unit.uniques.contains("Great Person - [War]")) if (unit != null && unit.uniques.contains("Great Person - [War]"))
@ -400,7 +401,7 @@ object Battle {
} }
} }
private fun conquerCity(city: CityInfo, attacker: ICombatant) { private fun conquerCity(city: CityInfo, attacker: MapUnitCombatant) {
val attackerCiv = attacker.getCivInfo() val attackerCiv = attacker.getCivInfo()
attackerCiv.addNotification("We have conquered the city of [${city.name}]!", city.location, NotificationIcon.War) attackerCiv.addNotification("We have conquered the city of [${city.name}]!", city.location, NotificationIcon.War)
@ -412,10 +413,10 @@ object Battle {
for (airUnit in airUnits.toList()) airUnit.destroy() for (airUnit in airUnits.toList()) airUnit.destroy()
} }
for (unique in attackerCiv.getMatchingUniques("Upon capturing a city, receive [] times its [] production as [] immediately")) { for (unique in attacker.getMatchingUniques("Upon capturing a city, receive [] times its [] production as [] immediately")) {
attackerCiv.addStat( attackerCiv.addStat(
Stat.valueOf(unique.params[2]), Stat.valueOf(unique.params[2]),
unique.params[0].toInt() * city.cityStats.currentCityStats.get(Stat.valueOf(unique.params[1])).toInt() unique.params[0].toInt() * city.cityStats.currentCityStats[Stat.valueOf(unique.params[1])].toInt()
) )
} }
@ -559,6 +560,10 @@ object Battle {
for (civ in attackingCiv.getKnownCivs()) { for (civ in attackingCiv.getKnownCivs()) {
civ.getDiplomacyManager(attackingCiv).setModifier(DiplomaticModifiers.UsedNuclearWeapons, -50f) civ.getDiplomacyManager(attackingCiv).setModifier(DiplomaticModifiers.UsedNuclearWeapons, -50f)
} }
if (!attacker.isDefeated()) {
attacker.unit.attacksThisTurn += 1
}
} }
// todo: reduce extreme code duplication, parameterize probabilities where an unique already used // todo: reduce extreme code duplication, parameterize probabilities where an unique already used
@ -578,8 +583,15 @@ object Battle {
if (city != null && tile.position == city.location) { if (city != null && tile.position == city.location) {
var populationLoss = city.population.population * (0.3 + Random().nextFloat() * 0.4) var populationLoss = city.population.population * (0.3 + Random().nextFloat() * 0.4)
var populationLossReduced = false var populationLossReduced = false
for (unique in city.civInfo.getMatchingUniques("Population loss from nuclear attacks -[]%")) { // Deprecated since 3.16.11
populationLoss *= 1 - unique.params[0].toFloat() / 100f for (unique in city.getLocalMatchingUniques("Population loss from nuclear attacks -[]%")) {
populationLoss *= 1 - unique.params[0].toFloat() / 100f
populationLossReduced = true
}
//
for (unique in city.getMatchingUniques("Population loss from nuclear attacks []% []")) {
if (!city.matchesFilter(unique.params[1])) continue
populationLoss *= unique.params[0].toPercent()
populationLossReduced = true populationLossReduced = true
} }
if (city.population.population < 5 && !populationLossReduced) { if (city.population.population < 5 && !populationLossReduced) {
@ -647,8 +659,15 @@ object Battle {
} else { } else {
var populationLoss = city.population.population * (0.6 + Random().nextFloat() * 0.2) var populationLoss = city.population.population * (0.6 + Random().nextFloat() * 0.2)
var populationLossReduced = false var populationLossReduced = false
for (unique in city.civInfo.getMatchingUniques("Population loss from nuclear attacks -[]%")) { // Deprecated since 3.15.11
populationLoss *= 1 - unique.params[0].toFloat() / 100f for (unique in city.getLocalMatchingUniques("Population loss from nuclear attacks -[]%")) {
populationLoss *= 1 - unique.params[0].toFloat() / 100f
populationLossReduced = true
}
//
for (unique in city.getMatchingUniques("Population loss from nuclear attacks []% []")) {
if (!city.matchesFilter(unique.params[1]))
populationLoss *= unique.params[0].toPercent()
populationLossReduced = true populationLossReduced = true
} }
city.population.addPopulation(-populationLoss.toInt()) city.population.addPopulation(-populationLoss.toInt())

View File

@ -4,6 +4,7 @@ import com.unciv.logic.civilization.CivilizationInfo
import com.unciv.logic.map.MapUnit import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.TileInfo import com.unciv.logic.map.TileInfo
import com.unciv.models.UncivSound import com.unciv.models.UncivSound
import com.unciv.models.ruleset.Unique
import com.unciv.models.ruleset.unit.UnitType import com.unciv.models.ruleset.unit.UnitType
class MapUnitCombatant(val unit: MapUnit) : ICombatant { class MapUnitCombatant(val unit: MapUnit) : ICombatant {
@ -43,5 +44,6 @@ class MapUnitCombatant(val unit: MapUnit) : ICombatant {
return unit.name+" of "+unit.civInfo.civName return unit.name+" of "+unit.civInfo.civName
} }
fun getMatchingUniques(uniqueTemplate: String): Sequence<Unique> = unit.getMatchingUniques(uniqueTemplate)
} }

View File

@ -141,7 +141,7 @@ class CityInfo {
civInfo.policies.tryToAddPolicyBuildings() civInfo.policies.tryToAddPolicyBuildings()
for (unique in civInfo.getMatchingUniques("Gain a free [] []")) { for (unique in getMatchingUniques("Gain a free [] []")) {
val freeBuildingName = unique.params[0] val freeBuildingName = unique.params[0]
if (matchesFilter(unique.params[1])) { if (matchesFilter(unique.params[1])) {
if (!cityConstructions.isBuilt(freeBuildingName)) if (!cityConstructions.isBuilt(freeBuildingName))
@ -370,15 +370,15 @@ class CityInfo {
} }
// Sweden UP // Sweden UP
for (otherciv in civInfo.getKnownCivs()) { for (otherCiv in civInfo.getKnownCivs()) {
if (!civInfo.getDiplomacyManager(otherciv) if (!civInfo.getDiplomacyManager(otherCiv)
.hasFlag(DiplomacyFlags.DeclarationOfFriendship) .hasFlag(DiplomacyFlags.DeclarationOfFriendship)
) continue ) continue
for (ourunique in civInfo.getMatchingUniques("When declaring friendship, both parties gain a []% boost to great person generation")) for (ourUnique in civInfo.getMatchingUniques("When declaring friendship, both parties gain a []% boost to great person generation"))
allGppPercentageBonus += ourunique.params[0].toInt() allGppPercentageBonus += ourUnique.params[0].toInt()
for (theirunique in otherciv.getMatchingUniques("When declaring friendship, both parties gain a []% boost to great person generation")) for (theirUnique in otherCiv.getMatchingUniques("When declaring friendship, both parties gain a []% boost to great person generation"))
allGppPercentageBonus += theirunique.params[0].toInt() allGppPercentageBonus += theirUnique.params[0].toInt()
} }
for (unitName in gppCounter.keys) for (unitName in gppCounter.keys)

View File

@ -181,7 +181,7 @@ class CityInfoConquestFunctions(val city: CityInfo){
.addModifier(DiplomaticModifiers.CapturedOurCities, respectForLiberatingOurCity) .addModifier(DiplomaticModifiers.CapturedOurCities, respectForLiberatingOurCity)
} else { } else {
//Liberating a city state gives a large amount of influence, and peace //Liberating a city state gives a large amount of influence, and peace
foundingCiv.getDiplomacyManager(conqueringCiv).influence = 90f foundingCiv.getDiplomacyManager(conqueringCiv).setInfluence(90f)
if (foundingCiv.isAtWarWith(conqueringCiv)) { if (foundingCiv.isAtWarWith(conqueringCiv)) {
val tradeLogic = TradeLogic(foundingCiv, conqueringCiv) val tradeLogic = TradeLogic(foundingCiv, conqueringCiv)
tradeLogic.currentTrade.ourOffers.add(TradeOffer(Constants.peaceTreaty, TradeType.Treaty)) tradeLogic.currentTrade.ourOffers.add(TradeOffer(Constants.peaceTreaty, TradeType.Treaty))

View File

@ -294,10 +294,7 @@ class CityInfoReligionManager {
for (unique in cityInfo.getMatchingUniques("[]% Natural religion spread [] with []")) for (unique in cityInfo.getMatchingUniques("[]% Natural religion spread [] with []"))
if (pressuredCity.matchesFilter(unique.params[1]) if (pressuredCity.matchesFilter(unique.params[1])
&& ( && cityInfo.civInfo.hasTechOrPolicy(unique.params[2])
cityInfo.civInfo.tech.isResearched(unique.params[2])
|| cityInfo.civInfo.policies.isAdopted(unique.params[2])
)
) pressure *= 1f + unique.params[0].toFloat() / 100f ) pressure *= 1f + unique.params[0].toFloat() / 100f
return pressure.toInt() return pressure.toInt()

View File

@ -55,7 +55,7 @@ class CityStats(val cityInfo: CityInfo) {
if (!cityInfo.isCapital() && cityInfo.isConnectedToCapital()) { if (!cityInfo.isCapital() && cityInfo.isConnectedToCapital()) {
val civInfo = cityInfo.civInfo val civInfo = cityInfo.civInfo
stats.gold = civInfo.getCapital().population.population * 0.15f + cityInfo.population.population * 1.1f - 1 // Calculated by http://civilization.wikia.com/wiki/Trade_route_(Civ5) stats.gold = civInfo.getCapital().population.population * 0.15f + cityInfo.population.population * 1.1f - 1 // Calculated by http://civilization.wikia.com/wiki/Trade_route_(Civ5)
for (unique in civInfo.getMatchingUniques("[] from each Trade Route")) for (unique in cityInfo.getMatchingUniques("[] from each Trade Route"))
stats.add(unique.stats) stats.add(unique.stats)
if (civInfo.hasUnique("Gold from all trade routes +25%")) stats.gold *= 1.25f // Machu Picchu speciality if (civInfo.hasUnique("Gold from all trade routes +25%")) stats.gold *= 1.25f // Machu Picchu speciality
} }
@ -105,20 +105,7 @@ class CityStats(val cityInfo: CityInfo) {
} }
private fun getStatsFromNationUnique(): Stats { private fun getStatsFromNationUnique(): Stats {
val stats = Stats() return getStatsFromUniques(cityInfo.civInfo.nation.uniqueObjects.asSequence())
stats.add(getStatsFromUniques(cityInfo.civInfo.nation.uniqueObjects.asSequence()))
if (cityInfo.civInfo.hasUnique("+2 Culture per turn from cities before discovering Steam Power")
&& !cityInfo.civInfo.tech.isResearched("Steam Power"))
stats.culture += 2
for (unique in cityInfo.civInfo.getMatchingUniques("[] per turn from cities before []")) {
if (!cityInfo.civInfo.tech.isResearched(unique.params[1])
&& !cityInfo.civInfo.policies.adoptedPolicies.contains(unique.params[1]))
stats.add(unique.stats)
}
return stats
} }
private fun getStatsFromCityStates(): Stats { private fun getStatsFromCityStates(): Stats {
@ -140,8 +127,11 @@ class CityStats(val cityInfo: CityInfo) {
return stats return stats
} }
for (bonus in if (otherCiv.getDiplomacyManager(cityInfo.civInfo).relationshipLevel() == RelationshipLevel.Friend) for (bonus in
eraInfo.friendBonus[otherCiv.cityStateType.name]!! else eraInfo.allyBonus[otherCiv.cityStateType.name]!!) { if (otherCiv.getDiplomacyManager(cityInfo.civInfo).relationshipLevel() == RelationshipLevel.Friend)
eraInfo.friendBonus[otherCiv.cityStateType.name]!!
else eraInfo.allyBonus[otherCiv.cityStateType.name]!!
) {
if (bonus.getPlaceholderText() == "Provides [] [] []" && cityInfo.matchesFilter(bonus.getPlaceholderParameters()[2])) { if (bonus.getPlaceholderText() == "Provides [] [] []" && cityInfo.matchesFilter(bonus.getPlaceholderParameters()[2])) {
stats.add(Stat.valueOf(bonus.getPlaceholderParameters()[1]), bonus.getPlaceholderParameters()[0].toFloat()) stats.add(Stat.valueOf(bonus.getPlaceholderParameters()[1]), bonus.getPlaceholderParameters()[0].toFloat())
} }
@ -200,8 +190,13 @@ class CityStats(val cityInfo: CityInfo) {
val specialist = cityInfo.getRuleset().specialists[specialistName] val specialist = cityInfo.getRuleset().specialists[specialistName]
?: return Stats() ?: return Stats()
val stats = specialist.clone() val stats = specialist.clone()
for (unique in cityInfo.civInfo.getMatchingUniques("[] from every specialist")) // Deprecated since 3.16.11
stats.add(unique.stats) for (unique in cityInfo.civInfo.getMatchingUniques("[] from every specialist"))
stats.add(unique.stats)
//
for (unique in cityInfo.getMatchingUniques("[] from every specialist []"))
if (cityInfo.matchesFilter(unique.params[1]))
stats.add(unique.stats)
for (unique in cityInfo.civInfo.getMatchingUniques("[] from every []")) for (unique in cityInfo.civInfo.getMatchingUniques("[] from every []"))
if (unique.params[1] == specialistName) if (unique.params[1] == specialistName)
stats.add(unique.stats) stats.add(unique.stats)
@ -240,6 +235,14 @@ class CityStats(val cityInfo: CityInfo) {
// "[stats] if this city has at least [amount] specialists" // "[stats] if this city has at least [amount] specialists"
if (unique.placeholderText == "[] if this city has at least [] specialists" && cityInfo.population.getNumberOfSpecialists() >= unique.params[1].toInt()) if (unique.placeholderText == "[] if this city has at least [] specialists" && cityInfo.population.getNumberOfSpecialists() >= unique.params[1].toInt())
stats.add(unique.stats) stats.add(unique.stats)
// Deprecated since a very long time ago, moved here from another code section
if (unique.placeholderText == "+2 Culture per turn from cities before discovering Steam Power" && !cityInfo.civInfo.tech.isResearched("Steam Power"))
stats.culture += 2
//
if (unique.placeholderText == "[] per turn from cities before []" && !cityInfo.civInfo.hasTechOrPolicy(unique.params[1]))
stats.add(unique.stats)
} }
return stats return stats
@ -371,8 +374,14 @@ class CityStats(val cityInfo: CityInfo) {
var unhappinessFromCitizens = cityInfo.population.population.toFloat() var unhappinessFromCitizens = cityInfo.population.population.toFloat()
var unhappinessFromSpecialists = cityInfo.population.getNumberOfSpecialists().toFloat() var unhappinessFromSpecialists = cityInfo.population.getNumberOfSpecialists().toFloat()
for (unique in civInfo.getMatchingUniques("Specialists only produce []% of normal unhappiness")) { // Deprecated since 3.16.11
unhappinessFromSpecialists *= (1f - unique.params[0].toFloat() / 100f) for (unique in civInfo.getMatchingUniques("Specialists only produce []% of normal unhappiness"))
unhappinessFromSpecialists *= (1f - unique.params[0].toFloat() / 100f)
//
for (unique in cityInfo.getMatchingUniques("[]% unhappiness from specialists []")) {
if (cityInfo.matchesFilter(unique.params[1]))
unhappinessFromSpecialists *= unique.params[0].toPercent()
} }
unhappinessFromCitizens -= cityInfo.population.getNumberOfSpecialists().toFloat() - unhappinessFromSpecialists unhappinessFromCitizens -= cityInfo.population.getNumberOfSpecialists().toFloat() - unhappinessFromSpecialists
@ -382,13 +391,19 @@ class CityStats(val cityInfo: CityInfo) {
else if (hasExtraAnnexUnhappiness()) else if (hasExtraAnnexUnhappiness())
unhappinessFromCitizens *= 2f unhappinessFromCitizens *= 2f
for (unique in civInfo.getMatchingUniques("Unhappiness from population decreased by []%")) // Deprecated since 3.16.11
unhappinessFromCitizens *= (1 - unique.params[0].toFloat() / 100) for (unique in civInfo.getMatchingUniques("Unhappiness from population decreased by []%"))
for (unique in civInfo.getMatchingUniques("Unhappiness from population decreased by []% []"))
if (cityInfo.matchesFilter(unique.params[1]))
unhappinessFromCitizens *= (1 - unique.params[0].toFloat() / 100) unhappinessFromCitizens *= (1 - unique.params[0].toFloat() / 100)
for (unique in civInfo.getMatchingUniques("Unhappiness from population decreased by []% []"))
if (cityInfo.matchesFilter(unique.params[1]))
unhappinessFromCitizens *= (1 - unique.params[0].toFloat() / 100)
//
for (unique in cityInfo.getMatchingUniques("[]% unhappiness from population []"))
if (cityInfo.matchesFilter(unique.params[1]))
unhappinessFromCitizens *= unique.params[0].toPercent()
newHappinessList["Population"] = -unhappinessFromCitizens * unhappinessModifier newHappinessList["Population"] = -unhappinessFromCitizens * unhappinessModifier
val happinessFromPolicies = getStatsFromUniques(civInfo.policies.policyUniques.getAllUniques()).happiness val happinessFromPolicies = getStatsFromUniques(civInfo.policies.policyUniques.getAllUniques()).happiness
@ -563,8 +578,14 @@ class CityStats(val cityInfo: CityInfo) {
foodEaten = cityInfo.population.population.toFloat() * 2 foodEaten = cityInfo.population.population.toFloat() * 2
var foodEatenBySpecialists = 2f * cityInfo.population.getNumberOfSpecialists() var foodEatenBySpecialists = 2f * cityInfo.population.getNumberOfSpecialists()
for (unique in cityInfo.civInfo.getMatchingUniques("-[]% food consumption by specialists")) // Deprecated since 3.16.11
foodEatenBySpecialists *= 1f - unique.params[0].toFloat() / 100f for (unique in cityInfo.civInfo.getMatchingUniques("-[]% food consumption by specialists"))
foodEatenBySpecialists *= 1f - unique.params[0].toFloat() / 100f
//
for (unique in cityInfo.getMatchingUniques("[]% food consumption by specialists []"))
if (cityInfo.matchesFilter(unique.params[1]))
foodEatenBySpecialists *= unique.params[0].toPercent()
foodEaten -= 2f * cityInfo.population.getNumberOfSpecialists() - foodEatenBySpecialists foodEaten -= 2f * cityInfo.population.getNumberOfSpecialists() - foodEatenBySpecialists
} }

View File

@ -1,6 +1,7 @@
package com.unciv.logic.civilization package com.unciv.logic.civilization
import com.unciv.logic.civilization.diplomacy.RelationshipLevel import com.unciv.logic.civilization.diplomacy.RelationshipLevel
import com.unciv.logic.map.RoadStatus
import com.unciv.models.metadata.BASE_GAME_DURATION_TURNS import com.unciv.models.metadata.BASE_GAME_DURATION_TURNS
import com.unciv.models.ruleset.BeliefType import com.unciv.models.ruleset.BeliefType
import com.unciv.models.ruleset.Policy import com.unciv.models.ruleset.Policy
@ -60,12 +61,13 @@ class CivInfoStats(val civInfo: CivilizationInfo) {
// just to go over them once is a waste of memory - there are low-end phones who don't have much ram // just to go over them once is a waste of memory - there are low-end phones who don't have much ram
val ignoredTileTypes = civInfo.getMatchingUniques("No Maintenance costs for improvements in [] tiles") val ignoredTileTypes = civInfo.getMatchingUniques("No Maintenance costs for improvements in [] tiles")
.map { it.params[0] }.toHashSet() // needs to be .toHashSet()ed, .map { it.params[0] }.toHashSet() // needs to be .toHashSet()ed,
// Because we go over every tile in every city and check if it's in this list, which can get real heavy. // Because we go over every tile in every city and check if it's in this list, which can get real heavy.
for (city in civInfo.cities) { for (city in civInfo.cities) {
for (tile in city.getTiles()) { for (tile in city.getTiles()) {
if (tile.isCityCenter()) continue if (tile.isCityCenter()) continue
if (tile.roadStatus == RoadStatus.None) continue // Cheap checks before pricy checks
if (ignoredTileTypes.any { tile.matchesFilter(it, civInfo) }) continue if (ignoredTileTypes.any { tile.matchesFilter(it, civInfo) }) continue
transportationUpkeep += tile.roadStatus.upkeep transportationUpkeep += tile.roadStatus.upkeep
@ -185,20 +187,21 @@ class CivInfoStats(val civInfo: CivilizationInfo) {
val ownedLuxuries = civInfo.getCivResources().map { it.resource }.filter { it.resourceType == ResourceType.Luxury } val ownedLuxuries = civInfo.getCivResources().map { it.resource }.filter { it.resourceType == ResourceType.Luxury }
statMap["Luxury resources"] = civInfo.getCivResources().map { it.resource } statMap["Luxury resources"] = civInfo.getCivResources()
.count { it.resourceType === ResourceType.Luxury } * happinessPerUniqueLuxury .map { it.resource }
.count { it.resourceType === ResourceType.Luxury } * happinessPerUniqueLuxury
val happinessBonusForCityStateProvidedLuxuries = val happinessBonusForCityStateProvidedLuxuries =
civInfo.getMatchingUniques("Happiness from Luxury Resources gifted by City-States increased by []%") civInfo.getMatchingUniques("Happiness from Luxury Resources gifted by City-States increased by []%")
.map { it.params[0].toFloat() / 100f }.sum() .sumBy { it.params[0].toInt() } / 100f
val luxuriesProvidedByCityStates = val luxuriesProvidedByCityStates = civInfo.getKnownCivs().asSequence()
civInfo.getKnownCivs().asSequence() .filter { it.isCityState() && it.getAllyCiv() == civInfo.civName }
.filter { it.isCityState() && it.getAllyCiv() == civInfo.civName } .flatMap { it.getCivResources().map { res -> res.resource } }
.flatMap { it.getCivResources().map { res -> res.resource } } .distinct()
.distinct().count { it.resourceType === ResourceType.Luxury } .count { it.resourceType === ResourceType.Luxury && ownedLuxuries.contains(it) }
statMap["City-State Luxuries"] = happinessBonusForCityStateProvidedLuxuries * luxuriesProvidedByCityStates * happinessPerUniqueLuxury statMap["City-State Luxuries"] = happinessPerUniqueLuxury * luxuriesProvidedByCityStates * happinessBonusForCityStateProvidedLuxuries
val luxuriesAllOfWhichAreTradedAway = civInfo.detailedCivResources val luxuriesAllOfWhichAreTradedAway = civInfo.detailedCivResources
.filter { it.amount < 0 && it.resource.resourceType == ResourceType.Luxury .filter { it.amount < 0 && it.resource.resourceType == ResourceType.Luxury
@ -256,7 +259,7 @@ class CivInfoStats(val civInfo: CivilizationInfo) {
for (otherCiv in civInfo.getKnownCivs()) { for (otherCiv in civInfo.getKnownCivs()) {
if (otherCiv.isCityState() && otherCiv.getDiplomacyManager(civInfo).relationshipLevel() >= RelationshipLevel.Friend) { if (otherCiv.isCityState() && otherCiv.getDiplomacyManager(civInfo).relationshipLevel() >= RelationshipLevel.Friend) {
val eraInfo = civInfo.getEraObject() val eraInfo = civInfo.getEraObject()
val relevantbonuses = val relevantBonuses =
when { when {
eraInfo == null -> null eraInfo == null -> null
otherCiv.getDiplomacyManager(civInfo).relationshipLevel() == RelationshipLevel.Friend -> otherCiv.getDiplomacyManager(civInfo).relationshipLevel() == RelationshipLevel.Friend ->
@ -265,8 +268,8 @@ class CivInfoStats(val civInfo: CivilizationInfo) {
eraInfo.allyBonus[otherCiv.cityStateType.name] eraInfo.allyBonus[otherCiv.cityStateType.name]
} }
if (relevantbonuses != null) { if (relevantBonuses != null) {
for (bonus in relevantbonuses) { for (bonus in relevantBonuses) {
if (bonus.getPlaceholderText() == "Provides [] Happiness") { if (bonus.getPlaceholderText() == "Provides [] Happiness") {
if (statMap.containsKey("City-States")) if (statMap.containsKey("City-States"))
statMap["City-States"] = statMap["City-States"]!! + bonus.getPlaceholderParameters()[0].toFloat() statMap["City-States"] = statMap["City-States"]!! + bonus.getPlaceholderParameters()[0].toFloat()

View File

@ -511,6 +511,9 @@ class CivilizationInfo {
else greatPeople.toHashSet() else greatPeople.toHashSet()
} }
fun hasTechOrPolicy(techOrPolicyName: String) =
tech.isResearched(techOrPolicyName) || policies.isAdopted(techOrPolicyName)
//endregion //endregion
//region state-changing functions //region state-changing functions
@ -881,8 +884,7 @@ class CivilizationInfo {
if (!cityState.isCityState()) throw Exception("You can only gain influence with City-States!") if (!cityState.isCityState()) throw Exception("You can only gain influence with City-States!")
addGold(-giftAmount) addGold(-giftAmount)
cityState.addGold(giftAmount) cityState.addGold(giftAmount)
cityState.getDiplomacyManager(this).influence += influenceGainedByGift(giftAmount) cityState.getDiplomacyManager(this).addInfluence(influenceGainedByGift(giftAmount).toFloat())
cityState.updateAllyCivForCityState()
updateStatsForNextTurn() updateStatsForNextTurn()
} }
@ -940,20 +942,20 @@ class CivilizationInfo {
} }
fun addProtectorCiv(otherCiv: CivilizationInfo) { fun addProtectorCiv(otherCiv: CivilizationInfo) {
if(!this.isCityState() or !otherCiv.isMajorCiv() or otherCiv.isDefeated()) return if (!isCityState() || !otherCiv.isMajorCiv() || otherCiv.isDefeated()) return
if(!knows(otherCiv) or isAtWarWith(otherCiv)) return //Exception if (!knows(otherCiv) || isAtWarWith(otherCiv)) return //Exception
val diplomacy = getDiplomacyManager(otherCiv.civName) val diplomacy = getDiplomacyManager(otherCiv.civName)
diplomacy.diplomaticStatus = DiplomaticStatus.Protector diplomacy.diplomaticStatus = DiplomaticStatus.Protector
} }
fun removeProtectorCiv(otherCiv: CivilizationInfo) { fun removeProtectorCiv(otherCiv: CivilizationInfo) {
if(!this.isCityState() or !otherCiv.isMajorCiv() or otherCiv.isDefeated()) return if (!isCityState() || !otherCiv.isMajorCiv() || otherCiv.isDefeated()) return
if(!knows(otherCiv) or isAtWarWith(otherCiv)) return //Exception if (!knows(otherCiv) || isAtWarWith(otherCiv)) return //Exception
val diplomacy = getDiplomacyManager(otherCiv.civName) val diplomacy = getDiplomacyManager(otherCiv.civName)
diplomacy.diplomaticStatus = DiplomaticStatus.Peace diplomacy.diplomaticStatus = DiplomaticStatus.Peace
diplomacy.influence -= 20 diplomacy.addInfluence(-20f)
} }
fun updateAllyCivForCityState() { fun updateAllyCivForCityState() {
@ -1136,9 +1138,8 @@ class CivilizationInfo {
if (!cityState.isCityState()) throw Exception("You can only demand gold from City-States!") if (!cityState.isCityState()) throw Exception("You can only demand gold from City-States!")
val goldAmount = goldGainedByTribute() val goldAmount = goldGainedByTribute()
addGold(goldAmount) addGold(goldAmount)
cityState.getDiplomacyManager(this).influence -= 15 cityState.getDiplomacyManager(this).addInfluence(-15f)
cityState.addFlag(CivFlags.RecentlyBullied.name, 20) cityState.addFlag(CivFlags.RecentlyBullied.name, 20)
cityState.updateAllyCivForCityState()
updateStatsForNextTurn() updateStatsForNextTurn()
} }
@ -1153,9 +1154,8 @@ class CivilizationInfo {
if (buildableWorkerLikeUnits.isEmpty()) return // Bad luck? if (buildableWorkerLikeUnits.isEmpty()) return // Bad luck?
placeUnitNearTile(cityState.getCapital().location, buildableWorkerLikeUnits.keys.random()) placeUnitNearTile(cityState.getCapital().location, buildableWorkerLikeUnits.keys.random())
cityState.getDiplomacyManager(this).influence -= 50 cityState.getDiplomacyManager(this).addInfluence(-50f)
cityState.addFlag(CivFlags.RecentlyBullied.name, 20) cityState.addFlag(CivFlags.RecentlyBullied.name, 20)
cityState.updateAllyCivForCityState()
} }
fun canGiveStat(statType: Stat): Boolean { fun canGiveStat(statType: Stat): Boolean {

View File

@ -343,7 +343,7 @@ class QuestManager {
val rewardInfluence = civInfo.gameInfo.ruleSet.quests[assignedQuest.questName]!!.influece val rewardInfluence = civInfo.gameInfo.ruleSet.quests[assignedQuest.questName]!!.influece
val assignee = civInfo.gameInfo.getCivilization(assignedQuest.assignee) val assignee = civInfo.gameInfo.getCivilization(assignedQuest.assignee)
civInfo.getDiplomacyManager(assignedQuest.assignee).influence += rewardInfluence civInfo.getDiplomacyManager(assignedQuest.assignee).addInfluence(rewardInfluence)
if (rewardInfluence > 0) if (rewardInfluence > 0)
assignee.addNotification( assignee.addNotification(
"[${civInfo.civName}] rewarded you with [${rewardInfluence.toInt()}] influence for completing the [${assignedQuest.questName}] quest.", "[${civInfo.civName}] rewarded you with [${rewardInfluence.toInt()}] influence for completing the [${assignedQuest.questName}] quest.",

View File

@ -6,6 +6,7 @@ import com.unciv.models.Religion
import com.unciv.models.ruleset.Belief import com.unciv.models.ruleset.Belief
import com.unciv.models.ruleset.BeliefType import com.unciv.models.ruleset.BeliefType
import com.unciv.ui.pickerscreens.BeliefContainer import com.unciv.ui.pickerscreens.BeliefContainer
import com.unciv.ui.utils.toPercent
import kotlin.random.Random import kotlin.random.Random
class ReligionManager { class ReligionManager {
@ -110,7 +111,7 @@ class ReligionManager {
civInfo.gameInfo.gameParameters.gameSpeed.modifier civInfo.gameInfo.gameParameters.gameSpeed.modifier
for (unique in civInfo.getMatchingUniques("[]% Faith cost of generating Great Prophet equivalents")) for (unique in civInfo.getMatchingUniques("[]% Faith cost of generating Great Prophet equivalents"))
faithCost *= 1f + unique.params[0].toFloat() / 100f faithCost *= unique.params[0].toPercent()
return faithCost.toInt() return faithCost.toInt()
} }

View File

@ -100,9 +100,7 @@ class DiplomacyManager() {
/** For city-states. Influence is saved in the CITY STATE -> major civ Diplomacy, NOT in the major civ -> city state diplomacy. /** For city-states. Influence is saved in the CITY STATE -> major civ Diplomacy, NOT in the major civ -> city state diplomacy.
* Won't go below [MINIMUM_INFLUENCE]. Note this declaration leads to Major Civs getting MINIMUM_INFLUENCE serialized, but that is ignored. */ * Won't go below [MINIMUM_INFLUENCE]. Note this declaration leads to Major Civs getting MINIMUM_INFLUENCE serialized, but that is ignored. */
var influence = 0f var influence = 0f
set(value) { private set
field = max(value, MINIMUM_INFLUENCE)
}
get() = if (civInfo.isAtWarWith(otherCiv())) MINIMUM_INFLUENCE else field get() = if (civInfo.isAtWarWith(otherCiv())) MINIMUM_INFLUENCE else field
/** Total of each turn Science during Research Agreement */ /** Total of each turn Science during Research Agreement */
@ -199,6 +197,15 @@ class DiplomacyManager() {
} }
} }
fun addInfluence(amount: Float) {
setInfluence(influence + amount)
}
fun setInfluence(amount: Float) {
influence = max(amount, MINIMUM_INFLUENCE)
civInfo.updateAllyCivForCityState()
}
// 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
@ -393,6 +400,7 @@ class DiplomacyManager() {
val increment = getCityStateInfluenceRecovery() val increment = getCityStateInfluenceRecovery()
influence = min(restingPoint, influence + increment) influence = min(restingPoint, influence + increment)
} }
civInfo.updateAllyCivForCityState()
if (!civInfo.isDefeated()) { // don't display city state relationship notifications when the city state is currently defeated if (!civInfo.isDefeated()) { // don't display city state relationship notifications when the city state is currently defeated
val civCapitalLocation = if (civInfo.cities.isNotEmpty()) civInfo.getCapital().location else null val civCapitalLocation = if (civInfo.cities.isNotEmpty()) civInfo.getCapital().location else null
@ -658,7 +666,7 @@ class DiplomacyManager() {
thirdCiv.getDiplomacyManager(otherCiv).makePeace() thirdCiv.getDiplomacyManager(otherCiv).makePeace()
// Other city states that are not our ally don't like the fact that we made peace with their enemy // Other city states that are not our ally don't like the fact that we made peace with their enemy
if (thirdCiv.getAllyCiv() != civInfo.civName && thirdCiv.isAtWarWith(otherCiv)) if (thirdCiv.getAllyCiv() != civInfo.civName && thirdCiv.isAtWarWith(otherCiv))
thirdCiv.getDiplomacyManager(civInfo).influence -= 10 thirdCiv.getDiplomacyManager(civInfo).addInfluence(-10f)
} }
} }

View File

@ -168,9 +168,10 @@ class MapUnit {
fun getTile(): TileInfo = currentTile fun getTile(): TileInfo = currentTile
fun getMaxMovement(): Int { fun getMaxMovement(): Int {
if (isEmbarked()) return getEmbarkedMovement() var movement =
if (isEmbarked()) 2
else baseUnit.movement
var movement = baseUnit.movement
movement += getMatchingUniques("[] Movement").sumBy { it.params[0].toInt() } movement += getMatchingUniques("[] Movement").sumBy { it.params[0].toInt() }
for (unique in civInfo.getMatchingUniques("+[] Movement for all [] units")) for (unique in civInfo.getMatchingUniques("+[] Movement for all [] units"))
@ -182,6 +183,13 @@ class MapUnit {
) )
movement += 1 movement += 1
// Deprecated since 3.16.11
if (isEmbarked()) {
movement += civInfo.getMatchingUniques("Increases embarked movement +1").count()
if (civInfo.hasUnique("+1 Movement for all embarked units")) movement += 1
}
//
return movement return movement
} }
@ -194,10 +202,11 @@ class MapUnit {
fun getUniques(): ArrayList<Unique> = tempUniques fun getUniques(): ArrayList<Unique> = tempUniques
fun getMatchingUniques(placeholderText: String): Sequence<Unique> = fun getMatchingUniques(placeholderText: String): Sequence<Unique> =
tempUniques.asSequence().filter { it.placeholderText == placeholderText } tempUniques.asSequence().filter { it.placeholderText == placeholderText } +
civInfo.getMatchingUniques(placeholderText)
fun hasUnique(unique: String): Boolean { fun hasUnique(unique: String): Boolean {
return getUniques().any { it.placeholderText == unique } return getUniques().any { it.placeholderText == unique } || civInfo.hasUnique(unique)
} }
fun updateUniques() { fun updateUniques() {
@ -341,7 +350,6 @@ class MapUnit {
return range return range
} }
fun isEmbarked(): Boolean { fun isEmbarked(): Boolean {
if (!baseUnit.isLandUnit()) return false if (!baseUnit.isLandUnit()) return false
return currentTile.isWater return currentTile.isWater
@ -357,13 +365,6 @@ class MapUnit {
return false return false
} }
fun getEmbarkedMovement(): Int {
var movement = 2
movement += civInfo.getMatchingUniques("Increases embarked movement +1").count()
if (civInfo.hasUnique("+1 Movement for all embarked units")) movement += 1
return movement
}
fun getUnitToUpgradeTo(): BaseUnit { fun getUnitToUpgradeTo(): BaseUnit {
var unit = baseUnit() var unit = baseUnit()

View File

@ -392,7 +392,7 @@ class TileMap {
unitName: String, unitName: String,
civInfo: CivilizationInfo civInfo: CivilizationInfo
): MapUnit? { ): MapUnit? {
val unit = gameInfo.ruleSet.units[unitName]!!.getMapUnit(gameInfo.ruleSet) val unit = gameInfo.ruleSet.units[unitName]!!.getMapUnit(civInfo)
fun getPassableNeighbours(tileInfo: TileInfo): Set<TileInfo> = fun getPassableNeighbours(tileInfo: TileInfo): Set<TileInfo> =
tileInfo.neighbors.filter { unit.movement.canPassThrough(it) }.toSet() tileInfo.neighbors.filter { unit.movement.canPassThrough(it) }.toSet()

View File

@ -192,10 +192,8 @@ class Building : NamedStats(), INonPerpetualConstruction, ICivilopediaText {
val stats = percentStatBonus?.clone() ?: Stats() val stats = percentStatBonus?.clone() ?: Stats()
val civInfo = cityInfo?.civInfo ?: return stats // initial stats val civInfo = cityInfo?.civInfo ?: return stats // initial stats
val baseBuildingName = getBaseBuilding(civInfo.gameInfo.ruleSet).name
for (unique in civInfo.getMatchingUniques("+[]% [] from every []")) { for (unique in civInfo.getMatchingUniques("+[]% [] from every []")) {
if (unique.params[2] == baseBuildingName) if (matchesFilter(unique.params[2]))
stats.add(Stat.valueOf(unique.params[1]), unique.params[0].toFloat()) stats.add(Stat.valueOf(unique.params[1]), unique.params[0].toFloat())
} }
@ -521,7 +519,7 @@ class Building : NamedStats(), INonPerpetualConstruction, ICivilopediaText {
val filter = unique.params[0] val filter = unique.params[0]
if (filter in civInfo.gameInfo.ruleSet.buildings) { if (filter in civInfo.gameInfo.ruleSet.buildings) {
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" // this reason should not be displayed } else if (!civInfo.policies.isAdopted(filter)) return "Policy is not adopted" // this reason should not be displayed
} }
"Requires a [] in this city" -> { "Requires a [] in this city" -> {
@ -673,10 +671,6 @@ class Building : NamedStats(), INonPerpetualConstruction, ICivilopediaText {
return false return false
} }
fun getBaseBuilding(ruleset: Ruleset): Building {
return if (replaces == null) this else ruleset.buildings[replaces!!]!!
}
fun getImprovement(ruleset: Ruleset): TileImprovement? { fun getImprovement(ruleset: Ruleset): TileImprovement? {
val improvementUnique = uniqueObjects val improvementUnique = uniqueObjects
.firstOrNull { it.placeholderText == "Creates a [] improvement on a specific tile" } .firstOrNull { it.placeholderText == "Creates a [] improvement on a specific tile" }

View File

@ -192,11 +192,14 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText {
return textList return textList
} }
fun getMapUnit(ruleset: Ruleset): MapUnit { fun getMapUnit(civInfo: CivilizationInfo): MapUnit {
val unit = MapUnit() val unit = MapUnit()
unit.name = name unit.name = name
unit.civInfo = civInfo
unit.setTransients(ruleset) // must be after setting name because it sets the baseUnit according to the name // must be after setting name & civInfo because it sets the baseUnit according to the name
// and the civInfo is required for using `hasUnique` when determining its movement options
unit.setTransients(civInfo.gameInfo.ruleSet)
return unit return unit
} }
@ -326,15 +329,16 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText {
) return "Disabled by setting" ) return "Disabled by setting"
for (unique in uniqueObjects.filter { it.placeholderText == "Unlocked with []" }) for (unique in uniqueObjects.filter { it.placeholderText == "Unlocked with []" })
if (civInfo.tech.researchedTechnologies.none { it.era() == unique.params[0] || it.name == unique.params[0] } // ToDo: Clean this up when eras.json is required
&& !civInfo.policies.isAdopted(unique.params[0])) if ((civInfo.gameInfo.ruleSet.getEraNumber(unique.params[0]) != -1 && civInfo.getEraNumber() >= civInfo.gameInfo.ruleSet.getEraNumber(unique.params[0]))
return unique.text || civInfo.hasTechOrPolicy(unique.params[0])
) return unique.text
for (unique in uniqueObjects.filter { it.placeholderText == "Requires []" }) { for (unique in uniqueObjects.filter { it.placeholderText == "Requires []" }) {
val filter = unique.params[0] val filter = unique.params[0]
if (!ignoreTechPolicyRequirements && filter in civInfo.gameInfo.ruleSet.buildings) { if (!ignoreTechPolicyRequirements && filter in civInfo.gameInfo.ruleSet.buildings) {
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 (!ignoreTechPolicyRequirements && !civInfo.policies.adoptedPolicies.contains(filter)) return "Policy is not adopted" } else if (!ignoreTechPolicyRequirements && !civInfo.policies.isAdopted(filter)) return "Policy is not adopted"
} }
for ((resource, amount) in getResourceRequirements()) for ((resource, amount) in getResourceRequirements())

View File

@ -464,7 +464,7 @@ object UnitActions {
"Can undertake a trade mission with City-State, giving a large sum of gold and [] Influence" -> { "Can undertake a trade mission with City-State, giving a large sum of gold and [] Influence" -> {
val canConductTradeMission = tile.owningCity?.civInfo?.isCityState() == true val canConductTradeMission = tile.owningCity?.civInfo?.isCityState() == true
&& tile.owningCity?.civInfo?.isAtWarWith(unit.civInfo) == false && tile.owningCity?.civInfo?.isAtWarWith(unit.civInfo) == false
val influenceEarned = unique.params[0].toInt() val influenceEarned = unique.params[0].toFloat()
actionList += UnitAction(UnitActionType.ConductTradeMission, actionList += UnitAction(UnitActionType.ConductTradeMission,
action = { action = {
// http://civilization.wikia.com/wiki/Great_Merchant_(Civ5) // http://civilization.wikia.com/wiki/Great_Merchant_(Civ5)
@ -472,7 +472,7 @@ object UnitActions {
if (unit.civInfo.hasUnique("Double gold from Great Merchant trade missions")) if (unit.civInfo.hasUnique("Double gold from Great Merchant trade missions"))
goldEarned *= 2 goldEarned *= 2
unit.civInfo.addGold(goldEarned) unit.civInfo.addGold(goldEarned)
tile.owningCity!!.civInfo.getDiplomacyManager(unit.civInfo).influence += influenceEarned tile.owningCity!!.civInfo.getDiplomacyManager(unit.civInfo).addInfluence(influenceEarned)
unit.civInfo.addNotification("Your trade mission to [${tile.owningCity!!.civInfo}] has earned you [${goldEarned}] gold and [$influenceEarned] influence!", unit.civInfo.addNotification("Your trade mission to [${tile.owningCity!!.civInfo}] has earned you [${goldEarned}] gold and [$influenceEarned] influence!",
tile.owningCity!!.civInfo.civName, NotificationIcon.Gold, NotificationIcon.Culture) tile.owningCity!!.civInfo.civName, NotificationIcon.Gold, NotificationIcon.Culture)
addStatsPerGreatPersonUsage(unit) addStatsPerGreatPersonUsage(unit)
@ -553,7 +553,7 @@ object UnitActions {
title = "Spread [${unit.religion!!}]", title = "Spread [${unit.religion!!}]",
action = { action = {
val followersOfOtherReligions = city.religion.getFollowersOfOtherReligionsThan(unit.religion!!) val followersOfOtherReligions = city.religion.getFollowersOfOtherReligionsThan(unit.religion!!)
for (unique in unit.civInfo.getMatchingUniques("When spreading religion to a city, gain [] times the amount of followers of other religions as []")) { for (unique in unit.getMatchingUniques("When spreading religion to a city, gain [] times the amount of followers of other religions as []")) {
unit.civInfo.addStat(Stat.valueOf(unique.params[1]), followersOfOtherReligions * unique.params[0].toInt()) unit.civInfo.addStat(Stat.valueOf(unique.params[1]), followersOfOtherReligions * unique.params[0].toInt())
} }
city.religion.addPressure(unit.religion!!, unit.getPressureAddedFromSpread()) city.religion.addPressure(unit.religion!!, unit.getPressureAddedFromSpread())
@ -751,14 +751,12 @@ object UnitActions {
// We need to be in another civs territory. // We need to be in another civs territory.
if (recipient == null || recipient.isCurrentPlayer()) return null if (recipient == null || recipient.isCurrentPlayer()) return null
// City States only take military units (and GPs for certain civs) // City States only take military units (and units specifically allowed by uniques)
if (recipient.isCityState()) { if (recipient.isCityState()) {
if (unit.isGreatPerson()) { if (!unit.matchesFilter("Military")
// Do we have a unique ability to gift GPs? && unit.getMatchingUniques("Gain [] Influence with a [] gift to a City-State")
if (unit.civInfo.getMatchingUniques("Gain [] Influence with a [] gift to a City-State").none { .none { unit.matchesFilter(it.params[1]) }
it.params[1] == "Great Person" } ) return null ) return null
}
else if (!unit.baseUnit().matchesFilter("Military")) return null
} }
// If gifting to major civ they need to be friendly // If gifting to major civ they need to be friendly
else if (!tile.isFriendlyTerritory(unit.civInfo)) return null else if (!tile.isFriendlyTerritory(unit.civInfo)) return null
@ -769,15 +767,15 @@ object UnitActions {
val giftAction = { val giftAction = {
if (recipient.isCityState()) { if (recipient.isCityState()) {
for (unique in unit.civInfo.getMatchingUniques("Gain [] Influence with a [] gift to a City-State")) { for (unique in unit.civInfo.getMatchingUniques("Gain [] Influence with a [] gift to a City-State")) {
if((unit.isGreatPerson() && unique.params[1] == "Great Person") if ((unit.isGreatPerson() && unique.params[1] == "Great Person")
|| unit.matchesFilter(unique.params[1])) { || unit.matchesFilter(unique.params[1])
recipient.getDiplomacyManager(unit.civInfo).influence += unique.params[0].toInt() - 5 ) {
recipient.getDiplomacyManager(unit.civInfo).addInfluence(unique.params[0].toFloat() - 5f)
break
} }
} }
recipient.getDiplomacyManager(unit.civInfo).influence += 5 recipient.getDiplomacyManager(unit.civInfo).addInfluence(5f)
recipient.updateAllyCivForCityState()
} }
else recipient.getDiplomacyManager(unit.civInfo).addModifier(DiplomaticModifiers.GaveUsUnits, 5f) else recipient.getDiplomacyManager(unit.civInfo).addModifier(DiplomaticModifiers.GaveUsUnits, 5f)