mirror of
https://github.com/yairm210/Unciv.git
synced 2025-09-23 11:34:54 -04:00
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:
parent
1bbf5514cf
commit
eb5e8ae226
@ -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
|
||||||
|
@ -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 =
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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()
|
||||||
|
@ -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()
|
||||||
|
@ -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()
|
||||||
|
@ -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") {
|
||||||
|
@ -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) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user