diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index 21dfd6e182..7fd1451bbf 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -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 = diff --git a/core/src/com/unciv/logic/automation/Automation.kt b/core/src/com/unciv/logic/automation/Automation.kt index bc6a22e7c5..886f7a5f1e 100644 --- a/core/src/com/unciv/logic/automation/Automation.kt +++ b/core/src/com/unciv/logic/automation/Automation.kt @@ -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 diff --git a/core/src/com/unciv/logic/automation/NextTurnAutomation.kt b/core/src/com/unciv/logic/automation/NextTurnAutomation.kt index ba8207ff81..8e191614bd 100644 --- a/core/src/com/unciv/logic/automation/NextTurnAutomation.kt +++ b/core/src/com/unciv/logic/automation/NextTurnAutomation.kt @@ -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 diff --git a/core/src/com/unciv/logic/civilization/CityStateFunctions.kt b/core/src/com/unciv/logic/civilization/CityStateFunctions.kt index 077fef49fe..aa7f42110c 100644 --- a/core/src/com/unciv/logic/civilization/CityStateFunctions.kt +++ b/core/src/com/unciv/logic/civilization/CityStateFunctions.kt @@ -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 diff --git a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt index f948ce7058..044a367a14 100644 --- a/core/src/com/unciv/logic/civilization/CivilizationInfo.kt +++ b/core/src/com/unciv/logic/civilization/CivilizationInfo.kt @@ -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 { 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() { diff --git a/core/src/com/unciv/logic/trade/TradeEvaluation.kt b/core/src/com/unciv/logic/trade/TradeEvaluation.kt index 2aaea11de8..039589d1b2 100644 --- a/core/src/com/unciv/logic/trade/TradeEvaluation.kt +++ b/core/src/com/unciv/logic/trade/TradeEvaluation.kt @@ -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() diff --git a/core/src/com/unciv/ui/trade/DiplomacyScreen.kt b/core/src/com/unciv/ui/trade/DiplomacyScreen.kt index 3803a852e3..8013eba0b3 100644 --- a/core/src/com/unciv/ui/trade/DiplomacyScreen.kt +++ b/core/src/com/unciv/ui/trade/DiplomacyScreen.kt @@ -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 {