Switchable gzipping of saved games (#6735)

* Switchable gzipping of saved games

* Switchable gzipping of saved games - consensus says default off
This commit is contained in:
SomeTroglodyte 2022-05-11 15:23:11 +02:00 committed by GitHub
parent 1bbf5514cf
commit eb5e8ae226
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 98 additions and 57 deletions

View File

@ -581,6 +581,8 @@ Days = Tage
Current saves = Gespeicherte Spiele Current saves = Gespeicherte Spiele
Show autosaves = Zeige automatisch gespeicherte Spiele an Show autosaves = Zeige automatisch gespeicherte Spiele an
Saved game name = Name des gespeicherten Spiels Saved game name = Name des gespeicherten Spiels
# This is the save game name the dialog will suggest
[player] - [turns] turns = [player] ([turns] Runden)
Copy to clipboard = In die Zwischenablage kopieren Copy to clipboard = In die Zwischenablage kopieren
Copy saved game to clipboard = Gespeichertes Spiel in die Zwischenablage kopieren Copy saved game to clipboard = Gespeichertes Spiel in die Zwischenablage kopieren
Could not load game = Spiel konnte nicht geladen werden Could not load game = Spiel konnte nicht geladen werden

View File

@ -584,6 +584,8 @@ Days =
Current saves = Current saves =
Show autosaves = Show autosaves =
Saved game name = Saved game name =
# This is the save game name the dialog will suggest
[player] - [turns] turns =
Copy to clipboard = Copy to clipboard =
Copy saved game to clipboard = Copy saved game to clipboard =
Could not load game = Could not load game =

View File

@ -6,7 +6,6 @@ import android.net.Uri
import android.os.Build import android.os.Build
import androidx.annotation.GuardedBy import androidx.annotation.GuardedBy
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import com.unciv.json.json
import com.unciv.logic.CustomSaveLocationHelper import com.unciv.logic.CustomSaveLocationHelper
import com.unciv.logic.GameInfo import com.unciv.logic.GameInfo
import com.unciv.logic.GameSaver import com.unciv.logic.GameSaver
@ -74,7 +73,7 @@ class CustomSaveLocationHelperAndroid(private val activity: Activity) : CustomSa
activity.contentResolver.openOutputStream(uri, "rwt") activity.contentResolver.openOutputStream(uri, "rwt")
?.writer() ?.writer()
?.use { ?.use {
it.write(json().toJson(gameInfo)) it.write(GameSaver.gameInfoToString(gameInfo))
} }
} }

View File

