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:
SimonCeder 2021-09-06 14:50:39 +02:00 committed by GitHub
parent 486e2a7a8a
commit 2ca42a705f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 50 additions and 29 deletions

View File

@ -187,7 +187,7 @@ The unique luxury is one of: =
Demand Tribute =
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 =
No Cities =
Base value =

View File

@ -8,6 +8,7 @@ import com.unciv.models.ruleset.VictoryType
import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.models.stats.Stats
import com.unciv.ui.victoryscreen.RankingType
import kotlin.math.max
import kotlin.math.sqrt
@ -99,17 +100,9 @@ object Automation {
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 {
val powerLevelComparison =
evaluateCombatStrength(assessed) / evaluateCombatStrength(assessor).toFloat()
assessed.getStatForRanking(RankingType.Force) / assessor.getStatForRanking(RankingType.Force).toFloat()
return when {
powerLevelComparison > 2 -> ThreatLevel.VeryHigh
powerLevelComparison > 1.5f -> ThreatLevel.High

View File

@ -23,6 +23,7 @@ import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.models.stats.Stat
import com.unciv.models.translations.tr
import com.unciv.ui.pickerscreens.BeliefContainer
import com.unciv.ui.victoryscreen.RankingType
import kotlin.math.min
object NextTurnAutomation {
@ -185,6 +186,9 @@ object NextTurnAutomation {
if (distance < 20)
value -= (20 - distance) / 4
}
else if (civInfo.victoryType() == VictoryType.Diplomatic) {
value += 5 // Generally be friendly
}
if (civInfo.gold < 100) {
// Consider bullying for cash
value -= 5
@ -229,7 +233,7 @@ object NextTurnAutomation {
if(diplomacyManager.relationshipLevel() < RelationshipLevel.Friend
&& diplomacyManager.diplomaticStatus == DiplomaticStatus.Peace
&& valueCityStateAlliance(civInfo, state) <= 0
&& state.getTributeWillingness(civInfo) > 0) {
&& state.getTributeWillingness(civInfo) >= 0) {
if (state.getTributeWillingness(civInfo, demandingWorker = true) > 0)
state.tributeWorker(civInfo)
else
@ -493,12 +497,12 @@ object NextTurnAutomation {
}
private fun motivationToAttack(civInfo: CivilizationInfo, otherCiv: CivilizationInfo): Int {
val ourCombatStrength = Automation.evaluateCombatStrength(civInfo).toFloat()
var theirCombatStrength = Automation.evaluateCombatStrength(otherCiv)
val ourCombatStrength = civInfo.getStatForRanking(RankingType.Force).toFloat()
var theirCombatStrength = otherCiv.getStatForRanking(RankingType.Force).toFloat()
//for city-states, also consider there protectors
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

View File

@ -231,15 +231,15 @@ class CityStateFunctions(val civInfo: CivilizationInfo) {
if (civInfo.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)
// 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)
return modifiers
val forceRank = civInfo.gameInfo.getAliveMajorCivs().sortedByDescending { it.getStatForRanking(
RankingType.Force) }.indexOf(demandingCiv)
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
val bullyRange = max(5, civInfo.gameInfo.tileMap.tileMatrix.size / 10) // Longer range for larger maps

View File

@ -30,15 +30,13 @@ 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.utils.toPercent
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.math.*
class CivilizationInfo {
@ -94,6 +92,9 @@ class CivilizationInfo {
@Transient
val cityStateFunctions = CityStateFunctions(this)
@Transient
private var cachedMilitaryMight = -1
var playerType = PlayerType.AI
/** Used in online multiplayer for human players */
@ -497,13 +498,33 @@ class CivilizationInfo {
RankingType.Production -> statsForNextTurn.production.roundToInt()
RankingType.Gold -> gold
RankingType.Territory -> cities.sumBy { it.tiles.size }
RankingType.Force -> units.sumBy { it.baseUnit.strength }
RankingType.Force -> getMilitaryMight()
RankingType.Happiness -> getHappiness()
RankingType.Technologies -> tech.researchedTechnologies.size
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> {
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
updateAllyCivForCityState()
updateHasActiveGreatWall()
cachedMilitaryMight = -1 // Reset so we don't use a value from a previous turn
}
private fun startTurnFlags() {

View File

@ -9,6 +9,7 @@ import com.unciv.logic.civilization.diplomacy.RelationshipLevel
import com.unciv.models.ruleset.ModOptionsConstants
import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.ui.victoryscreen.RankingType
import kotlin.math.min
import kotlin.math.sqrt
@ -259,12 +260,12 @@ class TradeEvaluation {
}
fun evaluatePeaceCostForThem(ourCivilization: CivilizationInfo, otherCivilization: CivilizationInfo): Int {
val ourCombatStrength = Automation.evaluateCombatStrength(ourCivilization)
val theirCombatStrength = Automation.evaluateCombatStrength(otherCivilization)
val ourCombatStrength = ourCivilization.getStatForRanking(RankingType.Force)
val theirCombatStrength = otherCivilization.getStatForRanking(RankingType.Force)
if (ourCombatStrength*1.5f >= theirCombatStrength && theirCombatStrength * 1.5f >= ourCombatStrength)
return 0 // we're roughly equal, there's no huge power imbalance
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) {
val absoluteAdvantage = ourCombatStrength - theirCombatStrength
val percentageAdvantage = absoluteAdvantage / theirCombatStrength.toFloat()

View File

@ -431,17 +431,17 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() {
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
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.add("At least 0 to take gold, at least 30 and size 4 city for worker".toLabel()).row()
diplomacyTable.addSeparator()
val demandGoldButton = "Take [${otherCiv.goldGainedByTribute()}] gold (-15 Influence)".toTextButton()
@ -451,7 +451,7 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() {
rightSideTable.add(ScrollPane(getCityStateDiplomacyTable(otherCiv)))
}
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()
demandWorkerButton.onClick {
@ -460,7 +460,7 @@ class DiplomacyScreen(val viewingCiv:CivilizationInfo):CameraStageBaseScreen() {
rightSideTable.add(ScrollPane(getCityStateDiplomacyTable(otherCiv)))
}
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()
backButton.onClick {