How Unciv Multiplayer and Turn Synchronization Works

Unciv implements multiplayer through a dual-storage architecture where the Multiplayer class orchestrates between local MultiplayerFiles and remote MultiplayerServer, using throttled polling loops and event-driven UI updates to keep game states synchronized across devices.

Unciv is an open-source, turn-based strategy game inspired by Civilization V. Understanding Unciv multiplayer and turn synchronization requires examining how the game balances local state management with remote server interactions to ensure all players see consistent game states across different devices.

Core Architecture: The Dual-Storage Model

Unciv’s multiplayer system is built around two complementary data stores coordinated by a central orchestrator.

Local Storage with MultiplayerFiles

The MultiplayerFiles class (core/src/com/unciv/logic/multiplayer/MultiplayerFiles.kt) manages the offline side of the system. It stores local copies of saved games, tracks game previews, and provides filesystem helpers for quick access without network latency.

Remote Storage with MultiplayerServer

The MultiplayerServer class (core/src/com/unciv/logic/multiplayer/storage/MultiplayerServer.kt) handles the online side, abstracting remote storage backends such as Dropbox (storage/DropBox.kt) or custom servers (storage/UncivServerFileStorage.kt). It manages authentication, upload/download operations for full game files, and lightweight preview synchronization.

The Multiplayer Orchestrator

The Multiplayer class (core/src/com/unciv/logic/multiplayer/Multiplayer.kt) serves as the central coordinator. It maintains references to both local files and the remote server, providing the high-level API used by UI components and the game engine to create, load, and synchronize multiplayer games.

Game Lifecycle Operations

Creating Online Games

When starting a new multiplayer session, Multiplayer.createGame(newGame) uploads the full GameInfo object to the server, then immediately adds a local preview via MultiplayerFiles.addGame. This ensures the creator has immediate offline access while the server hosts the authoritative copy.

// In NewGameScreen.createGame()
if (gameSetupInfo.gameParameters.isOnlineMultiplayer) {
    // Verify server connection first
    if (!checkConnectionToMultiplayerServer()) return
    // Create the game locally and upload it
    game.onlineMultiplayer.createGame(newGame)   // ← Multiplayer.createGame
}

Source: NewGameScreen.kt – lines 161‑167

Loading and Updating Games

When entering a game, WorldScreen.loadLatestMultiplayerState first attempts to use the local copy stored in MultiplayerFiles. If the local state is out-of-date (detected via preview comparison), the system calls Multiplayer.downloadGame(gameId) to fetch the latest server version.

Resigning and Skipping Turns

Both player resignation and turn skipping follow a consistent pattern: download the latest server copy, modify the GameInfo, run AI automation if needed, then re-upload the updated state. The Multiplayer.resignPlayer and Multiplayer.skipCurrentPlayerTurn methods handle these flows.

suspend fun skipCurrentPlayerTurn(game: MultiplayerGamePreview,
                                 playerCivName: String,
                                 responsibleCivNameOrPlayerId: String): String? {
    // download latest version
    val gameInfo = multiplayerServer.tryDownloadGame(preview.gameId)
    // sanity checks …
    // run AI automation (if needed) and advance turn
    NextTurnAutomation.automateCivMoves(playerCiv, false)
    gameInfo.nextTurn()
    // upload updated state + preview
    multiplayerServer.uploadGame(gameInfo, withPreview = true)
    game.updatePreview(gameInfo.asPreview())
    return null
}

Source: Multiplayer.kt – lines 94‑132

Turn Synchronization Mechanism

When a player ends their turn, Unciv employs a multi-stage synchronization process to propagate state changes to all participants.

The Update Request Flow

  1. UI Trigger: The "Next Turn" button calls WorldScreen.game.onlineMultiplayer.requestUpdate() (see WorldScreen at line 199).
  2. Throttled Iteration: Multiplayer.requestUpdate iterates over every locally-known game preview, applying rate-limiting via the throttle helper (lines 29‑44).
  3. Preview Comparison: Each MultiplayerGamePreview.requestUpdate contacts the server via MultiplayerServer.downloadGamePreview and compares the remote preview with the local copy.

