Resolved #3626 - can rename multiplayer games now.

Also organized multiplayer screens better.
This commit is contained in:
Yair Morgenstern
2021-02-28 23:21:12 +02:00
parent 97546a7499
commit be7fd4cb01
4 changed files with 209 additions and 185 deletions

View File

@ -0,0 +1,55 @@
package com.unciv.ui
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.scenes.scene2d.ui.Table
import com.badlogic.gdx.scenes.scene2d.ui.TextButton
import com.badlogic.gdx.scenes.scene2d.ui.TextField
import com.unciv.logic.IdChecker
import com.unciv.models.translations.tr
import com.unciv.ui.pickerscreens.PickerScreen
import com.unciv.ui.utils.ToastPopup
import com.unciv.ui.utils.enable
import com.unciv.ui.utils.onClick
import com.unciv.ui.utils.toLabel
import java.util.*
class AddMultiplayerGameScreen(backScreen: MultiplayerScreen) : PickerScreen(){
init {
val gameNameTextField = TextField("", skin)
val gameIDTextField = TextField("", skin)
val pasteGameIDButton = TextButton("Paste gameID from clipboard", skin)
pasteGameIDButton.onClick {
gameIDTextField.text = Gdx.app.clipboard.contents
}
topTable.add("GameID".toLabel()).row()
val gameIDTable = Table()
gameIDTable.add(gameIDTextField).pad(10f).width(2*stage.width/3 - pasteGameIDButton.width)
gameIDTable.add(pasteGameIDButton)
topTable.add(gameIDTable).padBottom(30f).row()
topTable.add("Game name".toLabel()).row()
topTable.add(gameNameTextField).pad(10f).padBottom(30f).width(stage.width/2).row()
//CloseButton Setup
closeButton.setText("Back".tr())
closeButton.onClick {
backScreen.game.setScreen(backScreen)
}
//RightSideButton Setup
rightSideButton.setText("Save game".tr())
rightSideButton.enable()
rightSideButton.onClick {
try {
UUID.fromString(IdChecker.checkAndReturnGameUuid(gameIDTextField.text))
}catch (ex: Exception){
ToastPopup("Invalid game ID!", this)
return@onClick
}
backScreen.addMultiplayerGame(gameIDTextField.text.trim(), gameNameTextField.text.trim())
backScreen.game.setScreen(backScreen)
}
}
}

View File

