afnm-types 0.6.50 → 0.6.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- export type GameScreen = 'location' | 'recipe' | 'mission' | 'craftingHall' | 'manual' | 'cultivation' | 'map' | 'healer' | 'market' | 'favour' | 'herbField' | 'mine' | 'recipeLibrary' | 'requestBoard' | 'compendium' | 'library' | 'altar' | 'research' | 'reforge' | 'pillarGrid' | 'fallenStar' | 'trainingGround' | 'tenThousandFlames' | 'lifeScreen' | 'soulShardDelve' | 'enchantmentShop';
1
+ export type GameScreen = 'location' | 'recipe' | 'mission' | 'craftingHall' | 'manual' | 'cultivation' | 'map' | 'healer' | 'market' | 'favour' | 'herbField' | 'mine' | 'recipeLibrary' | 'requestBoard' | 'compendium' | 'library' | 'altar' | 'research' | 'reforge' | 'pillarGrid' | 'fallenStar' | 'trainingGround' | 'tenThousandFlames' | 'lifeScreen' | 'soulShardDelve' | 'enchantmentShop' | 'challengeBoard';
package/dist/buff.d.ts CHANGED
@@ -9,7 +9,7 @@ interface BaseTechniqueCondition {
9
9
  /**
10
10
  * When true, triggered effects (triggeredBuffEffects) on this buff will still
11
11
  * run even when this condition fails. The condition still blocks normal
12
- * onTechniqueEffects and onRoundEffects.
12
+ * beforeTechniqueEffects, afterTechniqueEffects, and onRoundEffects.
13
13
  */
14
14
  allowTriggers?: boolean;
15
15
  }
