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,
"isWonder": true,
"greatPersonPoints": {"Great Artist": 1},
"uniques": ["Unhappiness from population decreased by [10]%"],
"uniques": ["[-10]% unhappiness from population [in all cities]"],
"requiredTech": "Banking",
"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,
"isWonder": true,
"greatPersonPoints": {"Great Engineer": 2},
"uniques": ["[+1 Production] from every specialist"],
"uniques": ["[+1 Production] from every specialist [in all cities]"],
"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"
},
@ -1085,7 +1085,7 @@
"cost": 300,
"maintenance": 1,
"requiredTech": "Telecommunications",
"uniques": ["Population loss from nuclear attacks -[75]%"]
"uniques": ["Population loss from nuclear attacks [-75]% [in this city]"]
},
{
"name": "SS Cockpit",

View File

@ -314,7 +314,7 @@
"outerColor": [16,126,5],
"innerColor": [255,153,51],
"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",
"Kolhapur","Prayaga","Ayodhya","Indraprastha","Mathura","Ujjain","Gulbarga","Jaunpur","Rajagriha","Sravasti","Tiruchirapalli","Thanjavur",
"Bodhgaya","Kushinagar","Amaravati","Gaur","Gwalior","Jaipur","Karachi"]
@ -392,7 +392,7 @@
"outerColor": [26,32,96],
"innerColor": [255,0,0],
"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",
"Changwon","Andong","Gongju","Haeju","Cheongju","Mokpo","Dongducheon","Geoje","Suncheon","Jinju","Sangju",
"Rason","Gyeongju","Chungju","Sacheon","Gimje","Anju"]

View File

@ -75,7 +75,7 @@
},
{
"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"],
"row": 2,
"column": 5
@ -288,7 +288,7 @@
"policies": [
{
"name": "Secularism",
"uniques": ["[+2 Science] from every specialist"],
"uniques": ["[+2 Science] from every specialist [in all cities]"],
"row": 1,
"column": 2
},
@ -347,7 +347,7 @@
},
{
"name": "Civil Society",
"uniques": ["-[50]% food consumption by specialists"],
"uniques": ["-[50]% food consumption by specialists [in all cities]"],
"row": 1,
"column": 5
},
@ -360,7 +360,7 @@
},
{
"name": "Democracy",
"uniques": ["Specialists only produce [50]% of normal unhappiness"],
"uniques": ["[-50]% unhappiness from specialists [in all cities]"],
"requires": ["Civil Society"],
"row": 2,
"column": 5

View File

@ -292,22 +292,20 @@ class GameInfo {
for (baseUnit in ruleSet.units.values)
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)
if (currentPlayer == "") currentPlayer = civilizations.first { it.isPlayerCivilization() }.civName
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]!!
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.updateSightAndResources()

View File

