ModAPI Reference

The ModAPI provides access to game data, content registration functions, and utility helpers for mod development.

Structure

The ModAPI is available globally as window.modAPI with four main sections:

interface ModAPI {
  gameData: {
    /* Access to all game content */
  };
  actions: {
    /* Functions to add new content */
  };
  utils: {
    /* Helper functions for mod development */
  };
  hooks: {
    /* Interceptors for game behavior */
  };
}

Game Data Access

Access existing game content through window.modAPI.gameData:

Core Collections

  • items - Record<string, Item> - All items in the game
  • characters - Record<string, Character> - All NPCs and characters
  • techniques - Record<string, Technique> - All cultivation techniques
  • locations - Record<string, GameLocation> - All game locations
  • quests - Record<string, Quest> - All available quests
  • manuals - Record<string, ManualItem> - All technique manuals
  • destinies - Record<string, Destiny> - All destiny definitions
  • calendarEvents - CalendarEvent[] - All registered calendar events
  • triggeredEvents - TriggeredEvent[] - All registered triggered events

Realm-Based Collections

  • auction - Record<Realm, AuctionItemDef[]> - Auction items by realm
  • breakthroughs - Record<Realm, Breakthrough[]> - Breakthrough requirements
  • crops - Record<Realm, Crop[]> - Crops available by realm
  • mineChambers - Record<Realm, Record<RealmProgress, MineChamber[]>> - Mine chambers by realm and progress
  • uncutStones - Record<Realm, UncutStonePool | undefined> - Uncut stone pools by realm

Specialized Collections

  • backgrounds - Character backgrounds by life stage:
    • birth: Background[]
    • child: Background[]
    • teen: Background[]
  • craftingTechniques - Record<string, CraftingTechnique> - All crafting techniques
  • techniqueBuffs - School-specific technique buffs:
    • blood, blossom, celestial, cloud, fist, weapon
  • guilds - Record<string, Guild> - All guilds
  • enchantments - Enchantment[] - All equipment enchantments
  • fallenStars - FallenStar[] - All fallen star events
  • rooms - Room[] - All house rooms
  • mysticalRegionBlessings - Blessing[] - All mystical region blessings
  • dualCultivationTechniques - IntimateTechnique[] - All dual cultivation techniques
  • monsters - EnemyEntity[] - All registered enemy entities
  • puppets - PuppetType[] - All training ground puppet types
  • alternativeStarts - AlternativeStart[] - All alternative game starts (first entry is always the default)
  • researchableMap - Record<string, RecipeItem[]> - Maps base item keys to researchable recipes
  • recipeConditionEffects - RecipeConditionEffect[] - All crafting condition effects
  • harmonyConfigs - Record<RecipeHarmonyType, HarmonyTypeConfig> - Harmony type configurations
  • itemTypeToHarmonyType - Record<ItemKind, RecipeHarmonyType> - Maps item kinds to harmony types
  • tutorials - Tutorial system data:
    • newGameTutorials: Tutorial[] — Base game tutorials played during a new game
    • tutorialTriggers: TriggeredEvent[] — Triggered events forming the opening sequence

Content Registration

Add new content through window.modAPI.actions:

Items and Equipment

window.modAPI.actions.addItem(item: Item)
window.modAPI.actions.addItemToShop(item, stacks, location, realm, valueModifier?, reputation?)
window.modAPI.actions.addItemToGuild(item, stacks, guild, rank, valueModifier?, reputation?)
window.modAPI.actions.addItemToAuction(item, chance, condition, countOverride?, countMultiplier?)
window.modAPI.actions.addItemToFallenStar(item, realm)
window.modAPI.actions.addToSectShop(item, stacks, realm, valueModifier?, reputation?)
  • addItemToGuild — Add an item to a guild’s rank shop. guild is the guild name, rank is the minimum rank required to purchase.
  • addItemToFallenStar — Add an item to the drop table for fallen stars of a given realm.
  • addToSectShop — Add an item to the Nine Mountain Sect’s Favour Exchange shop at the specified realm tier. Optionally apply a price multiplier and gate the item behind a reputation tier. Items without a reputation tier go into itemPool[realm]; items with a tier go into reputationPool[realm].
// Add item to sect shop at qiCondensation tier
window.modAPI.actions.addToSectShop(myItem, 3, 'qiCondensation');

// With price multiplier and reputation gate
window.modAPI.actions.addToSectShop(myRareItem, 1, 'coreFormation', 2.0, 'Honored');

Characters and Backgrounds

window.modAPI.actions.addCharacter(character: Character)
window.modAPI.actions.addBirthBackground(background: Background)
window.modAPI.actions.addChildBackground(background: Background)
window.modAPI.actions.addTeenBackground(background: Background)

Cultivation Content

window.modAPI.actions.addBreakthrough(realm: Realm, breakthrough: Breakthrough)
window.modAPI.actions.addTechnique(technique: Technique)
window.modAPI.actions.addManual(manual: ManualItem)
window.modAPI.actions.addCraftingTechnique(technique: CraftingTechnique)
window.modAPI.actions.addDestiny(destiny: Destiny)

Note on addTechnique: When technique.realm is set, the game automatically creates and registers a corresponding technique item (used in the debug inventory and item systems). If technique.realm is undefined, no item is created.

World Content

window.modAPI.actions.addLocation(location: GameLocation)
window.modAPI.actions.linkLocations(existing: string, link: ConditionalLink | ExplorationLink)
window.modAPI.actions.registerRootLocation(locationName: string, condition: string)
window.modAPI.actions.addQuest(quest: Quest)
window.modAPI.actions.addCalendarEvent(event: CalendarEvent)
window.modAPI.actions.addTriggeredEvent(event: TriggeredEvent)

registerRootLocation

Mark an existing location as a discovery root. Root locations are entry points for the map discovery system, all locations reachable from a root are discovered when its condition is met. Use this for secret areas or alternative starting points that are not connected to the main map.

