Add personality uses (#11807)

* Reduced max motivationToAttack from relative combat strength

* Refactored MotivationToAttackAutomation to use a float instead of an int

* Added personality values to MotivationToAttackAutomation

* Fixed minor build errors

* Added DiplomacyAutomation personality modifiers

* Improved ConstructionAutomation personality values

* Added some more personality implementations

* Tweaked trainSettler

* Fixed civilizations asking for help against city-states

* Adjusted DiplomacyAutomation to account for war mongering

* Fixed typo
This commit is contained in:
Oskar Niesen
2024-06-22 14:23:38 -05:00
committed by GitHub
parent 2087c36c6d
commit f65709ab09
12 changed files with 238 additions and 194 deletions

View File

@ -233,8 +233,8 @@ object Automation {
if (civInfo.gameInfo.turns > 120 * speed.barbarianModifier * multiplier)
multiplier /= 2
// If we have a lot of, or no cities we are not afraid
if (civInfo.cities.isEmpty() || civInfo.cities.size >= 4 * multiplier)
// If we have no cities or a lot of units we are not afraid
if (civInfo.cities.isEmpty() || civInfo.units.getCivUnits().count() >= 4 * multiplier)
return false
// If we have vision of our entire starting continent (ish) we are not afraid

View File

@ -183,7 +183,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions) {
modifier = 5f // there's a settler just sitting here, doing nothing - BAD
if (!civInfo.isAIOrAutoPlaying()) modifier /= 2 // Players prefer to make their own unit choices usually
modifier *= personality.scaledFocus(PersonalityValue.Aggressive)
modifier *= personality.modifierFocus(PersonalityValue.Military, .3f)
addChoice(relativeCostEffectiveness, militaryUnit, modifier)
}
@ -256,7 +256,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions) {
if (!civInfo.hasUnique(UniqueType.EnablesConstructionOfSpaceshipParts)) return
val spaceshipPart = (nonWonders + units).filter { it.name in spaceshipParts }.filterBuildable().firstOrNull()
?: return
val modifier = 2f
val modifier = 3f * personality.modifierFocus(PersonalityValue.Science, .4f)
addChoice(relativeCostEffectiveness, spaceshipPart.name, modifier)
}
@ -287,8 +287,8 @@ class ConstructionAutomation(val cityConstructions: CityConstructions) {
var value = 0f
if (!cityIsOverAverageProduction) return value
if (building.isWonder) value += 2f
if (building.hasUnique(UniqueType.TriggersCulturalVictory)) value += 10f
if (building.hasUnique(UniqueType.EnablesConstructionOfSpaceshipParts)) value += 10f
if (building.hasUnique(UniqueType.TriggersCulturalVictory)) value += 10f * personality.modifierFocus(PersonalityValue.Culture, .3f)
if (building.hasUnique(UniqueType.EnablesConstructionOfSpaceshipParts)) value += 10f * personality.modifierFocus(PersonalityValue.Science, .3f)
return value
}
@ -300,8 +300,8 @@ class ConstructionAutomation(val cityConstructions: CityConstructions) {
.mapNotNull { NextTurnAutomation.getClosestCities(civInfo, it) }
.any { it.city1 == city })
warModifier *= 2f
value += warModifier * building.cityHealth.toFloat() / city.getMaxHealth()
value += warModifier * building.cityStrength.toFloat() / (city.getStrength() + 3) // The + 3 here is to reduce the priority of building walls immedietly
value += warModifier * building.cityHealth.toFloat() / city.getMaxHealth() * personality.inverseModifierFocus(PersonalityValue.Aggressive, .3f)
value += warModifier * building.cityStrength.toFloat() / (city.getStrength() + 3) * personality.inverseModifierFocus(PersonalityValue.Aggressive, .3f) // The + 3 here is to reduce the priority of building walls immedietly
for (experienceUnique in building.getMatchingUniques(UniqueType.UnitStartingExperience, cityState)) {
var modifier = experienceUnique.params[1].toFloat() / 5
@ -346,7 +346,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions) {
if (civInfo.wantsToFocusOn(stat))
buildingStats[stat] *= 2f
buildingStats[stat] *= personality.scaledFocus(PersonalityValue[stat])
buildingStats[stat] *= personality.modifierFocus(PersonalityValue[stat], .5f)
}
return Automation.rankStatsValue(civInfo.getPersonality().scaleStats(buildingStats.clone(), .3f), civInfo)

View File

