How the EspionageManager Works in Unciv: Spy Management and Intelligence Operations
The EspionageManager in Unciv orchestrates all spy operations for a civilization by maintaining an ArrayList<Spy> of operative agents, calculating visibility through enemy cities, filtering stealable technologies, and coordinating turn-based actions while delegating specific espionage behaviors to the Spy class state machine.
The espionage system in the open-source Civilization V clone Unciv relies on the EspionageManager class to coordinate covert operations between rival civilizations. Located in core/src/com/unciv/logic/civilization/managers/EspionageManager.kt, this manager works in tandem with the Spy class defined in core/src/com/unciv/models/Spy.kt and the city-level effects handler in CityEspionageManager.kt to handle everything from spy recruitment to tech theft. Understanding how the EspionageManager works reveals the architecture behind diplomatic subterfuge and intelligence gathering in the yairm210/Unciv repository.
Core Architecture of the EspionageManager
The EspionageManager serves as the central coordinator for all espionage activities within a civilization. It maintains an ArrayList<Spy> called spyList that tracks every operative under your control.
According to the Unciv source code, the manager delegates actual spy behavior to the Spy class, which implements a state machine for actions like establishing surveillance networks, stealing technology, inciting coups, and conducting counter-intelligence. The EspionageManager handles higher-level concerns such as visibility calculations, technology filtering, and UI state management, while CityEspionageManager.kt manages city-specific effects like sabotage.
Lifecycle and Initialization
Transient Setup and Civilization Linking
When a game loads, the EspionageManager receives its parent civilization reference through the setTransients method. This method propagates the civilization reference to every stored spy via Spy.setTransients, ensuring each spy knows which civilization it serves and can access game-wide data.
Spy Creation via addSpy()
The addSpy() method handles operative recruitment by generating a unique name through getSpyName(), determining the starting rank via getStartingSpyRank(), and instantiating a new Spy object. The new spy is registered in spyList and returned to the caller.
// Recruit a new spy for your civilization
val newSpy = civ.espionageManager.addSpy()
println("Created spy ${newSpy.name} with rank ${newSpy.rank}")
Visibility and Intelligence Operations
Calculating Spy Vision with getTilesVisibleViaSpies()
The manager determines which tiles are visible through espionage using getTilesVisibleViaSpies(). This method iterates over all spies in the "set-up" state, retrieves their assigned cities, and returns tiles within one-tile distance of each city center. This feeds directly into the fog-of-war system to reveal enemy territory on the world map.
// Reveal tiles visible through established spy networks
val visibleTiles = civ.espionageManager.getTilesVisibleViaSpies()
visibleTiles.forEach { tile ->
// Process revealed tile for map display
}
Tech Stealing Preparation
Before a spy can steal technology, the manager filters available targets through getTechsToSteal(otherCiv). This method examines the target civilization's researched technologies and returns only those your civilization has not yet discovered or cannot currently research. The Spy class uses this filtered list when executing StealingTech actions.
City and Location Queries
The EspionageManager provides several utility methods for locating spies:
getCitiesWithOurSpies(): Returns all cities currently hosting your spiesgetSpiesInCity(city): Retrieves all spies stationed in a specific citygetSpyAssignedToCity(city): Returns the specific spy assigned to a city, or null if none exists
// List all cities with active spy networks
val citiesWithSpies = civ.espionageManager.getCitiesWithOurSpies()
citiesWithSpies.forEach { city ->
val spy = civ.espionageManager.getSpyAssignedToCity(city)
println("Spy ${spy?.name} is operating in ${city.name}")
}
Turn Processing and State Management
The endTurn() Method
At the conclusion of each civilization's turn, endTurn() forwards the call to every spy in spyList. Each spy then advances its action countdowns and potentially triggers state changes, such as completing network establishment or beginning active tech theft. This delegation pattern keeps the manager focused on coordination while the Spy class handles individual operative logic.
// Advance all espionage activities at end of turn
civ.espionageManager.endTurn()
Cleanup on Civilization Destruction
When a civilization is defeated, removeAllSpies() executes to clean up operative networks. This method moves every spy to the hideout by setting their action to None, effectively removing them from foreign cities while preserving the spy objects in the list.
UI Integration and Player Feedback
The EspionageManager drives the espionage overview interface through shouldShowMoveSpies(). This method determines whether to display the "Move Spies" button by checking three conditions: whether the player has dismissed the hint (dismissedShouldMoveSpies), whether any spy is currently idle, and whether there are explored foreign cities lacking assigned spies.
The EspionageOverviewScreen.kt file queries these values alongside idle spy lists to render the appropriate buttons and operative lists.
// Check if the Move Spies button should be visible
if (civ.espionageManager.shouldShowMoveSpies()) {
// Enable the movement button in the UI
}
Summary
- The EspionageManager in
EspionageManager.ktmaintains thespyListand coordinates all civilization-level espionage operations - Spy creation occurs through
addSpy(), which generates unique names and starting ranks via helper methods - Vision calculations use
getTilesVisibleViaSpies()to reveal tiles within one range of established spy networks - Tech stealing relies on
getTechsToSteal()to filter targetable technologies from other civilizations - Turn processing delegates to individual
Spyobjects through theendTurn()method, advancing countdowns and state machines - UI visibility is controlled by
shouldShowMoveSpies(), which checks for idle spies and available target cities
Frequently Asked Questions
What file contains the EspionageManager class in Unciv?
The EspionageManager class is implemented in core/src/com/unciv/logic/civilization/managers/EspionageManager.kt. This file contains the complete manager logic for spy lifecycle, visibility calculations, and UI coordination.
How does a spy's visibility range work in Unciv?
The getTilesVisibleViaSpies() method in EspionageManager calculates visibility by iterating over all established spies and returning tiles within one-tile distance of each occupied city center. This is implemented in the manager rather than individual spies to aggregate vision across all operatives for the fog-of-war system.
What happens to spies when a civilization is defeated?
When a civilization is destroyed, the removeAllSpies() method moves all spies to the hideout by setting their action to None. This clears them from foreign cities while preserving the spy objects in the spyList.
How does the EspionageManager determine which technologies can be stolen?
The manager uses getTechsToSteal(otherCiv) to filter the target civilization's technologies, returning only those your civilization has not yet researched or cannot currently access. The Spy class then selects from this filtered list when executing theft actions.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →