Minimum city distance across continents (#5404)

This commit is contained in:
SomeTroglodyte
2021-10-06 21:51:52 +02:00
committed by GitHub
parent 22ebe2b9af
commit d25adacec7
15 changed files with 66 additions and 51 deletions

View File

@ -2,10 +2,7 @@ package com.unciv
object Constants {
const val worker = "Worker"
const val canBuildImprovements = "Can build [] improvements on tiles"
const val workBoatsUnique = "May create improvements on water resources"
const val settler = "Settler"
const val settlerUnique = "Founds a new city"
const val eraSpecificUnit = "Era Starting Unit"
const val spreadReligionAbilityCount = "Spread Religion"
const val removeHeresyAbilityCount = "Remove Foreign religions from your own cities"

View File

@ -262,7 +262,7 @@ object GameStarter {
val startingLocations = getStartingLocations(allCivs, tileMap, landTilesInBigEnoughGroup, startScores)
val settlerLikeUnits = ruleSet.units.filter {
it.value.uniqueObjects.any { unique -> unique.placeholderText == Constants.settlerUnique }
it.value.hasUnique(UniqueType.FoundCity)
}
// no starting units for Barbarians and Spectators
@ -315,9 +315,8 @@ object GameStarter {
}
if (unit == "Worker" && "Worker" !in ruleSet.units) {
val buildableWorkerLikeUnits = ruleSet.units.filter {
it.value.uniqueObjects.any { unique -> unique.placeholderText == Constants.canBuildImprovements }
&& it.value.isBuildable(civ)
&& it.value.isCivilian()
it.value.hasUnique(UniqueType.BuildImprovements) &&
it.value.isBuildable(civ) && it.value.isCivilian()
}
if (buildableWorkerLikeUnits.isEmpty()) return null // No workers in this mod
return civ.getEquivalentUnit(buildableWorkerLikeUnits.keys.random()).name

View File

@ -1,6 +1,5 @@
package com.unciv.logic.automation
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.city.CityConstructions
import com.unciv.logic.city.PerpetualConstruction
@ -10,8 +9,8 @@ import com.unciv.logic.civilization.PlayerType
import com.unciv.logic.map.BFS
import com.unciv.models.ruleset.Building
import com.unciv.models.ruleset.VictoryType
import com.unciv.models.ruleset.unique.UniqueType
import com.unciv.models.stats.Stat
import com.unciv.models.translations.equalsPlaceholderText
import kotlin.math.min
import kotlin.math.sqrt
@ -105,7 +104,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
if (!cityIsOverAverageProduction) modifier /= 5 // higher production cities will deal with this
val civilianUnit = cityInfo.getCenterTile().civilianUnit
if (civilianUnit != null && civilianUnit.hasUnique(Constants.settlerUnique)
if (civilianUnit != null && civilianUnit.hasUnique(UniqueType.FoundCity)
&& cityInfo.getCenterTile().getTilesInDistance(5).none { it.militaryUnit?.civInfo == civInfo })
modifier = 5f // there's a settler just sitting here, doing nothing - BAD
@ -115,10 +114,10 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
private fun addWorkBoatChoice() {
val buildableWorkboatUnits = cityInfo.cityConstructions.getConstructableUnits()
.filter { it.uniques.contains(Constants.workBoatsUnique)
.filter { it.hasUnique(UniqueType.CreateWaterImprovements)
&& Automation.allowSpendingResource(civInfo, it) }
val canBuildWorkboat = buildableWorkboatUnits.any()
&& !cityInfo.getTiles().any { it.civilianUnit?.hasUnique(Constants.workBoatsUnique) == true }
&& !cityInfo.getTiles().any { it.civilianUnit?.hasUnique(UniqueType.CreateWaterImprovements) == true }
if (!canBuildWorkboat) return
val tilesThatNeedWorkboat = cityInfo.getTiles()
.filter { it.isWater && it.hasViewableResource(civInfo) && it.improvement == null }.toList()
@ -139,9 +138,9 @@ class ConstructionAutomation(val cityConstructions: CityConstructions){
private fun addWorkerChoice() {
val workerEquivalents = civInfo.gameInfo.ruleSet.units.values
.filter { it.uniques.any {
unique -> unique.equalsPlaceholderText(Constants.canBuildImprovements)
} && it.isBuildable(cityConstructions)
.filter {
it.hasUnique(UniqueType.BuildImprovements)
&& it.isBuildable(cityConstructions)
&& Automation.allowSpendingResource(civInfo, it) }
if (workerEquivalents.isEmpty()) return // for mods with no worker units
if (civInfo.getIdleUnits().any { it.isAutomated() && it.hasUniqueToBuildImprovements })

View File

@ -689,12 +689,12 @@ object NextTurnAutomation {
if (civInfo.cities.none() || civInfo.getHappiness() <= civInfo.cities.size + 5) return
val settlerUnits = civInfo.gameInfo.ruleSet.units.values
.filter { it.uniques.contains(Constants.settlerUnique) && it.isBuildable(civInfo) }
.filter { it.hasUnique(UniqueType.FoundCity) && it.isBuildable(civInfo) }
if (settlerUnits.isEmpty()) return
if (civInfo.getCivUnits().none { it.hasUnique(Constants.settlerUnique) }
if (civInfo.getCivUnits().none { it.hasUnique(UniqueType.FoundCity) }
&& civInfo.cities.none {
val currentConstruction = it.cityConstructions.getCurrentConstruction()
currentConstruction is BaseUnit && currentConstruction.uniques.contains(Constants.settlerUnique)
currentConstruction is BaseUnit && currentConstruction.hasUnique(UniqueType.FoundCity)
}) {
val bestCity = civInfo.cities.maxByOrNull { it.cityStats.currentCityStats.production }!!

View File

@ -159,17 +159,23 @@ object SpecificUnitAutomation {
&& !unit.civInfo.isCityState() // ..unless you're a city state that was unable to settle its city on turn 1
&& unit.getDamageFromTerrain() < unit.health) return // Also make sure we won't die waiting
val tilesNearCities = unit.civInfo.gameInfo.getCities().asSequence()
.flatMap {
val distanceAwayFromCity =
if (unit.civInfo.knows(it.civInfo)
// If the CITY OWNER knows that the UNIT OWNER agreed not to settle near them
&& it.civInfo.getDiplomacyManager(unit.civInfo).hasFlag(DiplomacyFlags.AgreedToNotSettleNearUs))
6
else 3
it.getCenterTile().getTilesInDistance(distanceAwayFromCity)
val tilesNearCities = sequence {
for (city in unit.civInfo.gameInfo.getCities()) {
val center = city.getCenterTile()
if (unit.civInfo.knows(city.civInfo) &&
// If the CITY OWNER knows that the UNIT OWNER agreed not to settle near them
city.civInfo.getDiplomacyManager(unit.civInfo).hasFlag(DiplomacyFlags.AgreedToNotSettleNearUs)
) {
yieldAll(center.getTilesInDistance(6))
continue
}
.toSet()
for (tile in center.getTilesAtDistance(3)) {
if (tile.getContinent() == center.getContinent())
yield(tile)
}
yieldAll(center.getTilesInDistance(2))
}
}.toSet()
// This is to improve performance - instead of ranking each tile in the area up to 19 times, do it once.
val nearbyTileRankings = unit.getTile().getTilesInDistance(7)

View File

@ -101,7 +101,7 @@ object UnitAutomation {
if (unit.isCivilian()) {
if (tryRunAwayIfNeccessary(unit)) return
if (unit.hasUnique(Constants.settlerUnique))
if (unit.hasUnique(UniqueType.FoundCity))
return SpecificUnitAutomation.automateSettlerActions(unit)
if (unit.hasUniqueToBuildImprovements)
@ -119,7 +119,7 @@ object UnitAutomation {
)
return SpecificUnitAutomation.enhanceReligion(unit)
if (unit.hasUnique(Constants.workBoatsUnique))
if (unit.hasUnique(UniqueType.CreateWaterImprovements))
return SpecificUnitAutomation.automateWorkBoats(unit)
if (unit.hasUnique("Bonus for units in 2 tile radius 15%"))
@ -311,7 +311,7 @@ object UnitAutomation {
.firstOrNull {
val tile = it.currentTile
it.isCivilian() &&
(it.hasUnique(Constants.settlerUnique) || unit.isGreatPerson())
(it.hasUnique(UniqueType.FoundCity) || unit.isGreatPerson())
&& tile.militaryUnit == null && unit.movement.canMoveTo(tile) && unit.movement.canReach(tile)
} ?: return false
unit.movement.headTowards(settlerOrGreatPersonToAccompany.currentTile)

View File

@ -1,6 +1,5 @@
package com.unciv.logic.civilization
import com.unciv.Constants
import com.unciv.logic.automation.NextTurnAutomation
import com.unciv.logic.civilization.diplomacy.*
import com.unciv.models.metadata.GameSpeed
@ -23,7 +22,7 @@ class CityStateFunctions(val civInfo: CivilizationInfo) {
/** Attempts to initialize the city state, returning true if successful. */
fun initCityState(ruleset: Ruleset, startingEra: String, unusedMajorCivs: Collection<String>): Boolean {
val cityStateType = ruleset.nations[civInfo.civName]?.cityStateType
if (cityStateType == null) return false
?: return false
val startingTechs = ruleset.technologies.values.filter { it.uniques.contains("Starting tech") }
for (tech in startingTechs)
@ -406,9 +405,8 @@ class CityStateFunctions(val civInfo: CivilizationInfo) {
if (!civInfo.isCityState()) throw Exception("You can only demand workers from City-States!")
val buildableWorkerLikeUnits = civInfo.gameInfo.ruleSet.units.filter {
it.value.uniqueObjects.any { unique -> unique.placeholderText == Constants.canBuildImprovements }
&& it.value.isCivilian()
&& it.value.isBuildable(civInfo)
it.value.hasUnique(UniqueType.BuildImprovements) &&
it.value.isCivilian() && it.value.isBuildable(civInfo)
}
if (buildableWorkerLikeUnits.isEmpty()) return // Bad luck?
demandingCiv.placeUnitNearTile(civInfo.getCapital().location, buildableWorkerLikeUnits.keys.random())

View File

@ -72,9 +72,9 @@ class CivInfoTransientUpdater(val civInfo: CivilizationInfo) {
// And so, sequences to the rescue!
val ownedTiles = civInfo.cities.asSequence().flatMap { it.getTiles() }
newViewableTiles.addAll(ownedTiles)
val neighboringUnownedTiles = ownedTiles.flatMap { it.neighbors.filter { it.getOwner() != civInfo } }
val neighboringUnownedTiles = ownedTiles.flatMap { tile -> tile.neighbors.filter { it.getOwner() != civInfo } }
newViewableTiles.addAll(neighboringUnownedTiles)
newViewableTiles.addAll(civInfo.getCivUnits().flatMap { it.viewableTiles.asSequence().filter { it.getOwner() != civInfo } })
newViewableTiles.addAll(civInfo.getCivUnits().flatMap { unit -> unit.viewableTiles.asSequence().filter { it.getOwner() != civInfo } })
if (!civInfo.isCityState()) {
for (otherCiv in civInfo.getKnownCivs()) {
@ -110,9 +110,7 @@ class CivInfoTransientUpdater(val civInfo: CivilizationInfo) {
}
if (civInfo.hasUnique("100 Gold for discovering a Natural Wonder (bonus enhanced to 500 Gold if first to discover it)")) {
if (!discoveredNaturalWonders.contains(tile.naturalWonder!!))
goldGained += 500
else goldGained += 100
goldGained += if (discoveredNaturalWonders.contains(tile.naturalWonder!!)) 100 else 500
}
if (goldGained > 0) {

View File

@ -277,7 +277,7 @@ class MapUnit {
cannotEnterOceanTiles = hasUnique(UniqueType.CannotEnterOcean)
cannotEnterOceanTilesUntilAstronomy = hasUnique(UniqueType.CannotEnterOceanUntilAstronomy)
hasUniqueToBuildImprovements = hasUnique(Constants.canBuildImprovements)
hasUniqueToBuildImprovements = hasUnique(UniqueType.BuildImprovements)
canEnterForeignTerrain =
hasUnique("May enter foreign tiles without open borders, but loses [] religious strength each turn it ends there")
|| hasUnique("May enter foreign tiles without open borders")
@ -1051,7 +1051,7 @@ class MapUnit {
}
fun canBuildImprovement(improvement: TileImprovement, tile: TileInfo = currentTile): Boolean {
val matchingUniques = getMatchingUniques(Constants.canBuildImprovements)
val matchingUniques = getMatchingUniques(UniqueType.BuildImprovements)
return matchingUniques.any { improvement.matchesFilter(it.params[0]) || tile.matchesTerrainFilter(it.params[0]) }
}

View File

@ -8,6 +8,7 @@ import com.unciv.models.stats.Stat
// parameterName values should be compliant with autogenerated values in TranslationFileWriter.generateStringsFromJSONs
// Eventually we'll merge the translation generation to take this as the source of that
@Suppress("unused") // Some are used only via enumerating the enum matching on parameterName
enum class UniqueParameterType(val parameterName:String) {
Number("amount") {
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
@ -145,6 +146,16 @@ enum class UniqueParameterType(val parameterName:String) {
else -> UniqueType.UniqueComplianceErrorSeverity.RulesetSpecific
}
},
/** should mirror TileImprovement.matchesFilter exactly */
ImprovementFilter("improvementFilter") {
private val knownValues = setOf("All", "All Road", "Great Improvement", "Great")
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
UniqueType.UniqueComplianceErrorSeverity? {
if (parameterText in knownValues) return null
if (ruleset.tileImprovements.containsKey(parameterText)) return null
return UniqueType.UniqueComplianceErrorSeverity.RulesetSpecific
}
},
Resource("resource") {
override fun getErrorSeverity(parameterText: String, ruleset: Ruleset):
UniqueType.UniqueComplianceErrorSeverity? = when (parameterText) {

View File

@ -1,6 +1,7 @@
package com.unciv.models.ruleset.unique
import com.badlogic.gdx.math.Vector2
import com.unciv.Constants
import com.unciv.logic.city.CityInfo
import com.unciv.logic.civilization.*
import com.unciv.logic.map.MapUnit
@ -33,7 +34,7 @@ object UniqueTriggerActivation {
OneTimeFreeUnit -> {
val unitName = unique.params[0]
val unit = civInfo.gameInfo.ruleSet.units[unitName]
if (chosenCity == null || unit == null || (unit.uniques.contains("Founds a new city") && civInfo.isOneCityChallenger()))
if (chosenCity == null || unit == null || (unit.hasUnique(UniqueType.FoundCity) && civInfo.isOneCityChallenger()))
return false
val placedUnit = civInfo.addUnit(unitName, chosenCity)
@ -49,7 +50,7 @@ object UniqueTriggerActivation {
OneTimeAmountFreeUnits -> {
val unitName = unique.params[1]
val unit = civInfo.gameInfo.ruleSet.units[unitName]
if (chosenCity == null || unit == null || (unit.uniques.contains("Founds a new city") && civInfo.isOneCityChallenger()))
if (chosenCity == null || unit == null || (unit.hasUnique(UniqueType.FoundCity) && civInfo.isOneCityChallenger()))
return false
val tilesUnitsWerePlacedOn: MutableList<Vector2> = mutableListOf()

View File

@ -124,7 +124,10 @@ enum class UniqueType(val text:String, vararg targets: UniqueTarget) {
///////////////////////////////////////// UNIT UNIQUES /////////////////////////////////////////
FoundCity("Founds a new city", UniqueTarget.Unit),
BuildImprovements("Can build [improvementFilter/terrainFilter] improvements on tiles", UniqueTarget.Unit),
CreateWaterImprovements("May create improvements on water resources", UniqueTarget.Unit),
Strength("[amount]% Strength", UniqueTarget.Unit, UniqueTarget.Global),
StrengthNearCapital("[amount]% Strength decreasing with distance from the capital", UniqueTarget.Unit),

View File

@ -403,7 +403,7 @@ class BaseUnit : RulesetObject(), INonPerpetualConstruction {
})
}
if (uniques.contains(Constants.settlerUnique) &&
if (hasUnique(UniqueType.FoundCity) &&
(civInfo.isCityState() || civInfo.isOneCityChallenger())
)
rejectionReasons.add(RejectionReason.NoSettlerForOneCityPlayers)

View File

@ -128,7 +128,7 @@ object UnitActions {
fun getWaterImprovementAction(unit: MapUnit): UnitAction? {
val tile = unit.currentTile
if (!tile.isWater || !unit.hasUnique(Constants.workBoatsUnique) || tile.resource == null) return null
if (!tile.isWater || !unit.hasUnique(UniqueType.CreateWaterImprovements) || tile.resource == null) return null
val improvementName = tile.getTileResource().improvement ?: return null
val improvement = tile.ruleset.tileImprovements[improvementName] ?: return null
@ -161,9 +161,11 @@ object UnitActions {
* (no movement left, too close to another city).
*/
fun getFoundCityAction(unit: MapUnit, tile: TileInfo): UnitAction? {
if (!unit.hasUnique("Founds a new city") || tile.isWater || tile.isImpassible()) return null
if (!unit.hasUnique(UniqueType.FoundCity) || tile.isWater || tile.isImpassible()) return null
if (unit.currentMovement <= 0 || tile.getTilesInDistance(3).any { it.isCityCenter() })
if (unit.currentMovement <= 0 ||
tile.getTilesInDistance(2).any { it.isCityCenter() } ||
tile.getTilesAtDistance(3).any { it.isCityCenter() && it.getContinent() == tile.getContinent() })
return UnitAction(UnitActionType.FoundCity, action = null)
val foundAction = {

View File

@ -14,6 +14,7 @@ import com.unciv.models.metadata.GameSettings
import com.unciv.models.metadata.Player
import com.unciv.models.ruleset.RulesetCache
import com.unciv.models.metadata.GameSetupInfo
import com.unciv.models.ruleset.unique.UniqueType
import org.junit.After
import org.junit.Assert
import org.junit.Before
@ -68,7 +69,7 @@ class SerializationTests {
// Found a city otherwise too many classes have no instance and are not tested
val civ = game.getCurrentPlayerCivilization()
val unit = civ.getCivUnits().first { it.hasUnique(Constants.settlerUnique) }
val unit = civ.getCivUnits().first { it.hasUnique(UniqueType.FoundCity) }
val tile = unit.getTile()
unit.civInfo.addCity(tile.position)
if (tile.ruleset.tileImprovements.containsKey("City center"))