// Always-active root
window.modAPI.actions.registerRootLocation('Hidden Valley', '1');

// Unlocks after a flag is set
window.modAPI.actions.registerRootLocation('Ancient Ruins', 'foundAncientMap == 1');

Modifying Existing Locations

Add content to locations that already exist in the game:

window.modAPI.actions.addBuildingsToLocation(location: string, buildings: LocationBuilding[])
window.modAPI.actions.addEnemiesToLocation(location: string, enemies: LocationEnemy[])
window.modAPI.actions.addEventsToLocation(location: string, events: LocationEvent[])
window.modAPI.actions.addExplorationEventsToLocation(location: string, events: LocationEvent[])
window.modAPI.actions.addMapEventsToLocation(location: string, mapEvents: LocationMapEvent[])
window.modAPI.actions.addMissionsToLocation(location: string, missions: SectMission[])
window.modAPI.actions.addCraftingMissionsToLocation(location: string, missions: CraftingMission[])

All functions take the location key as their first argument. The location must already exist (either as a base game location or one you registered with addLocation).

addQuestToRequestBoard

Add a quest to a location’s request board so players can accept it as a commission:

window.modAPI.actions.addQuestToRequestBoard(
  quest: Quest,
  realm: Realm,
  rarity: Rarity,
  condition: string,
  location: string,
)
  • quest — The quest definition. If the quest is not already registered, it is automatically added to the quest registry.
  • realm — The realm tier this quest appears under on the request board (e.g. 'qiCondensation').
  • rarity — Controls the display tier of the request. Valid values: 'mundane', 'qitouched', 'empowered', 'resplendent', 'incandescent', 'transcendent'.
  • condition — Flag expression that must be true for the quest to appear (e.g. '1' for always available, 'myMod_unlocked == 1' for conditional).
  • location — The location key. The location must have a requestBoard building, an error is thrown if the location does not exist or has no request board.
// Example: add a gathering quest to the Liang Tiao Village request board
window.modAPI.actions.addQuestToRequestBoard(
  myGatheringQuest,
  'qiCondensation',
  'qitouched',
  '1',
  'Liang Tiao Village',
);

Custom Screens

Register a full-page screen that players can navigate to:

window.modAPI.actions.addScreen({
  key: string;          // Screen identifier for navigation
  component: ModScreenFC; // Your React functional component
  music?: string;        // Optional music track name
  ambience?: string;    // Optional ambient sound
  priority?: number;    // Optional priority for screen resolution
})
  • key — Use setScreen('yourKey') to navigate to this screen from other screens or button click handlers.
  • component — A ModScreenFC receiving screenAPI: ModReduxAPI as props. The screen API gives you hooks (useSelector, useGameFlags, usePlaySfx, useKeybinding), actions (setScreen, setFlag, changeMoney, addItem, advanceDays, etc.), and pre-styled components (GameDialog, GameButton, GameIconButton, BackgroundImage, PlayerComponent).
  • Custom screens cannot be navigated to from event steps. The built-in changeScreen event step only supports the game’s own screen types. To navigate to a mod screen mid-event, use a completion hook (e.g. onCompleteCombat) that calls window.modAPI.actions.setScreen().

Example screen:

const MyScreen: ModScreenFC = ({ screenAPI }) => {
  const { useSelector, actions, components } = screenAPI;
  const { GameDialog, GameButton, BackgroundImage, PlayerComponent } = components;
  const player = useSelector((state) => state.player.player);

  return (
    <Box position="relative" flexGrow={1} display="flex" flexDirection="column">
      <BackgroundImage image="town.png" />
      <GameDialog title="My Screen" onClose={() => actions.setScreen('location')}>
        <Typography>Hello, {player.forename}!</Typography>
        <GameButton onClick={() => actions.changeMoney(100)}>Get Stones</GameButton>
      </GameDialog>
      <Box position="absolute" width="100%" height="100%" display="flex" flexDirection="column">
        <Box flexGrow={1} />
        <Box display="flex">
          <PlayerComponent />
        </Box>
      </Box>
    </Box>
  );
};

window.modAPI.actions.addScreen({ key: 'myScreen', component: MyScreen });

For full documentation on building screens, see Adding Screens.

UI Injection

Inject React content into named slots inside existing game dialogs or screens:

window.modAPI.injectUI(slotName: string, generator: (api: ModReduxAPI, element: HTMLElement, inject: InjectHelper) => void)

Slot names:

  • For dialogs: the dialog’s DOM id (e.g. 'combat-victory'). Open the game in dev mode and inspect the element to find the id for the dialog you want to target.
  • For full screens: the ScreenType value (e.g. 'combat', 'location', 'crafting', 'jadeCutting'). You can also target specific sub-slots within screens (e.g. 'combat-topBarPlayerInfo', 'crafting-craftingScreen', 'stoneCutting-jadeCuttingScreen').

The inject helper:

inject(selector: string, content: ReactNode, mode?: 'overlay' | 'inline', position?: InjectPosition)
  • selector — CSS selector to target inside element
  • content — React node to render
  • mode'overlay' (default, floats over target) or 'inline' (inserts as a sibling after target)
  • position — Where to insert content relative to the target. Only valid for inline mode. after (default) inserts as the next sibling; before inserts as the previous sibling. For overlay mode, position is ignored — content is always placed inside the target element.
window.modAPI.injectUI('combat-victory', (api, element, inject) => {
  return inject(
    '[aria-live="assertive"]',
    <button style= onClick={() => console.log('bonus!')}>
      Claim Bonus
    </button>,
    'inline',
    'after'
  );
});

For a full list of available slots on each screen, see the screen-specific slot names added to combat, crafting, and jade cutting screens. Use dev tools to inspect elements for the complete set of available injection points.

Specialized Content