@@ -84,8 +84,10 @@ export interface Buff {
84
84
  noneType?: string;
85
85
  secondaryType?: TechniqueElement | 'origin';
86
86
  enhancement?: number;
87
- onTechniqueEffects: BuffEffect[];
88
- onRoundEffects: BuffEffect[];
87
+ beforeTechniqueEffects?: BuffEffect[];
88
+ afterTechniqueEffects?: BuffEffect[];
89
+ onStackGainEffects?: BuffEffect[];
90
+ onRoundEffects?: BuffEffect[];
89
91
  onRoundStartEffects?: BuffEffect[];
90
92
  onCombatStartEffects?: BuffEffect[];
91
93
  interceptBuffEffects?: {
@@ -131,7 +133,6 @@ export interface Buff {
131
133
  effects?: BuffEffect[];
132
134
  }[];
133
135
  priority?: number;
134
- afterTechnique?: boolean;
135
136
  combatImage?: BuffCombatImage;
136
137
  maxStacks?: number;
137
138
  upgradeKey?: string;
@@ -0,0 +1,113 @@
1
+ import { BreakthroughState } from './breakthrough';
2
+ import { PhysicalStatistic } from './stat';
3
+ import { KnownTechnique } from './technique';
4
+ import { StoredStance, StoredStyle, AutoUseLoadout } from './entity';
5
+ import { TechniqueElement } from './element';
6
+ import { ItemDesc } from './item';
7
+ /**
8
+ * Optional combat modifiers chosen by the submitter when posting a challenge seal.
9
+ * Applied symmetrically to both sides when the challenge is accepted.
10
+ */
11
+ export interface ChallengeModifiers {
12
+ /** Number of grace period rounds both sides get damage reduction at the start of combat (0 or undefined = no grace period). */
13
+ gracePeriodRounds?: number;
14
+ /** Restrict both sides to a single technique school — all other schools are disabled (null or undefined = no restriction). */
15
+ schoolVow?: TechniqueElement | null;
16
+ /** Disable auto-use items (pills/concoctions) for both sides. */
17
+ disableItems?: boolean;
18
+ /** Disable qi droplets for both sides — techniques with droplet costs cannot be used. */
19
+ disableDroplets?: boolean;
20
+ /** Both sides gain 1 stack of Shadow Sickness at the start of each round. */
21
+ shadowSickness?: boolean;
22
+ }
23
+ export interface SubmittedEquipment {
24
+ clothing: string | ItemDesc | undefined;
25
+ mount: string | ItemDesc | undefined;
26
+ talismans: (string | ItemDesc | undefined)[];
27
+ artefacts: (string | ItemDesc | undefined)[];
28
+ }
29
+ /**
30
+ * Serialisable representation of a player build, ready to send to the server.
31
+ * Stored temporarily in GameEventState.pendingBuildSubmission until dispatched.
32
+ *
33
+ * Stat normalisation:
34
+ * physicalStatRelativeRatios — each stat's share of the player's total
35
+ * physical stat pool (all values sum to 1.0). On the server the NPC
36
+ * rebuild allocates `getExpectedPhysicalStat(realm) × 6` points distributed
37
+ * according to these ratios, so modded absolute values never inflate the NPC.
38
+ */
39
+ export interface NormalizedBuildSubmission {
40
+ /** Game version that produced the submission. */
41
+ version: string;
42
+ /** Stable per-save UUID that identifies the submitting player. */
43
+ playerId: string;
44
+ /** Player-chosen display name for the board entry. */
45
+ displayName: string;
46
+ realm: string;
47
+ realmProgress: string;
48
+ /**
49
+ * Relative distribution of physical stats. Each value is the fraction of
50
+ * the player's *total* physical stat pool allocated to that stat
51
+ * (values sum to 1.0 across all six stats).
52
+ */
53
+ physicalStatRelativeRatios: Record<PhysicalStatistic, number>;
54
+ /** Raw battlesense value. */
55
+ battlesense: number;
56
+ /** All techniques known by the player at time of submission. */
57
+ knownTechniques: KnownTechnique[];
58
+ /** The active style at time of submission, or null. */
59
+ currentStyle: StoredStyle | null;
60
+ /** Equipment item names at time of submission. */
61
+ equipment: SubmittedEquipment;
62
+ submittedAt: string;
63
+ /** Player sex — used to select the appropriate default NPC sprite when images are not yet approved. */
64
+ sex?: string;
65
+ /**
66
+ * Portrait index into the player sprite array for this sex.
67
+ * Indices < MALE/FEMALE_BUILT_IN_COUNT refer to base-game portraits and are
68
+ * used directly at roster load time. Indices >= the built-in count indicate a
69
+ * mod sprite; these are serialised to base64 and sent through the custom image
70
+ * approval flow the same way user-uploaded images are.
71
+ */
72
+ imageIndex: number;
73
+ /** Background IDs — resolved via backgroundMap at roster load time. */
74
+ background: string[];
75
+ /** Destiny IDs — resolved via destiniesMap for combat stat bonuses. */
76
+ destiny?: string[];
77
+ /** Names of consumed stat pills — resolved via itemMap for permanent rawStats bonuses. */
78
+ consumedStatPills?: string[];
79
+ /** Full breakthrough state — used for condensation art, pillar shards, figments, etc. */
80
+ breakthrough: BreakthroughState;
81
+ /** Technique affinities — used to apply the player's affinity multipliers in combat. */
82
+ affinities?: Partial<Record<string, number>>;
83
+ /** Pills per round from socialStats — applied directly to the combat entity. */
84
+ pillsPerRound?: number;
85
+ /** Qi droplets at time of submission — applied directly to the combat entity. */
86
+ qiDroplets?: number;
87
+ /**
88
+ * Custom combat image filenames (locally stored, not base64).
89
+ * Resolved to base64 before submission by the useBuildSubmission hook.
90
+ */
91
+ customImages?: {
92
+ idle?: string;
93
+ hit?: string;
94
+ support?: string;
95
+ defensive?: string;
96
+ utility?: string;
97
+ offensive?: string;
98
+ aggressive?: string;
99
+ };
100
+ /** Optional combat modifiers chosen by the submitter, applied symmetrically to both sides. */
101
+ combatModifiers?: ChallengeModifiers;
102
+ /** Active auto-use item loadout at time of submission, applied as conditional pills during challenge fights. */
103
+ currentAutoUseLoadout?: AutoUseLoadout;
104
+ /** Manifested figment (lifeform companion) at time of submission, added as a party member during challenge fights. */
105
+ lifeform?: {
106
+ primarySpecies: string;
107
+ secondarySpecies: string;
108
+ /** Display name (custom or auto-generated). */
109
+ name?: string;
110
+ stances: StoredStance[];
111
+ currentStyle?: StoredStyle;
112
+ };
113
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -273,6 +273,13 @@ export interface CharacterEncounter {
273
273
  max: number;
274
274
  };
275
275
  locations?: string[];
276
+ /**
277
+ * Marks this encounter as part of the global pity pool.
278
+ * Works the same way as `pity` on location events: the encounter's selection
279
+ * weight is multiplied by the global pity counter, and a successful pity
280
+ * encounter resets the counter.
281
+ */
282
+ pity?: true;
276
283
  }
277
284
  export type CharacterDefinition = NeutralCharacterDefinition | EnemyCharacterDefinition | CompanionCharacterDefinition;
278
285
  interface BaseCharacterDefinition {
@@ -882,6 +882,28 @@ function extractFromFile(filePath) {
882
882
  }
883
883
  }
884
884
  }
885
+ // Find shorthand property assignments where the variable name IS the property name
886
+ // e.g., `const description = 'text'; const item = { description }` (shorthand for description: description)
887
+ if (typescript_1.default.isShorthandPropertyAssignment(node)) {
888
+ const propName = node.name.text;
889
+ if ((0, config_js_1.isTranslatableProperty)(propName)) {
890
+ // Resolve the variable name to its string value
891
+ const resolved = (0, resolvers_js_1.resolveImportedString)(filePath, propName) || (0, resolvers_js_1.tryResolveExpression)(filePath, propName);
892
+ if (resolved && resolved.trim().length > 0 && !(0, config_js_1.isCamelCase)(resolved)) {
893
+ const { parentName, objectType, nestedPath } = getParentObjectInfo(node, filePath);
894
+ const contextField = nestedPath ? `${nestedPath}/${propName}` : propName;
895
+ results.push({
896
+ text: resolved,
897
+ file: filePath,
898
+ line: (0, template_processor_js_1.getLineNumber)(sourceFile, node.getStart()),
899
+ context: `data-${contextField}-resolved`,
900
+ original: propName,
901
+ parentId: parentName,
902
+ objectType,
903
+ });
904
+ }
905
+ }
906
+ }
885
907
  // Find array literals
886
908
  // Skip DIRECT exported arrays - they're handled by extractFromToNameMaps with proper context
887
909
  // e.g., export const combatSpeeds = ["Manual", ...] - skip
@@ -32,6 +32,7 @@ export type GameButtonFC = React.ForwardRefRenderFunction<HTMLButtonElement, But
32
32
  export type GameIconButtonFC = React.ForwardRefRenderFunction<HTMLButtonElement, IconButtonProps & {
33
33
  hoverSfx?: SoundEffectName;
34
34
  clickSfx?: SoundEffectName;
35
+ tooltip?: ReactNode;
35
36
  }>;
36
37
  export type GameCloseButtonFC = React.ForwardRefRenderFunction<HTMLButtonElement, Omit<IconButtonProps & {
37
38
  hoverSfx?: SoundEffectName;
package/dist/element.js CHANGED
@@ -23,7 +23,7 @@ export const elementToTint = {
23
23
  celestial: 'white',
24
24
  cloud: '#45d1d1',
25
25
  blossom: '#ff63dd',
26
- none: 'grey',
26
+ none: 'rgb(73, 73, 73)',
27
27
  };
28
28
  export const elementToColour = {
29
29
  weapon: 'radial-gradient(steelblue 50%, black)',
@@ -32,7 +32,7 @@ export const elementToColour = {
32
32
  celestial: 'radial-gradient(white 30%, black)',
33
33
  cloud: 'radial-gradient(teal 50%, black)',
34
34
  blossom: 'radial-gradient(#d654b9 50%, black)',
35
- none: 'radial-gradient(darkgrey 50%, black)',
35
+ none: 'radial-gradient(rgb(53, 53, 53) 50%, black 85%)',
36
36
  };
37
37
  export const elementToColourRaw = {
38
38
  weapon: 'steelblue',
@@ -41,5 +41,5 @@ export const elementToColourRaw = {
41
41
  celestial: 'white',
42
42
  cloud: 'teal',
43
43
  blossom: '#d654b9',
44
- none: 'darkgrey',
44
+ none: 'rgb(15, 15, 15)',
45
45
  };
package/dist/entity.d.ts CHANGED
@@ -35,6 +35,10 @@ export interface PlayerEntity {
35
35
  utility?: string;
36
36
  offensive?: string;
37
37
  aggressive?: string;
38
+ craftingSupport?: string;
39
+ craftingStabilize?: string;
40
+ craftingRefine?: string;
41
+ craftingFusion?: string;
38
42
  };
39
43
  realm: Realm;
40
44
  realmOverride?: Realm;
@@ -58,7 +62,10 @@ export interface PlayerEntity {
58
62
  clothing: ItemDesc | undefined;
59
63
  talismans: (ItemDesc | undefined)[];
60
64
  artefacts: (ItemDesc | undefined)[];
61
- autoUseItems?: (string | undefined)[];
65
+ /** @deprecated legacy only, do not use in new code */
66
+ autoUseItems?: (AutoUseItem | string | undefined)[];
67
+ currentAutoUseLoadout?: AutoUseLoadout;
68
+ storedAutoUseLoadouts?: AutoUseLoadout[];
62
69
  quickAccess?: (string | undefined)[];
63
70
  cauldron: ItemDesc | undefined;
64
71
  flame: ItemDesc | undefined;
@@ -181,6 +188,9 @@ export interface EnemyEntity {
181
188
  phases?: EnemyEntity[];
182
189
  party?: PartyMemberConfig[];
183
190
  preservePartyMembers?: boolean;
191
+ /** When set, stance selection uses the full player-style algorithm instead of stanceRotation/rotationOverrides. */
192
+ playerStyleStances?: StoredStance[];
193
+ playerStyleCycles?: ConditionalCycle[];
184
194
  }
185
195
  export type PartyMemberConfig = EnemyEntity;
186
196
  export interface CombatMessage {
@@ -295,6 +305,13 @@ export interface CombatEntity {
295
305
  usedOverrides: boolean[];
296
306
  usedPills: boolean[];
297
307
  defenseFactor: number;
308
+ /** Tracks per-combat state for enemies using playerStyleStances. Mirrors PlayerStanceData. */
309
+ enemyStanceData?: {
310
+ playerStanceIndex: number;
311
+ usedPlayerStanceOpeners: boolean[];
312
+ lastPlayerStance: string;
313
+ lastCycleKey?: string;
314
+ };
298
315
  battlesenseBonus?: number;
299
316
  spawnRoar?: SoundEffectName;
300
317
  buffs: Buff[];
@@ -315,6 +332,8 @@ export interface CombatEntity {
315
332
  partyId?: string;
316
333
  /** When true, critchance is forced to 0 for this entity (used in training ground) */
317
334
  noCrit?: boolean;
335
+ /** Cached hash for getVariablesFromEntity. Cleared whenever stats or buffs mutate. */
336
+ _cachedHash?: string;
318
337
  }
319
338
  export type StanceRule = SingleStance | RandomStance;
320
339
  export interface SingleStance {
@@ -342,13 +361,31 @@ export interface ConditionalCycle {
342
361
  }>;
343
362
  operator: 'AND' | 'OR';
344
363
  }
364
+ export interface AutoUseCondition {
365
+ condition: string;
366
+ check: '<' | '==' | '>' | '!=';
367
+ value: number;
368
+ }
369
+ export interface AutoUseItem {
370
+ item?: string;
371
+ conditions?: AutoUseCondition[];
372
+ maxCount?: number;
373
+ }
374
+ export interface AutoUseLoadout {
375
+ id: string;
376
+ name: string;
377
+ autoName?: boolean;
378
+ slots: AutoUseItem[];
379
+ }
345
380
  export interface StoredStyle {
346
381
  name: string;
347
382
  id: string;
348
383
  autoName?: boolean;
349
384
  stances: StoredStance[];
350
385
  conditionalCycles?: ConditionalCycle[];
351
- autoUseItems?: (string | undefined)[];
386
+ /** @deprecated legacy only, do not use in new code */
387
+ autoUseItems?: (AutoUseItem | string | undefined)[];
388
+ autoUseLoadoutId?: string;
352
389
  }
353
390
  export type StoredRule = OpenerStoredRule | RotationStoredRule | ConditionalStoredRule | ConditionalRotationStoredRule;
354
391
  export interface OpenerStoredRule {
@@ -15,4 +15,4 @@
15
15
  * };
16
16
  * }
17
17
  */
18
- export declare const GAME_VERSION = "0.6.50";
18
+ export declare const GAME_VERSION = "0.6.52";
@@ -15,4 +15,4 @@
15
15
  * };
16
16
  * }
17
17
  */
18
- export const GAME_VERSION = "0.6.50";
18
+ export const GAME_VERSION = "0.6.52";
package/dist/item.d.ts CHANGED
@@ -13,7 +13,7 @@ import type { Realm, RealmProgress } from './realm';
13
13
  import type { RecipeDifficulty } from './RecipeDifficulty';
14
14
  import type { CombatStatistic, CombatStatsMap, CraftingStatistic, CraftingStatsMap, PhysicalStatistic, Scaling, SocialStatistic } from './stat';
15
15
  import type { Technique, TechniqueEffect } from './technique';
16
- export declare const itemKinds: readonly ["clothing", "talisman", "artefact", "mount", "cauldron", "flame", "upgrade", "fruit", "elixir", "recipe", "technique", "action", "transport_seal", "enchantment", "pill", "reagent", "concoction", "consumable", "recuperation", "formation", "breakthrough", "pillar_shard", "material", "flare", "mystical_key", "condensation_art", "blueprint", "trophy", "treasure", "token", "life_essence", "device", "manual", "pillar_pattern"];
16
+ export declare const itemKinds: readonly ["clothing", "talisman", "artefact", "mount", "cauldron", "flame", "upgrade", "fruit", "elixir", "recipe", "technique", "action", "transport_seal", "enchantment", "pill", "reagent", "concoction", "consumable", "recuperation", "formation", "breakthrough", "pillar_shard", "material", "flare", "mystical_key", "condensation_art", "blueprint", "trophy", "treasure", "token", "life_essence", "device", "manual", "pillar_pattern", "local_map"];
17
17
  export type ItemKind = (typeof itemKinds)[number];
18
18
  export declare const itemKindToName: {
19
19
  [key in ItemKind]: string;
@@ -26,7 +26,7 @@ export type ItemCostMap = {
26
26
  };
27
27
  export declare const buyItemCostMap: ItemCostMap;
28
28
  export declare const sellItemCostMap: ItemCostMap;
29
- export type Item = TechniqueItem | TechniqueCrystalItem | TechniqueShardItem | TechniqueEnhancementDust | TransportSealItem | SpiritFruitItem | RecipeItem | ElixirItem | TreasureItem | CraftingItem | ClothingItem | TalismanItem | ArtefactItem | PillItem | ConcoctionItem | CombatItem | CauldronItem | FlameItem | BreakthroughItem | RecuperationItem | MountItem | FlareItem | CraftingTechniqueItem | EnchantmentItem | MysticalKeyItem | CondensationArtItem | FormationItem | PillarShardItem | TrophyItem | BlueprintItem | TokenItem | UpgradeItem | CraftingReagentItem | LifeEssenceItem | DeviceItem | ManualItem | PillarPatternItem;
29
+ export type Item = TechniqueItem | TechniqueCrystalItem | TechniqueShardItem | TechniqueEnhancementDust | TransportSealItem | SpiritFruitItem | RecipeItem | ElixirItem | TreasureItem | CraftingItem | ClothingItem | TalismanItem | ArtefactItem | PillItem | ConcoctionItem | CombatItem | CauldronItem | FlameItem | BreakthroughItem | RecuperationItem | MountItem | FlareItem | CraftingTechniqueItem | EnchantmentItem | MysticalKeyItem | CondensationArtItem | FormationItem | PillarShardItem | TrophyItem | BlueprintItem | TokenItem | UpgradeItem | CraftingReagentItem | LifeEssenceItem | DeviceItem | ManualItem | PillarPatternItem | LocalMapItem;
30
30
  interface ItemBase {
31
31
  kind: ItemKind;
32
32
  name: string;
@@ -96,6 +96,7 @@ export interface ManualItem extends ItemBase {
96
96
  }
97
97
  export interface ManualStyle {
98
98
  name: string;
99
+ displayName?: Translatable;
99
100
  stances: ManualStance[];
100
101
  }
101
102
  export interface ManualStance {
@@ -528,4 +529,37 @@ export interface PillarPatternItem extends ItemBase {
528
529
  };
529
530
  }[];
530
531
  }
532
+ /** Describes the enemy modifier and reward scaling applied during a local map run. */
533
+ export interface LocalMapModifier {
534
+ /** Display name shown in the UI (e.g. "Bulwark", "Frenzied"). */
535
+ name: string;
536
+ displayName?: Translatable;
537
+ /** Yield multiplier applied to drops and shards. */
538
+ yieldMult: number;
539
+ /** HP stat multiplier bonus added to the enemy's base. */
540
+ hpBonus: number;
541
+ /** Power stat multiplier bonus added to the enemy's base. */
542
+ powerBonus: number;
543
+ /** Pool of buffs to draw from. A random subset is applied to each enemy. */
544
+ buffPool: Buff[];
545
+ /** Minimum number of buffs selected from the pool. */
546
+ minBuffs: number;
547
+ /** Maximum number of buffs selected from the pool. */
548
+ maxBuffs: number;
549
+ }
550
+ export interface LocalMapItem extends ItemBase {
551
+ kind: 'local_map';
552
+ /** The cultivation realm this map applies to (must match the location's realm). */
553
+ realm: Realm;
554
+ /** Number of sequential fights triggered when this map is used. */
555
+ encounterCount: {
556
+ min: number;
557
+ max: number;
558
+ };
559
+ /**
560
+ * If present, enemies are modified and drop rates are scaled.
561
+ * Absent on standard/scout maps — plain fights, no modifiers.
562
+ */
563
+ modifier?: LocalMapModifier;
564
+ }
531
565
  export {};
package/dist/item.js CHANGED
@@ -33,6 +33,7 @@ export const itemKinds = [
33
33
  'device',
34
34
  'manual',
35
35
  'pillar_pattern',
36
+ 'local_map',
36
37
  ];
37
38
  export const itemKindToName = {
38
39
  clothing: 'Clothing',
@@ -69,6 +70,7 @@ export const itemKindToName = {
69
70
  device: 'Device',
70
71
  manual: 'Manual',
71
72
  pillar_pattern: 'Pillar Pattern',
73
+ local_map: 'Local Map',
72
74
  };
73
75
  export const itemKindPluralToName = {
74
76
  clothing: 'Clothing',
@@ -105,6 +107,7 @@ export const itemKindPluralToName = {
105
107
  device: 'Devices',
106
108
  manual: 'Manuals',
107
109
  pillar_pattern: 'Pillar Patterns',
110
+ local_map: 'Local Maps',
108
111
  };
109
112
  export const buyItemCostMap = {
110
113
  technique: 3000,
@@ -141,6 +144,7 @@ export const buyItemCostMap = {
141
144
  device: 2500,
142
145
  manual: 15000,
143
146
  pillar_pattern: 3000,
147
+ local_map: 200,
144
148
  };
145
149
  export const sellItemCostMap = {
146
150
  technique: 400,
@@ -177,6 +181,7 @@ export const sellItemCostMap = {
177
181
  device: 600,
178
182
  manual: 0,
179
183
  pillar_pattern: 500,
184
+ local_map: 20,
180
185
  };
181
186
  export const pillKindToName = {
182
187
  combat: 'Combat Pill',
@@ -38,4 +38,5 @@ export const itemTypeToHarmonyType = {
38
38
  life_essence: 'resonance',
39
39
  manual: 'resonance',
40
40
  pillar_pattern: 'resonance',
41
+ local_map: 'resonance',
41
42
  };
@@ -146,8 +146,8 @@ export interface CraftingMission {
146
146
  condition: string;
147
147
  }
148
148
  export declare const exploresPerUnlock = 3;
149
- export type BuildingType = 'cultivation' | 'manual' | 'crafting' | 'mission' | 'craftingHall' | 'healer' | 'market' | 'favourExchange' | 'vault' | 'custom' | 'herbField' | 'mine' | 'recipe' | 'requestBoard' | 'compendium' | 'mysticalRegion' | 'expedition' | 'trainingGround' | 'library' | 'house' | 'altar' | 'research' | 'reforge' | 'guild' | 'tenThousandFlames' | 'soulShardDelve' | 'enchantmentShop' | 'modBuilding';
150
- export type LocationBuilding = CultivationBuilding | ManualBuilding | CraftingBuilding | MissionBuilding | CraftingHallBuilding | HealerBuilding | MarketBuilding | VaultBuilding | FavourExchangeBuilding | CustomBuilding | HerbFieldBuilding | MineBuilding | RecipeLibraryBuilding | RequestBoardBuilding | CompendiumBuilding | MysticalRegionBuilding | TrainingGroundBuilding | LibraryBuilding | HouseBuilding | CompressionAltarBuilding | ResearchBuilding | ReforgeBuilding | GuildBuilding | TenThousandFlamesBuilding | SoulShardDelveBuilding | EnchantmentShopBuilding | ModBuilding;
149
+ export type BuildingType = 'cultivation' | 'manual' | 'crafting' | 'mission' | 'craftingHall' | 'healer' | 'market' | 'favourExchange' | 'vault' | 'custom' | 'herbField' | 'mine' | 'recipe' | 'requestBoard' | 'compendium' | 'mysticalRegion' | 'expedition' | 'trainingGround' | 'library' | 'house' | 'altar' | 'research' | 'reforge' | 'guild' | 'tenThousandFlames' | 'soulShardDelve' | 'enchantmentShop' | 'challengeBoard' | 'modBuilding';
150
+ export type LocationBuilding = CultivationBuilding | ManualBuilding | CraftingBuilding | MissionBuilding | CraftingHallBuilding | HealerBuilding | MarketBuilding | VaultBuilding | FavourExchangeBuilding | CustomBuilding | HerbFieldBuilding | MineBuilding | RecipeLibraryBuilding | RequestBoardBuilding | CompendiumBuilding | MysticalRegionBuilding | TrainingGroundBuilding | LibraryBuilding | HouseBuilding | CompressionAltarBuilding | ResearchBuilding | ReforgeBuilding | GuildBuilding | TenThousandFlamesBuilding | SoulShardDelveBuilding | EnchantmentShopBuilding | ChallengeBoardBuilding | ModBuilding;
151
151
  export type LocationBuildingState = MissionBuildingState | CraftingHallBuildingState | ShopBuildingState | RequestBoardBuildingState | HouseBuildingState | CompressionAltarBuildingState;
152
152
  interface BuildingBase {
153
153
  kind: BuildingType;
@@ -269,6 +269,9 @@ export interface MysticalRegionBuilding extends BuildingBase {
269
269
  export interface TrainingGroundBuilding extends BuildingBase {
270
270
  kind: 'trainingGround';
271
271
  }
272
+ export interface ChallengeBoardBuilding extends BuildingBase {
273
+ kind: 'challengeBoard';
274
+ }
272
275
  export interface TenThousandFlamesBuilding extends BuildingBase {
273
276
  kind: 'tenThousandFlames';
274
277
  }
package/dist/location.js CHANGED
@@ -27,5 +27,6 @@ export const buildingTypeToName = {
27
27
  tenThousandFlames: 'Furnace of Ten Thousand Flames',
28
28
  soulShardDelve: 'Soul Shard',
29
29
  enchantmentShop: 'Enchantment Shop',
30
+ challengeBoard: 'Challenge Board',
30
31
  modBuilding: '',
31
32
  };
package/dist/mod.d.ts CHANGED
@@ -43,6 +43,8 @@ import type { Tutorial } from './tutorial';
43
43
  import type { Sex } from './entity';
44
44
  import type { DamageType } from './DamageType';
45
45
  import { AvatarEffectShader } from './avatarEffects';
46
+ import type { Translatable, TranslatableString } from '../types/translatable';
47
+ import type { SaveFileInfo } from './electron';
46
48
  /**
47
49
  * Sprite images for a custom player character.
48
50
  * All images should be strings (either mod:// URLs for mod assets, or data: URLs).
@@ -301,6 +303,35 @@ export interface ModReduxAPI {
301
303
  * console.log('Completion after:', preview.progressState?.completion);
302
304
  */
303
305
  previewCraftingTechnique: (technique: CraftingTechnique, state: CraftingState) => CraftingState;
306
+ /**
307
+ * Save the current game state to a character-scoped backup file. The save is
308
+ * written to the character's backup folder with the given filename. Throws an
309
+ * error if no save is currently loaded or if the save operation fails.
310
+ * @param filename - Name of the save file (e.g. 'quicksave-001.json')
311
+ * @example
312
+ * makeSave('quicksave-001.json'); // Save to character-backup/quicksave-001.json
313
+ * makeSave('backup-myprogress.json');
314
+ */
315
+ makeSave: (filename: string) => Promise<void>;
316
+ /**
317
+ * Load game state from a character-scoped backup file. The current game state
318
+ * will be replaced by the loaded save. Throws an error if the file cannot be
319
+ * read or parsed.
320
+ * @param filename - Name of the save file to load (must exist in character backup folder)
321
+ * @example
322
+ * loadSave('quicksave-001.json'); // Load from character-backup/quicksave-001.json
323
+ * loadSave('backup-myprogress.json');
324
+ */
325
+ loadSave: (filename: string) => Promise<void>;
326
+ /**
327
+ * List all backup save files available for the current character. Throws if
328
+ * no save is currently loaded or if the operation fails.
329
+ * @returns Array of save file metadata
330
+ * @example
331
+ * const saves = listSaves();
332
+ * saves.forEach(s => console.log(s.name, new Date(s.lastModified)));
333
+ */
334
+ listSaves: () => Promise<SaveFileInfo[]>;
304
335
  };
305
336
  components: {
306
337
  GameDialog: GameDialogFC;
@@ -479,6 +510,17 @@ export interface ModAPI {
479
510
  * @param reputation - (Optional) Required reputation tier
480
511
  */
481
512
  addItemToGuild: (item: Item, stacks: number, guild: string, rank: number, valueModifier?: number, reputation?: ReputationTier) => void;
513
+ /**
514
+ * Add an item to the sect favour exchange shop.
515
+ * @param item - Item to add
516
+ * @param stacks - Number of stacks available
517
+ * @param realm - Required realm tier to purchase
518
+ * @param valueModifier - (Optional) Price multiplier (default 1.0)
519
+ * @param reputation - (Optional) Required reputation tier
520
+ * @example
521
+ * addToSectShop(myItem, 3, 'qiCondensation');
522
+ */
523
+ addToSectShop: (item: Item, stacks: number, realm: Realm, valueModifier?: number, reputation?: ReputationTier) => void;
482
524
  /**
483
525
  * Add a crafting recipe to the sect recipe library.
484
526
  * @param item - Recipe item configuration
@@ -1637,7 +1679,7 @@ export interface ModAPI {
1637
1679
  * @param defenderProtection - Defender's protection stat
1638
1680
  * @returns Final damage value
1639
1681
  */
1640
- calculateDamage: (attackPower: number, defenderDefense: number, defenderDr: number, defenderDefenseFactor: number, maxReduction: number, defenderVulnerability: number, realm: Realm, realmProgress: RealmProgress, defenderProtection: number) => number;
1682
+ calculateDamage: (attackPower: number, defenderDefense: number, defenderDr: number, defenderDefenseFactor: number, maxReduction: number, defenderVulnerability: number, realm: Realm, realmProgress: RealmProgress, defenderProtection: number, cultivatorResistance: number) => number;
1641
1683
  /**
1642
1684
  * Format a number for display (e.g. 1000 -> "1,000").
1643
1685
  * @param number - Number to format
@@ -1874,6 +1916,42 @@ export interface ModAPI {
1874
1916
  * const bonusStacks = entity.buffs.find(b => b.name === completionBonusBuffName)?.stacks ?? 0;
1875
1917
  */
1876
1918
  completionBonusBuffName: string;
1919
+ /**
1920
+ * Translate a string or translatable value to the current language.
1921
+ * @param value - The string or translatable object to translate
1922
+ * @param variables - Optional variables to substitute in the translation
1923
+ * @param context - Optional context key for disambiguation
1924
+ * @returns The translated string
1925
+ * @example
1926
+ * t('Hello world');
1927
+ * t('You have {count} items', { count: 5 });
1928
+ */
1929
+ t: (value: Translatable, variables?: Record<string, Translatable | number>, context?: string) => string;
1930
+ /**
1931
+ * Translate a string with pluralization support.
1932
+ * @param count - The count to determine singular/plural
1933
+ * @param one - Translation key for count === 1 (singular)
1934
+ * @param other - Translation key for count !== 1 (plural), should use {count} placeholder
1935
+ * @param variables - Additional variables to substitute
1936
+ * @returns The translated string with pluralization applied
1937
+ * @example
1938
+ * tPlural(days, '1 day remaining', '{count} days remaining');
1939
+ */
1940
+ tPlural: (count: number, one: string, other: string, variables?: Record<string, string | number>) => string;
1941
+ /**
1942
+ * Create a deferred translation object. Use this in data definitions instead of t().
1943
+ * The translation is resolved at render time, not at module load time.
1944
+ * @param key - The English text (also serves as the translation key)
1945
+ * @param variables - Optional variables to substitute when resolved
1946
+ * @param context - Optional context key for disambiguation
1947
+ * @returns A TranslatableString object to be resolved later
1948
+ * @example
1949
+ * const item = {
1950
+ * name: 'Spirit Core',
1951
+ * description: tr('A dense crystallisation of pure qi.'),
1952
+ * };
1953
+ */
1954
+ tr: (key: string, variables?: Record<string, Translatable | number>, context?: string) => TranslatableString;
1877
1955
  };
1878
1956
  hooks: {
1879
1957
  /**
@@ -2061,15 +2139,15 @@ export interface ModAPI {
2061
2139
  }) => void;
2062
2140
  /**
2063
2141
  * Hook fired after every Redux state update. Receives the action type, the
2064
- * state before the action was applied, and the state after. Return a modified
2065
- * copy of `stateAfter` to override what is stored, or return `stateAfter`
2066
- * unchanged to leave the update as-is.
2142
+ * state before the action was applied, the state after, and a readonly copy of
2143
+ * the action payload. Return a modified copy of `stateAfter` to override what
2144
+ * is stored, or return `stateAfter` unchanged to leave the update as-is.
2067
2145
  *
2068
2146
  * This is called inside the reducer — keep the implementation fast and avoid
2069
2147
  * side-effects. Thrown exceptions are caught and logged.
2070
2148
  *
2071
2149
  * @example
2072
- * modAPI.hooks.onReduxAction((actionType, before, after) => {
2150
+ * modAPI.hooks.onReduxAction((actionType, before, after, payload) => {
2073
2151
  * if (actionType === 'inventory/addItem') {
2074
2152
  * // double every item added in hard mode
2075
2153
  * if (after.gameData.flags?.hard_mode) {
@@ -2079,7 +2157,34 @@ export interface ModAPI {
2079
2157
  * return after;
2080
2158
  * });
2081
2159
  */
2082
- onReduxAction: (interceptor: (actionType: string, stateBefore: RootState, stateAfter: RootState) => RootState) => void;
2160
+ onReduxAction: (interceptor: (actionType: string, stateBefore: RootState, stateAfter: RootState, payload: Readonly<unknown>) => RootState) => void;
2161
+ /**
2162
+ * Hook fired before a Redux action is passed to the reducer. Allows
2163
+ * intercepting and modifying the action payload, or dropping the action
2164
+ * entirely.
2165
+ *
2166
+ * Return a modified payload to replace the original (any non-null value,
2167
+ * including `undefined`, replaces the current payload), or return `null` to
2168
+ * drop the action (the reducer will not be called and the state will remain
2169
+ * unchanged). When multiple interceptors are chained, each one receives the
2170
+ * payload returned by the previous one.
2171
+ *
2172
+ * This is called before the reducer — keep the implementation fast and avoid
2173
+ * side-effects. Thrown exceptions are caught and logged.
2174
+ *
2175
+ * @example
2176
+ * modAPI.hooks.onReduxActionPayload((actionType, payload) => {
2177
+ * if (actionType === 'inventory/removeItem') {
2178
+ * const p = payload as { name: string; stacks: number };
2179
+ * // prevent blueprint removal by zeroing the stack count
2180
+ * if (modAPI.gameData.items[p.name]?.kind === 'blueprint') {
2181
+ * return { ...p, stacks: 0 };
2182
+ * }
2183
+ * }
2184
+ * return payload;
2185
+ * });
2186
+ */
2187
+ onReduxActionPayload: (interceptor: (actionType: string, payload: unknown) => unknown | null) => void;
2083
2188
  };
2084
2189
  /**
2085
2190
  * Inject UI into a named slot (dialog title or screen name).
@@ -88,6 +88,12 @@ export interface CombatState {
88
88
  };
89
89
  /** Snapshot of entire event state when combat started, restored on completion */
90
90
  eventStateSnapshot?: GameEventState;
91
+ /** Set before a challenge board spar; survives cleanupCombat so the screen can process the result on remount */
92
+ pendingChallengeResult?: {
93
+ enemyName: string;
94
+ buildId: string | null;
95
+ prevWins: number;
96
+ };
91
97
  }
92
98
  export type CraftingSliceState = ExistingCraftingState;
93
99
  export interface ResetFlag {
@@ -318,6 +324,13 @@ export interface FallenStarState {
318
324
  export interface ExpeditionState {
319
325
  activeExpeditionKey?: string;
320
326
  }
327
+ /** A single build entry submitted to the challenge board. */
328
+ export interface SubmittedBuild {
329
+ /** Server-assigned ID for this submission. */
330
+ id: string;
331
+ /** Display name chosen by the player for this board entry. */
332
+ name: string;
333
+ }
321
334
  export interface NewGameState {
322
335
  characterCreated: boolean;
323
336
  forename: string;
@@ -333,6 +346,10 @@ export interface NewGameState {
333
346
  utility?: string;
334
347
  offensive?: string;
335
348
  aggressive?: string;
349
+ craftingSupport?: string;
350
+ craftingStabilize?: string;
351
+ craftingRefine?: string;
352
+ craftingFusion?: string;
336
353
  };
337
354
  birthBackground: Background | undefined;
338
355
  childBackground: Background | undefined;
@@ -340,6 +357,11 @@ export interface NewGameState {
340
357
  createdAt: number;
341
358
  playtime: number;
342
359
  lastPlayed?: number;
360
+ /**
361
+ * Currently submitted challenge builds (up to 9).
362
+ * Each has its own server-assigned ID and player-chosen display name.
363
+ */
364
+ submittedBuilds: SubmittedBuild[];
343
365
  }
344
366
  export interface GameDataState {
345
367
  flags: Record<string, number>;
@@ -410,12 +432,20 @@ export interface RecipeFilters {
410
432
  recipeFilter: string;
411
433
  selectedRecipe: string;
412
434
  pinnedRecipes: string[];
413
- category: 'all' | ItemKind;
435
+ category: 'all' | 'pinned' | 'pinned-upgrades' | ItemKind;
414
436
  realmFilter: 'all' | Realm;
415
437
  }
438
+ export interface PinnedReforgeItem {
439
+ itemName: string;
440
+ qualityTier: number;
441
+ enchantmentKind?: string;
442
+ enchantmentRarity?: string;
443
+ mode: 'upgrade' | 'reforge';
444
+ }
416
445
  export interface CharacterUiPreferencesState {
417
446
  techniqueFilter: string;
418
447
  recipeFilters: RecipeFilters;
448
+ pinnedReforgeItems: PinnedReforgeItem[];
419
449
  }
420
450
  export interface RootState {
421
451
  gameData: GameDataState;
package/dist/stat.d.ts CHANGED
@@ -3,7 +3,7 @@ import type { TechniqueElement } from './element';
3
3
  export declare const physicalStatistics: readonly ["eyes", "meridians", "dantian", "muscles", "digestion", "flesh"];
4
4
  export declare const socialStatistics: readonly ["age", "lifespan", "charisma", "battlesense", "craftskill", "artefactslots", "talismanslots", "condenseEfficiency", "pillsPerRound"];
5
5
  export declare const craftingStatistics: readonly ["maxpool", "pool", "maxtoxicity", "toxicity", "resistance", "itemEffectiveness", "control", "intensity", "critchance", "critmultiplier", "pillsPerRound", "poolCostPercentage", "stabilityCostPercentage", "successChanceBonus", "poolCostFlat"];
6
- export declare const combatStatistics: readonly ["maxhp", "hp", "maxbarrier", "barrier", "maxtoxicity", "toxicity", "resistance", "pillsPerRound", "itemEffectiveness", "power", "artefactpower", "critchance", "defense", "protection", "dr", "barrierMitigation", "lifesteal", "critmultiplier", "vulnerability", "weakness", "fistBoost", "blossomBoost", "weaponBoost", "cloudBoost", "bloodBoost", "celestialBoost", "fistAffinity", "blossomAffinity", "weaponAffinity", "cloudAffinity", "bloodAffinity", "celestialAffinity", "noneAffinity", "qiDroplets", "fistDisabled", "bloodDisabled", "blossomDisabled", "cloudDisabled", "celestialDisabled", "weaponDisabled", "noneDisabled", "fistResistance", "blossomResistance", "weaponResistance", "cloudResistance", "bloodResistance", "celestialResistance", "damageBoost", "healingBoost", "barrierBoost", "overheal", "barrierBleed", "formationPartRecovery", "overcrit"];
6
+ export declare const combatStatistics: readonly ["maxhp", "hp", "maxbarrier", "barrier", "maxtoxicity", "toxicity", "resistance", "pillsPerRound", "itemEffectiveness", "power", "artefactpower", "critchance", "defense", "protection", "dr", "barrierMitigation", "lifesteal", "critmultiplier", "vulnerability", "weakness", "fistBoost", "blossomBoost", "weaponBoost", "cloudBoost", "bloodBoost", "celestialBoost", "fistAffinity", "blossomAffinity", "weaponAffinity", "cloudAffinity", "bloodAffinity", "celestialAffinity", "noneAffinity", "qiDroplets", "fistDisabled", "bloodDisabled", "blossomDisabled", "cloudDisabled", "celestialDisabled", "weaponDisabled", "noneDisabled", "pillsDisabled", "dropletsDisabled", "fistResistance", "blossomResistance", "weaponResistance", "cloudResistance", "bloodResistance", "celestialResistance", "damageBoost", "healingBoost", "barrierBoost", "overheal", "barrierBleed", "formationPartRecovery", "overcrit", "cultivatorResistance"];
7
7
  export type PhysicalStatistic = (typeof physicalStatistics)[number];
8
8
  export type SocialStatistic = (typeof socialStatistics)[number];
9
9
  export type CraftingStatistic = (typeof craftingStatistics)[number];
package/dist/stat.js CHANGED
@@ -76,6 +76,8 @@ export const combatStatistics = [
76
76
  'celestialDisabled',
77
77
  'weaponDisabled',
78
78
  'noneDisabled',
79
+ 'pillsDisabled',
80
+ 'dropletsDisabled',
79
81
  'fistResistance',
80
82
  'blossomResistance',
81
83
  'weaponResistance',
@@ -89,6 +91,7 @@ export const combatStatistics = [
89
91
  'barrierBleed',
90
92
  'formationPartRecovery',
91
93
  'overcrit',
94
+ 'cultivatorResistance',
92
95
  ];
93
96
  export const baseStatNumber = 10;
94
97
  export const expectedHpPerFlesh = 1000;
@@ -113,7 +116,7 @@ export const combatStatToName = {
113
116
  power: 'Power',
114
117
  artefactpower: 'Artefact Power',
115
118
  critchance: 'Crit Chance',
116
- defense: 'Defense',
119
+ defense: 'Armour',
117
120
  protection: 'Protection',
118
121
  dr: 'Damage Resistance',
119
122
  barrierMitigation: 'Barrier Effectiveness',
@@ -142,6 +145,8 @@ export const combatStatToName = {
142
145
  celestialDisabled: '<n>Celestial</n> techniques are disabled',
143
146
  weaponDisabled: '<n>Weapon</n> techniques are disabled',
144
147
  noneDisabled: '<n>Neutral</n> techniques are disabled',
148
+ pillsDisabled: 'Items are disabled',
149
+ dropletsDisabled: 'Qi Droplets are disabled',
145
150
  pillsPerRound: 'Items Per Round',
146
151
  itemEffectiveness: 'Item Effectiveness',
147
152
  fistResistance: 'Fist Resistance',
@@ -157,6 +162,7 @@ export const combatStatToName = {
157
162
  barrierBleed: 'Barrier Bleed',
158
163
  formationPartRecovery: 'Formation Part Recovery',
159
164
  overcrit: 'Overcrit',
165
+ cultivatorResistance: 'Cultivator Resistance',
160
166
  };
161
167
  export const combatStatToDescription = {
162
168
  maxhp: 'The amount of damage you can take before you are unable to continue.',
@@ -198,6 +204,8 @@ export const combatStatToDescription = {
198
204
  celestialDisabled: '',
199
205
  weaponDisabled: '',
200
206
  noneDisabled: '',
207
+ pillsDisabled: '',
208
+ dropletsDisabled: '',
201
209
  fistResistance: '',
202
210
  blossomResistance: '',
203
211
  weaponResistance: '',
@@ -213,6 +221,7 @@ export const combatStatToDescription = {
213
221
  barrierBleed: '',
214
222
  formationPartRecovery: '',
215
223
  overcrit: '',
224
+ cultivatorResistance: '',
216
225
  };
217
226
  // Uncommon stats that need auxiliary tooltips when they appear on buffs
218
227
  // These descriptions only show as aux tooltips in buff tooltips, not in the stats dialog
@@ -261,7 +270,7 @@ export const craftingStatToDescription = {
261
270
  critmultiplier: 'The effectiveness of your critical actions.',
262
271
  successChanceBonus: '',
263
272
  itemEffectiveness: 'The effectiveness of your pills and reagents.',
264
- poolCostFlat: 'A flat amount added to the Qi Pool cost of every crafting action.',
273
+ poolCostFlat: '',
265
274
  };
266
275
  export const socialStatToName = {
267
276
  charisma: 'Charisma',
@@ -69,6 +69,7 @@ interface BaseTechniqueEffect {
69
69
  condition?: TechniqueCondition;
70
70
  triggerKey?: string;
71
71
  isAdditionalTooltip?: boolean;
72
+ cantUpgrade?: boolean;
72
73
  }
73
74
  interface BuffSelfTechniqueEffect extends BaseTechniqueEffect {
74
75
  kind: 'buffSelf';
@@ -159,7 +160,7 @@ export interface PermanentStatChangeTechniqueEffect extends BaseTechniqueEffect
159
160
  stat: PhysicalStatistic | SocialStatistic;
160
161
  amount: Scaling;
161
162
  }
162
- export type TechniqueMastery = PowerTechniqueMastery | EffectTechniqueMastery | CritChanceTechniqueMastery | CritMultiplierTechniqueMastery | UpgradeTechniqueMastery;
163
+ export type TechniqueMastery = PowerTechniqueMastery | EffectTechniqueMastery | CritChanceTechniqueMastery | CritMultiplierTechniqueMastery | UpgradeTechniqueMastery | OverwriteEffectsTechniqueMastery;
163
164
  interface BaseTechniqueMastery {
164
165
  condition?: TechniqueCondition;
165
166
  tooltip?: Translatable;
@@ -188,6 +189,11 @@ export interface UpgradeTechniqueMastery extends BaseTechniqueMastery {
188
189
  change: number;
189
190
  shouldMultiply?: boolean;
190
191
  }
192
+ export interface OverwriteEffectsTechniqueMastery extends BaseTechniqueMastery {
193
+ kind: 'overwriteEffects';
194
+ tooltip: Translatable;
195
+ newEffects: TechniqueEffect[];
196
+ }
191
197
  export type TechniqueMasteryRarityMap = {
192
198
  [key in Rarity]: TechniqueMastery | undefined;
193
199
  } & {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "afnm-types",
3
- "version": "0.6.50",
3
+ "version": "0.6.52",
4
4
  "description": "Type definitions for Ascend From Nine Mountains",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",