diff --git a/android/assets/jsons/translations/template.properties b/android/assets/jsons/translations/template.properties index 855a8fd880..402ef32270 100644 --- a/android/assets/jsons/translations/template.properties +++ b/android/assets/jsons/translations/template.properties @@ -361,6 +361,7 @@ Disable starting bias = Raging Barbarians = No Ancient Ruins = No Natural Wonders = +Allow anyone to spectate = Victory Conditions = Scientific = Domination = @@ -602,6 +603,7 @@ Paste gameID from clipboard = GameID = Game name = Loading latest game state... = +You are not allowed to spectate! = Couldn't download the latest game state! = Resign = Are you sure you want to resign? = diff --git a/core/src/com/unciv/MainMenuScreen.kt b/core/src/com/unciv/MainMenuScreen.kt index 2aed734a30..40beb8787e 100644 --- a/core/src/com/unciv/MainMenuScreen.kt +++ b/core/src/com/unciv/MainMenuScreen.kt @@ -8,6 +8,7 @@ import com.badlogic.gdx.scenes.scene2d.ui.Table import com.badlogic.gdx.utils.Align import com.unciv.logic.GameInfo import com.unciv.logic.GameStarter +import com.unciv.logic.UncivShowableException import com.unciv.logic.map.MapParameters import com.unciv.logic.map.MapShape import com.unciv.logic.map.MapSizeNew @@ -227,6 +228,10 @@ class MainMenuScreen: BaseScreen(), RecreateOnResize { // Can fail when starting the game... try { newGame = GameStarter.startNewGame(GameSetupInfo.fromSettings("Chieftain")) + } catch (notAPlayer: UncivShowableException) { + val (message) = LoadGameScreen.getLoadExceptionMessage(notAPlayer) + launchOnGLThread { ToastPopup(message, this@MainMenuScreen) } + return@run } catch (ex: Exception) { launchOnGLThread { ToastPopup(errorText, this@MainMenuScreen) } return@run @@ -239,6 +244,11 @@ class MainMenuScreen: BaseScreen(), RecreateOnResize { launchOnGLThread { ToastPopup("Not enough memory on phone to load game!", this@MainMenuScreen) } + } catch (notAPlayer: UncivShowableException) { + val (message) = LoadGameScreen.getLoadExceptionMessage(notAPlayer) + launchOnGLThread { + ToastPopup(message, this@MainMenuScreen) + } } catch (ex: Exception) { launchOnGLThread { ToastPopup(errorText, this@MainMenuScreen) diff --git a/core/src/com/unciv/UncivGame.kt b/core/src/com/unciv/UncivGame.kt index 41fa033225..6853e26f20 100644 --- a/core/src/com/unciv/UncivGame.kt +++ b/core/src/com/unciv/UncivGame.kt @@ -10,6 +10,7 @@ import com.badlogic.gdx.utils.Align import com.unciv.logic.GameInfo import com.unciv.logic.IsPartOfGameInfoSerialization import com.unciv.logic.UncivFiles +import com.unciv.logic.UncivShowableException import com.unciv.logic.civilization.PlayerType import com.unciv.logic.multiplayer.OnlineMultiplayer import com.unciv.models.metadata.GameSettings @@ -179,6 +180,12 @@ class UncivGame(parameters: UncivGameParameters) : Game() { val prevGameInfo = gameInfo gameInfo = newGameInfo + if (gameInfo?.gameParameters?.anyoneCanSpectate == false) { + if (gameInfo!!.civilizations.none { it.playerId == settings.multiplayer.userId }) { + throw UncivShowableException("You are not allowed to spectate!") + } + } + initializeResources(prevGameInfo, newGameInfo) val isLoadingSameGame = worldScreen != null && prevGameInfo != null && prevGameInfo.gameId == newGameInfo.gameId diff --git a/core/src/com/unciv/models/metadata/GameParameters.kt b/core/src/com/unciv/models/metadata/GameParameters.kt index a31187bfbe..5a84199e74 100644 --- a/core/src/com/unciv/models/metadata/GameParameters.kt +++ b/core/src/com/unciv/models/metadata/GameParameters.kt @@ -35,6 +35,7 @@ class GameParameters : IsPartOfGameInfoSerialization { // Default values are the var startingEra = "Ancient era" var isOnlineMultiplayer = false + var anyoneCanSpectate = false var baseRuleset: String = BaseRuleset.Civ_V_GnK.fullName var mods = LinkedHashSet() diff --git a/core/src/com/unciv/ui/multiplayer/MultiplayerHelpers.kt b/core/src/com/unciv/ui/multiplayer/MultiplayerHelpers.kt index d2f808aaf8..8b85c622d5 100644 --- a/core/src/com/unciv/ui/multiplayer/MultiplayerHelpers.kt +++ b/core/src/com/unciv/ui/multiplayer/MultiplayerHelpers.kt @@ -6,6 +6,7 @@ import com.unciv.logic.multiplayer.OnlineMultiplayer import com.unciv.logic.multiplayer.OnlineMultiplayerGame import com.unciv.models.translations.tr import com.unciv.ui.popup.Popup +import com.unciv.ui.popup.ToastPopup import com.unciv.ui.saves.LoadGameScreen import com.unciv.ui.utils.BaseScreen import com.unciv.ui.utils.extensions.formatShort diff --git a/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt b/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt index 86884462c0..0dfa505144 100644 --- a/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt +++ b/core/src/com/unciv/ui/newgamescreen/GameOptionsTable.kt @@ -70,6 +70,8 @@ class GameOptionsTable( checkboxTable.addOneCityChallengeCheckbox() checkboxTable.addNuclearWeaponsCheckbox() checkboxTable.addIsOnlineMultiplayerCheckbox() + if (gameParameters.isOnlineMultiplayer) + checkboxTable.addAnyoneCanSpectateCheckbox() if (UncivGame.Current.settings.enableEspionageOption) checkboxTable.addEnableEspionageCheckbox() checkboxTable.addNoStartBiasCheckbox() @@ -92,8 +94,8 @@ class GameOptionsTable( { gameParameters.noBarbarians = it } private fun Table.addRagingBarbariansCheckbox() = - addCheckbox("Raging Barbarians", gameParameters.ragingBarbarians) - { gameParameters.ragingBarbarians = it } + addCheckbox("Raging Barbarians", gameParameters.ragingBarbarians) + { gameParameters.ragingBarbarians = it } private fun Table.addOneCityChallengeCheckbox() = addCheckbox("One City Challenge", gameParameters.oneCityChallenge) @@ -111,6 +113,13 @@ class GameOptionsTable( if (shouldUseMultiplayer) { MultiplayerHelpers.showDropboxWarning(previousScreen as BaseScreen) } + update() + } + + private fun Table.addAnyoneCanSpectateCheckbox() = + addCheckbox("Allow anyone to spectate", gameParameters.anyoneCanSpectate) + { + gameParameters.anyoneCanSpectate = it } private fun Table.addEnableEspionageCheckbox() = diff --git a/core/src/com/unciv/ui/newgamescreen/NewGameScreen.kt b/core/src/com/unciv/ui/newgamescreen/NewGameScreen.kt index e45e06cdeb..05101752fd 100644 --- a/core/src/com/unciv/ui/newgamescreen/NewGameScreen.kt +++ b/core/src/com/unciv/ui/newgamescreen/NewGameScreen.kt @@ -24,6 +24,7 @@ import com.unciv.ui.pickerscreens.PickerScreen import com.unciv.ui.popup.ConfirmPopup import com.unciv.ui.popup.Popup import com.unciv.ui.popup.ToastPopup +import com.unciv.ui.saves.LoadGameScreen import com.unciv.ui.utils.BaseScreen import com.unciv.ui.utils.ExpanderTab import com.unciv.ui.utils.RecreateOnResize @@ -35,6 +36,7 @@ import com.unciv.ui.utils.extensions.onClick import com.unciv.ui.utils.extensions.pad import com.unciv.ui.utils.extensions.toLabel import com.unciv.ui.utils.extensions.toTextButton +import com.unciv.ui.worldscreen.WorldScreen import com.unciv.utils.Log import com.unciv.utils.concurrency.Concurrency import com.unciv.utils.concurrency.launchOnGLThread @@ -113,6 +115,16 @@ class NewGameScreen( return@onClick } } + + if (!gameSetupInfo.gameParameters.anyoneCanSpectate) { + if (gameSetupInfo.gameParameters.players.none { it.playerId == UncivGame.Current.settings.multiplayer.userId }) { + val notAllowedToSpectate = Popup(this) + notAllowedToSpectate.addGoodSizedLabel("You are not allowed to spectate!".tr()).row() + notAllowedToSpectate.addCloseButton() + notAllowedToSpectate.open() + return@onClick + } + } } if (gameSetupInfo.gameParameters.players.none { diff --git a/core/src/com/unciv/ui/saves/LoadGameScreen.kt b/core/src/com/unciv/ui/saves/LoadGameScreen.kt index 092413d988..be9f519447 100644 --- a/core/src/com/unciv/ui/saves/LoadGameScreen.kt +++ b/core/src/com/unciv/ui/saves/LoadGameScreen.kt @@ -119,6 +119,12 @@ class LoadGameScreen(previousScreen:BaseScreen) : LoadOrSaveScreen() { // This is what can lead to ANRs - reading the file and setting the transients, that's why this is in another thread val loadedGame = game.files.loadGameByName(selectedSave) game.loadGame(loadedGame) + } catch (notAPlayer: UncivShowableException) { + launchOnGLThread { + val (message) = getLoadExceptionMessage(notAPlayer) + loadingPopup.reuseWith(message, true) + handleLoadGameException(notAPlayer) + } } catch (ex: Exception) { launchOnGLThread { loadingPopup.close() diff --git a/core/src/com/unciv/ui/saves/QuickSave.kt b/core/src/com/unciv/ui/saves/QuickSave.kt index b9ce4b61ce..720f18aae0 100644 --- a/core/src/com/unciv/ui/saves/QuickSave.kt +++ b/core/src/com/unciv/ui/saves/QuickSave.kt @@ -4,6 +4,7 @@ import com.unciv.Constants import com.unciv.MainMenuScreen import com.unciv.UncivGame import com.unciv.logic.GameInfo +import com.unciv.logic.UncivShowableException import com.unciv.ui.multiplayer.MultiplayerHelpers import com.unciv.ui.popup.Popup import com.unciv.ui.popup.ToastPopup @@ -88,6 +89,12 @@ object QuickSave { screen.game.onlineMultiplayer.loadGame(savedGame) } catch (oom: OutOfMemoryError) { outOfMemory() + } catch (notAPlayer: UncivShowableException) { + val (message) = LoadGameScreen.getLoadExceptionMessage(notAPlayer) + launchOnGLThread { + loadingPopup.close() + ToastPopup(message, screen) + } } catch (ex: Exception) { Log.error("Could not autoload game", ex) val (message) = LoadGameScreen.getLoadExceptionMessage(ex) diff --git a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt index 94ad04b04a..e857c65f99 100644 --- a/core/src/com/unciv/ui/worldscreen/WorldScreen.kt +++ b/core/src/com/unciv/ui/worldscreen/WorldScreen.kt @@ -15,6 +15,7 @@ import com.unciv.Constants import com.unciv.MainMenuScreen import com.unciv.UncivGame import com.unciv.logic.GameInfo +import com.unciv.logic.UncivShowableException import com.unciv.logic.civilization.CivilizationInfo import com.unciv.logic.civilization.ReligionState import com.unciv.logic.civilization.diplomacy.DiplomaticStatus @@ -855,6 +856,13 @@ private fun startNewScreenJob(gameInfo: GameInfo, autosaveDisabled:Boolean = fal Concurrency.run { val newWorldScreen = try { UncivGame.Current.loadGame(gameInfo) + } catch (notAPlayer: UncivShowableException) { + withGLContext { + val (message) = LoadGameScreen.getLoadExceptionMessage(notAPlayer) + val mainMenu = UncivGame.Current.goToMainMenu() + ToastPopup(message, mainMenu) + } + return@run } catch (oom: OutOfMemoryError) { withGLContext { val mainMenu = UncivGame.Current.goToMainMenu()