@ -0,0 +1,132 @@
package com.unciv.ui
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.ui.TextField
import com.unciv.logic.GameInfo
import com.unciv.logic.GameSaver
import com.unciv.logic.civilization.PlayerType
import com.unciv.models.translations.tr
import com.unciv.ui.pickerscreens.PickerScreen
import com.unciv.ui.utils.*
import com.unciv.ui.worldscreen.mainmenu.OnlineMultiplayer
import kotlin.concurrent.thread
/** Subscreen of MultiplayerScreen to edit and delete saves
* backScreen is used for getting back to the MultiplayerScreen so it doesn't have to be created over and over again */
class EditMultiplayerGameInfoScreen(game: GameInfo?, gameName: String, backScreen: MultiplayerScreen): PickerScreen(){
init {
val textField = TextField(gameName, skin)
topTable.add("Rename".toLabel()).row()
topTable.add(textField).pad(10f).padBottom(30f).width(stage.width/2).row()
val deleteButton = "Delete save".toTextButton()
deleteButton.onClick {
val askPopup = YesNoPopup("Are you sure you want to delete this map?", {
backScreen.removeMultiplayerGame(game, gameName)
backScreen.game.setScreen(backScreen)
backScreen.reloadGameListUI()
}, this)
askPopup.open()
}.apply { color = Color.RED }
val giveUpButton = "Resign".toTextButton()
giveUpButton.onClick {
val askPopup = YesNoPopup("Are you sure you want to resign?", {
resign(game!!.gameId, gameName, backScreen)
}, this)
askPopup.open()
}
giveUpButton.apply { color = Color.RED }
topTable.add(deleteButton).pad(10f).row()
topTable.add(giveUpButton)
//CloseButton Setup
closeButton.setText("Back".tr())
closeButton.onClick {
backScreen.game.setScreen(backScreen)
}
//RightSideButton Setup
rightSideButton.setText("Save game".tr())
rightSideButton.enable()
rightSideButton.onClick {
rightSideButton.setText("Saving...".tr())
//remove the old game file
backScreen.removeMultiplayerGame(game, gameName)
//using addMultiplayerGame will download the game from Dropbox so the descriptionLabel displays the right things
backScreen.addMultiplayerGame(game!!.gameId, textField.text)
backScreen.game.setScreen(backScreen)
backScreen.reloadGameListUI()
}
if (game == null){
textField.isDisabled = true
textField.color = Color.GRAY
rightSideButton.disable()
giveUpButton.disable()
}
}
/**
* Helper function to decrease indentation
* Turns the current playerCiv into an AI civ and uploads the game afterwards.
*/
private fun resign(gameId: String, gameName: String, backScreen: MultiplayerScreen){
//Create a popup
val popup = Popup(this)
popup.addGoodSizedLabel("Working...").row()
popup.open()
thread {
try {
//download to work with newest game state
val gameInfo = OnlineMultiplayer().tryDownloadGame(gameId)
val playerCiv = gameInfo.currentPlayerCiv
//only give up if it's the users turn
//this ensures that no one can upload a newer game state while we try to give up
if (playerCiv.playerId == game.settings.userId) {
//Set own civ info to AI
playerCiv.playerType = PlayerType.AI
playerCiv.playerId = ""
//call next turn so turn gets simulated by AI
gameInfo.nextTurn()
//Add notification so everyone knows what happened
//call for every civ cause AI players are skipped anyway
for (civ in gameInfo.civilizations) {
civ.addNotification("[${playerCiv.civName}] resigned and is now controlled by AI", Color.RED)
}
//save game so multiplayer list stays up to date
GameSaver.saveGame(gameInfo, gameName, true)
OnlineMultiplayer().tryUploadGame(gameInfo)
Gdx.app.postRunnable {
popup.close()
//go back to the MultiplayerScreen
backScreen.game.setScreen(backScreen)
backScreen.reloadGameListUI()
}
} else {
Gdx.app.postRunnable {
//change popup text
popup.innerTable.clear()
popup.addGoodSizedLabel("You can only resign if it's your turn").row()
popup.addCloseButton()
}
}
} catch (ex: Exception) {
Gdx.app.postRunnable {
//change popup text
popup.innerTable.clear()
popup.addGoodSizedLabel("Could not upload game!").row()
popup.addCloseButton()
}
}
}
}
}

View File

