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)
|
||||
|
||||
Unforgivable = Oförlåtlig
|
||||
Afraid = Rädd
|
||||
Enemy = Fiende
|
||||
Competitor = Konkurrent
|
||||
Neutral = Neutral
|
||||
@ -173,6 +174,7 @@ Personality = Personlighet
|
||||
Influence = Inflytande
|
||||
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.
|
||||
|
||||
# Requires translation!
|
||||
When Friends: =
|
||||
# Requires translation!
|
||||
@ -180,6 +182,27 @@ When Allies: =
|
||||
# Requires translation!
|
||||
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
|
||||
|
||||
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) =
|
||||
|
||||
Unforgivable =
|
||||
Afraid =
|
||||
Enemy =
|
||||
Competitor =
|
||||
Neutral =
|
||||
@ -178,6 +179,27 @@ When Friends: =
|
||||
When Allies: =
|
||||
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
|
||||
|
||||
Trade =
|
||||
|
@ -16,6 +16,7 @@ import com.unciv.logic.trade.*
|
||||
import com.unciv.models.ruleset.ModOptionsConstants
|
||||
import com.unciv.models.ruleset.VictoryType
|
||||
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.stats.Stat
|
||||
import com.unciv.models.translations.tr
|
||||
@ -49,7 +50,10 @@ object NextTurnAutomation {
|
||||
chooseTechToResearch(civInfo)
|
||||
automateCityBombardment(civInfo)
|
||||
useGold(civInfo)
|
||||
protectCityStates(civInfo)
|
||||
if (!civInfo.isCityState()) {
|
||||
protectCityStates(civInfo)
|
||||
bullyCityStates(civInfo)
|
||||
}
|
||||
automateUnits(civInfo) // this is the most expensive part
|
||||
reassignWorkedTiles(civInfo) // second most expensive
|
||||
trainSettler(civInfo)
|
||||
@ -114,29 +118,19 @@ object NextTurnAutomation {
|
||||
|
||||
/** allow AI to spend money to purchase city-state friendship, buildings & unit */
|
||||
private fun useGold(civInfo: CivilizationInfo) {
|
||||
if (civInfo.victoryType() == VictoryType.Cultural) {
|
||||
for (cityState in civInfo.getKnownCivs()
|
||||
.filter { it.isCityState() && it.cityStateType == CityStateType.Cultured }) {
|
||||
val diploManager = cityState.getDiplomacyManager(civInfo)
|
||||
if (diploManager.influence < 40) { // we want to gain influence with them
|
||||
|
||||
if (!civInfo.isCityState()) {
|
||||
val potentialAllies = civInfo.getKnownCivs().filter { it.isCityState() }
|
||||
if (potentialAllies.isNotEmpty()) {
|
||||
val cityState =
|
||||
potentialAllies.maxByOrNull { valueCityStateAlliance(civInfo, it) }!!
|
||||
if (cityState.getAllyCiv() != civInfo.civName && valueCityStateAlliance(civInfo, cityState) > 0) {
|
||||
tryGainInfluence(civInfo, cityState)
|
||||
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 }) {
|
||||
val construction = city.cityConstructions.getCurrentConstruction()
|
||||
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) {
|
||||
for (state in civInfo.getKnownCivs().filter{!it.isDefeated() && it.isCityState()}) {
|
||||
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) {
|
||||
// City-States automatically get all techs that at least half of the major civs know
|
||||
val researchableTechs = civInfo.gameInfo.ruleSet.technologies.keys
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.unciv.logic.city
|
||||
|
||||
import com.badlogic.gdx.math.Vector2
|
||||
import com.unciv.logic.battle.CityCombatant
|
||||
import com.unciv.logic.civilization.CivilizationInfo
|
||||
import com.unciv.logic.civilization.GreatPersonManager
|
||||
import com.unciv.logic.civilization.ReligionState
|
||||
@ -22,6 +23,7 @@ import kotlin.collections.HashMap
|
||||
import kotlin.collections.HashSet
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.min
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class CityInfo {
|
||||
@ -673,6 +675,11 @@ class CityInfo {
|
||||
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> {
|
||||
val tilesList: HashSet<TileInfo> = getTiles().toHashSet()
|
||||
@ -688,7 +695,7 @@ class CityInfo {
|
||||
.distinct().toList()
|
||||
}
|
||||
fun getImprovableTiles(): Sequence<TileInfo> = getTiles()
|
||||
.filter {it.hasViewableResource(civInfo) && it.improvement == null}
|
||||
|
||||
.filter {it.hasViewableResource(civInfo) && it.improvement == null}
|
||||
|
||||
//endregion
|
||||
}
|
||||
}
|
@ -7,11 +7,13 @@ import com.unciv.logic.GameInfo
|
||||
import com.unciv.logic.UncivShowableException
|
||||
import com.unciv.logic.automation.NextTurnAutomation
|
||||
import com.unciv.logic.automation.WorkerAutomation
|
||||
import com.unciv.logic.battle.CityCombatant
|
||||
import com.unciv.logic.city.CityInfo
|
||||
import com.unciv.logic.civilization.RuinsManager.RuinsManager
|
||||
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
|
||||
import com.unciv.logic.civilization.diplomacy.DiplomacyManager
|
||||
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.TileInfo
|
||||
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.stats.Stat
|
||||
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.ui.victoryscreen.RankingType
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.HashMap
|
||||
import kotlin.collections.LinkedHashMap
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.system.measureNanoTime
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -993,4 +1151,5 @@ enum class CivFlags {
|
||||
TurnsTillNextDiplomaticVote,
|
||||
ShowDiplomaticVotingResults,
|
||||
ShouldResetDiplomaticVotes,
|
||||
RecentlyBullied
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import kotlin.math.min
|
||||
|
||||
enum class RelationshipLevel{
|
||||
Unforgivable,
|
||||
Afraid,
|
||||
Enemy,
|
||||
Competitor,
|
||||
Neutral,
|
||||
@ -35,7 +36,8 @@ enum class DiplomacyFlags{
|
||||
AgreedToNotSettleNearUs,
|
||||
IgnoreThemSettlingNearUs,
|
||||
ProvideMilitaryUnit,
|
||||
EverBeenFriends
|
||||
EverBeenFriends,
|
||||
NotifiedAfraid
|
||||
}
|
||||
|
||||
enum class DiplomaticModifiers{
|
||||
@ -142,6 +144,7 @@ class DiplomacyManager() {
|
||||
|
||||
if (civInfo.isCityState()) {
|
||||
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 >= 60 && civInfo.getAllyCiv() == otherCivName) return RelationshipLevel.Ally
|
||||
if (influence >= 30) return RelationshipLevel.Friend
|
||||
@ -400,6 +403,16 @@ class DiplomacyManager() {
|
||||
if (civCapitalLocation != null) otherCiv().addNotification(text, civCapitalLocation, 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.ui.utils.toPercent
|
||||
import java.text.DecimalFormat
|
||||
import kotlin.math.pow
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
@ -243,7 +243,7 @@ class TradeEvaluation {
|
||||
RelationshipLevel.Unforgivable -> 10000
|
||||
RelationshipLevel.Enemy -> 2000
|
||||
RelationshipLevel.Competitor -> 500
|
||||
RelationshipLevel.Neutral -> 200
|
||||
RelationshipLevel.Neutral, RelationshipLevel.Afraid -> 200
|
||||
RelationshipLevel.Favorable, RelationshipLevel.Friend, RelationshipLevel.Ally -> 100
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.HashMap
|
||||
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
|
||||
|
||||
@ -55,6 +56,9 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText {
|
||||
var uniqueTo: String? = null
|
||||
var attackSound: String? = null
|
||||
|
||||
@Transient
|
||||
var cachedForceEvaluation: Int = -1
|
||||
|
||||
lateinit var ruleset: Ruleset
|
||||
|
||||
override var civilopediaText = listOf<FormattedLine>()
|
||||
@ -444,4 +448,78 @@ class BaseUnit : INamed, INonPerpetualConstruction, ICivilopediaText {
|
||||
&& (uniqueObjects + getType().uniqueObjects)
|
||||
.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) {
|
||||
RelationshipLevel.Unforgivable -> Color.RED
|
||||
RelationshipLevel.Enemy -> Color.ORANGE
|
||||
RelationshipLevel.Afraid -> Color.YELLOW
|
||||
RelationshipLevel.Neutral, RelationshipLevel.Friend -> Color.LIME
|
||||
RelationshipLevel.Ally -> Color.SKY
|
||||
else -> Color.DARK_GRAY
|
||||
|
@ -239,6 +239,14 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() {
|
||||
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)
|
||||
if (!viewingCiv.gameInfo.ruleSet.modOptions.uniques.contains(ModOptionsConstants.diplomaticRelationshipsCannotChange)) {
|
||||
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 {
|
||||
val questTable = Table()
|
||||
questTable.defaults().pad(10f)
|
||||
@ -630,6 +682,7 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() {
|
||||
RelationshipLevel.Neutral -> Color.WHITE
|
||||
RelationshipLevel.Favorable, RelationshipLevel.Friend,
|
||||
RelationshipLevel.Ally -> Color.GREEN
|
||||
RelationshipLevel.Afraid -> Color.YELLOW
|
||||
else -> Color.RED
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user