mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-27 05:46:43 -04:00
Demanding tribute from city states (#4976)
* tribute willingness calculations * implement demanding gold and workers * Revisions * unit power calculation * show modifiers in the diplo screen * template.properties * G&K modifiers * promotions start at 0 * notiifications, AI * conflict * conflict? * template and translation, failing test? * another missing string * missing space * afraid relationship status * missing space 2 * Slight optimization * optimization pt 2 * reviews
This commit is contained in:
parent
f3a53516da
commit
a01a6270fc
@ -104,6 +104,7 @@ We promised not to settle near them ([count] turns remaining) = Vi lovade att in
|
|||||||
They promised not to settle near us ([count] turns remaining) = De lovade att inte bygga städer nära oss ([count] drag kvar)
|
They promised not to settle near us ([count] turns remaining) = De lovade att inte bygga städer nära oss ([count] drag kvar)
|
||||||
|
|
||||||
Unforgivable = Oförlåtlig
|
Unforgivable = Oförlåtlig
|
||||||
|
Afraid = Rädd
|
||||||
Enemy = Fiende
|
Enemy = Fiende
|
||||||
Competitor = Konkurrent
|
Competitor = Konkurrent
|
||||||
Neutral = Neutral
|
Neutral = Neutral
|
||||||
@ -173,6 +174,7 @@ Personality = Personlighet
|
|||||||
Influence = Inflytande
|
Influence = Inflytande
|
||||||
Reach 30 for friendship. = Nå 30 för vänskap.
|
Reach 30 for friendship. = Nå 30 för vänskap.
|
||||||
Reach highest influence above 60 for alliance. = Nå högsta inflytande över 60 för allians.
|
Reach highest influence above 60 for alliance. = Nå högsta inflytande över 60 för allians.
|
||||||
|
|
||||||
# Requires translation!
|
# Requires translation!
|
||||||
When Friends: =
|
When Friends: =
|
||||||
# Requires translation!
|
# Requires translation!
|
||||||
@ -180,6 +182,27 @@ When Allies: =
|
|||||||
# Requires translation!
|
# Requires translation!
|
||||||
The unique luxury is one of: =
|
The unique luxury is one of: =
|
||||||
|
|
||||||
|
Demand Tribute = Kräv Brandskatt
|
||||||
|
Tribute Willingness = Brandskattsvillighet
|
||||||
|
>0 to take gold, >30 and size 4 city for worker = >0 för att ta guld, >30 och stad av storlek 4 för arbetare
|
||||||
|
Major Civ = Stor Civilisation
|
||||||
|
No Cities = Inga Städer
|
||||||
|
Base value = Grundvärde
|
||||||
|
Has Ally = Har Allierad
|
||||||
|
Has Protector = Har Beskyddare
|
||||||
|
Demanding a Worker = Kräver Arbetare
|
||||||
|
Demanding a Worker from small City-State = Kräver Arbetare från liten Stadsstat
|
||||||
|
Very recently paid tribute = Alldeles nyss brandskattad
|
||||||
|
Recently paid tribute = Nyligen brandskattad
|
||||||
|
Influence below -30 = Inflytande under -30
|
||||||
|
Military Rank = Militär Rangordning
|
||||||
|
Military near City-State = Militär nära Stadsstaten
|
||||||
|
Sum: = Summa:
|
||||||
|
Take [amount] gold (-15 Influence) = Ta [amount] guld (-15 Inflytande)
|
||||||
|
Take worker (-50 Influence) = Ta arbetare (-50 Inflytande)
|
||||||
|
[civName] is afraid of your military power! = [civName] är rädd för din militära makt!
|
||||||
|
|
||||||
|
|
||||||
# Trades
|
# Trades
|
||||||
|
|
||||||
Trade = Handelsavtal
|
Trade = Handelsavtal
|
||||||
|
@ -104,6 +104,7 @@ We promised not to settle near them ([count] turns remaining) =
|
|||||||
They promised not to settle near us ([count] turns remaining) =
|
They promised not to settle near us ([count] turns remaining) =
|
||||||
|
|
||||||
Unforgivable =
|
Unforgivable =
|
||||||
|
Afraid =
|
||||||
Enemy =
|
Enemy =
|
||||||
Competitor =
|
Competitor =
|
||||||
Neutral =
|
Neutral =
|
||||||
@ -178,6 +179,27 @@ When Friends: =
|
|||||||
When Allies: =
|
When Allies: =
|
||||||
The unique luxury is one of: =
|
The unique luxury is one of: =
|
||||||
|
|
||||||
|
Demand Tribute =
|
||||||
|
Tribute Willingness =
|
||||||
|
>0 to take gold, >30 and size 4 city for worker =
|
||||||
|
Major Civ =
|
||||||
|
No Cities =
|
||||||
|
Base value =
|
||||||
|
Has Ally =
|
||||||
|
Has Protector =
|
||||||
|
Demanding a Worker =
|
||||||
|
Demanding a Worker from small City-State =
|
||||||
|
Very recently paid tribute =
|
||||||
|
Recently paid tribute =
|
||||||
|
Influence below -30 =
|
||||||
|
Military Rank =
|
||||||
|
Military near City-State =
|
||||||
|
Sum: =
|
||||||
|
Take [amount] gold (-15 Influence) =
|
||||||
|
Take worker (-50 Influence) =
|
||||||
|
[civName] is afraid of your military power! =
|
||||||
|
|
||||||
|
|
||||||
# Trades
|
# Trades
|
||||||
|
|
||||||
Trade =
|
Trade =
|
||||||
|
@ -16,6 +16,7 @@ import com.unciv.logic.trade.*
|
|||||||
import com.unciv.models.ruleset.ModOptionsConstants
|
import com.unciv.models.ruleset.ModOptionsConstants
|
||||||
import com.unciv.models.ruleset.VictoryType
|
import com.unciv.models.ruleset.VictoryType
|
||||||
import com.unciv.models.ruleset.tech.Technology
|
import com.unciv.models.ruleset.tech.Technology
|
||||||
|
import com.unciv.models.ruleset.tile.ResourceType
|
||||||
import com.unciv.models.ruleset.unit.BaseUnit
|
import com.unciv.models.ruleset.unit.BaseUnit
|
||||||
import com.unciv.models.stats.Stat
|
import com.unciv.models.stats.Stat
|
||||||
import com.unciv.models.translations.tr
|
import com.unciv.models.translations.tr
|
||||||
@ -49,7 +50,10 @@ object NextTurnAutomation {
|
|||||||
chooseTechToResearch(civInfo)
|
chooseTechToResearch(civInfo)
|
||||||
automateCityBombardment(civInfo)
|
automateCityBombardment(civInfo)
|
||||||
useGold(civInfo)
|
useGold(civInfo)
|
||||||
protectCityStates(civInfo)
|
if (!civInfo.isCityState()) {
|
||||||
|
protectCityStates(civInfo)
|
||||||
|
bullyCityStates(civInfo)
|
||||||
|
}
|
||||||
automateUnits(civInfo) // this is the most expensive part
|
automateUnits(civInfo) // this is the most expensive part
|
||||||
reassignWorkedTiles(civInfo) // second most expensive
|
reassignWorkedTiles(civInfo) // second most expensive
|
||||||
trainSettler(civInfo)
|
trainSettler(civInfo)
|
||||||
@ -114,29 +118,19 @@ object NextTurnAutomation {
|
|||||||
|
|
||||||
/** allow AI to spend money to purchase city-state friendship, buildings & unit */
|
/** allow AI to spend money to purchase city-state friendship, buildings & unit */
|
||||||
private fun useGold(civInfo: CivilizationInfo) {
|
private fun useGold(civInfo: CivilizationInfo) {
|
||||||
if (civInfo.victoryType() == VictoryType.Cultural) {
|
|
||||||
for (cityState in civInfo.getKnownCivs()
|
if (!civInfo.isCityState()) {
|
||||||
.filter { it.isCityState() && it.cityStateType == CityStateType.Cultured }) {
|
val potentialAllies = civInfo.getKnownCivs().filter { it.isCityState() }
|
||||||
val diploManager = cityState.getDiplomacyManager(civInfo)
|
if (potentialAllies.isNotEmpty()) {
|
||||||
if (diploManager.influence < 40) { // we want to gain influence with them
|
val cityState =
|
||||||
|
potentialAllies.maxByOrNull { valueCityStateAlliance(civInfo, it) }!!
|
||||||
|
if (cityState.getAllyCiv() != civInfo.civName && valueCityStateAlliance(civInfo, cityState) > 0) {
|
||||||
tryGainInfluence(civInfo, cityState)
|
tryGainInfluence(civInfo, cityState)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (civInfo.getHappiness() < 5) {
|
|
||||||
for (cityState in civInfo.getKnownCivs()
|
|
||||||
.filter { it.isCityState() && it.cityStateType == CityStateType.Mercantile }) {
|
|
||||||
val diploManager = cityState.getDiplomacyManager(civInfo)
|
|
||||||
if (diploManager.influence < 40) { // we want to gain influence with them
|
|
||||||
tryGainInfluence(civInfo, cityState)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
for (city in civInfo.cities.sortedByDescending { it.population.population }) {
|
for (city in civInfo.cities.sortedByDescending { it.population.population }) {
|
||||||
val construction = city.cityConstructions.getCurrentConstruction()
|
val construction = city.cityConstructions.getCurrentConstruction()
|
||||||
if (construction is PerpetualConstruction) continue
|
if (construction is PerpetualConstruction) continue
|
||||||
@ -147,6 +141,47 @@ object NextTurnAutomation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun valueCityStateAlliance(civInfo: CivilizationInfo, cityState: CivilizationInfo): Int {
|
||||||
|
var value = 0
|
||||||
|
if (!cityState.isAlive() || cityState.cities.isEmpty())
|
||||||
|
return value
|
||||||
|
|
||||||
|
if (civInfo.victoryType() == VictoryType.Cultural && cityState.canGiveStat(Stat.Culture)) {
|
||||||
|
value += 10
|
||||||
|
}
|
||||||
|
else if (civInfo.victoryType() == VictoryType.Scientific && cityState.canGiveStat(Stat.Science)) {
|
||||||
|
// In case someone mods this in
|
||||||
|
value += 10
|
||||||
|
}
|
||||||
|
else if (civInfo.victoryType() == VictoryType.Domination) {
|
||||||
|
// Don't ally close city-states, conquer them instead
|
||||||
|
val distance = getMinDistanceBetweenCities(civInfo, cityState)
|
||||||
|
if (distance < 20)
|
||||||
|
value -= (20 - distance) / 4
|
||||||
|
}
|
||||||
|
if (civInfo.gold < 100) {
|
||||||
|
// Consider bullying for cash
|
||||||
|
value -= 5
|
||||||
|
}
|
||||||
|
if (civInfo.getHappiness() < 5 && cityState.canGiveStat(Stat.Happiness)) {
|
||||||
|
value += 10 - civInfo.getHappiness()
|
||||||
|
}
|
||||||
|
if (civInfo.getHappiness() > 5 && cityState.canGiveStat(Stat.Food)) {
|
||||||
|
value += 5
|
||||||
|
}
|
||||||
|
if (cityState.getAllyCiv() != null && cityState.getAllyCiv() != civInfo.civName) {
|
||||||
|
// easier not to compete if a third civ has this locked down
|
||||||
|
val thirdCivInfluence = cityState.getDiplomacyManager(cityState.getAllyCiv()!!).influence.toInt()
|
||||||
|
value -= (thirdCivInfluence - 60) / 10
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bonus for luxury resources we can get from them
|
||||||
|
value += cityState.detailedCivResources.count { it.resource.resourceType == ResourceType.Luxury
|
||||||
|
&& !(it.resource in civInfo.detailedCivResources) }
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
private fun protectCityStates(civInfo: CivilizationInfo) {
|
private fun protectCityStates(civInfo: CivilizationInfo) {
|
||||||
for (state in civInfo.getKnownCivs().filter{!it.isDefeated() && it.isCityState()}) {
|
for (state in civInfo.getKnownCivs().filter{!it.isDefeated() && it.isCityState()}) {
|
||||||
val diplomacyManager = state.getDiplomacyManager(civInfo.civName)
|
val diplomacyManager = state.getDiplomacyManager(civInfo.civName)
|
||||||
@ -161,6 +196,21 @@ object NextTurnAutomation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun bullyCityStates(civInfo: CivilizationInfo) {
|
||||||
|
for (state in civInfo.getKnownCivs().filter{!it.isDefeated() && it.isCityState()}) {
|
||||||
|
val diplomacyManager = state.getDiplomacyManager(civInfo.civName)
|
||||||
|
if(diplomacyManager.relationshipLevel() < RelationshipLevel.Friend
|
||||||
|
&& diplomacyManager.diplomaticStatus == DiplomaticStatus.Peace
|
||||||
|
&& valueCityStateAlliance(civInfo, state) <= 0
|
||||||
|
&& state.getTributeWillingness(civInfo) > 0) {
|
||||||
|
if (state.getTributeWillingness(civInfo, demandingWorker = true) > 0)
|
||||||
|
civInfo.demandWorker(state)
|
||||||
|
else
|
||||||
|
civInfo.demandGold(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun getFreeTechForCityStates(civInfo: CivilizationInfo) {
|
private fun getFreeTechForCityStates(civInfo: CivilizationInfo) {
|
||||||
// City-States automatically get all techs that at least half of the major civs know
|
// City-States automatically get all techs that at least half of the major civs know
|
||||||
val researchableTechs = civInfo.gameInfo.ruleSet.technologies.keys
|
val researchableTechs = civInfo.gameInfo.ruleSet.technologies.keys
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.unciv.logic.city
|
package com.unciv.logic.city
|
||||||
|
|
||||||
import com.badlogic.gdx.math.Vector2
|
import com.badlogic.gdx.math.Vector2
|
||||||
|
import com.unciv.logic.battle.CityCombatant
|
||||||
import com.unciv.logic.civilization.CivilizationInfo
|
import com.unciv.logic.civilization.CivilizationInfo
|
||||||
import com.unciv.logic.civilization.GreatPersonManager
|
import com.unciv.logic.civilization.GreatPersonManager
|
||||||
import com.unciv.logic.civilization.ReligionState
|
import com.unciv.logic.civilization.ReligionState
|
||||||
@ -22,6 +23,7 @@ import kotlin.collections.HashMap
|
|||||||
import kotlin.collections.HashSet
|
import kotlin.collections.HashSet
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
import kotlin.math.pow
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class CityInfo {
|
class CityInfo {
|
||||||
@ -673,6 +675,11 @@ class CityInfo {
|
|||||||
return !isOriginalCapital && !isHolyCity() && (!isCapital() || justCaptured)
|
return !isOriginalCapital && !isHolyCity() && (!isCapital() || justCaptured)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getForceEvaluation(): Int {
|
||||||
|
// Same as for units, so higher values count more
|
||||||
|
return CityCombatant(this).getCityStrength().toFloat().pow(1.5f).toInt()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun getNeighbouringCivs(): List<String> {
|
fun getNeighbouringCivs(): List<String> {
|
||||||
val tilesList: HashSet<TileInfo> = getTiles().toHashSet()
|
val tilesList: HashSet<TileInfo> = getTiles().toHashSet()
|
||||||
@ -688,7 +695,7 @@ class CityInfo {
|
|||||||
.distinct().toList()
|
.distinct().toList()
|
||||||
}
|
}
|
||||||
fun getImprovableTiles(): Sequence<TileInfo> = getTiles()
|
fun getImprovableTiles(): Sequence<TileInfo> = getTiles()
|
||||||
.filter {it.hasViewableResource(civInfo) && it.improvement == null}
|
.filter {it.hasViewableResource(civInfo) && it.improvement == null}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
}
|
}
|
@ -7,11 +7,13 @@ import com.unciv.logic.GameInfo
|
|||||||
import com.unciv.logic.UncivShowableException
|
import com.unciv.logic.UncivShowableException
|
||||||
import com.unciv.logic.automation.NextTurnAutomation
|
import com.unciv.logic.automation.NextTurnAutomation
|
||||||
import com.unciv.logic.automation.WorkerAutomation
|
import com.unciv.logic.automation.WorkerAutomation
|
||||||
|
import com.unciv.logic.battle.CityCombatant
|
||||||
import com.unciv.logic.city.CityInfo
|
import com.unciv.logic.city.CityInfo
|
||||||
import com.unciv.logic.civilization.RuinsManager.RuinsManager
|
import com.unciv.logic.civilization.RuinsManager.RuinsManager
|
||||||
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
|
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
|
||||||
import com.unciv.logic.civilization.diplomacy.DiplomacyManager
|
import com.unciv.logic.civilization.diplomacy.DiplomacyManager
|
||||||
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
|
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
|
||||||
|
import com.unciv.logic.civilization.diplomacy.RelationshipLevel
|
||||||
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.logic.map.UnitMovementAlgorithms
|
import com.unciv.logic.map.UnitMovementAlgorithms
|
||||||
@ -26,14 +28,20 @@ import com.unciv.models.ruleset.tile.TileResource
|
|||||||
import com.unciv.models.ruleset.unit.BaseUnit
|
import com.unciv.models.ruleset.unit.BaseUnit
|
||||||
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.models.translations.getPlaceholderParameters
|
||||||
|
import com.unciv.models.translations.getPlaceholderText
|
||||||
import com.unciv.models.translations.tr
|
import com.unciv.models.translations.tr
|
||||||
import com.unciv.ui.victoryscreen.RankingType
|
import com.unciv.ui.victoryscreen.RankingType
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
import kotlin.collections.HashMap
|
import kotlin.collections.HashMap
|
||||||
|
import kotlin.collections.LinkedHashMap
|
||||||
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
import kotlin.system.measureNanoTime
|
||||||
|
import kotlin.system.measureTimeMillis
|
||||||
|
|
||||||
class CivilizationInfo {
|
class CivilizationInfo {
|
||||||
|
|
||||||
@ -977,6 +985,156 @@ class CivilizationInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getTributeWillingness(demandingCiv: CivilizationInfo, demandingWorker: Boolean = false): Int {
|
||||||
|
return getTributeModifiers(demandingCiv, demandingWorker).values.sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTributeModifiers(demandingCiv: CivilizationInfo, demandingWorker: Boolean = false, requireWholeList: Boolean = false): HashMap<String, Int> {
|
||||||
|
val modifiers = LinkedHashMap<String, Int>() // Linked to preserve order when presenting the modifiers table
|
||||||
|
// Can't bully major civs or unsettled CS's
|
||||||
|
if (!isCityState()) {
|
||||||
|
modifiers["Major Civ"] = -999
|
||||||
|
return modifiers
|
||||||
|
}
|
||||||
|
if (cities.isEmpty()) {
|
||||||
|
modifiers["No Cities"] = -999
|
||||||
|
return modifiers
|
||||||
|
}
|
||||||
|
|
||||||
|
modifiers["Base value"] = -110
|
||||||
|
|
||||||
|
if (cityStatePersonality == CityStatePersonality.Hostile)
|
||||||
|
modifiers["Hostile"] = -10
|
||||||
|
if (cityStateType == CityStateType.Militaristic)
|
||||||
|
modifiers["Militaristic"] = -10
|
||||||
|
if (allyCivName != null && allyCivName != demandingCiv.civName)
|
||||||
|
modifiers["Has Ally"] = -10
|
||||||
|
if (getProtectorCivs().any { it != demandingCiv })
|
||||||
|
modifiers["Has Protector"] = -20
|
||||||
|
if (demandingWorker)
|
||||||
|
modifiers["Demanding a Worker"] = -30
|
||||||
|
if (demandingWorker && getCapital().population.population < 4)
|
||||||
|
modifiers["Demanding a Worker from small City-State"] = -300
|
||||||
|
val recentBullying = flagsCountdown[CivFlags.RecentlyBullied.name]
|
||||||
|
if (recentBullying != null && recentBullying > 10)
|
||||||
|
modifiers["Very recently paid tribute"] = -300
|
||||||
|
else if (recentBullying != null)
|
||||||
|
modifiers["Recently paid tribute"] = -40
|
||||||
|
if (getDiplomacyManager(demandingCiv).influence < -30)
|
||||||
|
modifiers["Influence below -30"] = -300
|
||||||
|
|
||||||
|
// Slight optimization, we don't do the expensive stuff if we have no chance of getting a positive result
|
||||||
|
if (!requireWholeList && modifiers.values.sum() <= -200)
|
||||||
|
return modifiers
|
||||||
|
|
||||||
|
val forceRank = gameInfo.getAliveMajorCivs().sortedByDescending { it.getStatForRanking(RankingType.Force) }.indexOf(demandingCiv)
|
||||||
|
modifiers["Military Rank"] = 100 - ((100 / gameInfo.gameParameters.players.size) * forceRank)
|
||||||
|
|
||||||
|
if (!requireWholeList && modifiers.values.sum() <= -100)
|
||||||
|
return modifiers
|
||||||
|
|
||||||
|
val bullyRange = max(5, gameInfo.tileMap.tileMatrix.size / 10) // Longer range for larger maps
|
||||||
|
val inRangeTiles = getCapital().getCenterTile().getTilesInDistanceRange(1..bullyRange)
|
||||||
|
val forceNearCity = inRangeTiles
|
||||||
|
.sumBy { if (it.militaryUnit?.civInfo == demandingCiv)
|
||||||
|
it.militaryUnit!!.getForceEvaluation()
|
||||||
|
else 0
|
||||||
|
}
|
||||||
|
val csForce = getCapital().getForceEvaluation() + inRangeTiles
|
||||||
|
.sumBy { if (it.militaryUnit?.civInfo == this)
|
||||||
|
it.militaryUnit!!.getForceEvaluation()
|
||||||
|
else 0
|
||||||
|
}
|
||||||
|
val forceRatio = forceNearCity.toFloat() / csForce.toFloat()
|
||||||
|
|
||||||
|
modifiers["Military near City-State"] = when {
|
||||||
|
forceRatio > 3f -> 100
|
||||||
|
forceRatio > 2f -> 80
|
||||||
|
forceRatio > 1.5f -> 60
|
||||||
|
forceRatio > 1f -> 40
|
||||||
|
forceRatio > 0.5f -> 20
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return modifiers
|
||||||
|
}
|
||||||
|
|
||||||
|
fun goldGainedByTribute(): Int {
|
||||||
|
// These values are close enough, linear increase throughout the game
|
||||||
|
var gold = when (gameInfo.gameParameters.gameSpeed) {
|
||||||
|
GameSpeed.Quick -> 60
|
||||||
|
GameSpeed.Standard -> 50
|
||||||
|
GameSpeed.Epic -> 35
|
||||||
|
GameSpeed.Marathon -> 30
|
||||||
|
}
|
||||||
|
val turnsToIncrement = when (gameInfo.gameParameters.gameSpeed) {
|
||||||
|
GameSpeed.Quick -> 5f
|
||||||
|
GameSpeed.Standard -> 6.5f
|
||||||
|
GameSpeed.Epic -> 14f
|
||||||
|
GameSpeed.Marathon -> 32f
|
||||||
|
}
|
||||||
|
gold += 5 * (gameInfo.turns / turnsToIncrement).toInt()
|
||||||
|
|
||||||
|
return gold
|
||||||
|
}
|
||||||
|
|
||||||
|
fun demandGold(cityState: 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.addFlag(CivFlags.RecentlyBullied.name, 20)
|
||||||
|
cityState.updateAllyCivForCityState()
|
||||||
|
updateStatsForNextTurn()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun demandWorker(cityState: CivilizationInfo) {
|
||||||
|
if (!cityState.isCityState()) throw Exception("You can only demand workers from City-States!")
|
||||||
|
|
||||||
|
val buildableWorkerLikeUnits = gameInfo.ruleSet.units.filter {
|
||||||
|
it.value.uniqueObjects.any { it.placeholderText == Constants.canBuildImprovements }
|
||||||
|
&& it.value.isBuildable(this)
|
||||||
|
&& it.value.isCivilian()
|
||||||
|
}
|
||||||
|
if (buildableWorkerLikeUnits.isEmpty()) return // Bad luck?
|
||||||
|
placeUnitNearTile(cityState.getCapital().location, buildableWorkerLikeUnits.keys.random())
|
||||||
|
|
||||||
|
cityState.getDiplomacyManager(this).influence -= 50
|
||||||
|
cityState.addFlag(CivFlags.RecentlyBullied.name, 20)
|
||||||
|
cityState.updateAllyCivForCityState()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun canGiveStat(statType: Stat): Boolean {
|
||||||
|
if (!isCityState())
|
||||||
|
return false
|
||||||
|
val eraInfo = getEraObject()
|
||||||
|
val bonuses = if (eraInfo == null) null
|
||||||
|
else eraInfo.allyBonus[cityStateType.name]
|
||||||
|
if (bonuses != null) {
|
||||||
|
// Defined city states in json
|
||||||
|
bonuses.addAll(eraInfo!!.friendBonus[cityStateType.name]!!)
|
||||||
|
for (bonus in bonuses) {
|
||||||
|
if (statType == Stat.Happiness && bonus.getPlaceholderText() == "Provides [] Happiness")
|
||||||
|
return true
|
||||||
|
if (bonus.getPlaceholderText() == "Provides [] [] per turn" && bonus.getPlaceholderParameters()[1] == statType.name)
|
||||||
|
return true
|
||||||
|
if (bonus.getPlaceholderText() == "Provides [] [] []" && bonus.getPlaceholderParameters()[1] == statType.name)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// compatibility mode
|
||||||
|
return when {
|
||||||
|
cityStateType == CityStateType.Mercantile && statType == Stat.Happiness -> true
|
||||||
|
cityStateType == CityStateType.Cultured && statType == Stat.Culture -> true
|
||||||
|
cityStateType == CityStateType.Maritime && statType == Stat.Food -> true
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -993,4 +1151,5 @@ enum class CivFlags {
|
|||||||
TurnsTillNextDiplomaticVote,
|
TurnsTillNextDiplomaticVote,
|
||||||
ShowDiplomaticVotingResults,
|
ShowDiplomaticVotingResults,
|
||||||
ShouldResetDiplomaticVotes,
|
ShouldResetDiplomaticVotes,
|
||||||
|
RecentlyBullied
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import kotlin.math.min
|
|||||||
|
|
||||||
enum class RelationshipLevel{
|
enum class RelationshipLevel{
|
||||||
Unforgivable,
|
Unforgivable,
|
||||||
|
Afraid,
|
||||||
Enemy,
|
Enemy,
|
||||||
Competitor,
|
Competitor,
|
||||||
Neutral,
|
Neutral,
|
||||||
@ -35,7 +36,8 @@ enum class DiplomacyFlags{
|
|||||||
AgreedToNotSettleNearUs,
|
AgreedToNotSettleNearUs,
|
||||||
IgnoreThemSettlingNearUs,
|
IgnoreThemSettlingNearUs,
|
||||||
ProvideMilitaryUnit,
|
ProvideMilitaryUnit,
|
||||||
EverBeenFriends
|
EverBeenFriends,
|
||||||
|
NotifiedAfraid
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class DiplomaticModifiers{
|
enum class DiplomaticModifiers{
|
||||||
@ -142,6 +144,7 @@ class DiplomacyManager() {
|
|||||||
|
|
||||||
if (civInfo.isCityState()) {
|
if (civInfo.isCityState()) {
|
||||||
if (influence <= -30 || civInfo.isAtWarWith(otherCiv())) return RelationshipLevel.Unforgivable
|
if (influence <= -30 || civInfo.isAtWarWith(otherCiv())) return RelationshipLevel.Unforgivable
|
||||||
|
if (influence < 30 && civInfo.getTributeWillingness(otherCiv()) > 0) return RelationshipLevel.Afraid
|
||||||
if (influence < 0) return RelationshipLevel.Enemy
|
if (influence < 0) return RelationshipLevel.Enemy
|
||||||
if (influence >= 60 && civInfo.getAllyCiv() == otherCivName) return RelationshipLevel.Ally
|
if (influence >= 60 && civInfo.getAllyCiv() == otherCivName) return RelationshipLevel.Ally
|
||||||
if (influence >= 30) return RelationshipLevel.Friend
|
if (influence >= 30) return RelationshipLevel.Friend
|
||||||
@ -400,6 +403,16 @@ class DiplomacyManager() {
|
|||||||
if (civCapitalLocation != null) otherCiv().addNotification(text, civCapitalLocation, civInfo.civName, NotificationIcon.Diplomacy)
|
if (civCapitalLocation != null) otherCiv().addNotification(text, civCapitalLocation, civInfo.civName, NotificationIcon.Diplomacy)
|
||||||
else otherCiv().addNotification(text, civInfo.civName, NotificationIcon.Diplomacy)
|
else otherCiv().addNotification(text, civInfo.civName, NotificationIcon.Diplomacy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Potentially notify about afraid status
|
||||||
|
if (influence < 30 // We usually don't want to bully our friends
|
||||||
|
&& !hasFlag(DiplomacyFlags.NotifiedAfraid)
|
||||||
|
&& civInfo.getTributeWillingness(otherCiv()) > 0) {
|
||||||
|
setFlag(DiplomacyFlags.NotifiedAfraid, 20) // Wait 20 turns until next reminder
|
||||||
|
val text = "[${civInfo.civName}] is afraid of your military power!"
|
||||||
|
if (civCapitalLocation != null) otherCiv().addNotification(text, civCapitalLocation, civInfo.civName, NotificationIcon.Diplomacy)
|
||||||
|
else otherCiv().addNotification(text, civInfo.civName, NotificationIcon.Diplomacy)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import com.unciv.models.ruleset.unit.BaseUnit
|
|||||||
import com.unciv.models.ruleset.unit.UnitType
|
import com.unciv.models.ruleset.unit.UnitType
|
||||||
import com.unciv.ui.utils.toPercent
|
import com.unciv.ui.utils.toPercent
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
|
import kotlin.math.pow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The immutable properties and mutable game state of an individual unit present on the map
|
* The immutable properties and mutable game state of an individual unit present on the map
|
||||||
@ -985,5 +986,13 @@ class MapUnit {
|
|||||||
if (isPreparingParadrop()) action = null
|
if (isPreparingParadrop()) action = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getForceEvaluation(): Int {
|
||||||
|
val promotionBonus = (promotions.numberOfPromotions + 1).toFloat().pow(0.3f)
|
||||||
|
var power = (baseUnit.getForceEvaluation() * promotionBonus).toInt()
|
||||||
|
power *= health
|
||||||
|
power /= 100
|
||||||
|
return power
|
||||||
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
}
|
}
|
||||||
|
@ -243,7 +243,7 @@ class TradeEvaluation {
|
|||||||
RelationshipLevel.Unforgivable -> 10000
|
RelationshipLevel.Unforgivable -> 10000
|
||||||
RelationshipLevel.Enemy -> 2000
|
RelationshipLevel.Enemy -> 2000
|
||||||
RelationshipLevel.Competitor -> 500
|
RelationshipLevel.Competitor -> 500
|
||||||
RelationshipLevel.Neutral -> 200
|
RelationshipLevel.Neutral, RelationshipLevel.Afraid -> 200
|
||||||
RelationshipLevel.Favorable, RelationshipLevel.Friend, RelationshipLevel.Ally -> 100
|
RelationshipLevel.Favorable, RelationshipLevel.Friend, RelationshipLevel.Ally -> 100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import java.util.*
|
|||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
import kotlin.collections.HashMap
|
import kotlin.collections.HashMap
|
||||||
import kotlin.collections.HashSet
|
import kotlin.collections.HashSet
|
||||||
|
import kotlin.math.pow
|
||||||
|
|
||||||
// This is BaseUnit because Unit is already a base Kotlin class and to avoid mixing the two up
|
// This is BaseUnit because Unit is already a base Kotlin class and to avoid mixing the two up
|
||||||
|
|
||||||
@ -55,6 +56,9 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText {
|
|||||||
var uniqueTo: String? = null
|
var uniqueTo: String? = null
|
||||||
var attackSound: String? = null
|
var attackSound: String? = null
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
var cachedForceEvaluation: Int = -1
|
||||||
|
|
||||||
lateinit var ruleset: Ruleset
|
lateinit var ruleset: Ruleset
|
||||||
|
|
||||||
override var civilopediaText = listOf<FormattedLine>()
|
override var civilopediaText = listOf<FormattedLine>()
|
||||||
@ -444,4 +448,78 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText {
|
|||||||
&& (uniqueObjects + getType().uniqueObjects)
|
&& (uniqueObjects + getType().uniqueObjects)
|
||||||
.any { it.placeholderText == "+[]% Strength vs []" && it.params[1] == "City" }
|
.any { it.placeholderText == "+[]% Strength vs []" && it.params[1] == "City" }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun getForceEvaluation(): Int {
|
||||||
|
if (cachedForceEvaluation < 0) evaluateForce()
|
||||||
|
return cachedForceEvaluation
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun evaluateForce() {
|
||||||
|
if (strength == 0 && rangedStrength == 0) {
|
||||||
|
cachedForceEvaluation = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var power = strength.toFloat().pow(1.5f).toInt()
|
||||||
|
var rangedPower = rangedStrength.toFloat().pow(1.45f).toInt()
|
||||||
|
|
||||||
|
// Value ranged naval units less
|
||||||
|
if (isWaterUnit()) {
|
||||||
|
rangedPower /= 2
|
||||||
|
}
|
||||||
|
if (rangedPower > 0)
|
||||||
|
power = rangedPower
|
||||||
|
|
||||||
|
// Replicates the formula from civ V, which is a lower multiplier than probably intended, because math
|
||||||
|
// They did fix it in BNW so it was completely bugged and always 1, again math
|
||||||
|
power = (power * movement.toFloat().pow(0.3f)).toInt()
|
||||||
|
|
||||||
|
if (uniqueObjects.any { it.placeholderText =="Self-destructs when attacking" } )
|
||||||
|
power /= 2
|
||||||
|
if (uniqueObjects.any { it.placeholderText =="Nuclear weapon of Strength []" } )
|
||||||
|
power += 4000
|
||||||
|
|
||||||
|
// Uniques
|
||||||
|
for (unique in uniqueObjects) {
|
||||||
|
|
||||||
|
when {
|
||||||
|
unique.placeholderText == "+[]% Strength vs []" && unique.params[1] == "City" // City Attack - half the bonus
|
||||||
|
-> power += (power * unique.params[0].toInt()) / 200
|
||||||
|
unique.placeholderText == "+[]% Strength vs []" && unique.params[1] != "City" // Bonus vs something else - a quarter of the bonus
|
||||||
|
-> power += (power * unique.params[0].toInt()) / 400
|
||||||
|
unique.placeholderText == "+[]% Strength when attacking" // Attack - half the bonus
|
||||||
|
-> power += (power * unique.params[0].toInt()) / 200
|
||||||
|
unique.placeholderText == "+[]% Strength when defending" // Defense - half the bonus
|
||||||
|
-> power += (power * unique.params[0].toInt()) / 200
|
||||||
|
unique.placeholderText == "May Paradrop up to [] tiles from inside friendly territory" // Paradrop - 25% bonus
|
||||||
|
-> power += power / 4
|
||||||
|
unique.placeholderText == "Must set up to ranged attack" // Must set up - 20 % penalty
|
||||||
|
-> power -= power / 5
|
||||||
|
unique.placeholderText == "+[]% Strength in []" // Bonus in terrain or feature - half the bonus
|
||||||
|
-> power += (power * unique.params[0].toInt()) / 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base promotions
|
||||||
|
for (promotionName in promotions) {
|
||||||
|
for (unique in ruleset.unitPromotions[promotionName]!!.uniqueObjects) {
|
||||||
|
when {
|
||||||
|
unique.placeholderText == "+[]% Strength vs []" && unique.params[1] == "City" // City Attack - half the bonus
|
||||||
|
-> power += (power * unique.params[0].toInt()) / 200
|
||||||
|
unique.placeholderText == "+[]% Strength vs []" && unique.params[1] != "City" // Bonus vs something else - a quarter of the bonus
|
||||||
|
-> power += (power * unique.params[0].toInt()) / 400
|
||||||
|
unique.placeholderText == "+[]% Strength when attacking" // Attack - half the bonus
|
||||||
|
-> power += (power * unique.params[0].toInt()) / 200
|
||||||
|
unique.placeholderText == "+[]% Strength when defending" // Defense - half the bonus
|
||||||
|
-> power += (power * unique.params[0].toInt()) / 200
|
||||||
|
unique.placeholderText == "[] additional attacks per turn" // Extra attacks - 20% bonus per extra attack
|
||||||
|
-> power += (power * unique.params[0].toInt()) / 5
|
||||||
|
unique.placeholderText == "+[]% Strength in []" // Bonus in terrain or feature - half the bonus
|
||||||
|
-> power += (power * unique.params[0].toInt()) / 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
cachedForceEvaluation = power
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -409,6 +409,7 @@ class CityButton(val city: CityInfo, private val tileGroup: WorldTileGroup): Tab
|
|||||||
val color = when (relationshipLevel) {
|
val color = when (relationshipLevel) {
|
||||||
RelationshipLevel.Unforgivable -> Color.RED
|
RelationshipLevel.Unforgivable -> Color.RED
|
||||||
RelationshipLevel.Enemy -> Color.ORANGE
|
RelationshipLevel.Enemy -> Color.ORANGE
|
||||||
|
RelationshipLevel.Afraid -> Color.YELLOW
|
||||||
RelationshipLevel.Neutral, RelationshipLevel.Friend -> Color.LIME
|
RelationshipLevel.Neutral, RelationshipLevel.Friend -> Color.LIME
|
||||||
RelationshipLevel.Ally -> Color.SKY
|
RelationshipLevel.Ally -> Color.SKY
|
||||||
else -> Color.DARK_GRAY
|
else -> Color.DARK_GRAY
|
||||||
|
@ -239,6 +239,14 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() {
|
|||||||
if (isNotPlayersTurn()) protectionButton.disable()
|
if (isNotPlayersTurn()) protectionButton.disable()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val demandTributeButton = "Demand Tribute".toTextButton()
|
||||||
|
demandTributeButton.onClick {
|
||||||
|
rightSideTable.clear()
|
||||||
|
rightSideTable.add(ScrollPane(getDemandTributeTable(otherCiv)))
|
||||||
|
}
|
||||||
|
diplomacyTable.add(demandTributeButton).row()
|
||||||
|
if (isNotPlayersTurn()) demandTributeButton.disable()
|
||||||
|
|
||||||
val diplomacyManager = viewingCiv.getDiplomacyManager(otherCiv)
|
val diplomacyManager = viewingCiv.getDiplomacyManager(otherCiv)
|
||||||
if (!viewingCiv.gameInfo.ruleSet.modOptions.uniques.contains(ModOptionsConstants.diplomaticRelationshipsCannotChange)) {
|
if (!viewingCiv.gameInfo.ruleSet.modOptions.uniques.contains(ModOptionsConstants.diplomaticRelationshipsCannotChange)) {
|
||||||
if (viewingCiv.isAtWarWith(otherCiv)) {
|
if (viewingCiv.isAtWarWith(otherCiv)) {
|
||||||
@ -384,6 +392,50 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getDemandTributeTable(otherCiv: CivilizationInfo): Table {
|
||||||
|
val diplomacyTable = getCityStateDiplomacyTableHeader(otherCiv)
|
||||||
|
diplomacyTable.addSeparator()
|
||||||
|
diplomacyTable.add("Tribute Willingness".toLabel()).row()
|
||||||
|
diplomacyTable.add(">0 to take gold, >30 and size 4 city for worker".toLabel()).row()
|
||||||
|
val modifierTable = Table()
|
||||||
|
val tributeModifiers = otherCiv.getTributeModifiers(viewingCiv, requireWholeList = true)
|
||||||
|
for (item in tributeModifiers) {
|
||||||
|
val color = if (item.value > 0) Color.GREEN else Color.RED
|
||||||
|
modifierTable.add(item.key.toLabel(color))
|
||||||
|
modifierTable.add(item.value.toString().toLabel(color)).row()
|
||||||
|
}
|
||||||
|
modifierTable.add("Sum:".toLabel())
|
||||||
|
modifierTable.add(tributeModifiers.values.sum().toLabel()).row()
|
||||||
|
diplomacyTable.add(modifierTable).row()
|
||||||
|
diplomacyTable.addSeparator()
|
||||||
|
|
||||||
|
val demandGoldButton = "Take [${otherCiv.goldGainedByTribute()}] gold (-15 Influence)".toTextButton()
|
||||||
|
demandGoldButton.onClick {
|
||||||
|
viewingCiv.demandGold(otherCiv)
|
||||||
|
rightSideTable.clear()
|
||||||
|
rightSideTable.add(ScrollPane(getCityStateDiplomacyTable(otherCiv)))
|
||||||
|
}
|
||||||
|
diplomacyTable.add(demandGoldButton).row()
|
||||||
|
if (otherCiv.getTributeWillingness(viewingCiv, demandingWorker = false) <= 0) demandGoldButton.disable()
|
||||||
|
|
||||||
|
val demandWorkerButton = "Take worker (-50 Influence)".toTextButton()
|
||||||
|
demandWorkerButton.onClick {
|
||||||
|
viewingCiv.demandWorker(otherCiv)
|
||||||
|
rightSideTable.clear()
|
||||||
|
rightSideTable.add(ScrollPane(getCityStateDiplomacyTable(otherCiv)))
|
||||||
|
}
|
||||||
|
diplomacyTable.add(demandWorkerButton).row()
|
||||||
|
if (otherCiv.getTributeWillingness(viewingCiv, demandingWorker = true) <= 0) demandWorkerButton.disable()
|
||||||
|
|
||||||
|
val backButton = "Back".toTextButton()
|
||||||
|
backButton.onClick {
|
||||||
|
rightSideTable.clear()
|
||||||
|
rightSideTable.add(ScrollPane(getCityStateDiplomacyTable(otherCiv)))
|
||||||
|
}
|
||||||
|
diplomacyTable.add(backButton)
|
||||||
|
return diplomacyTable
|
||||||
|
}
|
||||||
|
|
||||||
private fun getQuestTable(assignedQuest: AssignedQuest): Table {
|
private fun getQuestTable(assignedQuest: AssignedQuest): Table {
|
||||||
val questTable = Table()
|
val questTable = Table()
|
||||||
questTable.defaults().pad(10f)
|
questTable.defaults().pad(10f)
|
||||||
@ -630,6 +682,7 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() {
|
|||||||
RelationshipLevel.Neutral -> Color.WHITE
|
RelationshipLevel.Neutral -> Color.WHITE
|
||||||
RelationshipLevel.Favorable, RelationshipLevel.Friend,
|
RelationshipLevel.Favorable, RelationshipLevel.Friend,
|
||||||
RelationshipLevel.Ally -> Color.GREEN
|
RelationshipLevel.Ally -> Color.GREEN
|
||||||
|
RelationshipLevel.Afraid -> Color.YELLOW
|
||||||
else -> Color.RED
|
else -> Color.RED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user