mirror of
https://github.com/yairm210/Unciv.git
synced 2025-01-10 23:37:31 +07:00
Multi-server preparations: Save server info in game, save server functionality in specific class (#9379)
* Save server info in game * Moved server functionality into server class * Fix Android multiplayer update
This commit is contained in:
parent
97769b89b6
commit
48bd416347
@ -29,7 +29,7 @@ import com.badlogic.gdx.backends.android.DefaultAndroidFiles
|
||||
import com.unciv.logic.GameInfo
|
||||
import com.unciv.logic.files.UncivFiles
|
||||
import com.unciv.logic.multiplayer.storage.FileStorageRateLimitReached
|
||||
import com.unciv.logic.multiplayer.storage.OnlineMultiplayerFiles
|
||||
import com.unciv.logic.multiplayer.storage.OnlineMultiplayerServer
|
||||
import com.unciv.models.metadata.GameSettingsMultiplayer
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.io.FileNotFoundException
|
||||
@ -307,7 +307,7 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
|
||||
|
||||
try {
|
||||
Log.d(LOG_TAG, "doWork download $gameId")
|
||||
val gamePreview = OnlineMultiplayerFiles(fileStorage, mapOf("Authorization" to authHeader)).tryDownloadGamePreview(gameId)
|
||||
val gamePreview = OnlineMultiplayerServer(fileStorage, mapOf("Authorization" to authHeader)).tryDownloadGamePreview(gameId)
|
||||
Log.d(LOG_TAG, "doWork download $gameId done")
|
||||
val currentTurnPlayer = gamePreview.getCivilization(gamePreview.currentPlayer)
|
||||
|
||||
|
@ -182,7 +182,7 @@ open class UncivGame(val isConsoleMode: Boolean = false) : Game(), PlatformSpeci
|
||||
Concurrency.run {
|
||||
// Check if the server is available in case the feature set has changed
|
||||
try {
|
||||
onlineMultiplayer.checkServerStatus()
|
||||
onlineMultiplayer.multiplayerServer.checkServerStatus()
|
||||
} catch (ex: Exception) {
|
||||
debug("Couldn't connect to server: " + ex.message)
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package com.unciv.logic.multiplayer
|
||||
import com.badlogic.gdx.files.FileHandle
|
||||
import com.unciv.Constants
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.json.json
|
||||
import com.unciv.logic.GameInfo
|
||||
import com.unciv.logic.GameInfoPreview
|
||||
import com.unciv.logic.civilization.NotificationCategory
|
||||
@ -12,15 +11,13 @@ import com.unciv.logic.event.EventBus
|
||||
import com.unciv.logic.multiplayer.storage.FileStorageRateLimitReached
|
||||
import com.unciv.logic.multiplayer.storage.MultiplayerAuthException
|
||||
import com.unciv.logic.multiplayer.storage.MultiplayerFileNotFoundException
|
||||
import com.unciv.logic.multiplayer.storage.OnlineMultiplayerFiles
|
||||
import com.unciv.logic.multiplayer.storage.OnlineMultiplayerServer
|
||||
import com.unciv.ui.components.extensions.isLargerThan
|
||||
import com.unciv.logic.multiplayer.storage.SimpleHttp
|
||||
import com.unciv.utils.Log
|
||||
import com.unciv.utils.Concurrency
|
||||
import com.unciv.utils.Dispatcher
|
||||
import com.unciv.utils.debug
|
||||
import com.unciv.utils.launchOnThreadPool
|
||||
import com.unciv.utils.withGLContext
|
||||
import com.unciv.utils.debug
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
@ -45,8 +42,7 @@ private val FILE_UPDATE_THROTTLE_PERIOD = Duration.ofSeconds(60)
|
||||
*/
|
||||
class OnlineMultiplayer {
|
||||
private val files = UncivGame.Current.files
|
||||
private val multiplayerFiles = OnlineMultiplayerFiles()
|
||||
private var featureSet = ServerFeatureSet()
|
||||
val multiplayerServer = OnlineMultiplayerServer()
|
||||
|
||||
private val savedGames: MutableMap<FileHandle, OnlineMultiplayerGame> = Collections.synchronizedMap(mutableMapOf())
|
||||
|
||||
@ -55,7 +51,6 @@ class OnlineMultiplayer {
|
||||
private val lastCurGameRefresh: AtomicReference<Instant?> = AtomicReference()
|
||||
|
||||
val games: Set<OnlineMultiplayerGame> get() = savedGames.values.toSet()
|
||||
val serverFeatureSet: ServerFeatureSet get() = featureSet
|
||||
|
||||
init {
|
||||
flow<Unit> {
|
||||
@ -125,7 +120,7 @@ class OnlineMultiplayer {
|
||||
* @throws FileStorageRateLimitReached if the file storage backend can't handle any additional actions for a time
|
||||
*/
|
||||
suspend fun createGame(newGame: GameInfo) {
|
||||
multiplayerFiles.tryUploadGame(newGame, withPreview = true)
|
||||
multiplayerServer.tryUploadGame(newGame, withPreview = true)
|
||||
addGame(newGame)
|
||||
}
|
||||
|
||||
@ -139,11 +134,12 @@ class OnlineMultiplayer {
|
||||
*/
|
||||
suspend fun addGame(gameId: String, gameName: String? = null) {
|
||||
val saveFileName = if (gameName.isNullOrBlank()) gameId else gameName
|
||||
val gamePreview: GameInfoPreview = try {
|
||||
multiplayerFiles.tryDownloadGamePreview(gameId)
|
||||
var gamePreview: GameInfoPreview
|
||||
try {
|
||||
gamePreview = multiplayerServer.tryDownloadGamePreview(gameId)
|
||||
} catch (ex: MultiplayerFileNotFoundException) {
|
||||
// Game is so old that a preview could not be found on dropbox lets try the real gameInfo instead
|
||||
multiplayerFiles.tryDownloadGame(gameId).asPreview()
|
||||
gamePreview = multiplayerServer.tryDownloadGame(gameId).asPreview()
|
||||
}
|
||||
addGame(gamePreview, saveFileName)
|
||||
}
|
||||
@ -189,7 +185,7 @@ class OnlineMultiplayer {
|
||||
suspend fun resign(game: OnlineMultiplayerGame): Boolean {
|
||||
val preview = game.preview ?: throw game.error!!
|
||||
// download to work with the latest game state
|
||||
val gameInfo = multiplayerFiles.tryDownloadGame(preview.gameId)
|
||||
val gameInfo = multiplayerServer.tryDownloadGame(preview.gameId)
|
||||
val playerCiv = gameInfo.getCurrentPlayerCivilization()
|
||||
|
||||
if (!gameInfo.isUsersTurn()) {
|
||||
@ -212,7 +208,7 @@ class OnlineMultiplayer {
|
||||
|
||||
val newPreview = gameInfo.asPreview()
|
||||
files.saveGame(newPreview, game.fileHandle)
|
||||
multiplayerFiles.tryUploadGame(gameInfo, withPreview = true)
|
||||
multiplayerServer.tryUploadGame(gameInfo, withPreview = true)
|
||||
game.doManualUpdate(newPreview)
|
||||
return true
|
||||
}
|
||||
@ -248,7 +244,7 @@ class OnlineMultiplayer {
|
||||
*/
|
||||
suspend fun loadGame(gameInfo: GameInfo) = coroutineScope {
|
||||
val gameId = gameInfo.gameId
|
||||
val preview = multiplayerFiles.tryDownloadGamePreview(gameId)
|
||||
val preview = multiplayerServer.tryDownloadGamePreview(gameId)
|
||||
if (hasLatestGameState(gameInfo, preview)) {
|
||||
gameInfo.isUpToDate = true
|
||||
UncivGame.Current.loadGame(gameInfo)
|
||||
@ -262,7 +258,7 @@ class OnlineMultiplayer {
|
||||
* @throws MultiplayerFileNotFoundException if the file can't be found
|
||||
*/
|
||||
suspend fun downloadGame(gameId: String): GameInfo {
|
||||
val latestGame = multiplayerFiles.tryDownloadGame(gameId)
|
||||
val latestGame = multiplayerServer.tryDownloadGame(gameId)
|
||||
latestGame.isUpToDate = true
|
||||
return latestGame
|
||||
}
|
||||
@ -311,7 +307,7 @@ class OnlineMultiplayer {
|
||||
*/
|
||||
suspend fun updateGame(gameInfo: GameInfo) {
|
||||
debug("Updating remote game %s", gameInfo.gameId)
|
||||
multiplayerFiles.tryUploadGame(gameInfo, withPreview = true)
|
||||
multiplayerServer.tryUploadGame(gameInfo, withPreview = true)
|
||||
val game = getGameByGameId(gameInfo.gameId)
|
||||
debug("Existing OnlineMultiplayerGame: %s", game)
|
||||
if (game == null) {
|
||||
@ -330,66 +326,6 @@ class OnlineMultiplayer {
|
||||
&& gameInfo.turns == preview.turns
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the server is alive and sets the [serverFeatureSet] accordingly.
|
||||
* @return true if the server is alive, false otherwise
|
||||
*/
|
||||
fun checkServerStatus(): Boolean {
|
||||
var statusOk = false
|
||||
SimpleHttp.sendGetRequest("${UncivGame.Current.settings.multiplayer.server}/isalive") { success, result, _ ->
|
||||
statusOk = success
|
||||
if (result.isNotEmpty()) {
|
||||
featureSet = try {
|
||||
json().fromJson(ServerFeatureSet::class.java, result)
|
||||
} catch (ex: Exception) {
|
||||
Log.error("${UncivGame.Current.settings.multiplayer.server} does not support server feature set", ex)
|
||||
ServerFeatureSet()
|
||||
}
|
||||
}
|
||||
}
|
||||
return statusOk
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the authentication was successful or the server does not support authentication.
|
||||
* @throws FileStorageRateLimitReached if the file storage backend can't handle any additional actions for a time
|
||||
* @throws MultiplayerAuthException if the authentication failed
|
||||
*/
|
||||
fun authenticate(password: String?): Boolean {
|
||||
if (featureSet.authVersion == 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
val settings = UncivGame.Current.settings.multiplayer
|
||||
|
||||
val success = multiplayerFiles.fileStorage().authenticate(
|
||||
userId=settings.userId,
|
||||
password=password ?: settings.passwords[settings.server] ?: ""
|
||||
)
|
||||
if (password != null && success) {
|
||||
settings.passwords[settings.server] = password
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if setting the password was successful, false otherwise.
|
||||
* @throws FileStorageRateLimitReached if the file storage backend can't handle any additional actions for a time
|
||||
* @throws MultiplayerAuthException if the authentication failed
|
||||
*/
|
||||
fun setPassword(password: String): Boolean {
|
||||
if (
|
||||
featureSet.authVersion > 0 &&
|
||||
multiplayerFiles.fileStorage().setPassword(newPassword = password)
|
||||
) {
|
||||
val settings = UncivGame.Current.settings.multiplayer
|
||||
settings.passwords[settings.server] = password
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if [preview1] has a more recent game state than [preview2]
|
||||
|
@ -8,11 +8,11 @@ import com.unciv.logic.multiplayer.GameUpdateResult.Type.CHANGED
|
||||
import com.unciv.logic.multiplayer.GameUpdateResult.Type.FAILURE
|
||||
import com.unciv.logic.multiplayer.GameUpdateResult.Type.UNCHANGED
|
||||
import com.unciv.logic.multiplayer.storage.FileStorageRateLimitReached
|
||||
import com.unciv.logic.multiplayer.storage.OnlineMultiplayerFiles
|
||||
import com.unciv.logic.multiplayer.storage.OnlineMultiplayerServer
|
||||
import com.unciv.ui.components.extensions.isLargerThan
|
||||
import com.unciv.utils.debug
|
||||
import com.unciv.utils.launchOnGLThread
|
||||
import com.unciv.utils.withGLContext
|
||||
import com.unciv.utils.debug
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
@ -65,7 +65,7 @@ class OnlineMultiplayerGame(
|
||||
* Fires: [MultiplayerGameUpdateStarted], [MultiplayerGameUpdated], [MultiplayerGameUpdateUnchanged], [MultiplayerGameUpdateFailed]
|
||||
*
|
||||
* @throws FileStorageRateLimitReached if the file storage backend can't handle any additional actions for a time
|
||||
* @throws MultiplayerFileNotFoundException if the file can't be found
|
||||
* @throws MultiplayerFileNotFoundException if the file can't be found
|
||||
*/
|
||||
suspend fun requestUpdate(forceUpdate: Boolean = false) = coroutineScope {
|
||||
val onUnchanged = { GameUpdateResult(UNCHANGED, preview!!) }
|
||||
@ -106,7 +106,8 @@ class OnlineMultiplayerGame(
|
||||
|
||||
private suspend fun update(): GameUpdateResult {
|
||||
val curPreview = if (preview != null) preview!! else loadPreviewFromFile()
|
||||
val newPreview = OnlineMultiplayerFiles().tryDownloadGamePreview(curPreview.gameId)
|
||||
val serverIdentifier = curPreview.gameParameters.multiplayerServerUrl
|
||||
val newPreview = OnlineMultiplayerServer(serverIdentifier).tryDownloadGamePreview(curPreview.gameId)
|
||||
if (newPreview.turns == curPreview.turns && newPreview.currentPlayer == curPreview.currentPlayer) return GameUpdateResult(UNCHANGED, newPreview)
|
||||
UncivGame.Current.files.saveGame(newPreview, fileHandle)
|
||||
preview = newPreview
|
||||
|
@ -2,9 +2,12 @@ package com.unciv.logic.multiplayer.storage
|
||||
|
||||
import com.unciv.Constants
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.json.json
|
||||
import com.unciv.logic.GameInfo
|
||||
import com.unciv.logic.GameInfoPreview
|
||||
import com.unciv.logic.files.UncivFiles
|
||||
import com.unciv.logic.multiplayer.ServerFeatureSet
|
||||
import com.unciv.utils.Log
|
||||
|
||||
/**
|
||||
* Allows access to games stored on a server for multiplayer purposes.
|
||||
@ -17,12 +20,14 @@ import com.unciv.logic.files.UncivFiles
|
||||
* @see UncivGame.Current.settings.multiplayerServer
|
||||
*/
|
||||
@Suppress("RedundantSuspendModifier") // Methods can take a long time, so force users to use them in a coroutine to not get ANRs on Android
|
||||
class OnlineMultiplayerFiles(
|
||||
private var fileStorageIdentifier: String? = null,
|
||||
class OnlineMultiplayerServer(
|
||||
fileStorageIdentifier: String? = null,
|
||||
private var authenticationHeader: Map<String, String>? = null
|
||||
) {
|
||||
internal var featureSet = ServerFeatureSet()
|
||||
val serverUrl = fileStorageIdentifier ?: UncivGame.Current.settings.multiplayer.server
|
||||
|
||||
fun fileStorage(): FileStorage {
|
||||
val identifier = if (fileStorageIdentifier == null) UncivGame.Current.settings.multiplayer.server else fileStorageIdentifier
|
||||
val authHeader = if (authenticationHeader == null) {
|
||||
val settings = UncivGame.Current.settings.multiplayer
|
||||
mapOf("Authorization" to settings.getAuthHeader())
|
||||
@ -30,16 +35,73 @@ class OnlineMultiplayerFiles(
|
||||
authenticationHeader
|
||||
}
|
||||
|
||||
return if (identifier == Constants.dropboxMultiplayerServer) {
|
||||
return if (serverUrl == Constants.dropboxMultiplayerServer) {
|
||||
DropBox
|
||||
} else {
|
||||
UncivServerFileStorage.apply {
|
||||
serverUrl = identifier!!
|
||||
serverUrl = this@OnlineMultiplayerServer.serverUrl
|
||||
this.authHeader = authHeader
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the server is alive and sets the [serverFeatureSet] accordingly.
|
||||
* @return true if the server is alive, false otherwise
|
||||
*/
|
||||
fun checkServerStatus(): Boolean {
|
||||
var statusOk = false
|
||||
SimpleHttp.sendGetRequest("${serverUrl}/isalive") { success, result, _ ->
|
||||
statusOk = success
|
||||
if (result.isNotEmpty()) {
|
||||
featureSet = try {
|
||||
json().fromJson(ServerFeatureSet::class.java, result)
|
||||
} catch (ex: Exception) {
|
||||
Log.error("${UncivGame.Current.settings.multiplayer.server} does not support server feature set", ex)
|
||||
ServerFeatureSet()
|
||||
}
|
||||
}
|
||||
}
|
||||
return statusOk
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return true if the authentication was successful or the server does not support authentication.
|
||||
* @throws FileStorageRateLimitReached if the file storage backend can't handle any additional actions for a time
|
||||
* @throws MultiplayerAuthException if the authentication failed
|
||||
*/
|
||||
fun authenticate(password: String?): Boolean {
|
||||
if (featureSet.authVersion == 0) return true
|
||||
|
||||
val settings = UncivGame.Current.settings.multiplayer
|
||||
|
||||
val success = fileStorage().authenticate(
|
||||
userId=settings.userId,
|
||||
password=password ?: settings.passwords[settings.server] ?: ""
|
||||
)
|
||||
if (password != null && success) {
|
||||
settings.passwords[settings.server] = password
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if setting the password was successful, false otherwise.
|
||||
* @throws FileStorageRateLimitReached if the file storage backend can't handle any additional actions for a time
|
||||
* @throws MultiplayerAuthException if the authentication failed
|
||||
*/
|
||||
fun setPassword(password: String): Boolean {
|
||||
if (featureSet.authVersion > 0 && fileStorage().setPassword(newPassword = password)) {
|
||||
val settings = UncivGame.Current.settings.multiplayer
|
||||
settings.passwords[settings.server] = password
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @throws FileStorageRateLimitReached if the file storage backend can't handle any additional actions for a time
|
||||
* @throws MultiplayerAuthException if the authentication failed
|
||||
@ -81,7 +143,9 @@ class OnlineMultiplayerFiles(
|
||||
*/
|
||||
suspend fun tryDownloadGame(gameId: String): GameInfo {
|
||||
val zippedGameInfo = fileStorage().loadFileData(gameId)
|
||||
return UncivFiles.gameInfoFromString(zippedGameInfo)
|
||||
val gameInfo = UncivFiles.gameInfoFromString(zippedGameInfo)
|
||||
gameInfo.gameParameters.multiplayerServerUrl = UncivGame.Current.settings.multiplayer.server
|
||||
return gameInfo
|
||||
}
|
||||
|
||||
/**
|
@ -43,6 +43,7 @@ class GameParameters : IsPartOfGameInfoSerialization { // Default values are the
|
||||
var startingEra = "Ancient era"
|
||||
|
||||
var isOnlineMultiplayer = false
|
||||
var multiplayerServerUrl: String? = null
|
||||
var anyoneCanSpectate = true
|
||||
var baseRuleset: String = BaseRuleset.Civ_V_GnK.fullName
|
||||
var mods = LinkedHashSet<String>()
|
||||
@ -74,6 +75,7 @@ class GameParameters : IsPartOfGameInfoSerialization { // Default values are the
|
||||
parameters.victoryTypes = ArrayList(victoryTypes)
|
||||
parameters.startingEra = startingEra
|
||||
parameters.isOnlineMultiplayer = isOnlineMultiplayer
|
||||
parameters.multiplayerServerUrl = multiplayerServerUrl
|
||||
parameters.anyoneCanSpectate = anyoneCanSpectate
|
||||
parameters.baseRuleset = baseRuleset
|
||||
parameters.mods = LinkedHashSet(mods)
|
||||
|
@ -20,7 +20,7 @@ class AuthPopup(stage: Stage, authSuccessful: ((Boolean) -> Unit)? = null)
|
||||
|
||||
button.onClick {
|
||||
try {
|
||||
UncivGame.Current.onlineMultiplayer.authenticate(passwordField.text)
|
||||
UncivGame.Current.onlineMultiplayer.multiplayerServer.authenticate(passwordField.text)
|
||||
authSuccessful?.invoke(true)
|
||||
close()
|
||||
} catch (ex: Exception) {
|
||||
|
@ -192,7 +192,7 @@ private fun addMultiplayerServerOptions(
|
||||
}
|
||||
}).row()
|
||||
|
||||
if (UncivGame.Current.onlineMultiplayer.serverFeatureSet.authVersion > 0) {
|
||||
if (UncivGame.Current.onlineMultiplayer.multiplayerServer.featureSet.authVersion > 0) {
|
||||
val passwordTextField = UncivTextField.create(
|
||||
settings.multiplayer.passwords[settings.multiplayer.server] ?: "Password"
|
||||
)
|
||||
@ -255,11 +255,11 @@ private fun addTurnCheckerOptions(
|
||||
private fun successfullyConnectedToServer(action: (Boolean, Boolean) -> Unit) {
|
||||
Concurrency.run("TestIsAlive") {
|
||||
try {
|
||||
val connectionSuccess = UncivGame.Current.onlineMultiplayer.checkServerStatus()
|
||||
val connectionSuccess = UncivGame.Current.onlineMultiplayer.multiplayerServer.checkServerStatus()
|
||||
var authSuccess = false
|
||||
if (connectionSuccess) {
|
||||
try {
|
||||
authSuccess = UncivGame.Current.onlineMultiplayer.authenticate(null)
|
||||
authSuccess = UncivGame.Current.onlineMultiplayer.multiplayerServer.authenticate(null)
|
||||
} catch (_: Exception) {
|
||||
// We ignore the exception here, because we handle the failed auth onGLThread
|
||||
}
|
||||
@ -289,7 +289,7 @@ private fun setPassword(password: String, optionsPopup: OptionsPopup) {
|
||||
return
|
||||
}
|
||||
|
||||
if (UncivGame.Current.onlineMultiplayer.serverFeatureSet.authVersion == 0) {
|
||||
if (UncivGame.Current.onlineMultiplayer.multiplayerServer.featureSet.authVersion == 0) {
|
||||
popup.reuseWith("This server does not support authentication", true)
|
||||
return
|
||||
}
|
||||
@ -327,7 +327,7 @@ private fun setPassword(password: String, optionsPopup: OptionsPopup) {
|
||||
private fun successfullySetPassword(password: String, action: (Boolean, Exception?) -> Unit) {
|
||||
Concurrency.run("SetPassword") {
|
||||
try {
|
||||
val setSuccess = UncivGame.Current.onlineMultiplayer.setPassword(password)
|
||||
val setSuccess = UncivGame.Current.onlineMultiplayer.multiplayerServer.setPassword(password)
|
||||
launchOnGLThread {
|
||||
action(setSuccess, null)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user