mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-31 07:09:26 +07:00
* Handle subclassing of Events properly Previously, you could only listen to the exact class * Add relevant parent classes for the multiplayer events * Refactor: use the old name as the main name in MultiplayerGameNameChanged event * Add being able to stop listening to events in the EventBus * Add tests for EventBus * Refactor: Extract GameList into standalone file * Refactor: safeUpdateIf to more generic throttle function * Refactor: Extract multiplayer UI helper functions into separate file * Refactor: Extract load/download multiplayer game into logic class from UI * Make loading a multiplayer game automatically update the in-memory game in OnlineMultiplayer * Refactor: Extract multiplayer settings into separate object * Add multiplayer status display * Fix error with multiplayer games not correctly being cleaned up after successful update * Prevent loadLatestMultiplayerState() while next turn update is running * Show "Working..." while waiting for next turn calculations instead of "Waiting for [civ]..." * Fix race condition while updating online game state
This commit is contained in:
BIN
android/Images/OtherIcons/Loading.png
Normal file
BIN
android/Images/OtherIcons/Loading.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Before Width: | Height: | Size: 1009 KiB After Width: | Height: | Size: 1008 KiB |
@ -542,6 +542,7 @@ Username =
|
||||
Multiplayer =
|
||||
Could not download game! =
|
||||
Could not upload game! =
|
||||
Retry =
|
||||
Join game =
|
||||
Invalid game ID! =
|
||||
Copy user ID =
|
||||
@ -577,6 +578,7 @@ You can only resign if it's your turn =
|
||||
[civName] resigned and is now controlled by AI =
|
||||
Last refresh: [time] [timeUnit] ago =
|
||||
Current Turn: [civName] since [time] [timeUnit] ago =
|
||||
Seconds =
|
||||
Minutes =
|
||||
Hours =
|
||||
Days =
|
||||
@ -1346,7 +1348,7 @@ Choose name for [unitName] =
|
||||
# Multiplayer Turn Checker Service
|
||||
|
||||
Enable out-of-game turn notifications =
|
||||
Time between turn checks out-of-game (in minutes) =
|
||||
Out-of-game, update status of all games every: =
|
||||
Show persistent notification for turn notifier service =
|
||||
Take user ID from clipboard =
|
||||
Doing this will reset your current user ID to the clipboard contents - are you sure? =
|
||||
@ -1355,6 +1357,9 @@ Invalid ID! =
|
||||
|
||||
# Multiplayer options menu
|
||||
|
||||
Enable multiplayer status button in singleplayer games =
|
||||
Update status of currently played game every: =
|
||||
In-game, update status of all games every: =
|
||||
Server address =
|
||||
Reset to Dropbox =
|
||||
Check connection to server =
|
||||
|
@ -69,9 +69,10 @@ open class AndroidLauncher : AndroidApplication() {
|
||||
override fun onPause() {
|
||||
if (UncivGame.isCurrentInitialized()
|
||||
&& UncivGame.Current.isGameInfoInitialized()
|
||||
&& UncivGame.Current.settings.multiplayerTurnCheckerEnabled
|
||||
&& UncivGame.Current.settings.multiplayer.turnCheckerEnabled
|
||||
&& UncivGame.Current.gameSaver.getMultiplayerSaves().any()) {
|
||||
MultiplayerTurnCheckWorker.startTurnChecker(applicationContext, UncivGame.Current.gameSaver, UncivGame.Current.gameInfo, UncivGame.Current.settings)
|
||||
MultiplayerTurnCheckWorker.startTurnChecker(applicationContext, UncivGame.Current.gameSaver,
|
||||
UncivGame.Current.gameInfo, UncivGame.Current.settings.multiplayer)
|
||||
}
|
||||
super.onPause()
|
||||
}
|
||||
|
@ -24,11 +24,13 @@ import com.unciv.logic.GameSaver
|
||||
import com.unciv.logic.multiplayer.storage.FileStorageRateLimitReached
|
||||
import com.unciv.models.metadata.GameSettings
|
||||
import com.unciv.logic.multiplayer.storage.OnlineMultiplayerGameSaver
|
||||
import com.unciv.models.metadata.GameSettingsMultiplayer
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
import java.io.Writer
|
||||
import java.time.Duration
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@ -59,8 +61,7 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
|
||||
private const val PERSISTENT_NOTIFICATION_ENABLED = "PERSISTENT_NOTIFICATION_ENABLED"
|
||||
private const val FILE_STORAGE = "FILE_STORAGE"
|
||||
|
||||
fun enqueue(appContext: Context,
|
||||
delayInMinutes: Int, inputData: Data) {
|
||||
fun enqueue(appContext: Context, delay: Duration, inputData: Data) {
|
||||
|
||||
val constraints = Constraints.Builder()
|
||||
// If no internet is available, worker waits before becoming active.
|
||||
@ -69,7 +70,7 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
|
||||
|
||||
val checkTurnWork = OneTimeWorkRequestBuilder<MultiplayerTurnCheckWorker>()
|
||||
.setConstraints(constraints)
|
||||
.setInitialDelay(delayInMinutes.toLong(), TimeUnit.MINUTES)
|
||||
.setInitialDelay(delay.seconds, TimeUnit.SECONDS)
|
||||
.addTag(WORK_TAG)
|
||||
.setInputData(inputData)
|
||||
.build()
|
||||
@ -123,7 +124,7 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
|
||||
* The persistent notification is purely for informational reasons.
|
||||
* It is not technically necessary for the Worker, since it is not a Service.
|
||||
*/
|
||||
fun showPersistentNotification(appContext: Context, lastTimeChecked: String, checkPeriod: String) {
|
||||
fun showPersistentNotification(appContext: Context, lastTimeChecked: String, checkPeriod: Duration) {
|
||||
val flags = (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) FLAG_IMMUTABLE else 0) or
|
||||
FLAG_UPDATE_CURRENT
|
||||
val pendingIntent: PendingIntent =
|
||||
@ -136,7 +137,7 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
|
||||
.setContentTitle(appContext.resources.getString(R.string.Notify_Persist_Short) + " " + lastTimeChecked)
|
||||
.setStyle(NotificationCompat.BigTextStyle()
|
||||
.bigText(appContext.resources.getString(R.string.Notify_Persist_Long_P1) + " " +
|
||||
appContext.resources.getString(R.string.Notify_Persist_Long_P2) + " " + checkPeriod + " "
|
||||
appContext.resources.getString(R.string.Notify_Persist_Long_P2) + " " + checkPeriod.seconds / 60f + " "
|
||||
+ appContext.resources.getString(R.string.Notify_Persist_Long_P3)
|
||||
+ " " + appContext.resources.getString(R.string.Notify_Persist_Long_P4)))
|
||||
.setSmallIcon(R.drawable.uncivnotification)
|
||||
@ -180,7 +181,7 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
|
||||
}
|
||||
}
|
||||
|
||||
fun startTurnChecker(applicationContext: Context, gameSaver: GameSaver, currentGameInfo: GameInfo, settings: GameSettings) {
|
||||
fun startTurnChecker(applicationContext: Context, gameSaver: GameSaver, currentGameInfo: GameInfo, settings: GameSettingsMultiplayer) {
|
||||
Log.i(LOG_TAG, "startTurnChecker")
|
||||
val gameFiles = gameSaver.getMultiplayerSaves()
|
||||
val gameIds = Array(gameFiles.count()) {""}
|
||||
@ -211,17 +212,16 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
|
||||
}
|
||||
} else {
|
||||
val inputData = workDataOf(Pair(FAIL_COUNT, 0), Pair(GAME_ID, gameIds), Pair(GAME_NAME, gameNames),
|
||||
Pair(USER_ID, settings.userId), Pair(CONFIGURED_DELAY, settings.multiplayerTurnCheckerDelayInMinutes),
|
||||
Pair(PERSISTENT_NOTIFICATION_ENABLED, settings.multiplayerTurnCheckerPersistentNotificationEnabled),
|
||||
Pair(FILE_STORAGE, settings.multiplayerServer))
|
||||
Pair(USER_ID, settings.userId), Pair(CONFIGURED_DELAY, settings.turnCheckerDelay.seconds),
|
||||
Pair(PERSISTENT_NOTIFICATION_ENABLED, settings.turnCheckerPersistentNotificationEnabled),
|
||||
Pair(FILE_STORAGE, settings.server))
|
||||
|
||||
if (settings.multiplayerTurnCheckerPersistentNotificationEnabled) {
|
||||
showPersistentNotification(applicationContext,
|
||||
"—", settings.multiplayerTurnCheckerDelayInMinutes.toString())
|
||||
if (settings.turnCheckerPersistentNotificationEnabled) {
|
||||
showPersistentNotification(applicationContext, "—", settings.turnCheckerDelay)
|
||||
}
|
||||
Log.d(LOG_TAG, "startTurnChecker enqueue")
|
||||
// Initial check always happens after a minute, ignoring delay config. Better user experience this way.
|
||||
enqueue(applicationContext, 1, inputData)
|
||||
enqueue(applicationContext, Duration.ofMinutes(1), inputData)
|
||||
}
|
||||
}
|
||||
|
||||
@ -247,6 +247,11 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getConfiguredDelay(inputData: Data): Duration {
|
||||
val delay = inputData.getLong(CONFIGURED_DELAY, Duration.ofMinutes(5).seconds)
|
||||
return Duration.ofSeconds(delay)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -270,7 +275,7 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
|
||||
override fun doWork(): Result = runBlocking {
|
||||
Log.i(LOG_TAG, "doWork")
|
||||
val showPersistNotific = inputData.getBoolean(PERSISTENT_NOTIFICATION_ENABLED, true)
|
||||
val configuredDelay = inputData.getInt(CONFIGURED_DELAY, 5)
|
||||
val configuredDelay = getConfiguredDelay(inputData)
|
||||
val fileStorage = inputData.getString(FILE_STORAGE)
|
||||
|
||||
try {
|
||||
@ -350,13 +355,12 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
|
||||
}
|
||||
return@runBlocking Result.failure()
|
||||
} else {
|
||||
if (showPersistNotific) { showPersistentNotification(applicationContext,
|
||||
applicationContext.resources.getString(R.string.Notify_Error_Retrying), configuredDelay.toString()) }
|
||||
if (showPersistNotific) { showPersistentNotification(applicationContext, applicationContext.resources.getString(R.string.Notify_Error_Retrying), configuredDelay) }
|
||||
// If check fails, retry in one minute.
|
||||
// Makes sense, since checks only happen if Internet is available in principle.
|
||||
// Therefore a failure means either a problem with the GameInfo or with Dropbox.
|
||||
val inputDataFailIncrease = Data.Builder().putAll(inputData).putInt(FAIL_COUNT, failCount + 1).build()
|
||||
enqueue(applicationContext, 1, inputDataFailIncrease)
|
||||
enqueue(applicationContext, Duration.ofMinutes(1), inputDataFailIncrease)
|
||||
}
|
||||
} catch (outOfMemory: OutOfMemoryError){ // no point in trying multiple times if this was an oom error
|
||||
return@runBlocking Result.failure()
|
||||
@ -379,8 +383,7 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
|
||||
}
|
||||
val displayTime = "$hour:$minute"
|
||||
|
||||
showPersistentNotification(applicationContext, displayTime,
|
||||
inputData.getInt(CONFIGURED_DELAY, 5).toString())
|
||||
showPersistentNotification(applicationContext, displayTime, getConfiguredDelay(inputData))
|
||||
}
|
||||
|
||||
private fun showErrorNotification(stackTraceString: String) {
|
||||
|
Reference in New Issue
Block a user