window.modAPI.actions.addCrop(realm: Realm, crop: Crop)
window.modAPI.actions.addMineChamber(realm: Realm, progress: RealmProgress, chamber: MineChamber)
window.modAPI.actions.addGuild(guild: Guild)
window.modAPI.actions.addDualCultivationTechnique(technique: IntimateTechnique)
window.modAPI.actions.addEnchantment(enchantment: Enchantment)
window.modAPI.actions.addFallenStar(fallenStar: FallenStar)
window.modAPI.actions.addRoom(room: Room)
window.modAPI.actions.addMysticalRegionBlessing(blessing: Blessing)
window.modAPI.actions.addPuppetType(puppet: PuppetType)
window.modAPI.actions.addAlternativeStart(start: AlternativeStart)
window.modAPI.actions.addPlayerSprite(sprite: PlayerSprite)
  • addMysticalRegionBlessing — Register a new blessing for mystical regions.
  • addPuppetType — Register a new puppet type for the training ground.
  • addAlternativeStart — Register an alternative game start. Players select from available starts when creating a new game. The AlternativeStart defines the opening event, starting location, starting items, and starting money.
  • addPlayerSprite — Register a custom player sprite that appears in character creation alongside the defaults for the specified gender.
interface PlayerSprite {
  id: string;
  name: string;
  gender: Sex | 'both';
  sprites: PlayerSpriteImages;
}

interface PlayerSpriteImages {
  base: string;
  aggressive: string;
  defensive: string;
  hit: string;
  offensive: string;
  support: string;
  utility: string;
  craftingSupport?: string;
  craftingStabilize?: string;
  craftingRefine?: string;
  craftingFusion?: string;
}

Crafting System

window.modAPI.actions.addRecipeToLibrary(item: RecipeItem)
window.modAPI.actions.addRecipeToResearch(baseItem: Item, recipe: RecipeItem)
window.modAPI.actions.addResearchableRecipe(baseItem: string, recipe: RecipeItem)
window.modAPI.actions.addUncutStone(realm: Realm, uncutStone: Item)
window.modAPI.actions.addHarmonyType(harmonyType: RecipeHarmonyType, config: HarmonyTypeConfig)
window.modAPI.actions.overrideItemTypeToHarmonyType(mapping: Partial<Record<ItemKind, RecipeHarmonyType>>)

Global Flags

window.modAPI.actions.setGlobalFlag(flag: string, value: number)
window.modAPI.actions.getGlobalFlags(): Record<string, number>

Global flags persist across all save files and are automatically injected into game flags. Use them for mod data that is not tied to a specific save (for example, high scores or global unlock states).

window.modAPI.actions.setGlobalFlag('myMod_highScore', 42);
const flags = window.modAPI.actions.getGlobalFlags();
const highScore = flags['myMod_highScore'] ?? 0;

Flag conventions:

  • Use dot-notation with your mod name as prefix to avoid collisions: 'myMod.enabled', 'myMod.difficulty'
  • Store booleans as 0 / 1 and normalize any legacy values on startup
  • Initialize expected flags to defaults on mod load rather than assuming they exist
  • When renaming flags between versions, migrate old values to the new keys so existing users retain their settings

    Save Data

Mod-specific data that persists with the save file:

window.modAPI.actions.setModData(modName: string, key: string, data: unknown)
window.modAPI.actions.removeModData(modName: string, key: string)

Save-paired data lives in the Redux store alongside the save file. Unlike global flags, save-paired data is tied to a specific save and is not shared across saves.

// Store custom per-save data
window.modAPI.actions.setModData('myMod', 'customNPC_affinity', { npcId: 'elder_li', value: 75 });

// Read it back via useSelector
const npcAffinity = useSelector((state) => state.modData('myMod')?.customNPC_affinity);

// Remove a key when no longer needed
window.modAPI.actions.removeModData('myMod', 'customNPC_affinity');
  • setModData — Store any JSON-serializable data namespaced under your mod name. Persists with the save file. Use for quest state, NPC relationships, discovered secrets, or any other per-save data.
  • removeModData — Remove a specific key from your mod’s save-paired data thereafter.

Keybinding Registration

Keybinding Registration

Register custom keyboard shortcuts that players can rebind in the game settings Controls tab:

window.modAPI.actions.registerKeybinding(definition: KeybindingDefinition)
  • definition.action — Unique action name (e.g. 'myMod.specialAction')
  • definition.category — Display category for grouping in the controls UI (e.g. 'general', 'combat')
  • definition.displayName — Human-readable name shown in the controls UI
  • definition.description — Tooltip description
  • definition.defaultKey — Default key binding (e.g. 'F', 'Shift+KeyG')
  • definition.allowRebind — Whether players can rebind this key
  • definition.modName — (optional) Mod name shown as a section heading in the Controls tab. Automatically set when using registerKeybinding via the ModAPI. Defaults to the mod’s display name.
window.modAPI.actions.registerKeybinding({
  action: 'myMod.specialAction',
  category: 'general',
  displayName: 'Special Action',
  description: 'Performs a special action',
  defaultKey: 'F',
  allowRebind: true,
});

Registered keybindings appear in the Controls settings UI grouped by mod name. Call registerKeybinding during mod initialization. Keybindings are permanent for the session once registered.

Reading keybind values at runtime — Use getRegisteredKeybindValue to check what key is currently bound to an action:

window.modAPI.utils.getRegisteredKeybindValue(action: string): RegisteredKeybind | undefined


`RegisteredKeybind` has the following shape:

```typescript
interface RegisteredKeybind {
  displayText: string;  // Human-readable key display (e.g. 'F', 'Ctrl+S')
  code: string;         // KeyboardEvent.code value (e.g. 'KeyF', 'KeyS')
  ctrlKey: boolean;
  altKey: boolean;
  shiftKey: boolean;
}

Returns a `RegisteredKeybind` object with the current bound key details, or `undefined` if the action is not registered. Useful for displaying key hints in custom UI or comparing against `KeyboardEvent.code`.

```typescript
window.modAPI.actions.registerKeybinding({
  action: 'myMod.specialAction',
  category: 'general',
  displayName: 'Special Action',
  description: 'Performs a special action',
  defaultKey: 'F',
  allowRebind: true,
});

