Map editor update - concurrency, resource amounts, file double-click (#9461)

This commit is contained in:
SomeTroglodyte 2023-05-27 20:44:23 +02:00 committed by GitHub
parent 1c124c9ddf
commit ef193babee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 141 additions and 53 deletions

View File

@ -501,6 +501,7 @@ Map copy and paste =
Position: [param] = Position: [param] =
Starting location(s): [param] = Starting location(s): [param] =
Continent: [param] ([amount] tiles) = Continent: [param] ([amount] tiles) =
Resource abundance =
Change map to fit selected ruleset? = Change map to fit selected ruleset? =
Area: [amount] tiles, [amount2] continents/islands = Area: [amount] tiles, [amount2] continents/islands =
Area: [amount] tiles, [amount2]% water, [amount3] continents/islands = Area: [amount] tiles, [amount2]% water, [amount3] continents/islands =

View File

@ -8,9 +8,11 @@ import com.unciv.logic.city.City
import com.unciv.logic.civilization.Civilization import com.unciv.logic.civilization.Civilization
import com.unciv.logic.civilization.PlayerType import com.unciv.logic.civilization.PlayerType
import com.unciv.logic.map.HexMath import com.unciv.logic.map.HexMath
import com.unciv.logic.map.MapParameters // Kdoc only
import com.unciv.logic.map.MapResources import com.unciv.logic.map.MapResources
import com.unciv.logic.map.TileMap import com.unciv.logic.map.TileMap
import com.unciv.logic.map.mapunit.MapUnit import com.unciv.logic.map.mapunit.MapUnit
import com.unciv.logic.map.mapunit.UnitMovement // Kdoc only
import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.tile.ResourceType import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.ruleset.tile.Terrain import com.unciv.models.ruleset.tile.Terrain
@ -676,7 +678,7 @@ open class Tile : IsPartOfGameInfoSerialization {
/** /**
* @returns whether units of [civInfo] can pass through this tile, considering only civ-wide filters. * @returns whether units of [civInfo] can pass through this tile, considering only civ-wide filters.
* Use [UnitMovementAlgorithms.canPassThrough] to check whether a specific unit can pass through a tile. * Use [UnitMovement.canPassThrough] to check whether a specific unit can pass through a tile.
*/ */
fun canCivPassThrough(civInfo: Civilization): Boolean { fun canCivPassThrough(civInfo: Civilization): Boolean {
val tileOwner = getOwner() val tileOwner = getOwner()
@ -785,7 +787,10 @@ open class Tile : IsPartOfGameInfoSerialization {
fun setTileResource(newResource: TileResource, majorDeposit: Boolean? = null, rng: Random = Random.Default) { fun setTileResource(newResource: TileResource, majorDeposit: Boolean? = null, rng: Random = Random.Default) {
resource = newResource.name resource = newResource.name
if (newResource.resourceType != ResourceType.Strategic) return if (newResource.resourceType != ResourceType.Strategic) {
resourceAmount = 0
return
}
for (unique in newResource.getMatchingUniques(UniqueType.ResourceAmountOnTiles, StateForConditionals(tile = this))) { for (unique in newResource.getMatchingUniques(UniqueType.ResourceAmountOnTiles, StateForConditionals(tile = this))) {
if (matchesTerrainFilter(unique.params[0])) { if (matchesTerrainFilter(unique.params[0])) {

View File

@ -83,6 +83,7 @@ class TileLayerMisc(tileGroup: TileGroup, size: Float) : TileLayer(tileGroup, si
private var hexOutlineIcon: Actor? = null private var hexOutlineIcon: Actor? = null
private var resourceName: String? = null private var resourceName: String? = null
private var resourceAmount: Int = -1
private var resourceIcon: Actor? = null private var resourceIcon: Actor? = null
private var workedIcon: Actor? = null private var workedIcon: Actor? = null
@ -168,15 +169,16 @@ class TileLayerMisc(tileGroup: TileGroup, size: Float) : TileLayer(tileGroup, si
} }
// If resource has changed (e.g. tech researched) - force new icon next time it's needed // If resource has changed (e.g. tech researched) - force new icon next time it's needed
if (resourceName != tile().resource) { if (resourceName != tile().resource || resourceAmount != tile().resourceAmount) {
resourceName = tile().resource resourceName = tile().resource
resourceAmount = tile().resourceAmount
resourceIcon?.remove() resourceIcon?.remove()
resourceIcon = null resourceIcon = null
} }
// Get a fresh Icon if and only if necessary // Get a fresh Icon if and only if necessary
if (resourceName != null && effectiveVisible && resourceIcon == null) { if (resourceName != null && effectiveVisible && resourceIcon == null) {
val icon = ImageGetter.getResourcePortrait(resourceName!!, 20f, tile().resourceAmount) val icon = ImageGetter.getResourcePortrait(resourceName!!, 20f, resourceAmount)
icon.center(tileGroup) icon.center(tileGroup)
icon.x -= 22 // left icon.x -= 22 // left
icon.y += 10 // top icon.y += 10 // top

View File

@ -12,13 +12,15 @@ import com.unciv.ui.images.ImageGetter
import com.unciv.ui.screens.basescreen.BaseScreen import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.components.extensions.keyShortcuts import com.unciv.ui.components.extensions.keyShortcuts
import com.unciv.ui.components.extensions.onClick import com.unciv.ui.components.extensions.onClick
import com.unciv.ui.components.extensions.onDoubleClick
import com.unciv.ui.components.extensions.pad import com.unciv.ui.components.extensions.pad
import com.unciv.ui.components.extensions.toLabel import com.unciv.ui.components.extensions.toLabel
class MapEditorFilesTable( class MapEditorFilesTable(
initWidth: Float, initWidth: Float,
private val includeMods: Boolean = false, private val includeMods: Boolean = false,
private val onSelect: (FileHandle) -> Unit private val onSelect: (FileHandle) -> Unit,
private val onDoubleClick: () -> Unit
): Table(BaseScreen.skin) { ): Table(BaseScreen.skin) {
private var selectedIndex = -1 private var selectedIndex = -1
@ -41,7 +43,7 @@ class MapEditorFilesTable(
onSelect(sortedFiles[row].file) onSelect(sortedFiles[row].file)
} }
fun moveSelection(delta: Int) { private fun moveSelection(delta: Int) {
selectedIndex = when { selectedIndex = when {
selectedIndex + delta in sortedFiles.indices -> selectedIndex + delta in sortedFiles.indices ->
selectedIndex + delta selectedIndex + delta
@ -92,6 +94,10 @@ class MapEditorFilesTable(
mapButton.onClick { mapButton.onClick {
markSelection(mapButton, index) markSelection(mapButton, index)
} }
mapButton.onDoubleClick {
markSelection(mapButton, index)
onDoubleClick()
}
add(mapButton).row() add(mapButton).row()
} }
layout() layout()

View File

@ -22,12 +22,15 @@ import com.unciv.ui.components.KeyCharAndCode
import com.unciv.ui.components.KeyboardPanningListener import com.unciv.ui.components.KeyboardPanningListener
import com.unciv.ui.screens.basescreen.RecreateOnResize import com.unciv.ui.screens.basescreen.RecreateOnResize
import com.unciv.ui.screens.worldscreen.ZoomButtonPair import com.unciv.ui.screens.worldscreen.ZoomButtonPair
import com.unciv.utils.Concurrency
import com.unciv.utils.Dispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
//todo normalize properly //todo normalize properly
//todo Remove "Area: [amount] tiles, [amount2] continents/islands = " after 2022-07-01 //todo Remove "Area: [amount] tiles, [amount2] continents/islands = " after 2022-07-01
//todo Direct Strategic Resource abundance control
//todo functional Tab for Units (empty Tab is prepared but commented out in MapEditorEditTab.AllEditSubTabs) //todo functional Tab for Units (empty Tab is prepared but commented out in MapEditorEditTab.AllEditSubTabs)
//todo copy/paste tile areas? (As tool tab, brush sized, floodfill forbidden, tab displays copied area) //todo copy/paste tile areas? (As tool tab, brush sized, floodfill forbidden, tab displays copied area)
//todo Synergy with Civilopedia for drawing loose tiles / terrain icons //todo Synergy with Civilopedia for drawing loose tiles / terrain icons
@ -73,6 +76,9 @@ class MapEditorScreen(map: TileMap? = null): BaseScreen(), RecreateOnResize {
private val highlightedTileGroups = mutableListOf<TileGroup>() private val highlightedTileGroups = mutableListOf<TileGroup>()
// Control of background jobs - make them cancel on context changes like exit editor or resize screen
private val jobs = ArrayDeque<Job>(3)
init { init {
if (map == null) { if (map == null) {
ruleset = RulesetCache[BaseRuleset.Civ_V_GnK.fullName]!! ruleset = RulesetCache[BaseRuleset.Civ_V_GnK.fullName]!!
@ -189,6 +195,7 @@ class MapEditorScreen(map: TileMap? = null): BaseScreen(), RecreateOnResize {
"Do you want to leave without saving the recent changes?", "Do you want to leave without saving the recent changes?",
"Leave" "Leave"
) { ) {
cancelJobs()
game.popScreen() game.popScreen()
} }
} }
@ -216,5 +223,32 @@ class MapEditorScreen(map: TileMap? = null): BaseScreen(), RecreateOnResize {
highlightTile(tile, color) highlightTile(tile, color)
} }
override fun recreate(): BaseScreen = MapEditorScreen(tileMap) override fun recreate(): BaseScreen {
cancelJobs()
return MapEditorScreen(tileMap)
}
override fun dispose() {
cancelJobs()
super.dispose()
}
fun startBackgroundJob(
name: String,
isDaemon: Boolean = true,
block: suspend CoroutineScope.() -> Unit
) {
val scope = CoroutineScope(if (isDaemon) Dispatcher.DAEMON else Dispatcher.NON_DAEMON)
val newJob = Concurrency.run(name, scope, block)
jobs.add(newJob)
newJob.invokeOnCompletion {
jobs.remove(newJob)
}
}
private fun cancelJobs() {
for (job in jobs)
job.cancel()
jobs.clear()
}
} }

View File

@ -156,6 +156,7 @@ class MapEditorEditResourcesTab(
add(eraser.render(0f).apply { onClick { add(eraser.render(0f).apply { onClick {
editTab.setBrush("Remove resource", eraserIcon, true) { tile -> editTab.setBrush("Remove resource", eraserIcon, true) { tile ->
tile.resource = null tile.resource = null
tile.resourceAmount = 0
} }
} }).padBottom(0f).row() } }).padBottom(0f).row()
add( add(
@ -165,7 +166,10 @@ class MapEditorEditResourcesTab(
) { resourceName -> ) { resourceName ->
val resource = ruleset.tileResources[resourceName]!! val resource = ruleset.tileResources[resourceName]!!
editTab.setBrush(resourceName, resource.makeLink()) { editTab.setBrush(resourceName, resource.makeLink()) {
it.setTileResource(resource, rng = editTab.randomness.RNG) if (it.resource == resourceName && resource.resourceType == ResourceType.Strategic)
it.resourceAmount = (it.resourceAmount + 1).coerceAtMost(42)
else
it.setTileResource(resource, rng = editTab.randomness.RNG)
} }
}).padTop(0f).row() }).padTop(0f).row()
} }

View File

@ -11,26 +11,19 @@ import com.unciv.logic.map.mapgenerator.RiverGenerator
import com.unciv.logic.map.tile.Tile import com.unciv.logic.map.tile.Tile
import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
import com.unciv.ui.screens.civilopediascreen.FormattedLine
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.screens.mapeditorscreen.tabs.MapEditorEditFeaturesTab
import com.unciv.ui.screens.mapeditorscreen.tabs.MapEditorEditImprovementsTab
import com.unciv.ui.screens.mapeditorscreen.tabs.MapEditorEditResourcesTab
import com.unciv.ui.screens.mapeditorscreen.tabs.MapEditorEditRiversTab
import com.unciv.ui.screens.mapeditorscreen.tabs.MapEditorEditStartsTab
import com.unciv.ui.screens.mapeditorscreen.tabs.MapEditorEditTerrainTab
import com.unciv.ui.screens.mapeditorscreen.tabs.MapEditorEditWondersTab
import com.unciv.ui.screens.mapeditorscreen.MapEditorScreen
import com.unciv.ui.screens.mapeditorscreen.TileInfoNormalizer
import com.unciv.ui.screens.mapeditorscreen.tabs.MapEditorOptionsTab.TileMatchFuzziness
import com.unciv.ui.popups.ToastPopup
import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.components.KeyCharAndCode import com.unciv.ui.components.KeyCharAndCode
import com.unciv.ui.components.TabbedPager import com.unciv.ui.components.TabbedPager
import com.unciv.ui.components.UncivSlider import com.unciv.ui.components.UncivSlider
import com.unciv.ui.components.extensions.addSeparator import com.unciv.ui.components.extensions.addSeparator
import com.unciv.ui.components.extensions.keyShortcuts import com.unciv.ui.components.extensions.keyShortcuts
import com.unciv.ui.components.extensions.toLabel import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.images.ImageGetter
import com.unciv.ui.popups.ToastPopup
import com.unciv.ui.screens.basescreen.BaseScreen
import com.unciv.ui.screens.civilopediascreen.FormattedLine
import com.unciv.ui.screens.mapeditorscreen.MapEditorScreen
import com.unciv.ui.screens.mapeditorscreen.TileInfoNormalizer
import com.unciv.ui.screens.mapeditorscreen.tabs.MapEditorOptionsTab.TileMatchFuzziness
import com.unciv.utils.Log import com.unciv.utils.Log
class MapEditorEditTab( class MapEditorEditTab(
@ -190,7 +183,7 @@ class MapEditorEditTab(
editorScreen.tileClickHandler = null editorScreen.tileClickHandler = null
} }
fun tileClickHandler(tile: Tile) { private fun tileClickHandler(tile: Tile) {
if (brushSize < -1 || brushSize > 5 || brushHandlerType == BrushHandlerType.None) return if (brushSize < -1 || brushSize > 5 || brushHandlerType == BrushHandlerType.None) return
if (editorScreen.mapHolder.isPanning || editorScreen.mapHolder.isZooming()) return if (editorScreen.mapHolder.isPanning || editorScreen.mapHolder.isZooming()) return
editorScreen.hideSelection() editorScreen.hideSelection()

View File

@ -29,8 +29,8 @@ import com.unciv.ui.components.extensions.onClick
import com.unciv.ui.components.extensions.toCheckBox import com.unciv.ui.components.extensions.toCheckBox
import com.unciv.ui.components.extensions.toLabel import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.components.extensions.toTextButton import com.unciv.ui.components.extensions.toTextButton
import com.unciv.utils.Concurrency
import com.unciv.utils.Log import com.unciv.utils.Log
import kotlin.concurrent.thread
class MapEditorGenerateTab( class MapEditorGenerateTab(
private val editorScreen: MapEditorScreen private val editorScreen: MapEditorScreen
@ -73,7 +73,7 @@ class MapEditorGenerateTab(
val mapParameters = editorScreen.newMapParameters.clone() // this clone is very important here val mapParameters = editorScreen.newMapParameters.clone() // this clone is very important here
val message = mapParameters.mapSize.fixUndesiredSizes(mapParameters.worldWrap) val message = mapParameters.mapSize.fixUndesiredSizes(mapParameters.worldWrap)
if (message != null) { if (message != null) {
Gdx.app.postRunnable { Concurrency.runOnGLThread {
ToastPopup( message, editorScreen, 4000 ) ToastPopup( message, editorScreen, 4000 )
newTab.mapParametersTable.run { mapParameters.mapSize.also { newTab.mapParametersTable.run { mapParameters.mapSize.also {
customMapSizeRadius.text = it.radius.toString() customMapSizeRadius.text = it.radius.toString()
@ -113,7 +113,7 @@ class MapEditorGenerateTab(
} }
// Map generation can take a while and we don't want ANRs // Map generation can take a while and we don't want ANRs
thread(name = "MapGenerator", isDaemon = true) { editorScreen.startBackgroundJob("MapEditor.MapGenerator") {
try { try {
val (newRuleset, generator) = if (step > MapGeneratorSteps.Landmass) null to null val (newRuleset, generator) = if (step > MapGeneratorSteps.Landmass) null to null
else { else {
@ -124,7 +124,7 @@ class MapEditorGenerateTab(
MapGeneratorSteps.All -> { MapGeneratorSteps.All -> {
val generatedMap = generator!!.generateMap(mapParameters) val generatedMap = generator!!.generateMap(mapParameters)
val savedScale = editorScreen.mapHolder.scaleX val savedScale = editorScreen.mapHolder.scaleX
Gdx.app.postRunnable { Concurrency.runOnGLThread {
freshMapCompleted(generatedMap, mapParameters, newRuleset!!, selectPage = 0) freshMapCompleted(generatedMap, mapParameters, newRuleset!!, selectPage = 0)
editorScreen.mapHolder.zoom(savedScale) editorScreen.mapHolder.zoom(savedScale)
} }
@ -136,7 +136,7 @@ class MapEditorGenerateTab(
mapParameters.type = editorScreen.newMapParameters.type mapParameters.type = editorScreen.newMapParameters.type
generator.generateSingleStep(generatedMap, step) generator.generateSingleStep(generatedMap, step)
val savedScale = editorScreen.mapHolder.scaleX val savedScale = editorScreen.mapHolder.scaleX
Gdx.app.postRunnable { Concurrency.runOnGLThread {
freshMapCompleted(generatedMap, mapParameters, newRuleset!!, selectPage = 1) freshMapCompleted(generatedMap, mapParameters, newRuleset!!, selectPage = 1)
editorScreen.mapHolder.zoom(savedScale) editorScreen.mapHolder.zoom(savedScale)
} }
@ -144,14 +144,14 @@ class MapEditorGenerateTab(
else -> { else -> {
editorScreen.tileMap.mapParameters.seed = mapParameters.seed editorScreen.tileMap.mapParameters.seed = mapParameters.seed
MapGenerator(editorScreen.ruleset).generateSingleStep(editorScreen.tileMap, step) MapGenerator(editorScreen.ruleset).generateSingleStep(editorScreen.tileMap, step)
Gdx.app.postRunnable { Concurrency.runOnGLThread {
stepCompleted(step) stepCompleted(step)
} }
} }
} }
} catch (exception: Exception) { } catch (exception: Exception) {
Log.error("Exception while generating map", exception) Log.error("Exception while generating map", exception)
Gdx.app.postRunnable { Concurrency.runOnGLThread {
setButtonsEnabled(true) setButtonsEnabled(true)
Gdx.input.inputProcessor = editorScreen.stage Gdx.input.inputProcessor = editorScreen.stage
Popup(editorScreen).apply { Popup(editorScreen).apply {

View File

@ -22,8 +22,10 @@ import com.unciv.ui.components.extensions.isEnabled
import com.unciv.ui.components.extensions.keyShortcuts import com.unciv.ui.components.extensions.keyShortcuts
import com.unciv.ui.components.extensions.onActivation import com.unciv.ui.components.extensions.onActivation
import com.unciv.ui.components.extensions.toTextButton import com.unciv.ui.components.extensions.toTextButton
import com.unciv.utils.Concurrency
import com.unciv.utils.Log import com.unciv.utils.Log
import kotlin.concurrent.thread import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.isActive
class MapEditorLoadTab( class MapEditorLoadTab(
private val editorScreen: MapEditorScreen, private val editorScreen: MapEditorScreen,
@ -32,7 +34,9 @@ class MapEditorLoadTab(
private val mapFiles = MapEditorFilesTable( private val mapFiles = MapEditorFilesTable(
initWidth = editorScreen.getToolsWidth() - 20f, initWidth = editorScreen.getToolsWidth() - 20f,
includeMods = true, includeMods = true,
this::selectFile) this::selectFile,
this::loadHandler
)
private val loadButton = "Load map".toTextButton() private val loadButton = "Load map".toTextButton()
private val deleteButton = "Delete map".toTextButton() private val deleteButton = "Delete map".toTextButton()
@ -53,7 +57,7 @@ class MapEditorLoadTab(
val fileTableHeight = editorScreen.stage.height - headerHeight - buttonTable.height - 2f val fileTableHeight = editorScreen.stage.height - headerHeight - buttonTable.height - 2f
val scrollPane = AutoScrollPane(mapFiles, skin) val scrollPane = AutoScrollPane(mapFiles, skin)
scrollPane.setOverscroll(false, true) scrollPane.setOverscroll(false, true)
add(scrollPane).height(fileTableHeight).width(editorScreen.getToolsWidth() - 20f).row() add(scrollPane).size(editorScreen.getToolsWidth() - 20f, fileTableHeight).padTop(10f).row()
add(buttonTable).row() add(buttonTable).row()
} }
@ -63,7 +67,7 @@ class MapEditorLoadTab(
"Do you want to load another map without saving the recent changes?", "Do you want to load another map without saving the recent changes?",
"Load map" "Load map"
) { ) {
thread(name = "MapLoader", isDaemon = true, block = this::loaderThread) editorScreen.startBackgroundJob("MapLoader") { loaderThread() }
} }
} }
@ -89,18 +93,18 @@ class MapEditorLoadTab(
pager.setScrollDisabled(false) pager.setScrollDisabled(false)
} }
fun selectFile(file: FileHandle?) { private fun selectFile(file: FileHandle?) {
chosenMap = file chosenMap = file
loadButton.isEnabled = (file != null) loadButton.isEnabled = (file != null)
deleteButton.isEnabled = (file != null) deleteButton.isEnabled = (file != null)
deleteButton.color = if (file != null) Color.SCARLET else Color.BROWN deleteButton.color = if (file != null) Color.SCARLET else Color.BROWN
} }
fun loaderThread() { private fun CoroutineScope.loaderThread() {
var popup: Popup? = null var popup: Popup? = null
var needPopup = true // loadMap can fail faster than postRunnable runs var needPopup = true // loadMap can fail faster than postRunnable runs
Gdx.app.postRunnable { Concurrency.runOnGLThread {
if (!needPopup) return@postRunnable if (!needPopup) return@runOnGLThread
popup = Popup(editorScreen).apply { popup = Popup(editorScreen).apply {
addGoodSizedLabel(Constants.loading) addGoodSizedLabel(Constants.loading)
open() open()
@ -108,6 +112,7 @@ class MapEditorLoadTab(
} }
try { try {
val map = MapSaver.loadMap(chosenMap!!) val map = MapSaver.loadMap(chosenMap!!)
if (!isActive) return
val missingMods = map.mapParameters.mods.filter { it !in RulesetCache }.toMutableList() val missingMods = map.mapParameters.mods.filter { it !in RulesetCache }.toMutableList()
// [TEMPORARY] conversion of old maps with a base ruleset contained in the mods // [TEMPORARY] conversion of old maps with a base ruleset contained in the mods
@ -117,12 +122,12 @@ class MapEditorLoadTab(
if (map.mapParameters.baseRuleset !in RulesetCache) missingMods += map.mapParameters.baseRuleset if (map.mapParameters.baseRuleset !in RulesetCache) missingMods += map.mapParameters.baseRuleset
if (missingMods.isNotEmpty()) { if (missingMods.isNotEmpty()) {
Gdx.app.postRunnable { Concurrency.runOnGLThread {
needPopup = false needPopup = false
popup?.close() popup?.close()
ToastPopup("Missing mods: [${missingMods.joinToString()}]", editorScreen) ToastPopup("Missing mods: [${missingMods.joinToString()}]", editorScreen)
} }
} else Gdx.app.postRunnable { } else Concurrency.runOnGLThread {
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.
try { try {
// For deprecated maps, set the base ruleset field if it's still saved in the mods field // For deprecated maps, set the base ruleset field if it's still saved in the mods field
@ -155,7 +160,7 @@ class MapEditorLoadTab(
} }
} catch (ex: Throwable) { } catch (ex: Throwable) {
needPopup = false needPopup = false
Gdx.app.postRunnable { Concurrency.runOnGLThread {
popup?.close() popup?.close()
Log.error("Error loading map \"$chosenMap\"", ex) Log.error("Error loading map \"$chosenMap\"", ex)

View File

@ -1,6 +1,5 @@
package com.unciv.ui.screens.mapeditorscreen.tabs package com.unciv.ui.screens.mapeditorscreen.tabs
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.Table
@ -25,8 +24,10 @@ import com.unciv.ui.components.extensions.onActivation
import com.unciv.ui.components.extensions.onChange import com.unciv.ui.components.extensions.onChange
import com.unciv.ui.components.extensions.onClick import com.unciv.ui.components.extensions.onClick
import com.unciv.ui.components.extensions.toTextButton import com.unciv.ui.components.extensions.toTextButton
import com.unciv.utils.Concurrency
import com.unciv.utils.Log import com.unciv.utils.Log
import kotlin.concurrent.thread import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.isActive
class MapEditorSaveTab( class MapEditorSaveTab(
private val editorScreen: MapEditorScreen, private val editorScreen: MapEditorScreen,
@ -35,7 +36,9 @@ class MapEditorSaveTab(
private val mapFiles = MapEditorFilesTable( private val mapFiles = MapEditorFilesTable(
initWidth = editorScreen.getToolsWidth() - 40f, initWidth = editorScreen.getToolsWidth() - 40f,
includeMods = false, includeMods = false,
this::selectFile) this::selectFile,
this::saveHandler
)
private val saveButton = "Save map".toTextButton() private val saveButton = "Save map".toTextButton()
private val deleteButton = "Delete map".toTextButton() private val deleteButton = "Delete map".toTextButton()
@ -76,11 +79,17 @@ class MapEditorSaveTab(
add(buttonTable).row() add(buttonTable).row()
} }
private fun setSaveButton(enabled: Boolean) {
saveButton.isEnabled = enabled
saveButton.setText((if (enabled) "Save map" else "Working...").tr())
}
private fun saveHandler() { private fun saveHandler() {
if (mapNameTextField.text.isBlank()) return if (mapNameTextField.text.isBlank()) return
editorScreen.tileMap.mapParameters.name = mapNameTextField.text editorScreen.tileMap.mapParameters.name = mapNameTextField.text
editorScreen.tileMap.mapParameters.type = MapGeneratedMainType.custom editorScreen.tileMap.mapParameters.type = MapGeneratedMainType.custom
thread(name = "MapSaver", block = this::saverThread) setSaveButton(false)
editorScreen.startBackgroundJob("MapSaver", false) { saverThread() }
} }
private fun deleteHandler() { private fun deleteHandler() {
@ -106,7 +115,7 @@ class MapEditorSaveTab(
stage.keyboardFocus = null stage.keyboardFocus = null
} }
fun selectFile(file: FileHandle?) { private fun selectFile(file: FileHandle?) {
chosenMap = file chosenMap = file
mapNameTextField.text = file?.name() ?: editorScreen.tileMap.mapParameters.name mapNameTextField.text = file?.name() ?: editorScreen.tileMap.mapParameters.name
if (mapNameTextField.text.isBlank()) mapNameTextField.text = "My new map".tr() if (mapNameTextField.text.isBlank()) mapNameTextField.text = "My new map".tr()
@ -117,22 +126,26 @@ class MapEditorSaveTab(
deleteButton.color = if (file != null) Color.SCARLET else Color.BROWN deleteButton.color = if (file != null) Color.SCARLET else Color.BROWN
} }
private fun saverThread() { private fun CoroutineScope.saverThread() {
try { try {
val mapToSave = editorScreen.getMapCloneForSave() val mapToSave = editorScreen.getMapCloneForSave()
if (!isActive) return
mapToSave.assignContinents(TileMap.AssignContinentsMode.Reassign) mapToSave.assignContinents(TileMap.AssignContinentsMode.Reassign)
if (!isActive) return
MapSaver.saveMap(mapNameTextField.text, mapToSave) MapSaver.saveMap(mapNameTextField.text, mapToSave)
Gdx.app.postRunnable { Concurrency.runOnGLThread {
ToastPopup("Map saved successfully!", editorScreen) ToastPopup("Map saved successfully!", editorScreen)
} }
editorScreen.isDirty = false editorScreen.isDirty = false
setSaveButton(true)
} catch (ex: Exception) { } catch (ex: Exception) {
Log.error("Failed to save map", ex) Log.error("Failed to save map", ex)
Gdx.app.postRunnable { Concurrency.runOnGLThread {
val cantLoadGamePopup = Popup(editorScreen) val cantLoadGamePopup = Popup(editorScreen)
cantLoadGamePopup.addGoodSizedLabel("It looks like your map can't be saved!").row() cantLoadGamePopup.addGoodSizedLabel("It looks like your map can't be saved!").row()
cantLoadGamePopup.addCloseButton() cantLoadGamePopup.addCloseButton()
cantLoadGamePopup.open(force = true) cantLoadGamePopup.open(force = true)
setSaveButton(true)
} }
} }
} }

View File

@ -3,6 +3,7 @@ package com.unciv.ui.screens.mapeditorscreen.tabs
import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.ui.Cell import com.badlogic.gdx.scenes.scene2d.ui.Cell
import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.utils.Align
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.logic.GameInfo import com.unciv.logic.GameInfo
import com.unciv.logic.civilization.Civilization import com.unciv.logic.civilization.Civilization
@ -12,15 +13,18 @@ import com.unciv.logic.map.tile.TileDescription
import com.unciv.models.Counter import com.unciv.models.Counter
import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.Ruleset
import com.unciv.models.ruleset.nation.Nation import com.unciv.models.ruleset.nation.Nation
import com.unciv.models.ruleset.tile.ResourceType
import com.unciv.models.stats.Stats import com.unciv.models.stats.Stats
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
import com.unciv.ui.components.ExpanderTab import com.unciv.ui.components.ExpanderTab
import com.unciv.ui.components.TabbedPager import com.unciv.ui.components.TabbedPager
import com.unciv.ui.components.UncivSlider
import com.unciv.ui.components.WrappableLabel import com.unciv.ui.components.WrappableLabel
import com.unciv.ui.components.extensions.addSeparator import com.unciv.ui.components.extensions.addSeparator
import com.unciv.ui.components.extensions.darken import com.unciv.ui.components.extensions.darken
import com.unciv.ui.components.extensions.onClick import com.unciv.ui.components.extensions.onClick
import com.unciv.ui.components.extensions.pad import com.unciv.ui.components.extensions.pad
import com.unciv.ui.components.extensions.toLabel
import com.unciv.ui.components.extensions.toTextButton import com.unciv.ui.components.extensions.toTextButton
import com.unciv.ui.popups.ToastPopup import com.unciv.ui.popups.ToastPopup
import com.unciv.ui.screens.basescreen.BaseScreen import com.unciv.ui.screens.basescreen.BaseScreen
@ -43,6 +47,8 @@ class MapEditorViewTab(
init { init {
top() top()
// Note on width: max expander content width + 2 * expander.defaultPad + 2 * the following horizontal pad
// should not exceed editorScreen.getToolsWidth() or the page will scroll horizontally!
defaults().pad(5f, 20f) defaults().pad(5f, 20f)
update() update()
} }
@ -72,7 +78,8 @@ class MapEditorViewTab(
val headerText = tileMap.mapParameters.name.ifEmpty { "New map" } val headerText = tileMap.mapParameters.name.ifEmpty { "New map" }
add(ExpanderTab( add(ExpanderTab(
headerText, headerText,
startsOutOpened = false startsOutOpened = false,
defaultPad = 0f // See note in init
) { ) {
val mapParameterText = tileMap.mapParameters.toString() val mapParameterText = tileMap.mapParameters.toString()
.replace("\"${tileMap.mapParameters.name}\" ", "") .replace("\"${tileMap.mapParameters.name}\" ", "")
@ -196,7 +203,7 @@ class MapEditorViewTab(
lines += FormattedLine("Continent: [$continent] ([${tile.tileMap.continentSizes[continent]}] tiles)", link = "continent") lines += FormattedLine("Continent: [$continent] ([${tile.tileMap.continentSizes[continent]}] tiles)", link = "continent")
} }
tileDataCell?.setActor(MarkupRenderer.render(lines, labelWidth) { val renderedInfo = MarkupRenderer.render(lines, labelWidth) {
if (it == "continent") { if (it == "continent") {
// Visualize the continent this tile is on // Visualize the continent this tile is on
editorScreen.hideSelection() editorScreen.hideSelection()
@ -209,7 +216,25 @@ class MapEditorViewTab(
// This needs CivilopediaScreen to be able to work without a GameInfo! // This needs CivilopediaScreen to be able to work without a GameInfo!
UncivGame.Current.pushScreen(CivilopediaScreen(tile.ruleset, link = it)) UncivGame.Current.pushScreen(CivilopediaScreen(tile.ruleset, link = it))
} }
}) }
if (tile.resource != null && (tile.resourceAmount > 0 || tile.tileResource.resourceType == ResourceType.Strategic)) {
renderedInfo.addSeparator(Color.GRAY)
renderedInfo.add(Table().apply {
add("Resource abundance".toLabel(alignment = Align.left)).left().growX()
val slider = UncivSlider(0f, 42f, 1f,
initial = tile.resourceAmount.toFloat()
) {
tile.resourceAmount = it.toInt()
editorScreen.updateTile(tile)
editorScreen.isDirty = true
}
slider.setSnapToValues(floatArrayOf(0f,1f,2f,3f,4f,5f,6f,7f,8f,9f,10f,12f,15f,20f,30f,40f), 5f)
add(slider).right().minWidth(80f).fillX().padTop(15f)
}).fillX()
}
tileDataCell?.setActor(renderedInfo)
editorScreen.hideSelection() editorScreen.hideSelection()
editorScreen.highlightTile(tile, Color.CORAL) editorScreen.highlightTile(tile, Color.CORAL)