mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-26 21:35:14 -04:00
City-States Influence rates; Wary status; Proximity calculations (#5198)
* Rates for natural influence change * Minor civ wariness, proximity calculation * CS can declare permanent war * CS can in fact not declare permanent war * adjustments, template.properties * neater code * fix failing test? . * move proximity code, for reals fix failing check * now? * revisions * BFS only once, better check for water map * assign continents on pre-made maps as well * now works on all pre-made maps
This commit is contained in:
parent
297618706c
commit
7bd555ac95
@ -182,6 +182,7 @@ Diplomatic Marriage ([amount] Gold) =
|
|||||||
We have married into the ruling family of [civName], bringing them under our control. =
|
We have married into the ruling family of [civName], bringing them under our control. =
|
||||||
[civName] has married into the ruling family of [civName2], bringing them under their control. =
|
[civName] has married into the ruling family of [civName2], bringing them under their control. =
|
||||||
You have broken your Pledge to Protect [civName]! =
|
You have broken your Pledge to Protect [civName]! =
|
||||||
|
City-States grow wary of your aggression. The resting point for Influence has decreased by [amount] for [civName]. =
|
||||||
|
|
||||||
Cultured =
|
Cultured =
|
||||||
Maritime =
|
Maritime =
|
||||||
|
@ -37,19 +37,25 @@ object GameStarter {
|
|||||||
|
|
||||||
gameInfo.gameParameters = gameSetupInfo.gameParameters
|
gameInfo.gameParameters = gameSetupInfo.gameParameters
|
||||||
val ruleset = RulesetCache.getComplexRuleset(gameInfo.gameParameters.mods)
|
val ruleset = RulesetCache.getComplexRuleset(gameInfo.gameParameters.mods)
|
||||||
|
val mapGen = MapGenerator(ruleset)
|
||||||
|
|
||||||
if (gameSetupInfo.mapParameters.name != "") runAndMeasure("loadMap") {
|
if (gameSetupInfo.mapParameters.name != "") runAndMeasure("loadMap") {
|
||||||
tileMap = MapSaver.loadMap(gameSetupInfo.mapFile!!)
|
tileMap = MapSaver.loadMap(gameSetupInfo.mapFile!!)
|
||||||
// Don't override the map parameters - this can include if we world wrap or not!
|
// Don't override the map parameters - this can include if we world wrap or not!
|
||||||
} else runAndMeasure("generateMap") {
|
} else runAndMeasure("generateMap") {
|
||||||
tileMap = MapGenerator(ruleset).generateMap(gameSetupInfo.mapParameters)
|
tileMap = mapGen.generateMap(gameSetupInfo.mapParameters)
|
||||||
tileMap.mapParameters = gameSetupInfo.mapParameters
|
tileMap.mapParameters = gameSetupInfo.mapParameters
|
||||||
}
|
}
|
||||||
|
|
||||||
runAndMeasure("addCivilizations") {
|
runAndMeasure("addCivilizations") {
|
||||||
gameInfo.tileMap = tileMap
|
gameInfo.tileMap = tileMap
|
||||||
tileMap.gameInfo = gameInfo // need to set this transient before placing units in the map
|
tileMap.gameInfo =
|
||||||
addCivilizations(gameSetupInfo.gameParameters, gameInfo, ruleset) // this is before gameInfo.setTransients, so gameInfo doesn't yet have the gameBasics
|
gameInfo // need to set this transient before placing units in the map
|
||||||
|
addCivilizations(
|
||||||
|
gameSetupInfo.gameParameters,
|
||||||
|
gameInfo,
|
||||||
|
ruleset
|
||||||
|
) // this is before gameInfo.setTransients, so gameInfo doesn't yet have the gameBasics
|
||||||
}
|
}
|
||||||
|
|
||||||
runAndMeasure("Remove units") {
|
runAndMeasure("Remove units") {
|
||||||
@ -78,6 +84,11 @@ object GameStarter {
|
|||||||
addCivStats(gameInfo)
|
addCivStats(gameInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
runAndMeasure("assignContinents?") {
|
||||||
|
if (tileMap.continentSizes.isEmpty()) // Probably saved map without continent data
|
||||||
|
mapGen.assignContinents(tileMap)
|
||||||
|
}
|
||||||
|
|
||||||
runAndMeasure("addCivStartingUnits") {
|
runAndMeasure("addCivStartingUnits") {
|
||||||
// and only now do we add units for everyone, because otherwise both the gameInfo.setTransients() and the placeUnit will both add the unit to the civ's unit list!
|
// and only now do we add units for everyone, because otherwise both the gameInfo.setTransients() and the placeUnit will both add the unit to the civ's unit list!
|
||||||
addCivStartingUnits(gameInfo)
|
addCivStartingUnits(gameInfo)
|
||||||
@ -334,12 +345,15 @@ object GameStarter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun getStartingLocations(civs: List<CivilizationInfo>, tileMap: TileMap, startScores: HashMap<TileInfo, Float>): HashMap<CivilizationInfo, TileInfo> {
|
private fun getStartingLocations(civs: List<CivilizationInfo>, tileMap: TileMap, startScores: HashMap<TileInfo, Float>): HashMap<CivilizationInfo, TileInfo> {
|
||||||
|
val landTilesInBigEnoughGroup = tileMap.landTilesInBigEnoughGroup
|
||||||
|
if (landTilesInBigEnoughGroup.isEmpty()) {
|
||||||
|
// Worst case - a pre-made map with continent data. This means we didn't re-run assignContinents,
|
||||||
|
// so we don't have a cached landTilesInBigEnoughGroup. So we need to do it the hard way.
|
||||||
var landTiles = tileMap.values
|
var landTiles = tileMap.values
|
||||||
// Games starting on snow might as well start over...
|
// Games starting on snow might as well start over...
|
||||||
.filter { it.isLand && !it.isImpassible() && it.baseTerrain != Constants.snow }
|
.filter { it.isLand && !it.isImpassible() && it.baseTerrain != Constants.snow }
|
||||||
|
|
||||||
val landTilesInBigEnoughGroup = ArrayList<TileInfo>()
|
|
||||||
while (landTiles.any()) {
|
while (landTiles.any()) {
|
||||||
val bfs = BFS(landTiles.random()) { it.isLand && !it.isImpassible() }
|
val bfs = BFS(landTiles.random()) { it.isLand && !it.isImpassible() }
|
||||||
bfs.stepToEnd()
|
bfs.stepToEnd()
|
||||||
@ -348,6 +362,7 @@ object GameStarter {
|
|||||||
if (tilesInGroup.size > 20) // is this a good number? I dunno, but it's easy enough to change later on
|
if (tilesInGroup.size > 20) // is this a good number? I dunno, but it's easy enough to change later on
|
||||||
landTilesInBigEnoughGroup.addAll(tilesInGroup)
|
landTilesInBigEnoughGroup.addAll(tilesInGroup)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val civsOrderedByAvailableLocations = civs.shuffled() // Order should be random since it determines who gets best start
|
val civsOrderedByAvailableLocations = civs.shuffled() // Order should be random since it determines who gets best start
|
||||||
.sortedBy { civ ->
|
.sortedBy { civ ->
|
||||||
|
@ -3,6 +3,7 @@ package com.unciv.logic.city
|
|||||||
import com.badlogic.gdx.math.Vector2
|
import com.badlogic.gdx.math.Vector2
|
||||||
import com.unciv.logic.battle.CityCombatant
|
import com.unciv.logic.battle.CityCombatant
|
||||||
import com.unciv.logic.civilization.CivilizationInfo
|
import com.unciv.logic.civilization.CivilizationInfo
|
||||||
|
import com.unciv.logic.civilization.Proximity
|
||||||
import com.unciv.logic.civilization.ReligionState
|
import com.unciv.logic.civilization.ReligionState
|
||||||
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
|
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
|
||||||
import com.unciv.logic.map.RoadStatus
|
import com.unciv.logic.map.RoadStatus
|
||||||
@ -129,6 +130,18 @@ class CityInfo {
|
|||||||
population.autoAssignPopulation()
|
population.autoAssignPopulation()
|
||||||
cityStats.update()
|
cityStats.update()
|
||||||
|
|
||||||
|
// Update proximity rankings for all civs
|
||||||
|
for (otherCiv in civInfo.gameInfo.getAliveMajorCivs()) {
|
||||||
|
if (civInfo.getProximity(otherCiv) != Proximity.Neighbors) // unless already neighbors
|
||||||
|
civInfo.updateProximity(otherCiv,
|
||||||
|
otherCiv.updateProximity(civInfo))
|
||||||
|
}
|
||||||
|
for (otherCiv in civInfo.gameInfo.getAliveCityStates()) {
|
||||||
|
if (civInfo.getProximity(otherCiv) != Proximity.Neighbors) // unless already neighbors
|
||||||
|
civInfo.updateProximity(otherCiv,
|
||||||
|
otherCiv.updateProximity(civInfo))
|
||||||
|
}
|
||||||
|
|
||||||
triggerCitiesSettledNearOtherCiv()
|
triggerCitiesSettledNearOtherCiv()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -556,6 +569,16 @@ class CityInfo {
|
|||||||
if (isCapital() && civInfo.cities.isNotEmpty()) { // Move the capital if destroyed (by a nuke or by razing)
|
if (isCapital() && civInfo.cities.isNotEmpty()) { // Move the capital if destroyed (by a nuke or by razing)
|
||||||
civInfo.cities.first().cityConstructions.addBuilding(capitalCityIndicator())
|
civInfo.cities.first().cityConstructions.addBuilding(capitalCityIndicator())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update proximity rankings for all civs
|
||||||
|
for (otherCiv in civInfo.gameInfo.getAliveMajorCivs()) {
|
||||||
|
civInfo.updateProximity(otherCiv,
|
||||||
|
otherCiv.updateProximity(civInfo))
|
||||||
|
}
|
||||||
|
for (otherCiv in civInfo.gameInfo.getAliveCityStates()) {
|
||||||
|
civInfo.updateProximity(otherCiv,
|
||||||
|
otherCiv.updateProximity(civInfo))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun annexCity() = CityInfoConquestFunctions(this).annexCity()
|
fun annexCity() = CityInfoConquestFunctions(this).annexCity()
|
||||||
|
@ -276,6 +276,10 @@ class CityInfoConquestFunctions(val city: CityInfo){
|
|||||||
|
|
||||||
tryUpdateRoadStatus()
|
tryUpdateRoadStatus()
|
||||||
cityStats.update()
|
cityStats.update()
|
||||||
|
|
||||||
|
// Update proximity rankings
|
||||||
|
civInfo.updateProximity(oldCiv,
|
||||||
|
oldCiv.updateProximity(civInfo))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,17 +2,16 @@ package com.unciv.logic.civilization
|
|||||||
|
|
||||||
import com.unciv.Constants
|
import com.unciv.Constants
|
||||||
import com.unciv.logic.automation.NextTurnAutomation
|
import com.unciv.logic.automation.NextTurnAutomation
|
||||||
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
|
import com.unciv.logic.civilization.diplomacy.*
|
||||||
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
|
|
||||||
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
|
|
||||||
import com.unciv.logic.civilization.diplomacy.RelationshipLevel
|
|
||||||
import com.unciv.models.metadata.GameSpeed
|
import com.unciv.models.metadata.GameSpeed
|
||||||
import com.unciv.models.ruleset.Ruleset
|
import com.unciv.models.ruleset.Ruleset
|
||||||
import com.unciv.models.stats.Stat
|
import com.unciv.models.stats.Stat
|
||||||
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.ui.victoryscreen.RankingType
|
import com.unciv.ui.victoryscreen.RankingType
|
||||||
|
import java.util.*
|
||||||
import kotlin.collections.HashMap
|
import kotlin.collections.HashMap
|
||||||
|
import kotlin.collections.HashSet
|
||||||
import kotlin.collections.LinkedHashMap
|
import kotlin.collections.LinkedHashMap
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
@ -506,10 +505,58 @@ class CityStateFunctions(val civInfo: CivilizationInfo) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A city state was attacked. What are its protectors going to do about it??? */
|
/** A city state was attacked. What are its protectors going to do about it??? Also checks for Wary */
|
||||||
fun cityStateAttacked(attacker: CivilizationInfo) {
|
fun cityStateAttacked(attacker: CivilizationInfo) {
|
||||||
if (!civInfo.isCityState()) return // What are we doing here?
|
if (!civInfo.isCityState()) return // What are we doing here?
|
||||||
|
|
||||||
|
// We might become wary!
|
||||||
|
if (attacker.isMinorCivWarmonger()) { // They've attacked a lot of city-states
|
||||||
|
civInfo.getDiplomacyManager(attacker).becomeWary()
|
||||||
|
}
|
||||||
|
else if (attacker.isMinorCivAggressor()) { // They've attacked a few
|
||||||
|
if (Random().nextBoolean()) { // 50% chance
|
||||||
|
civInfo.getDiplomacyManager(attacker).becomeWary()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Others might become wary!
|
||||||
|
if (attacker.isMinorCivAggressor()) {
|
||||||
|
for (cityState in civInfo.gameInfo.getAliveCityStates()) {
|
||||||
|
if (cityState == civInfo) // Must be a different minor
|
||||||
|
continue
|
||||||
|
if (cityState.getAllyCiv() == attacker.civName) // Must not be allied to the attacker
|
||||||
|
continue
|
||||||
|
if (!cityState.knows(attacker)) // Must have met
|
||||||
|
continue
|
||||||
|
|
||||||
|
var probability: Int
|
||||||
|
if (attacker.isMinorCivWarmonger()) {
|
||||||
|
// High probability if very aggressive
|
||||||
|
probability = when (cityState.getProximity(attacker)) {
|
||||||
|
Proximity.Neighbors -> 100
|
||||||
|
Proximity.Close -> 75
|
||||||
|
Proximity.Far -> 50
|
||||||
|
Proximity.Distant -> 25
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Lower probability if only somewhat aggressive
|
||||||
|
probability = when (cityState.getProximity(attacker)) {
|
||||||
|
Proximity.Neighbors -> 50
|
||||||
|
Proximity.Close -> 20
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Higher probability if already at war
|
||||||
|
if (cityState.isAtWarWith(attacker))
|
||||||
|
probability += 50
|
||||||
|
|
||||||
|
if (Random().nextInt(100) <= probability) {
|
||||||
|
cityState.getDiplomacyManager(attacker).becomeWary()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (protector in civInfo.getProtectorCivs()) {
|
for (protector in civInfo.getProtectorCivs()) {
|
||||||
if (!protector.knows(attacker)) // Who?
|
if (!protector.knows(attacker)) // Who?
|
||||||
continue
|
continue
|
||||||
|
@ -10,9 +10,7 @@ import com.unciv.logic.civilization.RuinsManager.RuinsManager
|
|||||||
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
|
import com.unciv.logic.civilization.diplomacy.DiplomacyFlags
|
||||||
import com.unciv.logic.civilization.diplomacy.DiplomacyManager
|
import com.unciv.logic.civilization.diplomacy.DiplomacyManager
|
||||||
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
|
import com.unciv.logic.civilization.diplomacy.DiplomaticStatus
|
||||||
import com.unciv.logic.map.MapUnit
|
import com.unciv.logic.map.*
|
||||||
import com.unciv.logic.map.TileInfo
|
|
||||||
import com.unciv.logic.map.UnitMovementAlgorithms
|
|
||||||
import com.unciv.logic.trade.TradeEvaluation
|
import com.unciv.logic.trade.TradeEvaluation
|
||||||
import com.unciv.logic.trade.TradeRequest
|
import com.unciv.logic.trade.TradeRequest
|
||||||
import com.unciv.models.Counter
|
import com.unciv.models.Counter
|
||||||
@ -34,6 +32,14 @@ import kotlin.math.min
|
|||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
import kotlin.math.sqrt
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
|
enum class Proximity {
|
||||||
|
None, // ie no cities
|
||||||
|
Neighbors,
|
||||||
|
Close,
|
||||||
|
Far,
|
||||||
|
Distant
|
||||||
|
}
|
||||||
|
|
||||||
class CivilizationInfo {
|
class CivilizationInfo {
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
@ -109,6 +115,7 @@ class CivilizationInfo {
|
|||||||
var victoryManager = VictoryManager()
|
var victoryManager = VictoryManager()
|
||||||
var ruinsManager = RuinsManager()
|
var ruinsManager = RuinsManager()
|
||||||
var diplomacy = HashMap<String, DiplomacyManager>()
|
var diplomacy = HashMap<String, DiplomacyManager>()
|
||||||
|
var proximity = HashMap<String, Proximity>()
|
||||||
var notifications = ArrayList<Notification>()
|
var notifications = ArrayList<Notification>()
|
||||||
val popupAlerts = ArrayList<PopupAlert>()
|
val popupAlerts = ArrayList<PopupAlert>()
|
||||||
private var allyCivName: String? = null
|
private var allyCivName: String? = null
|
||||||
@ -143,6 +150,9 @@ class CivilizationInfo {
|
|||||||
// default false once we no longer want legacy save-game compatibility
|
// default false once we no longer want legacy save-game compatibility
|
||||||
var hasEverOwnedOriginalCapital: Boolean? = null
|
var hasEverOwnedOriginalCapital: Boolean? = null
|
||||||
|
|
||||||
|
// For Aggressor, Warmonger status
|
||||||
|
private var numMinorCivsAttacked = 0
|
||||||
|
|
||||||
constructor()
|
constructor()
|
||||||
|
|
||||||
constructor(civName: String) {
|
constructor(civName: String) {
|
||||||
@ -167,6 +177,7 @@ class CivilizationInfo {
|
|||||||
toReturn.allyCivName = allyCivName
|
toReturn.allyCivName = allyCivName
|
||||||
for (diplomacyManager in diplomacy.values.map { it.clone() })
|
for (diplomacyManager in diplomacy.values.map { it.clone() })
|
||||||
toReturn.diplomacy[diplomacyManager.otherCivName] = diplomacyManager
|
toReturn.diplomacy[diplomacyManager.otherCivName] = diplomacyManager
|
||||||
|
toReturn.proximity.putAll(proximity)
|
||||||
toReturn.cities = cities.map { it.clone() }
|
toReturn.cities = cities.map { it.clone() }
|
||||||
|
|
||||||
// This is the only thing that is NOT switched out, which makes it a source of ConcurrentModification errors.
|
// This is the only thing that is NOT switched out, which makes it a source of ConcurrentModification errors.
|
||||||
@ -187,6 +198,7 @@ class CivilizationInfo {
|
|||||||
toReturn.boughtConstructionsWithGloballyIncreasingPrice.putAll(boughtConstructionsWithGloballyIncreasingPrice)
|
toReturn.boughtConstructionsWithGloballyIncreasingPrice.putAll(boughtConstructionsWithGloballyIncreasingPrice)
|
||||||
//
|
//
|
||||||
toReturn.hasEverOwnedOriginalCapital = hasEverOwnedOriginalCapital
|
toReturn.hasEverOwnedOriginalCapital = hasEverOwnedOriginalCapital
|
||||||
|
toReturn.numMinorCivsAttacked = numMinorCivsAttacked
|
||||||
return toReturn
|
return toReturn
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,6 +211,9 @@ class CivilizationInfo {
|
|||||||
fun getDiplomacyManager(civInfo: CivilizationInfo) = getDiplomacyManager(civInfo.civName)
|
fun getDiplomacyManager(civInfo: CivilizationInfo) = getDiplomacyManager(civInfo.civName)
|
||||||
fun getDiplomacyManager(civName: String) = diplomacy[civName]!!
|
fun getDiplomacyManager(civName: String) = diplomacy[civName]!!
|
||||||
|
|
||||||
|
fun getProximity(civInfo: CivilizationInfo) = getProximity(civInfo.civName)
|
||||||
|
fun getProximity(civName: String) = proximity[civName] ?: Proximity.None
|
||||||
|
|
||||||
/** Returns only undefeated civs, aka the ones we care about */
|
/** Returns only undefeated civs, aka the ones we care about */
|
||||||
fun getKnownCivs() = diplomacy.values.map { it.otherCiv() }.filter { !it.isDefeated() }
|
fun getKnownCivs() = diplomacy.values.map { it.otherCiv() }.filter { !it.isDefeated() }
|
||||||
fun knows(otherCivName: String) = diplomacy.containsKey(otherCivName)
|
fun knows(otherCivName: String) = diplomacy.containsKey(otherCivName)
|
||||||
@ -556,6 +571,9 @@ class CivilizationInfo {
|
|||||||
fun hasTechOrPolicy(techOrPolicyName: String) =
|
fun hasTechOrPolicy(techOrPolicyName: String) =
|
||||||
tech.isResearched(techOrPolicyName) || policies.isAdopted(techOrPolicyName)
|
tech.isResearched(techOrPolicyName) || policies.isAdopted(techOrPolicyName)
|
||||||
|
|
||||||
|
fun isMinorCivAggressor() = numMinorCivsAttacked >= 2
|
||||||
|
fun isMinorCivWarmonger() = numMinorCivsAttacked >= 4
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
//region state-changing functions
|
//region state-changing functions
|
||||||
@ -612,6 +630,10 @@ class CivilizationInfo {
|
|||||||
updateDetailedCivResources()
|
updateDetailedCivResources()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun changeMinorCivsAttacked(count: Int) {
|
||||||
|
numMinorCivsAttacked += count
|
||||||
|
}
|
||||||
|
|
||||||
// implementation in a separate class, to not clog up CivInfo
|
// implementation in a separate class, to not clog up CivInfo
|
||||||
fun initialSetCitiesConnectedToCapitalTransients() = transients().updateCitiesConnectedToCapital(true)
|
fun initialSetCitiesConnectedToCapitalTransients() = transients().updateCitiesConnectedToCapital(true)
|
||||||
fun updateHasActiveGreatWall() = transients().updateHasActiveGreatWall()
|
fun updateHasActiveGreatWall() = transients().updateHasActiveGreatWall()
|
||||||
@ -912,6 +934,81 @@ class CivilizationInfo {
|
|||||||
).toInt()
|
).toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateProximity(otherCiv: CivilizationInfo, preCalculated: Proximity? = null): Proximity {
|
||||||
|
if (otherCiv == this) return Proximity.None
|
||||||
|
if (preCalculated != null) {
|
||||||
|
// We usually want to update this for a pair of civs at the same time
|
||||||
|
// Since this function *should* be symmetrical for both civs, we can just do it once
|
||||||
|
this.proximity[otherCiv.civName] = preCalculated
|
||||||
|
return preCalculated
|
||||||
|
}
|
||||||
|
if (cities.isEmpty() || otherCiv.cities.isEmpty()) {
|
||||||
|
proximity[otherCiv.civName] = Proximity.None
|
||||||
|
return Proximity.None
|
||||||
|
}
|
||||||
|
|
||||||
|
val mapParams = gameInfo.tileMap.mapParameters
|
||||||
|
var minDistance = 100000 // a long distance
|
||||||
|
var totalDistance = 0
|
||||||
|
var connections = 0
|
||||||
|
|
||||||
|
var proximity = Proximity.None
|
||||||
|
|
||||||
|
for (ourCity in cities) {
|
||||||
|
for (theirCity in otherCiv.cities) {
|
||||||
|
val distance = ourCity.getCenterTile().aerialDistanceTo(theirCity.getCenterTile())
|
||||||
|
totalDistance += distance
|
||||||
|
connections++
|
||||||
|
if (minDistance > distance) minDistance = distance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minDistance <= 7) {
|
||||||
|
proximity = Proximity.Neighbors
|
||||||
|
} else if (connections > 0) {
|
||||||
|
val averageDistance = totalDistance / connections
|
||||||
|
val mapFactor = if (mapParams.shape == MapShape.rectangular)
|
||||||
|
(mapParams.mapSize.height + mapParams.mapSize.width) / 2
|
||||||
|
else (mapParams.mapSize.radius * 3) / 2 // slightly less area than equal size rect
|
||||||
|
|
||||||
|
val closeDistance = ((mapFactor * 25) / 100).coerceIn(10, 20)
|
||||||
|
val farDistance = ((mapFactor * 45) / 100).coerceIn(20, 50)
|
||||||
|
|
||||||
|
proximity = if (minDistance <= 11 && averageDistance <= closeDistance)
|
||||||
|
Proximity.Close
|
||||||
|
else if (averageDistance <= farDistance)
|
||||||
|
Proximity.Far
|
||||||
|
else
|
||||||
|
Proximity.Distant
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if different continents (unless already max distance, or water map)
|
||||||
|
if (connections > 0 && proximity != Proximity.Distant
|
||||||
|
&& !gameInfo.tileMap.isWaterMap()) {
|
||||||
|
|
||||||
|
if (getCapital().getCenterTile().getContinent() != otherCiv.getCapital().getCenterTile().getContinent()) {
|
||||||
|
// Different continents - increase separation by one step
|
||||||
|
proximity = when (proximity) {
|
||||||
|
Proximity.Far -> Proximity.Distant
|
||||||
|
Proximity.Close -> Proximity.Far
|
||||||
|
Proximity.Neighbors -> Proximity.Close
|
||||||
|
else -> proximity
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there aren't many players (left) we can't be that far
|
||||||
|
val numMajors = gameInfo.getAliveMajorCivs().count()
|
||||||
|
if (numMajors <= 2 && proximity > Proximity.Close)
|
||||||
|
proximity = Proximity.Close
|
||||||
|
if (numMajors <= 4 && proximity > Proximity.Far)
|
||||||
|
proximity = Proximity.Far
|
||||||
|
|
||||||
|
this.proximity[otherCiv.civName] = proximity
|
||||||
|
|
||||||
|
return proximity
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////////// City State wrapper functions ////////////////////////
|
//////////////////////// City State wrapper functions ////////////////////////
|
||||||
|
|
||||||
fun initCityState(ruleset: Ruleset, startingEra: String, unusedMajorCivs: Collection<String>)
|
fun initCityState(ruleset: Ruleset, startingEra: String, unusedMajorCivs: Collection<String>)
|
||||||
|
@ -9,6 +9,7 @@ import com.unciv.logic.trade.TradeType
|
|||||||
import com.unciv.models.ruleset.tile.ResourceSupplyList
|
import com.unciv.models.ruleset.tile.ResourceSupplyList
|
||||||
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.ui.utils.toPercent
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
@ -49,7 +50,8 @@ enum class DiplomacyFlags {
|
|||||||
RememberAttackedProtectedMinor,
|
RememberAttackedProtectedMinor,
|
||||||
RememberBulliedProtectedMinor,
|
RememberBulliedProtectedMinor,
|
||||||
RememberSidedWithProtectedMinor,
|
RememberSidedWithProtectedMinor,
|
||||||
Denunciation
|
Denunciation,
|
||||||
|
WaryOf,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class DiplomaticModifiers {
|
enum class DiplomaticModifiers {
|
||||||
@ -238,6 +240,9 @@ class DiplomacyManager() {
|
|||||||
restingPoint += unique.params[0].toInt()
|
restingPoint += unique.params[0].toInt()
|
||||||
|
|
||||||
if (diplomaticStatus == DiplomaticStatus.Protector) restingPoint += 10
|
if (diplomaticStatus == DiplomaticStatus.Protector) restingPoint += 10
|
||||||
|
|
||||||
|
if (hasFlag(DiplomacyFlags.WaryOf)) restingPoint -= 20
|
||||||
|
|
||||||
return restingPoint
|
return restingPoint
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,47 +250,47 @@ class DiplomacyManager() {
|
|||||||
if (influence < getCityStateInfluenceRestingPoint())
|
if (influence < getCityStateInfluenceRestingPoint())
|
||||||
return 0f
|
return 0f
|
||||||
|
|
||||||
val decrement = when (civInfo.cityStatePersonality) {
|
val decrement = when {
|
||||||
CityStatePersonality.Hostile -> 1.5f
|
civInfo.cityStatePersonality == CityStatePersonality.Hostile -> 1.5f
|
||||||
else -> 1f
|
otherCiv().isMinorCivAggressor() -> 2f
|
||||||
}
|
|
||||||
|
|
||||||
var modifier = when (civInfo.cityStatePersonality) {
|
|
||||||
CityStatePersonality.Hostile -> 2f
|
|
||||||
CityStatePersonality.Irrational -> 1.5f
|
|
||||||
CityStatePersonality.Friendly -> .5f
|
|
||||||
else -> 1f
|
else -> 1f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var modifierPercent = 0f
|
||||||
for (unique in otherCiv().getMatchingUniques("City-State Influence degrades []% slower"))
|
for (unique in otherCiv().getMatchingUniques("City-State Influence degrades []% slower"))
|
||||||
modifier *= 1f - unique.params[0].toFloat() / 100f
|
modifierPercent -= unique.params[0].toFloat()
|
||||||
|
|
||||||
|
val religion = if (civInfo.cities.isEmpty()) null
|
||||||
|
else civInfo.getCapital().religion.getMajorityReligionName()
|
||||||
|
if (religion != null && religion == otherCiv().religionManager.religion?.name)
|
||||||
|
modifierPercent -= 25f // 25% slower degrade when sharing a religion
|
||||||
|
|
||||||
for (civ in civInfo.gameInfo.civilizations.filter { it.isMajorCiv() && it != otherCiv()}) {
|
for (civ in civInfo.gameInfo.civilizations.filter { it.isMajorCiv() && it != otherCiv()}) {
|
||||||
for (unique in civ.getMatchingUniques("Influence of all other civilizations with all city-states degrades []% faster")) {
|
for (unique in civ.getMatchingUniques("Influence of all other civilizations with all city-states degrades []% faster")) {
|
||||||
modifier *= 1f + unique.params[0].toFloat() / 100f
|
modifierPercent += unique.params[0].toFloat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return max(0f, decrement) * max(0f, modifier)
|
return max(0f, decrement) * max(-100f, modifierPercent).toPercent()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getCityStateInfluenceRecovery(): Float {
|
private fun getCityStateInfluenceRecovery(): Float {
|
||||||
if (influence > getCityStateInfluenceRestingPoint())
|
if (influence > getCityStateInfluenceRestingPoint())
|
||||||
return 0f
|
return 0f
|
||||||
|
|
||||||
val increment = 1f
|
val increment = 1f // sic: personality does not matter here
|
||||||
|
|
||||||
var modifier = when (civInfo.cityStatePersonality) {
|
var modifierPercent = 0f
|
||||||
CityStatePersonality.Friendly -> 2f
|
|
||||||
CityStatePersonality.Irrational -> 1.5f
|
|
||||||
CityStatePersonality.Hostile -> .5f
|
|
||||||
else -> 1f
|
|
||||||
}
|
|
||||||
|
|
||||||
if (otherCiv().hasUnique("City-State Influence recovers at twice the normal rate"))
|
if (otherCiv().hasUnique("City-State Influence recovers at twice the normal rate"))
|
||||||
modifier *= 2f
|
modifierPercent += 100f
|
||||||
|
|
||||||
return max(0f, increment) * max(0f, modifier)
|
val religion = if (civInfo.cities.isEmpty()) null
|
||||||
|
else civInfo.getCapital().religion.getMajorityReligionName()
|
||||||
|
if (religion != null && religion == otherCiv().religionManager.religion?.name)
|
||||||
|
modifierPercent += 50f // 50% quicker recovery when sharing a religion
|
||||||
|
|
||||||
|
return max(0f, increment) * max(0f, modifierPercent).toPercent()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun canDeclareWar() = turnsToPeaceTreaty() == 0 && diplomaticStatus != DiplomaticStatus.War
|
fun canDeclareWar() = turnsToPeaceTreaty() == 0 && diplomaticStatus != DiplomaticStatus.War
|
||||||
@ -642,6 +647,7 @@ class DiplomacyManager() {
|
|||||||
otherCivDiplomacy.setModifier(DiplomaticModifiers.DeclaredWarOnUs, -20f)
|
otherCivDiplomacy.setModifier(DiplomaticModifiers.DeclaredWarOnUs, -20f)
|
||||||
if (otherCiv.isCityState()) {
|
if (otherCiv.isCityState()) {
|
||||||
otherCivDiplomacy.setInfluence(-60f)
|
otherCivDiplomacy.setInfluence(-60f)
|
||||||
|
civInfo.changeMinorCivsAttacked(1)
|
||||||
otherCiv.cityStateAttacked(civInfo)
|
otherCiv.cityStateAttacked(civInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -832,5 +838,11 @@ class DiplomacyManager() {
|
|||||||
otherCivDiplomacy().setFlag(DiplomacyFlags.RememberSidedWithProtectedMinor, 25)
|
otherCivDiplomacy().setFlag(DiplomacyFlags.RememberSidedWithProtectedMinor, 25)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun becomeWary() {
|
||||||
|
if (hasFlag(DiplomacyFlags.WaryOf)) return // once is enough
|
||||||
|
setFlag(DiplomacyFlags.WaryOf, -1) // Never expires
|
||||||
|
otherCiv().addNotification("City-States grow wary of your aggression. The resting point for Influence has decreased by [20] for [${civInfo.civName}].", civInfo.civName)
|
||||||
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
}
|
}
|
||||||
|
@ -23,10 +23,13 @@ class BFS(
|
|||||||
tilesReached[startingPoint] = startingPoint
|
tilesReached[startingPoint] = startingPoint
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Process fully until there's nowhere left to check */
|
/** Process fully until there's nowhere left to check
|
||||||
fun stepToEnd() {
|
* Optionally assigns a continent ID as it goes */
|
||||||
|
fun stepToEnd(continent: Int? = null) {
|
||||||
|
if (continent != null)
|
||||||
|
startingPoint.setContinent(continent)
|
||||||
while (!hasEnded())
|
while (!hasEnded())
|
||||||
nextStep()
|
nextStep(continent)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -46,13 +49,15 @@ class BFS(
|
|||||||
*
|
*
|
||||||
* Will do nothing when [hasEnded] returns `true`
|
* Will do nothing when [hasEnded] returns `true`
|
||||||
*/
|
*/
|
||||||
fun nextStep() {
|
fun nextStep(continent: Int? = null) {
|
||||||
if (tilesReached.size >= maxSize) { tilesToCheck.clear(); return }
|
if (tilesReached.size >= maxSize) { tilesToCheck.clear(); return }
|
||||||
val current = tilesToCheck.removeFirstOrNull() ?: return
|
val current = tilesToCheck.removeFirstOrNull() ?: return
|
||||||
for (neighbor in current.neighbors) {
|
for (neighbor in current.neighbors) {
|
||||||
if (neighbor !in tilesReached && predicate(neighbor)) {
|
if (neighbor !in tilesReached && predicate(neighbor)) {
|
||||||
tilesReached[neighbor] = current
|
tilesReached[neighbor] = current
|
||||||
tilesToCheck.add(neighbor)
|
tilesToCheck.add(neighbor)
|
||||||
|
if (continent != null)
|
||||||
|
neighbor.setContinent(continent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,6 +70,8 @@ open class TileInfo {
|
|||||||
var hasBottomRiver = false
|
var hasBottomRiver = false
|
||||||
var hasBottomLeftRiver = false
|
var hasBottomLeftRiver = false
|
||||||
|
|
||||||
|
private var continent = -1
|
||||||
|
|
||||||
val latitude: Float
|
val latitude: Float
|
||||||
get() = HexMath.getLatitude(position)
|
get() = HexMath.getLatitude(position)
|
||||||
val longitude: Float
|
val longitude: Float
|
||||||
@ -92,6 +94,7 @@ open class TileInfo {
|
|||||||
toReturn.hasBottomLeftRiver = hasBottomLeftRiver
|
toReturn.hasBottomLeftRiver = hasBottomLeftRiver
|
||||||
toReturn.hasBottomRightRiver = hasBottomRightRiver
|
toReturn.hasBottomRightRiver = hasBottomRightRiver
|
||||||
toReturn.hasBottomRiver = hasBottomRiver
|
toReturn.hasBottomRiver = hasBottomRiver
|
||||||
|
toReturn.continent = continent
|
||||||
return toReturn
|
return toReturn
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -654,6 +657,7 @@ open class TileInfo {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getContinent() = continent
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
@ -773,5 +777,12 @@ open class TileInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Should only be set once at map generation
|
||||||
|
fun setContinent(continent: Int) {
|
||||||
|
if (this.continent != -1)
|
||||||
|
throw Exception("Continent already assigned @ $position")
|
||||||
|
this.continent = continent
|
||||||
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
}
|
}
|
||||||
|
@ -34,6 +34,7 @@ class TileMap {
|
|||||||
var mapParameters = MapParameters()
|
var mapParameters = MapParameters()
|
||||||
|
|
||||||
private var tileList = ArrayList<TileInfo>()
|
private var tileList = ArrayList<TileInfo>()
|
||||||
|
val continentSizes = HashMap<Int, Int>() // Continent ID, Continent size
|
||||||
|
|
||||||
/** Structure geared for simple serialization by Gdx.Json (which is a little blind to kotlin collections, especially HashSet)
|
/** Structure geared for simple serialization by Gdx.Json (which is a little blind to kotlin collections, especially HashSet)
|
||||||
* @param position [Vector2] of the location
|
* @param position [Vector2] of the location
|
||||||
@ -78,6 +79,9 @@ class TileMap {
|
|||||||
@Transient
|
@Transient
|
||||||
val startingLocationsByNation = HashMap<String,HashSet<TileInfo>>()
|
val startingLocationsByNation = HashMap<String,HashSet<TileInfo>>()
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
val landTilesInBigEnoughGroup = ArrayList<TileInfo>() // cached at map gen
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
//region Constructors
|
//region Constructors
|
||||||
|
|
||||||
@ -123,6 +127,7 @@ class TileMap {
|
|||||||
toReturn.startingLocations.clear()
|
toReturn.startingLocations.clear()
|
||||||
toReturn.startingLocations.ensureCapacity(startingLocations.size)
|
toReturn.startingLocations.ensureCapacity(startingLocations.size)
|
||||||
toReturn.startingLocations.addAll(startingLocations)
|
toReturn.startingLocations.addAll(startingLocations)
|
||||||
|
toReturn.continentSizes.putAll(continentSizes)
|
||||||
return toReturn
|
return toReturn
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -344,6 +349,12 @@ class TileMap {
|
|||||||
return rulesetIncompatibilities
|
return rulesetIncompatibilities
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isWaterMap(): Boolean {
|
||||||
|
val bigIslands = continentSizes.count { it.value > 20 }
|
||||||
|
val players = gameInfo.gameParameters.players.count()
|
||||||
|
return bigIslands >= players
|
||||||
|
}
|
||||||
|
|
||||||
//endregion
|
//endregion
|
||||||
//region State-Changing Methods
|
//region State-Changing Methods
|
||||||
|
|
||||||
|
@ -73,6 +73,9 @@ class MapGenerator(val ruleset: Ruleset) {
|
|||||||
runAndMeasure("spawnIce") {
|
runAndMeasure("spawnIce") {
|
||||||
spawnIce(map)
|
spawnIce(map)
|
||||||
}
|
}
|
||||||
|
runAndMeasure("assignContinents") {
|
||||||
|
assignContinents(map)
|
||||||
|
}
|
||||||
runAndMeasure("NaturalWonderGenerator") {
|
runAndMeasure("NaturalWonderGenerator") {
|
||||||
NaturalWonderGenerator(ruleset, randomness).spawnNaturalWonders(map)
|
NaturalWonderGenerator(ruleset, randomness).spawnNaturalWonders(map)
|
||||||
}
|
}
|
||||||
@ -461,6 +464,26 @@ class MapGenerator(val ruleset: Ruleset) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set a continent id for each tile, so we can quickly see which tiles are connected.
|
||||||
|
// Can also be called on saved maps
|
||||||
|
fun assignContinents(tileMap: TileMap) {
|
||||||
|
var landTiles = tileMap.values
|
||||||
|
.filter { it.isLand && !it.isImpassible()}
|
||||||
|
var currentContinent = 0
|
||||||
|
|
||||||
|
while (landTiles.any()) {
|
||||||
|
val bfs = BFS(landTiles.random()) { it.isLand && !it.isImpassible() }
|
||||||
|
bfs.stepToEnd(currentContinent)
|
||||||
|
val continent = bfs.getReachedTiles()
|
||||||
|
tileMap.continentSizes[currentContinent] = continent.size
|
||||||
|
if (continent.size > 20) {
|
||||||
|
tileMap.landTilesInBigEnoughGroup.addAll(continent)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentContinent++
|
||||||
|
landTiles = landTiles.filter { it !in continent }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MapGenerationRandomness {
|
class MapGenerationRandomness {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user