// Later, in a screen or injectUI callback:
const binding = window.modAPI.utils.getRegisteredKeybindValue('myMod.specialAction');
if (binding) {
  console.log(`Special action is bound to ${binding.displayText} (${binding.code})`);
  // Use binding.code to compare against KeyboardEvent.code
}

Mod Settings UI

window.modAPI.actions.registerOptionsUI(component: ModOptionsFC)

Register a React settings component for your mod in the game’s mod-loading dialog. This is the preferred home for cross-save configuration such as toggles, sliders, and mode selectors.

Global flags are numeric, so store booleans as 0 / 1 and normalize any legacy values yourself.

JSX example (if your build pipeline supports JSX):

const MyModOptions: ModOptionsFC = ({ api }) => {
  const flags = api.actions.getGlobalFlags();
  const enabled = (flags['myMod.enabled'] ?? 1) === 1;
  const GameButton = api.components.GameButton ?? 'button';

  return (
    <GameButton
      onClick={() => api.actions.setGlobalFlag('myMod.enabled', enabled ? 0 : 1)}
    >
      {enabled ? 'Disable Mod' : 'Enable Mod'}
    </GameButton>
  );
};

window.modAPI.actions.registerOptionsUI(MyModOptions);

createElement alternative (works regardless of build setup):

If JSX is not available in your options panel context, use window.React.createElement directly. Both approaches produce the same result:

const MyModOptions: ModOptionsFC = ({ api }) => {
  const ReactRuntime = window.React;
  if (!ReactRuntime?.createElement) return null;

  const createElement = ReactRuntime.createElement.bind(ReactRuntime);
  const flags = api.actions.getGlobalFlags();
  const enabled = (flags['myMod.enabled'] ?? 1) === 1;
  const GameButton = api.components.GameButton ?? 'button';

  return createElement(
    GameButton,
    { onClick: () => api.actions.setGlobalFlag('myMod.enabled', enabled ? 0 : 1) },
    enabled ? 'Disable Mod' : 'Enable Mod',
  );
};

window.modAPI?.actions?.registerOptionsUI?.(MyModOptions);

Audio

window.modAPI.actions.addMusic(name: string, path: string[])
window.modAPI.actions.addSfx(name: string, path: string)

Note: When adding audio files the compiler will not know they exist at first, so you will get errors when trying to use the new names you added. To get around that, you will need to cast it to the expected type 'my_music' as MusicName manually. This is essentially just saying to the compiler, ‘trust me, this exists’.

Mod Hooks

Intercept and modify game behavior at specific points through window.modAPI.hooks:

Player Combat Hooks

onCreatePlayerCombatEntity

Modify the player’s combat entity after it is created but before combat begins.

window.modAPI.hooks.onCreatePlayerCombatEntity((player, combatEntity, breakthrough, flags) => {
  if (flags.enhanced_powers) {
    combatEntity.stats.power *= 1.2;
  }
  return combatEntity;
});
  • player — The PlayerEntity from the Redux store
  • combatEntity — The newly created CombatEntity for the player
  • breakthrough — The current BreakthroughState (realm and progress)
  • flags — Current game flags

onCreatePlayerCraftingEntity

Modify the player’s crafting entity after it is created.

window.modAPI.hooks.onCreatePlayerCraftingEntity((player, craftingEntity, breakthrough, characters, flags) => {
  if (flags.master_craftsman) {
    craftingEntity.stats.control *= 1.2;
  }
  return craftingEntity;
});
  • player — The PlayerEntity
  • craftingEntity — The newly created CraftingEntity for the player
  • breakthrough — The current BreakthroughState
  • characters — The CharactersState from Redux (may be undefined)
  • flags — Current game flags

Crafting Hooks

onBeforeCraft

Modify the recipe or recipe stats before crafting begins. Also allows modifying the player crafting entity.

window.modAPI.hooks.onBeforeCraft((player, recipe, recipeStats, flags) => {
  if (flags.sharp_tools && recipeStats.difficulty > 100) {
    return { recipeStats: { ...recipeStats, difficulty: recipeStats.difficulty * 0.9 } };
  }
  return undefined;
});
  • player — The current CraftingEntity
  • recipe — The RecipeItem being crafted
  • recipeStats — The calculated CraftingRecipeStats (completion, perfection, stability thresholds)
  • flags — Current game flags

Return { recipe?: RecipeItem; recipeStats?: CraftingRecipeStats; player?: CraftingEntity } to modify any of these, or undefined to leave them unchanged.

onDeriveRecipeDifficulty

Modify the difficulty and stats of crafting recipes during the crafting process.

window.modAPI.hooks.onDeriveRecipeDifficulty((recipe, recipeStats, flags) => {
  if (flags.unlockedUltimateCauldron === 1) {
    recipeStats.completion *= 0.8;
    recipeStats.perfection *= 0.8;
  }
  return recipeStats;
});

Lifecycle Hooks

onNewGame

Fires when a new game is started, before any data is loaded. Use to set up initial mod state, global flags, or start events for a new playthrough.

window.modAPI.hooks.onNewGame((intent: NewGameIntent) => {
  // Initialize global flags for new game
  window.modAPI.actions.setGlobalFlag('myMod.initialized', 1);
  // Start a custom intro event
  window.modAPI.actions.startEvent(myIntroEvent);
});
  • intentNewGameIntent containing characterName, characterType, background, alternativeStart, difficulty, mods, and seed. Use this to branch mod setup based on the selected character configuration.

The NewGameIntent structure:

interface NewGameIntent {
  characterName: string;
  characterType: 'npc' | 'normal';
  background?: Background;       // Selected background, or undefined for 'normal' character type
  alternativeStart?: AlternativeStart;  // Alternative start data if one was selected
  difficulty: string;
  mods: string[];
  seed: number;
}

