mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-26 13:27:22 -04:00
Save and load reorg and keyboard handling (#6929)
* Load and Save Game Screens rework - Linting * Load and Save Game Screens rework - Modularize and Keyboard * Load and Save Game Screens rework - error handling * Load and Save Game Screens rework - Move other save/load code * Load and Save Game Screens rework - More Keyboard * Load and Save Game Screens rework - Increase clipboard limit * Load and Save Game Screens rework - Post-merge patch * Load and Save Game Screens rework - Home, End, harden * Load and Save Game Screens rework - Post-merge patch again * Load and Save Game Screens rework - reviews
This commit is contained in:
parent
983a9b705e
commit
1b008905f6
@ -598,6 +598,9 @@ Copy saved game to clipboard =
|
|||||||
Could not load game =
|
Could not load game =
|
||||||
Load [saveFileName] =
|
Load [saveFileName] =
|
||||||
Delete save =
|
Delete save =
|
||||||
|
[saveFileName] deleted successfully. =
|
||||||
|
Insufficient permissions to delete [saveFileName]. =
|
||||||
|
Failed to delete [saveFileName]. =
|
||||||
Saved at =
|
Saved at =
|
||||||
Saving... =
|
Saving... =
|
||||||
Overwrite existing file? =
|
Overwrite existing file? =
|
||||||
@ -610,6 +613,7 @@ Load from custom location =
|
|||||||
Could not load game from custom location! =
|
Could not load game from custom location! =
|
||||||
Save to custom location =
|
Save to custom location =
|
||||||
Could not save game to custom location! =
|
Could not save game to custom location! =
|
||||||
|
Download missing mods =
|
||||||
Missing mods are downloaded successfully. =
|
Missing mods are downloaded successfully. =
|
||||||
Could not load the missing mods! =
|
Could not load the missing mods! =
|
||||||
Could not download mod list. =
|
Could not download mod list. =
|
||||||
|
@ -66,6 +66,7 @@ object Constants {
|
|||||||
const val close = "Close"
|
const val close = "Close"
|
||||||
const val yes = "Yes"
|
const val yes = "Yes"
|
||||||
const val no = "No"
|
const val no = "No"
|
||||||
|
const val loading = "Loading..."
|
||||||
|
|
||||||
const val barbarians = "Barbarians"
|
const val barbarians = "Barbarians"
|
||||||
const val spectator = "Spectator"
|
const val spectator = "Spectator"
|
||||||
|
@ -27,6 +27,7 @@ import com.unciv.ui.newgamescreen.NewGameScreen
|
|||||||
import com.unciv.ui.pickerscreens.ModManagementScreen
|
import com.unciv.ui.pickerscreens.ModManagementScreen
|
||||||
import com.unciv.ui.popup.*
|
import com.unciv.ui.popup.*
|
||||||
import com.unciv.ui.saves.LoadGameScreen
|
import com.unciv.ui.saves.LoadGameScreen
|
||||||
|
import com.unciv.ui.saves.QuickSave
|
||||||
import com.unciv.ui.utils.*
|
import com.unciv.ui.utils.*
|
||||||
import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip
|
import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip
|
||||||
import com.unciv.ui.worldscreen.mainmenu.WorldScreenMenuPopup
|
import com.unciv.ui.worldscreen.mainmenu.WorldScreenMenuPopup
|
||||||
@ -175,49 +176,7 @@ class MainMenuScreen: BaseScreen() {
|
|||||||
curWorldScreen.popups.filterIsInstance(WorldScreenMenuPopup::class.java).forEach(Popup::close)
|
curWorldScreen.popups.filterIsInstance(WorldScreenMenuPopup::class.java).forEach(Popup::close)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
QuickSave.autoLoadGame(this)
|
||||||
val loadingPopup = Popup(this)
|
|
||||||
loadingPopup.addGoodSizedLabel("Loading...")
|
|
||||||
loadingPopup.open()
|
|
||||||
launchCrashHandling("autoLoadGame") {
|
|
||||||
// Load game from file to class on separate thread to avoid ANR...
|
|
||||||
fun outOfMemory() {
|
|
||||||
postCrashHandlingRunnable {
|
|
||||||
loadingPopup.close()
|
|
||||||
ToastPopup("Not enough memory on phone to load game!", this@MainMenuScreen)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val savedGame: GameInfo
|
|
||||||
try {
|
|
||||||
savedGame = game.gameSaver.loadLatestAutosave()
|
|
||||||
} catch (oom: OutOfMemoryError) {
|
|
||||||
outOfMemory()
|
|
||||||
return@launchCrashHandling
|
|
||||||
} catch (ex: Exception) {
|
|
||||||
postCrashHandlingRunnable {
|
|
||||||
loadingPopup.close()
|
|
||||||
ToastPopup("Cannot resume game!", this@MainMenuScreen)
|
|
||||||
}
|
|
||||||
return@launchCrashHandling
|
|
||||||
}
|
|
||||||
|
|
||||||
if (savedGame.gameParameters.isOnlineMultiplayer) {
|
|
||||||
try {
|
|
||||||
game.onlineMultiplayer.loadGame(savedGame)
|
|
||||||
} catch (oom: OutOfMemoryError) {
|
|
||||||
outOfMemory()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
postCrashHandlingRunnable { /// ... and load it into the screen on main thread for GL context
|
|
||||||
try {
|
|
||||||
game.loadGame(savedGame)
|
|
||||||
} catch (oom: OutOfMemoryError) {
|
|
||||||
outOfMemory()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun quickstartNewGame() {
|
private fun quickstartNewGame() {
|
||||||
|
@ -73,8 +73,12 @@ class GameSaver(
|
|||||||
|
|
||||||
fun canLoadFromCustomSaveLocation() = customFileLocationHelper != null
|
fun canLoadFromCustomSaveLocation() = customFileLocationHelper != null
|
||||||
|
|
||||||
fun deleteSave(gameName: String) {
|
/** Deletes a save.
|
||||||
getSave(gameName).delete()
|
* @return `true` if successful.
|
||||||
|
* @throws SecurityException when delete access was denied
|
||||||
|
*/
|
||||||
|
fun deleteSave(gameName: String): Boolean {
|
||||||
|
return getSave(gameName).delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteMultiplayerSave(gameName: String) {
|
fun deleteMultiplayerSave(gameName: String) {
|
||||||
|
@ -204,7 +204,7 @@ class CityConstructionsTable(private val cityScreen: CityScreen) {
|
|||||||
val constructionsScrollY = availableConstructionsScrollPane.scrollY
|
val constructionsScrollY = availableConstructionsScrollPane.scrollY
|
||||||
|
|
||||||
if (!availableConstructionsTable.hasChildren()) { //
|
if (!availableConstructionsTable.hasChildren()) { //
|
||||||
availableConstructionsTable.add("Loading...".toLabel()).pad(10f)
|
availableConstructionsTable.add(Constants.loading.toLabel()).pad(10f)
|
||||||
}
|
}
|
||||||
|
|
||||||
launchCrashHandling("Construction info gathering - ${cityScreen.city.name}") {
|
launchCrashHandling("Construction info gathering - ${cityScreen.city.name}") {
|
||||||
|
@ -5,6 +5,7 @@ import com.badlogic.gdx.Input
|
|||||||
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
|
||||||
|
import com.unciv.Constants
|
||||||
import com.unciv.logic.MapSaver
|
import com.unciv.logic.MapSaver
|
||||||
import com.unciv.logic.UncivShowableException
|
import com.unciv.logic.UncivShowableException
|
||||||
import com.unciv.models.ruleset.RulesetCache
|
import com.unciv.models.ruleset.RulesetCache
|
||||||
@ -89,7 +90,7 @@ class MapEditorLoadTab(
|
|||||||
Gdx.app.postRunnable {
|
Gdx.app.postRunnable {
|
||||||
if (!needPopup) return@postRunnable
|
if (!needPopup) return@postRunnable
|
||||||
popup = Popup(editorScreen).apply {
|
popup = Popup(editorScreen).apply {
|
||||||
addGoodSizedLabel("Loading...")
|
addGoodSizedLabel(Constants.loading)
|
||||||
open()
|
open()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
package com.unciv.ui.multiplayer
|
package com.unciv.ui.multiplayer
|
||||||
|
|
||||||
|
import com.unciv.Constants
|
||||||
import com.unciv.ui.utils.BaseScreen
|
import com.unciv.ui.utils.BaseScreen
|
||||||
import com.unciv.ui.utils.center
|
import com.unciv.ui.utils.center
|
||||||
import com.unciv.ui.utils.toLabel
|
import com.unciv.ui.utils.toLabel
|
||||||
|
|
||||||
class LoadDeepLinkScreen : BaseScreen() {
|
class LoadDeepLinkScreen : BaseScreen() {
|
||||||
init {
|
init {
|
||||||
val loadingLabel = "Loading...".toLabel()
|
val loadingLabel = Constants.loading.toLabel()
|
||||||
stage.addActor(loadingLabel)
|
stage.addActor(loadingLabel)
|
||||||
loadingLabel.center(stage)
|
loadingLabel.center(stage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ open class PickerScreen(disableScroll: Boolean = false) : BaseScreen() {
|
|||||||
init {
|
init {
|
||||||
pickerPane.setFillParent(true)
|
pickerPane.setFillParent(true)
|
||||||
stage.addActor(pickerPane)
|
stage.addActor(pickerPane)
|
||||||
ensureLayout()
|
ensureLayout()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Make sure that anyone relying on sizes of the tables within this class during construction gets correct size readings.
|
/** Make sure that anyone relying on sizes of the tables within this class during construction gets correct size readings.
|
||||||
@ -39,7 +39,7 @@ open class PickerScreen(disableScroll: Boolean = false) : BaseScreen() {
|
|||||||
* Initializes the [Close button][closeButton]'s action (and the Back/ESC handler)
|
* Initializes the [Close button][closeButton]'s action (and the Back/ESC handler)
|
||||||
* to return to the [previousScreen] if specified, or else to the world screen.
|
* to return to the [previousScreen] if specified, or else to the world screen.
|
||||||
*/
|
*/
|
||||||
fun setDefaultCloseAction(previousScreen: BaseScreen?=null) {
|
fun setDefaultCloseAction(previousScreen: BaseScreen? = null) {
|
||||||
val closeAction = {
|
val closeAction = {
|
||||||
if (previousScreen != null) game.setScreen(previousScreen)
|
if (previousScreen != null) game.setScreen(previousScreen)
|
||||||
else game.resetToWorldScreen()
|
else game.resetToWorldScreen()
|
||||||
|
@ -3,12 +3,10 @@ package com.unciv.ui.saves
|
|||||||
import com.badlogic.gdx.Gdx
|
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.actions.Actions
|
|
||||||
import com.badlogic.gdx.scenes.scene2d.ui.CheckBox
|
|
||||||
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.utils.Align
|
import com.badlogic.gdx.utils.SerializationException
|
||||||
import com.unciv.UncivGame
|
import com.unciv.Constants
|
||||||
import com.unciv.logic.GameSaver
|
import com.unciv.logic.GameSaver
|
||||||
import com.unciv.logic.MissingModsException
|
import com.unciv.logic.MissingModsException
|
||||||
import com.unciv.logic.UncivShowableException
|
import com.unciv.logic.UncivShowableException
|
||||||
@ -16,90 +14,126 @@ import com.unciv.models.ruleset.RulesetCache
|
|||||||
import com.unciv.models.translations.tr
|
import com.unciv.models.translations.tr
|
||||||
import com.unciv.ui.crashhandling.launchCrashHandling
|
import com.unciv.ui.crashhandling.launchCrashHandling
|
||||||
import com.unciv.ui.crashhandling.postCrashHandlingRunnable
|
import com.unciv.ui.crashhandling.postCrashHandlingRunnable
|
||||||
import com.unciv.ui.images.ImageGetter
|
|
||||||
import com.unciv.ui.pickerscreens.Github
|
import com.unciv.ui.pickerscreens.Github
|
||||||
import com.unciv.ui.pickerscreens.PickerScreen
|
|
||||||
import com.unciv.ui.popup.Popup
|
import com.unciv.ui.popup.Popup
|
||||||
import com.unciv.ui.popup.ToastPopup
|
import com.unciv.ui.popup.ToastPopup
|
||||||
import com.unciv.ui.utils.*
|
import com.unciv.ui.utils.*
|
||||||
import com.unciv.ui.utils.UncivDateFormat.formatDate
|
import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip
|
||||||
import java.util.*
|
import java.io.FileNotFoundException
|
||||||
import java.util.concurrent.CancellationException
|
import java.util.concurrent.CancellationException
|
||||||
import com.unciv.ui.utils.AutoScrollPane as ScrollPane
|
|
||||||
|
|
||||||
class LoadGameScreen(previousScreen:BaseScreen) : PickerScreen(disableScroll = true) {
|
class LoadGameScreen(previousScreen:BaseScreen) : LoadOrSaveScreen() {
|
||||||
lateinit var selectedSave: String
|
private val copySavedGameToClipboardButton = getCopyExistingSaveToClipboardButton()
|
||||||
private val copySavedGameToClipboardButton = "Copy saved game to clipboard".toTextButton()
|
private val errorLabel = "".toLabel(Color.RED).apply { isVisible = false }
|
||||||
private val saveTable = Table()
|
private val loadMissingModsButton = getLoadMissingModsButton()
|
||||||
private val deleteSaveButton = "Delete save".toTextButton()
|
|
||||||
private val errorLabel = "".toLabel(Color.RED)
|
|
||||||
private val loadMissingModsButton = "Download missing mods".toTextButton()
|
|
||||||
private val showAutosavesCheckbox = CheckBox("Show autosaves".tr(), skin)
|
|
||||||
private var missingModsToLoad = ""
|
private var missingModsToLoad = ""
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val loadGame = "Load game"
|
||||||
|
private const val loadFromCustomLocation = "Load from custom location"
|
||||||
|
private const val loadFromClipboard = "Load copied data"
|
||||||
|
private const val copyExistingSaveToClipboard = "Copy saved game to clipboard"
|
||||||
|
private const val downloadMissingMods = "Download missing mods"
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setDefaultCloseAction(previousScreen)
|
setDefaultCloseAction(previousScreen)
|
||||||
|
rightSideTable.initRightSideTable()
|
||||||
|
rightSideButton.onClick(::onLoadGame)
|
||||||
|
keyPressDispatcher[KeyCharAndCode.RETURN] = ::onLoadGame
|
||||||
|
}
|
||||||
|
|
||||||
resetWindowState()
|
override fun resetWindowState() {
|
||||||
topTable.add(ScrollPane(saveTable))
|
super.resetWindowState()
|
||||||
|
copySavedGameToClipboardButton.disable()
|
||||||
|
rightSideButton.setText(loadGame.tr())
|
||||||
|
rightSideButton.disable()
|
||||||
|
}
|
||||||
|
|
||||||
val rightSideTable = getRightSideTable()
|
override fun onExistingSaveSelected(saveGameFile: FileHandle) {
|
||||||
|
copySavedGameToClipboardButton.enable()
|
||||||
|
rightSideButton.setText("Load [$selectedSave]".tr())
|
||||||
|
rightSideButton.enable()
|
||||||
|
}
|
||||||
|
|
||||||
topTable.add(rightSideTable)
|
private fun Table.initRightSideTable() {
|
||||||
|
add(getLoadFromClipboardButton()).row()
|
||||||
|
addLoadFromCustomLocationButton()
|
||||||
|
add(errorLabel).row()
|
||||||
|
add(loadMissingModsButton).row()
|
||||||
|
add(deleteSaveButton).row()
|
||||||
|
add(copySavedGameToClipboardButton).row()
|
||||||
|
add(showAutosavesCheckbox).row()
|
||||||
|
}
|
||||||
|
|
||||||
rightSideButton.onClick {
|
private fun onLoadGame() {
|
||||||
val loadingPopup = Popup( this)
|
if (selectedSave.isEmpty()) return
|
||||||
loadingPopup.addGoodSizedLabel("Loading...")
|
val loadingPopup = Popup( this)
|
||||||
loadingPopup.open()
|
loadingPopup.addGoodSizedLabel(Constants.loading)
|
||||||
launchCrashHandling("Load Game") {
|
loadingPopup.open()
|
||||||
try {
|
launchCrashHandling(loadGame) {
|
||||||
// This is what can lead to ANRs - reading the file and setting the transients, that's why this is in another thread
|
try {
|
||||||
val loadedGame = game.gameSaver.loadGameByName(selectedSave)
|
// This is what can lead to ANRs - reading the file and setting the transients, that's why this is in another thread
|
||||||
postCrashHandlingRunnable { UncivGame.Current.loadGame(loadedGame) }
|
val loadedGame = game.gameSaver.loadGameByName(selectedSave)
|
||||||
} catch (ex: Exception) {
|
postCrashHandlingRunnable { game.loadGame(loadedGame) }
|
||||||
postCrashHandlingRunnable {
|
} catch (ex: Exception) {
|
||||||
loadingPopup.close()
|
postCrashHandlingRunnable {
|
||||||
val cantLoadGamePopup = Popup(this@LoadGameScreen)
|
loadingPopup.close()
|
||||||
cantLoadGamePopup.addGoodSizedLabel("It looks like your saved game can't be loaded!").row()
|
if (ex is MissingModsException) {
|
||||||
if (ex is UncivShowableException && ex.localizedMessage != null) {
|
handleLoadGameException("Could not load game", ex)
|
||||||
// thrown exceptions are our own tests and can be shown to the user
|
return@postCrashHandlingRunnable
|
||||||
cantLoadGamePopup.addGoodSizedLabel(ex.localizedMessage).row()
|
|
||||||
cantLoadGamePopup.addCloseButton()
|
|
||||||
cantLoadGamePopup.open()
|
|
||||||
} else {
|
|
||||||
cantLoadGamePopup.addGoodSizedLabel("If you could copy your game data (\"Copy saved game to clipboard\" - ").row()
|
|
||||||
cantLoadGamePopup.addGoodSizedLabel(" paste into an email to yairm210@hotmail.com)").row()
|
|
||||||
cantLoadGamePopup.addGoodSizedLabel("I could maybe help you figure out what went wrong, since this isn't supposed to happen!").row()
|
|
||||||
cantLoadGamePopup.addCloseButton()
|
|
||||||
cantLoadGamePopup.open()
|
|
||||||
ex.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
val cantLoadGamePopup = Popup(this@LoadGameScreen)
|
||||||
|
cantLoadGamePopup.addGoodSizedLabel("It looks like your saved game can't be loaded!").row()
|
||||||
|
if (ex is SerializationException)
|
||||||
|
cantLoadGamePopup.addGoodSizedLabel("The file data seems to be corrupted.").row()
|
||||||
|
if (ex.cause is FileNotFoundException && (ex.cause as FileNotFoundException).message?.contains("Permission denied") == true) {
|
||||||
|
cantLoadGamePopup.addGoodSizedLabel("You do not have sufficient permissions to access the file.").row()
|
||||||
|
} else if (ex is UncivShowableException) {
|
||||||
|
// thrown exceptions are our own tests and can be shown to the user
|
||||||
|
cantLoadGamePopup.addGoodSizedLabel(ex.message).row()
|
||||||
|
} else {
|
||||||
|
cantLoadGamePopup.addGoodSizedLabel("If you could copy your game data (\"Copy saved game to clipboard\" - ").row()
|
||||||
|
cantLoadGamePopup.addGoodSizedLabel(" paste into an email to yairm210@hotmail.com)").row()
|
||||||
|
cantLoadGamePopup.addGoodSizedLabel("I could maybe help you figure out what went wrong, since this isn't supposed to happen!").row()
|
||||||
|
ex.printStackTrace()
|
||||||
|
}
|
||||||
|
cantLoadGamePopup.addCloseButton()
|
||||||
|
cantLoadGamePopup.open()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getRightSideTable(): Table {
|
private fun getLoadFromClipboardButton(): TextButton {
|
||||||
val rightSideTable = Table()
|
val pasteButton = loadFromClipboard.toTextButton()
|
||||||
rightSideTable.defaults().pad(10f)
|
val pasteHandler: ()->Unit = {
|
||||||
|
launchCrashHandling(loadFromClipboard) {
|
||||||
val loadFromClipboardButton = "Load copied data".toTextButton()
|
try {
|
||||||
loadFromClipboardButton.onClick {
|
val clipboardContentsString = Gdx.app.clipboard.contents.trim()
|
||||||
try {
|
val loadedGame = GameSaver.gameInfoFromString(clipboardContentsString)
|
||||||
val clipboardContentsString = Gdx.app.clipboard.contents.trim()
|
postCrashHandlingRunnable { game.loadGame(loadedGame) }
|
||||||
val loadedGame = GameSaver.gameInfoFromString(clipboardContentsString)
|
} catch (ex: Exception) {
|
||||||
UncivGame.Current.loadGame(loadedGame)
|
postCrashHandlingRunnable { handleLoadGameException("Could not load game from clipboard!", ex) }
|
||||||
} catch (ex: Exception) {
|
}
|
||||||
handleLoadGameException("Could not load game from clipboard!", ex)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rightSideTable.add(loadFromClipboardButton).row()
|
pasteButton.onClick(pasteHandler)
|
||||||
if (game.gameSaver.canLoadFromCustomSaveLocation()) {
|
val ctrlV = KeyCharAndCode.ctrl('v')
|
||||||
val loadFromCustomLocation = "Load from custom location".toTextButton()
|
keyPressDispatcher[ctrlV] = pasteHandler
|
||||||
loadFromCustomLocation.onClick {
|
pasteButton.addTooltip(ctrlV)
|
||||||
game.gameSaver.loadGameFromCustomLocation { result ->
|
return pasteButton
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Table.addLoadFromCustomLocationButton() {
|
||||||
|
if (!game.gameSaver.canLoadFromCustomSaveLocation()) return
|
||||||
|
val loadFromCustomLocation = loadFromCustomLocation.toTextButton()
|
||||||
|
loadFromCustomLocation.onClick {
|
||||||
|
errorLabel.isVisible = false
|
||||||
|
loadFromCustomLocation.setText(Constants.loading.tr())
|
||||||
|
loadFromCustomLocation.disable()
|
||||||
|
launchCrashHandling(Companion.loadFromCustomLocation) {
|
||||||
|
game.gameSaver.loadGameFromCustomLocation { result ->
|
||||||
if (result.isError()) {
|
if (result.isError()) {
|
||||||
handleLoadGameException("Could not load game from custom location!", result.exception)
|
handleLoadGameException("Could not load game from custom location!", result.exception)
|
||||||
} else if (result.isSuccessful()) {
|
} else if (result.isSuccessful()) {
|
||||||
@ -107,73 +141,82 @@ class LoadGameScreen(previousScreen:BaseScreen) : PickerScreen(disableScroll = t
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rightSideTable.add(loadFromCustomLocation).row()
|
|
||||||
}
|
}
|
||||||
rightSideTable.add(errorLabel).row()
|
add(loadFromCustomLocation).row()
|
||||||
|
}
|
||||||
|
|
||||||
loadMissingModsButton.onClick {
|
private fun getCopyExistingSaveToClipboardButton(): TextButton {
|
||||||
|
val copyButton = copyExistingSaveToClipboard.toTextButton()
|
||||||
|
val copyHandler: ()->Unit = {
|
||||||
|
launchCrashHandling(copyExistingSaveToClipboard) {
|
||||||
|
try {
|
||||||
|
val gameText = game.gameSaver.getSave(selectedSave).readString()
|
||||||
|
Gdx.app.clipboard.contents = if (gameText[0] == '{') Gzip.zip(gameText) else gameText
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
ex.printStackTrace()
|
||||||
|
ToastPopup("Could not save game to clipboard!", this@LoadGameScreen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
copyButton.onClick(copyHandler)
|
||||||
|
copyButton.disable()
|
||||||
|
val ctrlC = KeyCharAndCode.ctrl('c')
|
||||||
|
keyPressDispatcher[ctrlC] = copyHandler
|
||||||
|
copyButton.addTooltip(ctrlC)
|
||||||
|
return copyButton
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getLoadMissingModsButton(): TextButton {
|
||||||
|
val button = downloadMissingMods.toTextButton()
|
||||||
|
button.onClick {
|
||||||
loadMissingMods()
|
loadMissingMods()
|
||||||
}
|
}
|
||||||
loadMissingModsButton.isVisible = false
|
button.isVisible = false
|
||||||
rightSideTable.add(loadMissingModsButton).row()
|
return button
|
||||||
|
|
||||||
deleteSaveButton.onClick {
|
|
||||||
game.gameSaver.deleteSave(selectedSave)
|
|
||||||
resetWindowState()
|
|
||||||
}
|
|
||||||
deleteSaveButton.disable()
|
|
||||||
rightSideTable.add(deleteSaveButton).row()
|
|
||||||
|
|
||||||
copySavedGameToClipboardButton.disable()
|
|
||||||
copySavedGameToClipboardButton.onClick {
|
|
||||||
val gameText = game.gameSaver.getSave(selectedSave).readString()
|
|
||||||
val gzippedGameText = Gzip.zip(gameText)
|
|
||||||
Gdx.app.clipboard.contents = gzippedGameText
|
|
||||||
}
|
|
||||||
rightSideTable.add(copySavedGameToClipboardButton).row()
|
|
||||||
|
|
||||||
showAutosavesCheckbox.isChecked = false
|
|
||||||
showAutosavesCheckbox.onChange {
|
|
||||||
updateLoadableGames(showAutosavesCheckbox.isChecked)
|
|
||||||
}
|
|
||||||
rightSideTable.add(showAutosavesCheckbox).row()
|
|
||||||
return rightSideTable
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLoadGameException(primaryText: String, ex: Exception?) {
|
private fun handleLoadGameException(primaryText: String, ex: Exception?) {
|
||||||
var errorText = primaryText.tr()
|
var errorText = primaryText.tr()
|
||||||
if (ex is UncivShowableException) errorText += "\n${ex.message}"
|
if (ex is UncivShowableException) errorText += "\n${ex.localizedMessage}"
|
||||||
errorLabel.setText(errorText)
|
|
||||||
ex?.printStackTrace()
|
ex?.printStackTrace()
|
||||||
if (ex is MissingModsException) {
|
postCrashHandlingRunnable {
|
||||||
loadMissingModsButton.isVisible = true
|
errorLabel.setText(errorText)
|
||||||
missingModsToLoad = ex.missingMods
|
errorLabel.isVisible = true
|
||||||
|
if (ex is MissingModsException) {
|
||||||
|
loadMissingModsButton.isVisible = true
|
||||||
|
missingModsToLoad = ex.missingMods
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadMissingMods() {
|
private fun loadMissingMods() {
|
||||||
loadMissingModsButton.isEnabled = false
|
loadMissingModsButton.isEnabled = false
|
||||||
descriptionLabel.setText("Loading...".tr())
|
descriptionLabel.setText(Constants.loading.tr())
|
||||||
launchCrashHandling("DownloadMods", runAsDaemon = false) {
|
launchCrashHandling(downloadMissingMods, runAsDaemon = false) {
|
||||||
try {
|
try {
|
||||||
val mods = missingModsToLoad.replace(' ', '-').lowercase().splitToSequence(",-")
|
val mods = missingModsToLoad.replace(' ', '-').lowercase().splitToSequence(",-")
|
||||||
for (modName in mods) {
|
for (modName in mods) {
|
||||||
val repos = Github.tryGetGithubReposWithTopic(10, 1, modName)
|
val repos = Github.tryGetGithubReposWithTopic(10, 1, modName)
|
||||||
?: throw UncivShowableException("Could not download mod list.".tr())
|
?: throw UncivShowableException("Could not download mod list.")
|
||||||
val repo = repos.items.firstOrNull { it.name.lowercase() == modName }
|
val repo = repos.items.firstOrNull { it.name.lowercase() == modName }
|
||||||
?: throw UncivShowableException("Could not find a mod named \"[$modName]\".".tr())
|
?: throw UncivShowableException("Could not find a mod named \"[$modName]\".")
|
||||||
val modFolder = Github.downloadAndExtract(
|
val modFolder = Github.downloadAndExtract(
|
||||||
repo.html_url, repo.default_branch,
|
repo.html_url, repo.default_branch,
|
||||||
Gdx.files.local("mods")
|
Gdx.files.local("mods")
|
||||||
)
|
)
|
||||||
?: throw Exception() // downloadAndExtract returns null for 404 errors and the like -> display something!
|
?: throw Exception() // downloadAndExtract returns null for 404 errors and the like -> display something!
|
||||||
Github.rewriteModOptions(repo, modFolder)
|
Github.rewriteModOptions(repo, modFolder)
|
||||||
|
val labelText = descriptionLabel.text // Surprise - a StringBuilder
|
||||||
|
labelText.appendLine()
|
||||||
|
labelText.append("[${repo.name}] Downloaded!".tr())
|
||||||
|
postCrashHandlingRunnable { descriptionLabel.setText(labelText) }
|
||||||
}
|
}
|
||||||
postCrashHandlingRunnable {
|
postCrashHandlingRunnable {
|
||||||
RulesetCache.loadRulesets()
|
RulesetCache.loadRulesets()
|
||||||
missingModsToLoad = ""
|
missingModsToLoad = ""
|
||||||
loadMissingModsButton.isVisible = false
|
loadMissingModsButton.isVisible = false
|
||||||
errorLabel.setText("")
|
errorLabel.isVisible = false
|
||||||
|
rightSideTable.pack()
|
||||||
ToastPopup("Missing mods are downloaded successfully.", this@LoadGameScreen)
|
ToastPopup("Missing mods are downloaded successfully.", this@LoadGameScreen)
|
||||||
}
|
}
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
@ -182,74 +225,6 @@ class LoadGameScreen(previousScreen:BaseScreen) : PickerScreen(disableScroll = t
|
|||||||
loadMissingModsButton.isEnabled = true
|
loadMissingModsButton.isEnabled = true
|
||||||
descriptionLabel.setText("")
|
descriptionLabel.setText("")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun resetWindowState() {
|
|
||||||
updateLoadableGames(showAutosavesCheckbox.isChecked)
|
|
||||||
deleteSaveButton.disable()
|
|
||||||
copySavedGameToClipboardButton.disable()
|
|
||||||
rightSideButton.setText("Load game".tr())
|
|
||||||
rightSideButton.disable()
|
|
||||||
descriptionLabel.setText("")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateLoadableGames(showAutosaves: Boolean) {
|
|
||||||
saveTable.clear()
|
|
||||||
|
|
||||||
val loadImage = ImageGetter.getImage("OtherIcons/Load")
|
|
||||||
loadImage.setSize(50f, 50f) // So the origin sets correctly
|
|
||||||
loadImage.setOrigin(Align.center)
|
|
||||||
loadImage.addAction(Actions.rotateBy(360f, 2f))
|
|
||||||
saveTable.add(loadImage).size(50f)
|
|
||||||
|
|
||||||
// Apparently, even just getting the list of saves can cause ANRs -
|
|
||||||
// not sure how many saves these guys had but Google Play reports this to have happened hundreds of times
|
|
||||||
launchCrashHandling("GetSaves") {
|
|
||||||
// .toList() because otherwise the lastModified will only be checked inside the postRunnable
|
|
||||||
val saves = game.gameSaver.getSaves(autoSaves = showAutosaves).sortedByDescending { it.lastModified() }.toList()
|
|
||||||
|
|
||||||
postCrashHandlingRunnable {
|
|
||||||
saveTable.clear()
|
|
||||||
for (save in saves) {
|
|
||||||
val textButton = TextButton(save.name(), skin)
|
|
||||||
textButton.onClick { onSaveSelected(save) }
|
|
||||||
saveTable.add(textButton).pad(5f).row()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun onSaveSelected(save: FileHandle) {
|
|
||||||
selectedSave = save.name()
|
|
||||||
copySavedGameToClipboardButton.enable()
|
|
||||||
|
|
||||||
rightSideButton.setText("Load [${save.name()}]".tr())
|
|
||||||
rightSideButton.enable()
|
|
||||||
deleteSaveButton.enable()
|
|
||||||
deleteSaveButton.color = Color.RED
|
|
||||||
descriptionLabel.setText("Loading...".tr())
|
|
||||||
|
|
||||||
|
|
||||||
val savedAt = Date(save.lastModified())
|
|
||||||
var textToSet = save.name() + "\n${"Saved at".tr()}: " + savedAt.formatDate()
|
|
||||||
launchCrashHandling("LoadMetaData") { // Even loading the game to get its metadata can take a long time on older phones
|
|
||||||
try {
|
|
||||||
val game = game.gameSaver.loadGamePreviewFromFile(save)
|
|
||||||
val playerCivNames = game.civilizations.filter { it.isPlayerCivilization() }.joinToString { it.civName.tr() }
|
|
||||||
textToSet += "\n" + playerCivNames +
|
|
||||||
", " + game.difficulty.tr() + ", ${Fonts.turn}" + game.turns
|
|
||||||
textToSet += "\n${"Base ruleset:".tr()} " + game.gameParameters.baseRuleset
|
|
||||||
if (game.gameParameters.mods.isNotEmpty())
|
|
||||||
textToSet += "\n${"Mods:".tr()} " + game.gameParameters.mods.joinToString()
|
|
||||||
} catch (ex: Exception) {
|
|
||||||
textToSet += "\n${"Could not load game".tr()}!"
|
|
||||||
}
|
|
||||||
|
|
||||||
postCrashHandlingRunnable {
|
|
||||||
descriptionLabel.setText(textToSet)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
126
core/src/com/unciv/ui/saves/LoadOrSaveScreen.kt
Normal file
126
core/src/com/unciv/ui/saves/LoadOrSaveScreen.kt
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
package com.unciv.ui.saves
|
||||||
|
|
||||||
|
import com.badlogic.gdx.files.FileHandle
|
||||||
|
import com.badlogic.gdx.graphics.Color
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.CheckBox
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||||
|
import com.unciv.Constants
|
||||||
|
import com.unciv.models.translations.tr
|
||||||
|
import com.unciv.ui.crashhandling.launchCrashHandling
|
||||||
|
import com.unciv.ui.crashhandling.postCrashHandlingRunnable
|
||||||
|
import com.unciv.ui.pickerscreens.PickerScreen
|
||||||
|
import com.unciv.ui.utils.Fonts
|
||||||
|
import com.unciv.ui.utils.KeyCharAndCode
|
||||||
|
import com.unciv.ui.utils.UncivDateFormat.formatDate
|
||||||
|
import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip
|
||||||
|
import com.unciv.ui.utils.disable
|
||||||
|
import com.unciv.ui.utils.enable
|
||||||
|
import com.unciv.ui.utils.onChange
|
||||||
|
import com.unciv.ui.utils.onClick
|
||||||
|
import com.unciv.ui.utils.pad
|
||||||
|
import com.unciv.ui.utils.toLabel
|
||||||
|
import com.unciv.ui.utils.toTextButton
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
|
||||||
|
abstract class LoadOrSaveScreen(
|
||||||
|
fileListHeaderText: String? = null
|
||||||
|
) : PickerScreen(disableScroll = true) {
|
||||||
|
|
||||||
|
abstract fun onExistingSaveSelected(saveGameFile: FileHandle)
|
||||||
|
|
||||||
|
protected var selectedSave = ""
|
||||||
|
private set
|
||||||
|
|
||||||
|
private val savesScrollPane = VerticalFileListScrollPane(keyPressDispatcher)
|
||||||
|
protected val rightSideTable = Table()
|
||||||
|
protected val deleteSaveButton = "Delete save".toTextButton()
|
||||||
|
protected val showAutosavesCheckbox = CheckBox("Show autosaves".tr(), skin)
|
||||||
|
|
||||||
|
init {
|
||||||
|
savesScrollPane.onChange(::selectExistingSave)
|
||||||
|
|
||||||
|
rightSideTable.defaults().pad(5f, 10f)
|
||||||
|
|
||||||
|
showAutosavesCheckbox.isChecked = false
|
||||||
|
showAutosavesCheckbox.onChange {
|
||||||
|
updateShownSaves(showAutosavesCheckbox.isChecked)
|
||||||
|
}
|
||||||
|
val ctrlA = KeyCharAndCode.ctrl('a')
|
||||||
|
keyPressDispatcher[ctrlA] = { showAutosavesCheckbox.toggle() }
|
||||||
|
showAutosavesCheckbox.addTooltip(ctrlA)
|
||||||
|
|
||||||
|
deleteSaveButton.disable()
|
||||||
|
deleteSaveButton.onClick(::onDeleteClicked)
|
||||||
|
keyPressDispatcher[KeyCharAndCode.DEL] = ::onDeleteClicked
|
||||||
|
deleteSaveButton.addTooltip(KeyCharAndCode.DEL)
|
||||||
|
|
||||||
|
if (fileListHeaderText != null)
|
||||||
|
topTable.add(fileListHeaderText.toLabel()).pad(10f).row()
|
||||||
|
|
||||||
|
updateShownSaves(false)
|
||||||
|
|
||||||
|
topTable.add(savesScrollPane)
|
||||||
|
topTable.add(rightSideTable)
|
||||||
|
topTable.pack()
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun resetWindowState() {
|
||||||
|
updateShownSaves(showAutosavesCheckbox.isChecked)
|
||||||
|
deleteSaveButton.disable()
|
||||||
|
descriptionLabel.setText("")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onDeleteClicked() {
|
||||||
|
if (selectedSave.isEmpty()) return
|
||||||
|
val result = try {
|
||||||
|
if (game.gameSaver.deleteSave(selectedSave)) {
|
||||||
|
resetWindowState()
|
||||||
|
"[$selectedSave] deleted successfully."
|
||||||
|
} else "Failed to delete [$selectedSave]."
|
||||||
|
} catch (ex: SecurityException) {
|
||||||
|
"Insufficient permissions to delete [$selectedSave]."
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
"Failed to delete [$selectedSave]."
|
||||||
|
}
|
||||||
|
descriptionLabel.setText(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateShownSaves(showAutosaves: Boolean) {
|
||||||
|
savesScrollPane.updateSaveGames(game.gameSaver, showAutosaves)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun selectExistingSave(saveGameFile: FileHandle) {
|
||||||
|
deleteSaveButton.enable()
|
||||||
|
deleteSaveButton.color = Color.RED
|
||||||
|
|
||||||
|
selectedSave = saveGameFile.name()
|
||||||
|
showSaveInfo(saveGameFile)
|
||||||
|
onExistingSaveSelected(saveGameFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showSaveInfo(saveGameFile: FileHandle) {
|
||||||
|
descriptionLabel.setText(Constants.loading.tr())
|
||||||
|
launchCrashHandling("LoadMetaData") { // Even loading the game to get its metadata can take a long time on older phones
|
||||||
|
val textToSet = try {
|
||||||
|
val savedAt = Date(saveGameFile.lastModified())
|
||||||
|
val game = game.gameSaver.loadGamePreviewFromFile(saveGameFile)
|
||||||
|
val playerCivNames = game.civilizations
|
||||||
|
.filter { it.isPlayerCivilization() }.joinToString { it.civName.tr() }
|
||||||
|
val mods = if (game.gameParameters.mods.isEmpty()) ""
|
||||||
|
else "\n{Mods:} " + game.gameParameters.mods.joinToString()
|
||||||
|
|
||||||
|
// Format result for textToSet
|
||||||
|
"${saveGameFile.name()}\n{Saved at}: ${savedAt.formatDate()}\n" +
|
||||||
|
"$playerCivNames, ${game.difficulty.tr()}, ${Fonts.turn}${game.turns}\n" +
|
||||||
|
"{Base ruleset:} ${game.gameParameters.baseRuleset}$mods"
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
"\n{Could not load game}!"
|
||||||
|
}
|
||||||
|
|
||||||
|
postCrashHandlingRunnable {
|
||||||
|
descriptionLabel.setText(textToSet.tr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
97
core/src/com/unciv/ui/saves/QuickSave.kt
Normal file
97
core/src/com/unciv/ui/saves/QuickSave.kt
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package com.unciv.ui.saves
|
||||||
|
|
||||||
|
import com.unciv.Constants
|
||||||
|
import com.unciv.MainMenuScreen
|
||||||
|
import com.unciv.UncivGame
|
||||||
|
import com.unciv.logic.GameInfo
|
||||||
|
import com.unciv.ui.crashhandling.launchCrashHandling
|
||||||
|
import com.unciv.ui.crashhandling.postCrashHandlingRunnable
|
||||||
|
import com.unciv.ui.popup.Popup
|
||||||
|
import com.unciv.ui.popup.ToastPopup
|
||||||
|
import com.unciv.ui.worldscreen.WorldScreen
|
||||||
|
|
||||||
|
|
||||||
|
//todo reduce code duplication
|
||||||
|
|
||||||
|
object QuickSave {
|
||||||
|
fun save(gameInfo: GameInfo, screen: WorldScreen) {
|
||||||
|
val gameSaver = UncivGame.Current.gameSaver
|
||||||
|
val toast = ToastPopup("Quicksaving...", screen)
|
||||||
|
launchCrashHandling("QuickSaveGame", runAsDaemon = false) {
|
||||||
|
gameSaver.saveGame(gameInfo, "QuickSave") {
|
||||||
|
postCrashHandlingRunnable {
|
||||||
|
toast.close()
|
||||||
|
if (it != null)
|
||||||
|
ToastPopup("Could not save game!", screen)
|
||||||
|
else
|
||||||
|
ToastPopup("Quicksave successful.", screen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun load(screen: WorldScreen) {
|
||||||
|
val gameSaver = UncivGame.Current.gameSaver
|
||||||
|
val toast = ToastPopup("Quickloading...", screen)
|
||||||
|
launchCrashHandling("QuickLoadGame") {
|
||||||
|
try {
|
||||||
|
val loadedGame = gameSaver.loadGameByName("QuickSave")
|
||||||
|
postCrashHandlingRunnable {
|
||||||
|
toast.close()
|
||||||
|
UncivGame.Current.loadGame(loadedGame)
|
||||||
|
ToastPopup("Quickload successful.", screen)
|
||||||
|
}
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
postCrashHandlingRunnable {
|
||||||
|
toast.close()
|
||||||
|
ToastPopup("Could not load game!", screen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun autoLoadGame(screen: MainMenuScreen) {
|
||||||
|
val loadingPopup = Popup(screen)
|
||||||
|
loadingPopup.addGoodSizedLabel(Constants.loading)
|
||||||
|
loadingPopup.open()
|
||||||
|
launchCrashHandling("autoLoadGame") {
|
||||||
|
// Load game from file to class on separate thread to avoid ANR...
|
||||||
|
fun outOfMemory() {
|
||||||
|
postCrashHandlingRunnable {
|
||||||
|
loadingPopup.close()
|
||||||
|
ToastPopup("Not enough memory on phone to load game!", screen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val savedGame: GameInfo
|
||||||
|
try {
|
||||||
|
savedGame = screen.game.gameSaver.loadLatestAutosave()
|
||||||
|
} catch (oom: OutOfMemoryError) {
|
||||||
|
outOfMemory()
|
||||||
|
return@launchCrashHandling
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
postCrashHandlingRunnable {
|
||||||
|
loadingPopup.close()
|
||||||
|
ToastPopup("Cannot resume game!", screen)
|
||||||
|
}
|
||||||
|
return@launchCrashHandling
|
||||||
|
}
|
||||||
|
|
||||||
|
if (savedGame.gameParameters.isOnlineMultiplayer) {
|
||||||
|
try {
|
||||||
|
screen.game.onlineMultiplayer.loadGame(savedGame)
|
||||||
|
} catch (oom: OutOfMemoryError) {
|
||||||
|
outOfMemory()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
postCrashHandlingRunnable { /// ... and load it into the screen on main thread for GL context
|
||||||
|
try {
|
||||||
|
screen.game.loadGame(savedGame)
|
||||||
|
} catch (oom: OutOfMemoryError) {
|
||||||
|
outOfMemory()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,9 @@
|
|||||||
package com.unciv.ui.saves
|
package com.unciv.ui.saves
|
||||||
|
|
||||||
import com.badlogic.gdx.Gdx
|
import com.badlogic.gdx.Gdx
|
||||||
|
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.CheckBox
|
|
||||||
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.TextField
|
import com.badlogic.gdx.scenes.scene2d.ui.TextField
|
||||||
import com.unciv.UncivGame
|
import com.unciv.UncivGame
|
||||||
import com.unciv.logic.GameInfo
|
import com.unciv.logic.GameInfo
|
||||||
@ -12,90 +11,99 @@ import com.unciv.logic.GameSaver
|
|||||||
import com.unciv.models.translations.tr
|
import com.unciv.models.translations.tr
|
||||||
import com.unciv.ui.crashhandling.launchCrashHandling
|
import com.unciv.ui.crashhandling.launchCrashHandling
|
||||||
import com.unciv.ui.crashhandling.postCrashHandlingRunnable
|
import com.unciv.ui.crashhandling.postCrashHandlingRunnable
|
||||||
import com.unciv.ui.pickerscreens.PickerScreen
|
|
||||||
import com.unciv.ui.popup.ToastPopup
|
import com.unciv.ui.popup.ToastPopup
|
||||||
import com.unciv.ui.popup.YesNoPopup
|
import com.unciv.ui.popup.YesNoPopup
|
||||||
import com.unciv.ui.utils.*
|
import com.unciv.ui.utils.KeyCharAndCode
|
||||||
|
import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip
|
||||||
|
import com.unciv.ui.utils.disable
|
||||||
|
import com.unciv.ui.utils.enable
|
||||||
|
import com.unciv.ui.utils.onClick
|
||||||
|
import com.unciv.ui.utils.toLabel
|
||||||
|
import com.unciv.ui.utils.toTextButton
|
||||||
import java.util.concurrent.CancellationException
|
import java.util.concurrent.CancellationException
|
||||||
import kotlin.concurrent.thread
|
|
||||||
import com.unciv.ui.utils.AutoScrollPane as ScrollPane
|
|
||||||
|
|
||||||
|
|
||||||
class SaveGameScreen(val gameInfo: GameInfo) : PickerScreen(disableScroll = true) {
|
class SaveGameScreen(val gameInfo: GameInfo) : LoadOrSaveScreen("Current saves") {
|
||||||
private val gameNameTextField = TextField("", skin)
|
private val gameNameTextField = TextField("", skin)
|
||||||
val currentSaves = Table()
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setDefaultCloseAction()
|
setDefaultCloseAction()
|
||||||
|
|
||||||
gameNameTextField.textFieldFilter = TextField.TextFieldFilter { _, char -> char != '\\' && char != '/' }
|
rightSideTable.initRightSideTable()
|
||||||
topTable.add("Current saves".toLabel()).pad(10f).row()
|
|
||||||
updateShownSaves(false)
|
|
||||||
topTable.add(ScrollPane(currentSaves))
|
|
||||||
|
|
||||||
val newSave = Table()
|
|
||||||
newSave.defaults().pad(5f, 10f)
|
|
||||||
val defaultSaveName = "[${gameInfo.currentPlayer}] - [${gameInfo.turns}] turns".tr()
|
|
||||||
gameNameTextField.text = defaultSaveName
|
|
||||||
|
|
||||||
newSave.add("Saved game name".toLabel()).row()
|
|
||||||
newSave.add(gameNameTextField).width(300f).row()
|
|
||||||
|
|
||||||
val copyJsonButton = "Copy to clipboard".toTextButton()
|
|
||||||
copyJsonButton.onClick {
|
|
||||||
thread(name="Copy to clipboard") { // the Gzip rarely leads to ANRs
|
|
||||||
try {
|
|
||||||
Gdx.app.clipboard.contents = GameSaver.gameInfoToString(gameInfo, forceZip = true)
|
|
||||||
} catch (OOM: OutOfMemoryError) {
|
|
||||||
// you don't get a special toast, this isn't nearly common enough, this is a total edge-case
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newSave.add(copyJsonButton).row()
|
|
||||||
|
|
||||||
if (game.gameSaver.canLoadFromCustomSaveLocation()) {
|
|
||||||
val saveText = "Save to custom location".tr()
|
|
||||||
val saveToCustomLocation = TextButton(saveText, BaseScreen.skin)
|
|
||||||
val errorLabel = "".toLabel(Color.RED)
|
|
||||||
saveToCustomLocation.enable()
|
|
||||||
saveToCustomLocation.onClick {
|
|
||||||
errorLabel.setText("")
|
|
||||||
saveToCustomLocation.setText("Saving...".tr())
|
|
||||||
saveToCustomLocation.disable()
|
|
||||||
launchCrashHandling("SaveGame", runAsDaemon = false) {
|
|
||||||
game.gameSaver.saveGameToCustomLocation(gameInfo, gameNameTextField.text) { result ->
|
|
||||||
if (result.isError()) {
|
|
||||||
errorLabel.setText("Could not save game to custom location!".tr())
|
|
||||||
result.exception?.printStackTrace()
|
|
||||||
} else if (result.isSuccessful()) {
|
|
||||||
game.resetToWorldScreen()
|
|
||||||
}
|
|
||||||
saveToCustomLocation.enable()
|
|
||||||
saveToCustomLocation.setText(saveText)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newSave.add(saveToCustomLocation).row()
|
|
||||||
newSave.add(errorLabel).row()
|
|
||||||
}
|
|
||||||
|
|
||||||
val showAutosavesCheckbox = CheckBox("Show autosaves".tr(), skin)
|
|
||||||
showAutosavesCheckbox.isChecked = false
|
|
||||||
showAutosavesCheckbox.onChange {
|
|
||||||
updateShownSaves(showAutosavesCheckbox.isChecked)
|
|
||||||
}
|
|
||||||
newSave.add(showAutosavesCheckbox).row()
|
|
||||||
|
|
||||||
topTable.add(newSave)
|
|
||||||
topTable.pack()
|
|
||||||
|
|
||||||
rightSideButton.setText("Save game".tr())
|
rightSideButton.setText("Save game".tr())
|
||||||
rightSideButton.onClick {
|
val saveAction = {
|
||||||
if (game.gameSaver.getSave(gameNameTextField.text).exists())
|
if (game.gameSaver.getSave(gameNameTextField.text).exists())
|
||||||
YesNoPopup("Overwrite existing file?", { saveGame() }, this).open()
|
YesNoPopup("Overwrite existing file?", { saveGame() }, this).open()
|
||||||
else saveGame()
|
else saveGame()
|
||||||
}
|
}
|
||||||
|
rightSideButton.onClick(saveAction)
|
||||||
rightSideButton.enable()
|
rightSideButton.enable()
|
||||||
|
|
||||||
|
keyPressDispatcher[KeyCharAndCode.RETURN] = saveAction
|
||||||
|
stage.keyboardFocus = gameNameTextField
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Table.initRightSideTable() {
|
||||||
|
addGameNameField()
|
||||||
|
|
||||||
|
val copyJsonButton = "Copy to clipboard".toTextButton()
|
||||||
|
copyJsonButton.onClick(::copyToClipboardHandler)
|
||||||
|
val ctrlC = KeyCharAndCode.ctrl('c')
|
||||||
|
keyPressDispatcher[ctrlC] = ::copyToClipboardHandler
|
||||||
|
copyJsonButton.addTooltip(ctrlC)
|
||||||
|
add(copyJsonButton).row()
|
||||||
|
|
||||||
|
addSaveToCustomLocation()
|
||||||
|
add(deleteSaveButton).row()
|
||||||
|
add(showAutosavesCheckbox).row()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Table.addGameNameField() {
|
||||||
|
gameNameTextField.setTextFieldFilter { _, char -> char != '\\' && char != '/' }
|
||||||
|
val defaultSaveName = "[${gameInfo.currentPlayer}] - [${gameInfo.turns}] turns".tr()
|
||||||
|
gameNameTextField.text = defaultSaveName
|
||||||
|
gameNameTextField.setSelection(0, defaultSaveName.length)
|
||||||
|
|
||||||
|
add("Saved game name".toLabel()).row()
|
||||||
|
add(gameNameTextField).width(300f).row()
|
||||||
|
stage.keyboardFocus = gameNameTextField
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun copyToClipboardHandler() {
|
||||||
|
launchCrashHandling("Copy game to clipboard") {
|
||||||
|
// the Gzip rarely leads to ANRs
|
||||||
|
try {
|
||||||
|
Gdx.app.clipboard.contents = GameSaver.gameInfoToString(gameInfo, forceZip = true)
|
||||||
|
} catch (ex: Throwable) {
|
||||||
|
ex.printStackTrace()
|
||||||
|
ToastPopup("Could not save game to clipboard!", this@SaveGameScreen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Table.addSaveToCustomLocation() {
|
||||||
|
if (!game.gameSaver.canLoadFromCustomSaveLocation()) return
|
||||||
|
val saveToCustomLocation = "Save to custom location".toTextButton()
|
||||||
|
val errorLabel = "".toLabel(Color.RED)
|
||||||
|
saveToCustomLocation.onClick {
|
||||||
|
errorLabel.setText("")
|
||||||
|
saveToCustomLocation.setText("Saving...".tr())
|
||||||
|
saveToCustomLocation.disable()
|
||||||
|
launchCrashHandling("Save to custom location", runAsDaemon = false) {
|
||||||
|
game.gameSaver.saveGameToCustomLocation(gameInfo, gameNameTextField.text) { result ->
|
||||||
|
if (result.isError()) {
|
||||||
|
errorLabel.setText("Could not save game to custom location!".tr())
|
||||||
|
result.exception?.printStackTrace()
|
||||||
|
} else if (result.isSuccessful()) {
|
||||||
|
game.resetToWorldScreen()
|
||||||
|
}
|
||||||
|
saveToCustomLocation.enable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add(saveToCustomLocation).row()
|
||||||
|
add(errorLabel).row()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveGame() {
|
private fun saveGame() {
|
||||||
@ -110,17 +118,8 @@ class SaveGameScreen(val gameInfo: GameInfo) : PickerScreen(disableScroll = true
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateShownSaves(showAutosaves: Boolean) {
|
override fun onExistingSaveSelected(saveGameFile: FileHandle) {
|
||||||
currentSaves.clear()
|
gameNameTextField.text = saveGameFile.name()
|
||||||
val saves = game.gameSaver.getSaves(autoSaves = showAutosaves)
|
|
||||||
.sortedByDescending { it.lastModified() }
|
|
||||||
for (saveGameFile in saves) {
|
|
||||||
val textButton = saveGameFile.name().toTextButton()
|
|
||||||
textButton.onClick {
|
|
||||||
gameNameTextField.text = saveGameFile.name()
|
|
||||||
}
|
|
||||||
currentSaves.add(textButton).pad(5f).row()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
151
core/src/com/unciv/ui/saves/VerticalFileListScrollPane.kt
Normal file
151
core/src/com/unciv/ui/saves/VerticalFileListScrollPane.kt
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
package com.unciv.ui.saves
|
||||||
|
|
||||||
|
import com.badlogic.gdx.Input
|
||||||
|
import com.badlogic.gdx.files.FileHandle
|
||||||
|
import com.badlogic.gdx.graphics.Color
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.actions.Actions
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||||
|
import com.badlogic.gdx.scenes.scene2d.ui.TextButton
|
||||||
|
import com.badlogic.gdx.utils.Align
|
||||||
|
import com.unciv.logic.GameSaver
|
||||||
|
import com.unciv.ui.crashhandling.launchCrashHandling
|
||||||
|
import com.unciv.ui.crashhandling.postCrashHandlingRunnable
|
||||||
|
import com.unciv.ui.images.ImageGetter
|
||||||
|
import com.unciv.ui.utils.AutoScrollPane
|
||||||
|
import com.unciv.ui.utils.BaseScreen
|
||||||
|
import com.unciv.ui.utils.KeyPressDispatcher
|
||||||
|
import com.unciv.ui.utils.onClick
|
||||||
|
|
||||||
|
//todo key auto-repeat for navigation keys?
|
||||||
|
|
||||||
|
/** A widget holding TextButtons vertically in a Table contained in a ScrollPane, with methods to
|
||||||
|
* hold file names and FileHandle's in those buttons. Used to display existing saves in the Load and Save game dialogs.
|
||||||
|
*
|
||||||
|
* @param keyPressDispatcher optionally pass in a [BaseScreen]'s [keyPressDispatcher][BaseScreen.keyPressDispatcher] to allow keyboard navigation.
|
||||||
|
* @param existingSavesTable exists here for coder convenience. No need to touch.
|
||||||
|
*/
|
||||||
|
class VerticalFileListScrollPane(
|
||||||
|
keyPressDispatcher: KeyPressDispatcher?,
|
||||||
|
private val existingSavesTable: Table = Table()
|
||||||
|
) : AutoScrollPane(existingSavesTable) {
|
||||||
|
|
||||||
|
private var previousSelection: TextButton? = null
|
||||||
|
|
||||||
|
private var onChangeListener: ((FileHandle) -> Unit)? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
if (keyPressDispatcher != null) {
|
||||||
|
keyPressDispatcher[Input.Keys.UP] = { onArrowKey(-1) }
|
||||||
|
keyPressDispatcher[Input.Keys.DOWN] = { onArrowKey(1) }
|
||||||
|
keyPressDispatcher[Input.Keys.PAGE_UP] = { onPageKey(-1) }
|
||||||
|
keyPressDispatcher[Input.Keys.PAGE_DOWN] = { onPageKey(1) }
|
||||||
|
keyPressDispatcher[Input.Keys.HOME] = { onHomeEndKey(0) }
|
||||||
|
keyPressDispatcher[Input.Keys.END] = { onHomeEndKey(1) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onChange(action: (FileHandle) -> Unit) {
|
||||||
|
onChangeListener = action
|
||||||
|
}
|
||||||
|
|
||||||
|
/** repopulate with existing saved games */
|
||||||
|
fun updateSaveGames(gameSaver: GameSaver, showAutosaves: Boolean) {
|
||||||
|
update(gameSaver.getSaves(showAutosaves)
|
||||||
|
.sortedByDescending { it.lastModified() })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** repopulate from a FileHandle Sequence - for other sources than saved games */
|
||||||
|
fun update(files: Sequence<FileHandle>) {
|
||||||
|
existingSavesTable.clear()
|
||||||
|
previousSelection = null
|
||||||
|
val loadImage = ImageGetter.getImage("OtherIcons/Load")
|
||||||
|
loadImage.setSize(50f, 50f) // So the origin sets correctly
|
||||||
|
loadImage.setOrigin(Align.center)
|
||||||
|
val loadAnimation = Actions.repeat(Int.MAX_VALUE, Actions.rotateBy(360f, 2f))
|
||||||
|
loadImage.addAction(loadAnimation)
|
||||||
|
existingSavesTable.add(loadImage).size(50f).center()
|
||||||
|
|
||||||
|
// Apparently, even just getting the list of saves can cause ANRs -
|
||||||
|
// not sure how many saves these guys had but Google Play reports this to have happened hundreds of times
|
||||||
|
launchCrashHandling("GetSaves") {
|
||||||
|
// .toList() materializes the result of the sequence
|
||||||
|
val saves = files.toList()
|
||||||
|
|
||||||
|
postCrashHandlingRunnable {
|
||||||
|
loadAnimation.reset()
|
||||||
|
existingSavesTable.clear()
|
||||||
|
for (saveGameFile in saves) {
|
||||||
|
val textButton = TextButton(saveGameFile.name(), BaseScreen.skin)
|
||||||
|
textButton.userObject = saveGameFile
|
||||||
|
textButton.onClick {
|
||||||
|
selectExistingSave(textButton)
|
||||||
|
}
|
||||||
|
existingSavesTable.add(textButton).pad(5f).row()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun selectExistingSave(textButton: TextButton) {
|
||||||
|
previousSelection?.color = Color.WHITE
|
||||||
|
textButton.color = Color.GREEN
|
||||||
|
previousSelection = textButton
|
||||||
|
|
||||||
|
val saveGameFile = textButton.userObject as? FileHandle ?: return
|
||||||
|
onChangeListener?.invoke(saveGameFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
//region Keyboard scrolling
|
||||||
|
|
||||||
|
// Helpers to simplify Scroll positioning - ScrollPane.scrollY goes down, normal Gdx Y goes up
|
||||||
|
// These functions all operate in the scrollY 'coordinate system'
|
||||||
|
private fun Table.getVerticalSpan(button: TextButton): ClosedFloatingPointRange<Float> {
|
||||||
|
val invertedY = height - button.y
|
||||||
|
return (invertedY - button.height)..invertedY
|
||||||
|
}
|
||||||
|
private fun getVerticalSpan() = scrollY..(scrollY + height)
|
||||||
|
private fun Table.getButtonAt(y: Float) = cells[getRow(height - y)].actor as TextButton
|
||||||
|
|
||||||
|
private fun onArrowKey(direction: Int) {
|
||||||
|
if (existingSavesTable.rows == 0) return
|
||||||
|
val rowIndex = if (previousSelection == null)
|
||||||
|
if (direction == 1) -1 else 0
|
||||||
|
else existingSavesTable.getCell(previousSelection).row
|
||||||
|
val newRow = (rowIndex + direction).let {
|
||||||
|
if (it < 0) existingSavesTable.rows - 1
|
||||||
|
else if (it >= existingSavesTable.rows) 0
|
||||||
|
else it
|
||||||
|
}
|
||||||
|
val button = existingSavesTable.cells[newRow].actor as TextButton
|
||||||
|
selectExistingSave(button)
|
||||||
|
|
||||||
|
// Make ScrollPane follow the selection
|
||||||
|
val buttonSpan = existingSavesTable.getVerticalSpan(button)
|
||||||
|
val scrollSpan = getVerticalSpan()
|
||||||
|
if (buttonSpan.start < scrollSpan.start)
|
||||||
|
scrollY = buttonSpan.start
|
||||||
|
if (buttonSpan.endInclusive > scrollSpan.endInclusive)
|
||||||
|
scrollY = buttonSpan.endInclusive - height
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onPageKey(direction: Int) {
|
||||||
|
scrollY += (height - 60f) * direction // ScrollPane does the clamping to 0..maxY
|
||||||
|
val buttonHeight = previousSelection?.height ?: return
|
||||||
|
val buttonSpan = existingSavesTable.getVerticalSpan(previousSelection!!)
|
||||||
|
val scrollSpan = getVerticalSpan()
|
||||||
|
val newButtonY = if (buttonSpan.start < scrollSpan.start)
|
||||||
|
scrollSpan.start + buttonHeight
|
||||||
|
else if (buttonSpan.endInclusive > scrollSpan.endInclusive)
|
||||||
|
scrollSpan.endInclusive - buttonHeight
|
||||||
|
else return
|
||||||
|
selectExistingSave(existingSavesTable.getButtonAt(newButtonY))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onHomeEndKey(direction: Int) {
|
||||||
|
scrollY = direction * maxY
|
||||||
|
if (existingSavesTable.rows == 0) return
|
||||||
|
val row = (existingSavesTable.rows - 1) * direction
|
||||||
|
selectExistingSave(existingSavesTable.cells[row].actor as TextButton)
|
||||||
|
}
|
||||||
|
//endregion
|
||||||
|
}
|
@ -52,6 +52,7 @@ import com.unciv.ui.popup.ToastPopup
|
|||||||
import com.unciv.ui.popup.YesNoPopup
|
import com.unciv.ui.popup.YesNoPopup
|
||||||
import com.unciv.ui.popup.hasOpenPopups
|
import com.unciv.ui.popup.hasOpenPopups
|
||||||
import com.unciv.ui.saves.LoadGameScreen
|
import com.unciv.ui.saves.LoadGameScreen
|
||||||
|
import com.unciv.ui.saves.QuickSave
|
||||||
import com.unciv.ui.saves.SaveGameScreen
|
import com.unciv.ui.saves.SaveGameScreen
|
||||||
import com.unciv.ui.trade.DiplomacyScreen
|
import com.unciv.ui.trade.DiplomacyScreen
|
||||||
import com.unciv.ui.utils.BaseScreen
|
import com.unciv.ui.utils.BaseScreen
|
||||||
@ -242,43 +243,6 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun addKeyboardPresses() {
|
private fun addKeyboardPresses() {
|
||||||
// Note these helpers might need unification with similar code e.g. in:
|
|
||||||
// GameSaver.autoSave, SaveGameScreen.saveGame, LoadGameScreen.rightSideButton.onClick,...
|
|
||||||
val quickSave = {
|
|
||||||
val toast = ToastPopup("Quicksaving...", this)
|
|
||||||
launchCrashHandling("SaveGame", runAsDaemon = false) {
|
|
||||||
game.gameSaver.saveGame(gameInfo, "QuickSave") {
|
|
||||||
postCrashHandlingRunnable {
|
|
||||||
toast.close()
|
|
||||||
if (it != null)
|
|
||||||
ToastPopup("Could not save game!", this@WorldScreen)
|
|
||||||
else {
|
|
||||||
ToastPopup("Quicksave successful.", this@WorldScreen)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Unit // change type of anonymous fun from ()->Thread to ()->Unit without unchecked cast
|
|
||||||
}
|
|
||||||
val quickLoad = {
|
|
||||||
val toast = ToastPopup("Quickloading...", this)
|
|
||||||
launchCrashHandling("LoadGame") {
|
|
||||||
try {
|
|
||||||
val loadedGame = game.gameSaver.loadGameByName("QuickSave")
|
|
||||||
postCrashHandlingRunnable {
|
|
||||||
toast.close()
|
|
||||||
UncivGame.Current.loadGame(loadedGame)
|
|
||||||
ToastPopup("Quickload successful.", this@WorldScreen)
|
|
||||||
}
|
|
||||||
} catch (ex: Exception) {
|
|
||||||
postCrashHandlingRunnable {
|
|
||||||
ToastPopup("Could not load game!", this@WorldScreen)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Unit // change type to ()->Unit
|
|
||||||
}
|
|
||||||
|
|
||||||
// Space and N are assigned in createNextTurnButton
|
// Space and N are assigned in createNextTurnButton
|
||||||
keyPressDispatcher[Input.Keys.F1] = { game.setScreen(CivilopediaScreen(gameInfo.ruleSet, this)) }
|
keyPressDispatcher[Input.Keys.F1] = { game.setScreen(CivilopediaScreen(gameInfo.ruleSet, this)) }
|
||||||
keyPressDispatcher['E'] = { game.setScreen(EmpireOverviewScreen(selectedCiv)) } // Empire overview last used page
|
keyPressDispatcher['E'] = { game.setScreen(EmpireOverviewScreen(selectedCiv)) } // Empire overview last used page
|
||||||
@ -296,8 +260,8 @@ class WorldScreen(val gameInfo: GameInfo, val viewingCiv:CivilizationInfo) : Bas
|
|||||||
keyPressDispatcher[Input.Keys.F8] = { game.setScreen(VictoryScreen(this)) } // Victory Progress
|
keyPressDispatcher[Input.Keys.F8] = { game.setScreen(VictoryScreen(this)) } // Victory Progress
|
||||||
keyPressDispatcher[Input.Keys.F9] = { game.setScreen(EmpireOverviewScreen(selectedCiv, "Stats")) } // Demographics
|
keyPressDispatcher[Input.Keys.F9] = { game.setScreen(EmpireOverviewScreen(selectedCiv, "Stats")) } // Demographics
|
||||||
keyPressDispatcher[Input.Keys.F10] = { game.setScreen(EmpireOverviewScreen(selectedCiv, "Resources")) } // originally Strategic View
|
keyPressDispatcher[Input.Keys.F10] = { game.setScreen(EmpireOverviewScreen(selectedCiv, "Resources")) } // originally Strategic View
|
||||||
keyPressDispatcher[Input.Keys.F11] = quickSave // Quick Save
|
keyPressDispatcher[Input.Keys.F11] = { QuickSave.save(gameInfo, this) } // Quick Save
|
||||||
keyPressDispatcher[Input.Keys.F12] = quickLoad // Quick Load
|
keyPressDispatcher[Input.Keys.F12] = { QuickSave.load(this) } // Quick Load
|
||||||
keyPressDispatcher[Input.Keys.HOME] = { // Capital City View
|
keyPressDispatcher[Input.Keys.HOME] = { // Capital City View
|
||||||
val capital = gameInfo.currentPlayerCiv.getCapital()
|
val capital = gameInfo.currentPlayerCiv.getCapital()
|
||||||
if (capital != null && !mapHolder.setCenterPosition(capital.location))
|
if (capital != null && !mapHolder.setCenterPosition(capital.location))
|
||||||
|
@ -25,6 +25,10 @@ internal object DesktopLauncher {
|
|||||||
// Solves a rendering problem in specific GPUs and drivers.
|
// Solves a rendering problem in specific GPUs and drivers.
|
||||||
// For more info see https://github.com/yairm210/Unciv/pull/3202 and https://github.com/LWJGL/lwjgl/issues/119
|
// For more info see https://github.com/yairm210/Unciv/pull/3202 and https://github.com/LWJGL/lwjgl/issues/119
|
||||||
System.setProperty("org.lwjgl.opengl.Display.allowSoftwareOpenGL", "true")
|
System.setProperty("org.lwjgl.opengl.Display.allowSoftwareOpenGL", "true")
|
||||||
|
// This setting (default 64) limits clipboard transfers. Value in kB!
|
||||||
|
// 386 is an almost-arbitrary choice from the saves I had at the moment and their GZipped size.
|
||||||
|
// There must be a reason for lwjgl3 being so stingy, which for me meant to stay conservative.
|
||||||
|
System.setProperty("org.lwjgl.system.stackSize", "384")
|
||||||
|
|
||||||
ImagePacker.packImages()
|
ImagePacker.packImages()
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user