diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt index fb8439d03f..21b08acffb 100644 --- a/core/src/com/unciv/UncivGame.kt +++ b/core/src/com/unciv/UncivGame.kt @@ -189,7 +189,9 @@ open class UncivGame(val isConsoleMode: Boolean = false) : Game(), PlatformSpeci ImageGetter.resetAtlases() ImageGetter.setNewRuleset(ImageGetter.ruleset) // This needs to come after the settings, since we may have default visual mods - if (settings.tileSet !in ImageGetter.getAvailableTilesets()) { // If one of the tilesets is no longer available, default back + val availableTileSets = ImageGetter.getAvailableTilesets().toSet() + .intersect(TileSetCache.getAvailableTilesets().toSet()) + if (settings.tileSet !in availableTileSets) { // If the configured tileset is no longer available, default back settings.tileSet = Constants.defaultTileset } diff --git a/core/src/com/unciv/models/tilesets/TileSetCache.kt b/core/src/com/unciv/models/tilesets/TileSetCache.kt index 5c15c2f551..f5def6a385 100644 --- a/core/src/com/unciv/models/tilesets/TileSetCache.kt +++ b/core/src/com/unciv/models/tilesets/TileSetCache.kt @@ -80,4 +80,20 @@ object TileSetCache : HashMap() { set(name, tileset) } } + + /** Determines potentially available TileSets - by scanning for TileSet jsons. + * + * Available before initialization finishes. + * To get more reliable info, either wait until `this` is fully initialized, + * or intersect with [ImageGetter.getAvailableTilesets] + */ + fun getAvailableTilesets() = sequence { + yieldAll(FileHandle("jsons/TileSets").list().asIterable()) + for (modFolder in FileHandle("mods").list()) { + if (!modFolder.isDirectory || modFolder.name().startsWith('.')) + continue + yieldAll(modFolder.child("jsons/TileSets").list().asIterable()) + } + }.filter { it.exists() } + .map { it.nameWithoutExtension().removeSuffix("Config") } } diff --git a/core/src/com/unciv/ui/images/ImageGetter.kt b/core/src/com/unciv/ui/images/ImageGetter.kt index 320cf7181a..4977bae14b 100644 --- a/core/src/com/unciv/ui/images/ImageGetter.kt +++ b/core/src/com/unciv/ui/images/ImageGetter.kt @@ -448,8 +448,16 @@ object ImageGetter { fun getAvailableSkins() = ninePatchDrawables.keys.asSequence().map { it.split("/")[1] }.distinct() - fun getAvailableTilesets() = textureRegionDrawables.keys.asSequence().filter { it.startsWith("TileSets") && !it.contains("/Units/") } - .map { it.split("/")[1] }.distinct() + /** Determines available TileSets from the currently loaded Texture paths. + * + * Note [TileSetCache] will not necessarily load all of them, e.g. if a Mod fails + * to provide a config json for a graphic with a Tileset path. + * + * Intersect with [TileSetCache.getAvailableTilesets] for a more reliable answer + */ + fun getAvailableTilesets() = textureRegionDrawables.keys.asSequence() + .filter { it.startsWith("TileSets") && !it.contains("/Units/") } + .map { it.split("/")[1] }.distinct() fun getAvailableUnitsets() = textureRegionDrawables.keys.asSequence().filter { it.contains("/Units/") } .map { it.split("/")[1] }.distinct() diff --git a/core/src/com/unciv/ui/screens/mainmenuscreen/MainMenuScreen.kt b/core/src/com/unciv/ui/screens/mainmenuscreen/MainMenuScreen.kt index 3143c7e74a..4dcf89bf53 100644 --- a/core/src/com/unciv/ui/screens/mainmenuscreen/MainMenuScreen.kt +++ b/core/src/com/unciv/ui/screens/mainmenuscreen/MainMenuScreen.kt @@ -21,6 +21,7 @@ import com.unciv.models.metadata.BaseRuleset import com.unciv.models.metadata.GameSetupInfo import com.unciv.models.ruleset.Ruleset import com.unciv.models.ruleset.RulesetCache +import com.unciv.models.tilesets.TileSetCache import com.unciv.ui.components.AutoScrollPane import com.unciv.ui.components.KeyCharAndCode import com.unciv.ui.components.UncivTooltip.Companion.addTooltip @@ -51,6 +52,7 @@ import com.unciv.ui.screens.worldscreen.WorldScreen import com.unciv.ui.screens.worldscreen.mainmenu.WorldScreenMenuPopup import com.unciv.utils.concurrency.Concurrency import com.unciv.utils.concurrency.launchOnGLThread +import kotlinx.coroutines.CoroutineScope import kotlin.math.min @@ -104,51 +106,14 @@ class MainMenuScreen: BaseScreen(), RecreateOnResize { // will not exist unless we reset the ruleset and images ImageGetter.ruleset = RulesetCache.getVanillaRuleset() - Concurrency.run("ShowMapBackground") { - var scale = 1f - var mapWidth = stage.width / TileGroupMap.groupHorizontalAdvance - var mapHeight = stage.height / TileGroupMap.groupSize - if (mapWidth * mapHeight > 3000f) { // 3000 as max estimated number of tiles is arbitrary (we had typically 721 before) - scale = mapWidth * mapHeight / 3000f - mapWidth /= scale - mapHeight /= scale - scale = min(scale, 20f) + // This is an extreme safeguard - should an invalid settings.tileSet ever make it past the + // guard in UncivGame.create, simply omit the background so the user can at least get to options + // (let him crash when loading a game but avoid locking him out entirely) + if (game.settings.tileSet in TileSetCache) + Concurrency.run("ShowMapBackground") { + showMapBackground() } - val baseRuleset = RulesetCache.getVanillaRuleset() - easterEggRuleset = EasterEggRulesets.getTodayEasterEggRuleset()?.let { - RulesetCache.getComplexRuleset(baseRuleset, listOf(it)) - } - val mapRuleset = if (game.settings.enableEasterEggs) easterEggRuleset ?: baseRuleset else baseRuleset - - val newMap = MapGenerator(mapRuleset) - .generateMap(MapParameters().apply { - shape = MapShape.rectangular - mapSize = MapSizeNew(MapSize.Small) - type = MapType.pangaea - temperatureExtremeness = .7f - waterThreshold = -0.1f // mainly land, gets about 30% water - modifyForEasterEgg() - }) - - launchOnGLThread { // for GL context - ImageGetter.setNewRuleset(mapRuleset) - val mapHolder = EditorMapHolder( - this@MainMenuScreen, - newMap - ) {} - mapHolder.setScale(scale) - backgroundTable.addAction(Actions.sequence( - Actions.fadeOut(0f), - Actions.run { - backgroundTable.addActor(mapHolder) - mapHolder.center(backgroundTable) - }, - Actions.fadeIn(0.3f) - )) - } - } - val column1 = Table().apply { defaults().pad(10f).fillX() } val column2 = if (singleColumn) column1 else Table().apply { defaults().pad(10f).fillX() } @@ -220,6 +185,50 @@ class MainMenuScreen: BaseScreen(), RecreateOnResize { stage.addActor(helpButton) } + private fun CoroutineScope.showMapBackground() { + var scale = 1f + var mapWidth = stage.width / TileGroupMap.groupHorizontalAdvance + var mapHeight = stage.height / TileGroupMap.groupSize + if (mapWidth * mapHeight > 3000f) { // 3000 as max estimated number of tiles is arbitrary (we had typically 721 before) + scale = mapWidth * mapHeight / 3000f + mapWidth /= scale + mapHeight /= scale + scale = min(scale, 20f) + } + + val baseRuleset = RulesetCache.getVanillaRuleset() + easterEggRuleset = EasterEggRulesets.getTodayEasterEggRuleset()?.let { + RulesetCache.getComplexRuleset(baseRuleset, listOf(it)) + } + val mapRuleset = if (game.settings.enableEasterEggs) easterEggRuleset ?: baseRuleset else baseRuleset + + val newMap = MapGenerator(mapRuleset) + .generateMap(MapParameters().apply { + shape = MapShape.rectangular + mapSize = MapSizeNew(MapSize.Small) + type = MapType.pangaea + temperatureExtremeness = 0.7f + waterThreshold = -0.1f // mainly land, gets about 30% water + modifyForEasterEgg() + }) + + launchOnGLThread { // for GL context + ImageGetter.setNewRuleset(mapRuleset) + val mapHolder = EditorMapHolder( + this@MainMenuScreen, + newMap + ) {} + mapHolder.setScale(scale) + backgroundTable.addAction(Actions.sequence( + Actions.fadeOut(0f), + Actions.run { + backgroundTable.addActor(mapHolder) + mapHolder.center(backgroundTable) + }, + Actions.fadeIn(0.3f) + )) + } + } private fun resumeGame() { if (GUI.isWorldLoaded()) {