Building Types
Buildings provide interactive services and content within locations. Each building type offers specific functionality, from commerce and healing to quest distribution and cultivation.
Basic Buildings
Healer
Provides healing services to restore health and remove injuries:
interface HealerBuilding {
kind: "healer";
condition?: string;
disabled?: string;
offset?: { x: number; y: number; };
completionFlag?: string; // Set after recuperation
daysOverride?: number; // Override healing duration, including pill-assisted healing
}
Cultivation Chamber
Allows meditation and qi cultivation:
{
kind: 'cultivation'
}
Manual Pavilion
Access to combat technique manuals:
{
kind: 'manual'
}
Crafting (Furnace Pagoda)
Alchemy and item crafting:
{
kind: 'crafting'
}
Crafting buildings also accept introduction?: { condition: string; steps: EventStep[] } and baseDaysOverride?: number to add an introductory event or override the base crafting duration.
Treasure Vault
Banking and storage services:
{
kind: 'vault',
slips: vaultSlips, // Record<Realm, TokenItem> defined by your mod
itemPool: vaultOfferings // Record<Realm, VaultOffering[]>
}
Material Compendium
Encyclopedia of crafting materials:
{
kind: 'compendium'
}
Mystical Region
Portal to special cultivation areas:
{
kind: 'mysticalRegion'
}
Expedition
Portal to expedition dungeons. Expedition buildings require a name matching an existing expedition tile pool (registered by the base game or via modAPI.actions.addExpeditionTiles).
interface ExpeditionBuilding {
kind: "expedition";
name: string;
displayName?: Translatable;
teamCount: number;
realm: Realm;
condition?: string;
disabled?: string;
offset?: { x: number; y: number; };
}
Example:
{
kind: 'expedition',
name: 'Tai Kong',
displayName: 'Tai Kong Expedition',
teamCount: 3,
realm: 'qiCondensation',
}
The name field is the expedition identifier. It must match the expeditionName passed to api.actions.addExpeditionTiles(expeditionName, tiles) so that the tile pool is correctly associated with this building. See Expedition Tiles below for how to register tiles for a custom expedition.
Training Ground
Combat training and sparring:
{
kind: 'trainingGround'
}
Research (Vault of Infinite Reflections)
Research and experimentation facility:
{
kind: 'research'
}
Reforge Workshop
Item reforging and enhancement:
{
kind: 'reforge'
}
Furnace of Ten Thousand Flames
Advanced alchemy furnace for high-tier crafting:
{
kind: 'tenThousandFlames'
}
Commerce Buildings
Market
General marketplace with realm-specific inventory:
interface MarketBuilding {
kind: "market";
tokenCurrency?: MarketTokenCurrency;
displayName?: Translatable;
itemPool: Record<Realm, ShopItem[]>;
reputationPool?: Record<Realm, ShopItem[]>;
costMultiplier: number;
refreshMonths: number;
condition?: string;
disabled?: string;
offset?: { x: number; y: number; };
}
Favour Exchange
Special shop using favour currency:
{
kind: 'favourExchange',
itemPool: emptyShopPool, // Same as market
costMultiplier: 2.0,
refreshMonths: 1
}
Enchantment Shop
Shop for purchasing item enchantments:
{
kind: 'enchantmentShop',
costMultiplier: 1.5, // Base price multiplier
refreshMonths: 1, // Inventory refresh period
stockCount: 6 // Number of enchantments available at a time
}
Mission Buildings
Mission Hall
Sect missions with rewards and crafting commissions:
{
kind: 'mission'
}
Missions and crafting commissions are defined at location level:
missions: [
{
realm: 'bodyForging',
rarity: 'mundane',
quest: 'ratascar_culling',
condition: '1'
}
]
To offer crafting commissions at a location, set offersCraftingMissions: true on the mission building and use addCraftingMissionsToLocation from the mod API. Crafting commissions require the player to craft an item to a quality threshold, appraised by an NPC who delivers separate outcome event steps for sublime, perfect, basic, and failure results.
Request Board
Player-requested tasks:
{
kind: 'requestBoard',
requests: {
mundane: [],
qiCondensation: [],
coreFormation: [],
pillarCreation: [],
lifeFlourishing: [],
worldShaping: [],
innerGenesis: [],
soulAscension: [],
bodyForging: [
{
quest: 'herb_collection',
condition: '1',
rarity: 'mundane'
}
],
meridianOpening: [],
// ...other realms
}
}
Resource Buildings
Herb Field
Herb gathering location:
{ kind: 'herbField', condition: 'farmingUnlocked == 1' }
Yinying Mine
Mining for ores and gems:
{ kind: 'mine', mineId: 'myModMine', condition: 'miningUnlocked == 1' }
Special Buildings
Recipe Library
Access to crafting recipes:
{
kind: 'recipe',
recipePool: {
mundane: [],
qiCondensation: [],
coreFormation: [],
pillarCreation: [],
lifeFlourishing: [],
worldShaping: [],
innerGenesis: [],
soulAscension: [],
bodyForging: [
'recuperation_pill_recipe',
'iron_skin_pill_recipe',
'clothing_blank_recipe'
],
meridianOpening: [],
// ...other realms
}
}
Library
Books and lore:
{
kind: 'library', title: 'Ancient Archives',
categories: [{
name: 'History', condition: '1',
books: [{ title: 'Rise of the Nine Mountains', author: 'Elder Shou', condition: 'historyInterest == 1', contents: 'Long ago, when the heavens...' }],
}],
}
House
Player housing:
{
kind: 'house',
houseDef: {
name: 'Heaven-Touched House',
description: 'Your rebuilt childhood home...',
background: homeImage,
screenEffect: 'dust',
qiDensity: 1000,
fixedRooms: [],
freeRooms: '3',
transportSeal: liangTiaoSeal
},
unlockCondition: 'houseRepaired == 1',
condition: 'houseRepaired == 1'
}
Rumour Trader
A trader who sells whispered secrets about hidden locations. Each rumour has a cost and a revealed hint that points toward a discovery opportunity.
{
kind: 'rumours', traderName: 'Old Huo the Altar-Mapper',
rumours: [{
key: 'myRumour_key', title: 'The Secret Chamber', cost: 2000, costRealm: 'coreFormation',
hint: 'Deep beneath the ancient ruins lies a hidden chamber...',
condition: 'secretFound == 0', foundCondition: 'altar_MySecretChamber == 1',
}],
}
condition: Optional. When set, the rumour is hidden unless the condition evaluates to true. Use this to gate rumours behind prerequisites or to hide hardmode-only secrets.
foundCondition: Optional. An expression evaluated to determine whether the player has already acted on this rumour. Drives the ‘Found’ status badge on the trader card so players can see at a glance which secrets they have already discovered. Typical values are the same flag the discovery event sets (for example ancestralAltarFound == 1) or an item-presence check.
Per-rumour state: The game tracks which rumours the player has purchased in RumoursBuildingState.purchased (a list of rumour keys). Use this to prevent a rumour from appearing again after purchase, or to show different dialogue when returning to the trader.
Compression Altar
Core compression service. Grants a temporary buff on use and a permanent breakthroughReward on the first compression at each altar.
{
kind: 'altar',
buff: {
name: 'Meditative Surge', icon: 'assets/meditative-surge.png',
tooltip: 'Your core has been compressed.', canStack: true, stacks: 1,
stats: { power: { value: 0.2, stat: 'power', scaling: 'stacks' } },
},
breakthroughReward: {
combatStats: { power: { value: 0.03, stat: 'power' } },
craftingStats: { poolCostPercentage: { value: -3, stat: undefined } },
},
}
breakthroughReward is optional. If omitted, compressing at the altar grants no permanent bonus. Each unique altar (identified by location name or house name) can grant its breakthroughReward once per playthrough. The same altar visited again provides only the temporary buff.
Available CombatStatistic keys include power, protection, critchance, critmultiplier, resistance, and dr.
Available CraftingStatistic keys include control, intensity, critchance, critmultiplier, successChanceBonus, and poolCostPercentage. A poolCostPercentage bonus of -3 reduces Qi Pool costs by 3 percentage points.
Guild
Guild headquarters:
interface GuildBuilding {
kind: "guild";
condition?: string;
guild: string;
position?: { x: number; y: number; };
disabled?: string;
offset?: { x: number; y: number; };
}
Items sold in a token-based guild rank shop declare a fixed token price:
const rankShopItem: ShopItem = {
name: myItem.name,
stacks: 1,
tokenCost: 3, // Fixed price in the guild's token currency
};
Important: When a guild declares token, every item in its rankShop must declare tokenCost. Items without it would be charged in spirit stones instead.
Custom Building
Fully customizable building with event steps:
{
kind: 'custom',
name: 'Mysterious Shop',
icon: shopIcon,
position: { x: 0.75, y: 0.5 }, // Normalized position in the location artwork
condition: 'mysteryUnlocked == 1',
eventSteps: [
{
kind: 'text',
text: 'You enter the mysterious shop...'
},
{
kind: 'choice',
choices: [
{
text: 'Browse wares',
children: []
},
{
text: 'Leave',
children: [
{ kind: 'exit' }
]
}
]
}
]
}
In AFNM 0.7.16, building position is an optional { x, y } object using normalized scene coordinates (0 to 1). The old string positions and CustomBuildingPosition type have been removed. Omit position to use automatic placement, or set coordinates: { x, y } for percentage coordinates. These fields apply to all building types.
Custom buildings can also set borderKind?: BuildingType to use the border colour of a service category.
Mod-Specific Buildings
Mod Building
A custom building that navigates to a registered mod screen. Use modBuilding alongside window.modAPI.actions.addScreen() to integrate fully custom UI into a location.
{
kind: 'modBuilding', name: 'Mysterious Device', displayName: 'Mysterious Device',
icon: myCustomIcon, screen: 'myModScreen', position: { x: 0.25, y: 0.5 },
condition: 'deviceUnlocked == 1', disabled: 'deviceBusy == 1',
}
The screen field must match the key used when registering the screen with window.modAPI.actions.addScreen(). See Adding Screens for how to create and register mod screens.
// Register the screen
window.modAPI.actions.addScreen({
key: 'myModScreen',
component: MyModScreenComponent,
});
// Add the building to a location
window.modAPI.actions.addBuildingsToLocation('Liang Tiao Village', [
{
kind: 'modBuilding',
name: 'Ancient Device',
icon: deviceIcon,
screen: 'myModScreen',
position: { x: 0.5, y: 0.1 },
condition: '1',
}
]);
Building Properties
Common Properties
All buildings support these optional fields:
{
kind: BuildingType, // Required building type
condition?: string, // When building is available
disabled?: string, // When building is disabled
position?: { // Normalized position in location scene artwork
x: number,
y: number
},
coordinates?: { x: number, y: number }, // Percentage coordinates; overrides automatic placement
offset?: { x: number, y: number }, // Additional position adjustment
}
Conditional Availability
Control when buildings appear:
{
kind: 'market',
condition: 'marketBuilt == 1 && realm >= meridianOpening',
itemPool: emptyShopPool,
costMultiplier: 1,
refreshMonths: 1,
// ...other properties
}
Disabled State
Temporarily disable buildings:
{
kind: 'healer',
disabled: 'injured == 0', // Disabled when not injured
}
Complete Example
export const myLocation: GameLocation = {
...baseLocation, // A complete GameLocation defined by your mod
buildings: [
// Basic services
{ kind: 'healer' },
{ kind: 'crafting' },
// Market with reputation items
{
kind: 'market',
itemPool: {
meridianOpening: [],
mundane: [],
qiCondensation: [],
coreFormation: [],
pillarCreation: [],
lifeFlourishing: [],
worldShaping: [],
innerGenesis: [],
soulAscension: [],
bodyForging: [
{ name: 'Healing Pill I', stacks: 5 },
{ name: 'Small Claw', stacks: 10 }
]
},
reputationPool: {
mundane: [],
meridianOpening: [],
qiCondensation: [],
coreFormation: [],
pillarCreation: [],
lifeFlourishing: [],
worldShaping: [],
innerGenesis: [],
soulAscension: [],
bodyForging: [
{
name: 'Rare Manual',
stacks: 1,
reputation: 'exalted',
valueModifier: 10
}
]
},
costMultiplier: 1.8,
refreshMonths: 2
},
// Custom event building
{
kind: 'custom',
name: 'Elder\'s Residence',
icon: elderIcon,
position: { x: 0.5, y: 0.1 },
condition: 'elderQuestComplete == 1',
eventSteps: [
{
kind: 'speech',
character: 'Village Elder',
text: 'Welcome back, young cultivator...'
},
// ...more events
]
},
// Conditional library
{
kind: 'library',
condition: 'libraryUnlocked == 1',
title: 'Village Archives',
categories: [
{
name: 'Local History',
condition: '1',
books: []
}
]
},
// Mod screen building
{
kind: 'modBuilding',
name: 'Alchemist Workshop',
icon: workshopIcon,
screen: 'myAlchemistScreen',
position: { x: 0.75, y: 0.5 },
condition: 'workshopBuilt == 1',
}
]
};
Expedition Tiles
For the small tile examples below, define shared base properties once:
const baseTile: Omit<EntranceTile, 'kind'> = {
name: 'Frozen Reach Tile',
icon: 'assets/tile.png',
bg: 'assets/frozen-reach.png',
description: 'A chamber in the Frozen Reach.',
rarity: 'mundane',
intro: [],
};
Give each registered tile a unique name when building your tile pool.
Expeditions use a tile-based dungeon generation system. Each tile has a kind (entrance, exit, combat, treasure, etc.) and defines connections to neighbouring tiles.
Tiles are registered via api.actions.addExpeditionTiles(expeditionName, tiles) and are keyed by the same expedition identifier used in the expedition building name field. An expedition building without any tiles registered will use the default game tiles.
Reading existing tiles:
// Inspect the tile pool for an existing expedition
const tilePool = window.modAPI.gameData.expeditionTiles['Tai Kong'];
const treasureTiles = tilePool.filter((t) => t.kind === 'treasure');
Registering tiles for a custom expedition:
// Register a new expedition with a custom treasure tile
window.modAPI.actions.addExpeditionTiles('Frozen Reach', [
{
kind: 'treasure',
name: 'Glacial Cache',
icon: 'mod://icons/ice.png',
bg: 'mod://backgrounds/iceBG.png',
description: 'A cache pulsing with cold qi.',
rarity: 'resplendent',
intro: [{ kind: 'text', text: 'You uncover a cache of frozen treasures.' }],
},
]);
Tile Kinds
All tile types share these base fields:
interface BaseTile {
name: string; // Display name
icon: string; // Icon path (use 'mod://' for mod assets)
bg: string; // Background image path
description: Translatable; // Shown when the tile is revealed
rarity: Rarity; // 'mundane' | 'qitouched' | 'empowered' | 'resplendent' | 'incandescent' | 'transcendent'
intro: EventStep[]; // Event steps shown when entering this tile
}
entrance - Starting tile. Exactly one per expedition.
{ ...baseTile, kind: 'entrance' }
exit - Goal tile. Reaching this tile completes the expedition.
{ ...baseTile, kind: 'exit' }
extract - Grants extraction rewards (materials, items).
{ ...baseTile, kind: 'extract', extractCount: 3 }
combat - Triggers a combat encounter.
interface CombatTile {
kind: "combat";
modifier?: number;
enemyCount?: number;
items?: { item: ItemDesc; count: number; }[];
name: string;
icon: string;
bg: string;
description: Translatable;
rarity: Rarity;
intro: EventStep[];
}
rest - Restores team health and removes debuffs.
{ ...baseTile, kind: 'rest', baseRestore: 50 }
treasure - Grants treasure room loot.
{ ...baseTile, kind: 'treasure' }
boss - Triggers a boss fight with scaled rewards.
interface BossTile {
kind: "boss";
modifier?: number;
items?: { item: ItemDesc; count: number; }[];
name: string;
icon: string;
bg: string;
description: Translatable;
rarity: Rarity;
intro: EventStep[];
}
buff - Applies a positive buff to the team.
{ ...baseTile, kind: 'buff' }
debuff - Applies a negative debuff to the team.
{ ...baseTile, kind: 'debuff' }
challenge - High-difficulty combat with extra rewards.
interface ChallengeTile {
kind: "challenge";
modifier?: number;
items?: { item: ItemDesc; count: number; }[];
name: string;
icon: string;
bg: string;
description: Translatable;
rarity: Rarity;
intro: EventStep[];
}
puzzle - A puzzle encounter, optionally with enemies.
interface PuzzleTile {
kind: "puzzle";
enemies?: boolean;
name: string;
icon: string;
bg: string;
description: Translatable;
rarity: Rarity;
intro: EventStep[];
}
boonBane - Random beneficial or harmful effect.
{ ...baseTile, kind: 'boonBane' }