Documentation Index Fetch the complete documentation index at: https://civ7community.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
This document provides a deep technical analysis of the Civilization VII TypeScript Modding Tools, focusing on the internal architecture, implementation patterns, and relationship with the game’s data structures.
Documentation Structure
The TypeScript Modding Tools documentation is divided into three complementary parts:
Main Documentation - Covers general concepts, features, and limitations
This Technical Guide - Details the toolkit’s architecture and internal implementation
How-To Guides : A series of practical tutorials for common modding tasks:
Repository Structure
The modding toolkit is organized with a clear separation of concerns:
civ7-modding-tools/
├── src/
│ ├── builders/ # Builder classes for game entities
│ ├── constants/ # Game-defined constants and enums
│ ├── core/ # Core functionality
│ ├── nodes/ # XML node representations
│ └── utils/ # Utility functions
├── examples/ # Example implementations
├── assets/ # Sample assets for examples
├── build.ts # Main build script for examples
└── package.json # Project configuration
Core Architecture
The toolkit uses a layered architecture combining builder pattern, factory methods, and composable nodes:
Builders - High-level abstractions to create game entities
Nodes - XML node representations that map to database entries
Files - File generators that combine nodes into proper XML files
Mod - Top-level container that manages files and builds the mod
Mod Lifecycle
The typical lifecycle of a mod created with this toolkit:
Create a Mod instance
Define entities using builders
Bind related entities together
Add entities to the mod
Build the mod, generating all required files
Builder Implementation
All builder classes extend the BaseBuilder class which provides core functionality:
// Simplified BaseBuilder implementation
export class BaseBuilder {
constructor ( options : BaseBuilderOptions ) {
this . actionGroupBundle = options . actionGroupBundle ;
this . initialize ();
}
// Creates nodes and manages relationships
initialize () { }
// Binds related entities
bind ( entities : BaseBuilder []) {
// Implementation
return this ;
}
// Returns files to be added to the mod
getFiles () : ( XmlFile | ImportFile )[] {
// Implementation
return [];
}
}
CivilizationBuilder
The CivilizationBuilder is one of the most complex implementations:
export class CivilizationBuilder extends BaseBuilder {
civilization : CivilizationNode ;
civilizationTag : TypeNode ;
// Other properties...
initialize () {
// Create civilization node
this . civilization = new CivilizationNode ( this . options . civilization );
// Create type node
this . civilizationTag = new TypeNode ({
type: this . civilization . civilizationType ,
kind: KIND . CIVILIZATION
});
// Create trait nodes, city names, etc.
// ...
}
bind ( entities : BaseBuilder []) {
// Process each entity to establish relationships
entities . forEach ( entity => {
if ( entity instanceof UnitBuilder ) {
this . bindUnit ( entity );
} else if ( entity instanceof ConstructibleBuilder ) {
this . bindConstructible ( entity );
}
// Other bindings...
});
return this ;
}
// Helper methods for binding specific entity types
private bindUnit ( unit : UnitBuilder ) { /* Implementation */ }
private bindConstructible ( constructible : ConstructibleBuilder ) { /* Implementation */ }
// Other helpers...
getFiles () {
// Generate database file
const database = new DatabaseNode ({
types: [ this . civilizationTag ],
civilizations: [ this . civilization ],
// Other collections...
});
// Create civilization file
const civFile = new XmlFile ({
path: `/civilizations/ ${ this . civilization . civilizationType } .xml` ,
content: database . toXmlElement (),
// Other properties...
});
return [ civFile , /* Other files... */ ];
}
}
See all 56 lines
UnitBuilder
The UnitBuilder demonstrates how specialized builders are implemented:
export class UnitBuilder extends BaseBuilder {
unit : UnitNode ;
unitTag : TypeNode ;
// Other properties...
initialize () {
// Create unit node
this . unit = new UnitNode ( this . options . unit );
// Create type node
this . unitTag = new TypeNode ({
type: this . unit . unitType ,
kind: KIND . UNIT
});
// Create cost, stat, and other nodes
// ...
}
getFiles () {
// Generate database file
const database = new DatabaseNode ({
types: [ this . unitTag ],
units: [ this . unit ],
// Other collections...
});
// Create unit file
const unitFile = new XmlFile ({
path: `/units/ ${ this . unit . unitType } .xml` ,
content: database . toXmlElement (),
// Other properties...
});
return [ unitFile ];
}
}
Constants and Game Data Mapping
The constants in the toolkit are directly mapped from game files to TypeScript enums. These ensure type safety and help with autocomplete.
Constants Structure
Constants are organized into categories matching game systems:
// Example of UNIT_CLASS constants
export const UNIT_CLASS = {
RECON: 'RECON' ,
MELEE: 'MELEE' ,
RANGED: 'RANGED' ,
CAVALRY: 'CAVALRY' ,
SIEGE: 'SIEGE' ,
NAVAL_MELEE: 'NAVAL_MELEE' ,
NAVAL_RANGED: 'NAVAL_RANGED' ,
SUPPORT: 'SUPPORT' ,
CIVILIAN: 'CIVILIAN' ,
RECON_ABILITIES: 'RECON_ABILITIES' ,
// ...
} as const ;
Mapping to Game Data
To understand how constants are derived from game data, let’s compare with actual game files:
CONSTRUCTIBLE_TYPE_TAG - Maps to TypeTags in constructibles.xml:
// In constants/CONSTRUCTIBLE_TYPE_TAG.ts
export const CONSTRUCTIBLE_TYPE_TAG = {
AGELESS: 'AGELESS' ,
PRODUCTION: 'PRODUCTION' ,
FOOD: 'FOOD' ,
GOLD: 'GOLD' ,
SCIENCE: 'SCIENCE' ,
CULTURE: 'CULTURE' ,
HAPPINESS: 'HAPPINESS' ,
DIPLOMACY: 'DIPLOMACY' ,
RELIGION: 'RELIGION' ,
UNIQUE: 'UNIQUE' ,
// ...
} as const ;
{/*In constructibles.xml*/}
< TypeTags >
< Row Type = "BUILDING_FISHING_QUAY" Tag = "FOOD" />
< Row Type = "BUILDING_FISHING_QUAY" Tag = "WAREHOUSE" />
< Row Type = "BUILDING_FISHING_QUAY" Tag = "AGELESS" />
< Row Type = "BUILDING_FISHING_QUAY" Tag = "PERSISTENT" />
< Row Type = "BUILDING_FISHING_QUAY" Tag = "WATER" />
{/*...*/}
</ TypeTags >
EFFECT - Maps to modifier effects defined in the game:
// In constants/EFFECT.ts (simplified)
export const EFFECT = {
UNIT_ADJUST_COMBAT_STRENGTH: 'EFFECT_ADJUST_UNIT_COMBAT_STRENGTH' ,
UNIT_ADJUST_MOVEMENT: 'EFFECT_ADJUST_UNIT_MOVEMENT' ,
CITY_ADJUST_YIELD: 'EFFECT_ADJUST_CITY_YIELD' ,
// ...
} as const ;
ACTION_GROUP - Controls when files are loaded:
// In constants/ACTION_GROUP.ts
export const ACTION_GROUP = {
AGE_ANTIQUITY_CURRENT: 'AGE_ANTIQUITY' ,
AGE_EXPLORATION_CURRENT: 'AGE_EXPLORATION' ,
AGE_MODERN_CURRENT: 'AGE_MODERN' ,
// ...
} as const ;
Comprehensive Constants Coverage
The toolkit includes constants for:
Basic Game Elements : Units, buildings, districts, civilizations
Modifiers : Collections, effects, requirements
Yields : Production, food, gold, science, culture, etc.
Terrains and Features : Mountains, rivers, forests, etc.
Resources : Strategic and luxury resources
Action Groups : File loading and database actions
Node System Implementation
The node system represents the backbone of the XML generation process. Each node corresponds to a database entry or XML element.
Node Base Class
All nodes inherit from a base class that provides XML conversion:
export class BaseNode {
// Converts the node to an XML element
toXmlElement () : XmlElement {
// Implementation
}
}
DatabaseNode
The DatabaseNode represents a complete database XML file:
export class DatabaseNode extends BaseNode {
types : TypeNode [];
units : UnitNode [];
civilizations : CivilizationNode [];
// Other collections...
constructor ( options : DatabaseNodeOptions ) {
super ();
this . types = options . types || [];
this . units = options . units || [];
// Assign other collections...
}
toXmlElement () : XmlElement {
const database = new XmlElement ( 'Database' );
// Add types section if not empty
if ( this . types . length ) {
const types = new XmlElement ( 'Types' );
this . types . forEach ( type => {
types . addChild ( type . toXmlElement ());
});
database . addChild ( types );
}
// Add units section if not empty
if ( this . units . length ) {
const units = new XmlElement ( 'Units' );
this . units . forEach ( unit => {
units . addChild ( unit . toXmlElement ());
});
database . addChild ( units );
}
// Process other collections...
return database ;
}
}
Specialized Nodes
Each game entity has its own node type:
export class UnitNode extends BaseNode {
unitType : string ;
baseMoves : number ;
baseSightRange : number ;
// Other properties...
constructor ( options : UnitNodeOptions ) {
super ();
this . unitType = options . unitType ;
this . baseMoves = options . baseMoves ;
this . baseSightRange = options . baseSightRange ;
// Assign other properties...
}
toXmlElement () : XmlElement {
const element = new XmlElement ( 'Row' );
element . addAttribute ( 'UnitType' , this . unitType );
element . addAttribute ( 'BaseMoves' , this . baseMoves . toString ());
element . addAttribute ( 'BaseSightRange' , this . baseSightRange . toString ());
// Add other attributes...
return element ;
}
}
File Generation Process
The toolkit generates two primary types of files:
XmlFile - XML database files with game data
ImportFile - Asset files like icons or SQL scripts
XmlFile Implementation
export class XmlFile {
path : string ;
name : string ;
content : XmlElement ;
actionGroups : string [];
actionGroupActions : string [];
constructor ( options : XmlFileOptions ) {
this . path = options . path ;
this . name = options . name ;
this . content = options . content ;
this . actionGroups = options . actionGroups || [];
this . actionGroupActions = options . actionGroupActions || [];
}
// Returns the file content as a string
toString () : string {
return '<?xml version="1.0" encoding="utf-8"?> \n ' +
this . content . toString ();
}
// Generates action group XML entries for modinfo file
getActionGroupXml () : string {
// Implementation
}
}
ImportFile Implementation
export class ImportFile {
path : string ;
name : string ;
content : string | Buffer ;
actionGroups : string [];
actionGroupActions : string [];
constructor ( options : ImportFileOptions ) {
this . path = options . path ;
this . name = options . name ;
this . content = options . content ;
this . actionGroups = options . actionGroups || [];
this . actionGroupActions = options . actionGroupActions || [];
}
// Gets file content
getContent () : string | Buffer {
return this . content ;
}
// Generates action group XML entries for modinfo file
getActionGroupXml () : string {
// Implementation
}
}
Mod Class Implementation
The Mod class orchestrates the entire mod generation process:
export class Mod {
id : string ;
version : string ;
files : ( XmlFile | ImportFile )[];
constructor ( options : ModOptions ) {
this . id = options . id ;
this . version = options . version ;
this . files = [];
}
// Adds builders to the mod
add ( builders : BaseBuilder []) {
builders . forEach ( builder => {
this . addFiles ( builder . getFiles ());
});
return this ;
}
// Adds files directly to the mod
addFiles ( files : ( XmlFile | ImportFile )[]) {
this . files . push ( ... files );
return this ;
}
// Builds the mod, writing all files to disk
build ( outputPath : string ) {
// Create mod directory structure
// Generate modinfo file
// Write all files
// Implementation...
}
// Generates the modinfo file
private generateModinfo () : string {
// Implementation
}
// Helper methods...
}
See all 40 lines
Technical Case Study: Creating a Civilization
To illustrate the complete process, let’s walk through the creation of a civilization with the toolkit:
1. Builder Initialization
const civilization = new CivilizationBuilder ({
actionGroupBundle: ACTION_GROUP_BUNDLE . AGE_ANTIQUITY ,
civilization: {
domain: 'AntiquityAgeCivilizations' ,
civilizationType: 'CIVILIZATION_DACIA'
},
civilizationTraits: [
TRAIT . ANTIQUITY_CIV ,
TRAIT . ATTRIBUTE_MILITARISTIC ,
],
// Other properties...
});
2. Node Creation (Inside Initialize)
initialize () {
// Create civilization node
this . civilization = new CivilizationNode ({
domain: 'AntiquityAgeCivilizations' ,
civilizationType: 'CIVILIZATION_DACIA'
});
// Create type node
this . civilizationTag = new TypeNode ({
type: 'CIVILIZATION_DACIA' ,
kind: KIND . CIVILIZATION
});
// Create trait nodes
this . civilizationTraits = [
new CivilizationTraitNode ({
civilizationType: 'CIVILIZATION_DACIA' ,
traitType: TRAIT . ANTIQUITY_CIV
}),
new CivilizationTraitNode ({
civilizationType: 'CIVILIZATION_DACIA' ,
traitType: TRAIT . ATTRIBUTE_MILITARISTIC
})
];
// Other nodes...
}
bind ( entities : BaseBuilder []) {
entities . forEach ( entity => {
if ( entity instanceof ModifierBuilder ) {
// Add modifier to civilization
this . modifiers . push ( entity . modifier );
// Add modifier to list of files
this . modifierFiles . push ( entity . getFiles ()[ 0 ]);
} else if ( entity instanceof UnitBuilder ) {
// Set unit as a civilization unit
entity . unit . civilizationType = this . civilization . civilizationType ;
// Add unit to list of files
this . unitFiles . push ( entity . getFiles ()[ 0 ]);
}
// Other bindings...
});
return this ;
}
4. File Generation
getFiles () {
// Create database node
const database = new DatabaseNode ({
types: [ this . civilizationTag ],
civilizations: [ this . civilization ],
civilizationTraits: this . civilizationTraits ,
// Other collections...
});
// Create civilization file
const civFile = new XmlFile ({
path: `/civilizations/ ${ this . civilization . civilizationType } .xml` ,
name: ` ${ this . civilization . civilizationType } .xml` ,
content: database . toXmlElement (),
actionGroups: [ ACTION_GROUP . AGE_ANTIQUITY_CURRENT ],
actionGroupActions: [ ACTION_GROUP_ACTION . UPDATE_DATABASE ]
});
// Return all files
return [ civFile , ... this . modifierFiles , ... this . unitFiles , ... this . localizationFiles ];
}
5. Mod Building
// Add to mod
mod . add ([ civilization ]);
// Build the mod to output directory
mod . build ( './output-directory' );
6. Generated XML (Example)
<? xml version = "1.0" encoding = "utf-8" ?>
< Database >
< Types >
< Row Type = "CIVILIZATION_DACIA" Kind = "KIND_CIVILIZATION" />
</ Types >
< Civilizations >
< Row CivilizationType = "CIVILIZATION_DACIA" Domain = "AntiquityAgeCivilizations" />
</ Civilizations >
< CivilizationTraits >
< Row CivilizationType = "CIVILIZATION_DACIA" TraitType = "TRAIT_ANTIQUITY_CIV" />
< Row CivilizationType = "CIVILIZATION_DACIA" TraitType = "TRAIT_ATTRIBUTE_MILITARISTIC" />
</ CivilizationTraits >
{/*Other sections...*/}
</ Database >
The toolkit’s performance is highly optimized through several techniques:
Lazy Evaluation - Nodes are only converted to XML when needed
Reuse of String Constants - String constants are defined once and reused
Minimal Dependencies - No external dependencies for the core functionality
Efficient File I/O - Files are written in parallel when possible
Extension Points
The toolkit is designed to be extended through several mechanisms:
Creating Custom Builders - Extend BaseBuilder for specialized builders
Adding Custom Nodes - Create new node types for game elements
Extending Constants - Add new constants as game content expands
Custom File Types - Implement additional file types beyond XML and assets
Conclusion
The Civilization VII TypeScript Modding Tools represent a sophisticated implementation of the builder pattern, combined with strong typing and declarative APIs. By mapping closely to the game’s internal data structure, the toolkit provides a powerful abstraction that simplifies mod development while maintaining the flexibility that advanced modders expect.
The layered architecture, with builders at the top level and nodes at the core, creates a system that is both easy to use for simple mods and powerful enough for complex ones. The comprehensive constants system ensures that modders can leverage autocomplete and type checking to avoid errors.
For developers looking to extend the toolkit or understand its inner workings, this technical implementation guide provides the foundation needed to navigate the codebase and build upon it.