@ -18,18 +18,18 @@ object DeclareWarPlanEvaluator {
* This style of declaring war favors fighting stronger civilizations.
* @return The movtivation of the plan. If it is > 0 then we can declare the war.
*/
fun evaluateTeamWarPlan(civInfo: Civilization, target: Civilization, teamCiv: Civilization, givenMotivation: Int?): Int {
fun evaluateTeamWarPlan(civInfo: Civilization, target: Civilization, teamCiv: Civilization, givenMotivation: Float?): Float {
val teamCivDiplo = civInfo.getDiplomacyManager(teamCiv)!!
if (civInfo.getPersonality()[PersonalityValue.DeclareWar] == 0f) return -1000
if (teamCivDiplo.isRelationshipLevelLT(RelationshipLevel.Neutral)) return -1000
if (civInfo.getPersonality()[PersonalityValue.DeclareWar] == 0f) return -1000f
if (teamCivDiplo.isRelationshipLevelLT(RelationshipLevel.Neutral)) return -1000f
var motivation = givenMotivation
?: MotivationToAttackAutomation.hasAtLeastMotivationToAttack(civInfo, target, 0)
?: MotivationToAttackAutomation.hasAtLeastMotivationToAttack(civInfo, target, 0f)
if (teamCivDiplo.isRelationshipLevelEQ(RelationshipLevel.Neutral)) motivation -= 5
if (teamCivDiplo.isRelationshipLevelEQ(RelationshipLevel.Neutral)) motivation -= 5f
// Make sure that they can actually help us with the target
if (!teamCiv.threatManager.getNeighboringCivilizations().contains(target)) {
motivation -= 40
motivation -= 40f
}
val civForce = civInfo.getStatForRanking(RankingType.Force)
@ -46,11 +46,11 @@ object DeclareWarPlanEvaluator {
if (civForce + teamCivForce < targetForce * multiplier) {
// We are weaker then them even with our combined forces
// If they have twice our combined force we will have -30 motivation
motivation -= (30 * ((targetForce * multiplier) / (teamCivForce + civForce) - 1)).toInt()
motivation -= 30 * ((targetForce * multiplier) / (teamCivForce + civForce) - 1)
} else if (civForce + teamCivForce > targetForce * 2) {
// Why gang up on such a weaker enemy when we can declare war ourselves?
// If our combined force is twice their force we will have -20 motivation
motivation -= (20 * ((civForce + teamCivForce) / targetForce * 2) - 1).toInt()
motivation -= 20 * ((civForce + teamCivForce) / targetForce * 2) - 1
}
val civScore = civInfo.getStatForRanking(RankingType.Score)
@ -59,9 +59,9 @@ object DeclareWarPlanEvaluator {
if (teamCivScore > civScore * 1.4f && teamCivScore >= targetCivScore) {
// If teamCiv has more score than us and the target they are likely in a good position already
motivation -= (20 * ((teamCivScore / (civScore * 1.4f)) - 1)).toInt()
motivation -= 20 * ((teamCivScore / (civScore * 1.4f)) - 1)
}
return motivation - 20
return motivation - 20f
}
/**
@ -70,20 +70,20 @@ object DeclareWarPlanEvaluator {
* Favors protecting allies.
* @return The movtivation of the plan. If it is > 0 then we can declare the war.
*/
fun evaluateJoinWarPlan(civInfo: Civilization, target: Civilization, civToJoin: Civilization, givenMotivation: Int?): Int {
fun evaluateJoinWarPlan(civInfo: Civilization, target: Civilization, civToJoin: Civilization, givenMotivation: Float?): Float {
val thirdCivDiplo = civInfo.getDiplomacyManager(civToJoin)!!
if (civInfo.getPersonality()[PersonalityValue.DeclareWar] == 0f) return -1000
if (thirdCivDiplo.isRelationshipLevelLE(RelationshipLevel.Favorable)) return -1000
if (civInfo.getPersonality()[PersonalityValue.DeclareWar] == 0f) return -1000f
if (thirdCivDiplo.isRelationshipLevelLE(RelationshipLevel.Favorable)) return -1000f
var motivation = givenMotivation
?: MotivationToAttackAutomation.hasAtLeastMotivationToAttack(civInfo, target, 0)
?: MotivationToAttackAutomation.hasAtLeastMotivationToAttack(civInfo, target, 0f)
// We need to be able to trust the thirdCiv at least somewhat
if (thirdCivDiplo.diplomaticStatus != DiplomaticStatus.DefensivePact &&
thirdCivDiplo.opinionOfOtherCiv() + motivation * 2 < 80) {
motivation -= 80 - (thirdCivDiplo.opinionOfOtherCiv() + motivation * 2).toInt()
motivation -= 80f - thirdCivDiplo.opinionOfOtherCiv() + motivation * 2
}
if (!civToJoin.threatManager.getNeighboringCivilizations().contains(target)) {
motivation -= 20
motivation -= 20f
}
val targetForce = target.getStatForRanking(RankingType.Force) - 0.8f * target.getCivsAtWarWith().sumOf { it.getStatForRanking(RankingType.Force) }.coerceAtLeast(100)
@ -93,7 +93,7 @@ object DeclareWarPlanEvaluator {
val civToJoinForce = (civToJoin.getStatForRanking(RankingType.Force) - 0.8f * civToJoin.getCivsAtWarWith().sumOf { it.getStatForRanking(RankingType.Force) }).coerceAtLeast(100f)
if (civToJoinForce < targetForce / 2) {
// Make sure that there is no wrap around
motivation -= (10 * (targetForce / civToJoinForce)).toInt().coerceIn(-1000, 1000)
motivation -= 10 * (targetForce / civToJoinForce).coerceIn(-1000f, 1000f)
}
// A higher motivation means that we can be riskier
@ -105,7 +105,7 @@ object DeclareWarPlanEvaluator {
else -> 0.8f
}
if (civToJoinForce + civForce < targetForce * multiplier) {
motivation -= (20 * (targetForce * multiplier) / (civToJoinForce + civForce)).toInt().coerceIn(-1000, 1000)
motivation -= 20 * (targetForce * multiplier) / (civToJoinForce + civForce).coerceIn(-1000f, 1000f)
}
return motivation - 15
@ -116,11 +116,11 @@ object DeclareWarPlanEvaluator {
*
* @return The movtivation of the plan. If it is > 0 then we can declare the war.
*/
fun evaluateJoinOurWarPlan(civInfo: Civilization, target: Civilization, civToJoin: Civilization, givenMotivation: Int?): Int {
if (civInfo.getDiplomacyManager(civToJoin)!!.isRelationshipLevelLT(RelationshipLevel.Favorable)) return -1000
var motivation = givenMotivation ?: 0
fun evaluateJoinOurWarPlan(civInfo: Civilization, target: Civilization, civToJoin: Civilization, givenMotivation: Float?): Float {
if (civInfo.getDiplomacyManager(civToJoin)!!.isRelationshipLevelLT(RelationshipLevel.Favorable)) return -1000f
var motivation = givenMotivation ?: 0f
if (!civToJoin.threatManager.getNeighboringCivilizations().contains(target)) {
motivation -= 50
motivation -= 50f
}
val targetForce = target.getStatForRanking(RankingType.Force)
@ -128,10 +128,10 @@ object DeclareWarPlanEvaluator {
// They need to be at least half the targets size
val thirdCivForce = (civToJoin.getStatForRanking(RankingType.Force) - 0.8f * civToJoin.getCivsAtWarWith().sumOf { it.getStatForRanking(RankingType.Force) }).coerceAtLeast(100f)
motivation += (20 * thirdCivForce / targetForce.toFloat()).toInt().coerceAtMost(40)
motivation += 20 * thirdCivForce / targetForce.toFloat().coerceAtMost(40f)
// If we have less relative force then the target then we have more motivation to accept
motivation += (30 * (1 - (civForce / targetForce.toFloat()))).toInt().coerceIn(-30, 30)
motivation += 30 * (1 - (civForce / targetForce.toFloat())).coerceIn(-30f, 30f)
return motivation - 20
}
@ -142,15 +142,15 @@ object DeclareWarPlanEvaluator {
*
* @return The movtivation of the plan. If it is > 0 then we can declare the war.
*/
fun evaluateDeclareWarPlan(civInfo: Civilization, target: Civilization, givenMotivation: Int?): Int {
if (civInfo.getPersonality()[PersonalityValue.DeclareWar] == 0f) return -1000
fun evaluateDeclareWarPlan(civInfo: Civilization, target: Civilization, givenMotivation: Float?): Float {
if (civInfo.getPersonality()[PersonalityValue.DeclareWar] == 0f) return -1000f
val motivation = givenMotivation
?: MotivationToAttackAutomation.hasAtLeastMotivationToAttack(civInfo, target, 0)
?: MotivationToAttackAutomation.hasAtLeastMotivationToAttack(civInfo, target, 0f)
val diploManager = civInfo.getDiplomacyManager(target)!!
if (diploManager.hasFlag(DiplomacyFlags.WaryOf) && diploManager.getFlag(DiplomacyFlags.WaryOf) < 0) {
val turnsToPlan = (10 - (motivation / 10)).coerceAtLeast(3)
val turnsToPlan = (10 - (motivation / 10)).coerceAtLeast(3f)
val turnsToWait = turnsToPlan + diploManager.getFlag(DiplomacyFlags.WaryOf)
return motivation - turnsToWait * 3
}
@ -163,14 +163,14 @@ object DeclareWarPlanEvaluator {
*
* @return The motivation of the plan. If it is > 0 then we can start planning the war.
*/
fun evaluateStartPreparingWarPlan(civInfo: Civilization, target: Civilization, givenMotivation: Int?): Int {
fun evaluateStartPreparingWarPlan(civInfo: Civilization, target: Civilization, givenMotivation: Float?): Float {
val motivation = givenMotivation
?: MotivationToAttackAutomation.hasAtLeastMotivationToAttack(civInfo, target, 0)
?: MotivationToAttackAutomation.hasAtLeastMotivationToAttack(civInfo, target, 0f)
// TODO: We use negative values in WaryOf for now so that we aren't adding any extra fields to the save file
// This will very likely change in the future and we will want to build upon it
val diploManager = civInfo.getDiplomacyManager(target)!!
if (diploManager.hasFlag(DiplomacyFlags.WaryOf)) return 0
if (diploManager.hasFlag(DiplomacyFlags.WaryOf)) return 0f
return motivation - 15
}

View File

@ -15,7 +15,7 @@ object DeclareWarTargetAutomation {
* Chooses a target civilization along with a plan of attack.
* Note that this doesn't guarantee that we will declare war on them immediatly, or that we will end up declaring war at all.
*/
fun chooseDeclareWarTarget(civInfo: Civilization, civAttackMotivations: List<Pair<Civilization, Int>>) {
fun chooseDeclareWarTarget(civInfo: Civilization, civAttackMotivations: List<Pair<Civilization, Float>>) {
val highestValueTargets = civAttackMotivations.sortedByDescending { it.first.getStatForRanking(RankingType.Score) }
for (target in highestValueTargets) {
@ -27,7 +27,7 @@ object DeclareWarTargetAutomation {
/**
* Determines a war plan against this [target] and executes it if able.
*/
private fun tryDeclareWarWithPlan(civInfo: Civilization, target: Civilization, motivation: Int): Boolean {
private fun tryDeclareWarWithPlan(civInfo: Civilization, target: Civilization, motivation: Float): Boolean {
if (!target.isCityState()) {
if (motivation > 5 && tryTeamWar(civInfo, target, motivation)) return true
@ -46,7 +46,7 @@ object DeclareWarTargetAutomation {
* The safest option for war is to invite a new ally to join the war with us.
* Together we are stronger and are more likely to take down bigger threats.
*/
private fun tryTeamWar(civInfo: Civilization, target: Civilization, motivation: Int): Boolean {
private fun tryTeamWar(civInfo: Civilization, target: Civilization, motivation: Float): Boolean {
val potentialAllies = civInfo.getDiplomacyManager(target)!!.getCommonKnownCivs()
.filter {
it.isMajorCiv()
@ -74,7 +74,7 @@ object DeclareWarTargetAutomation {
/**
* The next safest aproach is to join an existing war on the side of an ally that is already at war with [target].
*/
private fun tryJoinWar(civInfo: Civilization, target: Civilization, motivation: Int): Boolean {
private fun tryJoinWar(civInfo: Civilization, target: Civilization, motivation: Float): Boolean {
val potentialAllies = civInfo.getDiplomacyManager(target)!!.getCommonKnownCivs()
.filter {
it.isMajorCiv()
@ -102,7 +102,7 @@ object DeclareWarTargetAutomation {
/**
* Lastly, if our motivation is high enough and we don't have any better plans then lets just declare war.
*/
private fun declareWar(civInfo: Civilization, target: Civilization, motivation: Int): Boolean {
private fun declareWar(civInfo: Civilization, target: Civilization, motivation: Float): Boolean {
if (DeclareWarPlanEvaluator.evaluateDeclareWarPlan(civInfo, target, motivation) > 0) {
civInfo.getDiplomacyManager(target)!!.declareWar()
return true
@ -113,7 +113,7 @@ object DeclareWarTargetAutomation {
/**
* Slightly safter is to silently plan an invasion and declare war later.
*/
private fun prepareWar(civInfo: Civilization, target: Civilization, motivation: Int): Boolean {
private fun prepareWar(civInfo: Civilization, target: Civilization, motivation: Float): Boolean {
// TODO: We use negative values in WaryOf for now so that we aren't adding any extra fields to the save file
// This will very likely change in the future and we will want to build upon it
val diploManager = civInfo.getDiplomacyManager(target)!!

View File

@ -8,6 +8,7 @@ import com.unciv.logic.civilization.AlertType
import com.unciv.logic.civilization.Civilization
import com.unciv.logic.civilization.PopupAlert
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
import com.unciv.logic.civilization.diplomacy.RelationshipLevel
import com.unciv.logic.trade.TradeEvaluation
@ -33,7 +34,8 @@ object DiplomacyAutomation {
.sortedByDescending { it.getDiplomacyManager(civInfo)!!.relationshipLevel() }.toList()
for (otherCiv in civsThatWeCanDeclareFriendshipWith) {
// Default setting is 2, this will be changed according to different civ.
if ((1..10).random() <= 2 && wantsToSignDeclarationOfFrienship(civInfo, otherCiv)) {
if ((1..10).random() <= 2 * civInfo.getPersonality().modifierFocus(PersonalityValue.Diplomacy, .5f)
&& wantsToSignDeclarationOfFrienship(civInfo, otherCiv)) {
otherCiv.popupAlerts.add(PopupAlert(AlertType.DeclarationOfFriendship, civInfo.civName))
}
}
@ -53,7 +55,12 @@ object DiplomacyAutomation {
val allAliveCivs = allCivs - deadCivs
// Motivation should be constant as the number of civs changes
var motivation = diploManager.opinionOfOtherCiv().toInt() - 40
var motivation = diploManager.opinionOfOtherCiv() - 40f
// Warmongerers don't make good allies
if (diploManager.hasModifier(DiplomaticModifiers.WarMongerer)) {
motivation -= diploManager.getModifier(DiplomaticModifiers.WarMongerer) * civInfo.getPersonality().modifierFocus(PersonalityValue.Diplomacy, .5f)
}
// If the other civ is stronger than we are compelled to be nice to them
// If they are too weak, then thier friendship doesn't mean much to us
@ -65,13 +72,13 @@ object DiplomacyAutomation {
}
// Try to ally with a fourth of the civs in play
val civsToAllyWith = 0.25f * allAliveCivs
val civsToAllyWith = 0.25f * allAliveCivs * civInfo.getPersonality().modifierFocus(PersonalityValue.Diplomacy, .3f)
if (numOfFriends < civsToAllyWith) {
// Goes from 10 to 0 once the civ gets 1/4 of all alive civs as friends
motivation += (10 - 10 * (numOfFriends / civsToAllyWith)).toInt()
motivation += (10 - 10 * numOfFriends / civsToAllyWith)
} else {
// Goes from 0 to -120 as the civ gets more friends, offset by civsToAllyWith
motivation -= (120f * (numOfFriends - civsToAllyWith) / (knownCivs - civsToAllyWith)).toInt()
motivation -= (120f * (numOfFriends - civsToAllyWith) / (knownCivs - civsToAllyWith))
}
// The more friends they have the less we should want to sign friendship (To promote teams)
@ -88,7 +95,7 @@ object DiplomacyAutomation {
// Wait to declare frienships until more civs
// Goes from -30 to 0 when we know 75% of allCivs
val civsToKnow = 0.75f * allAliveCivs
motivation -= ((civsToKnow - knownCivs) / civsToKnow * 30f).toInt().coerceAtLeast(0)
motivation -= ((civsToKnow - knownCivs) / civsToKnow * 30f).coerceAtLeast(0f)
// If they are the only non-friendly civ near us then they are the only civ to attack and expand into
if (civInfo.threatManager.getNeighboringCivilizations().none {
@ -97,7 +104,7 @@ object DiplomacyAutomation {
})
motivation -= 20
motivation -= hasAtLeastMotivationToAttack(civInfo, otherCiv, motivation / 2) * 2
motivation -= hasAtLeastMotivationToAttack(civInfo, otherCiv, motivation / 2f) * 2
return motivation > 0
}
@ -139,7 +146,8 @@ object DiplomacyAutomation {
// Being able to see their cities can give us an advantage later on, especially with espionage enabled
if (otherCiv.cities.count { !it.getCenterTile().isVisible(civInfo) } < otherCiv.cities.count() * .8f)
return true
if (hasAtLeastMotivationToAttack(civInfo, otherCiv, (diploManager.opinionOfOtherCiv() / 2).toInt()) > 0)
if (hasAtLeastMotivationToAttack(civInfo, otherCiv,
diploManager.opinionOfOtherCiv() * civInfo.getPersonality().modifierFocus(PersonalityValue.Commerce, .3f) / 2) > 0)
return false
return true
}
@ -157,7 +165,7 @@ object DiplomacyAutomation {
for (otherCiv in canSignResearchAgreementCiv) {
// Default setting is 5, this will be changed according to different civ.
if ((1..10).random() <= 5) continue
if ((1..10).random() <= 5 * civInfo.getPersonality().modifierFocus(PersonalityValue.Science, .3f)) continue
val tradeLogic = TradeLogic(civInfo, otherCiv)
val cost = civInfo.diplomacyFunctions.getResearchAgreementCost(otherCiv)
tradeLogic.currentTrade.ourOffers.add(TradeOffer(Constants.researchAgreement, TradeType.Treaty, cost))
@ -174,13 +182,13 @@ object DiplomacyAutomation {
.filter {
civInfo.diplomacyFunctions.canSignDefensivePactWith(it)
&& !civInfo.getDiplomacyManager(it)!!.hasFlag(DiplomacyFlags.DeclinedDefensivePact)
&& civInfo.getDiplomacyManager(it)!!.opinionOfOtherCiv() < 70f
&& civInfo.getDiplomacyManager(it)!!.opinionOfOtherCiv() < 70f * civInfo.getPersonality().inverseModifierFocus(PersonalityValue.Aggressive, .2f)
&& !isTradeBeingOffered(civInfo, it, Constants.defensivePact)
}
for (otherCiv in canSignDefensivePactCiv) {
// Default setting is 3, this will be changed according to different civ.
if ((1..10).random() <= 7) continue
if ((1..10).random() <= 7 * civInfo.getPersonality().inverseModifierFocus(PersonalityValue.Loyal, .3f)) continue
if (wantsToSignDefensivePact(civInfo, otherCiv)) {
//todo: Add more in depth evaluation here
val tradeLogic = TradeLogic(civInfo, otherCiv)
@ -198,7 +206,7 @@ object DiplomacyAutomation {
fun wantsToSignDefensivePact(civInfo: Civilization, otherCiv: Civilization): Boolean {
val diploManager = civInfo.getDiplomacyManager(otherCiv)!!
if (diploManager.hasFlag(DiplomacyFlags.DeclinedDefensivePact)) return false
if (diploManager.opinionOfOtherCiv() < 60f) return false
if (diploManager.opinionOfOtherCiv() < 65f * civInfo.getPersonality().inverseModifierFocus(PersonalityValue.Aggressive, .3f)) return false
val commonknownCivs = diploManager.getCommonKnownCivs()
for (thirdCiv in commonknownCivs) {
// If they have bad relations with any of our friends, don't consider it
@ -222,28 +230,33 @@ object DiplomacyAutomation {
val allAliveCivs = allCivs - deadCivs
// We have to already be at RelationshipLevel.Ally, so we must have 80 oppinion of them
var motivation = diploManager.opinionOfOtherCiv().toInt() - 80
var motivation = diploManager.opinionOfOtherCiv() - 80
// Warmongerers don't make good allies
if (diploManager.hasModifier(DiplomaticModifiers.WarMongerer)) {
motivation -= diploManager.getModifier(DiplomaticModifiers.WarMongerer) * civInfo.getPersonality().modifierFocus(PersonalityValue.Diplomacy, .5f)
}
// If they are stronger than us, then we value it a lot more
// If they are weaker than us, then we don't value it
motivation += when (Automation.threatAssessment(civInfo, otherCiv)) {
ThreatLevel.VeryHigh -> 10
ThreatLevel.High -> 5
ThreatLevel.Low -> -5
ThreatLevel.VeryLow -> -10
ThreatLevel.Low -> -3
ThreatLevel.VeryLow -> -7
else -> 0
}
// If they have a defensive pact with another civ then we would get drawn into thier battles as well
// If they have a defensive pact with another civ then we would get drawn into their battles as well
motivation -= 15 * otherCivNonOverlappingDefensivePacts
// Becomre more desperate as we have more wars
motivation += civInfo.diplomacy.values.count { it.otherCiv().isMajorCiv() && it.diplomaticStatus == DiplomaticStatus.War } * 5
// Try to have a defensive pact with 1/5 of all civs
val civsToAllyWith = 0.20f * allAliveCivs
// Goes from 0 to -50 as the civ gets more allies, offset by civsToAllyWith
motivation -= (50f * (defensivePacts - civsToAllyWith) / (allAliveCivs - civsToAllyWith)).coerceAtMost(0f).toInt()
val civsToAllyWith = 0.20f * allAliveCivs * civInfo.getPersonality().modifierFocus(PersonalityValue.Diplomacy, .5f)
// Goes from 0 to -40 as the civ gets more allies, offset by civsToAllyWith
motivation -= (40f * (defensivePacts - civsToAllyWith) / (allAliveCivs - civsToAllyWith)).coerceAtMost(0f)
return motivation > 0
}
@ -270,8 +283,8 @@ object DiplomacyAutomation {
if (targetCivs.none()) return
val targetCivsWithMotivation: List<Pair<Civilization, Int>> = targetCivs
.map { Pair(it, hasAtLeastMotivationToAttack(civInfo, it, 0)) }
val targetCivsWithMotivation: List<Pair<Civilization, Float>> = targetCivs
.map { Pair(it, hasAtLeastMotivationToAttack(civInfo, it, 0f)) }
.filter { it.second > 0 }.toList()
DeclareWarTargetAutomation.chooseDeclareWarTarget(civInfo, targetCivsWithMotivation)
@ -293,7 +306,7 @@ object DiplomacyAutomation {
.filter { it.tradeRequests.none { tradeRequest -> tradeRequest.requestingCiv == civInfo.civName && tradeRequest.trade.isPeaceTreaty() } }
for (enemy in enemiesCiv) {
if (hasAtLeastMotivationToAttack(civInfo, enemy, 10) >= 10) {
if (hasAtLeastMotivationToAttack(civInfo, enemy, 10f) >= 10) {
// We can still fight. Refuse peace.
continue
}
@ -336,7 +349,8 @@ object DiplomacyAutomation {
internal fun askForHelp(civInfo: Civilization) {
if (!civInfo.isAtWar() || civInfo.cities.isEmpty() || civInfo.diplomacy.isEmpty()) return
for (enemyCiv in civInfo.getCivsAtWarWith().sortedByDescending { it.getStatForRanking(RankingType.Force) }) {
val enemyCivs = civInfo.getCivsAtWarWith().filter { it.isMajorCiv() }.sortedByDescending { it.getStatForRanking(RankingType.Force) }
for (enemyCiv in enemyCivs) {
val potentialAllies = enemyCiv.threatManager.getNeighboringCivilizations()
.filter {
civInfo.knows(it) && !it.isAtWarWith(enemyCiv)

View File

@ -12,6 +12,7 @@ import com.unciv.logic.map.BFS
import com.unciv.logic.map.MapPathing
import com.unciv.logic.map.tile.Tile
import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.nation.PersonalityValue
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.ruleset.unit.BaseUnit
import com.unciv.ui.screens.victoryscreen.RankingType
@ -20,33 +21,38 @@ object MotivationToAttackAutomation {
/** Will return the motivation to attack, but might short circuit if the value is guaranteed to
* be lower than `atLeast`. So any values below `atLeast` should not be used for comparison. */
fun hasAtLeastMotivationToAttack(civInfo: Civilization, targetCiv: Civilization, atLeast: Int): Int {
fun hasAtLeastMotivationToAttack(civInfo: Civilization, targetCiv: Civilization, atLeast: Float): Float {
val diplomacyManager = civInfo.getDiplomacyManager(targetCiv)!!
val personality = civInfo.getPersonality()
val targetCitiesWithOurCity = civInfo.threatManager.getNeighboringCitiesOfOtherCivs().filter { it.second.civ == targetCiv }.toList()
val targetCities = targetCitiesWithOurCity.map { it.second }
if (targetCitiesWithOurCity.isEmpty()) return 0
if (targetCitiesWithOurCity.isEmpty()) return 0f
if (targetCities.all { hasNoUnitsThatCanAttackCityWithoutDying(civInfo, it) })
return 0
return 0f
val baseForce = 100f
val ourCombatStrength = calculateSelfCombatStrength(civInfo, baseForce)
val theirCombatStrength = calculateCombatStrengthWithProtectors(targetCiv, baseForce, civInfo)
val modifiers:MutableList<Pair<String, Int>> = mutableListOf()
modifiers.add(Pair("Base motivation", -15))
val modifiers: MutableList<Pair<String, Float>> = mutableListOf()
modifiers.add(Pair("Relative combat strength", getCombatStrengthModifier(ourCombatStrength, theirCombatStrength + 0.8f * civInfo.threatManager.getCombinedForceOfWarringCivs())))
// If our personality is to declare war more then we should have a higher base motivation (a negative number closer to 0)
modifiers.add(Pair("Base motivation", -(15f * personality.inverseModifierFocus(PersonalityValue.DeclareWar, 0.5f))))
modifiers.add(Pair("Relative combat strength", getCombatStrengthModifier(civInfo, ourCombatStrength, theirCombatStrength + 0.8f * civInfo.threatManager.getCombinedForceOfWarringCivs())))
// TODO: For now this will be a very high value because the AI can't handle multiple fronts, this should be changed later though
modifiers.add(Pair("Concurrent wars", -civInfo.getCivsAtWarWith().count { it.isMajorCiv() && it != targetCiv } * 20))
modifiers.add(Pair("Their concurrent wars", targetCiv.getCivsAtWarWith().count { it.isMajorCiv() } * 3))
modifiers.add(Pair("Concurrent wars", -civInfo.getCivsAtWarWith().count { it.isMajorCiv() && it != targetCiv } * 20f))
modifiers.add(Pair("Their concurrent wars", targetCiv.getCivsAtWarWith().count { it.isMajorCiv() } * 3f))
modifiers.add(Pair("Their allies", getDefensivePactAlliesScore(targetCiv, civInfo, baseForce, ourCombatStrength)))
if (civInfo.threatManager.getNeighboringCivilizations().none { it != targetCiv && it.isMajorCiv()
&& civInfo.getDiplomacyManager(it)!!.isRelationshipLevelLT(RelationshipLevel.Friend) })
modifiers.add(Pair("No other threats", 10))
modifiers.add(Pair("No other threats", 10f))
if (targetCiv.isMajorCiv()) {
val scoreRatioModifier = getScoreRatioModifier(targetCiv, civInfo)
@ -55,61 +61,65 @@ object MotivationToAttackAutomation {
modifiers.add(Pair("Relative technologies", getRelativeTechModifier(civInfo, targetCiv)))
if (civInfo.stats.getUnitSupplyDeficit() != 0) {
modifiers.add(Pair("Over unit supply", (civInfo.stats.getUnitSupplyDeficit() * 2).coerceAtMost(20)))
modifiers.add(Pair("Over unit supply", (civInfo.stats.getUnitSupplyDeficit() * 2f).coerceAtMost(20f)))
} else if (targetCiv.stats.getUnitSupplyDeficit() == 0 && !targetCiv.isCityState()) {
modifiers.add(Pair("Relative production", getProductionRatioModifier(civInfo, targetCiv)))
}
}
val minTargetCityDistance = targetCitiesWithOurCity.minOf { it.second.getCenterTile().aerialDistanceTo(it.first.getCenterTile()) }
// Defensive civs should avoid fighting civilizations that are farther away and don't pose a threat
modifiers.add(Pair("Far away cities", when {
minTargetCityDistance > 20 -> -10
minTargetCityDistance > 14 -> -8
minTargetCityDistance > 10 -> -3
else -> 0
}))
if (minTargetCityDistance < 6) modifiers.add(Pair("Close cities", 5))
minTargetCityDistance > 20 -> -10f
minTargetCityDistance > 14 -> -8f
minTargetCityDistance > 10 -> -3f
else -> 0f
} * personality.inverseModifierFocus(PersonalityValue.Aggressive, 0.2f)))
val diplomacyManager = civInfo.getDiplomacyManager(targetCiv)!!
// Defensive civs want to deal with potential nearby cities to protect themselves
if (minTargetCityDistance < 6)
modifiers.add(Pair("Close cities", 5f * personality.inverseModifierFocus(PersonalityValue.Aggressive, 1f)))
if (diplomacyManager.hasFlag(DiplomacyFlags.ResearchAgreement))
modifiers.add(Pair("Research Agreement", -5))
modifiers.add(Pair("Research Agreement", -5f * personality.modifierFocus(PersonalityValue.Loyal, .2f)
* personality.scaledFocus(PersonalityValue.Science) * personality.modifierFocus(PersonalityValue.Commerce, .3f)))
if (diplomacyManager.hasFlag(DiplomacyFlags.DeclarationOfFriendship))
modifiers.add(Pair("Declaration of Friendship", -10))
modifiers.add(Pair("Declaration of Friendship", -10f * personality.modifierFocus(PersonalityValue.Loyal, .5f)))
if (diplomacyManager.hasFlag(DiplomacyFlags.DefensivePact))
modifiers.add(Pair("Defensive Pact", -15))
modifiers.add(Pair("Defensive Pact", -15f * personality.modifierFocus(PersonalityValue.Loyal, .3f)))
modifiers.add(Pair("Relationship", getRelationshipModifier(diplomacyManager)))
if (diplomacyManager.hasFlag(DiplomacyFlags.Denunciation)) {
modifiers.add(Pair("Denunciation", 5))
modifiers.add(Pair("Denunciation", 5f * personality.inverseModifierFocus(PersonalityValue.Diplomacy, .5f)))
}
if (diplomacyManager.hasFlag(DiplomacyFlags.WaryOf) && diplomacyManager.getFlag(DiplomacyFlags.WaryOf) < 0) {
modifiers.add(Pair("PlanningAttack", -diplomacyManager.getFlag(DiplomacyFlags.WaryOf)))
// Completely defensive civs will plan defensively and have a 0 here
modifiers.add(Pair("PlanningAttack", -diplomacyManager.getFlag(DiplomacyFlags.WaryOf) * personality.scaledFocus(PersonalityValue.Aggressive) / 2))
} else {
val attacksPlanned = civInfo.diplomacy.values.count { it.hasFlag(DiplomacyFlags.WaryOf) && it.getFlag(DiplomacyFlags.WaryOf) < 0 }
modifiers.add(Pair("PlanningAttackAgainstOtherCivs", -attacksPlanned * 5))
modifiers.add(Pair("PlanningAttackAgainstOtherCivs", -attacksPlanned * 5f * personality.inverseModifierFocus(PersonalityValue.Aggressive, .5f)))
}
if (diplomacyManager.resourcesFromTrade().any { it.amount > 0 })
modifiers.add(Pair("Receiving trade resources", -5))
modifiers.add(Pair("Receiving trade resources", -8f * personality.modifierFocus(PersonalityValue.Commerce, .5f) * personality.modifierFocus(PersonalityValue.Loyal, .2f)))
// If their cities don't have any nearby cities that are also targets to us and it doesn't include their capital
// Then there cities are likely isolated and a good target.
if (targetCiv.getCapital(true) !in targetCities
&& targetCities.all { theirCity -> !theirCity.neighboringCities.any { it !in targetCities } }) {
modifiers.add(Pair("Isolated city", 15))
modifiers.add(Pair("Isolated city", 10f * personality.modifierFocus(PersonalityValue.Aggressive, .8f)))
}
if (targetCiv.isCityState()) {
modifiers.add(Pair("Protectors", -targetCiv.cityStateFunctions.getProtectorCivs().size * 3))
modifiers.add(Pair("Protectors", -targetCiv.cityStateFunctions.getProtectorCivs().size * 3f))
if (targetCiv.cityStateFunctions.getProtectorCivs().contains(civInfo))
modifiers.add(Pair("Under our protection", -15))
modifiers.add(Pair("Under our protection", -15 * personality.modifierFocus(PersonalityValue.Loyal, .8f)))
if (targetCiv.getAllyCiv() == civInfo.civName)
modifiers.add(Pair("Allied City-state", -20)) // There had better be a DAMN good reason
modifiers.add(Pair("Allied City-state", -20 * personality.modifierFocus(PersonalityValue.Loyal, .8f))) // There had better be a DAMN good reason
}
addWonderBasedMotivations(targetCiv, modifiers)
@ -117,8 +127,8 @@ object MotivationToAttackAutomation {
modifiers.add(Pair("War with allies", getAlliedWarMotivation(civInfo, targetCiv)))
// Purely for debugging, remove modifiers that don't have an effect
modifiers.removeAll { it.second == 0 }
var motivationSoFar = modifiers.sumOf { it.second }
modifiers.removeAll { it.second == 0f }
var motivationSoFar = modifiers.map { it.second }.sum()
// Short-circuit to avoid A-star
if (motivationSoFar < atLeast) return motivationSoFar
@ -145,69 +155,70 @@ object MotivationToAttackAutomation {
return ourCombatStrength
}
private fun addWonderBasedMotivations(otherCiv: Civilization, modifiers: MutableList<Pair<String, Int>>) {
private fun addWonderBasedMotivations(otherCiv: Civilization, modifiers: MutableList<Pair<String, Float>>) {
var wonderCount = 0
for (city in otherCiv.cities) {
val construction = city.cityConstructions.getCurrentConstruction()
if (construction is Building && construction.hasUnique(UniqueType.TriggersCulturalVictory))
modifiers.add(Pair("About to win", 15))
modifiers.add(Pair("About to win", 15f))
if (construction is BaseUnit && construction.hasUnique(UniqueType.AddInCapital))
modifiers.add(Pair("About to win", 15))
modifiers.add(Pair("About to win", 15f))
wonderCount += city.cityConstructions.getBuiltBuildings().count { it.isWonder }
}
// The more wonders they have, the more beneficial it is to conquer them
// Civs need an army to protect thier wonders which give the most score
if (wonderCount > 0)
modifiers.add(Pair("Owned Wonders", wonderCount))
modifiers.add(Pair("Owned Wonders", wonderCount.toFloat()))
}
/** If they are at war with our allies, then we should join in */
private fun getAlliedWarMotivation(civInfo: Civilization, otherCiv: Civilization): Int {
var alliedWarMotivation = 0
private fun getAlliedWarMotivation(civInfo: Civilization, otherCiv: Civilization): Float {
var alliedWarMotivation = 0f
for (thirdCiv in civInfo.getDiplomacyManager(otherCiv)!!.getCommonKnownCivs()) {
val thirdCivDiploManager = civInfo.getDiplomacyManager(thirdCiv)
if (thirdCivDiploManager!!.isRelationshipLevelGE(RelationshipLevel.Friend)
&& thirdCiv.isAtWarWith(otherCiv)
) {
if (thirdCiv.getDiplomacyManager(otherCiv)!!.hasFlag(DiplomacyFlags.Denunciation))
alliedWarMotivation += 2
alliedWarMotivation += if (thirdCivDiploManager.hasFlag(DiplomacyFlags.DefensivePact)) 15
else if (thirdCivDiploManager.isRelationshipLevelGT(RelationshipLevel.Friend)) 5
else 2
if (thirdCivDiploManager!!.isRelationshipLevelLT(RelationshipLevel.Friend)) continue
if (thirdCiv.getDiplomacyManager(otherCiv)!!.hasFlag(DiplomacyFlags.Denunciation))
alliedWarMotivation += 2f
if (thirdCiv.isAtWarWith(otherCiv)) {
alliedWarMotivation += if (thirdCivDiploManager.hasFlag(DiplomacyFlags.DefensivePact)) 15f
else if (thirdCivDiploManager.hasFlag(DiplomacyFlags.DeclarationOfFriendship)) 5f
else 2f
}
}
return alliedWarMotivation
return alliedWarMotivation * civInfo.getPersonality().modifierFocus(PersonalityValue.Loyal, .5f)
}
private fun getRelationshipModifier(diplomacyManager: DiplomacyManager): Int {
private fun getRelationshipModifier(diplomacyManager: DiplomacyManager): Float {
val relationshipModifier = when (diplomacyManager.relationshipIgnoreAfraid()) {
RelationshipLevel.Unforgivable -> 15
RelationshipLevel.Enemy -> 10
RelationshipLevel.Competitor -> 5
RelationshipLevel.Favorable -> -2
RelationshipLevel.Friend -> -5
RelationshipLevel.Ally -> -10 // this is so that ally + DoF is not too unbalanced -
RelationshipLevel.Unforgivable -> 15f
RelationshipLevel.Enemy -> 10f
RelationshipLevel.Competitor -> 5f
RelationshipLevel.Favorable -> -2f
RelationshipLevel.Friend -> -5f
RelationshipLevel.Ally -> -10f // this is so that ally + DoF is not too unbalanced -
// still possible for AI to declare war for isolated city
else -> 0
else -> 0f
}
return relationshipModifier
return relationshipModifier * diplomacyManager.civInfo.getPersonality().modifierFocus(PersonalityValue.Diplomacy, .3f)
}
private fun getRelativeTechModifier(civInfo: Civilization, otherCiv: Civilization): Int {
private fun getRelativeTechModifier(civInfo: Civilization, otherCiv: Civilization): Float {
val relativeTech = civInfo.getStatForRanking(RankingType.Technologies) - otherCiv.getStatForRanking(RankingType.Technologies)
val relativeTechModifier = when {
relativeTech > 6 -> 10
relativeTech > 3 -> 5
relativeTech > -3 -> 0
relativeTech > -6 -> -2
relativeTech > -9 -> -5
else -> -10
relativeTech > 6 -> 10f
relativeTech > 3 -> 5f
relativeTech > -3 -> 0f
relativeTech > -6 -> -2f
relativeTech > -9 -> -5f
else -> -10f
}
return relativeTechModifier
return relativeTechModifier * civInfo.getPersonality().modifierFocus(PersonalityValue.Science, .2f)
}
private fun getProductionRatioModifier(civInfo: Civilization, otherCiv: Civilization): Int {
private fun getProductionRatioModifier(civInfo: Civilization, otherCiv: Civilization): Float {
// If either of our Civs are suffering from a supply deficit, our army must be too large
// There is no easy way to check the raw production if a civ has a supply deficit
// We might try to divide the current production by the getUnitSupplyProductionPenalty()
@ -215,67 +226,69 @@ object MotivationToAttackAutomation {
val productionRatio = civInfo.getStatForRanking(RankingType.Production).toFloat() / otherCiv.getStatForRanking(RankingType.Production).toFloat()
val productionRatioModifier = when {
productionRatio > 2f -> 15
productionRatio > 1.5f -> 7
productionRatio > 1.2 -> 3
productionRatio > .8f -> 0
productionRatio > .5f -> -5
productionRatio > .25f -> -10
else -> -10
productionRatio > 2f -> 15f
productionRatio > 1.5f -> 7f
productionRatio > 1.2 -> 3f
productionRatio > .8f -> 0f
productionRatio > .5f -> -5f
productionRatio > .25f -> -10f
else -> -10f
}
return productionRatioModifier
return productionRatioModifier * civInfo.getPersonality().modifierFocus(PersonalityValue.Production, .2f)
}
private fun getScoreRatioModifier(otherCiv: Civilization, civInfo: Civilization): Int {
private fun getScoreRatioModifier(otherCiv: Civilization, civInfo: Civilization): Float {
// Civs with more score are more threatening to our victory
// Bias towards attacking civs with a high score and low military
// Bias against attacking civs with a low score and a high military
// Designed to mitigate AIs declaring war on weaker civs instead of their rivals
val scoreRatio = otherCiv.getStatForRanking(RankingType.Score).toFloat() / civInfo.getStatForRanking(RankingType.Score).toFloat()
val scoreRatioModifier = when {
scoreRatio > 2f -> 20
scoreRatio > 1.5f -> 15
scoreRatio > 1.25f -> 10
scoreRatio > 1f -> 2
scoreRatio > .8f -> 0
scoreRatio > .5f -> -2
scoreRatio > .25f -> -5
else -> -10
scoreRatio > 2f -> 15f
scoreRatio > 1.5f -> 10f
scoreRatio > 1.25f -> 5f
scoreRatio > 1f -> 2f
scoreRatio > .8f -> 0f
scoreRatio > .5f -> -2f
scoreRatio > .25f -> -5f
else -> -10f
}
return scoreRatioModifier
}
private fun getDefensivePactAlliesScore(otherCiv: Civilization, civInfo: Civilization, baseForce: Float, ourCombatStrength: Float): Int {
var theirAlliesValue = 0
private fun getDefensivePactAlliesScore(otherCiv: Civilization, civInfo: Civilization, baseForce: Float, ourCombatStrength: Float): Float {
var theirAlliesValue = 0f
for (thirdCiv in otherCiv.diplomacy.values.filter { it.hasFlag(DiplomacyFlags.DefensivePact) && it.otherCiv() != civInfo }) {
val thirdCivCombatStrengthRatio = otherCiv.getStatForRanking(RankingType.Force).toFloat() + baseForce / ourCombatStrength
theirAlliesValue += when {
thirdCivCombatStrengthRatio > 5 -> -15
thirdCivCombatStrengthRatio > 2.5 -> -10
thirdCivCombatStrengthRatio > 2 -> -8
thirdCivCombatStrengthRatio > 1.5 -> -5
thirdCivCombatStrengthRatio > .8 -> -2
else -> 0
thirdCivCombatStrengthRatio > 5 -> -15f
thirdCivCombatStrengthRatio > 2.5 -> -10f
thirdCivCombatStrengthRatio > 2 -> -8f
thirdCivCombatStrengthRatio > 1.5 -> -5f
thirdCivCombatStrengthRatio > .8 -> -2f
else -> 0f
}
}
return theirAlliesValue
}
private fun getCombatStrengthModifier(ourCombatStrength: Float, theirCombatStrength: Float): Int {
private fun getCombatStrengthModifier(civInfo: Civilization, ourCombatStrength: Float, theirCombatStrength: Float): Float {
val combatStrengthRatio = ourCombatStrength / theirCombatStrength
val combatStrengthModifier = when {
combatStrengthRatio > 5f -> 30
combatStrengthRatio > 4f -> 20
combatStrengthRatio > 3f -> 15
combatStrengthRatio > 2f -> 10
combatStrengthRatio > 1.5f -> 5
combatStrengthRatio > .8f -> 0
combatStrengthRatio > .6f -> -5
combatStrengthRatio > .4f -> -15
combatStrengthRatio > .2f -> -20
else -> -20
combatStrengthRatio > 5f -> 20f
combatStrengthRatio > 4f -> 15f
combatStrengthRatio > 3f -> 12f
combatStrengthRatio > 2f -> 10f
combatStrengthRatio > 1.8f -> 8f
combatStrengthRatio > 1.6f -> 6f
combatStrengthRatio > 1.4f -> 4f
combatStrengthRatio > 1.2f -> 2f
combatStrengthRatio > .8f -> 0f
combatStrengthRatio > .6f -> -5f
combatStrengthRatio > .4f -> -15f
else -> -20f
}
return combatStrengthModifier
return combatStrengthModifier * civInfo.getPersonality().modifierFocus(PersonalityValue.Military, .2f)
}
private fun hasNoUnitsThatCanAttackCityWithoutDying(civInfo: Civilization, theirCity: City) = civInfo.units.getCivUnits().filter { it.isMilitary() }.none {
@ -295,7 +308,7 @@ object MotivationToAttackAutomation {
*
* @return The motivation ranging from -30 to around +10
*/
private fun getAttackPathsModifier(civInfo: Civilization, otherCiv: Civilization, targetCitiesWithOurCity: List<Pair<City, City>>): Int {
private fun getAttackPathsModifier(civInfo: Civilization, otherCiv: Civilization, targetCitiesWithOurCity: List<Pair<City, City>>): Float {
fun isTileCanMoveThrough(civInfo: Civilization, tile: Tile): Boolean {
val owner = tile.getOwner()
@ -308,12 +321,12 @@ object MotivationToAttackAutomation {
}
val attackPaths: MutableList<List<Tile>> = mutableListOf()
var attackPathModifiers: Int = -3
var attackPathModifiers: Float = -3f
// For each city, we want to calculate if there is an attack path to the enemy
for (attacksGroupedByCity in targetCitiesWithOurCity.groupBy { it.first }) {
val cityToAttackFrom = attacksGroupedByCity.key
var cityAttackValue = 0
var cityAttackValue = 0f
// We only want to calculate the best attack path and use it's value
// Land routes are clearly better than sea routes
@ -321,7 +334,7 @@ object MotivationToAttackAutomation {
val landAttackPath = MapPathing.getConnection(civInfo, cityToAttackFrom.getCenterTile(), cityToAttack.getCenterTile(), ::isLandTileCanMoveThrough)
if (landAttackPath != null && landAttackPath.size < 16) {
attackPaths.add(landAttackPath)
cityAttackValue = 3
cityAttackValue = 3f
break
}
@ -341,7 +354,7 @@ object MotivationToAttackAutomation {
val reachableEnemyCitiesBfs = BFS(civInfo.getCapital(true)!!.getCenterTile()) { isTileCanMoveThrough(civInfo, it) }
reachableEnemyCitiesBfs.stepToEnd()
val reachableEnemyCities = otherCiv.cities.filter { reachableEnemyCitiesBfs.hasReachedTile(it.getCenterTile()) }
if (reachableEnemyCities.isEmpty()) return -50 // Can't even reach the enemy city, no point in war.
if (reachableEnemyCities.isEmpty()) return -50f // Can't even reach the enemy city, no point in war.
val minAttackDistance = reachableEnemyCities.minOf { reachableEnemyCitiesBfs.getPathTo(it.getCenterTile()).count() }
// Longer attack paths are worse, but if the attack path is too far away we shouldn't completely discard the possibility

View File

@ -468,19 +468,18 @@ object NextTurnAutomation {
if (civInfo.cities.none()) return
if (civInfo.getHappiness() <= civInfo.cities.size) return
if (civInfo.units.getCivUnits().any { it.hasUnique(UniqueType.FoundCity) }) return
if (civInfo.cities.any {
val currentConstruction = it.cityConstructions.getCurrentConstruction()
currentConstruction is BaseUnit && currentConstruction.isCityFounder()
}) return
val settlerUnits = civInfo.gameInfo.ruleset.units.values
.filter { it.isCityFounder() && it.isBuildable(civInfo) &&
personality.getMatchingUniques(UniqueType.WillNotBuild, StateForConditionals(civInfo))
.none { unique -> it.matchesFilter(unique.params[0]) } }
if (settlerUnits.isEmpty()) return
if (!civInfo.units.getCivUnits().none { it.hasUnique(UniqueType.FoundCity) }) return
if (civInfo.cities.any {
val currentConstruction = it.cityConstructions.getCurrentConstruction()
currentConstruction is BaseUnit && currentConstruction.isCityFounder()
}) return
if (civInfo.units.getCivUnits().none { it.isMilitary() }) return // We need someone to defend him first
if (civInfo.units.getCivUnits().count { it.isMilitary() } < civInfo.cities.size) return // We need someone to defend them first
val workersBuildableForThisCiv = civInfo.gameInfo.ruleset.units.values.any {
it.hasUnique(UniqueType.BuildImprovements)

View File

@ -37,7 +37,7 @@ object UseGoldAutomation {
private fun useGoldForCityStates(civ: Civilization) {
// RARE EDGE CASE: If you ally with a city-state, you may reveal more map that includes ANOTHER civ!
// So if we don't lock this list, we may later discover that there are more known civs, concurrent modification exception!
val knownCityStates = civ.getKnownCivs().filter { it.isCityState() && MotivationToAttackAutomation.hasAtLeastMotivationToAttack(civ, it, 0) <= 0 }.toList()
val knownCityStates = civ.getKnownCivs().filter { it.isCityState() && MotivationToAttackAutomation.hasAtLeastMotivationToAttack(civ, it, 0f) <= 0 }.toList()
// canBeMarriedBy checks actual cost, but it can't be below 500*speedmodifier, and the later check is expensive
if (civ.gold >= 330 && civ.getHappiness() > 0 && civ.hasUnique(UniqueType.CityStateCanBeBoughtForGold)) {

View File

@ -7,6 +7,7 @@ import com.unciv.logic.civilization.DiplomacyAction
import com.unciv.logic.civilization.NotificationCategory
import com.unciv.logic.civilization.NotificationIcon
import com.unciv.logic.civilization.PopupAlert
import com.unciv.models.ruleset.nation.PersonalityValue
import com.unciv.models.ruleset.unique.UniqueTriggerActivation
import com.unciv.models.ruleset.unique.UniqueType
@ -186,11 +187,11 @@ object DeclareWar {
if (thirdCiv.isAtWarWith(otherCiv) && !thirdCiv.isAtWarWith(civInfo)) {
// Improve our relations
if (thirdCiv.isCityState()) thirdCiv.getDiplomacyManager(civInfo)!!.addInfluence(10f)
else thirdCiv.getDiplomacyManager(civInfo)!!.addModifier(DiplomaticModifiers.SharedEnemy, 5f)
else thirdCiv.getDiplomacyManager(civInfo)!!.addModifier(DiplomaticModifiers.SharedEnemy, 5f * civInfo.getPersonality().modifierFocus(PersonalityValue.Loyal, .3f))
} else if (thirdCiv.isAtWarWith(civInfo)) {
// Improve their relations
if (thirdCiv.isCityState()) thirdCiv.getDiplomacyManager(otherCiv)!!.addInfluence(10f)
else thirdCiv.getDiplomacyManager(otherCiv)!!.addModifier(DiplomaticModifiers.SharedEnemy, 5f)
else thirdCiv.getDiplomacyManager(otherCiv)!!.addModifier(DiplomaticModifiers.SharedEnemy, 5f * civInfo.getPersonality().modifierFocus(PersonalityValue.Loyal, .3f))
}
}
}
@ -220,12 +221,12 @@ object DeclareWar {
val diploManager = knownCiv.getDiplomacyManager(diplomacyManager.civInfo)!!
if (betrayedFriendship) {
val amount = if (knownCiv == otherCiv) -40f else -20f
diploManager.addModifier(DiplomaticModifiers.BetrayedDeclarationOfFriendship, amount)
diploManager.addModifier(DiplomaticModifiers.BetrayedDeclarationOfFriendship, amount * knownCiv.getPersonality().modifierFocus(PersonalityValue.Loyal, .3f))
}
if (betrayedDefensivePact) {
//Note: this stacks with Declaration of Friendship
val amount = if (knownCiv == otherCiv) -20f else -10f
diploManager.addModifier(DiplomaticModifiers.BetrayedDefensivePact, amount)
diploManager.addModifier(DiplomaticModifiers.BetrayedDefensivePact, amount * knownCiv.getPersonality().modifierFocus(PersonalityValue.Loyal, .3f))
}
diploManager.removeModifier(DiplomaticModifiers.DeclaredFriendshipWithOurAllies) // obviously this guy's declarations of friendship aren't worth much.
diploManager.removeModifier(DiplomaticModifiers.SignedDefensivePactWithOurAllies)

View File

@ -6,6 +6,7 @@ import com.unciv.logic.civilization.Civilization
import com.unciv.logic.civilization.NotificationCategory
import com.unciv.logic.civilization.NotificationIcon
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
import com.unciv.models.ruleset.nation.PersonalityValue
class Trade : IsPartOfGameInfoSerialization {
@ -57,9 +58,9 @@ class TradeRequest : IsPartOfGameInfoSerialization {
// the numbers of the flags (20,5) are the amount of turns to wait until offering again
if (trade.ourOffers.all { it.type == TradeType.Luxury_Resource }
&& trade.theirOffers.all { it.type == TradeType.Luxury_Resource })
requestingCivDiploManager.setFlag(DiplomacyFlags.DeclinedLuxExchange,5)
requestingCivDiploManager.setFlag(DiplomacyFlags.DeclinedLuxExchange,5 - (requestingCivInfo.getPersonality()[PersonalityValue.Commerce] / 2).toInt())
if (trade.ourOffers.any { it.name == Constants.researchAgreement })
requestingCivDiploManager.setFlag(DiplomacyFlags.DeclinedResearchAgreement,10)
requestingCivDiploManager.setFlag(DiplomacyFlags.DeclinedResearchAgreement,15 - requestingCivInfo.getPersonality()[PersonalityValue.Science].toInt())
if (trade.ourOffers.any { it.name == Constants.defensivePact })
requestingCivDiploManager.setFlag(DiplomacyFlags.DeclinedDefensivePact,10)
if (trade.ourOffers.any { it.name == Constants.openBorders })

View File

@ -163,7 +163,7 @@ class TradeEvaluation {
return 0
} else if (civInfo.isAtWarWith(civToDeclareWarOn)) {
// We shouldn't require them to pay us to join our war (no negative values)
return (20 * DeclareWarPlanEvaluator.evaluateJoinOurWarPlan(civInfo, civToDeclareWarOn, tradePartner, null)).coerceAtLeast(0)
return (20 * DeclareWarPlanEvaluator.evaluateJoinOurWarPlan(civInfo, civToDeclareWarOn, tradePartner, null)).toInt().coerceAtLeast(0)
} else {
// Why should we pay you to go fight someone else?
return 0
@ -281,13 +281,13 @@ class TradeEvaluation {
&& trade.ourOffers.any {it.type == TradeType.WarDeclaration && it.name == offer.name}) {
// Only accept if the war will benefit us, or if they pay us enough
// We shouldn't want to pay them for us to declare war (no negative values)
return (-20 * DeclareWarPlanEvaluator.evaluateTeamWarPlan(civInfo, civToDeclareWarOn, tradePartner, null)).coerceAtLeast(0)
return (-20 * DeclareWarPlanEvaluator.evaluateTeamWarPlan(civInfo, civToDeclareWarOn, tradePartner, null)).toInt().coerceAtLeast(0)
} else if (tradePartner.isAtWarWith(civToDeclareWarOn)) {
// We might want them to pay us to join them in war (no negative values)
return (-20 * DeclareWarPlanEvaluator.evaluateJoinWarPlan(civInfo, civToDeclareWarOn, tradePartner, null)).coerceAtLeast(0)
return (-20 * DeclareWarPlanEvaluator.evaluateJoinWarPlan(civInfo, civToDeclareWarOn, tradePartner, null)).toInt().coerceAtLeast(0)
} else {
// We might want them to pay us to declare war (no negative values)
return (-25 * DeclareWarPlanEvaluator.evaluateDeclareWarPlan(civInfo, civToDeclareWarOn, null)).coerceAtLeast(0)
return (-25 * DeclareWarPlanEvaluator.evaluateDeclareWarPlan(civInfo, civToDeclareWarOn, null)).toInt().coerceAtLeast(0)
}
}
@ -347,7 +347,7 @@ class TradeEvaluation {
if (ourCombatStrength * 1.5f >= theirCombatStrength && theirCombatStrength * 1.5f >= ourCombatStrength)
return 0 // we're roughly equal, there's no huge power imbalance
if (ourCombatStrength > theirCombatStrength) {
if (MotivationToAttackAutomation.hasAtLeastMotivationToAttack(ourCiv, otherCiv, 0) <= 0) return 0
if (MotivationToAttackAutomation.hasAtLeastMotivationToAttack(ourCiv, otherCiv, 0f) <= 0) return 0
val absoluteAdvantage = ourCombatStrength - theirCombatStrength
val percentageAdvantage = absoluteAdvantage / theirCombatStrength.toFloat()
// We don't add the same constraint here. We should not make peace easily if we're

View File

@ -93,12 +93,19 @@ class Personality: RulesetObject() {
}
/**
* Scales the value to a more meaningful range, where 10 is 2, and 5 is 1
* Scales the value to a more meaningful range, where 10 is 2, and 5 is 1, and 0 is 0
*/
fun scaledFocus(value: PersonalityValue): Float {
return nameToVariable(value).get() / 5
}
/**
* Inverse scales the value to a more meaningful range, where 0 is 2, and 5 is 1 and 10 is 0
*/
fun inverseScaledFocus(value: PersonalityValue): Float {
return (10 - nameToVariable(value).get()) / 5
}
/**
* @param weight a value between 0 and 1 that determines how much the modifier deviates from 1
* @return a modifier between 0 and 2 centered around 1 based off of the personality value and the weight given
@ -107,6 +114,15 @@ class Personality: RulesetObject() {
return 1f + (scaledFocus(value) - 1) * weight
}
/**
* An inverted version of [modifierFocus], a personality value of 0 becomes a 10, 8 becomes a 2, etc.
* @param weight a value between 0 and 1 that determines how much the modifier deviates from 1
* @return a modifier between 0 and 2 centered around 1 based off of the personality value and the weight given
*/
fun inverseModifierFocus(value: PersonalityValue, weight: Float): Float {
return 1f - (inverseScaledFocus(value) - 2) * weight
}
/**
* Scales the stats based on the personality and the weight given
* @param weight a positive value that determines how much the personality should impact the stats given