- Desktop: Allow specifying data directory (for "local" files, in GDX terms) separate from installed files directory (What GDX terms "internal").

- Added helper functions for common paths
- Does not include a way to specify said directory - pending tests
This commit is contained in:
yairm210 2024-07-27 23:13:27 +03:00
parent ab2ae35cf9
commit f03c30ed5c
28 changed files with 98 additions and 78 deletions

View File

@ -66,7 +66,7 @@ class AndroidFont : FontImplementation {
private fun createTypefaceCustom(path: String): Typeface {
return try {
Typeface.createFromFile(Gdx.files.local(path).file())
Typeface.createFromFile(UncivGame.Current.files.getLocalFile(path).file())
} catch (e: Exception) {
Log.error("Failed to create typeface, falling back to default", e)
// Falling back to default

View File

@ -60,6 +60,7 @@ import kotlin.reflect.KClass
open class UncivGame(val isConsoleMode: Boolean = false) : Game(), PlatformSpecific {
var deepLinkedMultiplayerGame: String? = null
override var customDataDirectory: String? = null
/** The game currently in progress */
var gameInfo: GameInfo? = null
@ -93,10 +94,10 @@ open class UncivGame(val isConsoleMode: Boolean = false) : Game(), PlatformSpeci
DebugUtils.VISIBLE_MAP = false
}
Current = this
files = UncivFiles(Gdx.files)
files = UncivFiles(Gdx.files, customDataDirectory)
Concurrency.run {
// Delete temporary files created when downloading mods
val tempFiles = Gdx.files.local("mods").list().filter { !it.isDirectory && it.name().startsWith("temp-") }
val tempFiles = files.getLocalFile("mods").list().filter { !it.isDirectory && it.name().startsWith("temp-") }
for (file in tempFiles) file.delete()
}

View File

@ -17,6 +17,7 @@ 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.Constants
import com.unciv.UncivGame
import com.unciv.models.UncivSound
import com.unciv.models.translations.tr
import com.unciv.ui.components.widgets.UncivTextField
@ -88,7 +89,7 @@ open class FileChooser(
// operational
private val maxHeight = stageToShowOn.height * 0.6f
private val absoluteLocalPath = Gdx.files.local("").file().absoluteFile.canonicalPath
private val absoluteLocalPath = UncivGame.Current.files.getDataFolder().file().absoluteFile.canonicalPath
private val absoluteExternalPath = if (Gdx.files.isExternalStorageAvailable)
Gdx.files.external("").file().absoluteFile.canonicalPath
else "/\\/\\/" // impossible placeholder
@ -204,7 +205,7 @@ open class FileChooser(
if (file.type() != Files.FileType.Absolute) return file
val path = file.path()
if (path.startsWith(absoluteLocalPath))
return Gdx.files.local(path.removePrefix(absoluteLocalPath).removePrefix(File.separator))
return UncivGame.Current.files.getLocalFile(path.removePrefix(absoluteLocalPath).removePrefix(File.separator))
if (path.startsWith(absoluteExternalPath))
return Gdx.files.external(path.removePrefix(absoluteExternalPath).removePrefix(File.separator))
return file

View File

@ -113,7 +113,7 @@ interface IMediaFinder {
//////////////////////////////////////////// Internal helpers
fun getModMediaFolder(modName: String): FileHandle =
Gdx.files.local("mods").child(modName).child(mediaSubFolderName)
UncivGame.Current.files.getModFolder(modName).child(mediaSubFolderName)
private fun FileHandle.directoryExists() = when {
type() != Files.FileType.Internal -> exists() && isDirectory

View File

@ -23,7 +23,7 @@ class LinuxX11SaverLoader : PlatformSaverLoader {
val startLocation =
if (suggestedLocation.startsWith(File.separator)) Gdx.files.absolute(suggestedLocation)
else if (Gdx.files.external(suggestedLocation).parent().exists()) Gdx.files.external(suggestedLocation)
else Gdx.files.local(suggestedLocation)
else UncivGame.Current.files.getLocalFile(suggestedLocation)
FileChooser.createSaveDialog(stage, "Save game", startLocation) {
success, file ->
if (!success)

View File

@ -1,7 +1,7 @@
package com.unciv.logic.files
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle
import com.unciv.UncivGame
import com.unciv.json.json
import com.unciv.logic.map.MapParameters
import com.unciv.logic.map.TileMap
@ -12,7 +12,7 @@ object MapSaver {
const val mapsFolder = "maps"
var saveZipped = true
private fun getMap(mapName: String) = Gdx.files.local("$mapsFolder/$mapName")
private fun getMap(mapName: String) = UncivGame.Current.files.getLocalFile("$mapsFolder/$mapName")
fun mapFromSavedString(mapString: String): TileMap {
val unzippedJson = try {
@ -36,7 +36,7 @@ object MapSaver {
return mapFromSavedString(mapFile.readString(Charsets.UTF_8.name()))
}
fun getMaps(): Array<FileHandle> = Gdx.files.local(mapsFolder).list()
fun getMaps(): Array<FileHandle> = UncivGame.Current.files.getLocalFile(mapsFolder).list()
private fun mapFromJson(json: String): TileMap = json().fromJson(TileMap::class.java, json)

View File

@ -40,7 +40,10 @@ class UncivFiles(
* This is necessary because the Android turn check background worker does not hold any reference to the actual [com.badlogic.gdx.Application],
* which is normally responsible for keeping the [Gdx] static variables from being garbage collected.
*/
private val files: Files
private val files: Files,
/** If not null, this is the path to the directory in which to store the local files - mods, saves, maps, etc */
val customDataDirectory: String?
) {
init {
debug("Creating UncivFiles, localStoragePath: %s, externalStoragePath: %s",
@ -51,6 +54,17 @@ class UncivFiles(
//region Helpers
fun getLocalFile(fileName: String): FileHandle {
return if (customDataDirectory == null) files.local(fileName)
else files.absolute(customDataDirectory + File.separator + fileName)
}
fun getModsFolder() = getLocalFile("mods")
fun getModFolder(modName: String) = getModsFolder().child(modName)
/** The folder that holds data that the game changes while running - all the mods, maps, save files, etc */
fun getDataFolder() = getLocalFile("")
fun getSave(gameName: String): FileHandle {
return getSave(SAVE_FILES_FOLDER, gameName)
}
@ -62,7 +76,7 @@ class UncivFiles(
debug("Getting save %s from folder %s, preferExternal: %s",
gameName, saveFolder, preferExternalStorage, files.externalStoragePath)
val location = "${saveFolder}/$gameName"
val localFile = files.local(location)
val localFile = getLocalFile(location)
val externalFile = files.external(location)
val toReturn = if (files.isExternalStorageAvailable && (
@ -88,7 +102,7 @@ class UncivFiles(
fun pathToFileHandler(path: String): FileHandle {
return if (preferExternalStorage && files.isExternalStorageAvailable) files.external(path)
else files.local(path)
else getLocalFile(path)
}
@ -106,9 +120,9 @@ class UncivFiles(
private fun getSaves(saveFolder: String): Sequence<FileHandle> {
debug("Getting saves from folder %s, externalStoragePath: %s", saveFolder, files.externalStoragePath)
val localFiles = files.local(saveFolder).list().asSequence()
val localFiles = getLocalFile(saveFolder).list().asSequence()
val externalFiles = if (files.isExternalStorageAvailable && files.local("").file().absolutePath != files.external("").file().absolutePath) {
val externalFiles = if (files.isExternalStorageAvailable && getDataFolder().file().absolutePath != files.external("").file().absolutePath) {
files.external(saveFolder).list().asSequence()
} else {
emptySequence()
@ -205,7 +219,7 @@ class UncivFiles(
onSaved: () -> Unit,
onError: (Exception) -> Unit
) {
val saveLocation = game.customSaveLocation ?: Gdx.files.local(gameName).path()
val saveLocation = game.customSaveLocation ?: UncivGame.Current.files.getLocalFile(gameName).path()
try {
val data = gameInfoToString(game)
@ -286,7 +300,7 @@ class UncivFiles(
private fun getGeneralSettingsFile(): FileHandle {
return if (UncivGame.Current.isConsoleMode) FileHandle(SETTINGS_FILE_NAME)
else files.local(SETTINGS_FILE_NAME)
else getLocalFile(SETTINGS_FILE_NAME)
}
fun getGeneralSettings(): GameSettings {

View File

@ -69,13 +69,13 @@ object Github {
/**
* Download a mod and extract, deleting any pre-existing version.
* @param folderFileHandle Destination handle of mods folder - also controls Android internal/external
* @param modsFolder Destination handle of mods folder - also controls Android internal/external
* @author **Warning**: This took a long time to get just right, so if you're changing this, ***TEST IT THOROUGHLY*** on _both_ Desktop _and_ Phone
* @return FileHandle for the downloaded Mod's folder or null if download failed
*/
fun downloadAndExtract(
repo: GithubAPI.Repo,
folderFileHandle: FileHandle
modsFolder: FileHandle
): FileHandle? {
var modNameFromFileName = repo.name
@ -102,7 +102,7 @@ object Github {
} ?: return null
// Download to temporary zip
val tempZipFileHandle = folderFileHandle.child("$tempName.zip")
val tempZipFileHandle = modsFolder.child("$tempName.zip")
tempZipFileHandle.write(inputStream, false)
// prepare temp unpacking folder
@ -122,7 +122,7 @@ object Github {
// modName can be "$repoName-$defaultBranch"
val finalDestinationName = modName.replace("-$defaultBranch", "").repoNameToFolderName()
// finalDestinationName is now the mod name as we display it. Folder name needs to be identical.
val finalDestination = folderFileHandle.child(finalDestinationName)
val finalDestination = modsFolder.child(finalDestinationName)
// prevent mixing new content with old
var tempBackup: FileHandle? = null

View File

@ -1,11 +1,11 @@
package com.unciv.models.metadata
import com.badlogic.gdx.Gdx
import com.unciv.UncivGame
import com.unciv.json.json
import com.unciv.logic.github.GithubAPI
import com.unciv.ui.components.widgets.TranslatedSelectBox
import com.unciv.logic.github.Github
import com.unciv.models.translations.tr
class ModCategories : ArrayList<ModCategories.Category>() {
@ -61,7 +61,7 @@ class ModCategories : ArrayList<ModCategories.Category>() {
val json = json()
val compact = json.toJson(this, ModCategories::class.java, Category::class.java)
val verbose = json.prettyPrint(compact)
Gdx.files.local(fileLocation).writeString(verbose, false, Charsets.UTF_8.name())
UncivGame.Current.files.getLocalFile(fileLocation).writeString(verbose, false, Charsets.UTF_8.name())
}
fun fromSelectBox(selectBox: TranslatedSelectBox): Category {

View File

@ -2,6 +2,7 @@ package com.unciv.models.ruleset
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle
import com.unciv.UncivGame
import com.unciv.logic.UncivShowableException
import com.unciv.logic.map.MapParameters
import com.unciv.models.metadata.BaseRuleset
@ -41,7 +42,7 @@ object RulesetCache : HashMap<String, Ruleset>() {
val errorLines = ArrayList<String>()
if (!noMods) {
val modsHandles = if (consoleMode) FileHandle("mods").list()
else Gdx.files.local("mods").list()
else UncivGame.Current.files.getModsFolder().list()
for (modFolder in modsHandles) {
if (modFolder.name().startsWith('.')) continue

View File

@ -3,6 +3,7 @@ package com.unciv.models.ruleset.validation
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.Color
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.json.fromJsonFile
import com.unciv.json.json
import com.unciv.logic.map.tile.RoadStatus
@ -151,7 +152,7 @@ class RulesetValidator(val ruleset: Ruleset) {
if (mapSelectUniques.size > 1)
lines.add("Specifying more than one map as preselection makes no sense", RulesetErrorSeverity.WarningOptionsOnly, sourceObject = null)
if (mapSelectUniques.isNotEmpty()) {
val mapsFolder = Gdx.files.local("mods").child(ruleset.name).child("maps")
val mapsFolder = UncivGame.Current.files.getModFolder(ruleset.name).child("maps")
if (mapsFolder.exists()) {
val maps = mapsFolder.list().map { it.name().lowercase() }
for (unique in mapSelectUniques) {

View File

@ -102,7 +102,7 @@ object TileSetCache : HashMap<String, TileSet>() {
* Available before initialization finishes.
*/
fun getAvailableTilesets(imageGetterTilesets: Sequence<String>): Set<String> {
val modTilesetConfigFiles = Gdx.files.local("mods").list().asSequence()
val modTilesetConfigFiles = UncivGame.Current.files.getModsFolder().list().asSequence()
.filter { it.isDirectory && !it.name().startsWith('.') }
.flatMap { it.child("jsons/TileSets").list().asSequence() }

View File

@ -1,7 +1,7 @@
package com.unciv.models.translations
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle
import com.unciv.UncivGame
import com.unciv.json.fromJsonFile
import com.unciv.json.json
import com.unciv.logic.civilization.diplomacy.DiplomaticModifiers
@ -72,7 +72,7 @@ object TranslationFileWriter {
// See #5168 for some background on this
for ((modName, modTranslations) in translations.modsWithTranslations) {
val modFolder = Gdx.files.local("mods").child(modName)
val modFolder = UncivGame.Current.files.getModFolder(modName)
val modPercentages = generateTranslationFiles(modTranslations, modFolder, translations)
writeLanguagePercentages(modPercentages, modFolder) // unused by the game but maybe helpful for the mod developer
}
@ -86,7 +86,7 @@ object TranslationFileWriter {
private fun getFileHandle(modFolder: FileHandle?, fileLocation: String) =
if (modFolder != null) modFolder.child(fileLocation)
else Gdx.files.local(fileLocation)
else UncivGame.Current.files.getLocalFile(fileLocation)
/**
* Writes new language files per Mod or for BaseRuleset - only each language that exists in [translations].
@ -108,7 +108,7 @@ object TranslationFileWriter {
linesToTranslate.addAll(templateFile.reader(TranslationFileReader.charset).readLines())
linesToTranslate += "\n\n#################### Lines from Unique Types #######################\n"
for (uniqueType in UniqueType.values()) {
for (uniqueType in UniqueType.entries) {
val deprecationAnnotation = uniqueType.getDeprecationAnnotation()
if (deprecationAnnotation != null) continue
if (uniqueType.flags.contains(UniqueFlag.HiddenToUsers)) continue
@ -116,37 +116,37 @@ object TranslationFileWriter {
linesToTranslate += "${uniqueType.getTranslatable()} = "
}
for (uniqueParameterType in UniqueParameterType.values()) {
for (uniqueParameterType in UniqueParameterType.entries) {
val strings = uniqueParameterType.getTranslationWriterStringsForOutput()
if (strings.isEmpty()) continue
linesToTranslate += "\n######### ${uniqueParameterType.displayName} ###########\n"
linesToTranslate.addAll(strings.map { "$it = " })
}
for (uniqueTarget in UniqueTarget.values())
for (uniqueTarget in UniqueTarget.entries)
linesToTranslate += "$uniqueTarget = "
linesToTranslate += "\n\n#################### Lines from spy actions #######################\n"
for (spyAction in SpyAction.values())
for (spyAction in SpyAction.entries)
linesToTranslate += "${spyAction.displayString} = "
linesToTranslate += "\n\n#################### Lines from diplomatic modifiers #######################\n"
for (diplomaticModifier in DiplomaticModifiers.values())
for (diplomaticModifier in DiplomaticModifiers.entries)
linesToTranslate += "${diplomaticModifier.text} = "
linesToTranslate += "\n\n#################### Lines from key bindings #######################\n"
for (bindingLabel in KeyboardBinding.getTranslationEntries())
linesToTranslate += "$bindingLabel = "
for (baseRuleset in BaseRuleset.values()) {
for (baseRuleset in BaseRuleset.entries) {
val generatedStringsFromBaseRuleset =
GenerateStringsFromJSONs(Gdx.files.local("jsons/${baseRuleset.fullName}"))
GenerateStringsFromJSONs(UncivGame.Current.files.getLocalFile("jsons/${baseRuleset.fullName}"))
for (entry in generatedStringsFromBaseRuleset)
fileNameToGeneratedStrings[entry.key + " from " + baseRuleset.fullName] = entry.value
}
// Tutorials reside one level above the base rulesets - if they were per-ruleset the following lines would be unnecessary
val tutorialStrings = GenerateStringsFromJSONs(Gdx.files.local("jsons")) { it.name == "Tutorials.json" }
val tutorialStrings = GenerateStringsFromJSONs(UncivGame.Current.files.getLocalFile("jsons")) { it.name == "Tutorials.json" }
fileNameToGeneratedStrings["Tutorials"] = tutorialStrings.values.first()
} else {
fileNameToGeneratedStrings.putAll(GenerateStringsFromJSONs(modFolder.child("jsons")))
@ -259,7 +259,7 @@ object TranslationFileWriter {
// used for unit test only
fun getGeneratedStringsSize(): Int {
return GenerateStringsFromJSONs(Gdx.files.local("jsons/Civ V - Vanilla")).values.sumOf {
return GenerateStringsFromJSONs(UncivGame.Current.files.getLocalFile("jsons/Civ V - Vanilla")).values.sumOf {
// exclude empty lines
it.count { line: String -> !line.startsWith(specialNewLineCode) }
}

View File

@ -53,7 +53,7 @@ class MusicController {
private fun getFile(path: String) =
if (musicLocation == FileType.External && Gdx.files.isExternalStorageAvailable)
Gdx.files.external(path)
else Gdx.files.local(path)
else UncivGame.Current.files.getLocalFile(path)
// These are replaced when we _know_ we're attached to Gdx.audio.update
private var needOwnTimer = true
@ -62,9 +62,9 @@ class MusicController {
}
init {
val oldFallbackFile = Gdx.files.local(musicFallbackLocation.removePrefix("/"))
val oldFallbackFile = UncivGame.Current.files.getLocalFile(musicFallbackLocation.removePrefix("/"))
if (oldFallbackFile.exists()) {
val newFallbackFile = Gdx.files.local(musicFallbackLocalName)
val newFallbackFile = UncivGame.Current.files.getLocalFile(musicFallbackLocalName)
if (!newFallbackFile.exists())
oldFallbackFile.moveTo(newFallbackFile)
}
@ -82,7 +82,7 @@ class MusicController {
else if (mod.isEmpty()) track else "$mod: $track"
companion object {
/** Parse a path - must be relative to `Gdx.files.local` */
/** Parse a path - must be relative to `UncivGame.Current.files.getLocalFile` */
fun parse(fileName: String): MusicTrackInfo {
if (fileName.isEmpty())
return MusicTrackInfo("", "", "")

View File

@ -138,9 +138,9 @@ object SoundPlayer {
private fun getFile(sound: UncivSound): FileHandle? {
val fileName = sound.fileName
for (modFolder in getFolders()) {
for (extension in SupportedExtensions.values()) {
for (extension in SupportedExtensions.entries) {
val path = "${modFolder}sounds$separator$fileName.${extension.name}"
val localFile = Gdx.files.local(path)
val localFile = UncivGame.Current.files.getLocalFile(path)
if (localFile.exists()) return localFile
val internalFile = Gdx.files.internal(path)
if (internalFile.exists()) return internalFile

View File

@ -76,7 +76,7 @@ object ImageGetter {
// These are from the mods
val visualMods = UncivGame.Current.settings.visualMods + ruleset.mods
for (mod in visualMods) {
loadModAtlases(mod, Gdx.files.local("mods/$mod"))
loadModAtlases(mod, UncivGame.Current.files.getModFolder(mod))
}
TileSetCache.assembleTileSetConfigs(ruleset.mods)
@ -185,7 +185,7 @@ object ImageGetter {
fun findExternalImage(name: String): FileHandle? {
val folders = try { // For CI mod checker, we can't access "local" files
// since Gdx files are not set up
ruleset.mods.asSequence().map { Gdx.files.local("mods/$it/ExtraImages") } +
ruleset.mods.asSequence().map { UncivGame.Current.files.getLocalFile("mods/$it/ExtraImages") } +
sequenceOf(Gdx.files.internal("ExtraImages"))
} catch (e: Exception) {
debug("Error loading mods: $e")

View File

@ -168,7 +168,7 @@ class AdvancedTab(
fonts.add(FontFamilyData.default)
// Add mods fonts
val modsDir = Gdx.files.local("mods/")
val modsDir = UncivGame.Current.files.getModsFolder()
for (mod in modsDir.list()) {
// Not a dir, continue

View File

@ -40,7 +40,7 @@ class TutorialController(screen: BaseScreen) {
val mods = UncivGame.Current.gameInfo?.ruleset?.mods
?: return@sequence
val files = mods.asSequence()
.map { Gdx.files.local("mods/$it/jsons/Tutorials.json") }
.map { UncivGame.Current.files.getLocalFile("mods/$it/jsons/Tutorials.json") }
yieldAll(files.filter { it.exists() })
}
}

View File

@ -1,8 +1,8 @@
package com.unciv.ui.screens.mapeditorscreen
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.json.json
import com.unciv.logic.UncivShowableException
import com.unciv.logic.files.FileChooser
@ -55,7 +55,7 @@ class MapEditorWesnothImporter(private val editorScreen: MapEditorScreen) : Disp
json().fromJson(
linkedMapOf<String,ArrayList<String>>()::class.java,
arrayListOf<String>()::class.java, // else we get Gdx.Array despite the class above stating ArrayList
Gdx.files.local("jsons/WesnothImportMappings.json")
UncivGame.Current.files.getLocalFile("jsons/WesnothImportMappings.json")
)
}

View File

@ -4,6 +4,7 @@ import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.Texture
import com.badlogic.gdx.scenes.scene2d.ui.Image
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.unciv.UncivGame
import com.unciv.logic.github.Github
import com.unciv.logic.github.GithubAPI
import com.unciv.models.metadata.BaseRuleset
@ -53,7 +54,7 @@ internal class ModInfoAndActionPane : Table() {
fun update(mod: Ruleset) {
val modName = mod.name
val modOptions = mod.modOptions // The ModOptions as enriched by us with GitHub metadata when originally downloaded
isBuiltin = modOptions.modUrl.isEmpty() && BaseRuleset.values().any { it.fullName == modName }
isBuiltin = modOptions.modUrl.isEmpty() && BaseRuleset.entries.any { it.fullName == modName }
enableVisualCheckBox = ModCompatibility.isAudioVisualMod(mod)
update(
modName, modOptions.modUrl, modOptions.defaultBranch,
@ -159,7 +160,7 @@ internal class ModInfoAndActionPane : Table() {
private fun addLocalPreviewImage(modName: String) {
// No concurrency, order of magnitude 20ms
val modFolder = Gdx.files.local("mods/$modName")
val modFolder = UncivGame.Current.files.getModFolder(modName)
val previewFile = modFolder.child("preview.jpg").takeIf { it.exists() }
?: modFolder.child("preview.png").takeIf { it.exists() }
?: return

View File

@ -481,7 +481,7 @@ class ModManagementScreen private constructor(
try {
val modFolder = Github.downloadAndExtract(
repo,
Gdx.files.local("mods")
UncivGame.Current.files.getModsFolder()
)
?: throw Exception("Exception during GitHub download") // downloadAndExtract returns null for 404 errors and the like -> display something!
Github.rewriteModOptions(repo, modFolder)

View File

@ -7,6 +7,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.TextButton
import com.badlogic.gdx.utils.SerializationException
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.logic.MissingModsException
import com.unciv.logic.UncivShowableException
import com.unciv.logic.files.PlatformSaverLoader
@ -176,7 +177,7 @@ class LoadGameScreen : LoadOrSaveScreen() {
errorLabel.isVisible = false
loadFromCustomLocationButton.setText(Constants.loading.tr())
loadFromCustomLocationButton.disable()
Concurrency.run(Companion.loadFromCustomLocation) {
Concurrency.run(loadFromCustomLocation) {
game.files.loadGameFromCustomLocation(
{
Concurrency.run { game.loadGame(it, callFromLoadScreen = true) }
@ -265,7 +266,7 @@ class LoadGameScreen : LoadOrSaveScreen() {
?: throw UncivShowableException("Could not find a mod named \"[$modName]\".")
val modFolder = Github.downloadAndExtract(
repo,
Gdx.files.local("mods")
UncivGame.Current.files.getModsFolder()
)
?: throw Exception("Unexpected 404 error") // downloadAndExtract returns null for 404 errors and the like -> display something!
Github.rewriteModOptions(repo, modFolder)

View File

@ -8,4 +8,6 @@ interface PlatformSpecific {
/** Install system audio hooks */
fun installAudioHooks() {}
/** If not null, this is the path to the directory in which to store the local files - mods, saves, maps, etc */
var customDataDirectory: String?
}

View File

@ -1,7 +1,7 @@
package com.unciv.app.desktop
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.Pixmap
import com.unciv.UncivGame
import com.unciv.ui.components.fonts.FontFamilyData
import com.unciv.ui.components.fonts.FontImplementation
import com.unciv.ui.components.fonts.FontMetricsCommon
@ -44,7 +44,7 @@ class DesktopFont : FontImplementation {
try
{
// Try to create and register new font
val fontFile = Gdx.files.local(path).file()
val fontFile = UncivGame.Current.files.getLocalFile(path).file()
val ge = GraphicsEnvironment.getLocalGraphicsEnvironment()
font = Font.createFont(Font.TRUETYPE_FONT, fontFile).deriveFont(size.toFloat())
ge.registerFont(font)

View File

@ -4,7 +4,7 @@ import com.badlogic.gdx.Gdx
import com.badlogic.gdx.backends.lwjgl3.Lwjgl3ApplicationConfiguration
import com.unciv.UncivGame
class DesktopGame(config: Lwjgl3ApplicationConfiguration) : UncivGame() {
class DesktopGame(config: Lwjgl3ApplicationConfiguration, override var customDataDirectory: String?) : UncivGame() {
private var discordUpdater = DiscordUpdater()
private val windowListener = UncivWindowListener()
@ -48,5 +48,4 @@ class DesktopGame(config: Lwjgl3ApplicationConfiguration) : UncivGame() {
discordUpdater.stopUpdates()
super.dispose()
}
}

View File

@ -92,7 +92,7 @@ internal object DesktopLauncher {
}
// HardenGdxAudio extends Lwjgl3Application, and the Lwjgl3Application constructor runs as long as the game runs
HardenGdxAudio(DesktopGame(config), config)
HardenGdxAudio(DesktopGame(config, null), config)
exitProcess(0)
}
}

View File

@ -1,6 +1,6 @@
package com.unciv.app.desktop
import com.badlogic.gdx.Gdx
import com.unciv.UncivGame
import com.unciv.logic.files.PlatformSaverLoader
import java.awt.Component
import java.awt.EventQueue
@ -57,7 +57,7 @@ class DesktopSaverLoader : PlatformSaverLoader {
try {
val fileChooser = JFileChooser().apply fileChooser@{
if (suggestedLocation == null) {
currentDirectory = Gdx.files.local("").file()
currentDirectory = UncivGame.Current.files.getDataFolder().file()
} else {
selectedFile = File(suggestedLocation)
}

View File

@ -1,7 +1,6 @@
// Taken from https://github.com/TomGrill/gdx-testing
package com.unciv.testing
import com.badlogic.gdx.Gdx
import com.unciv.Constants
import com.unciv.UncivGame
import com.unciv.models.metadata.BaseRuleset
@ -40,7 +39,7 @@ class BasicTests {
@Test
fun gamePngExists() {
Assert.assertTrue("This test will only pass when the game.png exists",
Gdx.files.local("").list().any { it.name().endsWith(".png") })
UncivGame.Current.files.getDataFolder().list().any { it.name().endsWith(".png") })
}
@Test
@ -91,7 +90,7 @@ class BasicTests {
@Test
fun baseRulesetHasNoBugs() {
for (baseRuleset in BaseRuleset.values()) {
for (baseRuleset in BaseRuleset.entries) {
val ruleset = RulesetCache[baseRuleset.fullName]!!
val modCheck = ruleset.checkModLinks()
if (modCheck.isNotOK())
@ -103,7 +102,7 @@ class BasicTests {
@Test
fun uniqueTypesHaveNoUnknownParameters() {
var noUnknownParameters = true
for (uniqueType in UniqueType.values()) {
for (uniqueType in UniqueType.entries) {
if (uniqueType.getDeprecationAnnotation()!=null) continue
for (entry in uniqueType.parameterTypeMap.withIndex()) {
for (paramType in entry.value) {
@ -121,7 +120,7 @@ class BasicTests {
@Test
fun allUniqueTypesHaveAtLeastOneTarget() {
var allOK = true
for (uniqueType in UniqueType.values()) {
for (uniqueType in UniqueType.entries) {
if (uniqueType.targetTypes.isEmpty()) {
debug("%s has no targets.", uniqueType.name)
allOK = false
@ -136,7 +135,7 @@ class BasicTests {
var allOK = true
for (unit in units) {
for (unique in unit.uniques) {
if (!UniqueType.values().any { it.placeholderText == unique.getPlaceholderText() }) {
if (!UniqueType.entries.any { it.placeholderText == unique.getPlaceholderText() }) {
debug("%s: %s", unit.name, unique)
allOK = false
}
@ -151,7 +150,7 @@ class BasicTests {
var allOK = true
for (building in buildings) {
for (unique in building.uniques) {
if (!UniqueType.values().any { it.placeholderText == unique.getPlaceholderText() }) {
if (!UniqueType.entries.any { it.placeholderText == unique.getPlaceholderText() }) {
debug("%s: %s", building.name, unique)
allOK = false
}
@ -166,7 +165,7 @@ class BasicTests {
var allOK = true
for (promotion in promotions) {
for (unique in promotion.uniques) {
if (!UniqueType.values().any { it.placeholderText == unique.getPlaceholderText() }) {
if (!UniqueType.entries.any { it.placeholderText == unique.getPlaceholderText() }) {
debug("%s: %s", promotion.name, unique)
allOK = false
}
@ -181,7 +180,7 @@ class BasicTests {
var allOK = true
for (policy in policies) {
for (unique in policy.uniques) {
if (!UniqueType.values().any { it.placeholderText == unique.getPlaceholderText() }) {
if (!UniqueType.entries.any { it.placeholderText == unique.getPlaceholderText() }) {
println("${policy.name}: $unique")
allOK = false
}
@ -197,7 +196,7 @@ class BasicTests {
var allOK = true
for (belief in beliefs) {
for (unique in belief.uniques) {
if (!UniqueType.values().any { it.placeholderText == unique.getPlaceholderText() }) {
if (!UniqueType.entries.any { it.placeholderText == unique.getPlaceholderText() }) {
println("${belief.name}: $unique")
allOK = false
}
@ -212,7 +211,7 @@ class BasicTests {
var allOK = true
for (era in eras) {
for (unique in era.uniques) {
if (!UniqueType.values().any { it.placeholderText == unique.getPlaceholderText() }) {
if (!UniqueType.entries.any { it.placeholderText == unique.getPlaceholderText() }) {
println("${era.name}: $unique")
allOK = false
}
@ -227,7 +226,7 @@ class BasicTests {
var allOK = true
for (reward in ruinRewards) {
for (unique in reward.uniques) {
if (!UniqueType.values().any { it.placeholderText == unique.getPlaceholderText() }) {
if (!UniqueType.entries.any { it.placeholderText == unique.getPlaceholderText() }) {
println("${reward.name}: $unique")
allOK = false
}
@ -239,7 +238,7 @@ class BasicTests {
@Test
fun allDeprecatedUniqueTypesHaveReplacewithThatMatchesOtherType() {
var allOK = true
for (uniqueType in UniqueType.values()) {
for (uniqueType in UniqueType.entries) {
val deprecationAnnotation = uniqueType.getDeprecationAnnotation() ?: continue
val uniquesToCheck = deprecationAnnotation.replaceWith.expression.split("\", \"", Constants.uniqueOrDelimiter)
@ -308,17 +307,17 @@ class BasicTests {
private fun statMathRunner(iterations: Int): Stats {
val random = Random(42)
val statCount = Stat.values().size
val statCount = Stat.entries.size
val stats = Stats()
repeat(iterations) {
val value: Float = random.nextDouble(-10.0, 10.0).toFloat()
stats.add( Stats(gold = value) )
stats.forEach {
val stat = Stat.values()[(it.key.ordinal + random.nextInt(1,statCount)).rem(statCount)]
val stat = Stat.entries[(it.key.ordinal + random.nextInt(1,statCount)).rem(statCount)]
stats.add(stat, -it.value)
}
val stat = Stat.values()[random.nextInt(statCount)]
val stat = Stat.entries[random.nextInt(statCount)]
stats.add(stat, stats.times(4)[stat])
stats.timesInPlace(0.8f)
if (abs(stats.values.maxOrNull()!!) > 1000000f)