@ -484,7 +484,7 @@ object NextTurnAutomation {
val unitsInBorder = otherCiv.getCivUnits().count { !it.isCivilian() && it.getTile().getOwner() == civInfo }
if (unitsInBorder > 0 && diplomacy.relationshipLevel() < RelationshipLevel.Friend) {
diplomacy.influence -= 10f
diplomacy.addInfluence(-10f)
if (!diplomacy.hasFlag(DiplomacyFlags.BorderConflict)) {
otherCiv.popupAlerts.add(PopupAlert(AlertType.BorderConflict, civInfo.civName))
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.diplomacy.DiplomaticModifiers
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
import com.unciv.logic.map.MapUnit
import com.unciv.logic.map.RoadStatus
import com.unciv.logic.map.TileInfo
import com.unciv.models.AttackableTile
@ -13,6 +14,7 @@ import com.unciv.models.UnitActionType
import com.unciv.models.ruleset.Unique
import com.unciv.models.stats.Stat
import com.unciv.models.stats.Stats
import com.unciv.ui.utils.toPercent
import java.util.*
import kotlin.math.max
import kotlin.math.min
@ -121,10 +123,11 @@ object Battle {
val bonusUniques = ArrayList<Unique>()
bonusUniques.addAll(civUnit.getCivInfo().getMatchingUniques(bonusUniquePlaceholderText))
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"
@ -386,9 +389,7 @@ object Battle {
if (thisCombatant.getCivInfo().isMajorCiv()) {
var greatGeneralPointsModifier = 1f
val unitUniques = thisCombatant.unit.getMatchingUniques("[] is earned []% faster")
val civUniques = thisCombatant.getCivInfo().getMatchingUniques("[] is earned []% faster")
for (unique in unitUniques + civUniques) {
for (unique in thisCombatant.getMatchingUniques("[] is earned []% faster")) {
val unitName = unique.params[0]
val unit = thisCombatant.getCivInfo().gameInfo.ruleSet.units[unitName]
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()
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 (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(
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()) {
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
@ -578,8 +583,15 @@ object Battle {
if (city != null && tile.position == city.location) {
var populationLoss = city.population.population * (0.3 + Random().nextFloat() * 0.4)
var populationLossReduced = false
for (unique in city.civInfo.getMatchingUniques("Population loss from nuclear attacks -[]%")) {
populationLoss *= 1 - unique.params[0].toFloat() / 100f
// Deprecated since 3.16.11
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
}
if (city.population.population < 5 && !populationLossReduced) {
@ -647,8 +659,15 @@ object Battle {
} else {
var populationLoss = city.population.population * (0.6 + Random().nextFloat() * 0.2)
var populationLossReduced = false
for (unique in city.civInfo.getMatchingUniques("Population loss from nuclear attacks -[]%")) {
populationLoss *= 1 - unique.params[0].toFloat() / 100f
// Deprecated since 3.15.11
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
}
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.TileInfo
import com.unciv.models.UncivSound
import com.unciv.models.ruleset.Unique
import com.unciv.models.ruleset.unit.UnitType
class MapUnitCombatant(val unit: MapUnit) : ICombatant {
@ -43,5 +44,6 @@ class MapUnitCombatant(val unit: MapUnit) : ICombatant {
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()
for (unique in civInfo.getMatchingUniques("Gain a free [] []")) {
for (unique in getMatchingUniques("Gain a free [] []")) {
val freeBuildingName = unique.params[0]
if (matchesFilter(unique.params[1])) {
if (!cityConstructions.isBuilt(freeBuildingName))
@ -370,15 +370,15 @@ class CityInfo {
}
// Sweden UP
for (otherciv in civInfo.getKnownCivs()) {
if (!civInfo.getDiplomacyManager(otherciv)
for (otherCiv in civInfo.getKnownCivs()) {
if (!civInfo.getDiplomacyManager(otherCiv)
.hasFlag(DiplomacyFlags.DeclarationOfFriendship)
) continue
for (ourunique in civInfo.getMatchingUniques("When declaring friendship, both parties gain a []% boost to great person generation"))
allGppPercentageBonus += ourunique.params[0].toInt()
for (theirunique in otherciv.getMatchingUniques("When declaring friendship, both parties gain a []% boost to great person generation"))
allGppPercentageBonus += theirunique.params[0].toInt()
for (ourUnique in civInfo.getMatchingUniques("When declaring friendship, both parties gain a []% boost to great person generation"))
allGppPercentageBonus += ourUnique.params[0].toInt()
for (theirUnique in otherCiv.getMatchingUniques("When declaring friendship, both parties gain a []% boost to great person generation"))
allGppPercentageBonus += theirUnique.params[0].toInt()
}
for (unitName in gppCounter.keys)

View File

@ -181,7 +181,7 @@ class CityInfoConquestFunctions(val city: CityInfo){
.addModifier(DiplomaticModifiers.CapturedOurCities, respectForLiberatingOurCity)
} else {
//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)) {
val tradeLogic = TradeLogic(foundingCiv, conqueringCiv)
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 []"))
if (pressuredCity.matchesFilter(unique.params[1])
&& (
cityInfo.civInfo.tech.isResearched(unique.params[2])
|| cityInfo.civInfo.policies.isAdopted(unique.params[2])
)
&& cityInfo.civInfo.hasTechOrPolicy(unique.params[2])
) pressure *= 1f + unique.params[0].toFloat() / 100f
return pressure.toInt()

View File

@ -55,7 +55,7 @@ class CityStats(val cityInfo: CityInfo) {
if (!cityInfo.isCapital() && cityInfo.isConnectedToCapital()) {
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)
for (unique in civInfo.getMatchingUniques("[] from each Trade Route"))
for (unique in cityInfo.getMatchingUniques("[] from each Trade Route"))
stats.add(unique.stats)
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 {
val stats = Stats()
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
return getStatsFromUniques(cityInfo.civInfo.nation.uniqueObjects.asSequence())
}
private fun getStatsFromCityStates(): Stats {
@ -140,8 +127,11 @@ class CityStats(val cityInfo: CityInfo) {
return stats
}
for (bonus in if (otherCiv.getDiplomacyManager(cityInfo.civInfo).relationshipLevel() == RelationshipLevel.Friend)
eraInfo.friendBonus[otherCiv.cityStateType.name]!! else eraInfo.allyBonus[otherCiv.cityStateType.name]!!) {
for (bonus in
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])) {
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]
?: return Stats()
val stats = specialist.clone()
for (unique in cityInfo.civInfo.getMatchingUniques("[] from every specialist"))
stats.add(unique.stats)
// Deprecated since 3.16.11
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 []"))
if (unique.params[1] == specialistName)
stats.add(unique.stats)
@ -240,6 +235,14 @@ class CityStats(val cityInfo: CityInfo) {
// "[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())
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
@ -371,8 +374,14 @@ class CityStats(val cityInfo: CityInfo) {
var unhappinessFromCitizens = cityInfo.population.population.toFloat()
var unhappinessFromSpecialists = cityInfo.population.getNumberOfSpecialists().toFloat()
for (unique in civInfo.getMatchingUniques("Specialists only produce []% of normal unhappiness")) {
unhappinessFromSpecialists *= (1f - unique.params[0].toFloat() / 100f)
// Deprecated since 3.16.11
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
@ -382,13 +391,19 @@ class CityStats(val cityInfo: CityInfo) {
else if (hasExtraAnnexUnhappiness())
unhappinessFromCitizens *= 2f
for (unique in civInfo.getMatchingUniques("Unhappiness from population decreased by []%"))
unhappinessFromCitizens *= (1 - unique.params[0].toFloat() / 100)
for (unique in civInfo.getMatchingUniques("Unhappiness from population decreased by []% []"))
if (cityInfo.matchesFilter(unique.params[1]))
// Deprecated since 3.16.11
for (unique in civInfo.getMatchingUniques("Unhappiness from population decreased by []%"))
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
val happinessFromPolicies = getStatsFromUniques(civInfo.policies.policyUniques.getAllUniques()).happiness
@ -563,8 +578,14 @@ class CityStats(val cityInfo: CityInfo) {
foodEaten = cityInfo.population.population.toFloat() * 2
var foodEatenBySpecialists = 2f * cityInfo.population.getNumberOfSpecialists()
for (unique in cityInfo.civInfo.getMatchingUniques("-[]% food consumption by specialists"))
foodEatenBySpecialists *= 1f - unique.params[0].toFloat() / 100f
// Deprecated since 3.16.11
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
}

View File

@ -1,6 +1,7 @@
package com.unciv.logic.civilization
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.ruleset.BeliefType
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
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.
for (city in civInfo.cities) {
for (tile in city.getTiles()) {
if (tile.isCityCenter()) continue
if (tile.roadStatus == RoadStatus.None) continue // Cheap checks before pricy checks
if (ignoredTileTypes.any { tile.matchesFilter(it, civInfo) }) continue
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 }
statMap["Luxury resources"] = civInfo.getCivResources().map { it.resource }
.count { it.resourceType === ResourceType.Luxury } * happinessPerUniqueLuxury
statMap["Luxury resources"] = civInfo.getCivResources()
.map { it.resource }
.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()
.sumBy { it.params[0].toInt() } / 100f
val luxuriesProvidedByCityStates =
civInfo.getKnownCivs().asSequence()
.filter { it.isCityState() && it.getAllyCiv() == civInfo.civName }
.flatMap { it.getCivResources().map { res -> res.resource } }
.distinct().count { it.resourceType === ResourceType.Luxury }
val luxuriesProvidedByCityStates = civInfo.getKnownCivs().asSequence()
.filter { it.isCityState() && it.getAllyCiv() == civInfo.civName }
.flatMap { it.getCivResources().map { res -> res.resource } }
.distinct()
.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
.filter { it.amount < 0 && it.resource.resourceType == ResourceType.Luxury
@ -256,7 +259,7 @@ class CivInfoStats(val civInfo: CivilizationInfo) {
for (otherCiv in civInfo.getKnownCivs()) {
if (otherCiv.isCityState() && otherCiv.getDiplomacyManager(civInfo).relationshipLevel() >= RelationshipLevel.Friend) {
val eraInfo = civInfo.getEraObject()
val relevantbonuses =
val relevantBonuses =
when {
eraInfo == null -> null
otherCiv.getDiplomacyManager(civInfo).relationshipLevel() == RelationshipLevel.Friend ->
@ -265,8 +268,8 @@ class CivInfoStats(val civInfo: CivilizationInfo) {
eraInfo.allyBonus[otherCiv.cityStateType.name]
}
if (relevantbonuses != null) {
for (bonus in relevantbonuses) {
if (relevantBonuses != null) {
for (bonus in relevantBonuses) {
if (bonus.getPlaceholderText() == "Provides [] Happiness") {
if (statMap.containsKey("City-States"))
statMap["City-States"] = statMap["City-States"]!! + bonus.getPlaceholderParameters()[0].toFloat()

View File

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

View File

@ -343,7 +343,7 @@ class QuestManager {
val rewardInfluence = civInfo.gameInfo.ruleSet.quests[assignedQuest.questName]!!.influece
val assignee = civInfo.gameInfo.getCivilization(assignedQuest.assignee)
civInfo.getDiplomacyManager(assignedQuest.assignee).influence += rewardInfluence
civInfo.getDiplomacyManager(assignedQuest.assignee).addInfluence(rewardInfluence)
if (rewardInfluence > 0)
assignee.addNotification(
"[${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.BeliefType
import com.unciv.ui.pickerscreens.BeliefContainer
import com.unciv.ui.utils.toPercent
import kotlin.random.Random
class ReligionManager {
@ -110,7 +111,7 @@ class ReligionManager {
civInfo.gameInfo.gameParameters.gameSpeed.modifier
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()
}

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.
* Won't go below [MINIMUM_INFLUENCE]. Note this declaration leads to Major Civs getting MINIMUM_INFLUENCE serialized, but that is ignored. */
var influence = 0f
set(value) {
field = max(value, MINIMUM_INFLUENCE)
}
private set
get() = if (civInfo.isAtWarWith(otherCiv())) MINIMUM_INFLUENCE else field
/** 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.
fun getCityStateInfluenceRestingPoint(): Float {
var restingPoint = 0f
@ -393,6 +400,7 @@ class DiplomacyManager() {
val increment = getCityStateInfluenceRecovery()
influence = min(restingPoint, influence + increment)
}
civInfo.updateAllyCivForCityState()
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
@ -658,7 +666,7 @@ class DiplomacyManager() {
thirdCiv.getDiplomacyManager(otherCiv).makePeace()
// 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))
thirdCiv.getDiplomacyManager(civInfo).influence -= 10
thirdCiv.getDiplomacyManager(civInfo).addInfluence(-10f)
}
}

View File

@ -168,9 +168,10 @@ class MapUnit {
fun getTile(): TileInfo = currentTile
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() }
for (unique in civInfo.getMatchingUniques("+[] Movement for all [] units"))
@ -182,6 +183,13 @@ class MapUnit {
)
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
}
@ -194,10 +202,11 @@ class MapUnit {
fun getUniques(): ArrayList<Unique> = tempUniques
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 {
return getUniques().any { it.placeholderText == unique }
return getUniques().any { it.placeholderText == unique } || civInfo.hasUnique(unique)
}
fun updateUniques() {
@ -341,7 +350,6 @@ class MapUnit {
return range
}
fun isEmbarked(): Boolean {
if (!baseUnit.isLandUnit()) return false
return currentTile.isWater
@ -357,13 +365,6 @@ class MapUnit {
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 {
var unit = baseUnit()

View File

@ -392,7 +392,7 @@ class TileMap {
unitName: String,
civInfo: CivilizationInfo
): MapUnit? {
val unit = gameInfo.ruleSet.units[unitName]!!.getMapUnit(gameInfo.ruleSet)
val unit = gameInfo.ruleSet.units[unitName]!!.getMapUnit(civInfo)
fun getPassableNeighbours(tileInfo: TileInfo): Set<TileInfo> =
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 civInfo = cityInfo?.civInfo ?: return stats // initial stats
val baseBuildingName = getBaseBuilding(civInfo.gameInfo.ruleSet).name
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())
}
@ -521,7 +519,7 @@ class Building : NamedStats(), INonPerpetualConstruction, ICivilopediaText {
val filter = unique.params[0]
if (filter in civInfo.gameInfo.ruleSet.buildings) {
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" -> {
@ -673,10 +671,6 @@ class Building : NamedStats(), INonPerpetualConstruction, ICivilopediaText {
return false
}
fun getBaseBuilding(ruleset: Ruleset): Building {
return if (replaces == null) this else ruleset.buildings[replaces!!]!!
}
fun getImprovement(ruleset: Ruleset): TileImprovement? {
val improvementUnique = uniqueObjects
.firstOrNull { it.placeholderText == "Creates a [] improvement on a specific tile" }

View File

@ -192,11 +192,14 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText {
return textList
}
fun getMapUnit(ruleset: Ruleset): MapUnit {
fun getMapUnit(civInfo: CivilizationInfo): MapUnit {
val unit = MapUnit()
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
}
@ -326,15 +329,16 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText {
) return "Disabled by setting"
for (unique in uniqueObjects.filter { it.placeholderText == "Unlocked with []" })
if (civInfo.tech.researchedTechnologies.none { it.era() == unique.params[0] || it.name == unique.params[0] }
&& !civInfo.policies.isAdopted(unique.params[0]))
return unique.text
// ToDo: Clean this up when eras.json is required
if ((civInfo.gameInfo.ruleSet.getEraNumber(unique.params[0]) != -1 && civInfo.getEraNumber() >= civInfo.gameInfo.ruleSet.getEraNumber(unique.params[0]))
|| civInfo.hasTechOrPolicy(unique.params[0])
) return unique.text
for (unique in uniqueObjects.filter { it.placeholderText == "Requires []" }) {
val filter = unique.params[0]
if (!ignoreTechPolicyRequirements && filter in civInfo.gameInfo.ruleSet.buildings) {
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())

View File

@ -464,7 +464,7 @@ object UnitActions {
"Can undertake a trade mission with City-State, giving a large sum of gold and [] Influence" -> {
val canConductTradeMission = tile.owningCity?.civInfo?.isCityState() == true
&& tile.owningCity?.civInfo?.isAtWarWith(unit.civInfo) == false
val influenceEarned = unique.params[0].toInt()
val influenceEarned = unique.params[0].toFloat()
actionList += UnitAction(UnitActionType.ConductTradeMission,
action = {
// 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"))
goldEarned *= 2
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!",
tile.owningCity!!.civInfo.civName, NotificationIcon.Gold, NotificationIcon.Culture)
addStatsPerGreatPersonUsage(unit)
@ -553,7 +553,7 @@ object UnitActions {
title = "Spread [${unit.religion!!}]",
action = {
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())
}
city.religion.addPressure(unit.religion!!, unit.getPressureAddedFromSpread())
@ -751,14 +751,12 @@ object UnitActions {
// We need to be in another civs territory.
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 (unit.isGreatPerson()) {
// Do we have a unique ability to gift GPs?
if (unit.civInfo.getMatchingUniques("Gain [] Influence with a [] gift to a City-State").none {
it.params[1] == "Great Person" } ) return null
}
else if (!unit.baseUnit().matchesFilter("Military")) return null
if (!unit.matchesFilter("Military")
&& unit.getMatchingUniques("Gain [] Influence with a [] gift to a City-State")
.none { unit.matchesFilter(it.params[1]) }
) return null
}
// If gifting to major civ they need to be friendly
else if (!tile.isFriendlyTerritory(unit.civInfo)) return null
@ -769,15 +767,15 @@ object UnitActions {
val giftAction = {
if (recipient.isCityState()) {
for (unique in unit.civInfo.getMatchingUniques("Gain [] Influence with a [] gift to a City-State")) {
if((unit.isGreatPerson() && unique.params[1] == "Great Person")
|| unit.matchesFilter(unique.params[1])) {
recipient.getDiplomacyManager(unit.civInfo).influence += unique.params[0].toInt() - 5
if ((unit.isGreatPerson() && unique.params[1] == "Great Person")
|| unit.matchesFilter(unique.params[1])
) {
recipient.getDiplomacyManager(unit.civInfo).addInfluence(unique.params[0].toFloat() - 5f)
break
}
}
recipient.getDiplomacyManager(unit.civInfo).influence += 5
recipient.updateAllyCivForCityState()
recipient.getDiplomacyManager(unit.civInfo).addInfluence(5f)
}
else recipient.getDiplomacyManager(unit.civInfo).addModifier(DiplomaticModifiers.GaveUsUnits, 5f)