diff --git a/core/src/com/unciv/logic/GameInfo.kt b/core/src/com/unciv/logic/GameInfo.kt index 011951d218..d2e9d4842f 100644 --- a/core/src/com/unciv/logic/GameInfo.kt +++ b/core/src/com/unciv/logic/GameInfo.kt @@ -787,6 +787,7 @@ class GameInfoPreview() { fun getCivilization(civName: String) = civilizations.first { it.civName == civName } fun getCurrentPlayerCiv() = getCivilization(currentPlayer) + fun getPlayerCiv(playerId: String) = civilizations.firstOrNull { it.playerId == playerId } } /** Class to use when parsing jsons if you only want the serialization [version]. */ diff --git a/core/src/com/unciv/logic/multiplayer/Multiplayer.kt b/core/src/com/unciv/logic/multiplayer/Multiplayer.kt index a366ab130f..801bf2cf5e 100644 --- a/core/src/com/unciv/logic/multiplayer/Multiplayer.kt +++ b/core/src/com/unciv/logic/multiplayer/Multiplayer.kt @@ -146,17 +146,16 @@ class Multiplayer { * @throws MultiplayerAuthException if the authentication failed * @return false if it's not the user's turn and thus resigning did not happen */ - suspend fun resign(game: MultiplayerGame): Boolean { + suspend fun resignCurrentPlayer(game: MultiplayerGame): Boolean { val preview = game.preview ?: throw game.error!! // download to work with the latest game state val gameInfo = multiplayerServer.tryDownloadGame(preview.gameId) + if (gameInfo.currentTurnStartTime != preview.currentTurnStartTime) + return false // Game was updated since we tried + val playerCiv = gameInfo.getCurrentPlayerCivilization() - if (!gameInfo.isUsersTurn()) { - return false - } - - //Set own civ info to AI + //Set civ info to AI playerCiv.playerType = PlayerType.AI playerCiv.playerId = "" diff --git a/core/src/com/unciv/ui/screens/multiplayerscreens/MultiplayerScreen.kt b/core/src/com/unciv/ui/screens/multiplayerscreens/MultiplayerScreen.kt index 1668529958..0acb2c33c7 100644 --- a/core/src/com/unciv/ui/screens/multiplayerscreens/MultiplayerScreen.kt +++ b/core/src/com/unciv/ui/screens/multiplayerscreens/MultiplayerScreen.kt @@ -10,6 +10,7 @@ import com.unciv.models.translations.tr import com.unciv.ui.components.widgets.UncivTextField import com.unciv.ui.components.extensions.disable import com.unciv.ui.components.extensions.enable +import com.unciv.ui.components.extensions.isEnabled import com.unciv.ui.components.extensions.toTextButton import com.unciv.ui.components.input.KeyCharAndCode import com.unciv.ui.components.input.keyShortcuts @@ -24,6 +25,8 @@ import com.unciv.ui.screens.savescreens.LoadGameScreen import com.unciv.utils.Concurrency import com.unciv.utils.Log import com.unciv.utils.launchOnGLThread +import java.time.Duration +import java.time.Instant import com.unciv.ui.components.widgets.AutoScrollPane as ScrollPane class MultiplayerScreen : PickerScreen() { @@ -31,8 +34,10 @@ class MultiplayerScreen : PickerScreen() { private val copyGameIdButton = createCopyGameIdButton() private val resignButton = createResignButton() + private val forceResignButton = createForceResignButton() private val deleteButton = createDeleteButton() private val renameButton = createRenameButton() + private val gameSpecificButtons = listOf(copyGameIdButton, resignButton, deleteButton, renameButton) private val addGameButton = createAddGameButton() @@ -51,7 +56,6 @@ class MultiplayerScreen : PickerScreen() { topTable.add(createMainContent()).row() setupHelpButton() - setupRightSideButton() game.onlineMultiplayer.requestUpdate() @@ -80,6 +84,7 @@ class MultiplayerScreen : PickerScreen() { gameSpecificActions.add(copyGameIdButton).row() gameSpecificActions.add(renameButton).row() gameSpecificActions.add(resignButton).row() + gameSpecificActions.add(forceResignButton).row() gameSpecificActions.add(deleteButton).row() table.add(gameSpecificActions) @@ -116,7 +121,23 @@ class MultiplayerScreen : PickerScreen() { "Are you sure you want to resign?", "Resign", ) { - resign(selectedGame!!) + resignCurrentPlayer(selectedGame!!) + } + askPopup.open() + } + return resignButton + } + + fun createForceResignButton(): TextButton { + val negativeButtonStyle = skin.get("negative", TextButton.TextButtonStyle::class.java) + val resignButton = "Force current player to resign".toTextButton(negativeButtonStyle).apply { isVisible = false } + resignButton.onClick { + val askPopup = ConfirmPopup( + this, + "Are you sure you want to force the current player to resign?", + "Yes", + ) { + resignCurrentPlayer(selectedGame!!) } askPopup.open() } @@ -124,10 +145,9 @@ class MultiplayerScreen : PickerScreen() { } /** - * Helper function to decrease indentation * Turns the current playerCiv into an AI civ and uploads the game afterwards. */ - private fun resign(multiplayerGame: MultiplayerGame) { + private fun resignCurrentPlayer(multiplayerGame: MultiplayerGame) { //Create a popup val popup = Popup(this) popup.addGoodSizedLabel(Constants.working).row() @@ -135,7 +155,7 @@ class MultiplayerScreen : PickerScreen() { Concurrency.runOnNonDaemonThreadPool("Resign") { try { - val resignSuccess = game.onlineMultiplayer.resign(multiplayerGame) + val resignSuccess = game.onlineMultiplayer.resignCurrentPlayer(multiplayerGame) launchOnGLThread { if (resignSuccess) { @@ -151,7 +171,7 @@ class MultiplayerScreen : PickerScreen() { if (ex is MultiplayerAuthException) { launchOnGLThread { AuthPopup(this@MultiplayerScreen) { success -> - if (success) resign(multiplayerGame) + if (success) resignCurrentPlayer(multiplayerGame) }.open(true) } return@runOnNonDaemonThreadPool @@ -281,6 +301,8 @@ class MultiplayerScreen : PickerScreen() { rightSideButton.disable() for (button in gameSpecificButtons) button.disable() + forceResignButton.isVisible = false + descriptionLabel.setText("") } @@ -301,10 +323,15 @@ class MultiplayerScreen : PickerScreen() { copyGameIdButton.disable() } - if (multiplayerGame.preview?.getCurrentPlayerCiv()?.playerId == game.settings.multiplayer.userId) { - resignButton.enable() + resignButton.isEnabled = multiplayerGame.preview?.getCurrentPlayerCiv()?.playerId == game.settings.multiplayer.userId + + if (resignButton.isEnabled || multiplayerGame.preview == null){ + forceResignButton.isVisible = false } else { - resignButton.disable() + val durationInactive = Duration.between(Instant.ofEpochMilli(multiplayerGame.preview!!.currentTurnStartTime), Instant.now()) + forceResignButton.isVisible = + multiplayerGame.preview?.getPlayerCiv(game.settings.multiplayer.userId)?.civName == Constants.spectator + || durationInactive > Duration.ofDays(2) } rightSideButton.enable()