mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-28 06:16:37 -04:00
StartingLocation-Improvements-be-gone phase 1 (#4951)
This commit is contained in:
parent
55f2bca9c7
commit
b4ad34988c
@ -354,8 +354,7 @@ class GameInfo {
|
|||||||
tile.terrainFeatures.remove(terrainFeature)
|
tile.terrainFeatures.remove(terrainFeature)
|
||||||
if (tile.resource != null && !ruleSet.tileResources.containsKey(tile.resource!!))
|
if (tile.resource != null && !ruleSet.tileResources.containsKey(tile.resource!!))
|
||||||
tile.resource = null
|
tile.resource = null
|
||||||
if (tile.improvement != null && !ruleSet.tileImprovements.containsKey(tile.improvement!!)
|
if (tile.improvement != null && !ruleSet.tileImprovements.containsKey(tile.improvement!!))
|
||||||
&& !tile.improvement!!.startsWith("StartingLocation ")) // To not remove the starting locations in GameStarter.startNewGame()
|
|
||||||
tile.improvement = null
|
tile.improvement = null
|
||||||
|
|
||||||
for (unit in tile.getUnits()) {
|
for (unit in tile.getUnits()) {
|
||||||
|
@ -20,53 +20,70 @@ import kotlin.collections.HashMap
|
|||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
object GameStarter {
|
object GameStarter {
|
||||||
|
// temporary instrumentation while tuning/debugging
|
||||||
|
private const val consoleOutput = true
|
||||||
|
private const val consoleTimings = true
|
||||||
|
|
||||||
fun startNewGame(gameSetupInfo: GameSetupInfo): GameInfo {
|
fun startNewGame(gameSetupInfo: GameSetupInfo): GameInfo {
|
||||||
|
if (consoleOutput || consoleTimings)
|
||||||
|
println("\nGameStarter run with parameters ${gameSetupInfo.gameParameters}, map ${gameSetupInfo.mapParameters}")
|
||||||
|
|
||||||
val gameInfo = GameInfo()
|
val gameInfo = GameInfo()
|
||||||
|
lateinit var tileMap: TileMap
|
||||||
|
|
||||||
gameInfo.gameParameters = gameSetupInfo.gameParameters
|
gameInfo.gameParameters = gameSetupInfo.gameParameters
|
||||||
val ruleset = RulesetCache.getComplexRuleset(gameInfo.gameParameters.mods)
|
val ruleset = RulesetCache.getComplexRuleset(gameInfo.gameParameters.mods)
|
||||||
|
|
||||||
if (gameSetupInfo.mapParameters.name != "") {
|
if (gameSetupInfo.mapParameters.name != "") runAndMeasure("loadMap") {
|
||||||
gameInfo.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 {
|
} else runAndMeasure("generateMap") {
|
||||||
gameInfo.tileMap = MapGenerator(ruleset).generateMap(gameSetupInfo.mapParameters)
|
tileMap = MapGenerator(ruleset).generateMap(gameSetupInfo.mapParameters)
|
||||||
gameInfo.tileMap.mapParameters = gameSetupInfo.mapParameters
|
tileMap.mapParameters = gameSetupInfo.mapParameters
|
||||||
}
|
}
|
||||||
|
|
||||||
|
runAndMeasure("addCivilizations") {
|
||||||
|
gameInfo.tileMap = tileMap
|
||||||
|
tileMap.gameInfo = 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
|
||||||
|
}
|
||||||
|
|
||||||
gameInfo.tileMap.gameInfo = gameInfo // need to set this transient before placing units in the map
|
runAndMeasure("Remove units") {
|
||||||
addCivilizations(gameSetupInfo.gameParameters, gameInfo, ruleset) // this is before gameInfo.setTransients, so gameInfo doesn't yet have the gameBasics
|
// Remove units for civs that aren't in this game
|
||||||
|
for (tile in tileMap.values)
|
||||||
|
for (unit in tile.getUnits())
|
||||||
|
if (gameInfo.civilizations.none { it.civName == unit.owner }) {
|
||||||
|
unit.currentTile = tile
|
||||||
|
unit.setTransients(ruleset)
|
||||||
|
unit.removeFromTile()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Remove units for civs that aren't in this game
|
runAndMeasure("setTransients") {
|
||||||
for (tile in gameInfo.tileMap.values)
|
tileMap.setTransients(ruleset) // if we're starting from a map with pre-placed units, they need the civs to exist first
|
||||||
for (unit in tile.getUnits())
|
tileMap.setStartingLocationsTransients()
|
||||||
if (gameInfo.civilizations.none { it.civName == unit.owner }) {
|
|
||||||
unit.currentTile = tile
|
|
||||||
unit.setTransients(ruleset)
|
|
||||||
unit.removeFromTile()
|
|
||||||
}
|
|
||||||
|
|
||||||
gameInfo.tileMap.setTransients(ruleset) // if we're starting from a map with preplaced units, they need the civs to exist first
|
gameInfo.difficulty = gameSetupInfo.gameParameters.difficulty
|
||||||
|
|
||||||
gameInfo.difficulty = gameSetupInfo.gameParameters.difficulty
|
gameInfo.setTransients() // needs to be before placeBarbarianUnit because it depends on the tilemap having its gameInfo set
|
||||||
|
}
|
||||||
|
|
||||||
|
runAndMeasure("Techs and Stats") {
|
||||||
|
addCivTechs(gameInfo, ruleset, gameSetupInfo)
|
||||||
|
|
||||||
gameInfo.setTransients() // needs to be before placeBarbarianUnit because it depends on the tilemap having its gameinfo set
|
addCivStats(gameInfo)
|
||||||
|
}
|
||||||
|
|
||||||
addCivTechs(gameInfo, ruleset, gameSetupInfo)
|
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!
|
||||||
addCivStats(gameInfo)
|
addCivStartingUnits(gameInfo)
|
||||||
|
}
|
||||||
// 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)
|
|
||||||
|
|
||||||
// remove starting locations once we're done
|
// remove starting locations once we're done
|
||||||
for (tile in gameInfo.tileMap.values) {
|
tileMap.clearStartingLocations()
|
||||||
if (tile.improvement != null && tile.improvement!!.startsWith("StartingLocation "))
|
|
||||||
tile.improvement = null
|
// set max starting movement for units loaded from map
|
||||||
// set max starting movement for units loaded from map
|
for (tile in tileMap.values) {
|
||||||
for (unit in tile.getUnits()) unit.currentMovement = unit.getMaxMovement().toFloat()
|
for (unit in tile.getUnits()) unit.currentMovement = unit.getMaxMovement().toFloat()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,6 +93,14 @@ object GameStarter {
|
|||||||
return gameInfo
|
return gameInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun runAndMeasure(text: String, action: ()->Unit) {
|
||||||
|
if (!consoleTimings) return action()
|
||||||
|
val startNanos = System.nanoTime()
|
||||||
|
action()
|
||||||
|
val delta = System.nanoTime() - startNanos
|
||||||
|
println("GameStarter.$text took ${delta/1000000L}.${(delta/10000L).rem(100)}ms")
|
||||||
|
}
|
||||||
|
|
||||||
private fun addPlayerIntros(gameInfo: GameInfo) {
|
private fun addPlayerIntros(gameInfo: GameInfo) {
|
||||||
gameInfo.civilizations.filter {
|
gameInfo.civilizations.filter {
|
||||||
// isNotEmpty should also exclude a spectator
|
// isNotEmpty should also exclude a spectator
|
||||||
@ -139,6 +164,8 @@ object GameStarter {
|
|||||||
availableCivNames.removeAll(newGameParameters.players.map { it.chosenCiv })
|
availableCivNames.removeAll(newGameParameters.players.map { it.chosenCiv })
|
||||||
availableCivNames.remove(Constants.barbarians)
|
availableCivNames.remove(Constants.barbarians)
|
||||||
|
|
||||||
|
val startingTechs = ruleset.technologies.values.filter { it.uniques.contains("Starting tech") }
|
||||||
|
|
||||||
if (!newGameParameters.noBarbarians && ruleset.nations.containsKey(Constants.barbarians)) {
|
if (!newGameParameters.noBarbarians && ruleset.nations.containsKey(Constants.barbarians)) {
|
||||||
val barbarianCivilization = CivilizationInfo(Constants.barbarians)
|
val barbarianCivilization = CivilizationInfo(Constants.barbarians)
|
||||||
gameInfo.civilizations.add(barbarianCivilization)
|
gameInfo.civilizations.add(barbarianCivilization)
|
||||||
@ -149,44 +176,36 @@ object GameStarter {
|
|||||||
else availableCivNames.pop()
|
else availableCivNames.pop()
|
||||||
|
|
||||||
val playerCiv = CivilizationInfo(nationName)
|
val playerCiv = CivilizationInfo(nationName)
|
||||||
for (tech in ruleset.technologies.values.filter { it.uniques.contains("Starting tech") })
|
for (tech in startingTechs)
|
||||||
playerCiv.tech.techsResearched.add(tech.name) // can't be .addTechnology because the civInfo isn't assigned yet
|
playerCiv.tech.techsResearched.add(tech.name) // can't be .addTechnology because the civInfo isn't assigned yet
|
||||||
playerCiv.playerType = player.playerType
|
playerCiv.playerType = player.playerType
|
||||||
playerCiv.playerId = player.playerId
|
playerCiv.playerId = player.playerId
|
||||||
gameInfo.civilizations.add(playerCiv)
|
gameInfo.civilizations.add(playerCiv)
|
||||||
}
|
}
|
||||||
|
|
||||||
val cityStatesWithStartingLocations =
|
val civNamesWithStartingLocations = gameInfo.tileMap.startingLocationsByNation.keys
|
||||||
gameInfo.tileMap.values
|
|
||||||
.filter { it.improvement != null && it.improvement!!.startsWith("StartingLocation ") }
|
|
||||||
.map { it.improvement!!.replace("StartingLocation ", "") }
|
|
||||||
|
|
||||||
val availableCityStatesNames = Stack<String>()
|
val availableCityStatesNames = Stack<String>()
|
||||||
// since we shuffle and then order by, we end up with all the City-States with starting tiles first in a random order,
|
// since we shuffle and then order by, we end up with all the City-States with starting tiles first in a random order,
|
||||||
// and then all the other City-States in a random order! Because the sortedBy function is stable!
|
// and then all the other City-States in a random order! Because the sortedBy function is stable!
|
||||||
availableCityStatesNames.addAll(ruleset.nations.filter { it.value.isCityState() }.keys
|
availableCityStatesNames.addAll(ruleset.nations.filter { it.value.isCityState() }.keys
|
||||||
.shuffled().sortedByDescending { it in cityStatesWithStartingLocations })
|
.shuffled().sortedByDescending { it in civNamesWithStartingLocations })
|
||||||
|
|
||||||
val unusedMercantileResources = ruleset.tileResources.values.filter { it.unique == "Can only be created by Mercantile City-States" }.toMutableList()
|
val allMercantileResources = ruleset.tileResources.values.filter { it.unique == "Can only be created by Mercantile City-States" }.map { it.name }
|
||||||
|
val unusedMercantileResources = Stack<String>()
|
||||||
|
unusedMercantileResources.addAll(allMercantileResources.shuffled())
|
||||||
|
|
||||||
for (cityStateName in availableCityStatesNames.take(newGameParameters.numberOfCityStates)) {
|
for (cityStateName in availableCityStatesNames.take(newGameParameters.numberOfCityStates)) {
|
||||||
val civ = CivilizationInfo(cityStateName)
|
val civ = CivilizationInfo(cityStateName)
|
||||||
civ.cityStatePersonality = CityStatePersonality.values().random()
|
civ.cityStatePersonality = CityStatePersonality.values().random()
|
||||||
if (ruleset.nations[cityStateName]?.cityStateType == CityStateType.Mercantile) {
|
civ.cityStateResource = when {
|
||||||
if (!ruleset.tileResources.values.any { it.unique == "Can only be created by Mercantile City-States" }) {
|
ruleset.nations[cityStateName]?.cityStateType != CityStateType.Mercantile -> null
|
||||||
civ.cityStateResource = null
|
allMercantileResources.isEmpty() -> null
|
||||||
} else if (unusedMercantileResources.isNotEmpty()) {
|
unusedMercantileResources.empty() -> allMercantileResources.random() // When unused luxuries exhausted, random
|
||||||
// First pick an unused luxury if possible
|
else -> unusedMercantileResources.pop() // First pick an unused luxury if possible
|
||||||
val unusedResource = unusedMercantileResources.random()
|
|
||||||
civ.cityStateResource = unusedResource.name
|
|
||||||
unusedMercantileResources.remove(unusedResource)
|
|
||||||
} else {
|
|
||||||
// Then random
|
|
||||||
civ.cityStateResource = ruleset.tileResources.values.filter { it.unique == "Can only be created by Mercantile City-States" }.random().name
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
gameInfo.civilizations.add(civ)
|
gameInfo.civilizations.add(civ)
|
||||||
for (tech in ruleset.technologies.values.filter { it.uniques.contains("Starting tech") })
|
for (tech in startingTechs)
|
||||||
civ.tech.techsResearched.add(tech.name) // can't be .addTechnology because the civInfo isn't assigned yet
|
civ.tech.techsResearched.add(tech.name) // can't be .addTechnology because the civInfo isn't assigned yet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -194,38 +213,35 @@ object GameStarter {
|
|||||||
private fun addCivStartingUnits(gameInfo: GameInfo) {
|
private fun addCivStartingUnits(gameInfo: GameInfo) {
|
||||||
|
|
||||||
val ruleSet = gameInfo.ruleSet
|
val ruleSet = gameInfo.ruleSet
|
||||||
|
val tileMap = gameInfo.tileMap
|
||||||
val startingEra = gameInfo.gameParameters.startingEra
|
val startingEra = gameInfo.gameParameters.startingEra
|
||||||
var startingUnits: MutableList<String>
|
var startingUnits: MutableList<String>
|
||||||
var eraUnitReplacement: String
|
var eraUnitReplacement: String
|
||||||
|
|
||||||
val startScores = HashMap<TileInfo, Float>()
|
val startScores = HashMap<TileInfo, Float>(tileMap.values.size)
|
||||||
for (tile in gameInfo.tileMap.values) {
|
for (tile in tileMap.values) {
|
||||||
startScores[tile] = tile.getTileStartScore()
|
startScores[tile] = tile.getTileStartScore()
|
||||||
}
|
}
|
||||||
|
|
||||||
// First we get start locations for the major civs, on the second pass the city states (without predetermined starts) can squeeze in wherever
|
// First we get start locations for the major civs, on the second pass the city states (without predetermined starts) can squeeze in wherever
|
||||||
// I hear copying code is good
|
// I hear copying code is good
|
||||||
val cityStatesWithStartingLocations =
|
val civNamesWithStartingLocations = tileMap.startingLocationsByNation.keys
|
||||||
gameInfo.tileMap.values
|
val bestCivs = gameInfo.civilizations.filter { !it.isBarbarian() && (!it.isCityState() || it.civName in civNamesWithStartingLocations) }
|
||||||
.filter { it.improvement != null && it.improvement!!.startsWith("StartingLocation ") }
|
val bestLocations = getStartingLocations(bestCivs, tileMap, startScores)
|
||||||
.map { it.improvement!!.replace("StartingLocation ", "") }
|
for ((civ, tile) in bestLocations) {
|
||||||
val bestCivs = gameInfo.civilizations.filter { !it.isBarbarian() && (!it.isCityState() || it.civName in cityStatesWithStartingLocations) }
|
if (civ.civName in civNamesWithStartingLocations) // Already have explicit starting locations
|
||||||
val bestLocations = getStartingLocations(bestCivs, gameInfo.tileMap, startScores)
|
|
||||||
for (civ in bestCivs)
|
|
||||||
{
|
|
||||||
if (civ.isCityState()) // Already have explicit starting locations
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
// Mark the best start locations so we remember them for the second pass
|
// Mark the best start locations so we remember them for the second pass
|
||||||
bestLocations[civ]!!.improvement = "StartingLocation " + civ.civName
|
tileMap.addStartingLocation(civ.civName, tile)
|
||||||
}
|
}
|
||||||
|
|
||||||
val startingLocations = getStartingLocations(
|
val startingLocations = getStartingLocations(
|
||||||
gameInfo.civilizations.filter { !it.isBarbarian() },
|
gameInfo.civilizations.filter { !it.isBarbarian() },
|
||||||
gameInfo.tileMap, startScores)
|
tileMap, startScores)
|
||||||
|
|
||||||
val settlerLikeUnits = ruleSet.units.filter {
|
val settlerLikeUnits = ruleSet.units.filter {
|
||||||
it.value.uniqueObjects.any { it.placeholderText == Constants.settlerUnique }
|
it.value.uniqueObjects.any { unique -> unique.placeholderText == Constants.settlerUnique }
|
||||||
}
|
}
|
||||||
|
|
||||||
// no starting units for Barbarians and Spectators
|
// no starting units for Barbarians and Spectators
|
||||||
@ -242,7 +258,6 @@ object GameStarter {
|
|||||||
|
|
||||||
for (tile in startingLocation.getTilesInDistance(3)) {
|
for (tile in startingLocation.getTilesInDistance(3)) {
|
||||||
if (tile.improvement != null
|
if (tile.improvement != null
|
||||||
&& !tile.improvement!!.startsWith("StartingLocation")
|
|
||||||
&& tile.getTileImprovement()!!.isAncientRuinsEquivalent()
|
&& tile.getTileImprovement()!!.isAncientRuinsEquivalent()
|
||||||
) {
|
) {
|
||||||
tile.improvement = null // Remove ancient ruins in immediate vicinity
|
tile.improvement = null // Remove ancient ruins in immediate vicinity
|
||||||
@ -305,7 +320,7 @@ object GameStarter {
|
|||||||
}
|
}
|
||||||
if (unit == "Worker" && "Worker" !in ruleSet.units) {
|
if (unit == "Worker" && "Worker" !in ruleSet.units) {
|
||||||
val buildableWorkerLikeUnits = ruleSet.units.filter {
|
val buildableWorkerLikeUnits = ruleSet.units.filter {
|
||||||
it.value.uniqueObjects.any { it.placeholderText == Constants.canBuildImprovements }
|
it.value.uniqueObjects.any { unique -> unique.placeholderText == Constants.canBuildImprovements }
|
||||||
&& it.value.isBuildable(civ)
|
&& it.value.isBuildable(civ)
|
||||||
&& it.value.isCivilian()
|
&& it.value.isCivilian()
|
||||||
}
|
}
|
||||||
@ -353,18 +368,14 @@ object GameStarter {
|
|||||||
landTilesInBigEnoughGroup.addAll(tilesInGroup)
|
landTilesInBigEnoughGroup.addAll(tilesInGroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
val tilesWithStartingLocations = tileMap.values
|
|
||||||
.filter { it.improvement != null && it.improvement!!.startsWith("StartingLocation ") }
|
|
||||||
|
|
||||||
|
|
||||||
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 ->
|
||||||
when {
|
when {
|
||||||
tilesWithStartingLocations.any { it.improvement == "StartingLocation " + civ.civName } -> 1 // harshest requirements
|
civ.civName in tileMap.startingLocationsByNation -> 1 // harshest requirements
|
||||||
civ.nation.startBias.contains("Tundra") -> 2 // Tundra starts are hard to find, so let's do them first
|
civ.nation.startBias.contains("Tundra") -> 2 // Tundra starts are hard to find, so let's do them first
|
||||||
civ.nation.startBias.isNotEmpty() -> 3 // less harsh
|
civ.nation.startBias.isNotEmpty() -> 3 // less harsh
|
||||||
else -> 4
|
else -> 4 // no requirements
|
||||||
} // no requirements
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (minimumDistanceBetweenStartingLocations in tileMap.tileMatrix.size / 4 downTo 0) {
|
for (minimumDistanceBetweenStartingLocations in tileMap.tileMatrix.size / 4 downTo 0) {
|
||||||
@ -375,7 +386,7 @@ object GameStarter {
|
|||||||
val startingLocations = HashMap<CivilizationInfo, TileInfo>()
|
val startingLocations = HashMap<CivilizationInfo, TileInfo>()
|
||||||
for (civ in civsOrderedByAvailableLocations) {
|
for (civ in civsOrderedByAvailableLocations) {
|
||||||
var startingLocation: TileInfo
|
var startingLocation: TileInfo
|
||||||
val presetStartingLocation = tilesWithStartingLocations.firstOrNull { it.improvement == "StartingLocation " + civ.civName }
|
val presetStartingLocation = tileMap.startingLocationsByNation[civ.civName]?.randomOrNull() // in case map editor is extended to allow alternate starting locations for a nation
|
||||||
var distanceToNext = minimumDistanceBetweenStartingLocations
|
var distanceToNext = minimumDistanceBetweenStartingLocations
|
||||||
|
|
||||||
if (presetStartingLocation != null) startingLocation = presetStartingLocation
|
if (presetStartingLocation != null) startingLocation = presetStartingLocation
|
||||||
@ -389,11 +400,14 @@ object GameStarter {
|
|||||||
var preferredTiles = freeTiles.toList()
|
var preferredTiles = freeTiles.toList()
|
||||||
|
|
||||||
for (startBias in civ.nation.startBias) {
|
for (startBias in civ.nation.startBias) {
|
||||||
if (startBias.startsWith("Avoid ")) {
|
preferredTiles = when {
|
||||||
val tileToAvoid = startBias.removePrefix("Avoid [").removeSuffix("]")
|
startBias.startsWith("Avoid [") -> {
|
||||||
preferredTiles = preferredTiles.filter { !it.matchesTerrainFilter(tileToAvoid) }
|
val tileToAvoid = startBias.removePrefix("Avoid [").removeSuffix("]")
|
||||||
} else if (startBias == Constants.coast) preferredTiles = preferredTiles.filter { it.isCoastalTile() }
|
preferredTiles.filter { !it.matchesTerrainFilter(tileToAvoid) }
|
||||||
else preferredTiles = preferredTiles.filter { it.matchesTerrainFilter(startBias) }
|
}
|
||||||
|
startBias == Constants.coast -> preferredTiles.filter { it.isCoastalTile() }
|
||||||
|
else -> preferredTiles.filter { it.matchesTerrainFilter(startBias) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startingLocation = if (preferredTiles.isNotEmpty()) preferredTiles.last() else freeTiles.last()
|
startingLocation = if (preferredTiles.isNotEmpty()) preferredTiles.last() else freeTiles.last()
|
||||||
|
@ -10,20 +10,32 @@ object MapSaver {
|
|||||||
fun json() = GameSaver.json()
|
fun json() = GameSaver.json()
|
||||||
|
|
||||||
private const val mapsFolder = "maps"
|
private const val mapsFolder = "maps"
|
||||||
|
private const val saveZipped = false
|
||||||
|
|
||||||
private fun getMap(mapName:String) = Gdx.files.local("$mapsFolder/$mapName")
|
private fun getMap(mapName:String) = Gdx.files.local("$mapsFolder/$mapName")
|
||||||
|
|
||||||
|
fun mapFromSavedString(mapString: String): TileMap {
|
||||||
|
val unzippedJson = try {
|
||||||
|
Gzip.unzip(mapString)
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
mapString
|
||||||
|
}
|
||||||
|
return mapFromJson(unzippedJson)
|
||||||
|
}
|
||||||
|
fun mapToSavedString(tileMap: TileMap): String {
|
||||||
|
val mapJson = json().toJson(tileMap)
|
||||||
|
return if (saveZipped) Gzip.zip(mapJson) else mapJson
|
||||||
|
}
|
||||||
|
|
||||||
fun saveMap(mapName: String,tileMap: TileMap) {
|
fun saveMap(mapName: String,tileMap: TileMap) {
|
||||||
getMap(mapName).writeString(Gzip.zip(json().toJson(tileMap)), false)
|
getMap(mapName).writeString(mapToSavedString(tileMap), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadMap(mapFile:FileHandle):TileMap {
|
fun loadMap(mapFile:FileHandle):TileMap {
|
||||||
val gzippedString = mapFile.readString()
|
return mapFromSavedString(mapFile.readString())
|
||||||
val unzippedJson = Gzip.unzip(gzippedString)
|
|
||||||
return json().fromJson(TileMap::class.java, unzippedJson)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMaps() = Gdx.files.local(mapsFolder).list()
|
fun getMaps(): Array<FileHandle> = Gdx.files.local(mapsFolder).list()
|
||||||
|
|
||||||
fun mapFromJson(json:String): TileMap = json().fromJson(TileMap::class.java, json)
|
private fun mapFromJson(json:String): TileMap = json().fromJson(TileMap::class.java, json)
|
||||||
}
|
}
|
@ -158,5 +158,6 @@ class MapParameters {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// For debugging and MapGenerator console output
|
// For debugging and MapGenerator console output
|
||||||
override fun toString() = "($mapSize ${if (worldWrap)"wrapped " else ""}$shape $type, Seed $seed, $elevationExponent/$temperatureExtremeness/$resourceRichness/$vegetationRichness/$rareFeaturesRichness/$maxCoastExtension/$tilesPerBiomeArea/$waterThreshold)"
|
override fun toString() = if (name.isNotEmpty()) "\"$name\""
|
||||||
|
else "($mapSize ${if (worldWrap)"wrapped " else ""}$shape $type, Seed $seed, $elevationExponent/$temperatureExtremeness/$resourceRichness/$vegetationRichness/$rareFeaturesRichness/$maxCoastExtension/$tilesPerBiomeArea/$waterThreshold)"
|
||||||
}
|
}
|
||||||
|
@ -733,7 +733,6 @@ class MapUnit {
|
|||||||
|
|
||||||
if (civInfo.isMajorCiv()
|
if (civInfo.isMajorCiv()
|
||||||
&& tile.improvement != null
|
&& tile.improvement != null
|
||||||
&& !tile.improvement!!.startsWith("StartingLocation ")
|
|
||||||
&& tile.getTileImprovement()!!.isAncientRuinsEquivalent()
|
&& tile.getTileImprovement()!!.isAncientRuinsEquivalent()
|
||||||
)
|
)
|
||||||
getAncientRuinBonus(tile)
|
getAncientRuinBonus(tile)
|
||||||
|
@ -659,8 +659,7 @@ open class TileInfo {
|
|||||||
out.add("Terrain feature [$terrainFeature] does not exist in ruleset!")
|
out.add("Terrain feature [$terrainFeature] does not exist in ruleset!")
|
||||||
if (resource != null && !ruleset.tileResources.containsKey(resource))
|
if (resource != null && !ruleset.tileResources.containsKey(resource))
|
||||||
out.add("Resource [$resource] does not exist in ruleset!")
|
out.add("Resource [$resource] does not exist in ruleset!")
|
||||||
if (improvement != null && !improvement!!.startsWith("StartingLocation")
|
if (improvement != null && !ruleset.tileImprovements.containsKey(improvement))
|
||||||
&& !ruleset.tileImprovements.containsKey(improvement))
|
|
||||||
out.add("Improvement [$improvement] does not exist in ruleset!")
|
out.add("Improvement [$improvement] does not exist in ruleset!")
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
@ -756,9 +755,9 @@ open class TileInfo {
|
|||||||
roadStatus = RoadStatus.None
|
roadStatus = RoadStatus.None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun normalizeTileImprovement(ruleset: Ruleset) {
|
private fun normalizeTileImprovement(ruleset: Ruleset) {
|
||||||
if (improvement!!.startsWith("StartingLocation")) {
|
// This runs from map editor too, so the Pseudo-improvements for starting locations need to stay.
|
||||||
|
if (improvement!!.startsWith(TileMap.startingLocationPrefix)) {
|
||||||
if (!isLand || getLastTerrain().impassable) improvement = null
|
if (!isLand || getLastTerrain().impassable) improvement = null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -10,11 +10,47 @@ import com.unciv.models.ruleset.Nation
|
|||||||
import com.unciv.models.ruleset.Ruleset
|
import com.unciv.models.ruleset.Ruleset
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
|
||||||
|
/** An Unciv map with all properties as produced by the [map editor][com.unciv.ui.mapeditor.MapEditorScreen]
|
||||||
|
* or [MapGenerator][com.unciv.logic.map.mapgenerator.MapGenerator]; or as part of a running [game][GameInfo].
|
||||||
|
*
|
||||||
|
* Note: Will be Serialized -> Take special care with lateinit and lazy!
|
||||||
|
*/
|
||||||
class TileMap {
|
class TileMap {
|
||||||
|
companion object {
|
||||||
|
const val startingLocationPrefix = "StartingLocation "
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To be backwards compatible, a json without a startingLocations element will be recognized by an entry with this marker
|
||||||
|
* New saved maps will never have this marker and will always have a serialized startingLocations list even if empty.
|
||||||
|
* New saved maps will also never have "StartingLocation" improvements, these _must_ be converted before use anywhere outside map editor.
|
||||||
|
*/
|
||||||
|
private const val legacyMarker = " Legacy "
|
||||||
|
}
|
||||||
|
|
||||||
|
//region Fields, Serialized
|
||||||
|
|
||||||
|
var mapParameters = MapParameters()
|
||||||
|
|
||||||
|
private var tileList = ArrayList<TileInfo>()
|
||||||
|
|
||||||
|
/** 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 nation Name of the nation
|
||||||
|
*/
|
||||||
|
private data class StartingLocation(val position: Vector2 = Vector2.Zero, val nation: String = "")
|
||||||
|
private val startingLocations = arrayListOf(StartingLocation(Vector2.Zero, legacyMarker))
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
//region Fields, Transient
|
||||||
|
|
||||||
|
/** Attention: lateinit will _stay uninitialized_ while in MapEditorScreen! */
|
||||||
@Transient
|
@Transient
|
||||||
lateinit var gameInfo: GameInfo
|
lateinit var gameInfo: GameInfo
|
||||||
|
|
||||||
|
/** Keep a copy of the [Ruleset] object passer to setTransients, for now only to allow subsequent setTransients without. Copied on [clone]. */
|
||||||
|
@Transient
|
||||||
|
var ruleset: Ruleset? = null
|
||||||
|
|
||||||
@Transient
|
@Transient
|
||||||
var tileMatrix = ArrayList<ArrayList<TileInfo?>>() // this works several times faster than a hashmap, the performance difference is really astounding
|
var tileMatrix = ArrayList<ArrayList<TileInfo?>>() // this works several times faster than a hashmap, the performance difference is really astounding
|
||||||
|
|
||||||
@ -33,25 +69,31 @@ class TileMap {
|
|||||||
@delegate:Transient
|
@delegate:Transient
|
||||||
val naturalWonders: List<String> by lazy { tileList.asSequence().filter { it.isNaturalWonder() }.map { it.naturalWonder!! }.distinct().toList() }
|
val naturalWonders: List<String> by lazy { tileList.asSequence().filter { it.isNaturalWonder() }.map { it.naturalWonder!! }.distinct().toList() }
|
||||||
|
|
||||||
var mapParameters = MapParameters()
|
// Excluded from Serialization by having no own backing field
|
||||||
|
|
||||||
private var tileList = ArrayList<TileInfo>()
|
|
||||||
|
|
||||||
val values: Collection<TileInfo>
|
val values: Collection<TileInfo>
|
||||||
get() = tileList
|
get() = tileList
|
||||||
|
|
||||||
|
@Transient
|
||||||
|
val startingLocationsByNation = HashMap<String,HashSet<TileInfo>>()
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
//region Constructors
|
||||||
|
|
||||||
/** for json parsing, we need to have a default constructor */
|
/** for json parsing, we need to have a default constructor */
|
||||||
constructor()
|
constructor()
|
||||||
|
|
||||||
/** generates an hexagonal map of given radius */
|
/** creates a hexagonal map of given radius (filled with grassland) */
|
||||||
constructor(radius: Int, ruleset: Ruleset, worldWrap: Boolean = false) {
|
constructor(radius: Int, ruleset: Ruleset, worldWrap: Boolean = false) {
|
||||||
|
startingLocations.clear()
|
||||||
for (vector in HexMath.getVectorsInDistance(Vector2.Zero, radius, worldWrap))
|
for (vector in HexMath.getVectorsInDistance(Vector2.Zero, radius, worldWrap))
|
||||||
tileList.add(TileInfo().apply { position = vector; baseTerrain = Constants.grassland })
|
tileList.add(TileInfo().apply { position = vector; baseTerrain = Constants.grassland })
|
||||||
setTransients(ruleset)
|
setTransients(ruleset)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** generates a rectangular map of given width and height*/
|
/** creates a rectangular map of given width and height (filled with grassland) */
|
||||||
constructor(width: Int, height: Int, ruleset: Ruleset, worldWrap: Boolean = false) {
|
constructor(width: Int, height: Int, ruleset: Ruleset, worldWrap: Boolean = false) {
|
||||||
|
startingLocations.clear()
|
||||||
|
|
||||||
// world-wrap maps must always have an even width, so round down
|
// world-wrap maps must always have an even width, so round down
|
||||||
val wrapAdjustedWidth = if (worldWrap && width % 2 != 0 ) width -1 else width
|
val wrapAdjustedWidth = if (worldWrap && width % 2 != 0 ) width -1 else width
|
||||||
|
|
||||||
@ -67,39 +109,57 @@ class TileMap {
|
|||||||
setTransients(ruleset)
|
setTransients(ruleset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
//region Operators and Standards
|
||||||
|
|
||||||
|
/** @return a deep-copy clone of the serializable fields, no transients initialized */
|
||||||
fun clone(): TileMap {
|
fun clone(): TileMap {
|
||||||
val toReturn = TileMap()
|
val toReturn = TileMap()
|
||||||
toReturn.tileList.addAll(tileList.map { it.clone() })
|
toReturn.tileList.addAll(tileList.map { it.clone() })
|
||||||
toReturn.mapParameters = mapParameters
|
toReturn.mapParameters = mapParameters
|
||||||
|
toReturn.ruleset = ruleset
|
||||||
|
toReturn.startingLocations.clear()
|
||||||
|
toReturn.startingLocations.ensureCapacity(startingLocations.size)
|
||||||
|
toReturn.startingLocations.addAll(startingLocations)
|
||||||
return toReturn
|
return toReturn
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun contains(vector: Vector2) = contains(vector.x.toInt(), vector.y.toInt())
|
operator fun contains(vector: Vector2) =
|
||||||
|
contains(vector.x.toInt(), vector.y.toInt())
|
||||||
|
|
||||||
fun contains(x: Int, y: Int): Boolean {
|
operator fun get(vector: Vector2) =
|
||||||
|
get(vector.x.toInt(), vector.y.toInt())
|
||||||
|
|
||||||
|
fun contains(x: Int, y: Int) =
|
||||||
|
getOrNull(x, y) != null
|
||||||
|
|
||||||
|
operator fun get(x: Int, y: Int) =
|
||||||
|
tileMatrix[x - leftX][y - bottomY]!!
|
||||||
|
|
||||||
|
/** @return tile at hex coordinates ([x],[y]) or null if they are outside the map. Does *not* respect world wrap, use [getIfTileExistsOrNull] for that. */
|
||||||
|
private fun getOrNull (x: Int, y: Int): TileInfo? {
|
||||||
val arrayXIndex = x - leftX
|
val arrayXIndex = x - leftX
|
||||||
if (arrayXIndex < 0 || arrayXIndex >= tileMatrix.size) return false
|
if (arrayXIndex < 0 || arrayXIndex >= tileMatrix.size) return null
|
||||||
val arrayYIndex = y - bottomY
|
val arrayYIndex = y - bottomY
|
||||||
if (arrayYIndex < 0 || arrayYIndex >= tileMatrix[arrayXIndex].size) return false
|
if (arrayYIndex < 0 || arrayYIndex >= tileMatrix[arrayXIndex].size) return null
|
||||||
return tileMatrix[arrayXIndex][arrayYIndex] != null
|
return tileMatrix[arrayXIndex][arrayYIndex]
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun get(x: Int, y: Int): TileInfo {
|
//endregion
|
||||||
val arrayXIndex = x - leftX
|
//region Pure Functions
|
||||||
val arrayYIndex = y - bottomY
|
|
||||||
return tileMatrix[arrayXIndex][arrayYIndex]!!
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun get(vector: Vector2): TileInfo {
|
|
||||||
return get(vector.x.toInt(), vector.y.toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/** @return All tiles in a hexagon of radius [distance], including the tile at [origin] and all up to [distance] steps away.
|
||||||
|
* Respects map edges and world wrap. */
|
||||||
fun getTilesInDistance(origin: Vector2, distance: Int): Sequence<TileInfo> =
|
fun getTilesInDistance(origin: Vector2, distance: Int): Sequence<TileInfo> =
|
||||||
getTilesInDistanceRange(origin, 0..distance)
|
getTilesInDistanceRange(origin, 0..distance)
|
||||||
|
|
||||||
|
/** @return All tiles in a hexagonal ring around [origin] with the distances in [range]. Excludes the [origin] tile unless [range] starts at 0.
|
||||||
|
* Respects map edges and world wrap. */
|
||||||
fun getTilesInDistanceRange(origin: Vector2, range: IntRange): Sequence<TileInfo> =
|
fun getTilesInDistanceRange(origin: Vector2, range: IntRange): Sequence<TileInfo> =
|
||||||
range.asSequence().flatMap { getTilesAtDistance(origin, it) }
|
range.asSequence().flatMap { getTilesAtDistance(origin, it) }
|
||||||
|
|
||||||
|
/** @return All tiles in a hexagonal ring 1 tile wide around [origin] with the [distance]. Contains the [origin] if and only if [distance] is <= 0.
|
||||||
|
* Respects map edges and world wrap. */
|
||||||
fun getTilesAtDistance(origin: Vector2, distance: Int): Sequence<TileInfo> =
|
fun getTilesAtDistance(origin: Vector2, distance: Int): Sequence<TileInfo> =
|
||||||
if (distance <= 0) // silently take negatives.
|
if (distance <= 0) // silently take negatives.
|
||||||
sequenceOf(get(origin))
|
sequenceOf(get(origin))
|
||||||
@ -133,6 +193,7 @@ class TileMap {
|
|||||||
}
|
}
|
||||||
}.filterNotNull()
|
}.filterNotNull()
|
||||||
|
|
||||||
|
/** @return tile at hex coordinates ([x],[y]) or null if they are outside the map. Respects map edges and world wrap. */
|
||||||
private fun getIfTileExistsOrNull(x: Int, y: Int): TileInfo? {
|
private fun getIfTileExistsOrNull(x: Int, y: Int): TileInfo? {
|
||||||
if (contains(x, y))
|
if (contains(x, y))
|
||||||
return get(x, y)
|
return get(x, y)
|
||||||
@ -156,6 +217,166 @@ class TileMap {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the clockPosition of [otherTile] seen from [tile]'s position
|
||||||
|
* Returns -1 if not neighbors
|
||||||
|
*/
|
||||||
|
fun getNeighborTileClockPosition(tile: TileInfo, otherTile: TileInfo): Int {
|
||||||
|
val radius = if (mapParameters.shape == MapShape.rectangular)
|
||||||
|
mapParameters.mapSize.width / 2
|
||||||
|
else mapParameters.mapSize.radius
|
||||||
|
|
||||||
|
val xDifference = tile.position.x - otherTile.position.x
|
||||||
|
val yDifference = tile.position.y - otherTile.position.y
|
||||||
|
val xWrapDifferenceBottom = tile.position.x - (otherTile.position.x - radius)
|
||||||
|
val yWrapDifferenceBottom = tile.position.y - (otherTile.position.y - radius)
|
||||||
|
val xWrapDifferenceTop = tile.position.x - (otherTile.position.x + radius)
|
||||||
|
val yWrapDifferenceTop = tile.position.y - (otherTile.position.y + radius)
|
||||||
|
|
||||||
|
return when {
|
||||||
|
xDifference == 1f && yDifference == 1f -> 6 // otherTile is below
|
||||||
|
xDifference == -1f && yDifference == -1f -> 12 // otherTile is above
|
||||||
|
xDifference == 1f || xWrapDifferenceBottom == 1f -> 4 // otherTile is bottom-right
|
||||||
|
yDifference == 1f || yWrapDifferenceBottom == 1f -> 8 // otherTile is bottom-left
|
||||||
|
xDifference == -1f || xWrapDifferenceTop == -1f -> 10 // otherTile is top-left
|
||||||
|
yDifference == -1f || yWrapDifferenceTop == -1f -> 2 // otherTile is top-right
|
||||||
|
else -> -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convert relative direction of [otherTile] seen from [tile]'s position into a vector
|
||||||
|
* in world coordinates of length sqrt(3), so that it can be used to go from tile center to
|
||||||
|
* the edge of the hex in that direction (meaning the center of the border between the hexes)
|
||||||
|
*/
|
||||||
|
fun getNeighborTilePositionAsWorldCoords(tile: TileInfo, otherTile: TileInfo): Vector2 =
|
||||||
|
HexMath.getClockDirectionToWorldVector(getNeighborTileClockPosition(tile, otherTile))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the closest position to (0, 0) outside the map which can be wrapped
|
||||||
|
* to the position of the given vector
|
||||||
|
*/
|
||||||
|
fun getUnWrappedPosition(position: Vector2): Vector2 {
|
||||||
|
if (!contains(position))
|
||||||
|
return position //The position is outside the map so its unwrapped already
|
||||||
|
|
||||||
|
val radius = if (mapParameters.shape == MapShape.rectangular)
|
||||||
|
mapParameters.mapSize.width / 2
|
||||||
|
else mapParameters.mapSize.radius
|
||||||
|
|
||||||
|
val vectorUnwrappedLeft = Vector2(position.x + radius, position.y - radius)
|
||||||
|
val vectorUnwrappedRight = Vector2(position.x - radius, position.y + radius)
|
||||||
|
|
||||||
|
return if (vectorUnwrappedRight.len() < vectorUnwrappedLeft.len())
|
||||||
|
vectorUnwrappedRight
|
||||||
|
else
|
||||||
|
vectorUnwrappedLeft
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return List of tiles visible from location [position] for a unit with sight range [sightDistance] */
|
||||||
|
fun getViewableTiles(position: Vector2, sightDistance: Int): List<TileInfo> {
|
||||||
|
val viewableTiles = getTilesInDistance(position, 1).toMutableList()
|
||||||
|
val currentTileHeight = get(position).getHeight()
|
||||||
|
|
||||||
|
for (i in 1..sightDistance) { // in each layer,
|
||||||
|
// This is so we don't use tiles in the same distance to "see over",
|
||||||
|
// that is to say, the "viewableTiles.contains(it) check will return false for neighbors from the same distance
|
||||||
|
val tilesToAddInDistanceI = ArrayList<TileInfo>()
|
||||||
|
|
||||||
|
for (cTile in getTilesAtDistance(position, i)) { // for each tile in that layer,
|
||||||
|
val cTileHeight = cTile.getHeight()
|
||||||
|
|
||||||
|
/*
|
||||||
|
Okay so, if we're looking at a tile from a to c with b in the middle,
|
||||||
|
we have several scenarios:
|
||||||
|
1. a>b - - I can see everything, b does not hide c
|
||||||
|
2. a==b
|
||||||
|
2.1 c>b - c is tall enough I can see it over b!
|
||||||
|
2.2 b blocks view from same-elevation tiles - hides c
|
||||||
|
2.3 none of the above - I can see c
|
||||||
|
3. a<b
|
||||||
|
3.1 b>=c - b hides c
|
||||||
|
3.2 b<c - c is tall enough I can see it over b!
|
||||||
|
|
||||||
|
This can all be summed up as "I can see c if a>b || c>b || (a==b && b !blocks same-elevation view)"
|
||||||
|
*/
|
||||||
|
|
||||||
|
val containsViewableNeighborThatCanSeeOver = cTile.neighbors.any {
|
||||||
|
bNeighbor: TileInfo ->
|
||||||
|
val bNeighborHeight = bNeighbor.getHeight()
|
||||||
|
viewableTiles.contains(bNeighbor) && (
|
||||||
|
currentTileHeight > bNeighborHeight // a>b
|
||||||
|
|| cTileHeight > bNeighborHeight // c>b
|
||||||
|
|| currentTileHeight == bNeighborHeight // a==b
|
||||||
|
&& !bNeighbor.hasUnique("Blocks line-of-sight from tiles at same elevation"))
|
||||||
|
}
|
||||||
|
if (containsViewableNeighborThatCanSeeOver) tilesToAddInDistanceI.add(cTile)
|
||||||
|
}
|
||||||
|
viewableTiles.addAll(tilesToAddInDistanceI)
|
||||||
|
}
|
||||||
|
|
||||||
|
return viewableTiles
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Strips all units from [TileMap]
|
||||||
|
* @return stripped [clone] of [TileMap]
|
||||||
|
*/
|
||||||
|
fun stripAllUnits(): TileMap {
|
||||||
|
return clone().apply { tileList.forEach { it.stripUnits() } }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Build a list of incompatibilities of a map with a ruleset for the new game loader
|
||||||
|
*
|
||||||
|
* Is run before setTransients, so make do without startingLocationsByNation
|
||||||
|
*/
|
||||||
|
fun getRulesetIncompatibility(ruleset: Ruleset): HashSet<String> {
|
||||||
|
val rulesetIncompatibilities = HashSet<String>()
|
||||||
|
for (set in values.map { it.getRulesetIncompatibility(ruleset) })
|
||||||
|
rulesetIncompatibilities.addAll(set)
|
||||||
|
for ((_, nationName) in startingLocations) {
|
||||||
|
if (nationName !in ruleset.nations)
|
||||||
|
rulesetIncompatibilities.add("Nation [$nationName] does not exist in ruleset!")
|
||||||
|
}
|
||||||
|
rulesetIncompatibilities.remove("")
|
||||||
|
return rulesetIncompatibilities
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
|
//region State-Changing Methods
|
||||||
|
|
||||||
|
/** Initialize transients - without, most operations, like [get] from coordinates, will fail.
|
||||||
|
* @param ruleset Required unless this is a clone of an initialized TileMap including one
|
||||||
|
* @param setUnitCivTransients when false Civ-specific parts of unit initialization are skipped, for the map editor.
|
||||||
|
*/
|
||||||
|
fun setTransients(ruleset: Ruleset? = null, setUnitCivTransients: Boolean = true) {
|
||||||
|
if (ruleset != null) this.ruleset = ruleset
|
||||||
|
if (this.ruleset == null) throw(IllegalStateException("TileMap.setTransients called without ruleset"))
|
||||||
|
|
||||||
|
if (tileMatrix.isEmpty()) {
|
||||||
|
val topY = tileList.asSequence().map { it.position.y.toInt() }.maxOrNull()!!
|
||||||
|
bottomY = tileList.asSequence().map { it.position.y.toInt() }.minOrNull()!!
|
||||||
|
val rightX = tileList.asSequence().map { it.position.x.toInt() }.maxOrNull()!!
|
||||||
|
leftX = tileList.asSequence().map { it.position.x.toInt() }.minOrNull()!!
|
||||||
|
|
||||||
|
for (x in leftX..rightX) {
|
||||||
|
val row = ArrayList<TileInfo?>()
|
||||||
|
for (y in bottomY..topY) row.add(null)
|
||||||
|
tileMatrix.add(row)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Yes the map generator calls this repeatedly, and we don't want to end up with an oversized tileMatrix
|
||||||
|
// rightX is -leftX or -leftX + 1
|
||||||
|
if (tileMatrix.size != 1 - 2 * leftX && tileMatrix.size != 2 - 2 * leftX)
|
||||||
|
throw(IllegalStateException("TileMap.setTransients called on existing tileMatrix of different size"))
|
||||||
|
}
|
||||||
|
|
||||||
|
for (tileInfo in values) {
|
||||||
|
tileMatrix[tileInfo.position.x.toInt() - leftX][tileInfo.position.y.toInt() - bottomY] = tileInfo
|
||||||
|
tileInfo.tileMap = this
|
||||||
|
tileInfo.ruleset = this.ruleset!!
|
||||||
|
tileInfo.setTerrainTransients()
|
||||||
|
tileInfo.setUnitTransients(setUnitCivTransients)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Tries to place the [unitName] into the [TileInfo] closest to the given [position]
|
/** Tries to place the [unitName] into the [TileInfo] closest to the given [position]
|
||||||
* @param position where to try to place the unit (or close - max 10 tiles distance)
|
* @param position where to try to place the unit (or close - max 10 tiles distance)
|
||||||
@ -224,64 +445,13 @@ class TileMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun getViewableTiles(position: Vector2, sightDistance: Int): List<TileInfo> {
|
/** Strips all units and starting locations from [TileMap] for specified [Player]
|
||||||
val viewableTiles = getTilesInDistance(position, 1).toMutableList()
|
|
||||||
val currentTileHeight = get(position).getHeight()
|
|
||||||
|
|
||||||
for (i in 1..sightDistance) { // in each layer,
|
|
||||||
// This is so we don't use tiles in the same distance to "see over",
|
|
||||||
// that is to say, the "viewableTiles.contains(it) check will return false for neighbors from the same distance
|
|
||||||
val tilesToAddInDistanceI = ArrayList<TileInfo>()
|
|
||||||
|
|
||||||
for (cTile in getTilesAtDistance(position, i)) { // for each tile in that layer,
|
|
||||||
val cTileHeight = cTile.getHeight()
|
|
||||||
|
|
||||||
/*
|
|
||||||
Okay so, if we're looking at a tile from a to c with b in the middle,
|
|
||||||
we have several scenarios:
|
|
||||||
1. a>b - - I can see everything, b does not hide c
|
|
||||||
2. a==b
|
|
||||||
2.1 c>b - c is tall enough I can see it over b!
|
|
||||||
2.2 b blocks view from same-elevation tiles - hides c
|
|
||||||
2.3 none of the above - I can see c
|
|
||||||
3. a<b
|
|
||||||
3.1 b>=c - b hides c
|
|
||||||
3.2 b<c - c is tall enough I can see it over b!
|
|
||||||
|
|
||||||
This can all be summed up as "I can see c if a>b || c>b || (a==b && b !blocks same-elevation view)"
|
|
||||||
*/
|
|
||||||
|
|
||||||
val containsViewableNeighborThatCanSeeOver = cTile.neighbors.any {
|
|
||||||
bNeighbor: TileInfo ->
|
|
||||||
val bNeighborHeight = bNeighbor.getHeight()
|
|
||||||
viewableTiles.contains(bNeighbor) && (
|
|
||||||
currentTileHeight > bNeighborHeight // a>b
|
|
||||||
|| cTileHeight > bNeighborHeight // c>b
|
|
||||||
|| currentTileHeight == bNeighborHeight // a==b
|
|
||||||
&& !bNeighbor.hasUnique("Blocks line-of-sight from tiles at same elevation"))
|
|
||||||
}
|
|
||||||
if (containsViewableNeighborThatCanSeeOver) tilesToAddInDistanceI.add(cTile)
|
|
||||||
}
|
|
||||||
viewableTiles.addAll(tilesToAddInDistanceI)
|
|
||||||
}
|
|
||||||
|
|
||||||
return viewableTiles
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Strips all units from [TileMap]
|
|
||||||
* @return stripped clone of [TileMap]
|
|
||||||
*/
|
|
||||||
fun stripAllUnits(): TileMap {
|
|
||||||
return clone().apply { tileList.forEach { it.stripUnits() } }
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Strips all units and starting location from [TileMap] for specified [Player]
|
|
||||||
* Operation in place
|
* Operation in place
|
||||||
* @param player units of player to be stripped off
|
* @param player units of this player will be removed
|
||||||
*/
|
*/
|
||||||
fun stripPlayer(player: Player) {
|
fun stripPlayer(player: Player) {
|
||||||
tileList.forEach {
|
tileList.forEach {
|
||||||
if (it.improvement == "StartingLocation " + player.chosenCiv) {
|
if (it.improvement == startingLocationPrefix + player.chosenCiv) {
|
||||||
it.improvement = null
|
it.improvement = null
|
||||||
}
|
}
|
||||||
for (unit in it.getUnits()) if (unit.owner == player.chosenCiv) unit.removeFromTile()
|
for (unit in it.getUnits()) if (unit.owner == player.chosenCiv) unit.removeFromTile()
|
||||||
@ -295,8 +465,8 @@ class TileMap {
|
|||||||
*/
|
*/
|
||||||
fun switchPlayersNation(player: Player, newNation: Nation) {
|
fun switchPlayersNation(player: Player, newNation: Nation) {
|
||||||
tileList.forEach {
|
tileList.forEach {
|
||||||
if (it.improvement == "StartingLocation " + player.chosenCiv) {
|
if (it.improvement == startingLocationPrefix + player.chosenCiv) {
|
||||||
it.improvement = "StartingLocation " + newNation.name
|
it.improvement = startingLocationPrefix + newNation.name
|
||||||
}
|
}
|
||||||
for (unit in it.getUnits()) if (unit.owner == player.chosenCiv) {
|
for (unit in it.getUnits()) if (unit.owner == player.chosenCiv) {
|
||||||
unit.owner = newNation.name
|
unit.owner = newNation.name
|
||||||
@ -305,80 +475,61 @@ class TileMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setTransients(ruleset: Ruleset, setUnitCivTransients: Boolean = true) { // In the map editor, no Civs or Game exist, so we won't set the unit transients
|
/**
|
||||||
val topY = tileList.asSequence().map { it.position.y.toInt() }.maxOrNull()!!
|
* Initialize startingLocations transients, including legacy support (maps saved with placeholder improvements)
|
||||||
bottomY = tileList.asSequence().map { it.position.y.toInt() }.minOrNull()!!
|
*/
|
||||||
val rightX = tileList.asSequence().map { it.position.x.toInt() }.maxOrNull()!!
|
fun setStartingLocationsTransients() {
|
||||||
leftX = tileList.asSequence().map { it.position.x.toInt() }.minOrNull()!!
|
if (startingLocations.size == 1 && startingLocations[0].nation == legacyMarker)
|
||||||
|
return translateStartingLocationsFromMap()
|
||||||
for (x in leftX..rightX) {
|
startingLocationsByNation.clear()
|
||||||
val row = ArrayList<TileInfo?>()
|
for ((position, nationName) in startingLocations) {
|
||||||
for (y in bottomY..topY) row.add(null)
|
val nationSet = startingLocationsByNation[nationName] ?: hashSetOf<TileInfo>().also { startingLocationsByNation[nationName] = it }
|
||||||
tileMatrix.add(row)
|
nationSet.add(get(position))
|
||||||
}
|
|
||||||
|
|
||||||
for (tileInfo in values) {
|
|
||||||
tileMatrix[tileInfo.position.x.toInt() - leftX][tileInfo.position.y.toInt() - bottomY] = tileInfo
|
|
||||||
tileInfo.tileMap = this
|
|
||||||
tileInfo.ruleset = ruleset
|
|
||||||
tileInfo.setTerrainTransients()
|
|
||||||
tileInfo.setUnitTransients(setUnitCivTransients)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the clockPosition of otherTile seen from tile's position
|
* Scan and remove placeholder improvements from map and build startingLocations from them
|
||||||
* Returns -1 if not neighbors
|
|
||||||
*/
|
*/
|
||||||
fun getNeighborTileClockPosition(tile: TileInfo, otherTile: TileInfo): Int {
|
fun translateStartingLocationsFromMap() {
|
||||||
val radius = if (mapParameters.shape == MapShape.rectangular)
|
startingLocations.clear()
|
||||||
mapParameters.mapSize.width / 2
|
tileList.asSequence()
|
||||||
else mapParameters.mapSize.radius
|
.filter { it.improvement?.startsWith(startingLocationPrefix) == true }
|
||||||
|
.map { it to StartingLocation(it.position, it.improvement!!.removePrefix(startingLocationPrefix)) }
|
||||||
|
.sortedBy { it.second.nation } // vanity, or to make diffs between un-gzipped map files easier
|
||||||
|
.forEach { (tile, startingLocation) ->
|
||||||
|
tile.improvement = null
|
||||||
|
startingLocations.add(startingLocation)
|
||||||
|
}
|
||||||
|
setStartingLocationsTransients()
|
||||||
|
}
|
||||||
|
|
||||||
val xDifference = tile.position.x - otherTile.position.x
|
/**
|
||||||
val yDifference = tile.position.y - otherTile.position.y
|
* Place placeholder improvements on the map for the startingLocations entries.
|
||||||
val xWrapDifferenceBottom = tile.position.x - (otherTile.position.x - radius)
|
*
|
||||||
val yWrapDifferenceBottom = tile.position.y - (otherTile.position.y - radius)
|
* **For use by the map editor only**
|
||||||
val xWrapDifferenceTop = tile.position.x - (otherTile.position.x + radius)
|
*
|
||||||
val yWrapDifferenceTop = tile.position.y - (otherTile.position.y + radius)
|
* This is a copy, the startingLocations array and transients are untouched.
|
||||||
|
* Any actual improvements on the tiles will be overwritten.
|
||||||
return when {
|
*/
|
||||||
xDifference == 1f && yDifference == 1f -> 6 // otherTile is below
|
fun translateStartingLocationsToMap() {
|
||||||
xDifference == -1f && yDifference == -1f -> 12 // otherTile is above
|
for ((position, nationName) in startingLocations) {
|
||||||
xDifference == 1f || xWrapDifferenceBottom == 1f -> 4 // otherTile is bottom-right
|
get(position).improvement = startingLocationPrefix + nationName
|
||||||
yDifference == 1f || yWrapDifferenceBottom == 1f -> 8 // otherTile is bottom-left
|
|
||||||
xDifference == -1f || xWrapDifferenceTop == -1f -> 10 // otherTile is top-left
|
|
||||||
yDifference == -1f || yWrapDifferenceTop == -1f -> 2 // otherTile is top-right
|
|
||||||
else -> -1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Convert relative direction of otherTile seen from tile's position into a vector
|
/** Adds a starting position, maintaining the transients */
|
||||||
* in world coordinates of length sqrt(3), so that it can be used to go from tile center to
|
fun addStartingLocation(nationName: String, tile: TileInfo) {
|
||||||
* the edge of the hex in that direction (meaning the center of the border between the hexes)
|
startingLocations.add(StartingLocation(tile.position, nationName))
|
||||||
*/
|
val nationSet = startingLocationsByNation[nationName] ?: hashSetOf<TileInfo>().also { startingLocationsByNation[nationName] = it }
|
||||||
fun getNeighborTilePositionAsWorldCoords(tile: TileInfo, otherTile: TileInfo): Vector2 =
|
nationSet.add(tile)
|
||||||
HexMath.getClockDirectionToWorldVector(getNeighborTileClockPosition(tile, otherTile))
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the closest position to (0, 0) outside the map which can be wrapped
|
|
||||||
* to the position of the given vector
|
|
||||||
*/
|
|
||||||
fun getUnWrappedPosition(position: Vector2): Vector2 {
|
|
||||||
if (!contains(position))
|
|
||||||
return position //The position is outside the map so its unwrapped already
|
|
||||||
|
|
||||||
var radius = mapParameters.mapSize.radius
|
|
||||||
if (mapParameters.shape == MapShape.rectangular)
|
|
||||||
radius = mapParameters.mapSize.width / 2
|
|
||||||
|
|
||||||
val vectorUnwrappedLeft = Vector2(position.x + radius, position.y - radius)
|
|
||||||
val vectorUnwrappedRight = Vector2(position.x - radius, position.y + radius)
|
|
||||||
|
|
||||||
return if (vectorUnwrappedRight.len() < vectorUnwrappedLeft.len())
|
|
||||||
vectorUnwrappedRight
|
|
||||||
else
|
|
||||||
vectorUnwrappedLeft
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Clears starting positions, e.g. after GameStarter is done with them. Does not clear the pseudo-improvements. */
|
||||||
|
fun clearStartingLocations() {
|
||||||
|
startingLocations.clear()
|
||||||
|
startingLocationsByNation.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
//endregion
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,7 @@ class GameParameters { // Default values are the default new game
|
|||||||
parameters.noBarbarians = noBarbarians
|
parameters.noBarbarians = noBarbarians
|
||||||
parameters.oneCityChallenge = oneCityChallenge
|
parameters.oneCityChallenge = oneCityChallenge
|
||||||
parameters.nuclearWeaponsEnabled = nuclearWeaponsEnabled
|
parameters.nuclearWeaponsEnabled = nuclearWeaponsEnabled
|
||||||
|
parameters.religionEnabled = religionEnabled
|
||||||
parameters.victoryTypes = ArrayList(victoryTypes)
|
parameters.victoryTypes = ArrayList(victoryTypes)
|
||||||
parameters.startingEra = startingEra
|
parameters.startingEra = startingEra
|
||||||
parameters.isOnlineMultiplayer = isOnlineMultiplayer
|
parameters.isOnlineMultiplayer = isOnlineMultiplayer
|
||||||
@ -47,4 +48,24 @@ class GameParameters { // Default values are the default new game
|
|||||||
parameters.mods = LinkedHashSet(mods)
|
parameters.mods = LinkedHashSet(mods)
|
||||||
return parameters
|
return parameters
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For debugging and MapGenerator console output
|
||||||
|
override fun toString() = "($difficulty $gameSpeed $startingEra, " +
|
||||||
|
"${players.count { it.playerType == PlayerType.Human }} ${PlayerType.Human} " +
|
||||||
|
"${players.count { it.playerType == PlayerType.AI }} ${PlayerType.AI} " +
|
||||||
|
"$numberOfCityStates CS, " +
|
||||||
|
sequence<String> {
|
||||||
|
if (isOnlineMultiplayer) yield("Online Multiplayer")
|
||||||
|
if (noBarbarians) yield("No barbs")
|
||||||
|
if (oneCityChallenge) yield("OCC")
|
||||||
|
if (!nuclearWeaponsEnabled) yield("No nukes")
|
||||||
|
if (religionEnabled) yield("Religion")
|
||||||
|
if (godMode) yield("God mode")
|
||||||
|
if (VictoryType.Cultural !in victoryTypes) yield("No ${VictoryType.Cultural} Victory")
|
||||||
|
if (VictoryType.Diplomatic in victoryTypes) yield("${VictoryType.Diplomatic} Victory")
|
||||||
|
if (VictoryType.Domination !in victoryTypes) yield("No ${VictoryType.Domination} Victory")
|
||||||
|
if (VictoryType.Scientific !in victoryTypes) yield("No ${VictoryType.Scientific} Victory")
|
||||||
|
}.joinToString() +
|
||||||
|
(if (mods.isEmpty()) ", no mods" else mods.joinToString(",", ", mods=(", ")", 6) ) +
|
||||||
|
")"
|
||||||
}
|
}
|
@ -162,7 +162,7 @@ class MapEditorOptionsTable(val mapEditorScreen: MapEditorScreen): Table(CameraS
|
|||||||
|
|
||||||
val nationImage = getHex(ImageGetter.getNationIndicator(nation, 40f))
|
val nationImage = getHex(ImageGetter.getNationIndicator(nation, 40f))
|
||||||
nationImage.onClick {
|
nationImage.onClick {
|
||||||
val improvementName = "StartingLocation " + nation.name
|
val improvementName = TileMap.startingLocationPrefix + nation.name
|
||||||
tileAction = {
|
tileAction = {
|
||||||
it.improvement = improvementName
|
it.improvement = improvementName
|
||||||
for ((tileInfo, tileGroups) in mapEditorScreen.mapHolder.tileGroups) {
|
for ((tileInfo, tileGroups) in mapEditorScreen.mapHolder.tileGroups) {
|
||||||
@ -267,17 +267,6 @@ class MapEditorOptionsTable(val mapEditorScreen: MapEditorScreen): Table(CameraS
|
|||||||
editorPickTable.add(AutoScrollPane(unitsTable)).height(scrollPanelHeight)
|
editorPickTable.add(AutoScrollPane(unitsTable)).height(scrollPanelHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun nationsFromMap(tileMap: TileMap): ArrayList<Nation> {
|
|
||||||
val tilesWithStartingLocations = tileMap.values
|
|
||||||
.filter { it.improvement != null && it.improvement!!.startsWith("StartingLocation ") }
|
|
||||||
var nations = ArrayList<Nation>()
|
|
||||||
for (tile in tilesWithStartingLocations) {
|
|
||||||
var civName = tile.improvement!!.removePrefix("StartingLocation ")
|
|
||||||
nations.add(ruleset.nations[civName]!!)
|
|
||||||
}
|
|
||||||
return nations
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getPlayerIndexString(player: Player): String {
|
private fun getPlayerIndexString(player: Player): String {
|
||||||
val index = gameParameters.players.indexOf(player) + 1
|
val index = gameParameters.players.indexOf(player) + 1
|
||||||
return "Player [$index]".tr()
|
return "Player [$index]".tr()
|
||||||
|
@ -35,6 +35,8 @@ class MapEditorScreen(): CameraStageBaseScreen() {
|
|||||||
private fun initialize() {
|
private fun initialize() {
|
||||||
ImageGetter.setNewRuleset(ruleset)
|
ImageGetter.setNewRuleset(ruleset)
|
||||||
tileMap.setTransients(ruleset,false)
|
tileMap.setTransients(ruleset,false)
|
||||||
|
tileMap.setStartingLocationsTransients()
|
||||||
|
tileMap.translateStartingLocationsToMap()
|
||||||
UncivGame.Current.translations.translationActiveMods = ruleset.mods
|
UncivGame.Current.translations.translationActiveMods = ruleset.mods
|
||||||
|
|
||||||
mapHolder = EditorMapHolder(this, tileMap)
|
mapHolder = EditorMapHolder(this, tileMap)
|
||||||
|
@ -6,14 +6,12 @@ import com.badlogic.gdx.graphics.Color
|
|||||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||||
import com.badlogic.gdx.scenes.scene2d.ui.TextButton
|
import com.badlogic.gdx.scenes.scene2d.ui.TextButton
|
||||||
import com.badlogic.gdx.scenes.scene2d.ui.TextField
|
import com.badlogic.gdx.scenes.scene2d.ui.TextField
|
||||||
import com.badlogic.gdx.utils.Json
|
|
||||||
import com.unciv.logic.MapSaver
|
import com.unciv.logic.MapSaver
|
||||||
import com.unciv.logic.map.MapType
|
import com.unciv.logic.map.MapType
|
||||||
import com.unciv.logic.map.TileMap
|
import com.unciv.logic.map.TileMap
|
||||||
import com.unciv.models.ruleset.RulesetCache
|
import com.unciv.models.ruleset.RulesetCache
|
||||||
import com.unciv.models.translations.tr
|
import com.unciv.models.translations.tr
|
||||||
import com.unciv.ui.pickerscreens.PickerScreen
|
import com.unciv.ui.pickerscreens.PickerScreen
|
||||||
import com.unciv.ui.saves.Gzip
|
|
||||||
import com.unciv.ui.utils.*
|
import com.unciv.ui.utils.*
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
import com.unciv.ui.utils.AutoScrollPane as ScrollPane
|
import com.unciv.ui.utils.AutoScrollPane as ScrollPane
|
||||||
@ -35,7 +33,7 @@ class SaveAndLoadMapScreen(mapToSave: TileMap?, save:Boolean = false, previousSc
|
|||||||
mapToSave.mapParameters.type = MapType.custom
|
mapToSave.mapParameters.type = MapType.custom
|
||||||
thread(name = "SaveMap") {
|
thread(name = "SaveMap") {
|
||||||
try {
|
try {
|
||||||
MapSaver.saveMap(mapNameTextField.text, mapToSave)
|
MapSaver.saveMap(mapNameTextField.text, getMapCloneForSave(mapToSave))
|
||||||
Gdx.app.postRunnable {
|
Gdx.app.postRunnable {
|
||||||
Gdx.input.inputProcessor = null // This is to stop ANRs happening here, until the map editor screen sets up.
|
Gdx.input.inputProcessor = null // This is to stop ANRs happening here, until the map editor screen sets up.
|
||||||
game.setScreen(MapEditorScreen(mapToSave))
|
game.setScreen(MapEditorScreen(mapToSave))
|
||||||
@ -119,9 +117,7 @@ class SaveAndLoadMapScreen(mapToSave: TileMap?, save:Boolean = false, previousSc
|
|||||||
if (save) {
|
if (save) {
|
||||||
val copyMapAsTextButton = "Copy to clipboard".toTextButton()
|
val copyMapAsTextButton = "Copy to clipboard".toTextButton()
|
||||||
val copyMapAsTextAction = {
|
val copyMapAsTextAction = {
|
||||||
val json = Json().toJson(mapToSave)
|
Gdx.app.clipboard.contents = MapSaver.mapToSavedString(getMapCloneForSave(mapToSave!!))
|
||||||
val base64Gzip = Gzip.zip(json)
|
|
||||||
Gdx.app.clipboard.contents = base64Gzip
|
|
||||||
}
|
}
|
||||||
copyMapAsTextButton.onClick (copyMapAsTextAction)
|
copyMapAsTextButton.onClick (copyMapAsTextAction)
|
||||||
keyPressDispatcher[KeyCharAndCode.ctrl('C')] = copyMapAsTextAction
|
keyPressDispatcher[KeyCharAndCode.ctrl('C')] = copyMapAsTextAction
|
||||||
@ -132,8 +128,7 @@ class SaveAndLoadMapScreen(mapToSave: TileMap?, save:Boolean = false, previousSc
|
|||||||
val loadFromClipboardAction = {
|
val loadFromClipboardAction = {
|
||||||
try {
|
try {
|
||||||
val clipboardContentsString = Gdx.app.clipboard.contents.trim()
|
val clipboardContentsString = Gdx.app.clipboard.contents.trim()
|
||||||
val decoded = Gzip.unzip(clipboardContentsString)
|
val loadedMap = MapSaver.mapFromSavedString(clipboardContentsString)
|
||||||
val loadedMap = MapSaver.mapFromJson(decoded)
|
|
||||||
game.setScreen(MapEditorScreen(loadedMap))
|
game.setScreen(MapEditorScreen(loadedMap))
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
couldNotLoadMapLabel.isVisible = true
|
couldNotLoadMapLabel.isVisible = true
|
||||||
@ -187,4 +182,8 @@ class SaveAndLoadMapScreen(mapToSave: TileMap?, save:Boolean = false, previousSc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getMapCloneForSave(mapToSave: TileMap) = mapToSave!!.clone().also {
|
||||||
|
it.setTransients(setUnitCivTransients = false)
|
||||||
|
it.translateStartingLocationsFromMap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,10 +91,7 @@ class NewGameScreen(
|
|||||||
|
|
||||||
if (mapOptionsTable.mapTypeSelectBox.selected.value == MapType.custom){
|
if (mapOptionsTable.mapTypeSelectBox.selected.value == MapType.custom){
|
||||||
val map = MapSaver.loadMap(gameSetupInfo.mapFile!!)
|
val map = MapSaver.loadMap(gameSetupInfo.mapFile!!)
|
||||||
val rulesetIncompatibilities = HashSet<String>()
|
val rulesetIncompatibilities = map.getRulesetIncompatibility(ruleset)
|
||||||
for (set in map.values.map { it.getRulesetIncompatibility(ruleset) })
|
|
||||||
rulesetIncompatibilities.addAll(set)
|
|
||||||
rulesetIncompatibilities.remove("")
|
|
||||||
|
|
||||||
if (rulesetIncompatibilities.isNotEmpty()) {
|
if (rulesetIncompatibilities.isNotEmpty()) {
|
||||||
val incompatibleMap = Popup(this)
|
val incompatibleMap = Popup(this)
|
||||||
|
@ -11,6 +11,7 @@ import com.unciv.UncivGame
|
|||||||
import com.unciv.logic.civilization.CivilizationInfo
|
import com.unciv.logic.civilization.CivilizationInfo
|
||||||
import com.unciv.logic.map.RoadStatus
|
import com.unciv.logic.map.RoadStatus
|
||||||
import com.unciv.logic.map.TileInfo
|
import com.unciv.logic.map.TileInfo
|
||||||
|
import com.unciv.logic.map.TileMap
|
||||||
import com.unciv.ui.cityscreen.YieldGroup
|
import com.unciv.ui.cityscreen.YieldGroup
|
||||||
import com.unciv.ui.utils.ImageGetter
|
import com.unciv.ui.utils.ImageGetter
|
||||||
import com.unciv.ui.utils.center
|
import com.unciv.ui.utils.center
|
||||||
@ -331,9 +332,11 @@ open class TileGroup(var tileInfo: TileInfo, var tileSetStrings:TileSetStrings,
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun removeMissingModReferences() {
|
private fun removeMissingModReferences() {
|
||||||
|
// This runs from map editor too, so the Pseudo-improvements for starting locations need to stay.
|
||||||
|
// The nations can be checked.
|
||||||
val improvementName = tileInfo.improvement
|
val improvementName = tileInfo.improvement
|
||||||
if(improvementName != null && improvementName.startsWith("StartingLocation ")){
|
if (improvementName != null && improvementName.startsWith(TileMap.startingLocationPrefix)) {
|
||||||
val nationName = improvementName.removePrefix("StartingLocation ")
|
val nationName = improvementName.removePrefix(TileMap.startingLocationPrefix)
|
||||||
if (!tileInfo.ruleset.nations.containsKey(nationName))
|
if (!tileInfo.ruleset.nations.containsKey(nationName))
|
||||||
tileInfo.improvement = null
|
tileInfo.improvement = null
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable
|
|||||||
import com.badlogic.gdx.utils.Align
|
import com.badlogic.gdx.utils.Align
|
||||||
import com.unciv.Constants
|
import com.unciv.Constants
|
||||||
import com.unciv.UncivGame
|
import com.unciv.UncivGame
|
||||||
|
import com.unciv.logic.map.TileMap
|
||||||
import com.unciv.models.ruleset.Era
|
import com.unciv.models.ruleset.Era
|
||||||
import com.unciv.models.ruleset.Nation
|
import com.unciv.models.ruleset.Nation
|
||||||
import com.unciv.models.ruleset.Ruleset
|
import com.unciv.models.ruleset.Ruleset
|
||||||
@ -253,8 +254,8 @@ object ImageGetter {
|
|||||||
fun getImprovementIcon(improvementName: String, size: Float = 20f): Actor {
|
fun getImprovementIcon(improvementName: String, size: Float = 20f): Actor {
|
||||||
if (improvementName.startsWith("Remove") || improvementName == Constants.cancelImprovementOrder)
|
if (improvementName.startsWith("Remove") || improvementName == Constants.cancelImprovementOrder)
|
||||||
return Table().apply { add(getImage("OtherIcons/Stop")).size(size) }
|
return Table().apply { add(getImage("OtherIcons/Stop")).size(size) }
|
||||||
if (improvementName.startsWith("StartingLocation ")) {
|
if (improvementName.startsWith(TileMap.startingLocationPrefix)) {
|
||||||
val nationName = improvementName.removePrefix("StartingLocation ")
|
val nationName = improvementName.removePrefix(TileMap.startingLocationPrefix)
|
||||||
val nation = ruleset.nations[nationName]!!
|
val nation = ruleset.nations[nationName]!!
|
||||||
return getNationIndicator(nation, size)
|
return getNationIndicator(nation, size)
|
||||||
}
|
}
|
||||||
|
@ -24,9 +24,9 @@ class TileInfoTable(private val viewingCiv :CivilizationInfo) : Table(CameraStag
|
|||||||
add(getStatsTable(tile))
|
add(getStatsTable(tile))
|
||||||
add( MarkupRenderer.render(tile.toMarkup(viewingCiv), padding = 0f, noLinkImages = true) {
|
add( MarkupRenderer.render(tile.toMarkup(viewingCiv), padding = 0f, noLinkImages = true) {
|
||||||
UncivGame.Current.setScreen(CivilopediaScreen(viewingCiv.gameInfo.ruleSet, link = it))
|
UncivGame.Current.setScreen(CivilopediaScreen(viewingCiv.gameInfo.ruleSet, link = it))
|
||||||
} ).pad(5f)
|
} ).pad(5f).row()
|
||||||
// For debug only!
|
if (UncivGame.Current.viewEntireMapForDebug)
|
||||||
// add(tile.position.toString().toLabel()).colspan(2).pad(10f)
|
add(tile.position.run { "(${x.toInt()},${y.toInt()})" }.toLabel()).colspan(2).pad(5f)
|
||||||
}
|
}
|
||||||
|
|
||||||
pack()
|
pack()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user