@ -27,7 +27,6 @@ import com.unciv.ui.utils.*
import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip import com.unciv.ui.utils.UncivTooltip.Companion.addTooltip
class MainMenuScreen: BaseScreen() { class MainMenuScreen: BaseScreen() {
private val autosave = "Autosave"
private val backgroundTable = Table().apply { background= ImageGetter.getBackground(Color.WHITE) } private val backgroundTable = Table().apply { background= ImageGetter.getBackground(Color.WHITE) }
private val singleColumn = isCrampedPortrait() private val singleColumn = isCrampedPortrait()
@ -90,7 +89,7 @@ class MainMenuScreen: BaseScreen() {
val column1 = Table().apply { defaults().pad(10f).fillX() } val column1 = Table().apply { defaults().pad(10f).fillX() }
val column2 = if(singleColumn) column1 else Table().apply { defaults().pad(10f).fillX() } val column2 = if(singleColumn) column1 else Table().apply { defaults().pad(10f).fillX() }
val autosaveGame = GameSaver.getSave(autosave, false) val autosaveGame = GameSaver.getSave(GameSaver.autoSaveFileName, false)
if (autosaveGame.exists()) { if (autosaveGame.exists()) {
val resumeTable = getMenuButton("Resume","OtherIcons/Resume", 'r') val resumeTable = getMenuButton("Resume","OtherIcons/Resume", 'r')
{ autoLoadGame() } { autoLoadGame() }
@ -163,7 +162,7 @@ class MainMenuScreen: BaseScreen() {
var savedGame: GameInfo var savedGame: GameInfo
try { try {
savedGame = GameSaver.loadGameByName(autosave) savedGame = GameSaver.loadGameByName(GameSaver.autoSaveFileName)
} catch (oom: OutOfMemoryError) { } catch (oom: OutOfMemoryError) {
outOfMemory() outOfMemory()
return@crashHandlingThread return@crashHandlingThread
@ -171,7 +170,7 @@ class MainMenuScreen: BaseScreen() {
// This can help for situations when the autosave is corrupted // This can help for situations when the autosave is corrupted
try { try {
val autosaves = GameSaver.getSaves() val autosaves = GameSaver.getSaves()
.filter { it.name() != autosave && it.name().startsWith(autosave) } .filter { it.name() != GameSaver.autoSaveFileName && it.name().startsWith(GameSaver.autoSaveFileName) }
savedGame = savedGame =
GameSaver.loadGameFromFile(autosaves.maxByOrNull { it.lastModified() }!!) GameSaver.loadGameFromFile(autosaves.maxByOrNull { it.lastModified() }!!)
} catch (oom: OutOfMemoryError) { // The autosave could have oom problems as well... smh } catch (oom: OutOfMemoryError) { // The autosave could have oom problems as well... smh

View File

@ -216,7 +216,7 @@ class UncivGame(parameters: UncivGameParameters) : Game() {
Thread.enumerate(threadList) Thread.enumerate(threadList)
if (isGameInfoInitialized()) { if (isGameInfoInitialized()) {
val autoSaveThread = threadList.firstOrNull { it.name == "Autosave" } val autoSaveThread = threadList.firstOrNull { it.name == GameSaver.autoSaveFileName }
if (autoSaveThread != null && autoSaveThread.isAlive) { if (autoSaveThread != null && autoSaveThread.isAlive) {
// auto save is already in progress (e.g. started by onPause() event) // auto save is already in progress (e.g. started by onPause() event)
// let's allow it to finish and do not try to autosave second time // let's allow it to finish and do not try to autosave second time

View File

@ -2,18 +2,24 @@ package com.unciv.logic
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.utils.Json
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.json.json import com.unciv.json.json
import com.unciv.logic.multiplayer.OnlineMultiplayer
import com.unciv.models.metadata.GameSettings import com.unciv.models.metadata.GameSettings
import com.unciv.ui.crashhandling.crashHandlingThread import com.unciv.ui.crashhandling.crashHandlingThread
import com.unciv.ui.crashhandling.postCrashHandlingRunnable import com.unciv.ui.crashhandling.postCrashHandlingRunnable
import com.unciv.ui.saves.Gzip
import java.io.File import java.io.File
object GameSaver { object GameSaver {
//region Data
private const val saveFilesFolder = "SaveFiles" private const val saveFilesFolder = "SaveFiles"
private const val multiplayerFilesFolder = "MultiplayerGames" private const val multiplayerFilesFolder = "MultiplayerGames"
const val autoSaveFileName = "Autosave"
const val settingsFileName = "GameSettings.json" const val settingsFileName = "GameSettings.json"
var saveZipped = false
@Volatile @Volatile
var customSaveLocationHelper: CustomSaveLocationHelper? = null var customSaveLocationHelper: CustomSaveLocationHelper? = null
@ -22,13 +28,16 @@ object GameSaver {
* See https://developer.android.com/training/data-storage/app-specific#external-access-files */ * See https://developer.android.com/training/data-storage/app-specific#external-access-files */
var externalFilesDirForAndroid = "" var externalFilesDirForAndroid = ""
fun getSubfolder(multiplayer: Boolean = false) = if (multiplayer) multiplayerFilesFolder else saveFilesFolder //endregion
//region Helpers
private fun getSubfolder(multiplayer: Boolean = false) = if (multiplayer) multiplayerFilesFolder else saveFilesFolder
fun getSave(GameName: String, multiplayer: Boolean = false): FileHandle { fun getSave(GameName: String, multiplayer: Boolean = false): FileHandle {
val localfile = Gdx.files.local("${getSubfolder(multiplayer)}/$GameName") val localFile = Gdx.files.local("${getSubfolder(multiplayer)}/$GameName")
if (externalFilesDirForAndroid == "" || !Gdx.files.isExternalStorageAvailable) return localfile if (externalFilesDirForAndroid == "" || !Gdx.files.isExternalStorageAvailable) return localFile
val externalFile = Gdx.files.absolute(externalFilesDirForAndroid + "/${getSubfolder(multiplayer)}/$GameName") val externalFile = Gdx.files.absolute(externalFilesDirForAndroid + "/${getSubfolder(multiplayer)}/$GameName")
if (localfile.exists() && !externalFile.exists()) return localfile if (localFile.exists() && !externalFile.exists()) return localFile
return externalFile return externalFile
} }
@ -38,15 +47,35 @@ object GameSaver {
return localSaves + Gdx.files.absolute(externalFilesDirForAndroid + "/${getSubfolder(multiplayer)}").list().asSequence() return localSaves + Gdx.files.absolute(externalFilesDirForAndroid + "/${getSubfolder(multiplayer)}").list().asSequence()
} }
fun canLoadFromCustomSaveLocation() = customSaveLocationHelper != null
fun deleteSave(GameName: String, multiplayer: Boolean = false) {
getSave(GameName, multiplayer).delete()
}
//endregion
//region Saving
fun saveGame(game: GameInfo, GameName: String, saveCompletionCallback: ((Exception?) -> Unit)? = null) { fun saveGame(game: GameInfo, GameName: String, saveCompletionCallback: ((Exception?) -> Unit)? = null) {
try { try {
json().toJson(game, getSave(GameName)) getSave(GameName).writeString(gameInfoToString(game), false)
saveCompletionCallback?.invoke(null) saveCompletionCallback?.invoke(null)
} catch (ex: Exception) { } catch (ex: Exception) {
saveCompletionCallback?.invoke(ex) saveCompletionCallback?.invoke(ex)
} }
} }
/** Returns gzipped serialization of [game], optionally gzipped ([forceZip] overrides [saveZipped]) */
fun gameInfoToString(game: GameInfo, forceZip: Boolean? = null): String {
val plainJson = json().toJson(game)
return if (forceZip ?: saveZipped) Gzip.zip(plainJson) else plainJson
}
/** Returns gzipped serialization of preview [game] - only called from [OnlineMultiplayer] */
fun gameInfoToString(game: GameInfoPreview): String {
return Gzip.zip(json().toJson(game))
}
/** /**
* Overload of function saveGame to save a GameInfoPreview in the MultiplayerGames folder * Overload of function saveGame to save a GameInfoPreview in the MultiplayerGames folder
*/ */
@ -63,13 +92,14 @@ object GameSaver {
customSaveLocationHelper!!.saveGame(game, GameName, forcePrompt = true, saveCompleteCallback = saveCompletionCallback) customSaveLocationHelper!!.saveGame(game, GameName, forcePrompt = true, saveCompleteCallback = saveCompletionCallback)
} }
//endregion
//region Loading
fun loadGameByName(GameName: String) = fun loadGameByName(GameName: String) =
loadGameFromFile(getSave(GameName)) loadGameFromFile(getSave(GameName))
fun loadGameFromFile(gameFile: FileHandle): GameInfo { fun loadGameFromFile(gameFile: FileHandle): GameInfo {
val game = json().fromJson(GameInfo::class.java, gameFile) return gameInfoFromString(gameFile.readString())
game.setTransients()
return game
} }
fun loadGamePreviewByName(GameName: String) = fun loadGamePreviewByName(GameName: String) =
@ -85,16 +115,15 @@ object GameSaver {
} }
} }
fun canLoadFromCustomSaveLocation() = customSaveLocationHelper != null
fun gameInfoFromString(gameData: String): GameInfo { fun gameInfoFromString(gameData: String): GameInfo {
val game = json().fromJson(GameInfo::class.java, gameData) return gameInfoFromStringWithoutTransients(gameData).apply {
game.setTransients() setTransients()
return game }
} }
/** Parses [gameData] as gzipped serialization of a [GameInfoPreview] - only called from [OnlineMultiplayer] */
fun gameInfoPreviewFromString(gameData: String): GameInfoPreview { fun gameInfoPreviewFromString(gameData: String): GameInfoPreview {
return json().fromJson(GameInfoPreview::class.java, gameData) return json().fromJson(GameInfoPreview::class.java, Gzip.unzip(gameData))
} }
/** /**
@ -102,15 +131,19 @@ object GameSaver {
* The returned GameInfo can not be used for most circumstances because its not initialized! * The returned GameInfo can not be used for most circumstances because its not initialized!
* It is therefore stateless and save to call for Multiplayer Turn Notifier, unlike gameInfoFromString(). * It is therefore stateless and save to call for Multiplayer Turn Notifier, unlike gameInfoFromString().
*/ */
fun gameInfoFromStringWithoutTransients(gameData: String): GameInfo { private fun gameInfoFromStringWithoutTransients(gameData: String): GameInfo {
return json().fromJson(GameInfo::class.java, gameData) val unzippedJson = try {
Gzip.unzip(gameData)
} catch (ex: Exception) {
gameData
}
return json().fromJson(GameInfo::class.java, unzippedJson)
} }
fun deleteSave(GameName: String, multiplayer: Boolean = false) { //endregion
getSave(GameName, multiplayer).delete() //region Settings
}
fun getGeneralSettingsFile(): FileHandle { private fun getGeneralSettingsFile(): FileHandle {
return if (UncivGame.Current.consoleMode) FileHandle(settingsFileName) return if (UncivGame.Current.consoleMode) FileHandle(settingsFileName)
else Gdx.files.local(settingsFileName) else Gdx.files.local(settingsFileName)
} }
@ -139,13 +172,20 @@ object GameSaver {
getGeneralSettingsFile().writeString(json().toJson(gameSettings), false) getGeneralSettingsFile().writeString(json().toJson(gameSettings), false)
} }
//endregion
//region Autosave
fun autoSave(gameInfo: GameInfo, postRunnable: () -> Unit = {}) { fun autoSave(gameInfo: GameInfo, postRunnable: () -> Unit = {}) {
// The save takes a long time (up to a few seconds on large games!) and we can do it while the player continues his game. // The save takes a long time (up to a few seconds on large games!) and we can do it while the player continues his game.
// On the other hand if we alter the game data while it's being serialized we could get a concurrent modification exception. // On the other hand if we alter the game data while it's being serialized we could get a concurrent modification exception.
// So what we do is we clone all the game data and serialize the clone. // So what we do is we clone all the game data and serialize the clone.
val gameInfoClone = gameInfo.clone() autoSaveUnCloned(gameInfo.clone(), postRunnable)
crashHandlingThread(name = "Autosave") { }
autoSaveSingleThreaded(gameInfoClone)
fun autoSaveUnCloned(gameInfo: GameInfo, postRunnable: () -> Unit = {}) {
// This is used when returning from WorldScreen to MainMenuScreen - no clone since UI access to it should be gone
crashHandlingThread(name = autoSaveFileName) {
autoSaveSingleThreaded(gameInfo)
// do this on main thread // do this on main thread
postCrashHandlingRunnable ( postRunnable ) postCrashHandlingRunnable ( postRunnable )
} }
@ -153,21 +193,21 @@ object GameSaver {
fun autoSaveSingleThreaded(gameInfo: GameInfo) { fun autoSaveSingleThreaded(gameInfo: GameInfo) {
try { try {
saveGame(gameInfo, "Autosave") saveGame(gameInfo, autoSaveFileName)
} catch (oom: OutOfMemoryError) { } catch (oom: OutOfMemoryError) {
return // not much we can do here return // not much we can do here
} }
// keep auto-saves for the last 10 turns for debugging purposes // keep auto-saves for the last 10 turns for debugging purposes
val newAutosaveFilename = val newAutosaveFilename =
saveFilesFolder + File.separator + "Autosave-${gameInfo.currentPlayer}-${gameInfo.turns}" saveFilesFolder + File.separator + autoSaveFileName + "-${gameInfo.currentPlayer}-${gameInfo.turns}"
getSave("Autosave").copyTo(Gdx.files.local(newAutosaveFilename)) getSave(autoSaveFileName).copyTo(Gdx.files.local(newAutosaveFilename))
fun getAutosaves(): Sequence<FileHandle> { fun getAutosaves(): Sequence<FileHandle> {
return getSaves().filter { it.name().startsWith("Autosave") } return getSaves().filter { it.name().startsWith(autoSaveFileName) }
} }
while (getAutosaves().count() > 10) { while (getAutosaves().count() > 10) {
val saveToDelete = getAutosaves().minByOrNull { it: FileHandle -> it.lastModified() }!! val saveToDelete = getAutosaves().minByOrNull { it.lastModified() }!!
deleteSave(saveToDelete.name()) deleteSave(saveToDelete.name())
} }
} }

View File

@ -3,11 +3,9 @@ package com.unciv.logic.multiplayer
import com.badlogic.gdx.Net import com.badlogic.gdx.Net
import com.unciv.Constants import com.unciv.Constants
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.json.json
import com.unciv.logic.GameInfo import com.unciv.logic.GameInfo
import com.unciv.logic.GameInfoPreview import com.unciv.logic.GameInfoPreview
import com.unciv.logic.GameSaver import com.unciv.logic.GameSaver
import com.unciv.ui.saves.Gzip
import java.util.* import java.util.*
interface IFileStorage { interface IFileStorage {
@ -87,7 +85,7 @@ class OnlineMultiplayer(var fileStorageIdentifier: String? = null) {
tryUploadGamePreview(gameInfo.asPreview()) tryUploadGamePreview(gameInfo.asPreview())
} }
val zippedGameInfo = Gzip.zip(json().toJson(gameInfo)) val zippedGameInfo = GameSaver.gameInfoToString(gameInfo, forceZip = true)
fileStorage.saveFileData(gameInfo.gameId, zippedGameInfo) fileStorage.saveFileData(gameInfo.gameId, zippedGameInfo)
} }
@ -98,17 +96,17 @@ class OnlineMultiplayer(var fileStorageIdentifier: String? = null) {
* @see GameInfo.asPreview * @see GameInfo.asPreview
*/ */
fun tryUploadGamePreview(gameInfo: GameInfoPreview) { fun tryUploadGamePreview(gameInfo: GameInfoPreview) {
val zippedGameInfo = Gzip.zip(json().toJson(gameInfo)) val zippedGameInfo = GameSaver.gameInfoToString(gameInfo)
fileStorage.saveFileData("${gameInfo.gameId}_Preview", zippedGameInfo) fileStorage.saveFileData("${gameInfo.gameId}_Preview", zippedGameInfo)
} }
fun tryDownloadGame(gameId: String): GameInfo { fun tryDownloadGame(gameId: String): GameInfo {
val zippedGameInfo = fileStorage.loadFileData(gameId) val zippedGameInfo = fileStorage.loadFileData(gameId)
return GameSaver.gameInfoFromString(Gzip.unzip(zippedGameInfo)) return GameSaver.gameInfoFromString(zippedGameInfo)
} }
fun tryDownloadGamePreview(gameId: String): GameInfoPreview { fun tryDownloadGamePreview(gameId: String): GameInfoPreview {
val zippedGameInfo = fileStorage.loadFileData("${gameId}_Preview") val zippedGameInfo = fileStorage.loadFileData("${gameId}_Preview")
return GameSaver.gameInfoPreviewFromString(Gzip.unzip(zippedGameInfo)) return GameSaver.gameInfoPreviewFromString(zippedGameInfo)
} }
} }

View File

@ -89,10 +89,7 @@ class LoadGameScreen(previousScreen:BaseScreen) : PickerScreen(disableScroll = t
loadFromClipboardButton.onClick { loadFromClipboardButton.onClick {
try { try {
val clipboardContentsString = Gdx.app.clipboard.contents.trim() val clipboardContentsString = Gdx.app.clipboard.contents.trim()
val decoded = val loadedGame = GameSaver.gameInfoFromString(clipboardContentsString)
if (clipboardContentsString.startsWith("{")) clipboardContentsString
else Gzip.unzip(clipboardContentsString)
val loadedGame = GameSaver.gameInfoFromString(decoded)
UncivGame.Current.loadGame(loadedGame) UncivGame.Current.loadGame(loadedGame)
} catch (ex: Exception) { } catch (ex: Exception) {
handleLoadGameException("Could not load game from clipboard!", ex) handleLoadGameException("Could not load game from clipboard!", ex)
@ -216,7 +213,7 @@ class LoadGameScreen(previousScreen:BaseScreen) : PickerScreen(disableScroll = t
postCrashHandlingRunnable { postCrashHandlingRunnable {
saveTable.clear() saveTable.clear()
for (save in saves) { for (save in saves) {
if (save.name().startsWith("Autosave") && !showAutosaves) continue if (save.name().startsWith(GameSaver.autoSaveFileName) && !showAutosaves) continue
val textButton = TextButton(save.name(), skin) val textButton = TextButton(save.name(), skin)
textButton.onClick { onSaveSelected(save) } textButton.onClick { onSaveSelected(save) }
saveTable.add(textButton).pad(5f).row() saveTable.add(textButton).pad(5f).row()

View File

@ -5,9 +5,7 @@ import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.ui.CheckBox 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.TextField import com.badlogic.gdx.scenes.scene2d.ui.TextField
import com.badlogic.gdx.utils.Json
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.json.json
import com.unciv.logic.GameInfo import com.unciv.logic.GameInfo
import com.unciv.logic.GameSaver import com.unciv.logic.GameSaver
import com.unciv.models.translations.tr import com.unciv.models.translations.tr
@ -36,7 +34,7 @@ class SaveGameScreen(val gameInfo: GameInfo) : PickerScreen(disableScroll = true
val newSave = Table() val newSave = Table()
newSave.defaults().pad(5f, 10f) newSave.defaults().pad(5f, 10f)
val defaultSaveName = gameInfo.currentPlayer + " - " + gameInfo.turns + " turns" val defaultSaveName = "[${gameInfo.currentPlayer}] - [${gameInfo.turns}] turns".tr()
gameNameTextField.text = defaultSaveName gameNameTextField.text = defaultSaveName
newSave.add("Saved game name".toLabel()).row() newSave.add("Saved game name".toLabel()).row()
@ -46,15 +44,14 @@ class SaveGameScreen(val gameInfo: GameInfo) : PickerScreen(disableScroll = true
copyJsonButton.onClick { copyJsonButton.onClick {
thread(name="Copy to clipboard") { // the Gzip rarely leads to ANRs thread(name="Copy to clipboard") { // the Gzip rarely leads to ANRs
try { try {
val json = json().toJson(gameInfo) Gdx.app.clipboard.contents = GameSaver.gameInfoToString(gameInfo, forceZip = true)
val base64Gzip = Gzip.zip(json)
Gdx.app.clipboard.contents = base64Gzip
} catch (OOM: OutOfMemoryError) { } catch (OOM: OutOfMemoryError) {
// you don't get a special toast, this isn't nearly common enough, this is a total edge-case // you don't get a special toast, this isn't nearly common enough, this is a total edge-case
} }
} }
} }
newSave.add(copyJsonButton).row() newSave.add(copyJsonButton).row()
if (GameSaver.canLoadFromCustomSaveLocation()) { if (GameSaver.canLoadFromCustomSaveLocation()) {
val saveToCustomLocation = "Save to custom location".toTextButton() val saveToCustomLocation = "Save to custom location".toTextButton()
val errorLabel = "".toLabel(Color.RED) val errorLabel = "".toLabel(Color.RED)
@ -79,7 +76,6 @@ class SaveGameScreen(val gameInfo: GameInfo) : PickerScreen(disableScroll = true
newSave.add(errorLabel).row() newSave.add(errorLabel).row()
} }
val showAutosavesCheckbox = CheckBox("Show autosaves".tr(), skin) val showAutosavesCheckbox = CheckBox("Show autosaves".tr(), skin)
showAutosavesCheckbox.isChecked = false showAutosavesCheckbox.isChecked = false
showAutosavesCheckbox.onChange { showAutosavesCheckbox.onChange {
@ -116,7 +112,7 @@ class SaveGameScreen(val gameInfo: GameInfo) : PickerScreen(disableScroll = true
val saves = GameSaver.getSaves() val saves = GameSaver.getSaves()
.sortedByDescending { it.lastModified() } .sortedByDescending { it.lastModified() }
for (saveGameFile in saves) { for (saveGameFile in saves) {
if (saveGameFile.name().startsWith("Autosave") && !showAutosaves) continue if (saveGameFile.name().startsWith(GameSaver.autoSaveFileName) && !showAutosaves) continue
val textButton = saveGameFile.name().toTextButton() val textButton = saveGameFile.name().toTextButton()
textButton.onClick { textButton.onClick {
gameNameTextField.text = saveGameFile.name() gameNameTextField.text = saveGameFile.name()

View File

@ -10,6 +10,7 @@ import com.badlogic.gdx.utils.Align
import com.unciv.Constants import com.unciv.Constants
import com.unciv.MainMenuScreen import com.unciv.MainMenuScreen
import com.unciv.UncivGame import com.unciv.UncivGame
import com.unciv.logic.GameSaver
import com.unciv.logic.MapSaver import com.unciv.logic.MapSaver
import com.unciv.logic.civilization.PlayerType import com.unciv.logic.civilization.PlayerType
import com.unciv.logic.multiplayer.SimpleHttp import com.unciv.logic.multiplayer.SimpleHttp
@ -553,8 +554,8 @@ class OptionsPopup(
private fun getDebugTab() = Table(BaseScreen.skin).apply { private fun getDebugTab() = Table(BaseScreen.skin).apply {
pad(10f) pad(10f)
defaults().pad(5f) defaults().pad(5f)
val game = UncivGame.Current val game = UncivGame.Current
val simulateButton = "Simulate until turn:".toTextButton() val simulateButton = "Simulate until turn:".toTextButton()
val simulateTextField = TextField(game.simulateUntilTurnForDebug.toString(), BaseScreen.skin) val simulateTextField = TextField(game.simulateUntilTurnForDebug.toString(), BaseScreen.skin)
val invalidInputLabel = "This is not a valid integer!".toLabel().also { it.isVisible = false } val invalidInputLabel = "This is not a valid integer!".toLabel().also { it.isVisible = false }
@ -571,6 +572,7 @@ class OptionsPopup(
add(simulateButton) add(simulateButton)
add(simulateTextField).row() add(simulateTextField).row()
add(invalidInputLabel).colspan(2).row() add(invalidInputLabel).colspan(2).row()
add("Supercharged".toCheckBox(game.superchargedForDebug) { add("Supercharged".toCheckBox(game.superchargedForDebug) {
game.superchargedForDebug = it game.superchargedForDebug = it
}).colspan(2).row() }).colspan(2).row()
@ -582,9 +584,13 @@ class OptionsPopup(
game.gameInfo.gameParameters.godMode = it game.gameInfo.gameParameters.godMode = it
}).colspan(2).row() }).colspan(2).row()
} }
add("Save games compressed".toCheckBox(GameSaver.saveZipped) {
GameSaver.saveZipped = it
}).colspan(2).row()
add("Save maps compressed".toCheckBox(MapSaver.saveZipped) { add("Save maps compressed".toCheckBox(MapSaver.saveZipped) {
MapSaver.saveZipped = it MapSaver.saveZipped = it
}).colspan(2).row() }).colspan(2).row()
add("Gdx Scene2D debug".toCheckBox(BaseScreen.enableSceneDebug) { add("Gdx Scene2D debug".toCheckBox(BaseScreen.enableSceneDebug) {
BaseScreen.enableSceneDebug = it BaseScreen.enableSceneDebug = it
}).colspan(2).row() }).colspan(2).row()

View File

@ -2,6 +2,7 @@ package com.unciv.ui.worldscreen.mainmenu
import com.badlogic.gdx.Gdx import com.badlogic.gdx.Gdx
import com.unciv.MainMenuScreen import com.unciv.MainMenuScreen
import com.unciv.logic.GameSaver
import com.unciv.ui.civilopedia.CivilopediaScreen import com.unciv.ui.civilopedia.CivilopediaScreen
import com.unciv.models.metadata.GameSetupInfo import com.unciv.models.metadata.GameSetupInfo
import com.unciv.ui.newgamescreen.NewGameScreen import com.unciv.ui.newgamescreen.NewGameScreen
@ -16,6 +17,7 @@ class WorldScreenMenuPopup(val worldScreen: WorldScreen) : Popup(worldScreen) {
defaults().fillX() defaults().fillX()
addButton("Main menu") { addButton("Main menu") {
GameSaver.autoSaveUnCloned(worldScreen.gameInfo)
worldScreen.game.setScreen(MainMenuScreen()) worldScreen.game.setScreen(MainMenuScreen())
} }
addButton("Civilopedia") { addButton("Civilopedia") {

View File

@ -19,7 +19,7 @@ class CustomSaveLocationHelperDesktop : CustomSaveLocationHelper {
File(customSaveLocation).outputStream() File(customSaveLocation).outputStream()
.writer() .writer()
.use { writer -> .use { writer ->
writer.write(json().toJson(gameInfo)) writer.write(GameSaver.gameInfoToString(gameInfo))
} }
saveCompleteCallback?.invoke(null) saveCompleteCallback?.invoke(null)
} catch (e: Exception) { } catch (e: Exception) {