@xforge-dev/forge-kit 0.24.0 → 0.24.2

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.
package/README.md CHANGED
@@ -132,11 +132,12 @@ the lowercased name without spaces, so a stored board never holds names.
132
132
  ### Legion Points (GMS v.272)
133
133
 
134
134
  v.272 removes the board. The points model sits beside it (the board stays for app versions still reading it):
135
- `legionPoints` (the automatic raid's ranks, B 1 … SSS 5, as many members as the Legion rank holds), stat levels
136
- (`LegionStats`, one point a level, `statCap` 15 for basic stats and up to 40 for expanded ones by Legion level),
135
+ `legionPoints` (the automatic raid's ranks, B 1 … SSS 5, as many members as the Legion rank holds; `raidSplit` gives
136
+ those and the bonus-only rest, since every member at rank B or higher gives its effect: `effectMembers`), stat levels
137
+ (`LegionStats`, one point a level, what a grid square was: same values, caps 15 inner and 40 outer),
137
138
  `allocationProblem`, `statTotals`, `suggestStats`, the two highest event blocks (`appliedEventBlocks`), 10 named
138
139
  presets (`SharedPoints`, stored under `points` in the shared row so an older board write leaves it alone) and the 5th
139
- Champion slot (`championSlotsOpen`). Values not yet confirmed sit in one marked block in `src/legion.ts`.
140
+ Champion slot (`championSlotsOpen`).
140
141
 
141
142
  ## Exports
142
143
 
package/dist/legion.d.ts CHANGED
@@ -166,9 +166,9 @@ export declare function sharedBoardFrom(raw: unknown): SharedBoard | null;
166
166
  export declare function crystalsFrom(raw: unknown): ArtifactCrystal[];
167
167
  /** The Legion's 16 stats, keyed as the grid's areas were. */
168
168
  export type LegionStatKey = GridKey;
169
- /** Basic stats, open from the start (the patch notes' order). */
169
+ /** Basic stats: the board's inner areas (the patch notes' order). */
170
170
  export declare const BASIC_STATS: readonly ["str", "dex", "int", "luk", "att", "matt", "hp", "mp"];
171
- /** Expanded stats, opening together at Legion level 2,000 (the patch notes' order). */
171
+ /** Expanded stats: the board's outer areas (the patch notes' order). */
172
172
  export declare const EXPANDED_STATS: readonly ["critDamage", "boss", "ied", "critRate", "buffDuration", "normal", "exp", "statusResist"];
173
173
  export type ExpandedStat = (typeof EXPANDED_STATS)[number];
174
174
  export declare const LEGION_STATS: readonly GridKey[];
@@ -190,24 +190,15 @@ export declare const LEGION_LEVEL_RANKS: readonly LegionLevelRank[];
190
190
  export declare function legionLevelRank(level: number): LegionLevelRank | null;
191
191
  /** Members the automatic Legion Raid takes at this Legion level (Nameless I's 9 below it). */
192
192
  export declare const raidMembers: (level: number) => number;
193
- /** Each stat's highest level. */
194
- export declare const STAT_MAX_LEVEL: Record<GridKey, number>;
195
- /** The expanded stats' shared level cap by Legion level (the guide's 유니온 레벨별 확장 스탯 최대 레벨). */
196
- export declare const EXPANDED_CAPS: readonly {
197
- level: number;
198
- cap: number;
199
- }[];
200
- /** The expanded stats' level cap at this Legion level: 0 below 2,000. */
201
- export declare function expandedCap(legionLevel: number): number;
202
- /** What one level of each stat gives. */
193
+ /** What one level of each stat gives: a grid square's value. */
203
194
  export declare const STAT_PER_LEVEL: Record<GridKey, number>;
204
- /** Points an applied event block gives. */
205
- export declare const eventBlockPoints: (_b: EventBlock) => number;
195
+ /** Each stat's highest level: its grid area's size. */
196
+ export declare const STAT_MAX_LEVEL: Record<GridKey, number>;
206
197
  /** Stat levels, one per point: absent or 0 when nothing is spent. */
207
198
  export type LegionStats = Partial<Record<GridKey, number>>;
208
199
  export declare const isExpandedStat: (k: GridKey) => k is ExpandedStat;
209
- /** A stat's level cap at this Legion level: basic stats 15, expanded stats as the Legion level allows. */
210
- export declare const statCap: (k: GridKey, legionLevel: number) => number;
200
+ /** A stat's level cap at this Legion level: its grid area's size at every Legion level, as the board had it. */
201
+ export declare const statCap: (k: GridKey, _legionLevel: number) => number;
211
202
  /** Whether a stat is open at this Legion level. */
212
203
  export declare const statUnlocked: (k: GridKey, legionLevel: number) => boolean;
213
204
  /** One member as the points model reads it: `rank` null below B (no points, no effect). */
@@ -220,10 +211,20 @@ export type PointsMember = {
220
211
  * The caller works out ranks (Zero's own levels) and Legion level.
221
212
  */
222
213
  export declare function raidComposition<M extends PointsMember>(members: readonly M[], legionLevel: number): M[];
214
+ /**
215
+ * The Legion split in two: `counted`, the members the raid takes (their ranks are the Legion Points), and `bonus`, the
216
+ * other members at rank B or higher, whose effects apply too but who give no points. Members below B are in neither.
217
+ */
218
+ export declare function raidSplit<M extends PointsMember>(members: readonly M[], legionLevel: number): {
219
+ counted: M[];
220
+ bonus: M[];
221
+ };
222
+ /** Members whose effects apply: every member at rank B or higher, counted by the raid or not. */
223
+ export declare const effectMembers: <M extends PointsMember>(members: readonly M[]) => M[];
223
224
  /** The two event blocks that apply: the highest values among those owned. */
224
225
  export declare function appliedEventBlocks(owned: readonly EventBlock[]): EventBlock[];
225
- /** Legion Points to spend: the raid's ranks (and the applied event blocks', none as far as known). */
226
- export declare function legionPoints(members: readonly PointsMember[], legionLevel: number, owned?: readonly EventBlock[]): number;
226
+ /** Legion Points to spend: the ranks of the members the raid counts. Event blocks add ATT, not points. */
227
+ export declare function legionPoints(members: readonly PointsMember[], legionLevel: number): number;
227
228
  /** Points the stat levels spend. */
228
229
  export declare const pointsSpent: (stats: LegionStats) => number;
229
230
  /** What each stat gives at its level (only stats with a level). */
package/dist/legion.js CHANGED
@@ -195,9 +195,9 @@ export function crystalsFrom(raw) {
195
195
  return { level, lines };
196
196
  });
197
197
  }
198
- /** Basic stats, open from the start (the patch notes' order). */
198
+ /** Basic stats: the board's inner areas (the patch notes' order). */
199
199
  export const BASIC_STATS = ['str', 'dex', 'int', 'luk', 'att', 'matt', 'hp', 'mp'];
200
- /** Expanded stats, opening together at Legion level 2,000 (the patch notes' order). */
200
+ /** Expanded stats: the board's outer areas (the patch notes' order). */
201
201
  export const EXPANDED_STATS = [
202
202
  'critDamage', 'boss', 'ied', 'critRate', 'buffDuration', 'normal', 'exp', 'statusResist',
203
203
  ];
@@ -226,37 +226,17 @@ export function legionLevelRank(level) {
226
226
  }
227
227
  /** Members the automatic Legion Raid takes at this Legion level (Nameless I's 9 below it). */
228
228
  export const raidMembers = (level) => (legionLevelRank(level) ?? LEGION_LEVEL_RANKS[0]).members;
229
- // Confirmed from KMS 1.2.416 (the same revamp, 2026-06-18; maplestory.nexon.com News/Update/806 and the official game
230
- // guide, Guide/N23GameInformation/Articles/407): basic stats go to level 15, expanded stats to 40; the 8 expanded stats
231
- // open together, their cap rising with Legion level (below); points are only the raid's ranks (a KMST screenshot:
232
- // Supreme I, 41 members, 202 points).
233
- /** Each stat's highest level. */
234
- export const STAT_MAX_LEVEL = Object.fromEntries(GRID_KEYS.map((k) => [k, gridMax(k)]));
235
- /** The expanded stats' shared level cap by Legion level (the guide's 유니온 레벨별 확장 스탯 최대 레벨). */
236
- export const EXPANDED_CAPS = [
237
- { level: 2000, cap: 5 }, { level: 3000, cap: 10 }, { level: 4000, cap: 20 }, { level: 5000, cap: 30 }, { level: 6000, cap: 40 },
238
- ];
239
- /** The expanded stats' level cap at this Legion level: 0 below 2,000. */
240
- export function expandedCap(legionLevel) {
241
- let cap = 0;
242
- for (const c of EXPANDED_CAPS)
243
- if (legionLevel >= c.level)
244
- cap = c.cap;
245
- return cap;
246
- }
247
- // ┌─ ASSUMED VALUES ────────────────────────────────────────────────────────────────────────────────────────────────┐
248
- // │ Not in the v.272 notes nor any KMS source found: what a stat level gives. Each is taken as one old grid square │
249
- // │ (KMS says the stats are "the same types as the old occupation effects", and the caps and 440 total match the │
250
- // │ board). Nor whether event blocks give points: taken as none (the notes only say they apply automatically and │
251
- // │ don't count as members). Change them here only; everything below reads them. │
252
- // └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
253
- /** What one level of each stat gives. */
229
+ // The rules are the board's, without the board: a stat level is what a grid square was (same value per level, the
230
+ // same caps: inner stats 15, outer stats 40, open from the start as every area was). Every member at rank B or higher
231
+ // gives its effect; only the members the raid counts (the top members by level, as many as the board's slots were)
232
+ // give points.
233
+ /** What one level of each stat gives: a grid square's value. */
254
234
  export const STAT_PER_LEVEL = { ...GRID_PER_SQUARE };
255
- /** Points an applied event block gives. */
256
- export const eventBlockPoints = (_b) => 0;
235
+ /** Each stat's highest level: its grid area's size. */
236
+ export const STAT_MAX_LEVEL = Object.fromEntries(GRID_KEYS.map((k) => [k, gridMax(k)]));
257
237
  export const isExpandedStat = (k) => EXPANDED_STATS.includes(k);
258
- /** A stat's level cap at this Legion level: basic stats 15, expanded stats as the Legion level allows. */
259
- export const statCap = (k, legionLevel) => (isExpandedStat(k) ? Math.min(expandedCap(legionLevel), STAT_MAX_LEVEL[k]) : STAT_MAX_LEVEL[k]);
238
+ /** A stat's level cap at this Legion level: its grid area's size at every Legion level, as the board had it. */
239
+ export const statCap = (k, _legionLevel) => STAT_MAX_LEVEL[k];
260
240
  /** Whether a stat is open at this Legion level. */
261
241
  export const statUnlocked = (k, legionLevel) => statCap(k, legionLevel) > 0;
262
242
  /**
@@ -266,14 +246,24 @@ export const statUnlocked = (k, legionLevel) => statCap(k, legionLevel) > 0;
266
246
  export function raidComposition(members, legionLevel) {
267
247
  return members.filter((m) => m.rank).sort((a, b) => b.level - a.level).slice(0, raidMembers(legionLevel));
268
248
  }
249
+ /**
250
+ * The Legion split in two: `counted`, the members the raid takes (their ranks are the Legion Points), and `bonus`, the
251
+ * other members at rank B or higher, whose effects apply too but who give no points. Members below B are in neither.
252
+ */
253
+ export function raidSplit(members, legionLevel) {
254
+ const counted = raidComposition(members, legionLevel);
255
+ const taken = new Set(counted);
256
+ return { counted, bonus: members.filter((m) => m.rank && !taken.has(m)).sort((a, b) => b.level - a.level) };
257
+ }
258
+ /** Members whose effects apply: every member at rank B or higher, counted by the raid or not. */
259
+ export const effectMembers = (members) => members.filter((m) => m.rank);
269
260
  /** The two event blocks that apply: the highest values among those owned. */
270
261
  export function appliedEventBlocks(owned) {
271
262
  return [...owned].sort((a, b) => b.value - a.value).slice(0, MAX_EVENT_BLOCKS);
272
263
  }
273
- /** Legion Points to spend: the raid's ranks (and the applied event blocks', none as far as known). */
274
- export function legionPoints(members, legionLevel, owned = []) {
275
- return raidComposition(members, legionLevel).reduce((t, m) => t + RANK_POINTS[m.rank], 0)
276
- + appliedEventBlocks(owned).reduce((t, b) => t + eventBlockPoints(b), 0);
264
+ /** Legion Points to spend: the ranks of the members the raid counts. Event blocks add ATT, not points. */
265
+ export function legionPoints(members, legionLevel) {
266
+ return raidComposition(members, legionLevel).reduce((t, m) => t + RANK_POINTS[m.rank], 0);
277
267
  }
278
268
  /** Points the stat levels spend. */
279
269
  export const pointsSpent = (stats) => Object.values(stats).reduce((t, n) => t + (n ?? 0), 0);
@@ -292,11 +282,8 @@ export function allocationProblem(a) {
292
282
  if (!n)
293
283
  continue;
294
284
  const cap = statCap(k, a.legionLevel);
295
- if (n > cap) {
296
- if (!cap)
297
- return `${STAT_LABELS[k]} opens at Legion level ${EXPANDED_CAPS[0].level.toLocaleString('en-US')}.`;
298
- return `${STAT_LABELS[k]} goes up to level ${cap}${cap < STAT_MAX_LEVEL[k] ? ' at this Legion level' : ''}.`;
299
- }
285
+ if (n > cap)
286
+ return `${STAT_LABELS[k]} goes up to level ${cap}.`;
300
287
  }
301
288
  const spent = pointsSpent(a.stats);
302
289
  if (spent > a.points)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xforge-dev/forge-kit",
3
- "version": "0.24.0",
3
+ "version": "0.24.2",
4
4
  "description": "Shared auth, app registry and UI for xForge apps",
5
5
  "license": "MIT",
6
6
  "repository": {