diff --git a/android/assets/jsons/Tutorials.json b/android/assets/jsons/Tutorials.json index 66a5052f11..4f807cc3c4 100644 --- a/android/assets/jsons/Tutorials.json +++ b/android/assets/jsons/Tutorials.json @@ -284,6 +284,17 @@ "What you don't see: The phone/tablet's back button will pop the question whether you wish to leave Unciv and go back to Real Life. On desktop versions, you can use the ESC key." ] }, + { + "name": "AutoPlay", + "steps": ["When at later stages of the game, you might have a lot of units but only a little to do. To help you we have implemented an AutoPlay feature that lets you use the AI to play part or all of your turn.", + "To enable AutoPlay, go to options and open the AutoPlay tab and press \"Show AutoPlay button\".", + "Clicking on the AutoPlay button opens a popup menue for choosing to AutoPlay parts or all of your turn.", + "Clicking Start AutoPlay in the pop-up menue or long pressing the AutoPlay button begins the multi-turn AutoPlay. This will play your next turns as if you were an AI.", + "To cancel multi-turn AutoPlay you can press the AutoPlay button, next turn button or open the options menue.", + "Multi-turn AutoPlay is not advised on harder difficulty levels as your AI will not play better against an AI with modifiers.", + "Multi-turn AutoPlay for multiplayer is not yet supported." + ] + }, { "name": "Faith", "steps": [ diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index 49c7b4b7d2..2bbb6d29a9 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -857,6 +857,16 @@ Hide = HIGHLY EXPERIMENTAL - YOU HAVE BEEN WARNED! = You need to restart the game for this change to take effect. = +# AutoPlay +AutoPlay = +Show AutoPlay button = +Multi-turn AutoPlay amount = + +Start AutoPlay = +AutoPlay End Turn = +AutoPlay Military Once = +AutoPlay Civilians Once = +AutoPlay Economy Once = # Notifications diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt index ed5d9734b0..4524947c92 100644 --- a/core/src/com/unciv/UncivGame.kt +++ b/core/src/com/unciv/UncivGame.kt @@ -292,6 +292,7 @@ open class UncivGame(val isConsoleMode: Boolean = false) : Game(), PlatformSpeci fun popScreen(): BaseScreen? { if (screenStack.size == 1) { musicController.pause() + UncivGame.Current.settings.autoPlay.stopAutoPlay() ConfirmPopup( screen = screenStack.last(), question = "Do you want to exit the game?", diff --git a/core/src/com/unciv/logic/automation/civilization/NextTurnAutomation.kt b/core/src/com/unciv/logic/automation/civilization/NextTurnAutomation.kt index 4eedbb46ce..3c2e514637 100644 --- a/core/src/com/unciv/logic/automation/civilization/NextTurnAutomation.kt +++ b/core/src/com/unciv/logic/automation/civilization/NextTurnAutomation.kt @@ -339,7 +339,7 @@ object NextTurnAutomation { for (unit in sortedUnits) UnitAutomation.automateUnitMoves(unit) } - private fun getUnitPriority(unit: MapUnit, isAtWar: Boolean): Int { + fun getUnitPriority(unit: MapUnit, isAtWar: Boolean): Int { if (unit.isCivilian() && !unit.isGreatPersonOfType("War")) return 1 // Civilian if (unit.baseUnit.isAirUnit()) return 2 val distance = if (!isAtWar) 0 else unit.civ.threatManager.getDistanceToClosestEnemyUnit(unit.getTile(),6) @@ -356,7 +356,7 @@ object NextTurnAutomation { for (city in civInfo.cities) UnitAutomation.tryBombardEnemy(city) } - private fun automateCities(civInfo: Civilization) { + fun automateCities(civInfo: Civilization) { val ownMilitaryStrength = civInfo.getStatForRanking(RankingType.Force) val sumOfEnemiesMilitaryStrength = civInfo.gameInfo.civilizations diff --git a/core/src/com/unciv/logic/automation/unit/UnitAutomation.kt b/core/src/com/unciv/logic/automation/unit/UnitAutomation.kt index e474677d4a..f90dc10f41 100644 --- a/core/src/com/unciv/logic/automation/unit/UnitAutomation.kt +++ b/core/src/com/unciv/logic/automation/unit/UnitAutomation.kt @@ -125,8 +125,8 @@ object UnitAutomation { } internal fun tryUpgradeUnit(unit: MapUnit): Boolean { - val isHuman = unit.civ.isHuman() - if (!UncivGame.Current.settings.automatedUnitsCanUpgrade && isHuman) return false + if (unit.civ.isHuman() && (!UncivGame.Current.settings.automatedUnitsCanUpgrade + || UncivGame.Current.settings.autoPlay.isAutoPlayingAndFullAI())) return false if (unit.baseUnit.upgradesTo == null) return false val upgradedUnit = unit.upgrade.getUnitToUpgradeTo() if (!upgradedUnit.isBuildable(unit.civ)) return false // for resource reasons, usually diff --git a/core/src/com/unciv/logic/automation/unit/WorkerAutomation.kt b/core/src/com/unciv/logic/automation/unit/WorkerAutomation.kt index a0a828a1e5..3a1b6e70a4 100644 --- a/core/src/com/unciv/logic/automation/unit/WorkerAutomation.kt +++ b/core/src/com/unciv/logic/automation/unit/WorkerAutomation.kt @@ -50,7 +50,8 @@ class WorkerAutomation( private val bestRoadAvailable: RoadStatus = cloningSource?.bestRoadAvailable ?: //Player can choose not to auto-build roads & railroads. - if (civInfo.isHuman() && !UncivGame.Current.settings.autoBuildingRoads) + if (civInfo.isHuman() && (!UncivGame.Current.settings.autoBuildingRoads + || UncivGame.Current.settings.autoPlay.isAutoPlayingAndFullAI())) RoadStatus.None else civInfo.tech.getBestRoadAvailable() @@ -340,11 +341,12 @@ class WorkerAutomation( //If the tile is a junk improvement or a fort placed in a bad location. val junkImprovement = tile.getTileImprovement()?.hasUnique(UniqueType.AutomatedWorkersWillReplace) == true - || (tile.improvement == Constants.fort && !evaluateFortSuroundings(tile, false) && !civInfo.isHuman()) + || (tile.improvement == Constants.fort && !evaluateFortSuroundings(tile, false) + && (!civInfo.isHuman() || UncivGame.Current.settings.autoPlay.isAutoPlayingAndFullAI())) if (tile.improvement != null && !junkImprovement && !UncivGame.Current.settings.automatedWorkersReplaceImprovements - && unit.civ.isHuman()) + && unit.civ.isHuman() && !UncivGame.Current.settings.autoPlay.isAutoPlayingAndFullAI()) return false if (tile.improvement == null || junkImprovement) { diff --git a/core/src/com/unciv/logic/battle/Battle.kt b/core/src/com/unciv/logic/battle/Battle.kt index da63b5cccd..24e0ef91d3 100644 --- a/core/src/com/unciv/logic/battle/Battle.kt +++ b/core/src/com/unciv/logic/battle/Battle.kt @@ -2,6 +2,7 @@ package com.unciv.logic.battle import com.badlogic.gdx.math.Vector2 import com.unciv.Constants +import com.unciv.GUI import com.unciv.UncivGame import com.unciv.logic.automation.civilization.NextTurnAutomation import com.unciv.logic.city.City @@ -535,7 +536,7 @@ object Battle { city.puppetCity(attackerCiv) //Although in Civ5 Venice is unable to re-annex their capital, that seems a bit silly. No check for May not annex cities here. city.annexCity() - } else if (attackerCiv.isHuman()) { + } else if (attackerCiv.isHuman() && !(UncivGame.Current.settings.autoPlay.isAutoPlayingAndFullAI())) { // we're not taking our former capital attackerCiv.popupAlerts.add(PopupAlert(AlertType.CityConquered, city.id)) } else automateCityConquer(attackerCiv, city) diff --git a/core/src/com/unciv/logic/city/CityConstructions.kt b/core/src/com/unciv/logic/city/CityConstructions.kt index c87dff610f..330ed3386a 100644 --- a/core/src/com/unciv/logic/city/CityConstructions.kt +++ b/core/src/com/unciv/logic/city/CityConstructions.kt @@ -697,7 +697,8 @@ class CityConstructions : IsPartOfGameInfoSerialization { val isCurrentPlayersTurn = city.civ.gameInfo.isUsersTurn() || !city.civ.gameInfo.gameParameters.isOnlineMultiplayer - if ((UncivGame.Current.settings.autoAssignCityProduction && isCurrentPlayersTurn) // only automate if the active human player has the setting to automate production + if ((isCurrentPlayersTurn && (UncivGame.Current.settings.autoAssignCityProduction + || UncivGame.Current.settings.autoPlay.isAutoPlayingAndFullAI())) // only automate if the active human player has the setting to automate production || !city.civ.isHuman() || city.isPuppet) { ConstructionAutomation(this).chooseNextConstruction() } diff --git a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyFunctions.kt b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyFunctions.kt index 867e250f7a..071f868e83 100644 --- a/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyFunctions.kt +++ b/core/src/com/unciv/logic/civilization/diplomacy/DiplomacyFunctions.kt @@ -156,7 +156,7 @@ class DiplomacyFunctions(val civInfo: Civilization) { if (diplomacyManager != null && (diplomacyManager.hasOpenBorders || diplomacyManager.diplomaticStatus == DiplomaticStatus.War)) return true // Players can always pass through city-state tiles - if (civInfo.isHuman() && otherCiv.isCityState()) return true + if ((civInfo.isHuman() && !UncivGame.Current.settings.autoPlay.isAutoPlayingAndFullAI()) && otherCiv.isCityState()) return true return false } diff --git a/core/src/com/unciv/logic/multiplayer/OnlineMultiplayer.kt b/core/src/com/unciv/logic/multiplayer/OnlineMultiplayer.kt index 2d4889e7ad..4686023a65 100644 --- a/core/src/com/unciv/logic/multiplayer/OnlineMultiplayer.kt +++ b/core/src/com/unciv/logic/multiplayer/OnlineMultiplayer.kt @@ -244,6 +244,7 @@ class OnlineMultiplayer { } else if (onlinePreview != null && hasNewerGameState(preview, onlinePreview)) { onlineGame.doManualUpdate(preview) } + UncivGame.Current.settings.autoPlay.stopAutoPlay() UncivGame.Current.loadGame(gameInfo) } diff --git a/core/src/com/unciv/models/metadata/GameSettings.kt b/core/src/com/unciv/models/metadata/GameSettings.kt index 2d824f8718..7c73a47e65 100644 --- a/core/src/com/unciv/models/metadata/GameSettings.kt +++ b/core/src/com/unciv/models/metadata/GameSettings.kt @@ -91,6 +91,8 @@ class GameSettings { var androidHideSystemUi = true var multiplayer = GameSettingsMultiplayer() + + var autoPlay = GameSettingsAutoPlay() var enableEspionageOption = false @@ -322,6 +324,35 @@ class GameSettings { } } + class GameSettingsAutoPlay { + var showAutoPlayButton: Boolean = false + var autoPlayMaxTurns = 10 + var fullAutoPlayAI: Boolean = true + var autoPlayMilitary: Boolean = true + var autoPlayCivilian: Boolean = true + var autoPlayEconomy: Boolean = true + var autoPlayTechnology: Boolean = true + 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 + } + @Suppress("SuspiciousCallableReferenceInLambda") // By @Azzurite, safe as long as that warning below is followed enum class GameSetting( val kClass: KClass<*>, diff --git a/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt b/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt index 101c4c0aaa..5dc2f0fe5e 100644 --- a/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt +++ b/core/src/com/unciv/models/ruleset/unique/UniqueTriggerActivation.kt @@ -209,7 +209,7 @@ object UniqueTriggerActivation { val greatPeople = civInfo.greatPeople.getGreatPeople() if (unique.type == UniqueType.MayanGainGreatPerson && civInfo.greatPeople.longCountGPPool.isEmpty()) civInfo.greatPeople.longCountGPPool = greatPeople.map { it.name }.toHashSet() - if (civInfo.isHuman()) { + if (civInfo.isHuman() && !UncivGame.Current.settings.autoPlay.isAutoPlayingAndFullAI()) { civInfo.greatPeople.freeGreatPeople++ // Anyone an idea for a good icon? if (unique.type == UniqueType.MayanGainGreatPerson) { diff --git a/core/src/com/unciv/ui/popups/options/AutoPlayTab.kt b/core/src/com/unciv/ui/popups/options/AutoPlayTab.kt new file mode 100644 index 0000000000..e3587250bd --- /dev/null +++ b/core/src/com/unciv/ui/popups/options/AutoPlayTab.kt @@ -0,0 +1,91 @@ +package com.unciv.ui.popups.options + +import com.badlogic.gdx.scenes.scene2d.ui.Table +import com.unciv.models.metadata.GameSettings +import com.unciv.ui.components.widgets.UncivSlider +import com.unciv.ui.components.extensions.toLabel +import com.unciv.ui.screens.basescreen.BaseScreen + +fun autoPlayTab( + optionsPopup: OptionsPopup +): Table = Table(BaseScreen.skin).apply { + pad(10f) + defaults().pad(5f) + + val settings = optionsPopup.settings +// fun addAutoPlaySections() { +// optionsPopup.addCheckbox( +// this, +// "AutoPlay Military", +// settings.autoPlay.autoPlayMilitary, false +// ) { settings.autoPlay.autoPlayMilitary = it } +// optionsPopup.addCheckbox( +// this, +// "AutoPlay Civilian", +// settings.autoPlay.autoPlayCivilian, false +// ) { settings.autoPlay.autoPlayCivilian = it } +// optionsPopup.addCheckbox( +// this, +// "AutoPlay Economy", +// settings.autoPlay.autoPlayEconomy, false +// ) { settings.autoPlay.autoPlayEconomy = it } +// optionsPopup.addCheckbox( +// this, +// "AutoPlay Diplomacy", +// settings.autoPlay.autoPlayDiplomacy, false +// ) { settings.autoPlay.autoPlayDiplomacy = it } +// optionsPopup.addCheckbox( +// this, +// "AutoPlay Technology", +// settings.autoPlay.autoPlayTechnology, false +// ) { settings.autoPlay.autoPlayTechnology = it } +// optionsPopup.addCheckbox( +// this, +// "AutoPlay Policies", +// settings.autoPlay.autoPlayPolicies, false +// ) { settings.autoPlay.autoPlayPolicies = it } +// optionsPopup.addCheckbox( +// this, +// "AutoPlay Religion", +// settings.autoPlay.autoPlayReligion, false +// ) { settings.autoPlay.autoPlayReligion = it } +// } + + optionsPopup.addCheckbox( + this, + "Show AutoPlay button", + settings.autoPlay.showAutoPlayButton, true + ) { settings.autoPlay.showAutoPlayButton = it + settings.autoPlay.stopAutoPlay() } + + + addAutoPlayMaxTurnsSlider(this, settings, optionsPopup.selectBoxMinWidth) +// optionsPopup.addCheckbox( +// this, +// "Full AutoPlay AI", +// settings.autoPlay.fullAutoPlayAI, false +// ) { settings.autoPlay.fullAutoPlayAI = it +// if (!it) addAutoPlaySections() +// else optionsPopup.tabs.replacePage(optionsPopup.tabs.activePage, autoPlayTab(optionsPopup)) +// } +// if (!settings.autoPlay.fullAutoPlayAI) +// addAutoPlaySections() +} + +private fun addAutoPlayMaxTurnsSlider( + table: Table, + settings: GameSettings, + selectBoxMinWidth: Float +) { + table.add("Multi-turn AutoPlay amount".toLabel()).left().fillX() + + val minimapSlider = UncivSlider( + 1f, 200f, 1f, + initial = settings.autoPlay.autoPlayMaxTurns.toFloat() + ) { + val turns = it.toInt() + settings.autoPlay.autoPlayMaxTurns = turns + } + table.add(minimapSlider).minWidth(selectBoxMinWidth).pad(10f).row() +} + diff --git a/core/src/com/unciv/ui/popups/options/GameplayTab.kt b/core/src/com/unciv/ui/popups/options/GameplayTab.kt index 092d43b786..2096d7497f 100644 --- a/core/src/com/unciv/ui/popups/options/GameplayTab.kt +++ b/core/src/com/unciv/ui/popups/options/GameplayTab.kt @@ -10,7 +10,7 @@ import com.unciv.ui.screens.basescreen.BaseScreen fun gameplayTab( optionsPopup: OptionsPopup -) = Table(BaseScreen.skin).apply { +): Table = Table(BaseScreen.skin).apply { pad(10f) defaults().pad(5f) diff --git a/core/src/com/unciv/ui/popups/options/OptionsPopup.kt b/core/src/com/unciv/ui/popups/options/OptionsPopup.kt index 90ce66b25c..1e2bc64037 100644 --- a/core/src/com/unciv/ui/popups/options/OptionsPopup.kt +++ b/core/src/com/unciv/ui/popups/options/OptionsPopup.kt @@ -88,6 +88,11 @@ class OptionsPopup( gameplayTab(this), ImageGetter.getImage("OtherIcons/Options"), 24f ) + tabs.addPage( + "AutoPlay", + autoPlayTab(this), + ImageGetter.getImage("OtherIcons/NationSwap"), 24f + ) tabs.addPage( "Language", LanguageTab(this, ::reloadWorldAndOptions), diff --git a/core/src/com/unciv/ui/screens/pickerscreens/GreatPersonPickerScreen.kt b/core/src/com/unciv/ui/screens/pickerscreens/GreatPersonPickerScreen.kt index f10e365a7e..d0ef8b9061 100644 --- a/core/src/com/unciv/ui/screens/pickerscreens/GreatPersonPickerScreen.kt +++ b/core/src/com/unciv/ui/screens/pickerscreens/GreatPersonPickerScreen.kt @@ -14,6 +14,7 @@ class GreatPersonPickerScreen(val civInfo:Civilization) : PickerScreen() { private var theChosenOne: BaseUnit? = null init { + UncivGame.Current.settings.autoPlay.stopAutoPlay() closeButton.isVisible = false rightSideButton.setText("Choose a free great person".tr()) diff --git a/core/src/com/unciv/ui/screens/savescreens/LoadGameScreen.kt b/core/src/com/unciv/ui/screens/savescreens/LoadGameScreen.kt index 72cd86b70a..c382e9e08e 100644 --- a/core/src/com/unciv/ui/screens/savescreens/LoadGameScreen.kt +++ b/core/src/com/unciv/ui/screens/savescreens/LoadGameScreen.kt @@ -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 @@ -121,6 +122,7 @@ class LoadGameScreen : LoadOrSaveScreen() { } private fun onLoadGame() { + UncivGame.Current.settings.autoPlay.stopAutoPlay() if (selectedSave == null) return val loadingPopup = LoadingPopup(this) Concurrency.run(loadGame) { diff --git a/core/src/com/unciv/ui/screens/savescreens/QuickSave.kt b/core/src/com/unciv/ui/screens/savescreens/QuickSave.kt index 2c592d0d6e..b369206431 100644 --- a/core/src/com/unciv/ui/screens/savescreens/QuickSave.kt +++ b/core/src/com/unciv/ui/screens/savescreens/QuickSave.kt @@ -35,6 +35,7 @@ object QuickSave { } fun load(screen: WorldScreen) { + UncivGame.Current.settings.autoPlay.stopAutoPlay() val files = UncivGame.Current.files val toast = ToastPopup("Quickloading...", screen) Concurrency.run("QuickLoadGame") { @@ -57,6 +58,7 @@ object QuickSave { } fun autoLoadGame(screen: MainMenuScreen) { + UncivGame.Current.settings.autoPlay.stopAutoPlay() val loadingPopup = LoadingPopup(screen) Concurrency.run("autoLoadGame") { // Load game from file to class on separate thread to avoid ANR... diff --git a/core/src/com/unciv/ui/screens/victoryscreen/VictoryScreen.kt b/core/src/com/unciv/ui/screens/victoryscreen/VictoryScreen.kt index b483ffe1ba..0a4de8ec23 100644 --- a/core/src/com/unciv/ui/screens/victoryscreen/VictoryScreen.kt +++ b/core/src/com/unciv/ui/screens/victoryscreen/VictoryScreen.kt @@ -85,6 +85,7 @@ class VictoryScreen( } init { + UncivGame.Current.settings.autoPlay.stopAutoPlay() //**************** Set up the tabs **************** splitPane.setFirstWidget(tabs) val iconSize = Constants.headingFontSize.toFloat() @@ -158,6 +159,7 @@ class VictoryScreen( displayWonOrLost("[$winningCiv] has won a [$victoryType] Victory!", victory.defeatString) music.chooseTrack(playerCiv.civName, MusicMood.Defeat, EnumSet.of(MusicTrackChooserFlags.SuffixMustMatch)) } + UncivGame.Current.settings.autoPlay.stopAutoPlay() } private fun displayWonOrLost(vararg descriptions: String) { diff --git a/core/src/com/unciv/ui/screens/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/screens/worldscreen/WorldScreen.kt index eadcf51d28..41551a2912 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/WorldScreen.kt @@ -11,6 +11,7 @@ import com.unciv.Constants import com.unciv.UncivGame import com.unciv.logic.GameInfo import com.unciv.logic.UncivShowableException +import com.unciv.logic.civilization.AlertType import com.unciv.logic.civilization.Civilization import com.unciv.logic.civilization.PlayerType import com.unciv.logic.civilization.diplomacy.DiplomaticStatus @@ -58,6 +59,7 @@ import com.unciv.ui.screens.worldscreen.bottombar.BattleTable import com.unciv.ui.screens.worldscreen.bottombar.TileInfoTable import com.unciv.ui.screens.worldscreen.mainmenu.WorldScreenMusicPopup import com.unciv.ui.screens.worldscreen.minimap.MinimapHolder +import com.unciv.ui.screens.worldscreen.status.AutoPlayStatusButton import com.unciv.ui.screens.worldscreen.status.MultiplayerStatusButton import com.unciv.ui.screens.worldscreen.status.NextTurnButton import com.unciv.ui.screens.worldscreen.status.NextTurnProgress @@ -92,7 +94,7 @@ class WorldScreen( /** Indicates it's the player's ([viewingCiv]) turn */ var isPlayersTurn = viewingCiv.isCurrentPlayer() internal set // only this class is allowed to make changes - + /** Selected civilization, used in spectator and replay mode, equals viewingCiv in ordinary games */ var selectedCiv = viewingCiv @@ -412,7 +414,13 @@ class WorldScreen( fogOfWarButton.isEnabled = !selectedCiv.isSpectator() fogOfWarButton.setPosition(10f, topBar.y - fogOfWarButton.height - 10f) - if (!hasOpenPopups() && isPlayersTurn) { + // If the game has ended, lets stop AutoPlay + if (game.settings.autoPlay.isAutoPlaying() + && !gameInfo.oneMoreTurnMode && (viewingCiv.isDefeated() || gameInfo.checkForVictory())) { + game.settings.autoPlay.stopAutoPlay() + } + + if (!hasOpenPopups() && !game.settings.autoPlay.isAutoPlaying() && isPlayersTurn) { when { viewingCiv.shouldShowDiplomaticVotingResults() -> UncivGame.Current.pushScreen(DiplomaticVoteResultScreen(gameInfo.diplomaticVictoryVotesCast, viewingCiv)) @@ -694,6 +702,7 @@ class WorldScreen( private fun updateGameplayButtons() { nextTurnButton.update() + updateAutoPlayStatusButton() updateMultiplayerStatusButton() statusButtons.wrap(false) @@ -707,6 +716,18 @@ class WorldScreen( statusButtons.setPosition(stage.width - statusButtons.width - 10f, topBar.y - statusButtons.height - 10f) } + private fun updateAutoPlayStatusButton() { + if (statusButtons.autoPlayStatusButton == null) { + if (game.settings.autoPlay.showAutoPlayButton) + statusButtons.autoPlayStatusButton = AutoPlayStatusButton(this, nextTurnButton) + } else { + if (!game.settings.autoPlay.showAutoPlayButton) { + statusButtons.autoPlayStatusButton = null + game.settings.autoPlay.stopAutoPlay() + } + } + } + private fun updateMultiplayerStatusButton() { if (gameInfo.gameParameters.isOnlineMultiplayer || game.settings.multiplayer.statusButtonInSinglePlayer) { if (statusButtons.multiplayerStatusButton != null) return diff --git a/core/src/com/unciv/ui/screens/worldscreen/mainmenu/WorldScreenMenuPopup.kt b/core/src/com/unciv/ui/screens/worldscreen/mainmenu/WorldScreenMenuPopup.kt index 672f7475bf..0a8f62319b 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/mainmenu/WorldScreenMenuPopup.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/mainmenu/WorldScreenMenuPopup.kt @@ -1,5 +1,6 @@ package com.unciv.ui.screens.worldscreen.mainmenu +import com.unciv.UncivGame import com.unciv.ui.components.input.KeyboardBinding import com.unciv.ui.components.input.onLongPress import com.unciv.ui.popups.Popup @@ -10,6 +11,7 @@ import com.unciv.ui.screens.worldscreen.WorldScreen class WorldScreenMenuPopup(val worldScreen: WorldScreen) : Popup(worldScreen, scrollable = Scrollability.All) { init { + UncivGame.Current.settings.autoPlay.stopAutoPlay() defaults().fillX() addButton("Main menu") { diff --git a/core/src/com/unciv/ui/screens/worldscreen/status/AutoPlayMenu.kt b/core/src/com/unciv/ui/screens/worldscreen/status/AutoPlayMenu.kt new file mode 100644 index 0000000000..5f8717c95e --- /dev/null +++ b/core/src/com/unciv/ui/screens/worldscreen/status/AutoPlayMenu.kt @@ -0,0 +1,81 @@ +package com.unciv.ui.popups + +import com.badlogic.gdx.scenes.scene2d.Actor +import com.badlogic.gdx.scenes.scene2d.Stage +import com.badlogic.gdx.scenes.scene2d.ui.Table +import com.unciv.GUI +import com.unciv.logic.automation.civilization.NextTurnAutomation +import com.unciv.logic.automation.unit.UnitAutomation +import com.unciv.logic.civilization.managers.TurnManager +import com.unciv.ui.components.input.KeyboardBinding +import com.unciv.ui.screens.worldscreen.WorldScreen +import com.unciv.ui.screens.worldscreen.status.NextTurnButton + +//todo Check move/top/end for "place one improvement" buildings +//todo Check add/remove-all for "place one improvement" buildings + +/** + * Adds a number of options + */ +class AutoPlayMenu( + stage: Stage, + positionNextTo: Actor, + private val nextTurnButton: NextTurnButton, + private val worldScreen: WorldScreen +) : AnimatedMenuPopup(stage, getActorTopRight(positionNextTo)) { + private val settings = GUI.getSettings() + + init { + closeListeners.add { + } + } + + override fun createContentTable(): Table? { + val table = super.createContentTable()!! + if (!worldScreen.gameInfo.gameParameters.isOnlineMultiplayer) + table.add(getButton("Start AutoPlay", KeyboardBinding.RaisePriority, ::autoPlay)).row() + table.add(getButton("AutoPlay End Turn", KeyboardBinding.RaisePriority, ::autoPlayEndTurn)).row() + table.add(getButton("AutoPlay Military Once", KeyboardBinding.RaisePriority, ::autoPlayMilitary)).row() + table.add(getButton("AutoPlay Civilians Once", KeyboardBinding.RaisePriority, ::autoPlayCivilian)).row() + table.add(getButton("AutoPlay Economy Once", KeyboardBinding.RaisePriority, ::autoPlayEconomy)).row() + + return table.takeUnless { it.cells.isEmpty } + } + + private fun autoPlayEndTurn() { + TurnManager(worldScreen.viewingCiv).automateTurn() + worldScreen.nextTurn() + } + + private fun autoPlay() { + settings.autoPlay.startAutoPlay() + nextTurnButton.update() + } + + private fun autoPlayMilitary() { + val civInfo = worldScreen.viewingCiv + val isAtWar = civInfo.isAtWar() + val sortedUnits = civInfo.units.getCivUnits().filter { it.isMilitary() }.sortedBy { unit -> NextTurnAutomation.getUnitPriority(unit, isAtWar) } + for (unit in sortedUnits) UnitAutomation.automateUnitMoves(unit) + + for (city in civInfo.cities) UnitAutomation.tryBombardEnemy(city) + worldScreen.shouldUpdate = true + worldScreen.render(0f) + } + + private fun autoPlayCivilian() { + val civInfo = worldScreen.viewingCiv + val isAtWar = civInfo.isAtWar() + val sortedUnits = civInfo.units.getCivUnits().filter { it.isCivilian() }.sortedBy { unit -> NextTurnAutomation.getUnitPriority(unit, isAtWar) } + for (unit in sortedUnits) UnitAutomation.automateUnitMoves(unit) + worldScreen.shouldUpdate = true + worldScreen.render(0f) + } + + private fun autoPlayEconomy() { + val civInfo = worldScreen.viewingCiv + NextTurnAutomation.automateCities(civInfo) + worldScreen.shouldUpdate = true + worldScreen.render(0f) + } +} diff --git a/core/src/com/unciv/ui/screens/worldscreen/status/AutoPlayStatusButton.kt b/core/src/com/unciv/ui/screens/worldscreen/status/AutoPlayStatusButton.kt new file mode 100644 index 0000000000..c696e28f0a --- /dev/null +++ b/core/src/com/unciv/ui/screens/worldscreen/status/AutoPlayStatusButton.kt @@ -0,0 +1,54 @@ +package com.unciv.ui.screens.worldscreen.status + +import com.badlogic.gdx.scenes.scene2d.ui.Button +import com.badlogic.gdx.scenes.scene2d.ui.Image +import com.badlogic.gdx.scenes.scene2d.ui.Stack +import com.badlogic.gdx.utils.Disposable +import com.unciv.GUI +import com.unciv.ui.components.extensions.setSize +import com.unciv.ui.components.input.onClick +import com.unciv.ui.components.input.onRightClick +import com.unciv.ui.images.ImageGetter +import com.unciv.ui.popups.AutoPlayMenu +import com.unciv.ui.screens.basescreen.BaseScreen +import com.unciv.ui.screens.worldscreen.WorldScreen + +class AutoPlayStatusButton( + val worldScreen: WorldScreen, + nextTurnButton: NextTurnButton +) : Button(BaseScreen.skin), Disposable { + private val autoPlayImage = createAutoplayImage() + + + init { + add(Stack(autoPlayImage)).pad(5f) + val settings = GUI.getSettings() + onClick { + if (settings.autoPlay.isAutoPlaying()) + settings.autoPlay.stopAutoPlay() + else if (worldScreen.viewingCiv == worldScreen.gameInfo.currentPlayerCiv) + AutoPlayMenu(stage,this, nextTurnButton, worldScreen) + } + onRightClick { + if (!worldScreen.gameInfo.gameParameters.isOnlineMultiplayer + && worldScreen.viewingCiv == worldScreen.gameInfo.currentPlayerCiv) { + settings.autoPlay.startAutoPlay() + nextTurnButton.update() + } + } + } + + private fun createAutoplayImage(): Image { + val img = ImageGetter.getImage("OtherIcons/NationSwap") + img.setSize(40f) + return img + } + + override fun dispose() { + val settings = GUI.getSettings() + if (isPressed && settings.autoPlay.isAutoPlaying()) { + settings.autoPlay.stopAutoPlay() + } + } +} + diff --git a/core/src/com/unciv/ui/screens/worldscreen/status/NextTurnAction.kt b/core/src/com/unciv/ui/screens/worldscreen/status/NextTurnAction.kt index d28fdefd06..1b058851b5 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/status/NextTurnAction.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/status/NextTurnAction.kt @@ -2,6 +2,7 @@ package com.unciv.ui.screens.worldscreen.status import com.badlogic.gdx.graphics.Color import com.unciv.Constants +import com.unciv.UncivGame import com.unciv.logic.civilization.managers.ReligionManager import com.unciv.logic.civilization.managers.ReligionState import com.unciv.models.Counter @@ -24,6 +25,12 @@ enum class NextTurnAction(protected val text: String, val color: Color) { override val icon get() = null override fun isChoice(worldScreen: WorldScreen) = false }, + AutoPlay("AutoPlay", Color.WHITE) { + override fun isChoice(worldScreen: WorldScreen) = + UncivGame.Current.settings.autoPlay.isAutoPlaying() + override fun action(worldScreen: WorldScreen) = + UncivGame.Current.settings.autoPlay.stopAutoPlay() + }, Working(Constants.working, Color.GRAY) { override fun isChoice(worldScreen: WorldScreen) = worldScreen.isNextTurnUpdateRunning() @@ -120,7 +127,7 @@ enum class NextTurnAction(protected val text: String, val color: Color) { }, ; - open val icon: String? get() = "NotificationIcons/$name" + open val icon: String? get() = if (text != "AutoPlay") "NotificationIcons/$name" else "NotificationIcons/Working" open fun getText(worldScreen: WorldScreen) = text abstract fun isChoice(worldScreen: WorldScreen): Boolean open fun action(worldScreen: WorldScreen) {} diff --git a/core/src/com/unciv/ui/screens/worldscreen/status/NextTurnButton.kt b/core/src/com/unciv/ui/screens/worldscreen/status/NextTurnButton.kt index d0551fa164..b37a6807c6 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/status/NextTurnButton.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/status/NextTurnButton.kt @@ -1,5 +1,7 @@ package com.unciv.ui.screens.worldscreen.status +import com.unciv.GUI +import com.unciv.logic.civilization.managers.TurnManager import com.unciv.models.translations.tr import com.unciv.ui.components.UncivTooltip.Companion.addTooltip import com.unciv.ui.components.extensions.isEnabled @@ -16,7 +18,6 @@ class NextTurnButton( private val worldScreen: WorldScreen ) : IconTextButton("", null, 30) { private var nextTurnAction = NextTurnAction.Default - init { // label.setFontSize(30) labelCell.pad(10f) @@ -30,10 +31,20 @@ class NextTurnButton( fun update() { nextTurnAction = getNextTurnAction(worldScreen) updateButton(nextTurnAction) - - isEnabled = !worldScreen.hasOpenPopups() && worldScreen.isPlayersTurn - && !worldScreen.waitingForAutosave && !worldScreen.isNextTurnUpdateRunning() - + val settings = GUI.getSettings() + if (!settings.autoPlay.autoPlayTurnInProgress && settings.autoPlay.isAutoPlaying() + && worldScreen.isPlayersTurn && !worldScreen.waitingForAutosave && !worldScreen.isNextTurnUpdateRunning()) { + settings.autoPlay.autoPlayTurnInProgress = true + if (!worldScreen.viewingCiv.isSpectator()) + TurnManager(worldScreen.viewingCiv).automateTurn() + worldScreen.nextTurn() + settings.autoPlay.turnsToAutoPlay-- + settings.autoPlay.autoPlayTurnInProgress = false + } + + isEnabled = nextTurnAction.getText (worldScreen) == "AutoPlay" + || (!worldScreen.hasOpenPopups() && worldScreen.isPlayersTurn + && !worldScreen.waitingForAutosave && !worldScreen.isNextTurnUpdateRunning()) if (isEnabled) addTooltip(KeyboardBinding.NextTurn) else addTooltip("") } diff --git a/core/src/com/unciv/ui/screens/worldscreen/status/NextTurnProgress.kt b/core/src/com/unciv/ui/screens/worldscreen/status/NextTurnProgress.kt index 470ba5acdc..862e4364a9 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/status/NextTurnProgress.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/status/NextTurnProgress.kt @@ -4,6 +4,7 @@ import com.badlogic.gdx.graphics.Color import com.badlogic.gdx.scenes.scene2d.actions.Actions import com.badlogic.gdx.scenes.scene2d.ui.Table import com.unciv.GUI +import com.unciv.UncivGame import com.unciv.models.metadata.GameParameters import com.unciv.ui.components.extensions.disable import com.unciv.ui.images.ImageGetter @@ -97,7 +98,9 @@ class NextTurnProgress( // On first update the button text is not yet updated. To stabilize geometry, do it now if (progress == 0) nextTurnButton?.apply { disable() - updateButton(NextTurnAction.Working) + if (UncivGame.Current.settings.autoPlay.isAutoPlaying()) + updateButton(NextTurnAction.AutoPlay) + else updateButton(NextTurnAction.Working) barWidth = width - removeHorizontalPad - (background.leftWidth + background.rightWidth) // "cut off" the rounded parts of the button this@NextTurnProgress.setPosition((width - barWidth) / 2, barYPos) diff --git a/core/src/com/unciv/ui/screens/worldscreen/status/StatusButtons.kt b/core/src/com/unciv/ui/screens/worldscreen/status/StatusButtons.kt index 9a89e5d374..d925387869 100644 --- a/core/src/com/unciv/ui/screens/worldscreen/status/StatusButtons.kt +++ b/core/src/com/unciv/ui/screens/worldscreen/status/StatusButtons.kt @@ -5,8 +5,17 @@ import com.badlogic.gdx.utils.Disposable class StatusButtons( nextTurnButton: NextTurnButton, + autoPlayStatusButton: AutoPlayStatusButton? = null, multiplayerStatusButton: MultiplayerStatusButton? = null ) : HorizontalGroup(), Disposable { + var autoPlayStatusButton: AutoPlayStatusButton? = autoPlayStatusButton + set(button) { + autoPlayStatusButton?.remove() + field = button + if (button != null) { + addActorAt(0, button) + } + } var multiplayerStatusButton: MultiplayerStatusButton? = multiplayerStatusButton set(button) { multiplayerStatusButton?.remove() @@ -15,6 +24,7 @@ class StatusButtons( addActorAt(0, button) } } + init { space(10f) @@ -22,6 +32,9 @@ class StatusButtons( wrapReverse() wrapSpace(10f) rowRight() + if (autoPlayStatusButton != null) { + addActor(autoPlayStatusButton) + } if (multiplayerStatusButton != null) { addActor(multiplayerStatusButton) } @@ -29,6 +42,7 @@ class StatusButtons( } override fun dispose() { + autoPlayStatusButton?.dispose() multiplayerStatusButton?.dispose() } }