@ -1,15 +1,12 @@
package com.unciv.ui
import com.unciv.ui.utils.AutoScrollPane as ScrollPane
import com.badlogic.gdx.Gdx
import com.badlogic.gdx.files.FileHandle
import com.badlogic.gdx.graphics.Color
import com.badlogic.gdx.scenes.scene2d.ui.*
import com.unciv.logic.GameInfo
import com.unciv.logic.GameSaver
import com.unciv.logic.IdChecker
import com.unciv.logic.UncivShowableException
import com.unciv.logic.civilization.PlayerType
import com.unciv.models.translations.tr
import com.unciv.ui.pickerscreens.PickerScreen
import com.unciv.ui.utils.*
@ -17,10 +14,12 @@ import com.unciv.ui.worldscreen.mainmenu.OnlineMultiplayer
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import kotlin.concurrent.thread
import com.unciv.ui.utils.AutoScrollPane as ScrollPane
class MultiplayerScreen(previousScreen: CameraStageBaseScreen) : PickerScreen() {
private lateinit var selectedGameFile: FileHandle
// Concurrent because we can get concurrent modification errors if we change things around while running redownloadAllGames() in another thread
private var multiplayerGames = ConcurrentHashMap<FileHandle, GameInfo>()
private val rightSideTable = Table()
@ -67,7 +66,7 @@ class MultiplayerScreen(previousScreen: CameraStageBaseScreen) : PickerScreen()
//TopTable Setup
//Have to put it into a separate Table to be able to add another copyGameID button
val mainTable = Table()
mainTable.add(ScrollPane(leftSideTable).apply { setScrollingDisabled(true, false) }).height(stage.height*2/3)
mainTable.add(ScrollPane(leftSideTable).apply { setScrollingDisabled(true, false) }).height(stage.height * 2 / 3)
mainTable.add(rightSideTable)
topTable.add(mainTable).row()
scrollPane.setScrollingDisabled(false, true)
@ -124,7 +123,7 @@ class MultiplayerScreen(previousScreen: CameraStageBaseScreen) : PickerScreen()
//Adds a new Multiplayer game to the List
//gameId must be nullable because clipboard content could be null
fun addMultiplayerGame(gameId: String?, gameName: String = ""){
fun addMultiplayerGame(gameId: String?, gameName: String = "") {
try {
//since the gameId is a String it can contain anything and has to be checked
UUID.fromString(IdChecker.checkAndReturnGameUuid(gameId!!))
@ -144,7 +143,7 @@ class MultiplayerScreen(previousScreen: CameraStageBaseScreen) : PickerScreen()
addGameButton.setText("Working...".tr())
addGameButton.disable()
thread(name="MultiplayerDownload") {
thread(name = "MultiplayerDownload") {
try {
// The tryDownload can take more than 500ms. Therefore, to avoid ANRs,
// we need to run it in a different thread.
@ -173,7 +172,7 @@ class MultiplayerScreen(previousScreen: CameraStageBaseScreen) : PickerScreen()
//just loads the game from savefile
//the game will be downloaded opon joining it anyway
private fun joinMultiplaerGame(){
private fun joinMultiplaerGame() {
try {
game.loadGame(multiplayerGames[selectedGameFile]!!)
} catch (ex: Exception) {
@ -185,7 +184,7 @@ class MultiplayerScreen(previousScreen: CameraStageBaseScreen) : PickerScreen()
}
}
private fun gameIsAlreadySavedAsMultiplayer(gameId: String) : Boolean{
private fun gameIsAlreadySavedAsMultiplayer(gameId: String): Boolean {
val games = multiplayerGames.filterValues { it.gameId == gameId }
return games.isNotEmpty()
}
@ -221,7 +220,7 @@ class MultiplayerScreen(previousScreen: CameraStageBaseScreen) : PickerScreen()
gameButton.onClick {
selectedGameFile = gameSaveFile
if (multiplayerGames[gameSaveFile] != null){
if (multiplayerGames[gameSaveFile] != null) {
copyGameIdButton.enable()
} else {
copyGameIdButton.disable()
@ -245,7 +244,7 @@ class MultiplayerScreen(previousScreen: CameraStageBaseScreen) : PickerScreen()
continue
}
thread (name = "loadGameFile") {
thread(name = "loadGameFile") {
try {
val game = gameSaver.loadGameFromFile(gameSaveFile)
@ -262,7 +261,7 @@ class MultiplayerScreen(previousScreen: CameraStageBaseScreen) : PickerScreen()
//set variable so it can be displayed when gameButton.onClick gets called
currentTurnUser = game.currentPlayer
}
}catch (usx: UncivShowableException){
} catch (usx: UncivShowableException) {
//Gets thrown when mods are not installed
Gdx.app.postRunnable {
val popup = Popup(this)
@ -273,7 +272,7 @@ class MultiplayerScreen(previousScreen: CameraStageBaseScreen) : PickerScreen()
turnIndicator.clear()
turnIndicator.add(ImageGetter.getImage("StatIcons/Malcontent")).size(50f)
}
}catch (ex: Exception){
} catch (ex: Exception) {
Gdx.app.postRunnable {
ToastPopup("Could not refresh!", this)
turnIndicator.clear()
@ -289,13 +288,13 @@ class MultiplayerScreen(previousScreen: CameraStageBaseScreen) : PickerScreen()
//redownload all games to update the list
//can maybe replaced when notification support gets introduced
private fun redownloadAllGames(){
private fun redownloadAllGames() {
addGameButton.disable()
refreshButton.setText("Working...".tr())
refreshButton.disable()
//One thread for all downloads
thread (name = "multiplayerGameDownload") {
thread(name = "multiplayerGameDownload") {
for (entry in multiplayerGames) {
try {
val game = OnlineMultiplayer().tryDownloadGame(entry.value.gameId)
@ -322,7 +321,7 @@ class MultiplayerScreen(previousScreen: CameraStageBaseScreen) : PickerScreen()
}
//Adds a Button to add the currently running game to multiplayerGameList
private fun addCurrentGameButton(){
private fun addCurrentGameButton() {
val currentlyRunningGame = game.gameInfo
if (!currentlyRunningGame.gameParameters.isOnlineMultiplayer || gameIsAlreadySavedAsMultiplayer(currentlyRunningGame.gameId))
return
@ -348,7 +347,7 @@ class MultiplayerScreen(previousScreen: CameraStageBaseScreen) : PickerScreen()
//It doesn't really unselect the game because selectedGame cant be null
//It just disables everything a selected game has set
private fun unselectGame(){
private fun unselectGame() {
editButton.disable()
copyGameIdButton.disable()
rightSideButton.disable()
@ -356,179 +355,16 @@ class MultiplayerScreen(previousScreen: CameraStageBaseScreen) : PickerScreen()
}
//check if its the users turn
private fun isUsersTurn(gameInfo: GameInfo) : Boolean{
return gameInfo.currentPlayerCiv.playerId == game.settings.userId
}
private fun isUsersTurn(gameInfo: GameInfo) = gameInfo.currentPlayerCiv.playerId == game.settings.userId
fun removeMultiplayerGame(gameInfo: GameInfo?, gameName: String){
fun removeMultiplayerGame(gameInfo: GameInfo?, gameName: String) {
val games = multiplayerGames.filterValues { it == gameInfo }.keys
try {
GameSaver.deleteSave(gameName, true)
if (games.isNotEmpty()){
multiplayerGames.remove(games.first())
}
}catch (ex: Exception) {
if (games.isNotEmpty()) multiplayerGames.remove(games.first())
} catch (ex: Exception) {
ToastPopup("Could not delete game!", this)
}
}
}
//Subscreen of MultiplayerScreen to edit and delete saves
//backScreen is used for getting back to the MultiplayerScreen so it doesn't have to be created over and over again
class EditMultiplayerGameInfoScreen(game: GameInfo?, gameName: String, backScreen: MultiplayerScreen): PickerScreen(){
init {
val textField = TextField(gameName, skin)
topTable.add("Rename".toLabel()).row()
topTable.add(textField).pad(10f).padBottom(30f).width(stage.width/2).row()
val deleteButton = "Delete save".toTextButton()
deleteButton.onClick {
val askPopup = YesNoPopup("Are you sure you want to delete this map?", {
backScreen.removeMultiplayerGame(game, gameName)
backScreen.game.setScreen(backScreen)
backScreen.reloadGameListUI()
}, this)
askPopup.open()
}.apply { color = Color.RED }
val giveUpButton = "Resign".toTextButton()
giveUpButton.onClick {
val askPopup = YesNoPopup("Are you sure you want to resign?", {
giveUp(game!!.gameId, gameName, backScreen)
}, this)
askPopup.open()
}
giveUpButton.apply { color = Color.RED }
topTable.add(deleteButton).pad(10f).row()
topTable.add(giveUpButton)
//CloseButton Setup
closeButton.setText("Back".tr())
closeButton.onClick {
backScreen.game.setScreen(backScreen)
}
//RightSideButton Setup
rightSideButton.setText("Save game".tr())
rightSideButton.onClick {
rightSideButton.setText("Saving...".tr())
//remove the old game file
backScreen.removeMultiplayerGame(game, gameName)
//using addMultiplayerGame will download the game from Dropbox so the descriptionLabel displays the right things
backScreen.addMultiplayerGame(game!!.gameId, textField.text)
backScreen.game.setScreen(backScreen)
backScreen.reloadGameListUI()
}
if (game == null){
textField.isDisabled = true
textField.color = Color.GRAY
rightSideButton.disable()
giveUpButton.disable()
}
}
/**
* Helper function to decrease indentation
* Turns the current playerCiv into an AI civ and uploads the game afterwards.
*/
private fun giveUp(gameId: String, gameName: String, backScreen: MultiplayerScreen){
//Create a popup
val popup = Popup(this)
popup.addGoodSizedLabel("Working...").row()
popup.open()
thread {
try {
//download to work with newest game state
val gameInfo = OnlineMultiplayer().tryDownloadGame(gameId)
val playerCiv = gameInfo.currentPlayerCiv
//only give up if it's the users turn
//this ensures that no one can upload a newer game state while we try to give up
if (playerCiv.playerId == game.settings.userId){
//Set own civ info to AI
playerCiv.playerType = PlayerType.AI
playerCiv.playerId = ""
//call next turn so turn gets simulated by AI
gameInfo.nextTurn()
//Add notification so everyone knows what happened
//call for every civ cause AI players are skipped anyway
for (civ in gameInfo.civilizations){
civ.addNotification("[${playerCiv.civName}] resigned and is now controlled by AI", Color.RED)
}
//save game so multiplayer list stays up to date
GameSaver.saveGame(gameInfo, gameName, true)
OnlineMultiplayer().tryUploadGame(gameInfo)
Gdx.app.postRunnable {
popup.close()
//go back to the MultiplayerScreen
backScreen.game.setScreen(backScreen)
backScreen.reloadGameListUI()
}
} else {
Gdx.app.postRunnable {
//change popup text
popup.innerTable.clear()
popup.addGoodSizedLabel("You can only resign if it's your turn").row()
popup.addCloseButton()
}
}
} catch (ex: Exception) {
Gdx.app.postRunnable {
//change popup text
popup.innerTable.clear()
popup.addGoodSizedLabel("Could not upload game!").row()
popup.addCloseButton()
}
}
}
}
}
class AddMultiplayerGameScreen(backScreen: MultiplayerScreen) : PickerScreen(){
init {
val gameNameTextField = TextField("", skin)
val gameIDTextField = TextField("", skin)
val pasteGameIDButton = TextButton("Paste gameID from clipboard", skin)
pasteGameIDButton.onClick {
gameIDTextField.text = Gdx.app.clipboard.contents
}
topTable.add("GameID".toLabel()).row()
val gameIDTable = Table()
gameIDTable.add(gameIDTextField).pad(10f).width(2*stage.width/3 - pasteGameIDButton.width)
gameIDTable.add(pasteGameIDButton)
topTable.add(gameIDTable).padBottom(30f).row()
topTable.add("Game name".toLabel()).row()
topTable.add(gameNameTextField).pad(10f).padBottom(30f).width(stage.width/2).row()
//CloseButton Setup
closeButton.setText("Back".tr())
closeButton.onClick {
backScreen.game.setScreen(backScreen)
}
//RightSideButton Setup
rightSideButton.setText("Save game".tr())
rightSideButton.enable()
rightSideButton.onClick {
try {
UUID.fromString(IdChecker.checkAndReturnGameUuid(gameIDTextField.text))
}catch (ex: Exception){
ToastPopup("Invalid game ID!", this)
return@onClick
}
backScreen.addMultiplayerGame(gameIDTextField.text.trim(), gameNameTextField.text.trim())
backScreen.game.setScreen(backScreen)
}
}
}
}

View File

@ -382,7 +382,8 @@ class WorldMapHolder(internal val worldScreen: WorldScreen, internal val tileMap
// The tile is within move range
tileToColor.showCircle(Color.BLUE, 0.3f)
}
if (unit.movement.canMoveTo(tile) || unit.movement.isUnknownTileWeShouldAssumeToBePassable(tile) && !unit.type.isAirUnit())
if (unit.movement.canMoveTo(tile) ||
unit.movement.isUnknownTileWeShouldAssumeToBePassable(tile) && !unit.type.isAirUnit())
tileToColor.showCircle(Color.WHITE,
if (UncivGame.Current.settings.singleTapMove || isAirUnit) 0.7f else 0.3f)
}