onGameLoad

Fires after a saved game is fully loaded. Use to restore per-save mod state, validate global flags, or trigger post-load events.

window.modAPI.hooks.onGameLoad((state: RootState) => {
  // Validate or migrate mod flags on load
  const flags = state.gameData.flags;
  if (flags.myMod_version === undefined) {
    window.modAPI.actions.setModData('myMod', 'needsMigration', true);
  }
});
  • state — The complete RootState Redux snapshot of the loaded save, before the game begins rendering. Use state.gameData.flags for flag values, state.player for character data, and other slices as needed.

onDeleteCharacter

Fires when a character save is deleted from the Load page. Use to clean up per-character mod configuration.

window.modAPI.hooks.onDeleteCharacter((saveInfo: Save) => {
  myModConfig.deleteCharacterConfig(saveInfo.dbName);
});
  • saveInfo — A Save object containing metadata about the deleted save:
interface Save {
  dbName: string;          // Unique database key
  playerName: string;      // Display name
  forename?: string;
  surname?: string;
  icon: string;
  realm: string;           // Realm name
  realmProgress: string;   // Realm progress name
  age: number;
  createdAt: number;        // Unix timestamp (ms)
  lastPlayed?: number;     // Unix timestamp (ms)
  playtime: number;        // Seconds played
  version: string;         // Game version at save time
  mods: string[];          // Mods active when save was created
  location?: string;       // Last location
  screen?: string;         // Last screen type
}

Combat Hooks

onCreateEnemyCombatEntity

Modify enemy stats after the combat entity is created.

window.modAPI.hooks.onCreateEnemyCombatEntity((enemy, combatEntity, flags) => {
  if (flags.hard_mode) {
    combatEntity.stats.hp *= 1.5;
    combatEntity.stats.power *= 1.2;
  }
  return combatEntity;
});

onBeforeCombat

Modify the enemy list and player state before combat starts. Return modified copies, mutations to the originals are not used.

window.modAPI.hooks.onBeforeCombat((enemies, playerState, flags) => {
  if (flags.hard_mode) {
    const scaled = enemies.map(e => ({ ...e, stats: { ...e.stats, hp: e.stats.hp * 2 } }));
    return { enemies: scaled, playerState };
  }
  return { enemies, playerState };
});

onCalculateDamage

Modify damage after all base reductions are applied.

window.modAPI.hooks.onCalculateDamage((attacker, defender, damage, damageType, flags) => {
  if (flags.iron_body && defender.entityType === 'Player') {
    return Math.floor(damage * 0.8);
  }
  return damage;
});

Combat Step Hooks

These hooks fire around individual steps of the combat round queue (player technique, enemy technique, artefact, party member, buffs, end-of-round). They fire from CombatScreen.tsx and allow mods to inspect and modify the combat state mid-round, or cancel a step entirely.

onCombatBeforeStep

Fires before each step in the round queue. Return a modified CurrentCombatState to change combat state, a cancel object { cancel: true, logMessage: string } to skip the step and log a message, or null to continue as normal.

window.modAPI.hooks.onCombatBeforeStep((step, ctx) => {
  if (step.kind === 'player' && ctx.roundNum >= 5) {
    return { cancel: true, logMessage: 'You refuse to continue after 5 rounds.' };
  }
  return null;
});
  • step – The RoundStep being executed ({ kind: 'player' }, { kind: 'enemy' }, { kind: 'playerArtefact', index: 0 }, { kind: 'buffs' }, { kind: 'end' }, etc.)
  • ctx – The current CurrentCombatState snapshot at the point before the step executes

onCombatAfterStep

Fires after each step in the round queue completes. Return a modified CurrentCombatState to change what gets stored, or null to keep the default.

window.modAPI.hooks.onCombatAfterStep((step, ctx) => {
  if (step.kind === 'enemy' && ctx.playerState && ctx.playerState.stats.hp <= 0) {
    // Custom death handling
    return { ...ctx, combatState: 'defeat' };
  }
  return null;
});

onCombatRoundStart

Fires at the start of each combat round, after the round queue is built but before any steps execute. Useful for round-based setup effects.

window.modAPI.hooks.onCombatRoundStart((ctx) => {
  console.log('Round', ctx.roundNum, 'starting');
  return null; // or return a modified ctx to apply changes
});

onCombatRoundEnd

Fires at the end of each combat round (when the { kind: 'end' } step completes). Useful for end-of-round effects, cleanup, or applying accumulated state changes.

window.modAPI.hooks.onCombatRoundEnd((ctx) => {
  // Clean up temporary buffs at end of round 3
  if (ctx.roundNum === 3) {
    const cleaned = { ...ctx };
    return cleaned;
  }
  return null;
});

onConsumeItem

Fires when the player consumes a pill, concoction, or combat consumable during combat.

window.modAPI.hooks.onConsumeItem((item, target, flags) => {
  console.log('Consumed:', item.name, 'target hp:', target.stats.hp);
});
  • item – The Item being consumed
  • target – The CombatEntity receiving the item
  • flags – Current game flags

Combat State Access

During active combat, modAPI.combat provides direct access to the current combat state and allows mods to pause and resume the combat screen to inject custom UI or trigger events.

if (modAPI.combat) {
  const state = modAPI.combat.getCombatState();
  modAPI.combat.setCombatState({ ...state, playerState: { ...state.playerState, stats: { ...state.playerState.stats, hp: 500 } } });
  console.log('In combat:', modAPI.combat.isInCombat());
  console.log('Log:', modAPI.combat.getCombatLog());
}
  • modAPI.combat.getCombatState() – Returns the current CurrentCombatState, or undefined if not in combat
  • modAPI.combat.setCombatState(state) – Overwrites the current combat state (use to modify HP, buffs, round number, etc.)
  • modAPI.combat.getCombatLog() – Returns the array of CombatLogEntry entries so far
  • modAPI.combat.isInCombat() – Returns true if a combat is in progress

To pause combat and show a custom screen, mods can call window.modAPI.actions.setCombatPaused(true) from a hook or UI injection. The CombatScreen returns null while paused, allowing a parent component to render custom UI instead. Call window.modAPI.actions.setCombatPaused(false) to resume.

Completion Hooks

These fire after specific activities complete and can inject additional event steps:

onCompleteCombat

window.modAPI.hooks.onCompleteCombat((eventStep, victory, playerState, enemies, droppedItems, flags) => {
  const events: EventStep[] = [];
  if (!victory && eventStep.kind === 'combat' && !eventStep.isSpar) {
    events.push({ kind: 'text', text: 'You fall, vision narrowing to black.' });
    events.push({ kind: 'changeSocialStat', stat: 'lifespan', amount: '-lifespan' });
  }
  return events;
});

onCompleteTournament

window.modAPI.hooks.onCompleteTournament((eventStep, tournamentState, flags) => {
  const events: EventStep[] = [];
  if (tournamentState === 'victory' && !flags.firstTournamentVictory) {
    events.push({ kind: 'unlockLocation', location: 'Champion Training Grounds' });
  }
  return events;
});

onCompleteDualCultivation, onCompleteCrafting, onCompleteAuction, onCompleteStoneCutting

Analogous to onCompleteCombat. Each receives the relevant event step, outcome data, and flags, and returns additional EventStep[] to inject.

Event Hooks

onEventDropItem

Intercept items granted by addItem, addMultipleItem, or dropItem event steps. Return a modified ItemDesc to change the item or suppress it entirely (return stacks <= 0).

window.modAPI.hooks.onEventDropItem((item, step, flags) => {
  if (flags.item_bonus && item.name === 'Iron Ore') {
    return { ...item, stacks: (item.stacks ?? 1) * 2 };
  }
  return item;
});

Exploration Hooks

onGenerateExploreEvents

Modify the pool of exploration events before one is selected. Fires after base-game eligibility filtering.

window.modAPI.hooks.onGenerateExploreEvents((locationId, events, flags) => {
  if (flags.lucky_mode) {
    return [...events, myBonusEvent];
  }
  return events;
});

Location Hooks

onLocationEnter

Fires when the player enters a new location. Observation only, no return value.

window.modAPI.hooks.onLocationEnter((locationId, flags) => {
  console.log('Entered:', locationId);
});

Loot Hooks

onLootDrop

Fires when combat loot is distributed. Observation only.

window.modAPI.hooks.onLootDrop((items, flags) => {
  items.forEach(item => console.log('Loot:', item.name));
});

Time Hooks

onAdvanceDay / onAdvanceMonth

Fire on time progression. Observation only.

window.modAPI.hooks.onAdvanceDay((days, flags) => {
  console.log('Days passed:', days);
});

window.modAPI.hooks.onAdvanceMonth((month, year, flags) => {
  if (month === 3) {
    window.modAPI.actions.startEvent(mySpringFestival);
  }
});

Redux Hooks

onReduxAction

Fires after every Redux action. Runs inside the reducer, keep it fast, deterministic, and side-effect free. Return a modified stateAfter to override what is stored.

window.modAPI.hooks.onReduxAction((actionType, stateBefore, stateAfter, payload) => {
  if (actionType === 'inventory/addItem' && stateAfter.gameData.flags?.hard_mode) {
    return { ...stateAfter, inventory: doubleInventory(stateAfter.inventory) };
  }
  return stateAfter;
});

onReduxActionPayload

Fires before the reducer runs. Allows modifying or dropping an action. Return null to drop the action entirely.

window.modAPI.hooks.onReduxActionPayload((actionType, payload) => {
  if (actionType === 'inventory/removeItem') {
    const p = payload as { name: string; stacks: number };
    if (window.modAPI.gameData.items[p.name]?.kind === 'blueprint') {
      return null; // prevent blueprint removal
    }
  }
  return payload;
});

Utility Functions

Helper functions through window.modAPI.utils:

State Access

window.modAPI.subscribe(callback: () => void): () => void
window.modAPI.getGameStateSnapshot(): RootState | null
window.modAPI.utils.determineCurrentScreen(rootState: RootState): ScreenType
  • subscribe — Subscribe to any Redux state change. The callback is called after every dispatched action. Returns an unsubscribe function. Prefer this over direct store access.
  • getGameStateSnapshot — Returns a read-only snapshot of the complete game state, or null if no save is loaded.
  • determineCurrentScreen — Determines the current screen type from the Redux root state. Useful in custom screens or hooks to branch behavior based on where the player is.
// Rate-limited reactive updates
let lastUpdate = 0;
window.modAPI.subscribe(() => {
  const now = Date.now();
  if (now - lastUpdate < 250) return;
  lastUpdate = now;
  const snap = window.modAPI.getGameStateSnapshot();
  if (snap) refreshMyPanel(snap);
});

// Determine current screen
const screen = window.modAPI.utils.determineCurrentScreen(store.getState());

Enemy Modifiers

window.modAPI.utils.alpha(enemy: EnemyEntity) // Elite version
window.modAPI.utils.alphaPlus(enemy: EnemyEntity) // Enhanced elite
window.modAPI.utils.realmbreaker(enemy: EnemyEntity) // Multiple realmbreaker variants
window.modAPI.utils.corrupted(enemy: EnemyEntity) // Corrupted version

Enemy Scaling

window.modAPI.utils.scaleEnemy(base: EnemyEntity, realm: Realm, realmProgress: RealmProgress)
window.modAPI.utils.calculateEnemyHp(enemy: EnemyEntity)
window.modAPI.utils.calculateEnemyPower(enemy: EnemyEntity)

Use these when creating enemies that need to appear at multiple realm tiers:

const scaledBandit = window.modAPI.utils.scaleEnemy(baseBandit, 'coreFormation', 'Middle');

Quest Creation

window.modAPI.utils.createCombatEvent(enemy: LocationEnemy)
window.modAPI.utils.createCullingMission(monster, location, description, favour)
window.modAPI.utils.createCollectionMission(item, location, description, favour)
window.modAPI.utils.createDeliveryMission(items, count, location, description, preDeliverySteps, postDeliverySteps, favour)
window.modAPI.utils.createHuntQuest(monster, location, description, encounter, spiritStones, reputation, reputationName, maxReputation, characterEncounter?)
window.modAPI.utils.createPackQuest(monster, location, description, encounter, spiritStones, reputation, reputationName, maxReputation)
window.modAPI.utils.createDeliveryQuest(location, description, predelivery, item, amount, postdelivery, spiritStones, reputation, reputationName, maxReputation)
window.modAPI.utils.createFetchQuest(title, description, srcLocation, srcHint, srcSteps, dstLocation, dstHint, dstSteps, spiritStones, reputation, reputationName, maxReputation)
window.modAPI.utils.createCraftingMission(recipe, cost, location, appraiser, description, introSteps, sublimeSteps, perfectSteps, basicSteps, failureSteps, favour)
  • createDeliveryMission — Simple delivery quest (favour reward only).
  • createPackQuest — Hunt quest targeting a group of the same monster type.
  • createDeliveryQuest — Full delivery quest with spirit stone and reputation rewards.
  • createFetchQuest — Two-location fetch quest with source and destination events.
  • createCraftingMission — Crafting hall commission with separate outcome steps for each quality tier (sublime, perfect, basic, failure).

Balance Calculations

window.modAPI.utils.getExpectedHealth(realm: Realm, progress: RealmProgress)
window.modAPI.utils.getExpectedPower(realm: Realm, progress: RealmProgress)
window.modAPI.utils.getExpectedDefense(realm: Realm, progress: RealmProgress)
window.modAPI.utils.getExpectedBarrier(realm: Realm, progress: RealmProgress)
window.modAPI.utils.getExpectedToxicity(realm: Realm, progress: RealmProgress)
window.modAPI.utils.getExpectedPool(realm: Realm, progress: RealmProgress)
window.modAPI.utils.getExpectedIntensity(realm: Realm, progress: RealmProgress)
window.modAPI.utils.getExpectedControl(realm: Realm, progress: RealmProgress)
window.modAPI.utils.getExpectedPlayerPower(realm: Realm, progress: RealmProgress)
window.modAPI.utils.getExpectedArtefactPower(realm: Realm, progress: RealmProgress)
window.modAPI.utils.getRealmQi(realm: Realm, realmProgress: RealmProgress)
window.modAPI.utils.getBreakthroughQi(realm: Realm, realmProgress: RealmProgress)
window.modAPI.utils.getNumericReward(base: number, realm: Realm, progress: RealmProgress)
window.modAPI.utils.getPillRealmMultiplier(realm: Realm)
window.modAPI.utils.getCraftingEquipmentStats(realm: Realm, realmProgress: RealmProgress, factors: { pool: number; control: number; intensity: number }, type: 'cauldron' | 'flame')
  • getExpectedBarrier — Expected max barrier for a player in the given realm.
  • getExpectedToxicity — Expected toxicity resistance.
  • getExpectedPool — Expected crafting qi pool size.
  • getExpectedIntensity — Expected crafting intensity.
  • getExpectedControl — Expected crafting control.
  • getExpectedArtefactPower — Expected artefact power stat.
  • getBreakthroughQi — Qi cost for a breakthrough at the given realm and progress.
  • getPillRealmMultiplier — Multiplier applied to pill effectiveness based on realm. Use when computing flat consumable values.

Equipment Calculations

window.modAPI.utils.getClothingDefense(realm: Realm, scale: number)
window.modAPI.utils.getClothingCharisma(realm: Realm, mult: number)
window.modAPI.utils.getBreakthroughCharisma(realm: Realm, mult: number)

Event Helpers

window.modAPI.utils.createQuestionAnswerList(key: string, questions: QuestionAnswer[], exit: QuestionAnswer, showExitOnAllComplete?: boolean)
window.modAPI.utils.flag(flag: string) // Convert flag name to game flag format
window.modAPI.utils.evalExp(exp: string, flags: Record<string, number>) // Evaluate an expression using the given flags, then floor it if the number is greater than 3
window.modAPI.utils.evalExpNoFloor(exp: string, flags: Record<string, number>) // The above but without the floor
window.modAPI.utils.evaluateScaling(scaling: Scaling, variables: Record<string, number>, stanceLength: number, preMaxTransform?: (value: number) => number)
window.modAPI.utils.generateSkipTutorialFlags(tutorials: Tutorial[], triggers: TriggeredEvent[])
  • evaluateScaling — Evaluate a Scaling object against a variables map. Applies base value, stat multipliers, equations, custom scaling, and max constraints. Useful when computing item or technique values in code.
  • generateSkipTutorialFlags — Generate the flags needed to skip tutorials for an alternative start. For each tutorial, sets {name}, {name}Started, {name}Completed; for each trigger, sets {name} and {name}Started.

Tooltip Utilities

Format and expand tooltip strings using the game’s tooltip system:

window.modAPI.utils.parseTooltipLine(tooltip: string): React.ReactNode
window.modAPI.utils.expandTooltipTemplate(template: string, templateValues: Map<string, string>, addPeriod?: boolean): string
window.modAPI.utils.expandTooltipTags(template: string): string
  • parseTooltipLine — Parse a tooltip string and return a React node with styled formatting. Handles colour tags, element tags, buff/item references, and numbers.
  • expandTooltipTemplate — Expand a template string by replacing `` placeholders with values from the map. Optionally appends a period.
  • expandTooltipTags — Expand <tag> syntax in a template string to their display equivalents.

