afnm-types 0.6.53 → 0.6.54-3

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.
@@ -11,83 +11,83 @@
11
11
  // Replicates the hand-authored glitch art style: strong persistent chromatic
12
12
  // aberration (cyan/magenta fringing always visible), periodic brightness
13
13
  // washout, and horizontal scanline tears during burst events.
14
- const GLITCH_FRAG_SRC = `#version 300 es
15
- precision mediump float;
16
- in vec2 v_uv;
17
- uniform sampler2D u_texA;
18
- uniform sampler2D u_texB;
19
- uniform float u_mix;
20
- uniform float u_aspectA;
21
- uniform float u_aspectB;
22
- uniform float u_canvasAspect;
23
- uniform float u_time;
24
- out vec4 fragColor;
25
-
26
- float hash(float n) {
27
- return fract(sin(n) * 43758.5453);
28
- }
29
-
30
- vec4 sampleContain(sampler2D tex, vec2 uv, float texAspect) {
31
- float rel = texAspect / u_canvasAspect;
32
- vec2 scale;
33
- if (rel > 1.0) {
34
- scale = vec2(1.0, 1.0 / rel);
35
- } else {
36
- scale = vec2(rel, 1.0);
37
- }
38
- vec2 offset = (1.0 - scale) * 0.5;
39
- vec2 mapped = (uv - offset) / scale;
40
- if (mapped.x < 0.0 || mapped.x > 1.0 || mapped.y < 0.0 || mapped.y > 1.0) {
41
- return vec4(0.0);
42
- }
43
- return texture(tex, mapped);
44
- }
45
-
46
- void main() {
47
- vec2 uv = v_uv;
48
-
49
- // Glitch burst timing — ~0.8 event slots per second, 35% chance each.
50
- float eventT = floor(u_time * 0.8);
51
- float isGlitching = step(0.65, hash(eventT * 1.618));
52
- // Sharp onset, slow decay — the burst lingers before fading.
53
- float slotFrac = fract(u_time * 0.8);
54
- float decay = (1.0 - smoothstep(0.1, 0.9, slotFrac)) * isGlitching;
55
-
56
- // Band-based horizontal displacement (scanline tearing) during bursts.
57
- float band = floor(uv.y * 24.0);
58
- float bandActive = step(0.55, hash(band + eventT * 23.7));
59
- float bandShift = (hash(band * 5.2 + eventT * 7.1) * 2.0 - 1.0) * 0.025;
60
- float shift = bandShift * bandActive * decay;
61
-
62
- // Occasional large block tears.
63
- float bigTear = step(0.92, hash(band * 3.7 + eventT * 11.3));
64
- shift += (hash(band * 9.1 + eventT * 4.7) * 2.0 - 1.0) * 0.08 * bigTear * decay;
65
-
66
- vec2 shiftedUv = vec2(uv.x + shift, uv.y);
67
-
68
- // Chromatic aberration: strong baseline always present, amplified by bursts.
69
- float aberr = 0.012 + 0.018 * decay;
70
-
71
- float rA = sampleContain(u_texA, vec2(shiftedUv.x + aberr, shiftedUv.y), u_aspectA).r;
72
- float gA = sampleContain(u_texA, shiftedUv, u_aspectA).g;
73
- float bA = sampleContain(u_texA, vec2(shiftedUv.x - aberr, shiftedUv.y), u_aspectA).b;
74
- float aA = sampleContain(u_texA, shiftedUv, u_aspectA).a;
75
- vec4 colA = vec4(rA, gA, bA, aA);
76
-
77
- float rB = sampleContain(u_texB, vec2(shiftedUv.x + aberr, shiftedUv.y), u_aspectB).r;
78
- float gB = sampleContain(u_texB, shiftedUv, u_aspectB).g;
79
- float bB = sampleContain(u_texB, vec2(shiftedUv.x - aberr, shiftedUv.y), u_aspectB).b;
80
- float aB = sampleContain(u_texB, shiftedUv, u_aspectB).a;
81
- vec4 colB = vec4(rB, gB, bB, aB);
82
-
83
- vec4 col = mix(colA, colB, u_mix);
84
-
85
- // Brightness washout during bursts — push toward overexposed white,
86
- // scaled by alpha so transparent edges don't bloom.
87
- float blowout = decay * 0.45;
88
- col.rgb = mix(col.rgb, vec3(1.0), blowout * col.a);
89
-
90
- fragColor = col;
14
+ const GLITCH_FRAG_SRC = `#version 300 es
15
+ precision mediump float;
16
+ in vec2 v_uv;
17
+ uniform sampler2D u_texA;
18
+ uniform sampler2D u_texB;
19
+ uniform float u_mix;
20
+ uniform float u_aspectA;
21
+ uniform float u_aspectB;
22
+ uniform float u_canvasAspect;
23
+ uniform float u_time;
24
+ out vec4 fragColor;
25
+
26
+ float hash(float n) {
27
+ return fract(sin(n) * 43758.5453);
28
+ }
29
+
30
+ vec4 sampleContain(sampler2D tex, vec2 uv, float texAspect) {
31
+ float rel = texAspect / u_canvasAspect;
32
+ vec2 scale;
33
+ if (rel > 1.0) {
34
+ scale = vec2(1.0, 1.0 / rel);
35
+ } else {
36
+ scale = vec2(rel, 1.0);
37
+ }
38
+ vec2 offset = (1.0 - scale) * 0.5;
39
+ vec2 mapped = (uv - offset) / scale;
40
+ if (mapped.x < 0.0 || mapped.x > 1.0 || mapped.y < 0.0 || mapped.y > 1.0) {
41
+ return vec4(0.0);
42
+ }
43
+ return texture(tex, mapped);
44
+ }
45
+
46
+ void main() {
47
+ vec2 uv = v_uv;
48
+
49
+ // Glitch burst timing — ~0.8 event slots per second, 35% chance each.
50
+ float eventT = floor(u_time * 0.8);
51
+ float isGlitching = step(0.65, hash(eventT * 1.618));
52
+ // Sharp onset, slow decay — the burst lingers before fading.
53
+ float slotFrac = fract(u_time * 0.8);
54
+ float decay = (1.0 - smoothstep(0.1, 0.9, slotFrac)) * isGlitching;
55
+
56
+ // Band-based horizontal displacement (scanline tearing) during bursts.
57
+ float band = floor(uv.y * 24.0);
58
+ float bandActive = step(0.55, hash(band + eventT * 23.7));
59
+ float bandShift = (hash(band * 5.2 + eventT * 7.1) * 2.0 - 1.0) * 0.025;
60
+ float shift = bandShift * bandActive * decay;
61
+
62
+ // Occasional large block tears.
63
+ float bigTear = step(0.92, hash(band * 3.7 + eventT * 11.3));
64
+ shift += (hash(band * 9.1 + eventT * 4.7) * 2.0 - 1.0) * 0.08 * bigTear * decay;
65
+
66
+ vec2 shiftedUv = vec2(uv.x + shift, uv.y);
67
+
68
+ // Chromatic aberration: strong baseline always present, amplified by bursts.
69
+ float aberr = 0.012 + 0.018 * decay;
70
+
71
+ float rA = sampleContain(u_texA, vec2(shiftedUv.x + aberr, shiftedUv.y), u_aspectA).r;
72
+ float gA = sampleContain(u_texA, shiftedUv, u_aspectA).g;
73
+ float bA = sampleContain(u_texA, vec2(shiftedUv.x - aberr, shiftedUv.y), u_aspectA).b;
74
+ float aA = sampleContain(u_texA, shiftedUv, u_aspectA).a;
75
+ vec4 colA = vec4(rA, gA, bA, aA);
76
+
77
+ float rB = sampleContain(u_texB, vec2(shiftedUv.x + aberr, shiftedUv.y), u_aspectB).r;
78
+ float gB = sampleContain(u_texB, shiftedUv, u_aspectB).g;
79
+ float bB = sampleContain(u_texB, vec2(shiftedUv.x - aberr, shiftedUv.y), u_aspectB).b;
80
+ float aB = sampleContain(u_texB, shiftedUv, u_aspectB).a;
81
+ vec4 colB = vec4(rB, gB, bB, aB);
82
+
83
+ vec4 col = mix(colA, colB, u_mix);
84
+
85
+ // Brightness washout during bursts — push toward overexposed white,
86
+ // scaled by alpha so transparent edges don't bloom.
87
+ float blowout = decay * 0.45;
88
+ col.rgb = mix(col.rgb, vec3(1.0), blowout * col.a);
89
+
90
+ fragColor = col;
91
91
  }`;
92
92
  // ─── Registry ─────────────────────────────────────────────────────────────────
93
93
  /**
package/dist/buff.d.ts CHANGED
@@ -4,6 +4,7 @@ import type { CombatStatistic, Scaling } from './stat';
4
4
  import type { DamageType } from './DamageType';
5
5
  import type { CombatEntity } from './entity';
6
6
  import { AvatarEffectId } from './avatarEffects';
7
+ import type { EffectTargeting } from './technique';
7
8
  export type TechniqueCondition = ChanceTechniqueCondition | BuffTechniqueCondition | HpTechniqueCondition | ConditionTechniqueCondition | InventoryItemTechniqueCondition;
8
9
  interface BaseTechniqueCondition {
9
10
  /**
@@ -110,6 +111,7 @@ export interface Buff {
110
111
  trigger?: TechniqueCondition;
111
112
  damageModifier: DamageModifier;
112
113
  effects?: BuffEffect[];
114
+ afterBarrier?: boolean;
113
115
  }[];
114
116
  /** Amplifies outgoing damage/barrier/heal effects. Runs before the effect is applied. */
115
117
  techniqueAmplifierEffects?: {
@@ -339,11 +341,13 @@ interface HealEffect extends BaseBuff {
339
341
  kind: 'heal';
340
342
  amount: Scaling;
341
343
  hits?: Scaling;
344
+ targeting?: EffectTargeting;
342
345
  }
343
346
  interface BarrierEffect extends BaseBuff {
344
347
  kind: 'barrier';
345
348
  amount: Scaling;
346
349
  hits?: Scaling;
350
+ targeting?: EffectTargeting;
347
351
  }
348
352
  interface CreateBuffSelfEffect extends BaseBuff {
349
353
  kind: 'buffSelf';
@@ -351,6 +355,7 @@ interface CreateBuffSelfEffect extends BaseBuff {
351
355
  buff: Buff;
352
356
  silent?: boolean;
353
357
  hideBuff?: boolean;
358
+ targeting?: EffectTargeting;
354
359
  }
355
360
  interface ConsumeBuffSelfEffect extends BaseBuff {
356
361
  kind: 'consumeSelf';
@@ -424,6 +429,7 @@ interface RepairEffect extends BaseBuff {
424
429
  group: string;
425
430
  /** Which matching buff(s) to repair: 'all', 'lowestHealth', or 'highestHealth' */
426
431
  rule: RepairRule;
432
+ targeting?: EffectTargeting;
427
433
  }
428
434
  interface ConsumeInventoryItemEffect extends BaseBuff {
429
435
  kind: 'consumeInventoryItem';
package/dist/event.d.ts CHANGED
@@ -86,7 +86,7 @@ export interface ChoiceStepChoice {
86
86
  hideIfDisabled?: boolean;
87
87
  children: EventStep[];
88
88
  }
89
- export type EventChoiceCondition = RealmCondition | PhysicalStatisticCondition | SocialStatisticCondition | ItemCondition | BuffCondition | MoneyCondition | FavourCondition | MultiCondition | AffinityCondition | QiCondition | ReputationCondition | HealthCondition | InjuredCondition;
89
+ export type EventChoiceCondition = RealmCondition | PhysicalStatisticCondition | SocialStatisticCondition | ItemCondition | BuffCondition | MoneyCondition | FavourCondition | MultiCondition | AffinityCondition | QiCondition | ReputationCondition | HealthCondition | InjuredCondition | SexualityCondition;
90
90
  export interface RealmCondition {
91
91
  kind: 'realm';
92
92
  realm: Realm;
@@ -106,6 +106,10 @@ interface InjuredCondition {
106
106
  kind: 'injured';
107
107
  injured: boolean;
108
108
  }
109
+ interface SexualityCondition {
110
+ kind: 'sexuality';
111
+ targetSex: 'male' | 'female';
112
+ }
109
113
  interface SocialStatisticCondition {
110
114
  kind: 'socialStatistic';
111
115
  stat: SocialStatistic;
@@ -15,4 +15,4 @@
15
15
  * };
16
16
  * }
17
17
  */
18
- export declare const GAME_VERSION = "0.6.53";
18
+ export declare const GAME_VERSION = "0.6.54";
@@ -15,4 +15,4 @@
15
15
  * };
16
16
  * }
17
17
  */
18
- export const GAME_VERSION = "0.6.53";
18
+ export const GAME_VERSION = "0.6.54";
package/dist/item.d.ts CHANGED
@@ -7,12 +7,12 @@ import type { TechniqueElement } from './element';
7
7
  import type { EnemyEntity, StoredRule } from './entity';
8
8
  import type { Room } from './house';
9
9
  import type { RegionContentType } from './mysticalRegion';
10
- import type { AbsorberConfig } from './soulShardDelve';
11
10
  import type { Rarity } from './rarity';
12
11
  import type { Realm, RealmProgress } from './realm';
13
12
  import type { RecipeDifficulty } from './RecipeDifficulty';
14
13
  import type { CombatStatistic, CombatStatsMap, CraftingStatistic, CraftingStatsMap, PhysicalStatistic, Scaling, SocialStatistic } from './stat';
15
14
  import type { Technique, TechniqueEffect } from './technique';
15
+ import { DelveRoom } from './soulShardDelve';
16
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: {
@@ -456,14 +456,7 @@ export interface PillarShardItem extends ItemBase {
456
456
  kind: 'pillar_shard';
457
457
  tooltip: Translatable;
458
458
  maxInstances?: number;
459
- /**
460
- * Shard subtype. 'soul' shards are used in soul shard delves — they have routing
461
- * and a room type but no combat/crafting effects. Omit for normal pillar shards.
462
- */
463
- shardSubtype?: 'soul';
464
- /** Absorber config for soul shard delves. Hardcoded on soul shards; generated
465
- * from the shard name hash for pillar shards when absent. */
466
- absorberConfig?: AbsorberConfig;
459
+ delveRoomConfig?: DelveRoom;
467
460
  variants?: PillarShardVariant[];
468
461
  stability?: number;
469
462
  portal?: {
@@ -0,0 +1,33 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { AppDispatch } from '../store';
3
+ import type { BreakthroughState } from './breakthrough';
4
+ import type { SoundEffectName } from './audio';
5
+ import type { Item } from './item';
6
+ import type { PlayerEntity } from './entity';
7
+ import type { InventoryState } from './reduxState';
8
+ interface OpenItem {
9
+ item: Item;
10
+ data: unknown;
11
+ onComplete: () => void;
12
+ }
13
+ export interface ItemActionContext {
14
+ item: Item;
15
+ player: PlayerEntity;
16
+ inventory: InventoryState;
17
+ breakthrough: BreakthroughState;
18
+ flags: Record<string, number | string | boolean>;
19
+ dispatch: AppDispatch;
20
+ setResult: (result: ReactNode) => void;
21
+ setClicked: (clicked: boolean) => void;
22
+ setOpenItem?: (openItem: OpenItem) => void;
23
+ setDoEnchant?: (doEnchant: boolean) => void;
24
+ setDoUpgrade?: (doUpgrade: boolean) => void;
25
+ setDoAppearanceChange?: (doAppearanceChange: boolean) => void;
26
+ playSfx: (sound: SoundEffectName) => void;
27
+ usageRestricted: boolean;
28
+ }
29
+ export interface ItemActionResult {
30
+ buttons: ReactNode[];
31
+ }
32
+ export type ItemActionHandler = (context: ItemActionContext) => ItemActionResult;
33
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -1,14 +1,14 @@
1
1
  export type KeybindingCategory = 'general' | 'navigation' | 'ui' | 'world' | 'combat' | 'crafting' | 'dialogs' | 'gamepad';
2
- export type KeybindingAction = 'confirm' | 'cancel' | 'pause' | 'alternateConfirm' | 'moveUp' | 'moveDown' | 'moveLeft' | 'moveRight' | 'openInventory' | 'openQuests' | 'openCharacterStats' | 'openTechniques' | 'openCalendar' | 'openWorldMap' | 'combatSelectStance0' | 'combatSelectStance1' | 'combatSelectStance2' | 'combatSelectStance3' | 'combatSelectStance4' | 'combatSelectStance5' | 'combatSelectStance6' | 'combatSelectStance7' | 'combatSelectStance8' | 'combatSelectStance9' | 'combatToggleSpeed' | 'combatToggleLog' | 'combatShowStats' | 'combatAutoBattle' | 'combatUseItem' | 'craftingAction1' | 'craftingAction2' | 'craftingAction3' | 'craftingAction4' | 'craftingAction5' | 'craftingAction6' | 'craftingAction7' | 'craftingAction8' | 'craftingAction9' | 'craftingAction10' | 'craftingAction11' | 'craftingAction12' | 'craftingAction13' | 'craftingAction14' | 'craftingAction15' | 'craftingAction16' | 'craftingAction17' | 'craftingAction18' | 'craftingAction19' | 'craftingAction20' | 'craftingAction21' | 'craftingAction22' | 'craftingAction23' | 'craftingAction24' | 'craftingAction25' | 'craftingAction26' | 'craftingAction27' | 'craftingAction28' | 'craftingAction29' | 'craftingAction30' | 'craftingAction31' | 'craftingAction32' | 'craftingAction33' | 'craftingAction34' | 'craftingAction35' | 'craftingAction36' | 'craftingAction37' | 'craftingAction38' | 'craftingAction39' | 'craftingAction40' | 'craftingAction41' | 'craftingAction42' | 'craftingAction43' | 'craftingAction44' | 'craftingAction45' | 'craftingAction46' | 'craftingAction47' | 'craftingAction48' | 'craftingAction49' | 'craftingAction50' | 'dialogChoice1' | 'dialogChoice2' | 'dialogChoice3' | 'dialogChoice4' | 'dialogChoice5' | 'dialogChoice6' | 'dialogChoice7' | 'dialogChoice8' | 'dialogChoice9' | 'gamepadConfirm' | 'gamepadCancel' | 'gamepadUp' | 'gamepadDown' | 'gamepadLeft' | 'gamepadRight';
2
+ export type KeybindingAction = 'confirm' | 'cancel' | 'pause' | 'alternateConfirm' | 'moveUp' | 'moveDown' | 'moveLeft' | 'moveRight' | 'openInventory' | 'openQuests' | 'openCharacterStats' | 'openTechniques' | 'openCalendar' | 'openWorldMap' | 'combatSelectStance0' | 'combatSelectStance1' | 'combatSelectStance2' | 'combatSelectStance3' | 'combatSelectStance4' | 'combatSelectStance5' | 'combatSelectStance6' | 'combatSelectStance7' | 'combatSelectStance8' | 'combatSelectStance9' | 'combatToggleSpeed' | 'combatToggleLog' | 'combatShowStats' | 'combatAutoBattle' | 'combatUseItem' | 'combatCancel' | 'craftingAction1' | 'craftingAction2' | 'craftingAction3' | 'craftingAction4' | 'craftingAction5' | 'craftingAction6' | 'craftingAction7' | 'craftingAction8' | 'craftingAction9' | 'craftingAction10' | 'craftingAction11' | 'craftingAction12' | 'craftingAction13' | 'craftingAction14' | 'craftingAction15' | 'craftingAction16' | 'craftingAction17' | 'craftingAction18' | 'craftingAction19' | 'craftingAction20' | 'craftingAction21' | 'craftingAction22' | 'craftingAction23' | 'craftingAction24' | 'craftingAction25' | 'craftingAction26' | 'craftingAction27' | 'craftingAction28' | 'craftingAction29' | 'craftingAction30' | 'craftingAction31' | 'craftingAction32' | 'craftingAction33' | 'craftingAction34' | 'craftingAction35' | 'craftingAction36' | 'craftingAction37' | 'craftingAction38' | 'craftingAction39' | 'craftingAction40' | 'craftingAction41' | 'craftingAction42' | 'craftingAction43' | 'craftingAction44' | 'craftingAction45' | 'craftingAction46' | 'craftingAction47' | 'craftingAction48' | 'craftingAction49' | 'craftingAction50' | 'dialogChoice1' | 'dialogChoice2' | 'dialogChoice3' | 'dialogChoice4' | 'dialogChoice5' | 'dialogChoice6' | 'dialogChoice7' | 'dialogChoice8' | 'dialogChoice9' | 'gamepadConfirm' | 'gamepadCancel' | 'gamepadUp' | 'gamepadDown' | 'gamepadLeft' | 'gamepadRight';
3
3
  export interface KeybindingDefinition {
4
- action: KeybindingAction;
4
+ action: KeybindingAction | string;
5
5
  category: KeybindingCategory;
6
6
  displayName: string;
7
7
  description: string;
8
8
  defaultKey: string;
9
9
  allowRebind: boolean;
10
10
  }
11
- export type KeybindingsMap = Record<KeybindingAction, string>;
11
+ export type KeybindingsMap = Record<KeybindingAction | string, string>;
12
12
  export declare const keybindingDefinitions: KeybindingDefinition[];
13
13
  export declare const keybindingCategoryInfo: Record<KeybindingCategory, {
14
14
  name: string;
@@ -237,6 +237,14 @@ export const keybindingDefinitions = [
237
237
  defaultKey: 's',
238
238
  allowRebind: true,
239
239
  },
240
+ {
241
+ action: 'combatCancel',
242
+ category: 'combat',
243
+ displayName: 'Cancel Auto Battle / Open Settings',
244
+ description: 'Cancel auto battle or open settings menu',
245
+ defaultKey: 'Escape',
246
+ allowRebind: true,
247
+ },
240
248
  // Crafting Actions (Number keys 1-9, then 0)
241
249
  {
242
250
  action: 'craftingAction1',
@@ -0,0 +1,8 @@
1
+ import { Realm } from './realm';
2
+ export interface Manual {
3
+ name: string;
4
+ icon: string;
5
+ realm: Realm;
6
+ techniques: string[];
7
+ stance: string[];
8
+ }
package/dist/manual.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/mod.d.ts CHANGED
@@ -49,6 +49,43 @@ import type { Translatable, TranslatableString } from './translatable';
49
49
  import type { SaveFileInfo } from './electron';
50
50
  import type { KeybindingDefinition } from './keybindings';
51
51
  import { ScreenType } from './GameScreen';
52
+ export type SkipDialogueMode = 'flash' | 'silent';
53
+ export type Sexuality = 'straight' | 'gay' | 'bisexual';
54
+ export interface GameSettingsProps {
55
+ enableStancePreview: boolean;
56
+ setEnableStancePreview: (value: boolean) => void;
57
+ removeCombatNumbers: boolean;
58
+ setRemoveCombatNumbers: (value: boolean) => void;
59
+ fastRewardAnimations: boolean;
60
+ setFastRewardAnimations: (value: boolean) => void;
61
+ showCraftingRawNumbers: boolean;
62
+ setShowCraftingRawNumbers: (value: boolean) => void;
63
+ skipSeenDialogue: boolean;
64
+ setSkipSeenDialogue: (value: boolean) => void;
65
+ skipDialogueMode: SkipDialogueMode;
66
+ setSkipDialogueMode: (value: SkipDialogueMode) => void;
67
+ hideEmptySlotWarnings: boolean;
68
+ setHideEmptySlotWarnings: (value: boolean) => void;
69
+ pauseAutoBattleOnLowHealth: boolean;
70
+ setPauseAutoBattleOnLowHealth: (value: boolean) => void;
71
+ sexuality: Sexuality;
72
+ setSexuality: (value: Sexuality) => void;
73
+ eventHistoryLimit: number;
74
+ setEventHistoryLimit: (value: number) => void;
75
+ }
76
+ export type InjectPosition = 'inside' | 'before' | 'after';
77
+ export interface NewGameIntent {
78
+ items: ItemDesc[];
79
+ techniques: string[];
80
+ recipes: string[];
81
+ destinies: string[];
82
+ quests: string[];
83
+ money: number;
84
+ favour: number;
85
+ flags: Record<string, number>;
86
+ player: PlayerEntity;
87
+ craftingActions: string[];
88
+ }
52
89
  /**
53
90
  * Sprite images for a custom player character.
54
91
  * All images should be strings (either mod:// URLs for mod assets, or data: URLs).
@@ -131,9 +168,10 @@ export interface ModReduxAPI {
131
168
  usePlaySfx: () => (name: SoundEffectName) => void;
132
169
  /**
133
170
  * Hook to register keyboard shortcuts
134
- * @param priority - Higher numbers take precedence (0-100 typical)
171
+ * @param priority - Higher numbers fully override lower numbers. Use 0 unless you have a specific reason to override other mods or base game keybindings.
135
172
  * @param binding - Map of key names to callback functions
136
173
  * @param disableSpaceOverride - If true, spacebar won't be overridden to map to Enter
174
+ * @param keyContext - Optional context string to group related keybindings (e.g. 'inventory', 'combat')
137
175
  * @example
138
176
  * useKeybinding(10, {
139
177
  * 'Escape': () => closeDialog(),
@@ -141,7 +179,16 @@ export interface ModReduxAPI {
141
179
  * 'i': () => openInventory()
142
180
  * });
143
181
  */
144
- useKeybinding: (priority: number, binding: Record<string, () => void> | undefined, disableSpaceOverride?: boolean) => void;
182
+ useKeybinding: (priority: number, binding: Record<string, () => void> | undefined, disableSpaceOverride?: boolean, keyContext?: string) => void;
183
+ /**
184
+ * Hook to access game settings/preferences.
185
+ * These are player-configured preferences like sexuality, combat display options, etc.
186
+ * @returns Object containing game settings and their setters
187
+ * @example
188
+ * const settings = useGameSettings();
189
+ * if (settings.sexuality === 'gay') { // Handle same-sex scenes }
190
+ */
191
+ useGameSettings: () => GameSettingsProps;
145
192
  actions: {
146
193
  /**
147
194
  * Navigate to a different game screen.
@@ -1061,6 +1108,21 @@ export interface ModAPI {
1061
1108
  * });
1062
1109
  */
1063
1110
  registerKeybinding: (definition: KeybindingDefinition) => void;
1111
+ /**
1112
+ * Trigger a UI refresh/reset across the entire game.
1113
+ * This causes all components wrapped in UIRefreshWrapper (including the combat screen,
1114
+ * dual cultivation screen, and other screen components) to unmount and remount,
1115
+ * forcing them to re-read their state and re-render fresh.
1116
+ *
1117
+ * Use this when your mod has modified game state that requires UI components
1118
+ * to fully re-render, such as after batch updates to Redux state or when
1119
+ * replacing content that mods might have extended.
1120
+ *
1121
+ * @example
1122
+ * // After making changes that require a full UI refresh
1123
+ * api.actions.triggerUIReset();
1124
+ */
1125
+ triggerUIReset: () => void;
1064
1126
  };
1065
1127
  utils: {
1066
1128
  /**
@@ -1995,6 +2057,24 @@ export interface ModAPI {
1995
2057
  * const screen = determineCurrentScreen(store.getState());
1996
2058
  */
1997
2059
  determineCurrentScreen: (rootState: RootState) => ScreenType;
2060
+ /**
2061
+ * Get the current keybind value for a registered keybinding action.
2062
+ * Use this to check what key is currently bound to an action, useful for
2063
+ * displaying key hints in custom UI or handling key events outside React components.
2064
+ * @param action - The action name (e.g., 'myMod.specialAction')
2065
+ * @returns The current bound key string (e.g., 'F12'), or undefined if not bound
2066
+ * @example
2067
+ * const key = modAPI.utils.getRegisteredKeybindValue('myMod.specialAction');
2068
+ * if (key) {
2069
+ * console.log(`Special action is bound to ${key}`);
2070
+ * }
2071
+ */
2072
+ getRegisteredKeybindValue: (action: string) => string | undefined;
2073
+ /**
2074
+ *
2075
+ * @returns true if a save is currently loaded and game state is available, false otherwise.
2076
+ */
2077
+ getHasSaveLoaded: () => boolean;
1998
2078
  };
1999
2079
  hooks: {
2000
2080
  /**
@@ -2062,6 +2142,26 @@ export interface ModAPI {
2062
2142
  * });
2063
2143
  */
2064
2144
  onDeriveRecipeDifficulty: (interceptor: (recipe: RecipeItem, recipeStats: CraftingRecipeStats, gameFlags: Record<string, number>) => CraftingRecipeStats) => void;
2145
+ /**
2146
+ * Hook to modify recipe ingredients before crafting calculations.
2147
+ * This runs before onDeriveRecipeDifficulty and allows mods to change
2148
+ * the items or amounts used in a recipe.
2149
+ * @param interceptor - Function to modify the recipe
2150
+ * @example
2151
+ * onModifyRecipeIngredients((recipe, flags) => {
2152
+ * if (flags.efficient_crafting) {
2153
+ * // Reduce all ingredient counts by 50%
2154
+ * const modified = { ...recipe };
2155
+ * modified.ingredients = recipe.ingredients.map(ing => ({
2156
+ * ...ing,
2157
+ * count: Math.max(1, Math.floor(ing.count * 0.5))
2158
+ * }));
2159
+ * return modified;
2160
+ * }
2161
+ * return recipe;
2162
+ * });
2163
+ */
2164
+ onModifyRecipeIngredients: (interceptor: (recipe: RecipeItem, gameFlags: Record<string, number>) => RecipeItem) => void;
2065
2165
  /**
2066
2166
  * Hook to add events after combat completion.
2067
2167
  * @param interceptor - Function returning additional event steps
@@ -2267,34 +2367,98 @@ export interface ModAPI {
2267
2367
  * return payload;
2268
2368
  * });
2269
2369
  */
2270
- onReduxActionPayload: (interceptor: (actionType: string, payload: unknown) => unknown | null) => void;
2370
+ onReduxActionPayload: (interceptor: (actionType: string, payload: unknown, stateBefore: RootState) => unknown | null) => void;
2371
+ /**
2372
+ * Called before the equipment upgrade dialog is shown. Allows mutating upgrade cost items and result (but not base item).
2373
+ *
2374
+ * @example
2375
+ * modAPI.hooks.onDeriveEquipmentUpgradeRequirement((baseItem, costItems, resultItem, flags) => {
2376
+ * // Modify the cost items (e.g., add additional requirements)
2377
+ * return { costItems: [...costItems, additionalItem], resultItem };
2378
+ * });
2379
+ */
2380
+ onDeriveEquipmentUpgradeRequirement: (interceptor: (baseItem: Item, costItems: Item[], resultItem: Item, gameFlags: Record<string, number>) => {
2381
+ costItems?: Item[];
2382
+ resultItem?: Item;
2383
+ } | undefined) => void;
2384
+ /**
2385
+ * Called before the completion dialog showing the upgraded equipment. Allows mutating upgrade cost items and result (but not base item).
2386
+ *
2387
+ * @example
2388
+ * modAPI.hooks.onCompleteEquipmentUpgrade((baseItem, costItems, resultItem, flags) => {
2389
+ * // Boost the result item's stats
2390
+ * return { costItems, resultItem: { ...resultItem, damage: (resultItem.damage ?? 0) + 10 } };
2391
+ * });
2392
+ */
2393
+ onCompleteEquipmentUpgrade: (interceptor: (baseItem: Item, costItems: Item[], resultItem: Item, gameFlags: Record<string, number>) => {
2394
+ costItems?: Item[];
2395
+ resultItem?: Item;
2396
+ } | undefined) => void;
2397
+ /**
2398
+ * Called before the equipment reforge dialog is shown. Allows mutating reforge cost items and result (but not base item).
2399
+ *
2400
+ * @example
2401
+ * modAPI.hooks.onDeriveEquipmentReforgeRequirement((baseItem, costItems, resultItem, flags) => {
2402
+ * // Modify the result item
2403
+ * return { costItems, resultItem: { ...resultItem, qualityTier: (resultItem.qualityTier ?? 0) + 1 } };
2404
+ * });
2405
+ */
2406
+ onDeriveEquipmentReforgeRequirement: (interceptor: (baseItem: Item, costItems: Item[], resultItem: Item, gameFlags: Record<string, number>) => {
2407
+ costItems?: Item[];
2408
+ resultItem?: Item;
2409
+ } | undefined) => void;
2410
+ /**
2411
+ * Called before the completion dialog showing the reforged equipment. Allows mutating reforge cost items and result (but not base item).
2412
+ *
2413
+ * @example
2414
+ * modAPI.hooks.onCompleteEquipmentReforge((baseItem, costItems, resultItem, flags) => {
2415
+ * // Boost the result item's stats
2416
+ * return { costItems, resultItem: { ...resultItem, damage: (resultItem.damage ?? 0) + 5 } };
2417
+ * });
2418
+ */
2419
+ onCompleteEquipmentReforge: (interceptor: (baseItem: Item, costItems: Item[], resultItem: Item, gameFlags: Record<string, number>) => {
2420
+ costItems?: Item[];
2421
+ resultItem?: Item;
2422
+ } | undefined) => void;
2423
+ /**
2424
+ * Intercept the full set of changes planned for a new game before they are applied.
2425
+ * Return a modified intent to change starting items, techniques, flags, etc.
2426
+ * @param interceptor - Receives the full intent and returns a modified copy
2427
+ * @example
2428
+ * modAPI.hooks.onNewGame((intent) => ({
2429
+ * ...intent,
2430
+ * flags: { ...intent.flags, my_mod_enabled: 1 },
2431
+ * }));
2432
+ */
2433
+ onNewGame: (interceptor: (intent: NewGameIntent) => NewGameIntent) => void;
2271
2434
  };
2272
2435
  /**
2273
2436
  * Inject UI into a named slot (dialog title or screen name).
2274
2437
  *
2275
- * The generator receives `(api, element, inject)`:
2276
- * - `api` full `ModReduxAPI` with game state, actions, and components
2277
- * - `element` root DOM element of the slot; use `querySelector` to find children
2278
- * - `inject(selector, content, mode?)` helper to portal `content` into the element
2279
- * matched by `selector`. `mode` is `'overlay'` (default, floats over) or `'inline'`
2438
+ * The generator receives (api, element, inject):
2439
+ * - api: full ModReduxAPI with game state, actions, and components
2440
+ * - element: root DOM element of the slot; use querySelector to find children
2441
+ * - inject(selector, content, mode?, position?): helper to portal content into the element
2442
+ * matched by selector. mode is 'overlay' (default, floats over) or 'inline'
2280
2443
  * (inserts as a sibling after the target, flowing with the layout).
2444
+ * position controls where content is placed (see InjectPosition enum).
2281
2445
  *
2282
- * Slot names for dialogs are the id (e.g. `'combat-victory'`). You can find these by inspecting the DOM in dev mode.
2283
- * Slot names for screens match the `ScreenType` value (e.g. `'combat'`).
2446
+ * Slot names for dialogs are the id (e.g. 'combat-victory'). You can find these by inspecting the DOM in dev mode.
2447
+ * Slot names for screens match the ScreenType value (e.g. 'combat').
2284
2448
  *
2285
2449
  * @example
2286
- * // Add a "Absorb Qi" button into the main body of the combat victory dialog.
2287
- * // The victory dialog's main content area has aria-live="assertive".
2450
+ * // Add a button to combat victory dialog
2288
2451
  * modAPI.ui.injectUI('combat-victory', (api, element, inject) => {
2289
2452
  * return inject('[aria-live="assertive"]',
2290
2453
  * <api.components.GameButton onClick={() => api.actions.changeQi(100)}>
2291
2454
  * Absorb Qi
2292
2455
  * </api.components.GameButton>,
2293
- * 'inline'
2456
+ * 'inline',
2457
+ * InjectPosition.After
2294
2458
  * );
2295
2459
  * });
2296
2460
  */
2297
- injectUI: (slotName: string, generator: (api: ModReduxAPI, element: HTMLElement | null, inject: (selector: string, content: React.ReactNode, mode?: 'overlay' | 'inline') => React.ReactNode) => React.ReactNode) => void;
2461
+ injectUI: (slotName: string, generator: (api: ModReduxAPI, element: HTMLElement | null, inject: (selector: string, content: React.ReactNode, mode?: 'overlay' | 'inline', position?: InjectPosition) => React.ReactNode) => React.ReactNode) => void;
2298
2462
  /**
2299
2463
  * Subscribe to any Redux state changes. The callback receives the new state.
2300
2464
  * Returns an unsubscribe function.
@@ -2319,3 +2483,6 @@ export interface ModAPI {
2319
2483
  */
2320
2484
  getGameStateSnapshot: () => RootState | null;
2321
2485
  }
2486
+ export type ModHooks = {
2487
+ [K in keyof ModAPI['hooks']]: Parameters<ModAPI['hooks'][K]>[0][];
2488
+ };
package/dist/reforge.d.ts CHANGED
@@ -2,8 +2,10 @@ import type { Item } from './item';
2
2
  export interface UpgradeUtil {
3
3
  item: Item;
4
4
  upgrade: Item;
5
+ costItems: Item[];
5
6
  }
6
7
  export interface PotentialUtil {
7
8
  item: Item;
8
9
  potentialUnlocked: Item;
10
+ costItems: Item[];
9
11
  }
@@ -3,6 +3,8 @@ import type { ItemDesc, RegionMonsters } from './item';
3
3
  import type { Rarity } from './rarity';
4
4
  import type { Beam } from './pillarGrid';
5
5
  import { Translatable } from './translatable';
6
+ import { Blessing, EnemyEntity } from '.';
7
+ import Prando from 'prando';
6
8
  export interface PlacedShardEntry {
7
9
  shardName: string;
8
10
  x: number;
@@ -19,8 +21,40 @@ export interface GridSimulationResult {
19
21
  power?: number;
20
22
  }[];
21
23
  }
22
- /** Room types for placed shard encounters. Fixed per shard (deterministic from name). */
23
- export type DelveRoomType = 'directReward' | 'cursedReward' | 'tradeReward' | 'figment' | 'eliteCombat' | 'augmentedElite' | 'hordeCombat' | 'resurrect' | 'reinforcement' | 'penalty' | 'dual' | 'trio' | 'blessingSelect' | 'coreBlessing';
24
+ export type DelveRoomStageKind = 'combat' | 'blessing' | 'curse' | 'reward' | 'event';
25
+ interface BaseDelveRoomStage {
26
+ kind: DelveRoomStageKind;
27
+ }
28
+ export interface CombatStage extends BaseDelveRoomStage {
29
+ kind: 'combat';
30
+ buildEnemies: (monsters: RegionMonsters, difficultyMult: number, rng: Prando) => EnemyEntity[];
31
+ }
32
+ export interface BlessingStage extends BaseDelveRoomStage {
33
+ kind: 'blessing';
34
+ blessingPool: Blessing[];
35
+ count: number;
36
+ }
37
+ export interface CurseStage extends BaseDelveRoomStage {
38
+ kind: 'curse';
39
+ cursePool: Blessing[];
40
+ count: number;
41
+ }
42
+ export interface RewardStage extends BaseDelveRoomStage {
43
+ kind: 'reward';
44
+ rewardPool: string;
45
+ increasedRarity: boolean;
46
+ }
47
+ export interface EventStage extends BaseDelveRoomStage {
48
+ kind: 'event';
49
+ event: GameEvent;
50
+ }
51
+ export type DelveRoomStage = CombatStage | BlessingStage | CurseStage | RewardStage | EventStage;
52
+ export interface DelveRoom {
53
+ name: string;
54
+ displayName?: Translatable;
55
+ description: string;
56
+ stages: DelveRoomStage[];
57
+ }
24
58
  /** A pre-placed cell in the soul shard grid (source, fixed node, or fixed routing shard). */
25
59
  export interface FixedGridCell {
26
60
  inputs?: {
@@ -40,114 +74,17 @@ export interface FixedGridCell {
40
74
  isNode?: boolean;
41
75
  icon: string;
42
76
  isSource: boolean;
43
- /** Minimum power to activate this node's bonus room (optional). */
77
+ /** Minimum qither through this node to add a one-time reward to the run. */
44
78
  nodeRoomThreshold?: number;
45
79
  }
46
- /** A reward slot earned from a room — the specific item is rolled when the remnant is opened. */
47
- export interface VictoryReward {
48
- rarity: Rarity;
49
- }
50
- /** Direct reward — no combat. Adds a deferred chest to end rewards. */
51
- export interface DirectRewardContent {
52
- kind: 'directReward';
53
- reward: VictoryReward;
54
- }
55
- /** Cursed reward — gain reward immediately + a persistent curse for the rest of the delve. */
56
- export interface CursedRewardContent {
57
- kind: 'cursedReward';
58
- reward: VictoryReward;
59
- curse: {
60
- buffName: string;
61
- buffStacks: number;
62
- };
63
- }
64
- /** Trade reward — sacrifice a spirit core for a powerful reward. */
65
- export interface TradeRewardContent {
66
- kind: 'tradeReward';
67
- coreName: string;
68
- reward: VictoryReward;
69
- }
70
- /** Figment — fight 3–4 normal enemies as a single party. Normal combat. */
71
- export interface FigmentContent {
72
- kind: 'figment';
73
- enemyTier: number;
74
- enemyCount: number;
75
- victoryReward: VictoryReward;
76
- }
77
- /** Elite combat — fight 1 elite. Normal combat. */
78
- export interface EliteCombatContent {
79
- kind: 'eliteCombat';
80
- enemyTier: number;
81
- victoryReward: VictoryReward;
82
- }
83
- /** Augmented elite — fight 1 elite + 1 normal as party. Normal combat. */
84
- export interface AugmentedEliteContent {
85
- kind: 'augmentedElite';
86
- enemyTier: number;
87
- victoryReward: VictoryReward;
88
- }
89
- /** Horde combat — fight 2 normals + 1 elite as back-to-back sequential enemies. Normal combat. */
90
- export interface HordeCombatContent {
91
- kind: 'hordeCombat';
92
- enemyTier: number;
93
- victoryReward: VictoryReward;
94
- }
95
- /** Resurrect — fight 1 elite + 1 normal as party; either resurrects if not killed within 2 rounds. Hard combat. */
96
- export interface ResurrectContent {
97
- kind: 'resurrect';
98
- enemyTier: number;
99
- victoryReward: VictoryReward;
100
- }
101
- /** Reinforcement — fight 1 elite with 1 normal summoned every 6 rounds (up to 4 party members). Hard combat. */
102
- export interface ReinforcementContent {
103
- kind: 'reinforcement';
104
- enemyTier: number;
105
- victoryReward: VictoryReward;
106
- }
107
- /** Penalty — any normal combat, but also applies a dangerous curse for the fight. Hard combat. */
108
- export interface PenaltyContent {
109
- kind: 'penalty';
110
- enemyTier: number;
111
- playerCurse: {
112
- buffName: string;
113
- buffStacks: number;
114
- };
115
- victoryReward: VictoryReward;
116
- }
117
- /** Dual — fight 2 elites as a party. Hard combat. */
118
- export interface DualContent {
119
- kind: 'dual';
120
- enemyTier: number;
121
- victoryReward: VictoryReward;
122
- }
123
- /** Trio — fight 3 elites back to back (sequential). Hard combat. */
124
- export interface TrioContent {
125
- kind: 'trio';
126
- enemyTier: number;
127
- victoryReward: VictoryReward;
128
- }
129
- /** Blessing select — choose 1 of 3 blessings drawn from the shard's themed pool. */
130
- export interface BlessingSelectContent {
131
- kind: 'blessingSelect';
132
- options: {
133
- buffName: string;
134
- buffStacks: number;
135
- }[];
136
- }
137
- /** Core blessing — trade a spirit core for 1 of 3 powerful blessings. */
138
- export interface CoreBlessingContent {
139
- kind: 'coreBlessing';
140
- coreName: string;
141
- options: {
142
- buffName: string;
143
- buffStacks: number;
144
- }[];
145
- }
146
- export type DelveRoomContent = DirectRewardContent | CursedRewardContent | TradeRewardContent | FigmentContent | EliteCombatContent | AugmentedEliteContent | HordeCombatContent | ResurrectContent | ReinforcementContent | PenaltyContent | DualContent | TrioContent | BlessingSelectContent | CoreBlessingContent;
147
80
  export interface DelveDropPool {
148
81
  id: string;
149
82
  displayName: Translatable;
150
- items: ItemDesc[];
83
+ items: {
84
+ name: string;
85
+ stacks: number;
86
+ rarity: Rarity;
87
+ }[];
151
88
  }
152
89
  /** A remnant accumulated during the delve. Item is rolled from pool when opened. */
153
90
  export interface PendingReward {
@@ -155,50 +92,22 @@ export interface PendingReward {
155
92
  /** Which loot pool to draw from when the remnant is opened. */
156
93
  pool: string;
157
94
  }
158
- export type BlessingRoomType = 'blessingSelect' | 'coreBlessing';
159
- export type RewardRoomType = Exclude<DelveRoomType, BlessingRoomType>;
160
- /** Absorber config for reward/combat rooms — always has a reward pool. */
161
- export interface RewardAbsorberConfig {
162
- roomType: RewardRoomType;
163
- rewardPoolId: string;
164
- cursePool?: string[];
165
- fightCursePool?: string[];
166
- }
167
- /** Absorber config for blessing rooms — has a blessing pool, no reward pool. */
168
- export interface BlessingAbsorberConfig {
169
- roomType: BlessingRoomType;
170
- blessingPool?: string[];
171
- }
172
- export type AbsorberConfig = RewardAbsorberConfig | BlessingAbsorberConfig;
173
- export interface DelveRoomDef {
174
- label: string;
175
- desc: string;
176
- /** Rarity offset relative to the shard's base tier (-1, 0, +1). */
177
- tierOffset: -1 | 0 | 1;
178
- }
179
- export declare const DELVE_ROOM_DEFS: Record<DelveRoomType, DelveRoomDef>;
180
95
  /** Static config for a soul shard delve location. */
181
96
  export interface SoulShardDelveConfig {
182
97
  key: string;
183
- displayName: string;
184
- description: string;
185
- icon: string;
186
- background: string;
187
- /** The overall tier of this shard — determines base reward rarity. */
188
- tier: Rarity;
98
+ location: string;
189
99
  /** Events played when each threshold is crossed, in order. Length determines total threshold count. */
190
- thresholdEvents: (GameEvent | undefined)[];
191
- essenceTypes: [string, string];
192
- /** Cooldown in calendar days after a completed run. */
193
- cooldownDays: number;
100
+ thresholdEvents: GameEvent[];
101
+ /** One-time item rewards granted when accumulatedQither reaches each intensity level. */
102
+ intensityRewards: {
103
+ intensity: number;
104
+ reward: ItemDesc;
105
+ }[];
194
106
  /** Source power regeneration rate: 1 qither per this many months. Default 6. */
195
107
  rechargeMonths: number;
196
- music?: string;
197
- /** Enemy pools — mob, elite, boss — same as mystical regions. */
198
108
  monsters: RegionMonsters;
199
- /** Spirit core item name required for tradeReward and coreBlessing rooms. */
200
- coreName: string;
201
- /** Loot pools by category. */
109
+ /** The boss room fought after all vestige rooms are cleared. */
110
+ boss: DelveRoom;
202
111
  customDropPool: DelveDropPool;
203
112
  }
204
113
  /** Persistent placed shard entry (survives between sessions). */
@@ -221,39 +130,38 @@ export interface SoulShardProgression {
221
130
  sourcePower: number;
222
131
  /** Months elapsed toward next recharge tick. */
223
132
  rechargeProgress: number;
133
+ /** How many intensityRewards have already been granted. */
134
+ claimedIntensityRewards: number;
224
135
  }
225
136
  /** Current run state stored in Redux. */
226
137
  export interface SoulShardDelveRunState {
227
138
  shards: string[];
228
139
  currentRoomIndex: number;
140
+ currentStageIndex: number;
229
141
  failedRun: boolean;
230
142
  progressQither: number;
231
143
  difficultyMultiplier: number;
232
- /** Player blessings gained during the delve (shown in bottom bar). */
233
- playerBuffs: {
234
- buffName: string;
235
- stacks: number;
236
- }[];
237
- /** Persistent curses gained from cursedReward rooms (applied to player in every subsequent fight). */
238
- delveCurses: {
239
- buffName: string;
240
- stacks: number;
241
- }[];
242
- /** Rewards accumulated during the run, revealed as chests at the end. */
243
- pendingRewards: PendingReward[];
144
+ blessings: string[];
145
+ curses: string[];
146
+ rewards: PendingReward[];
147
+ openedRewards: boolean[];
244
148
  }
245
149
  export interface SoulShardDelveState {
246
150
  /** Which soul shard config is currently selected (screen is open). */
247
151
  delveKey?: string;
248
152
  currentRun?: SoulShardDelveRunState;
249
- /** delveKey -> calendar day the cooldown expires */
250
- cooldowns: Record<string, number>;
251
153
  /** delveKey -> persistent progression data */
252
154
  progression: Record<string, SoulShardProgression>;
253
155
  }
254
- /** Cumulative qither required to reach threshold n (1-indexed). Scales quadratically. */
156
+ /** Cumulative qither required to reach threshold n (1-indexed).
157
+ * Based on ~2 perfect runs per threshold, where a perfect run = 3 + gridSize.
158
+ * Grid starts at ~60 cells and grows by ~15 per threshold, so run(t) = 63 + t*15.
159
+ * Cumulative: sum of 2*(63 + t*15) for t=0..n-1 = n*(111 + 15n).
160
+ * Gives: 126, 282, 468, 684, 930, ...
161
+ */
255
162
  export declare function getThresholdQither(n: number): number;
256
163
  /** Enemy difficulty multiplier from total qither used to activate all absorbers.
257
164
  * +2% HP and power per 1 qither.
258
165
  */
259
166
  export declare function getQitherDifficulty(totalQither: number): number;
167
+ export {};
@@ -1,79 +1,11 @@
1
- export const DELVE_ROOM_DEFS = {
2
- directReward: {
3
- label: 'Offering',
4
- desc: 'A remnant added to the Figment encounter, claimed on victory',
5
- tierOffset: -1,
6
- },
7
- cursedReward: {
8
- label: 'Cursed Offering',
9
- desc: 'Gain a remnant immediately, but carry a curse for the rest of the delve',
10
- tierOffset: 0,
11
- },
12
- tradeReward: {
13
- label: 'Bargain',
14
- desc: 'Trade a spirit core to gain a remnant',
15
- tierOffset: 0,
16
- },
17
- figment: {
18
- label: 'Figment',
19
- desc: 'Fight three to four shades at once to gain a remnant',
20
- tierOffset: 0,
21
- },
22
- eliteCombat: {
23
- label: 'Keeper',
24
- desc: 'Defeat an aspect to gain a remnant',
25
- tierOffset: 0,
26
- },
27
- augmentedElite: {
28
- label: "Keeper's Shadow",
29
- desc: 'Defeat an aspect with a shade at its side to gain a remnant',
30
- tierOffset: 0,
31
- },
32
- hordeCombat: {
33
- label: 'Surge',
34
- desc: 'Fight two shades then an aspect in sequence to gain a remnant',
35
- tierOffset: 0,
36
- },
37
- resurrect: {
38
- label: 'Tethered',
39
- desc: 'Fight an aspect and a shade; either will revive the other if not killed together',
40
- tierOffset: 1,
41
- },
42
- reinforcement: {
43
- label: 'Summoner',
44
- desc: 'Fight an aspect while it calls in shades every six rounds',
45
- tierOffset: 1,
46
- },
47
- penalty: {
48
- label: 'Marked',
49
- desc: 'Fight while carrying a dangerous curse to gain a remnant',
50
- tierOffset: 1,
51
- },
52
- dual: {
53
- label: 'Twin Keepers',
54
- desc: 'Fight two aspects at once to gain a remnant',
55
- tierOffset: 1,
56
- },
57
- trio: {
58
- label: 'Triad',
59
- desc: 'Fight three aspects in sequence to gain a remnant',
60
- tierOffset: 1,
61
- },
62
- blessingSelect: {
63
- label: 'Attunement',
64
- desc: "Choose one blessing from three drawn from the shard's pool",
65
- tierOffset: 0,
66
- },
67
- coreBlessing: {
68
- label: 'Deep Attunement',
69
- desc: 'Trade a spirit core for one of three powerful blessings',
70
- tierOffset: 0,
71
- },
72
- };
73
- /** Cumulative qither required to reach threshold n (1-indexed). Scales quadratically. */
1
+ /** Cumulative qither required to reach threshold n (1-indexed).
2
+ * Based on ~2 perfect runs per threshold, where a perfect run = 3 + gridSize.
3
+ * Grid starts at ~60 cells and grows by ~15 per threshold, so run(t) = 63 + t*15.
4
+ * Cumulative: sum of 2*(63 + t*15) for t=0..n-1 = n*(111 + 15n).
5
+ * Gives: 126, 282, 468, 684, 930, ...
6
+ */
74
7
  export function getThresholdQither(n) {
75
- // 10, 30, 60, 100, 150, 210, ... → n * (n + 1) * 5
76
- return n * (n + 1) * 5;
8
+ return n * (111 + 15 * n);
77
9
  }
78
10
  /** Enemy difficulty multiplier from total qither used to activate all absorbers.
79
11
  * +2% HP and power per 1 qither.
@@ -71,12 +71,14 @@ interface BaseTechniqueEffect {
71
71
  isAdditionalTooltip?: boolean;
72
72
  cantUpgrade?: boolean;
73
73
  }
74
+ export type EffectTargeting = 'self' | 'party' | 'all';
74
75
  interface BuffSelfTechniqueEffect extends BaseTechniqueEffect {
75
76
  kind: 'buffSelf';
76
77
  buff: Buff;
77
78
  amount: Scaling;
78
79
  hits?: Scaling;
79
80
  hideBuff?: boolean;
81
+ targeting?: EffectTargeting;
80
82
  }
81
83
  interface ConsumeSelfTechniqueEffect extends BaseTechniqueEffect {
82
84
  kind: 'consumeSelf';
@@ -125,11 +127,13 @@ interface BarrierTechniqueEffect extends BaseTechniqueEffect {
125
127
  kind: 'barrier';
126
128
  amount: Scaling;
127
129
  hits?: Scaling;
130
+ targeting?: EffectTargeting;
128
131
  }
129
132
  interface HealTechniqueEffect extends BaseTechniqueEffect {
130
133
  kind: 'heal';
131
134
  amount: Scaling;
132
135
  hits?: Scaling;
136
+ targeting?: EffectTargeting;
133
137
  }
134
138
  interface CleanseToxicityTechniqueEffect extends BaseTechniqueEffect {
135
139
  kind: 'cleanseToxicity';
@@ -154,6 +158,7 @@ interface RepairTechniqueEffect extends BaseTechniqueEffect {
154
158
  group: string;
155
159
  /** Which matching buff(s) to repair: 'all', 'lowestHealth', or 'highestHealth' */
156
160
  rule: RepairRule;
161
+ targeting?: EffectTargeting;
157
162
  }
158
163
  export interface PermanentStatChangeTechniqueEffect extends BaseTechniqueEffect {
159
164
  kind: 'permanentStatChange';
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Type Tests - These files ensure type compatibility across the codebase
3
+ * If any of these fail to compile, it means we have a type mismatch
4
+ */
5
+ import type { RootState as StoreRootState } from '../store';
6
+ import type { RootState as TypesRootState } from './reduxState';
7
+ type TestExactMatch = StoreRootState extends TypesRootState ? TypesRootState extends StoreRootState ? true : false : false;
8
+ export type RootStateIsConsistent = TestExactMatch;
9
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "afnm-types",
3
- "version": "0.6.53",
3
+ "version": "0.6.54-3",
4
4
  "description": "Type definitions for Ascend From Nine Mountains",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",