mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-11 00:08:58 +07:00
Show a preview of custom maps on new game screen (#9234)
* Show a preview of custom maps on new game screen * Show a preview of custom maps on new game screen - step 2 * Show a preview of custom maps on new game screen V2
This commit is contained in:
@ -0,0 +1,175 @@
|
|||||||
|
package com.unciv.ui.screens.newgamescreen
|
||||||
|
|
||||||
|
import com.badlogic.gdx.files.FileHandle
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.Group
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.actions.Actions
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.Container
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.SelectBox
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||||
|
import com.badlogic.gdx.utils.Array as GdxArray
|
||||||
|
import com.unciv.UncivGame
|
||||||
|
import com.unciv.logic.UncivShowableException
|
||||||
|
import com.unciv.logic.files.MapSaver
|
||||||
|
import com.unciv.logic.map.MapParameters
|
||||||
|
import com.unciv.models.ruleset.RulesetCache
|
||||||
|
import com.unciv.ui.components.extensions.onChange
|
||||||
|
import com.unciv.ui.components.extensions.pad
|
||||||
|
import com.unciv.ui.components.extensions.toLabel
|
||||||
|
import com.unciv.ui.popups.Popup
|
||||||
|
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||||
|
import com.unciv.ui.screens.victoryscreen.LoadMapPreview
|
||||||
|
import com.unciv.utils.concurrency.Concurrency
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.isActive
|
||||||
|
|
||||||
|
class MapFileSelectTable(
|
||||||
|
private val newGameScreen: NewGameScreen,
|
||||||
|
private val mapParameters: MapParameters
|
||||||
|
) : Table() {
|
||||||
|
|
||||||
|
private val mapFileSelectBox = SelectBox<FileHandleWrapper>(BaseScreen.skin)
|
||||||
|
private val miniMapWrapper = Container<Group?>()
|
||||||
|
private var mapPreviewJob: Job? = null
|
||||||
|
|
||||||
|
private val mapFilesSequence = sequence<FileHandle> {
|
||||||
|
yieldAll(MapSaver.getMaps().asSequence())
|
||||||
|
for (modFolder in RulesetCache.values.mapNotNull { it.folderLocation }) {
|
||||||
|
val mapsFolder = modFolder.child(MapSaver.mapsFolder)
|
||||||
|
if (mapsFolder.exists())
|
||||||
|
yieldAll(mapsFolder.list().asSequence())
|
||||||
|
}
|
||||||
|
}.map { FileHandleWrapper(it) }
|
||||||
|
|
||||||
|
private val columnWidth = newGameScreen.getColumnWidth()
|
||||||
|
|
||||||
|
init {
|
||||||
|
defaults().pad(5f, 10f) // Must stay same as in MapParametersTable
|
||||||
|
val mapFileLabel = "{Map file}:".toLabel()
|
||||||
|
add(mapFileLabel).left()
|
||||||
|
add(mapFileSelectBox)
|
||||||
|
// because SOME people gotta give the hugest names to their maps
|
||||||
|
.maxWidth((columnWidth - mapFileLabel.prefWidth).coerceAtLeast(120f))
|
||||||
|
.right().row()
|
||||||
|
add(miniMapWrapper)
|
||||||
|
.pad(15f)
|
||||||
|
.colspan(2).center().row()
|
||||||
|
|
||||||
|
mapFileSelectBox.onChange { onSelectBoxChange() }
|
||||||
|
}
|
||||||
|
|
||||||
|
// The SelectBox auto displays the text a object.toString(), which on the FileHandle itself includes the folder path.
|
||||||
|
// So we wrap it in another object with a custom toString()
|
||||||
|
private class FileHandleWrapper(val fileHandle: FileHandle) {
|
||||||
|
override fun toString(): String = fileHandle.name()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isNotEmpty() = mapFilesSequence.any()
|
||||||
|
fun recentlySavedMapExists() = mapFilesSequence.any {
|
||||||
|
it.fileHandle.lastModified() > System.currentTimeMillis() - 900000
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fillMapFileSelectBox() {
|
||||||
|
if (!mapFileSelectBox.items.isEmpty) return
|
||||||
|
|
||||||
|
val mapFiles = GdxArray<FileHandleWrapper>()
|
||||||
|
mapFilesSequence
|
||||||
|
.sortedWith(compareBy(UncivGame.Current.settings.getCollatorFromLocale()) { it.toString() })
|
||||||
|
.forEach { mapFiles.add(it) }
|
||||||
|
mapFileSelectBox.items = mapFiles
|
||||||
|
|
||||||
|
// Pre-select: a) map saved within last 15min or b) map named in mapParameters or c) alphabetically first
|
||||||
|
// This is a kludge - the better way would be to have a "play this map now" menu button in the editor
|
||||||
|
// (which would ideally not even require a save file - which makes implementation non-trivial)
|
||||||
|
val selectedItem =
|
||||||
|
mapFiles.maxByOrNull { it.fileHandle.lastModified() }
|
||||||
|
?.takeIf { it.fileHandle.lastModified() > System.currentTimeMillis() - 900000 }
|
||||||
|
?: mapFiles.firstOrNull { it.fileHandle.name() == mapParameters.name }
|
||||||
|
?: mapFiles.firstOrNull()
|
||||||
|
?: return
|
||||||
|
mapFileSelectBox.selected = selectedItem
|
||||||
|
mapParameters.name = selectedItem.toString()
|
||||||
|
newGameScreen.gameSetupInfo.mapFile = selectedItem.fileHandle
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onSelectBoxChange() {
|
||||||
|
cancelBackgroundJobs()
|
||||||
|
val mapFile = mapFileSelectBox.selected.fileHandle
|
||||||
|
val mapParams = try {
|
||||||
|
MapSaver.loadMapParameters(mapFile)
|
||||||
|
} catch (ex:Exception){
|
||||||
|
ex.printStackTrace()
|
||||||
|
Popup(newGameScreen).apply {
|
||||||
|
addGoodSizedLabel("Could not load map!").row()
|
||||||
|
if (ex is UncivShowableException)
|
||||||
|
addGoodSizedLabel(ex.message).row()
|
||||||
|
addCloseButton()
|
||||||
|
open()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mapParameters.name = mapFile.name()
|
||||||
|
newGameScreen.gameSetupInfo.mapFile = mapFile
|
||||||
|
val mapMods = mapParams.mods.partition { RulesetCache[it]?.modOptions?.isBaseRuleset == true }
|
||||||
|
newGameScreen.gameSetupInfo.gameParameters.mods = LinkedHashSet(mapMods.second)
|
||||||
|
newGameScreen.gameSetupInfo.gameParameters.baseRuleset = mapMods.first.firstOrNull() ?: mapParams.baseRuleset
|
||||||
|
newGameScreen.updateRuleset()
|
||||||
|
newGameScreen.updateTables()
|
||||||
|
hideMiniMap()
|
||||||
|
startMapPreview(mapFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startMapPreview(mapFile: FileHandle) {
|
||||||
|
mapPreviewJob = Concurrency.run {
|
||||||
|
try {
|
||||||
|
val map = MapSaver.loadMap(mapFile)
|
||||||
|
if (!isActive) return@run
|
||||||
|
map.setTransients(newGameScreen.ruleset, false)
|
||||||
|
if (!isActive) return@run
|
||||||
|
// ReplyMap still paints outside its bounds - so we subtract padding and a little extra
|
||||||
|
val size = (columnWidth - 40f).coerceAtMost(500f)
|
||||||
|
val miniMap = LoadMapPreview(map, size, size)
|
||||||
|
if (!isActive) return@run
|
||||||
|
Concurrency.runOnGLThread {
|
||||||
|
showMinimap(miniMap)
|
||||||
|
}
|
||||||
|
} catch (_: Throwable) {}
|
||||||
|
}.apply {
|
||||||
|
invokeOnCompletion {
|
||||||
|
mapPreviewJob = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun cancelBackgroundJobs() {
|
||||||
|
mapPreviewJob?.cancel()
|
||||||
|
mapPreviewJob = null
|
||||||
|
miniMapWrapper.clearActions()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showMinimap(miniMap: LoadMapPreview) {
|
||||||
|
if (miniMapWrapper.actor == miniMap) return
|
||||||
|
miniMapWrapper.clearActions()
|
||||||
|
miniMapWrapper.color.a = 0f
|
||||||
|
miniMapWrapper.actor = miniMap
|
||||||
|
miniMapWrapper.invalidateHierarchy()
|
||||||
|
miniMapWrapper.addAction(Actions.fadeIn(0.2f))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hideMiniMap() {
|
||||||
|
if (miniMapWrapper.actor !is LoadMapPreview) return
|
||||||
|
miniMapWrapper.clearActions()
|
||||||
|
miniMapWrapper.addAction(
|
||||||
|
Actions.sequence(
|
||||||
|
Actions.fadeOut(0.4f),
|
||||||
|
Actions.run {
|
||||||
|
// in portrait, simply removing the map preview will cause the layout to "jump".
|
||||||
|
// with a dummy holding the empty space, it jumps later and not as far.
|
||||||
|
val dummy = Group().apply {
|
||||||
|
setSize(miniMapWrapper.actor!!.width, miniMapWrapper.actor!!.height)
|
||||||
|
}
|
||||||
|
miniMapWrapper.actor = dummy
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,66 +1,35 @@
|
|||||||
package com.unciv.ui.screens.newgamescreen
|
package com.unciv.ui.screens.newgamescreen
|
||||||
|
|
||||||
import com.badlogic.gdx.files.FileHandle
|
|
||||||
import com.badlogic.gdx.scenes.scene2d.ui.SelectBox
|
|
||||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||||
import com.badlogic.gdx.utils.Array
|
|
||||||
import com.unciv.UncivGame
|
|
||||||
import com.unciv.logic.files.MapSaver
|
|
||||||
import com.unciv.logic.UncivShowableException
|
|
||||||
import com.unciv.logic.map.MapGeneratedMainType
|
import com.unciv.logic.map.MapGeneratedMainType
|
||||||
import com.unciv.models.ruleset.RulesetCache
|
|
||||||
import com.unciv.ui.popups.Popup
|
|
||||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
|
||||||
import com.unciv.ui.components.extensions.onChange
|
import com.unciv.ui.components.extensions.onChange
|
||||||
import com.unciv.ui.components.extensions.toLabel
|
import com.unciv.ui.components.extensions.toLabel
|
||||||
|
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||||
|
|
||||||
class MapOptionsTable(private val newGameScreen: NewGameScreen): Table() {
|
class MapOptionsTable(private val newGameScreen: NewGameScreen): Table() {
|
||||||
|
|
||||||
private val mapParameters = newGameScreen.gameSetupInfo.mapParameters
|
private val mapParameters = newGameScreen.gameSetupInfo.mapParameters
|
||||||
private var mapTypeSpecificTable = Table()
|
private var mapTypeSpecificTable = Table()
|
||||||
val generatedMapOptionsTable = MapParametersTable(newGameScreen, mapParameters, MapGeneratedMainType.generated)
|
internal val generatedMapOptionsTable = MapParametersTable(newGameScreen, mapParameters, MapGeneratedMainType.generated)
|
||||||
private val randomMapOptionsTable = MapParametersTable(newGameScreen, mapParameters, MapGeneratedMainType.randomGenerated)
|
private val randomMapOptionsTable = MapParametersTable(newGameScreen, mapParameters, MapGeneratedMainType.randomGenerated)
|
||||||
private val savedMapOptionsTable = Table()
|
private val savedMapOptionsTable = MapFileSelectTable(newGameScreen, mapParameters)
|
||||||
lateinit var mapTypeSelectBox: TranslatedSelectBox
|
internal val mapTypeSelectBox: TranslatedSelectBox
|
||||||
private val mapFileSelectBox = createMapFileSelectBox()
|
|
||||||
|
|
||||||
private val mapFilesSequence = sequence<FileHandle> {
|
|
||||||
yieldAll(MapSaver.getMaps().asSequence())
|
|
||||||
for (modFolder in RulesetCache.values.mapNotNull { it.folderLocation }) {
|
|
||||||
val mapsFolder = modFolder.child(MapSaver.mapsFolder)
|
|
||||||
if (mapsFolder.exists())
|
|
||||||
yieldAll(mapsFolder.list().asSequence())
|
|
||||||
}
|
|
||||||
}.map { FileHandleWrapper(it) }
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
//defaults().pad(5f) - each nested table having the same can give 'stairs' effects,
|
//defaults().pad(5f) - each nested table having the same can give 'stairs' effects,
|
||||||
// better control directly. Besides, the first Labels/Buttons should have 10f to look nice
|
// better control directly. Besides, the first Labels/Buttons should have 10f to look nice
|
||||||
addMapTypeSelection()
|
|
||||||
background = BaseScreen.skinStrings.getUiBackground("NewGameScreen/MapOptionsTable", tintColor = BaseScreen.skinStrings.skinConfig.clearColor)
|
background = BaseScreen.skinStrings.getUiBackground("NewGameScreen/MapOptionsTable", tintColor = BaseScreen.skinStrings.skinConfig.clearColor)
|
||||||
}
|
|
||||||
|
|
||||||
private fun addMapTypeSelection() {
|
|
||||||
val mapTypes = arrayListOf(MapGeneratedMainType.generated, MapGeneratedMainType.randomGenerated)
|
val mapTypes = arrayListOf(MapGeneratedMainType.generated, MapGeneratedMainType.randomGenerated)
|
||||||
if (mapFilesSequence.any()) mapTypes.add(MapGeneratedMainType.custom)
|
if (savedMapOptionsTable.isNotEmpty()) mapTypes.add(MapGeneratedMainType.custom)
|
||||||
mapTypeSelectBox = TranslatedSelectBox(mapTypes, "Generated", BaseScreen.skin)
|
mapTypeSelectBox = TranslatedSelectBox(mapTypes, "Generated", BaseScreen.skin)
|
||||||
|
|
||||||
savedMapOptionsTable.defaults().pad(5f)
|
|
||||||
savedMapOptionsTable.add("{Map file}:".toLabel()).left()
|
|
||||||
// because SOME people gotta give the hugest names to their maps
|
|
||||||
val columnWidth = newGameScreen.stage.width / (if (newGameScreen.isNarrowerThan4to3()) 1 else 3)
|
|
||||||
savedMapOptionsTable.add(mapFileSelectBox)
|
|
||||||
.maxWidth((columnWidth - 120f).coerceAtLeast(120f))
|
|
||||||
.right().row()
|
|
||||||
|
|
||||||
|
|
||||||
fun updateOnMapTypeChange() {
|
fun updateOnMapTypeChange() {
|
||||||
mapTypeSpecificTable.clear()
|
mapTypeSpecificTable.clear()
|
||||||
when (mapTypeSelectBox.selected.value) {
|
when (mapTypeSelectBox.selected.value) {
|
||||||
MapGeneratedMainType.custom -> {
|
MapGeneratedMainType.custom -> {
|
||||||
fillMapFileSelectBox()
|
savedMapOptionsTable.fillMapFileSelectBox()
|
||||||
mapParameters.type = MapGeneratedMainType.custom
|
mapParameters.type = MapGeneratedMainType.custom
|
||||||
mapParameters.name = mapFileSelectBox.selected.toString()
|
|
||||||
mapTypeSpecificTable.add(savedMapOptionsTable)
|
mapTypeSpecificTable.add(savedMapOptionsTable)
|
||||||
newGameScreen.unlockTables()
|
newGameScreen.unlockTables()
|
||||||
}
|
}
|
||||||
@ -82,7 +51,7 @@ class MapOptionsTable(private val newGameScreen: NewGameScreen): Table() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pre-select custom if any map saved within last 15 minutes
|
// Pre-select custom if any map saved within last 15 minutes
|
||||||
if (mapFilesSequence.any { it.fileHandle.lastModified() > System.currentTimeMillis() - 900000 })
|
if (savedMapOptionsTable.recentlySavedMapExists())
|
||||||
mapTypeSelectBox.selected =
|
mapTypeSelectBox.selected =
|
||||||
TranslatedSelectBox.TranslatedString(MapGeneratedMainType.custom)
|
TranslatedSelectBox.TranslatedString(MapGeneratedMainType.custom)
|
||||||
|
|
||||||
@ -98,59 +67,5 @@ class MapOptionsTable(private val newGameScreen: NewGameScreen): Table() {
|
|||||||
add(mapTypeSpecificTable).row()
|
add(mapTypeSpecificTable).row()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createMapFileSelectBox(): SelectBox<FileHandleWrapper> {
|
internal fun cancelBackgroundJobs() = savedMapOptionsTable.cancelBackgroundJobs()
|
||||||
val mapFileSelectBox = SelectBox<FileHandleWrapper>(BaseScreen.skin)
|
|
||||||
mapFileSelectBox.onChange {
|
|
||||||
val mapFile = mapFileSelectBox.selected.fileHandle
|
|
||||||
val mapParams = try {
|
|
||||||
MapSaver.loadMapParameters(mapFile)
|
|
||||||
} catch (ex:Exception){
|
|
||||||
ex.printStackTrace()
|
|
||||||
Popup(newGameScreen).apply {
|
|
||||||
addGoodSizedLabel("Could not load map!").row()
|
|
||||||
if (ex is UncivShowableException)
|
|
||||||
addGoodSizedLabel(ex.message).row()
|
|
||||||
addCloseButton()
|
|
||||||
open()
|
|
||||||
}
|
|
||||||
return@onChange
|
|
||||||
}
|
|
||||||
mapParameters.name = mapFile.name()
|
|
||||||
newGameScreen.gameSetupInfo.mapFile = mapFile
|
|
||||||
val mapMods = mapParams.mods.partition { RulesetCache[it]?.modOptions?.isBaseRuleset == true }
|
|
||||||
newGameScreen.gameSetupInfo.gameParameters.mods = LinkedHashSet(mapMods.second)
|
|
||||||
newGameScreen.gameSetupInfo.gameParameters.baseRuleset = mapMods.first.firstOrNull() ?: mapParams.baseRuleset
|
|
||||||
newGameScreen.updateRuleset()
|
|
||||||
newGameScreen.updateTables()
|
|
||||||
}
|
|
||||||
return mapFileSelectBox
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun fillMapFileSelectBox() {
|
|
||||||
if (!mapFileSelectBox.items.isEmpty) return
|
|
||||||
val mapFiles = Array<FileHandleWrapper>()
|
|
||||||
mapFilesSequence
|
|
||||||
.sortedWith(compareBy(UncivGame.Current.settings.getCollatorFromLocale()) { it.toString() })
|
|
||||||
.forEach { mapFiles.add(it) }
|
|
||||||
mapFileSelectBox.items = mapFiles
|
|
||||||
|
|
||||||
// Pre-select: a) map saved within last 15min or b) map named in mapParameters or c) alphabetically first
|
|
||||||
// This is a kludge - the better way would be to have a "play this map now" menu button in the editor
|
|
||||||
// (which would ideally not even require a save file - which makes implementation non-trivial)
|
|
||||||
val selectedItem =
|
|
||||||
mapFiles.maxByOrNull { it.fileHandle.lastModified() }
|
|
||||||
?.takeIf { it.fileHandle.lastModified() > System.currentTimeMillis() - 900000 }
|
|
||||||
?: mapFiles.firstOrNull { it.fileHandle.name() == mapParameters.name }
|
|
||||||
?: mapFiles.firstOrNull()
|
|
||||||
?: return
|
|
||||||
mapFileSelectBox.selected = selectedItem
|
|
||||||
newGameScreen.gameSetupInfo.mapFile = selectedItem.fileHandle
|
|
||||||
}
|
|
||||||
|
|
||||||
// The SelectBox auto displays the text a object.toString(), which on the FileHandle itself includes the folder path.
|
|
||||||
// So we wrap it in another object with a custom toString()
|
|
||||||
class FileHandleWrapper(val fileHandle: FileHandle) {
|
|
||||||
override fun toString(): String = fileHandle.name()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -80,7 +80,12 @@ class MapParametersTable(
|
|||||||
skin = BaseScreen.skin
|
skin = BaseScreen.skin
|
||||||
defaults().pad(5f, 10f)
|
defaults().pad(5f, 10f)
|
||||||
if (mapGeneratedMainType == MapGeneratedMainType.randomGenerated) {
|
if (mapGeneratedMainType == MapGeneratedMainType.randomGenerated) {
|
||||||
add("{Which options should be available to the random selection?}".toLabel()).colspan(2).grow().row()
|
val prompt = "Which options should be available to the random selection?"
|
||||||
|
val width = (previousScreen as? NewGameScreen)?.getColumnWidth() ?: 200f
|
||||||
|
val label = WrappableLabel(prompt, width - 20f) // 20 is the defaults() padding
|
||||||
|
label.setAlignment(Align.center)
|
||||||
|
label.wrap = true
|
||||||
|
add(label).colspan(2).grow().row()
|
||||||
}
|
}
|
||||||
addMapShapeSelectBox()
|
addMapShapeSelectBox()
|
||||||
addMapTypeSelectBox()
|
addMapTypeSelectBox()
|
||||||
|
@ -20,10 +20,13 @@ import com.unciv.models.metadata.GameSetupInfo
|
|||||||
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.components.ExpanderTab
|
import com.unciv.ui.components.ExpanderTab
|
||||||
|
import com.unciv.ui.components.KeyCharAndCode
|
||||||
import com.unciv.ui.components.extensions.addSeparator
|
import com.unciv.ui.components.extensions.addSeparator
|
||||||
import com.unciv.ui.components.extensions.addSeparatorVertical
|
import com.unciv.ui.components.extensions.addSeparatorVertical
|
||||||
import com.unciv.ui.components.extensions.disable
|
import com.unciv.ui.components.extensions.disable
|
||||||
import com.unciv.ui.components.extensions.enable
|
import com.unciv.ui.components.extensions.enable
|
||||||
|
import com.unciv.ui.components.extensions.keyShortcuts
|
||||||
|
import com.unciv.ui.components.extensions.onActivation
|
||||||
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.toLabel
|
||||||
@ -76,7 +79,11 @@ class NewGameScreen(
|
|||||||
updatePlayerPickerRandomLabel = { playerPickerTable.updateRandomNumberLabel() }
|
updatePlayerPickerRandomLabel = { playerPickerTable.updateRandomNumberLabel() }
|
||||||
)
|
)
|
||||||
mapOptionsTable = MapOptionsTable(this)
|
mapOptionsTable = MapOptionsTable(this)
|
||||||
setDefaultCloseAction()
|
pickerPane.closeButton.onActivation {
|
||||||
|
mapOptionsTable.cancelBackgroundJobs()
|
||||||
|
game.popScreen()
|
||||||
|
}
|
||||||
|
pickerPane.closeButton.keyShortcuts.add(KeyCharAndCode.BACK)
|
||||||
|
|
||||||
if (isPortrait) initPortrait()
|
if (isPortrait) initPortrait()
|
||||||
else initLandscape()
|
else initLandscape()
|
||||||
@ -104,6 +111,7 @@ class NewGameScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun onStartGameClicked() {
|
private fun onStartGameClicked() {
|
||||||
|
mapOptionsTable.cancelBackgroundJobs()
|
||||||
if (gameSetupInfo.gameParameters.isOnlineMultiplayer) {
|
if (gameSetupInfo.gameParameters.isOnlineMultiplayer) {
|
||||||
if (!checkConnectionToMultiplayerServer()) {
|
if (!checkConnectionToMultiplayerServer()) {
|
||||||
val noInternetConnectionPopup = Popup(this)
|
val noInternetConnectionPopup = Popup(this)
|
||||||
@ -206,6 +214,10 @@ class NewGameScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Subtables may need an upper limit to their width - they can ask this function. */
|
||||||
|
// In sync with isPortrait in init, here so UI details need not know about 3-column vs 1-column layout
|
||||||
|
internal fun getColumnWidth() = stage.width / (if (isNarrowerThan4to3()) 1 else 3)
|
||||||
|
|
||||||
private fun initLandscape() {
|
private fun initLandscape() {
|
||||||
scrollPane.setScrollingDisabled(true,true)
|
scrollPane.setScrollingDisabled(true,true)
|
||||||
|
|
||||||
|
@ -2,55 +2,126 @@ package com.unciv.ui.screens.victoryscreen
|
|||||||
|
|
||||||
import com.badlogic.gdx.scenes.scene2d.Group
|
import com.badlogic.gdx.scenes.scene2d.Group
|
||||||
import com.unciv.logic.civilization.Civilization
|
import com.unciv.logic.civilization.Civilization
|
||||||
|
import com.unciv.logic.map.MapShape
|
||||||
import com.unciv.logic.map.TileMap
|
import com.unciv.logic.map.TileMap
|
||||||
|
import com.unciv.logic.map.tile.Tile
|
||||||
import com.unciv.ui.screens.worldscreen.minimap.MinimapTile
|
import com.unciv.ui.screens.worldscreen.minimap.MinimapTile
|
||||||
import com.unciv.ui.screens.worldscreen.minimap.MinimapTileUtil
|
import com.unciv.ui.screens.worldscreen.minimap.MinimapTileUtil
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.math.sqrt
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
// Mostly copied from MiniMap
|
// Mostly copied from MiniMap
|
||||||
class ReplayMap(
|
|
||||||
val tileMap: TileMap,
|
@Suppress("LeakingThis")
|
||||||
val viewingCiv: Civilization,
|
/**
|
||||||
private val replayMapWidth: Float,
|
* Base for a MiniMap not intertwined with a WorldScreen.
|
||||||
private val replayMapHeight: Float
|
* For a _minimal_ implementation see [LoadMapPreview]
|
||||||
|
*
|
||||||
|
* TODO: Analyze why MiniMap needs the tight WorldScreen integration and clean up / merge
|
||||||
|
*/
|
||||||
|
abstract class IndependentMiniMap(
|
||||||
|
val tileMap: TileMap
|
||||||
) : Group() {
|
) : Group() {
|
||||||
private val tileLayer = Group()
|
protected lateinit var minimapTiles: List<MinimapTile>
|
||||||
private val minimapTiles: List<MinimapTile>
|
|
||||||
|
|
||||||
init {
|
/** Call this in the init of derived classes.
|
||||||
val tileSize = calcTileSize()
|
*
|
||||||
|
* Needs to be deferred only to allow [calcTileSize] or [includeTileFilter] to use class parameters added in the derived class. */
|
||||||
|
protected open fun deferredInit(maxWidth: Float, maxHeight: Float) {
|
||||||
|
val tileSize = calcTileSize(maxWidth, maxHeight)
|
||||||
minimapTiles = createReplayMap(tileSize)
|
minimapTiles = createReplayMap(tileSize)
|
||||||
val tileExtension = MinimapTileUtil.spreadOutMinimapTiles(tileLayer, minimapTiles, tileSize)
|
val tileExtension = MinimapTileUtil.spreadOutMinimapTiles(this, minimapTiles, tileSize)
|
||||||
|
|
||||||
for (group in tileLayer.children) {
|
for (group in children) {
|
||||||
group.moveBy(-tileExtension.x, -tileExtension.y)
|
group.moveBy(-tileExtension.x, -tileExtension.y)
|
||||||
}
|
}
|
||||||
|
|
||||||
// there are tiles "below the zero",
|
setSize(tileExtension.width, tileExtension.height)
|
||||||
// so we zero out the starting position of the whole board so they will be displayed as well
|
|
||||||
tileLayer.setSize(tileExtension.width, tileExtension.height)
|
|
||||||
setSize(tileLayer.width, tileLayer.height)
|
|
||||||
addActor(tileLayer)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun calcTileSize(): Float {
|
/** Calculate a tile radius in screen coordinates so that the resulting map, after distributimg
|
||||||
|
* the tiles using spreadOutMinimapTiles, will not exceed the bounds ([maxWidth],[maxHeight]) */
|
||||||
|
protected abstract fun calcTileSize(maxWidth: Float, maxHeight: Float): Float
|
||||||
|
|
||||||
|
/** Controls which tiles are included */
|
||||||
|
protected open fun includeTileFilter(tile: Tile): Boolean = true
|
||||||
|
|
||||||
|
private fun createReplayMap(tileSize: Float): List<MinimapTile> {
|
||||||
|
val doNothing = fun(){}
|
||||||
|
val tiles = ArrayList<MinimapTile>(tileMap.values.size)
|
||||||
|
for (tile in tileMap.values.filter(::includeTileFilter) ) {
|
||||||
|
val minimapTile = MinimapTile(tile, tileSize, doNothing)
|
||||||
|
minimapTile.updateColor(false, null)
|
||||||
|
tiles.add(minimapTile)
|
||||||
|
}
|
||||||
|
tiles.trimToSize()
|
||||||
|
return tiles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A minimap with no WorldScreen dependencies, always shows the entire map.
|
||||||
|
*
|
||||||
|
* @param tileMap Map to display minimap-style
|
||||||
|
* @param maxWidth Resulting Group will not exceed this width
|
||||||
|
* @param maxHeight Resulting Group will not exceed this height
|
||||||
|
*/
|
||||||
|
class LoadMapPreview(
|
||||||
|
tileMap: TileMap,
|
||||||
|
maxWidth: Float,
|
||||||
|
maxHeight: Float
|
||||||
|
) : IndependentMiniMap(tileMap) {
|
||||||
|
init {
|
||||||
|
deferredInit(maxWidth, maxHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun calcTileSize(maxWidth: Float, maxHeight: Float): Float {
|
||||||
|
val height: Float
|
||||||
|
val width: Float
|
||||||
|
val mapSize = tileMap.mapParameters.mapSize
|
||||||
|
if (tileMap.mapParameters.shape != MapShape.rectangular) {
|
||||||
|
height = mapSize.radius * 2 + 1f
|
||||||
|
width = height
|
||||||
|
} else {
|
||||||
|
height = mapSize.height.toFloat()
|
||||||
|
width = mapSize.width.toFloat()
|
||||||
|
}
|
||||||
|
// See HexMath.worldFromLatLong, the 0.6 is empiric to avoid rounding to cause the map to spill over
|
||||||
|
return min(
|
||||||
|
maxWidth / (width + 0.6f) / 1.5f * 2f,
|
||||||
|
maxHeight / (height + 0.6f) / sqrt(3f) * 2f,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A minimap with no WorldScreen dependencies, with the ability to show historical states.
|
||||||
|
*
|
||||||
|
* @param tileMap Map to display minimap-style
|
||||||
|
* @param viewingCiv used to determine tile visibility and explored area
|
||||||
|
* @param maxWidth Resulting Group should not exceed this width
|
||||||
|
* @param maxHeight Resulting Group should not exceed this height
|
||||||
|
*/
|
||||||
|
class ReplayMap(
|
||||||
|
tileMap: TileMap,
|
||||||
|
val viewingCiv: Civilization,
|
||||||
|
maxWidth: Float,
|
||||||
|
maxHeight: Float
|
||||||
|
) : IndependentMiniMap(tileMap) {
|
||||||
|
init {
|
||||||
|
deferredInit(maxWidth, maxHeight)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun calcTileSize(maxWidth: Float, maxHeight: Float): Float {
|
||||||
val height = viewingCiv.exploredRegion.getHeight().toFloat()
|
val height = viewingCiv.exploredRegion.getHeight().toFloat()
|
||||||
val width = viewingCiv.exploredRegion.getWidth().toFloat()
|
val width = viewingCiv.exploredRegion.getWidth().toFloat()
|
||||||
return min (
|
return min (
|
||||||
replayMapHeight / (height + 1.5f) / sqrt(3f) * 4f, // 1.5 - padding, hex height = sqrt(3) / 2 * d / 2 -> d = height / sqrt(3) * 2 * 2
|
maxHeight / (height + 1.5f) / sqrt(3f) * 4f, // 1.5 - padding, hex height = sqrt(3) / 2 * d / 2 -> d = height / sqrt(3) * 2 * 2
|
||||||
replayMapWidth / (width + 0.5f) / 0.75f // 0.5 - padding, hex width = 0.75 * d -> d = width / 0.75
|
maxWidth / (width + 0.5f) / 0.75f // 0.5 - padding, hex width = 0.75 * d -> d = width / 0.75
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createReplayMap(tileSize: Float): List<MinimapTile> {
|
override fun includeTileFilter(tile: Tile) = tile.isExplored(viewingCiv)
|
||||||
val tiles = ArrayList<MinimapTile>()
|
|
||||||
for (tile in tileMap.values.filter { it.isExplored(viewingCiv) }) {
|
|
||||||
val minimapTile = MinimapTile(tile, tileSize) {}
|
|
||||||
tiles.add(minimapTile)
|
|
||||||
}
|
|
||||||
return tiles
|
|
||||||
}
|
|
||||||
|
|
||||||
fun update(turn: Int) {
|
fun update(turn: Int) {
|
||||||
val viewingCivIsDefeated = viewingCiv.gameInfo.victoryData != null || !viewingCiv.isAlive()
|
val viewingCivIsDefeated = viewingCiv.gameInfo.victoryData != null || !viewingCiv.isAlive()
|
||||||
|
@ -51,7 +51,7 @@ class VictoryScreenReplay(
|
|||||||
gameInfo.tileMap,
|
gameInfo.tileMap,
|
||||||
worldScreen.viewingCiv,
|
worldScreen.viewingCiv,
|
||||||
worldScreen.stage.width - 50,
|
worldScreen.stage.width - 50,
|
||||||
worldScreen.stage.height - 250
|
worldScreen.stage.height - 250 // Empiric: `stage.height - pager.contentScroll_field.height` after init is 244.
|
||||||
)
|
)
|
||||||
|
|
||||||
playImage.setSize(24f)
|
playImage.setSize(24f)
|
||||||
|
Reference in New Issue
Block a user