These utilities use the same formatting system as the game’s built-in tooltips, ensuring consistent styling when you render custom tooltips in mod UI.

// Styled tooltip output for a custom buff display
const tooltipNode = window.modAPI.utils.parseTooltipLine('Applies [[blood corruption]] to target for 3 turns');

// Expand template with dynamic values
const expanded = window.modAPI.utils.expandTooltipTemplate(
  'Power scales with  (base )',
  new Map([['stat', 'Control'], ['base', '50']]),
  true,
);

Text Formatting

These helpers produce styled HTML strings for use in event text steps:

window.modAPI.utils.col(text: string | number, col: string) // Color any text with a CSS color
window.modAPI.utils.loc(text: string | number) // Purple, location names
window.modAPI.utils.rlm(realm: Realm, progress?: RealmProgress) // Styled realm name
window.modAPI.utils.num(number: string | number) // Styled number
window.modAPI.utils.buf(buff: string) // Pink, buff names
window.modAPI.utils.itm(item: string) // Pink, item names
window.modAPI.utils.char(text: string | number) // Green, character names
window.modAPI.utils.elem(element: TechniqueElement) // Styled technique element

Use these inside event text fields to produce consistent in-game styling:

{
  kind: 'text',
  text: `You travel to ${window.modAPI.utils.loc('Iron Peak Sect')} and meet ${window.modAPI.utils.char('Elder Zhang')}, who offers you ${window.modAPI.utils.itm('Spirit Core (III)')}.`
}

Crafting Utilities

window.modAPI.utils.previewCraftingTechnique(technique: CraftingTechnique, state: CraftingState): CraftingState

Preview the outcome of a crafting technique given the current crafting state. Returns the predicted CraftingState with progressState.completion and other metrics.

const craftingState = api.useSelector(state => state.crafting);
const technique = api.craftingTechniqueFromKnown(knownTechnique);
const preview = window.modAPI.utils.previewCraftingTechnique(technique, craftingState);
console.log('Completion after:', preview.progressState?.completion);

Player Entity Utilities

window.modAPI.utils.createPlayerCombatEntity(player: PlayerEntity, breakthrough: BreakthroughState, gameFlags?: Record<string, number>): CombatEntity
window.modAPI.utils.createPlayerCraftingEntity(player: PlayerEntity, breakthrough: BreakthroughState, characters?: CharactersState, options?: { noCompanionBuff?: boolean }, gameFlags?: Record<string, number>): CraftingEntity

Create full player entities for tooltips, calculations, and custom mechanics. Both functions apply breakthrough stats, scaling, destinies, and mod hooks.

  • createPlayerCombatEntity — Create a combat entity for damage calculations, tooltips, or custom combat mechanics. Returns a CombatEntity with all stats computed from the player’s current breakthrough state.

  • createPlayerCraftingEntity — Create a crafting entity for crafting tooltips, preview calculations, or custom crafting mechanics. Takes optional characters state for companion bonuses, and options.noCompanionBuff to skip those bonuses.

// Create a combat entity for tooltip display
const player = window.modAPI.getGameStateSnapshot()?.player.player;
const breakthrough = window.modAPI.getGameStateSnapshot()?.player.breakthrough;
if (player && breakthrough) {
  const combatEntity = window.modAPI.utils.createPlayerCombatEntity(player, breakthrough, flags);
  // Use for damage calculations, technique effect previews, etc.
}

// Create a crafting entity for crafting previews
const craftingEntity = window.modAPI.utils.createPlayerCraftingEntity(player, breakthrough, characters);

Components

The ModReduxAPI.components object provides pre-styled UI components for use in mod screens, injected UI, and options panels:

const { GameDialog, GameButton, GameIconButton, BackgroundImage, PlayerComponent, GameTooltip, GameTooltipBox, TooltipLine } = api.components;

GameDialog

Main content container with built-in title and close button:

<GameDialog
  title="Dialog Title"
  onClose={() => actions.setScreen('location')}  // Omit to disable close button
  removePad={false}
  showBackdrop={true}
  width="md"  // 'sm' | 'md' | 'lg'
>
  Your content here
</GameDialog>

GameButton

Styled button matching the game theme:

<GameButton
  onClick={() => handleClick()}
  disabled={false}
  keybinding={"Enter"}  // Keybinding that triggers click when pressed
  keyPriority={1}
  fancyBorder={false}  // When true, shows animated border like the combat fight button
>
  Button Text
</GameButton>

GameIconButton

Button with an icon (expects an MUI icon component):

<GameIconButton onClick={() => handleClick()}>
  <CloseIcon />
</GameIconButton>

BackgroundImage

Screen background with optional particle effects:

<BackgroundImage
  image="path/to/background.png"
  screenEffect="dust"  // 'dust', 'snow', 'rain', etc.
/>

PlayerComponent

Shows the player character. Always include in location/event screens unless you custom-render the character elsewhere:

<PlayerComponent />

GameTooltip, GameTooltipBox, TooltipLine

Styled tooltip components matching the game’s tooltip system. Use these to display formatted buff, item, or technique information in mod UI:

<GameTooltip>
  <GameTooltipBox title="My Buff">
    <TooltipLine left="Effect" right="Deal 50 damage" />
    <TooltipLine left="Duration" right="3 turns" />
  </GameTooltipBox>
</GameTooltip>

TooltipLine accepts left (label) and right (value) strings. parseTooltipLine in utils can format raw tooltip strings with colour tags, element tags, buff/item references, and numbers into styled ReactNode output suitable for use in these components.

Examples

Adding a Custom Item

const myTreasure: TreasureItem = {
  kind: 'treasure',
  name: 'The Best Treasure',
  description: 'Wooo mod content.',
  icon: icon,
  stacks: 1,
  rarity: 'mundane',
  realm: 'coreFormation',
};

window.modAPI.actions.addItem(myTreasure);

For docs on the more advanced features of the Mod API, then see the Advanced Mods page.