How Diplomatic Relations Work in Unciv: Inside the DiplomacyManager Architecture

Unciv models every bilateral relationship between civilizations using a DiplomacyManager class that tracks relationship levels, numeric opinion modifiers, and temporary turn-based flags to determine everything from trade access to war status.

In the open-source Civilization V clone yairm210/Unciv, diplomatic relations are not hardcoded states but dynamic data structures that persist across game turns. Understanding how diplomatic relations work in Unciv requires examining the three-layer system that stores opinion scores, temporary agreements, and the derived relationship status visible to players.

The Core Architecture of Unciv Diplomatic Relations

Every Civilization object maintains a map of DiplomacyManager instances keyed by the other civilization's ID. When two civilizations first meet, the constructor at lines 80‑89 in DiplomacyManager.kt instantiates a reciprocal manager for both parties. Because the class implements IsPartOfGameInfoSerialization (line 64), all diplomatic data survives save-game serialization.

The Three Layers of Diplomatic State

The DiplomacyManager stores state across three distinct layers that collectively determine how diplomatic relations work in Unciv.

1. Relationship Levels (The Visible Status)

The RelationshipLevel enum (lines 34‑45) defines nine possible states ranging from Ally and Friend to War. The function relationshipLevel() (lines 378‑386) derives the current level by evaluating diplomatic modifiers, war status, and city-state influence thresholds. This enum drives the UI color coding and determines which diplomatic actions are available.

2. Diplomatic Modifiers (The Hidden Opinion Score)

Beneath the visible relationship level lies a mutable map diplomaticModifiers: HashMap<String, Float> that stores numeric opinion values. Positive modifiers improve relations; negative modifiers damage them. The method opinionOfOtherCiv() (lines 314‑320) sums these values to produce the raw score used by relationshipLevel().

Modifiers are manipulated through setModifier() (lines 666‑674), which is invoked by high-level actions such as signDeclarationOfFriendship() (lines 697‑710) and signDefensivePact() (lines 745‑757).

3. Temporal Flags (Expiring Agreements)

Temporary diplomatic states are stored in flagsCountdown: HashMap<String, Int>, which tracks turn-based expiration timers. The helper methods hasFlag() (lines 552‑553) and setFlag() (lines 554‑558) manage entries like DiplomacyFlags.DeclarationOfFriendship or DiplomacyFlags.Denunciation. These flags block or enable specific actions regardless of the underlying opinion score.

City-State Diplomacy and the Influence System

For city-states, the standard modifier system is replaced by an influence mechanic. The influence field (lines 226‑227) stores a numeric value that determines the relationship through relationshipIgnoreAfraid() (lines 390‑403). Unlike major civilizations, city-states categorize relations as Afraid, Neutral, or Friend based strictly on influence thresholds, bypassing the diplomatic modifiers map entirely.

Key Diplomatic Actions in Code

High-level diplomatic moves are methods on the DiplomacyManager that update all three state layers atomically:

  • declareWar() (lines 28‑30): Sets the war flag and triggers relationship recalculation
  • signDeclarationOfFriendship() (lines 697‑710): Adds a positive modifier and sets a 30-turn flag
  • signDefensivePact() (lines 745‑757): Creates a mutual defense obligation with an expiration timer

How the UI Interacts with Diplomatic Data

The player interface bridges the gap between internal state and visual feedback. In TechPolicyDiplomacyButtons.kt (lines 87‑89), the diplomacy button launches DiplomacyScreen.kt, which aggregates data from MajorCivDiplomacyTable.kt. This table queries the DiplomacyManager to render relationship labels and conditionally expose action buttons based on canDeclareWar() and similar guard methods.

Code Examples: Working with Diplomatic Relations

Below are practical patterns for interacting with the diplomatic system in Unciv's Kotlin source:

// 1. Initialize diplomacy when two civilizations first meet
val diplomacy = DiplomacyManager(civA, civB)  // Constructor at lines 80-89
civA.diplomacyManagers[civB.civID] = diplomacy
civB.diplomacyManagers[civA.civID] = diplomacy

// 2. Query the current relationship level for UI display
val level = civA.getDiplomacyManager(civB)!!.relationshipLevel()
// Returns RelationshipLevel enum: Ally, Friend, Neutral, War, etc.

// 3. Modify opinion through diplomatic actions
civA.getDiplomacyManager(civB)!!.addModifier(
    DiplomaticModifiers.OpenBorders,  // Defined in enum at lines 104-112
    10f                               // +10 opinion points
)

// 4. Check prerequisites before declaring war
if (civA.getDiplomacyManager(civB)!!.canDeclareWar()) {
    civA.getDiplomacyManager(civB)!!.declareWar()
}

// 5. Establish temporary friendship (sets flag and modifier)
civA.getDiplomacyManager(civB)!!.signDeclarationOfFriendship()

// 6. Influence mechanics for city-states
civA.getDiplomacyManager(civB)!!.addInfluence(15f)
val influenceScore = civA.getDiplomacyManager(civB)!!.influence

Summary

  • DiplomacyManager is the central class in core/src/com/unciv/logic/civilization/diplomacy/DiplomacyManager.kt that stores all bilateral data.
  • Three data layers drive relations: the RelationshipLevel enum (visible status), diplomaticModifiers (numeric opinion), and flagsCountdown (temporary agreements).
  • City-states use an influence field rather than modifiers to determine relationship levels through relationshipIgnoreAfraid().
  • Persistence is guaranteed by implementing IsPartOfGameInfoSerialization, ensuring diplomatic states survive save/load cycles.
  • UI integration occurs through DiplomacyScreen.kt and MajorCivDiplomacyTable.kt, which read manager state to display options and colors.

Frequently Asked Questions

What determines the relationship level between civilizations in Unciv?

The relationshipLevel() function evaluates the sum of diplomaticModifiers, checks active war status, and applies city-state influence thresholds to return a RelationshipLevel enum value ranging from Ally to War. This calculation occurs in DiplomacyManager.kt at lines 378‑386.

How does Unciv store temporary diplomatic agreements like peace treaties?

Temporary agreements are stored in the flagsCountdown map as DiplomacyFlags enum entries with integer turn counters. The setFlag() method schedules expiration, while hasFlag() checks active status, as implemented at lines 552‑558.

What is the difference between major civilization and city-state diplomacy in Unciv?

Major civilizations use the diplomaticModifiers map to accumulate opinion scores that determine relationship levels, while city-states rely on the influence float field. The relationshipIgnoreAfraid() method (lines 390‑403) handles city-state-specific logic that ignores standard modifiers.

Where is the diplomatic data saved in Unciv?

All diplomatic data persists through the DiplomacyManager class implementing IsPartOfGameInfoSerialization (line 64). Each Civilization object serializes its diplomacyManagers map, ensuring that modifiers, flags, and influence values are preserved in save files.

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 →