Out-of-Date Detection

The Multiplayer.hasLatestGameState method (lines 99‑103) checks if the currentPlayer and turns fields match between local and remote previews. When differences are detected, the UI displays an "update available" indicator and automatically invokes Multiplayer.downloadGame to pull the full GameInfo.

Background Polling Loop

The Multiplayer constructor launches a coroutine named multiplayerGameUpdater that runs indefinitely, polling the server every 500 milliseconds:

multiplayerGameUpdater = flow<Unit> {
    while (true) {
        delay(500)
        // ... fetch settings, current game, then throttle refreshes
        throttle(lastCurGameRefresh, multiplayerSettings.currentGameRefreshDelay) {
            currentGame.requestUpdate()
        }
        throttle(lastAllGamesRefresh, multiplayerSettings.allGameRefreshDelay) {
            requestUpdate(doNotUpdate = listOf(currentGame))
        }
    }
}.launchIn(CoroutineScope(Dispatcher.DAEMON))

Source: Multiplayer.kt – lines 61‑79

This background sync respects user-configurable delays (currentGameRefreshDelay, allGameRefreshDelay) and ensures all participants see current turn information even without manual refresh actions.

Event-Driven UI Architecture

Unciv uses an internal EventBus system to decouple network operations from UI updates. Key events defined in OnlineMultiplayerEvents.kt include:

  • MultiplayerGameUpdated
  • MultiplayerGameUpdateStarted
  • MultiplayerGameUpdateEnded
  • MultiplayerGameNameChanged

UI components such as MultiplayerStatusButton, MultiplayerStatusPopup, and MultiplayerScreen subscribe to these events. For example, MultiplayerStatusButton registers listeners at lines 52‑71 in MultiplayerStatusButton.kt, displaying loading spinners and updating tooltips when synchronization occurs.

Server Abstraction and Authentication

MultiplayerServer abstracts concrete storage implementations behind a unified interface. The server URL is retrieved from user settings (UncivGame.Current.settings.multiplayer.getServer()).

Authentication is performed on each request if the server advertises an authVersion (see MultiplayerServer.authenticate). Servers also advertise capabilities via ServerFeatureSet, enabling optional UI features such as chat functionality.

Summary

  • Dual-storage architecture: MultiplayerFiles handles local caching while MultiplayerServer manages remote storage via Dropbox or custom servers.
  • Central orchestration: The Multiplayer class coordinates between local and remote states, providing the primary API for game lifecycle operations.
  • Throttled synchronization: The throttle helper prevents rate-limit violations, while the multiplayerGameUpdater coroutine polls every 500 milliseconds for background sync.
  • Preview-based detection: Lightweight MultiplayerGamePreview objects enable quick comparison of turn numbers and current players before downloading full game states.
  • Event-driven updates: The EventBus system keeps UI components synchronized with network state changes without tight coupling.

Frequently Asked Questions

How does Unciv determine whose turn it is in a multiplayer game?

Unciv uses extension functions in Multiplayer.kt (lines 71‑73) to check if the current user is the active player. The isUsersTurn() function compares the currentPlayer civilization's playerId against the locally stored user ID from settings.

What prevents Unciv from overwhelming the server with update requests?

All network operations pass through a throttle helper (lines 29‑44 in Multiplayer.kt) that enforces rate limits based on configurable refresh delays. The background polling loop respects these limits while maintaining responsiveness.

Can Unciv operate with servers other than Dropbox?

Yes. While Dropbox is the default backend, MultiplayerServer abstracts storage through interfaces that allow custom server implementations via UncivServerFileStorage.kt. Users can configure custom server URLs in the game settings, and the system supports authentication and feature negotiation via ServerFeatureSet.

What happens when a player skips their turn in Unciv?

When skipping a turn via Multiplayer.skipCurrentPlayerTurn, the system downloads the latest server state, runs AI automation for the skipped civilization using NextTurnAutomation.automateCivMoves, advances the turn counter with gameInfo.nextTurn(), and re-uploads both the full game state and a lightweight preview to the server.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →