From c3d592393e7ad66da01cdbe814d4ca26e840cfae Mon Sep 17 00:00:00 2001 From: Yair Morgenstern Date: Sat, 23 Dec 2023 23:36:14 +0200 Subject: [PATCH] Added experimental scenarios! --- .../jsons/translations/template.properties | 2 + .../unciv/logic/civilization/Civilization.kt | 6 ++- core/src/com/unciv/logic/files/UncivFiles.kt | 28 ++++++++++++ .../com/unciv/models/metadata/GameSettings.kt | 12 ++--- .../ui/components/input/KeyboardBinding.kt | 1 + .../unciv/ui/popups/options/AdvancedTab.kt | 6 +-- .../screens/mainmenuscreen/MainMenuScreen.kt | 8 ++++ .../screens/mainmenuscreen/ScenarioScreen.kt | 44 +++++++++++++++++++ docs/Modders/Scenarios.md | 20 +++++++++ 9 files changed, 116 insertions(+), 11 deletions(-) create mode 100644 core/src/com/unciv/ui/screens/mainmenuscreen/ScenarioScreen.kt create mode 100644 docs/Modders/Scenarios.md diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index 820aa4b352..e13bd91f5b 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -826,6 +826,7 @@ Default Font = Max zoom out = Enable Easter Eggs = +Enable Scenarios (experimental) = Enlarge selected notifications = Generate translation files = @@ -1835,6 +1836,7 @@ Date ↓ = Stars ↓ = Status ↓ = +Scenarios = # Uniques that are relevant to more than one type of game object diff --git a/core/src/com/unciv/logic/civilization/Civilization.kt b/core/src/com/unciv/logic/civilization/Civilization.kt index 704866227a..19f141576f 100644 --- a/core/src/com/unciv/logic/civilization/Civilization.kt +++ b/core/src/com/unciv/logic/civilization/Civilization.kt @@ -89,7 +89,7 @@ class Civilization : IsPartOfGameInfoSerialization { @Transient val units = UnitManager(this) - + @Transient var threatManager = ThreatManager(this) @@ -321,7 +321,9 @@ class Civilization : IsPartOfGameInfoSerialization { * city-states to contain the barbarians. Therefore, [getKnownCivs] will **not** list the barbarians * for major civs, but **will** do so for city-states after some gameplay. */ - fun getKnownCivs() = diplomacy.values.asSequence().map { it.otherCiv() }.filter { !it.isDefeated() } + fun getKnownCivs() = diplomacy.values.asSequence().map { it.otherCiv() } + .filter { !it.isDefeated() && !it.isSpectator() } + fun knows(otherCivName: String) = diplomacy.containsKey(otherCivName) fun knows(otherCiv: Civilization) = knows(otherCiv.civName) diff --git a/core/src/com/unciv/logic/files/UncivFiles.kt b/core/src/com/unciv/logic/files/UncivFiles.kt index 18e61a02e2..c50a9cdf49 100644 --- a/core/src/com/unciv/logic/files/UncivFiles.kt +++ b/core/src/com/unciv/logic/files/UncivFiles.kt @@ -15,9 +15,12 @@ import com.unciv.logic.GameInfoPreview import com.unciv.logic.GameInfoSerializationVersion import com.unciv.logic.HasGameInfoSerializationVersion import com.unciv.logic.UncivShowableException +import com.unciv.logic.civilization.PlayerType +import com.unciv.logic.civilization.managers.TurnManager import com.unciv.models.metadata.GameSettings import com.unciv.models.metadata.doMigrations import com.unciv.models.metadata.isMigrationNecessary +import com.unciv.models.ruleset.RulesetCache import com.unciv.ui.screens.savescreens.Gzip import com.unciv.utils.Concurrency import com.unciv.utils.Log @@ -309,6 +312,31 @@ class UncivFiles( getGeneralSettingsFile().writeString(json().toJson(gameSettings), false, Charsets.UTF_8.name()) } + val scenarioFolder = "scenarios" + fun getScenarioFiles() = sequence { + + for (mod in RulesetCache.values) { + val modFolder = mod.folderLocation ?: continue + val scenarioFolder = modFolder.child(scenarioFolder) + if (scenarioFolder.exists()) + for (file in scenarioFolder.list()) + yield(Pair(file, mod)) + } + } + + fun loadScenario(gameFile: FileHandle): GameInfo { + val game = loadGameFromFile(gameFile) + game.civilizations.removeAll { it.isSpectator() } + if (game.civilizations.none { it.isHuman() }) + game.civilizations.first { it.isMajorCiv() }.playerType = PlayerType.Human + + game.currentPlayerCiv = game.civilizations.first { it.playerType == PlayerType.Human } + game.currentPlayer = game.currentPlayerCiv.civName + TurnManager(game.currentPlayerCiv).startTurn() + + return game + } + companion object { var saveZipped = false diff --git a/core/src/com/unciv/models/metadata/GameSettings.kt b/core/src/com/unciv/models/metadata/GameSettings.kt index fce1919a28..1255e2a739 100644 --- a/core/src/com/unciv/models/metadata/GameSettings.kt +++ b/core/src/com/unciv/models/metadata/GameSettings.kt @@ -91,7 +91,7 @@ class GameSettings { var androidHideSystemUi = true var multiplayer = GameSettingsMultiplayer() - + var autoPlay = GameSettingsAutoPlay() var enableEspionageOption = false @@ -336,21 +336,21 @@ class GameSettings { var autoPlayPolicies: Boolean = true var autoPlayReligion: Boolean = true var autoPlayDiplomacy: Boolean = true - + var turnsToAutoPlay: Int = 0 var autoPlayTurnInProgress: Boolean = false - + fun startAutoPlay() { turnsToAutoPlay = autoPlayMaxTurns } - + fun stopAutoPlay() { turnsToAutoPlay = 0 autoPlayTurnInProgress = false } - + fun isAutoPlaying(): Boolean = turnsToAutoPlay > 0 - + fun isAutoPlayingAndFullAI():Boolean = isAutoPlaying() && fullAutoPlayAI } diff --git a/core/src/com/unciv/ui/components/input/KeyboardBinding.kt b/core/src/com/unciv/ui/components/input/KeyboardBinding.kt index 85d53daa47..a4bc4e36f8 100644 --- a/core/src/com/unciv/ui/components/input/KeyboardBinding.kt +++ b/core/src/com/unciv/ui/components/input/KeyboardBinding.kt @@ -26,6 +26,7 @@ enum class KeyboardBinding( Multiplayer(Category.MainMenu), // Name disambiguation maybe soon, not yet necessary MapEditor(Category.MainMenu, "Map editor", KeyCharAndCode('E')), ModManager(Category.MainMenu, "Mods", KeyCharAndCode('D')), + Scenarios(Category.MainMenu, "Scenarios", KeyCharAndCode('S')), MainMenuOptions(Category.MainMenu, "Options", KeyCharAndCode('O')), // Separate binding from World where it's Ctrl-O default // Worldscreen diff --git a/core/src/com/unciv/ui/popups/options/AdvancedTab.kt b/core/src/com/unciv/ui/popups/options/AdvancedTab.kt index 15d6b6f4a1..312e671b3c 100644 --- a/core/src/com/unciv/ui/popups/options/AdvancedTab.kt +++ b/core/src/com/unciv/ui/popups/options/AdvancedTab.kt @@ -17,8 +17,8 @@ import com.unciv.Constants import com.unciv.GUI import com.unciv.UncivGame import com.unciv.models.metadata.GameSettings -import com.unciv.models.metadata.ModCategories import com.unciv.models.metadata.GameSettings.ScreenSize +import com.unciv.models.metadata.ModCategories import com.unciv.models.translations.TranslationFileWriter import com.unciv.models.translations.tr import com.unciv.ui.components.UncivTooltip.Companion.addTooltip @@ -42,11 +42,11 @@ import com.unciv.utils.Concurrency import com.unciv.utils.Display import com.unciv.utils.ScreenOrientation import com.unciv.utils.launchOnGLThread -import java.util.UUID -import java.util.zip.Deflater import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import java.util.UUID +import java.util.zip.Deflater fun advancedTab( optionsPopup: OptionsPopup, diff --git a/core/src/com/unciv/ui/screens/mainmenuscreen/MainMenuScreen.kt b/core/src/com/unciv/ui/screens/mainmenuscreen/MainMenuScreen.kt index 24b362b3b7..8bd4ecc868 100644 --- a/core/src/com/unciv/ui/screens/mainmenuscreen/MainMenuScreen.kt +++ b/core/src/com/unciv/ui/screens/mainmenuscreen/MainMenuScreen.kt @@ -167,6 +167,12 @@ class MainMenuScreen: BaseScreen(), RecreateOnResize { { game.pushScreen(ModManagementScreen()) } column2.add(modsTable).row() + if (game.files.getScenarioFiles().any()){ + val scenarioTable = getMenuButton("Scenarios", "OtherIcons/Mods", KeyboardBinding.Scenarios) + { game.pushScreen(ScenarioScreen()) } + column2.add(scenarioTable).row() + } + val optionsTable = getMenuButton("Options", "OtherIcons/Options", KeyboardBinding.MainMenuOptions) { openOptionsPopup() } optionsTable.onLongPress { openOptionsPopup(withDebug = true) } @@ -352,3 +358,5 @@ class MainMenuScreen: BaseScreen(), RecreateOnResize { // We contain a map... override fun getShortcutDispatcherVetoer() = KeyShortcutDispatcherVeto.createTileGroupMapDispatcherVetoer() } + + diff --git a/core/src/com/unciv/ui/screens/mainmenuscreen/ScenarioScreen.kt b/core/src/com/unciv/ui/screens/mainmenuscreen/ScenarioScreen.kt new file mode 100644 index 0000000000..6afecc5f0c --- /dev/null +++ b/core/src/com/unciv/ui/screens/mainmenuscreen/ScenarioScreen.kt @@ -0,0 +1,44 @@ +package com.unciv.ui.screens.mainmenuscreen + +import com.badlogic.gdx.files.FileHandle +import com.unciv.models.translations.tr +import com.unciv.ui.components.extensions.enable +import com.unciv.ui.components.extensions.toTextButton +import com.unciv.ui.components.input.onClick +import com.unciv.ui.screens.pickerscreens.PickerScreen +import com.unciv.utils.Concurrency + +class ScenarioScreen: PickerScreen() { + + private var scenarioToLoad: FileHandle? = null + + init { + val scenarioFiles = game.files.getScenarioFiles() + rightSideButton.setText("Choose scenario") + Concurrency.run { + for ((file, mod) in scenarioFiles) { + try { + val scenarioPreview = game.files.loadGamePreviewFromFile(file) + Concurrency.runOnGLThread { + topTable.add(file.name().toTextButton().onClick { + descriptionLabel.setText("Mod: [${mod.name}]".tr()) + scenarioToLoad = file + rightSideButton.setText(file.name()) + rightSideButton.enable() + }) + } + } catch (ex: Exception) { } // invalid, couldn't even load preview, probably invalid json + } + } + + rightSideButton.onClick { + if (scenarioToLoad != null) + Concurrency.run { + val scenario = game.files.loadScenario(scenarioToLoad!!) + game.loadGame(scenario) + } + } + + setDefaultCloseAction() + } +} diff --git a/docs/Modders/Scenarios.md b/docs/Modders/Scenarios.md new file mode 100644 index 0000000000..52edcc9e4f --- /dev/null +++ b/docs/Modders/Scenarios.md @@ -0,0 +1,20 @@ +# Scenarios + +Scenarios are specific game states, set up so a player has a specific experience. + +These can range from just having cities and units in specific places, to having full-blown custom rulesets to support them. + +When creating a mod, we differentiate the *ruleset* from the *scenario* - the scenario is just a specific game state, or in other words - a saved game. + + +To create a scenario: +- Create a new game with the players you want, AND a spectator +- Enter the game as the spectator, and edit the save using the console +- Save the game, copy the game save file to a "scenarios" folder in your mod + +## Console + +To open the console, click the "`" button on your keyboard. + +To see available commands, click enter. This works for subcommands as well (e.g. when you entered `tile`) +