Character Structure
The Character
interface defines the core structure for all NPCs in the game, from simple merchants to complex companions.
Core Character Interface
interface Character {
name: string; // Unique identifier
displayName?: string; // Optional display name
allegiance: string | undefined; // Faction affiliation
bio: string; // Character description
condition: string; // When character appears
definitions: CharacterDefinition[]; // Realm-based definitions
relationship?: CharacterRelationshipDefinition[]; // Companion only
followInteraction?: FollowCharacterDefinition; // Party mechanics
portrait: string; // Portrait image path
image: string; // Full character image
imageScale?: number; // Image scaling factor
}
Character Definitions
Characters can have multiple definitions that activate based on conditions, typically realm progression:
type CharacterDefinition =
| NeutralCharacterDefinition
| EnemyCharacterDefinition
| CompanionCharacterDefinition;
Base Definition Properties
All character definitions share these core properties:
interface BaseCharacterDefinition {
kind: 'neutral' | 'enemy' | 'companion';
condition: string; // When this definition is active
realm: Realm; // Character's cultivation realm
realmProgress: RealmProgress; // Early/Middle/Late
stats: CharacterStats[]; // Combat statistics
locations: CharacterLocation[]; // Where to find character
encounters: CharacterEncounter[]; // Random events
breakthroughEncounter?: CharacterEncounter; // Special encounter
customInteractions?: CustomCharacterInteractionBlock[]; // Custom actions
}
Character Stats
Combat statistics for when the character is fought:
interface CharacterStats {
condition: string; // When these stats apply
stats: Omit<
EnemyEntity,
'name' | 'image' | 'imageScale' | 'realm' | 'realmProgress'
>;
}
The stats field uses the same structure as EnemyEntity
with these key properties:
interface EnemyEntity {
difficulty:
| 'veryeasy'
| 'easy'
| 'mediumEasy'
| 'medium'
| 'medium+'
| 'mediumhard'
| 'hard'
| 'hard+'
| 'veryhard'
| 'veryhard+'
| 'veryhard++'
| 'veryhard+++'
| 'veryhard++++';
battleLength:
| 'halfround'
| '1round'
| 'veryshort'
| 'short'
| 'medium'
| 'long'
| 'verylong'
| 'verylong+'
| 'verylong++'
| 'verylong+++'
| 'verylong++++';
stances: Stance[]; // Combat stances
stanceRotation: StanceRule[]; // Rotation logic
rotationOverrides: SingleStance[]; // Override conditions
drops: {
// Loot on defeat
item: Item;
amount: number;
chance: number;
condition?: string;
}[];
talismans?: ItemDesc[]; // Equipment
artefacts?: ItemDesc[];
affinities?: Partial<Record<TechniqueElement, number>>; // School affinities
// Optional properties
pillsPerRound?: number;
pills?: {
condition: string;
pill: CombatPillItem | ConcoctionItem | CombatItem;
}[];
qiDroplets?: number;
spawnCondition?: {
hpMult: number;
buffs: Buff[];
};
statMultipliers?: {
hp?: number;
power?: number;
};
}
Stance Structure
interface Stance {
name: string;
techniques: Technique[]; // Array of technique objects
}
// Stance rotation rules
type StanceRule = SingleStance | RandomStance;
interface SingleStance {
kind: 'single';
condition?: string;
stance: string; // Stance name to use
repeatable?: boolean;
alternatives?: StanceRule[];
}
interface RandomStance {
kind: 'random';
condition?: string;
stances: string[]; // Random stance names
repeatable?: boolean;
alternatives?: StanceRule[];
}
Location System
Characters move through the world using three location types:
Static Location
Character stays at one location:
{
kind: 'static',
condition: '1',
location: 'Nine Mountain Sect'
}
Wander Location
Character follows a set route:
{
kind: 'wander',
condition: '1',
route: [
{
location: 'Nine Mountain Sect',
duration: { min: 2, max: 4 } // Days at location
},
{
location: 'Shen Henda City',
duration: { min: 1, max: 2 }
}
]
}
Random Location
Character moves randomly between locations:
{
kind: 'random',
condition: '1',
locations: [
{
location: 'Crossroads',
duration: { min: 1, max: 3 }
},
{
location: 'Heian Forest',
duration: { min: 2, max: 5 }
}
]
}
Character Encounters
Random events that trigger when meeting the character:
interface CharacterEncounter {
id: string; // Unique encounter ID
condition: string; // When encounter can trigger
event: EventStep[]; // Event content
cooldown: { min: number; max: number }; // Days between triggers
locations?: string[]; // Specific locations only
}
Example encounter:
{
id: 'pi_lip_crafting_request',
condition: 'realm >= meridianOpening',
cooldown: { min: 10, max: 20 },
event: [
{
kind: 'text',
text: 'Pi Lip waves you over excitedly.'
},
{
kind: 'speech',
character: 'Pi Lip',
text: 'I need help gathering materials!'
}
// More event steps...
]
}
Complete Example
const myCharacter: Character = {
name: 'Scholar Wei',
allegiance: 'Nine Mountains',
bio: 'A dedicated researcher of ancient cultivation techniques...',
condition: '1', // Always available
portrait: 'assets/wei_portrait.png',
image: 'assets/wei_full.png',
definitions: [
{
kind: 'neutral',
condition: '1',
realm: 'meridianOpening',
realmProgress: 'Middle',
stats: [{
condition: '1',
stats: {
difficulty: 'medium',
battleLength: 'medium',
stances: [...],
drops: [
{ name: 'Spirit Stone', amount: 50 }
],
affinities: {
celestial: 60
}
}
}],
locations: [{
kind: 'static',
condition: '1',
location: 'Sect Library'
}],
encounters: [],
talkInteraction: [{
condition: '1',
event: [
{
kind: 'speech',
character: 'Scholar Wei',
text: 'Knowledge is the path to immortality.'
}
]
}],
shopInteraction: [{
condition: '1',
stock: {
meridianOpening: [
manualItem1,
manualItem2
]
},
costMultiplier: 1.5,
introSteps: [...],
exitSteps: [...]
}]
}
]
};
Registering Characters
Add characters to your mod:
window.modAPI.actions.addCharacter(myCharacter);