mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-19 20:28:56 +07:00
Moddable city ranges (#11708)
* Added city bombard, work and expand range ModConstants * Changed some city range integers to city.getWorkRange() or some equivalent * Fixed city screen * Fixed create game error * Improved support for CityScreen when work range is higher than expand range * Improved WonderOverviewTab Style * Improved Civilization.modConstants * Improved random spacing * Changed WonderOverviewTab to use a constant again * Added comments in documentation
This commit is contained in:
@ -463,7 +463,7 @@ class GameInfo : IsPartOfGameInfoSerialization, HasGameInfoSerializationVersion
|
||||
thisPlayer,
|
||||
thisPlayer.cities.filter { city ->
|
||||
city.canBombard() &&
|
||||
enemyUnitsCloseToTerritory.any { tile -> tile.aerialDistanceTo(city.getCenterTile()) <= city.range }
|
||||
enemyUnitsCloseToTerritory.any { tile -> tile.aerialDistanceTo(city.getCenterTile()) <= city.getBombardRange() }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -411,12 +411,12 @@ object Automation {
|
||||
// Resources are good: less points
|
||||
if (tile.hasViewableResource(city.civ)) {
|
||||
if (tile.tileResource.resourceType != ResourceType.Bonus) score -= 105
|
||||
else if (distance <= 3) score -= 104
|
||||
else if (distance <= city.getWorkRange()) score -= 104
|
||||
} else {
|
||||
// Water tiles without resources aren't great
|
||||
if (tile.isWater) score += 25
|
||||
// Can't work it anyways
|
||||
if (distance > 3) score += 100
|
||||
if (distance > city.getWorkRange()) score += 100
|
||||
}
|
||||
|
||||
if (tile.naturalWonder != null) score -= 105
|
||||
@ -430,12 +430,12 @@ object Automation {
|
||||
for (adjacentTile in tile.neighbors.filter { it.getOwner() == null }) {
|
||||
val adjacentDistance = city.getCenterTile().aerialDistanceTo(adjacentTile)
|
||||
if (adjacentTile.hasViewableResource(city.civ) &&
|
||||
(adjacentDistance < 3 ||
|
||||
(adjacentDistance < city.getWorkRange() ||
|
||||
adjacentTile.tileResource.resourceType != ResourceType.Bonus
|
||||
)
|
||||
) score -= 1
|
||||
if (adjacentTile.naturalWonder != null) {
|
||||
if (adjacentDistance < 3) adjacentNaturalWonder = true
|
||||
if (adjacentDistance < city.getWorkRange()) adjacentNaturalWonder = true
|
||||
score -= 1
|
||||
}
|
||||
}
|
||||
|
@ -179,7 +179,7 @@ class ConstructionAutomation(val cityConstructions: CityConstructions) {
|
||||
|
||||
val civilianUnit = city.getCenterTile().civilianUnit
|
||||
if (civilianUnit != null && civilianUnit.hasUnique(UniqueType.FoundCity)
|
||||
&& city.getCenterTile().getTilesInDistance(5).none { it.militaryUnit?.civ == civInfo })
|
||||
&& city.getCenterTile().getTilesInDistance(city.getExpandRange()).none { it.militaryUnit?.civ == civInfo })
|
||||
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
|
||||
|
@ -491,10 +491,9 @@ object NextTurnAutomation {
|
||||
// Otherwise, AI tries to produce settlers when it can hardly sustain itself
|
||||
.filter { city ->
|
||||
!workersBuildableForThisCiv
|
||||
|| city.getCenterTile().getTilesInDistance(2).count { it.improvement!=null } > 1
|
||||
|| city.getCenterTile().getTilesInDistance(3).any { it.civilianUnit?.hasUnique(UniqueType.BuildImprovements)==true }
|
||||
}
|
||||
.maxByOrNull { it.cityStats.currentCityStats.production }
|
||||
|| city.getCenterTile().getTilesInDistance(civInfo.modConstants.cityWorkRange - 1 ).count { it.improvement != null } > 1
|
||||
|| city.getCenterTile().getTilesInDistance(civInfo.modConstants.cityWorkRange).any { it.civilianUnit?.hasUnique(UniqueType.BuildImprovements) == true }
|
||||
}.maxByOrNull { it.cityStats.currentCityStats.production }
|
||||
?: return
|
||||
if (bestCity.cityConstructions.getBuiltBuildings().count() > 1) // 2 buildings or more, otherwise focus on self first
|
||||
bestCity.cityConstructions.currentConstructionFromQueue = settlerUnits.minByOrNull { it.cost }!!.name
|
||||
|
@ -201,7 +201,7 @@ object ReligionAutomation {
|
||||
var score = 0f
|
||||
|
||||
for (city in civInfo.cities) {
|
||||
for (tile in city.getCenterTile().getTilesInDistance(3)) {
|
||||
for (tile in city.getCenterTile().getTilesInDistance(city.getWorkRange())) {
|
||||
val tileScore = beliefBonusForTile(belief, tile, city)
|
||||
score += tileScore * when {
|
||||
city.workedTiles.contains(tile.position) -> 8
|
||||
|
@ -428,7 +428,7 @@ object UnitAutomation {
|
||||
|
||||
val tilesWithinBombardmentRange = unit.currentTile.getTilesInDistance(3)
|
||||
.filter { it.isCityCenter() && it.getCity()!!.civ.isAtWarWith(unit.civ) }
|
||||
.flatMap { it.getTilesInDistance(it.getCity()!!.range) }
|
||||
.flatMap { it.getTilesInDistance(it.getCity()!!.getBombardRange()) }
|
||||
|
||||
val tilesWithTerrainDamage = unit.currentTile.getTilesInDistance(3)
|
||||
.filter { unit.getDamageFromTerrain(it) > 0 }
|
||||
|
@ -393,7 +393,7 @@ class WorkerAutomation(
|
||||
// Check if it is not an unowned neighboring tile that can be in city range
|
||||
&& !(ruleSet.tileImprovements[improvementName]!!.hasUnique(UniqueType.CanBuildOutsideBorders)
|
||||
&& tile.neighbors.any { it.getOwner() == unit.civ && it.owningCity != null
|
||||
&& tile.aerialDistanceTo(it.owningCity!!.getCenterTile()) <= 3 } ))
|
||||
&& tile.aerialDistanceTo(it.owningCity!!.getCenterTile()) <= civInfo.modConstants.cityWorkRange } ))
|
||||
return 0f
|
||||
|
||||
val stats = tile.stats.getStatDiffForImprovement(improvement, civInfo, tile.getCity(), localUniqueCache)
|
||||
|
@ -137,7 +137,7 @@ object TargetHelper {
|
||||
|
||||
/** Get a list of visible tiles which have something attackable */
|
||||
fun getBombardableTiles(city: City): Sequence<Tile> =
|
||||
city.getCenterTile().getTilesInDistance(city.range)
|
||||
city.getCenterTile().getTilesInDistance(city.getBombardRange())
|
||||
.filter { it.isVisible(city.civ) && containsAttackableEnemy(it, CityCombatant(city)) }
|
||||
|
||||
}
|
||||
|
@ -40,9 +40,6 @@ class City : IsPartOfGameInfoSerialization, INamed {
|
||||
@Transient
|
||||
private lateinit var centerTile: Tile // cached for better performance
|
||||
|
||||
@Transient
|
||||
val range = 2
|
||||
|
||||
@Transient
|
||||
lateinit var tileMap: TileMap
|
||||
|
||||
@ -159,6 +156,10 @@ class City : IsPartOfGameInfoSerialization, INamed {
|
||||
fun isCapital(): Boolean = cityConstructions.getBuiltBuildings().any { it.hasUnique(UniqueType.IndicatesCapital) }
|
||||
fun isCoastal(): Boolean = centerTile.isCoastalTile()
|
||||
|
||||
fun getBombardRange(): Int = civ.gameInfo.ruleset.modOptions.constants.baseCityBombardRange
|
||||
fun getWorkRange(): Int = civ.gameInfo.ruleset.modOptions.constants.cityWorkRange
|
||||
fun getExpandRange(): Int = civ.gameInfo.ruleset.modOptions.constants.cityExpandRange
|
||||
|
||||
fun capitalCityIndicator(): Building? {
|
||||
val indicatorBuildings = getRuleset().buildings.values.asSequence()
|
||||
.filter { it.hasUnique(UniqueType.IndicatesCapital) }
|
||||
@ -262,7 +263,7 @@ class City : IsPartOfGameInfoSerialization, INamed {
|
||||
this.civ = civInfo
|
||||
tileMap = civInfo.gameInfo.tileMap
|
||||
centerTile = tileMap[location]
|
||||
tilesInRange = getCenterTile().getTilesInDistance(3).toHashSet()
|
||||
tilesInRange = getCenterTile().getTilesInDistance(getWorkRange()).toHashSet()
|
||||
population.city = this
|
||||
expansion.city = this
|
||||
expansion.setTransients()
|
||||
|
@ -100,7 +100,7 @@ class CityExpansionManager : IsPartOfGameInfoSerialization {
|
||||
return cost.roundToInt()
|
||||
}
|
||||
|
||||
fun getChoosableTiles() = city.getCenterTile().getTilesInDistance(5)
|
||||
fun getChoosableTiles() = city.getCenterTile().getTilesInDistance(city.getExpandRange())
|
||||
.filter { it.getOwner() == null }
|
||||
|
||||
fun chooseNewTileToOwn(): Tile? {
|
||||
|
@ -209,7 +209,7 @@ class CityPopulationManager : IsPartOfGameInfoSerialization {
|
||||
fun unassignExtraPopulation() {
|
||||
for (tile in city.workedTiles.map { city.tileMap[it] }) {
|
||||
if (tile.getOwner() != city.civ || tile.getWorkingCity() != city
|
||||
|| tile.aerialDistanceTo(city.getCenterTile()) > 3)
|
||||
|| tile.aerialDistanceTo(city.getCenterTile()) > city.getWorkRange())
|
||||
city.population.stopWorkingTile(tile.position)
|
||||
}
|
||||
|
||||
|
@ -95,7 +95,7 @@ class CityTurnManager(val city: City) {
|
||||
it.name != city.demandedResource && // Not same as last time
|
||||
!city.civ.hasResource(it.name) && // Not one we already have
|
||||
it.name in city.tileMap.resources && // Must exist somewhere on the map
|
||||
city.getCenterTile().getTilesInDistance(3).none { nearTile -> nearTile.resource == it.name } // Not in this city's radius
|
||||
city.getCenterTile().getTilesInDistance(city.getWorkRange()).none { nearTile -> nearTile.resource == it.name } // Not in this city's radius
|
||||
}
|
||||
|
||||
val chosenResource = candidates.randomOrNull()
|
||||
|
@ -136,6 +136,8 @@ class Civilization : IsPartOfGameInfoSerialization {
|
||||
@Transient
|
||||
var neutralRoads = HashSet<Vector2>()
|
||||
|
||||
val modConstants get() = gameInfo.ruleset.modOptions.constants
|
||||
|
||||
var playerType = PlayerType.AI
|
||||
|
||||
/** Used in online multiplayer for human players */
|
||||
|
@ -149,7 +149,7 @@ class ThreatManager(val civInfo: Civilization) {
|
||||
|
||||
val tilesWithinBombardmentRange = tilesWithEnemyUnits
|
||||
.filter { it.isCityCenter() && it.getCity()!!.civ.isAtWarWith(unit.civ) }
|
||||
.flatMap { it.getTilesInDistance(it.getCity()!!.range) }
|
||||
.flatMap { it.getTilesInDistance(it.getCity()!!.getBombardRange()) }
|
||||
|
||||
val tilesWithTerrainDamage = unit.currentTile.getTilesInDistance(distance)
|
||||
.filter { unit.getDamageFromTerrain(it) > 0 }
|
||||
|
@ -48,6 +48,10 @@ class ModConstants {
|
||||
var minimalCityDistance = 3
|
||||
var minimalCityDistanceOnDifferentContinents = 2
|
||||
|
||||
var baseCityBombardRange = 2
|
||||
var cityWorkRange = 3
|
||||
var cityExpandRange = 5
|
||||
|
||||
// Constants used to calculate Unit Upgrade gold Cost (can only be modded all-or-nothing)
|
||||
// This is a data class for one reason only: The equality implementation enables Gdx Json to omit it when default (otherwise only the individual fields are omitted)
|
||||
data class UnitUpgradeCost(
|
||||
|
@ -44,6 +44,7 @@ import com.unciv.ui.popups.closeAllPopups
|
||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
import com.unciv.ui.screens.basescreen.RecreateOnResize
|
||||
import com.unciv.ui.screens.worldscreen.WorldScreen
|
||||
import kotlin.math.max
|
||||
|
||||
class CityScreen(
|
||||
internal val city: City,
|
||||
@ -335,8 +336,9 @@ class CityScreen(
|
||||
}
|
||||
|
||||
private fun addTiles() {
|
||||
val viewRange = max(city.getExpandRange(), city.getWorkRange())
|
||||
val tileSetStrings = TileSetStrings()
|
||||
val cityTileGroups = city.getCenterTile().getTilesInDistance(5)
|
||||
val cityTileGroups = city.getCenterTile().getTilesInDistance(viewRange)
|
||||
.filter { selectedCiv.hasExplored(it) }
|
||||
.map { CityTileGroup(city, it, tileSetStrings, fireworks != null) }
|
||||
|
||||
@ -351,8 +353,8 @@ class CityScreen(
|
||||
for (tileGroup in tileGroups) {
|
||||
val xDifference = city.getCenterTile().position.x - tileGroup.tile.position.x
|
||||
val yDifference = city.getCenterTile().position.y - tileGroup.tile.position.y
|
||||
//if difference is bigger than 5 the tileGroup we are looking for is on the other side of the map
|
||||
if (xDifference > 5 || xDifference < -5 || yDifference > 5 || yDifference < -5) {
|
||||
//if difference is bigger than the expansion range the tileGroup we are looking for is on the other side of the map
|
||||
if (xDifference > viewRange || xDifference < -viewRange || yDifference > viewRange || yDifference < -viewRange) {
|
||||
//so we want to unwrap its position
|
||||
tilesToUnwrap.add(tileGroup)
|
||||
}
|
||||
|
@ -244,12 +244,12 @@ class WonderInfo {
|
||||
}
|
||||
if (status == WonderStatus.NotFound && !knownFromQuest(viewingPlayer, name)) continue
|
||||
val city = if (status == WonderStatus.NotFound) null
|
||||
else tile.getTilesInDistance(5)
|
||||
.filter { it.isCityCenter() }
|
||||
.filter { viewingPlayer.knows(it.getOwner()!!) }
|
||||
.filter { viewingPlayer.hasExplored(it) }
|
||||
.sortedBy { it.aerialDistanceTo(tile) }
|
||||
.firstOrNull()?.getCity()
|
||||
else gameInfo.getCities()
|
||||
.filter { it.getCenterTile().aerialDistanceTo(tile) <= 5
|
||||
&& viewingPlayer.knows(it.civ)
|
||||
&& viewingPlayer.hasExplored(it.getCenterTile()) }
|
||||
.sortedBy { it.getCenterTile().aerialDistanceTo(tile) }
|
||||
.firstOrNull()
|
||||
wonders[index + wonderCount] = WonderInfo(
|
||||
name, CivilopediaCategories.Terrain,
|
||||
"Natural Wonders", Color.FOREST, status, civ, city, tile
|
||||
|
@ -167,7 +167,8 @@ class ImprovementPickerScreen(
|
||||
&& !improvement.isRoad()
|
||||
&& stats.values.any { it > 0f }
|
||||
&& !improvement.name.startsWith(Constants.remove)
|
||||
&& !tile.getTilesInDistance(3).any { it.isCityCenter() && it.getCity()!!.civ == currentPlayerCiv }
|
||||
&& !tile.getTilesInDistance(currentPlayerCiv.modConstants.cityWorkRange)
|
||||
.any { it.isCityCenter() && it.getCity()!!.civ == currentPlayerCiv }
|
||||
)
|
||||
labelText += "\n" + "Not in city work range".tr()
|
||||
|
||||
|
@ -192,6 +192,9 @@ and city distance in another. In case of conflicts, there is no guarantee which
|
||||
| cityStrengthFromTechsExponent | Float | 2.8 | [^B] |
|
||||
| cityStrengthFromTechsFullMultiplier | Float | 1.0 | [^B] |
|
||||
| cityStrengthFromGarrison | Float | 0.2 | [^B] |
|
||||
| baseCityBombardRange | Int | 2 | [^S] |
|
||||
| cityWorkRange | Int | 3 | [^T] |
|
||||
| cityExpandRange | Int | 5 | [^U] |
|
||||
| unitSupplyPerPopulation | Float | 0.5 | [^C] |
|
||||
| minimalCityDistance | Int | 3 | [^D] |
|
||||
| minimalCityDistanceOnDifferentContinents | Int | 2 | [^D] |
|
||||
@ -225,6 +228,9 @@ Legend:
|
||||
defensiveBuildingStrength
|
||||
where %techs is the percentage of techs in the tech tree that are complete
|
||||
If no techs exist in this ruleset, %techs = 0.5 (=50%)
|
||||
- [^S]: The distance that cities can attack
|
||||
- [^T]: The tiles in distance that population in cities can work on. Note: Higher values may lead to performace issues and may cause bugs. cityWorkRange may be greater than cityExpandRange.
|
||||
- [^U]: The distance that cities can expand their borders to. Note: Higher values may lead to performace issues and may cause bugs.
|
||||
- [^C]: Formula for Unit Supply:
|
||||
Supply = unitSupplyBase (difficulties.json)
|
||||
unitSupplyPerCity \* amountOfCities + (difficulties.json)
|
||||
|
Reference in New Issue
Block a user