mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-08 14:57:58 +07:00
Force ranking, bullying improvements (#5102)
* Proper demand tribute thresholds * Proper calculation for Force rankings * debug strings * use new force ranking for AI threatAssessment * use new force ranking for AI threatAssessment, pt 2
This commit is contained in:
@ -187,7 +187,7 @@ The unique luxury is one of: =
|
|||||||
|
|
||||||
Demand Tribute =
|
Demand Tribute =
|
||||||
Tribute Willingness =
|
Tribute Willingness =
|
||||||
>0 to take gold, >30 and size 4 city for worker =
|
At least 0 to take gold, at least 30 and size 4 city for worker =
|
||||||
Major Civ =
|
Major Civ =
|
||||||
No Cities =
|
No Cities =
|
||||||
Base value =
|
Base value =
|
||||||
|
@ -8,6 +8,7 @@ import com.unciv.models.ruleset.VictoryType
|
|||||||
import com.unciv.models.ruleset.tile.ResourceType
|
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.Stats
|
import com.unciv.models.stats.Stats
|
||||||
|
import com.unciv.ui.victoryscreen.RankingType
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.sqrt
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
@ -99,17 +100,9 @@ object Automation {
|
|||||||
return chosenUnit.name
|
return chosenUnit.name
|
||||||
}
|
}
|
||||||
|
|
||||||
fun evaluateCombatStrength(civInfo: CivilizationInfo): Int {
|
|
||||||
// Since units become exponentially stronger per combat strength increase, we square em all
|
|
||||||
fun square(x: Int) = x * x
|
|
||||||
val unitStrength = civInfo.getCivUnits()
|
|
||||||
.map { square(max(it.baseUnit().strength, it.baseUnit().rangedStrength)) }.sum()
|
|
||||||
return sqrt(unitStrength.toDouble()).toInt() + 1 //avoid 0, because we divide by the result
|
|
||||||
}
|
|
||||||
|
|
||||||
fun threatAssessment(assessor: CivilizationInfo, assessed: CivilizationInfo): ThreatLevel {
|
fun threatAssessment(assessor: CivilizationInfo, assessed: CivilizationInfo): ThreatLevel {
|
||||||
val powerLevelComparison =
|
val powerLevelComparison =
|
||||||
evaluateCombatStrength(assessed) / evaluateCombatStrength(assessor).toFloat()
|
assessed.getStatForRanking(RankingType.Force) / assessor.getStatForRanking(RankingType.Force).toFloat()
|
||||||
return when {
|
return when {
|
||||||
powerLevelComparison > 2 -> ThreatLevel.VeryHigh
|
powerLevelComparison > 2 -> ThreatLevel.VeryHigh
|
||||||
powerLevelComparison > 1.5f -> ThreatLevel.High
|
powerLevelComparison > 1.5f -> ThreatLevel.High
|
||||||
|
@ -23,6 +23,7 @@ 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
|
||||||
import com.unciv.ui.pickerscreens.BeliefContainer
|
import com.unciv.ui.pickerscreens.BeliefContainer
|
||||||
|
import com.unciv.ui.victoryscreen.RankingType
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
object NextTurnAutomation {
|
object NextTurnAutomation {
|
||||||
@ -185,6 +186,9 @@ object NextTurnAutomation {
|
|||||||
if (distance < 20)
|
if (distance < 20)
|
||||||
value -= (20 - distance) / 4
|
value -= (20 - distance) / 4
|
||||||
}
|
}
|
||||||
|
else if (civInfo.victoryType() == VictoryType.Diplomatic) {
|
||||||
|
value += 5 // Generally be friendly
|
||||||
|
}
|
||||||
if (civInfo.gold < 100) {
|
if (civInfo.gold < 100) {
|
||||||
// Consider bullying for cash
|
// Consider bullying for cash
|
||||||
value -= 5
|
value -= 5
|
||||||
@ -229,7 +233,7 @@ object NextTurnAutomation {
|
|||||||
if(diplomacyManager.relationshipLevel() < RelationshipLevel.Friend
|
if(diplomacyManager.relationshipLevel() < RelationshipLevel.Friend
|
||||||
&& diplomacyManager.diplomaticStatus == DiplomaticStatus.Peace
|
&& diplomacyManager.diplomaticStatus == DiplomaticStatus.Peace
|
||||||
&& valueCityStateAlliance(civInfo, state) <= 0
|
&& valueCityStateAlliance(civInfo, state) <= 0
|
||||||
&& state.getTributeWillingness(civInfo) > 0) {
|
&& state.getTributeWillingness(civInfo) >= 0) {
|
||||||
if (state.getTributeWillingness(civInfo, demandingWorker = true) > 0)
|
if (state.getTributeWillingness(civInfo, demandingWorker = true) > 0)
|
||||||
state.tributeWorker(civInfo)
|
state.tributeWorker(civInfo)
|
||||||
else
|
else
|
||||||
@ -493,12 +497,12 @@ object NextTurnAutomation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun motivationToAttack(civInfo: CivilizationInfo, otherCiv: CivilizationInfo): Int {
|
private fun motivationToAttack(civInfo: CivilizationInfo, otherCiv: CivilizationInfo): Int {
|
||||||
val ourCombatStrength = Automation.evaluateCombatStrength(civInfo).toFloat()
|
val ourCombatStrength = civInfo.getStatForRanking(RankingType.Force).toFloat()
|
||||||
var theirCombatStrength = Automation.evaluateCombatStrength(otherCiv)
|
var theirCombatStrength = otherCiv.getStatForRanking(RankingType.Force).toFloat()
|
||||||
|
|
||||||
//for city-states, also consider there protectors
|
//for city-states, also consider there protectors
|
||||||
if(otherCiv.isCityState() and otherCiv.getProtectorCivs().isNotEmpty()) {
|
if(otherCiv.isCityState() and otherCiv.getProtectorCivs().isNotEmpty()) {
|
||||||
theirCombatStrength += otherCiv.getProtectorCivs().sumOf{Automation.evaluateCombatStrength(it)}
|
theirCombatStrength += otherCiv.getProtectorCivs().sumOf{it.getStatForRanking(RankingType.Force)}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (theirCombatStrength > ourCombatStrength) return 0
|
if (theirCombatStrength > ourCombatStrength) return 0
|
||||||
|
@ -231,15 +231,15 @@ class CityStateFunctions(val civInfo: CivilizationInfo) {
|
|||||||
if (civInfo.getDiplomacyManager(demandingCiv).influence < -30)
|
if (civInfo.getDiplomacyManager(demandingCiv).influence < -30)
|
||||||
modifiers["Influence below -30"] = -300
|
modifiers["Influence below -30"] = -300
|
||||||
|
|
||||||
// Slight optimization, we don't do the expensive stuff if we have no chance of getting a positive result
|
// Slight optimization, we don't do the expensive stuff if we have no chance of getting a >= 0 result
|
||||||
if (!requireWholeList && modifiers.values.sum() <= -200)
|
if (!requireWholeList && modifiers.values.sum() < -200)
|
||||||
return modifiers
|
return modifiers
|
||||||
|
|
||||||
val forceRank = civInfo.gameInfo.getAliveMajorCivs().sortedByDescending { it.getStatForRanking(
|
val forceRank = civInfo.gameInfo.getAliveMajorCivs().sortedByDescending { it.getStatForRanking(
|
||||||
RankingType.Force) }.indexOf(demandingCiv)
|
RankingType.Force) }.indexOf(demandingCiv)
|
||||||
modifiers["Military Rank"] = 100 - ((100 / civInfo.gameInfo.gameParameters.players.size) * forceRank)
|
modifiers["Military Rank"] = 100 - ((100 / civInfo.gameInfo.gameParameters.players.size) * forceRank)
|
||||||
|
|
||||||
if (!requireWholeList && modifiers.values.sum() <= -100)
|
if (!requireWholeList && modifiers.values.sum() < -100)
|
||||||
return modifiers
|
return modifiers
|
||||||
|
|
||||||
val bullyRange = max(5, civInfo.gameInfo.tileMap.tileMatrix.size / 10) // Longer range for larger maps
|
val bullyRange = max(5, civInfo.gameInfo.tileMap.tileMatrix.size / 10) // Longer range for larger maps
|
||||||
|
@ -30,15 +30,13 @@ import com.unciv.models.stats.Stats
|
|||||||
import com.unciv.models.translations.getPlaceholderParameters
|
import com.unciv.models.translations.getPlaceholderParameters
|
||||||
import com.unciv.models.translations.getPlaceholderText
|
import com.unciv.models.translations.getPlaceholderText
|
||||||
import com.unciv.models.translations.tr
|
import com.unciv.models.translations.tr
|
||||||
|
import com.unciv.ui.utils.toPercent
|
||||||
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.collections.LinkedHashMap
|
||||||
import kotlin.math.max
|
import kotlin.math.*
|
||||||
import kotlin.math.min
|
|
||||||
import kotlin.math.pow
|
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
class CivilizationInfo {
|
class CivilizationInfo {
|
||||||
|
|
||||||
@ -94,6 +92,9 @@ class CivilizationInfo {
|
|||||||
@Transient
|
@Transient
|
||||||
val cityStateFunctions = CityStateFunctions(this)
|
val cityStateFunctions = CityStateFunctions(this)
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
private var cachedMilitaryMight = -1
|
||||||
|
|
||||||
var playerType = PlayerType.AI
|
var playerType = PlayerType.AI
|
||||||
|
|
||||||
/** Used in online multiplayer for human players */
|
/** Used in online multiplayer for human players */
|
||||||
@ -497,13 +498,33 @@ class CivilizationInfo {
|
|||||||
RankingType.Production -> statsForNextTurn.production.roundToInt()
|
RankingType.Production -> statsForNextTurn.production.roundToInt()
|
||||||
RankingType.Gold -> gold
|
RankingType.Gold -> gold
|
||||||
RankingType.Territory -> cities.sumBy { it.tiles.size }
|
RankingType.Territory -> cities.sumBy { it.tiles.size }
|
||||||
RankingType.Force -> units.sumBy { it.baseUnit.strength }
|
RankingType.Force -> getMilitaryMight()
|
||||||
RankingType.Happiness -> getHappiness()
|
RankingType.Happiness -> getHappiness()
|
||||||
RankingType.Technologies -> tech.researchedTechnologies.size
|
RankingType.Technologies -> tech.researchedTechnologies.size
|
||||||
RankingType.Culture -> policies.adoptedPolicies.count { !Policy.isBranchCompleteByName(it) }
|
RankingType.Culture -> policies.adoptedPolicies.count { !Policy.isBranchCompleteByName(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getMilitaryMight(): Int {
|
||||||
|
if (cachedMilitaryMight < 0)
|
||||||
|
cachedMilitaryMight = calculateMilitaryMight()
|
||||||
|
return cachedMilitaryMight
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calculateMilitaryMight(): Int {
|
||||||
|
var sum = 0
|
||||||
|
for (unit in units) {
|
||||||
|
sum += if (unit.baseUnit.isWaterUnit())
|
||||||
|
unit.getForceEvaluation() / 2 // Really don't value water units highly
|
||||||
|
else
|
||||||
|
unit.getForceEvaluation()
|
||||||
|
}
|
||||||
|
val goldBonus = sqrt(gold.toFloat()).toPercent() // 2f if gold == 10000
|
||||||
|
sum = (sum * min(goldBonus, 2f)).toInt() // 2f is max bonus
|
||||||
|
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun getGreatPeople(): HashSet<BaseUnit> {
|
fun getGreatPeople(): HashSet<BaseUnit> {
|
||||||
val greatPeople = gameInfo.ruleSet.units.values.asSequence()
|
val greatPeople = gameInfo.ruleSet.units.values.asSequence()
|
||||||
@ -653,6 +674,8 @@ class CivilizationInfo {
|
|||||||
diplomacy.values.toList().forEach { it.nextTurn() } // we copy the diplomacy values so if it changes in-loop we won't crash
|
diplomacy.values.toList().forEach { it.nextTurn() } // we copy the diplomacy values so if it changes in-loop we won't crash
|
||||||
updateAllyCivForCityState()
|
updateAllyCivForCityState()
|
||||||
updateHasActiveGreatWall()
|
updateHasActiveGreatWall()
|
||||||
|
|
||||||
|
cachedMilitaryMight = -1 // Reset so we don't use a value from a previous turn
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startTurnFlags() {
|
private fun startTurnFlags() {
|
||||||
|
@ -9,6 +9,7 @@ import com.unciv.logic.civilization.diplomacy.RelationshipLevel
|
|||||||
import com.unciv.models.ruleset.ModOptionsConstants
|
import com.unciv.models.ruleset.ModOptionsConstants
|
||||||
import com.unciv.models.ruleset.Ruleset
|
import com.unciv.models.ruleset.Ruleset
|
||||||
import com.unciv.models.ruleset.tile.ResourceType
|
import com.unciv.models.ruleset.tile.ResourceType
|
||||||
|
import com.unciv.ui.victoryscreen.RankingType
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.math.sqrt
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
@ -259,12 +260,12 @@ class TradeEvaluation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun evaluatePeaceCostForThem(ourCivilization: CivilizationInfo, otherCivilization: CivilizationInfo): Int {
|
fun evaluatePeaceCostForThem(ourCivilization: CivilizationInfo, otherCivilization: CivilizationInfo): Int {
|
||||||
val ourCombatStrength = Automation.evaluateCombatStrength(ourCivilization)
|
val ourCombatStrength = ourCivilization.getStatForRanking(RankingType.Force)
|
||||||
val theirCombatStrength = Automation.evaluateCombatStrength(otherCivilization)
|
val theirCombatStrength = otherCivilization.getStatForRanking(RankingType.Force)
|
||||||
if (ourCombatStrength*1.5f >= theirCombatStrength && theirCombatStrength * 1.5f >= ourCombatStrength)
|
if (ourCombatStrength*1.5f >= theirCombatStrength && theirCombatStrength * 1.5f >= ourCombatStrength)
|
||||||
return 0 // we're roughly equal, there's no huge power imbalance
|
return 0 // we're roughly equal, there's no huge power imbalance
|
||||||
if (ourCombatStrength == 0) return -1000
|
if (ourCombatStrength == 0) return -1000
|
||||||
if (theirCombatStrength == 0) return 1000 // Chumps got no cities or units
|
if (theirCombatStrength == 0) return 1000 // Chumps got no combat units
|
||||||
if (ourCombatStrength > theirCombatStrength) {
|
if (ourCombatStrength > theirCombatStrength) {
|
||||||
val absoluteAdvantage = ourCombatStrength - theirCombatStrength
|
val absoluteAdvantage = ourCombatStrength - theirCombatStrength
|
||||||
val percentageAdvantage = absoluteAdvantage / theirCombatStrength.toFloat()
|
val percentageAdvantage = absoluteAdvantage / theirCombatStrength.toFloat()
|
||||||
|
@ -431,17 +431,17 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() {
|
|||||||
val diplomacyTable = getCityStateDiplomacyTableHeader(otherCiv)
|
val diplomacyTable = getCityStateDiplomacyTableHeader(otherCiv)
|
||||||
diplomacyTable.addSeparator()
|
diplomacyTable.addSeparator()
|
||||||
diplomacyTable.add("Tribute Willingness".toLabel()).row()
|
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 modifierTable = Table()
|
||||||
val tributeModifiers = otherCiv.getTributeModifiers(viewingCiv, requireWholeList = true)
|
val tributeModifiers = otherCiv.getTributeModifiers(viewingCiv, requireWholeList = true)
|
||||||
for (item in tributeModifiers) {
|
for (item in tributeModifiers) {
|
||||||
val color = if (item.value > 0) Color.GREEN else Color.RED
|
val color = if (item.value >= 0) Color.GREEN else Color.RED
|
||||||
modifierTable.add(item.key.toLabel(color))
|
modifierTable.add(item.key.toLabel(color))
|
||||||
modifierTable.add(item.value.toString().toLabel(color)).row()
|
modifierTable.add(item.value.toString().toLabel(color)).row()
|
||||||
}
|
}
|
||||||
modifierTable.add("Sum:".toLabel())
|
modifierTable.add("Sum:".toLabel())
|
||||||
modifierTable.add(tributeModifiers.values.sum().toLabel()).row()
|
modifierTable.add(tributeModifiers.values.sum().toLabel()).row()
|
||||||
diplomacyTable.add(modifierTable).row()
|
diplomacyTable.add(modifierTable).row()
|
||||||
|
diplomacyTable.add("At least 0 to take gold, at least 30 and size 4 city for worker".toLabel()).row()
|
||||||
diplomacyTable.addSeparator()
|
diplomacyTable.addSeparator()
|
||||||
|
|
||||||
val demandGoldButton = "Take [${otherCiv.goldGainedByTribute()}] gold (-15 Influence)".toTextButton()
|
val demandGoldButton = "Take [${otherCiv.goldGainedByTribute()}] gold (-15 Influence)".toTextButton()
|
||||||
@ -451,7 +451,7 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() {
|
|||||||
rightSideTable.add(ScrollPane(getCityStateDiplomacyTable(otherCiv)))
|
rightSideTable.add(ScrollPane(getCityStateDiplomacyTable(otherCiv)))
|
||||||
}
|
}
|
||||||
diplomacyTable.add(demandGoldButton).row()
|
diplomacyTable.add(demandGoldButton).row()
|
||||||
if (otherCiv.getTributeWillingness(viewingCiv, demandingWorker = false) <= 0) demandGoldButton.disable()
|
if (otherCiv.getTributeWillingness(viewingCiv, demandingWorker = false) < 0) demandGoldButton.disable()
|
||||||
|
|
||||||
val demandWorkerButton = "Take worker (-50 Influence)".toTextButton()
|
val demandWorkerButton = "Take worker (-50 Influence)".toTextButton()
|
||||||
demandWorkerButton.onClick {
|
demandWorkerButton.onClick {
|
||||||
@ -460,7 +460,7 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() {
|
|||||||
rightSideTable.add(ScrollPane(getCityStateDiplomacyTable(otherCiv)))
|
rightSideTable.add(ScrollPane(getCityStateDiplomacyTable(otherCiv)))
|
||||||
}
|
}
|
||||||
diplomacyTable.add(demandWorkerButton).row()
|
diplomacyTable.add(demandWorkerButton).row()
|
||||||
if (otherCiv.getTributeWillingness(viewingCiv, demandingWorker = true) <= 0) demandWorkerButton.disable()
|
if (otherCiv.getTributeWillingness(viewingCiv, demandingWorker = true) < 0) demandWorkerButton.disable()
|
||||||
|
|
||||||
val backButton = "Back".toTextButton()
|
val backButton = "Back".toTextButton()
|
||||||
backButton.onClick {
|
backButton.onClick {
|
||||||
|
Reference in New Issue
Block a user