@doublehitgames/gdd-mcp 0.4.1

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.
@@ -0,0 +1,682 @@
1
+ /**
2
+ * Type-specific MCP tools for each addon type.
3
+ *
4
+ * 12 types × 2 (create + update) = 24 tools.
5
+ * Each tool fixes the addon `type` and provides a typed schema for `data`,
6
+ * then delegates to the generic addon API endpoint.
7
+ */
8
+ import { z } from "zod/v3";
9
+ import { GddApiError } from "./client.js";
10
+ function json(data) {
11
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
12
+ }
13
+ function err(e) {
14
+ if (e instanceof GddApiError) {
15
+ return { content: [{ type: "text", text: `Error (${e.code}): ${e.message}` }], isError: true };
16
+ }
17
+ return { content: [{ type: "text", text: String(e) }], isError: true };
18
+ }
19
+ // Shared params present in every create/update tool
20
+ const projSec = {
21
+ projectId: z.string().describe("Project UUID"),
22
+ sectionId: z.string().describe("Section UUID"),
23
+ };
24
+ const projSecAddon = {
25
+ ...projSec,
26
+ addonId: z.string().describe("Addon UUID"),
27
+ };
28
+ export function registerAddonTools(server, client) {
29
+ // ── Helper to register a create + update pair ──────────────────
30
+ function pair(typeName, addonType, description, createFields, updateFields) {
31
+ // CREATE
32
+ server.tool(`create_${typeName}_addon`, `Create a ${description} addon`, {
33
+ ...projSec,
34
+ name: z.string().describe("Display name for the addon"),
35
+ group: z.string().optional().describe("Optional group name"),
36
+ ...createFields,
37
+ }, async ({ projectId, sectionId, name, group, ...data }) => {
38
+ try {
39
+ return json(await client.createAddon(projectId, sectionId, {
40
+ type: addonType,
41
+ name,
42
+ ...(group ? { group } : {}),
43
+ data,
44
+ }));
45
+ }
46
+ catch (e) {
47
+ return err(e);
48
+ }
49
+ });
50
+ // UPDATE
51
+ server.tool(`update_${typeName}_addon`, `Update a ${description} addon`, {
52
+ ...projSecAddon,
53
+ name: z.string().optional().describe("New display name"),
54
+ group: z.string().optional().describe("New group name"),
55
+ ...updateFields,
56
+ }, async ({ projectId, sectionId, addonId, name, group, ...data }) => {
57
+ try {
58
+ const fields = {};
59
+ if (name !== undefined)
60
+ fields.name = name;
61
+ if (group !== undefined)
62
+ fields.group = group;
63
+ if (Object.keys(data).length > 0)
64
+ fields.data = data;
65
+ return json(await client.updateAddon(projectId, sectionId, addonId, fields));
66
+ }
67
+ catch (e) {
68
+ return err(e);
69
+ }
70
+ });
71
+ }
72
+ // Helper to make all fields in a record optional
73
+ function optional(fields) {
74
+ const result = {};
75
+ for (const [k, v] of Object.entries(fields)) {
76
+ result[k] = v instanceof z.ZodOptional ? v : v.optional();
77
+ }
78
+ return result;
79
+ }
80
+ // Reusable Google Sheets binding for a scalar field (boolean or numeric). The in-app
81
+ // "Sincronizar tudo" reads the cell and overwrites the scalar (booleans: TRUE/1/YES/SIM
82
+ // → true). cellRef is the fallback position; use rowLock "auto" to anchor the row to the
83
+ // page DataID so many items can bind to the same column at once. Setting it via MCP only
84
+ // defines the binding — value resolution stays client-side.
85
+ const sheetsBinding = z.object({
86
+ source: z.literal("sheets"),
87
+ ref: z.object({
88
+ sheetName: z.string().describe("Sheet/tab name"),
89
+ cellRef: z.string().describe('Fallback position, e.g. "C2". Required even with locks.'),
90
+ columnLock: z.string().optional().describe("Column header name (resolves the column by name)."),
91
+ rowLock: z.string().optional().describe('"auto" = page DataID; or a fixed value matched in column A.'),
92
+ }),
93
+ }).optional();
94
+ // Reusable binding for a numeric value field. Accepts three sources:
95
+ // • sheets — a Google Sheets cell (same shape as sheetsBinding)
96
+ // • progressionColumn — a ProgressionTable column (level-scaled), intra-section
97
+ // • library — a Field Library entry (best for key/label fields; on a numeric
98
+ // field its resolved value is the entry, so prefer sheets/progression)
99
+ const valueBinding = z.union([
100
+ z.object({
101
+ source: z.literal("sheets"),
102
+ ref: z.object({
103
+ sheetName: z.string(),
104
+ cellRef: z.string().describe('Fallback position, e.g. "C2". Required even with locks.'),
105
+ columnLock: z.string().optional().describe("Column header name."),
106
+ rowLock: z.string().optional().describe('"auto" = page DataID; or a fixed value in column A.'),
107
+ }),
108
+ }),
109
+ z.object({
110
+ source: z.literal("progressionColumn"),
111
+ progressionAddonId: z.string().describe("Outer ID of the ProgressionTable addon"),
112
+ columnId: z.string().describe("Column ID inside that table"),
113
+ columnName: z.string().describe("Cached column name for display"),
114
+ }),
115
+ z.object({
116
+ source: z.literal("library"),
117
+ libraryAddonId: z.string().describe("Outer ID of the Field Library addon"),
118
+ entryId: z.string().describe("Entry ID inside the Field Library"),
119
+ }),
120
+ ]).optional();
121
+ // ── 1. Currency ─────────────────────────────────────────────────
122
+ const currencyFields = {
123
+ code: z.string().describe("Currency code (e.g. GLD, DIA)"),
124
+ displayName: z.string().describe("Display name shown in-game"),
125
+ kind: z.enum(["soft", "premium", "event", "other"]).describe("Currency category"),
126
+ decimals: z.number().default(0).describe("Decimal places (0 for integer currencies)"),
127
+ notes: z.string().optional().describe("Design notes"),
128
+ };
129
+ pair("currency", "currency", "currency (in-game money)", currencyFields, optional(currencyFields));
130
+ // ── 2. Inventory ────────────────────────────────────────────────
131
+ const inventoryFields = {
132
+ weight: z.number().default(0).describe("Item weight"),
133
+ stackable: z.boolean().default(true).describe("Can items stack?"),
134
+ maxStack: z.number().default(99).describe("Max stack size"),
135
+ inventoryCategory: z.string().default("").describe("Category (e.g. weapon, food, material). Ignored when categoryLibraryRef is set — the Library entry's label takes precedence."),
136
+ categoryLibraryRef: z.object({
137
+ libraryAddonId: z.string().describe("Outer ID of the Field Library addon"),
138
+ entryId: z.string().describe("Entry ID inside the Field Library"),
139
+ }).optional().describe("Bind the category to a Field Library entry — keeps category names consistent across items."),
140
+ slotSize: z.number().default(1).describe("Inventory slots occupied"),
141
+ hasDurabilityConfig: z.boolean().optional().describe("Toggle: show/hide the durability config. When false, durability/maxDurability are ignored by the UI."),
142
+ durability: z.number().default(0).describe("Base durability (0 = no durability)"),
143
+ maxDurability: z.number().optional().describe("Maximum durability; present only when hasDurabilityConfig is true."),
144
+ hasVolumeConfig: z.boolean().optional().describe("Toggle: show/hide the volume config."),
145
+ volume: z.number().optional().describe("Item volume; present only when hasVolumeConfig is true."),
146
+ bindType: z.enum(["none", "onPickup", "onEquip"]).default("none").describe("Bind on pickup/equip"),
147
+ showInShop: z.boolean().default(true).describe("Visible in shop?"),
148
+ showInShopBinding: sheetsBinding.describe("Optional Google Sheets binding for showInShop."),
149
+ consumable: z.boolean().default(false).describe("Is consumable?"),
150
+ consumableBinding: sheetsBinding.describe("Optional Google Sheets binding for consumable."),
151
+ discardable: z.boolean().default(true).describe("Can be discarded?"),
152
+ discardableBinding: sheetsBinding.describe("Optional Google Sheets binding for discardable."),
153
+ notes: z.string().optional().describe("Design notes"),
154
+ };
155
+ pair("inventory", "inventory", "inventory item", inventoryFields, optional(inventoryFields));
156
+ // ── 3. Economy Link ─────────────────────────────────────────────
157
+ const progressionLinkSchema = z.object({
158
+ progressionAddonId: z.string(),
159
+ columnId: z.string(),
160
+ columnName: z.string(),
161
+ }).optional();
162
+ const economyLinkFields = {
163
+ hasBuyConfig: z.boolean().optional().default(true).describe("Enable buy configuration"),
164
+ buyCurrencyRef: z.string().optional().describe("Currency section ID for buy price"),
165
+ buyValue: z.number().optional().describe("Buy price"),
166
+ buyValueBinding: valueBinding.describe("Optional binding for buyValue (sheets | progressionColumn)."),
167
+ buyValueProgressionLink: progressionLinkSchema.describe("Link buyValue to a progression table column (resolved by unlockValue)"),
168
+ minBuyValue: z.number().optional().describe("Minimum buy price"),
169
+ minBuyValueProgressionLink: progressionLinkSchema.describe("Link minBuyValue to a progression table column"),
170
+ maxBuyValue: z.number().optional().describe("Maximum buy price"),
171
+ maxBuyValueProgressionLink: progressionLinkSchema.describe("Link maxBuyValue to a progression table column"),
172
+ hasSellConfig: z.boolean().optional().default(true).describe("Enable sell configuration"),
173
+ sellCurrencyRef: z.string().optional().describe("Currency section ID for sell price"),
174
+ sellValue: z.number().optional().describe("Sell price"),
175
+ sellValueBinding: valueBinding.describe("Optional binding for sellValue (sheets | progressionColumn)."),
176
+ sellValueProgressionLink: progressionLinkSchema.describe("Link sellValue to a progression table column"),
177
+ minSellValue: z.number().optional().describe("Minimum sell price"),
178
+ minSellValueProgressionLink: progressionLinkSchema.describe("Link minSellValue to a progression table column"),
179
+ maxSellValue: z.number().optional().describe("Maximum sell price"),
180
+ maxSellValueProgressionLink: progressionLinkSchema.describe("Link maxSellValue to a progression table column"),
181
+ priceMultiplier: z.number().optional().describe("Multiplier applied to all buy/sell values (table or fixed). Default 1."),
182
+ hasProductionConfig: z.boolean().optional().default(false).describe("Enable production config"),
183
+ hasUnlockConfig: z.boolean().optional().default(false).describe("Enable unlock config"),
184
+ unlockRef: z.string().optional().describe("Reference to unlock requirement"),
185
+ unlockValue: z.number().optional().describe("Unlock cost"),
186
+ unlockValueBinding: valueBinding.describe("Optional binding for unlockValue (sheets | progressionColumn)."),
187
+ unlockValueMin: z.number().optional().describe("Minimum unlock cost"),
188
+ unlockValueMax: z.number().optional().describe("Maximum unlock cost"),
189
+ notes: z.string().optional().describe("Design notes"),
190
+ };
191
+ pair("economy_link", "economyLink", "economy link (buy/sell prices)", economyLinkFields, optional(economyLinkFields));
192
+ // ── 4. Global Variable ──────────────────────────────────────────
193
+ const globalVariableFields = {
194
+ key: z.string().describe("Variable key (e.g. drop_rate_bonus)"),
195
+ displayName: z.string().describe("Display name"),
196
+ valueType: z.enum(["percent", "multiplier", "flat", "boolean"]).describe("Value type"),
197
+ defaultValue: z.union([z.number(), z.boolean()]).describe("Default value"),
198
+ scope: z.enum(["global", "mode", "event", "season"]).default("global").describe("Variable scope"),
199
+ notes: z.string().optional().describe("Design notes"),
200
+ };
201
+ pair("global_variable", "globalVariable", "global variable", globalVariableFields, optional(globalVariableFields));
202
+ // ── 5. Progression Table ────────────────────────────────────────
203
+ const progressionColumnSchema = z.object({
204
+ id: z.string().describe("Column ID"),
205
+ name: z.string().describe("Column name. Ignored when libraryRef is set — the Library entry's label takes precedence."),
206
+ libraryRef: z.object({
207
+ libraryAddonId: z.string().describe("Outer ID of the Field Library addon"),
208
+ entryId: z.string().describe("Entry ID inside the Field Library"),
209
+ }).optional().describe("Bind the column name to a Field Library entry — keeps column names consistent across tables (and with Attribute Definitions / Data Schema)."),
210
+ decimals: z.number().optional().default(0),
211
+ isPercentage: z.boolean().optional(),
212
+ min: z.number().optional(),
213
+ max: z.number().optional(),
214
+ generator: z.object({
215
+ mode: z.enum(["manual", "linear", "exponential", "formula"]),
216
+ base: z.number().optional(),
217
+ step: z.number().optional(),
218
+ growth: z.number().optional(),
219
+ bias: z.number().optional().describe("Linear and exponential only — curve shape. 1.0 = pure linear/exponential, >1 slow early/fast late, <1 fast early/flat late. Endpoints are always preserved."),
220
+ expression: z.string().optional().describe("Formula mode only — expression evaluated per level."),
221
+ baseColumnId: z.string().optional().describe("Formula mode only — column whose values drive the expression variables."),
222
+ baseManualValue: z.number().optional().describe("Formula mode only — fallback constant used when baseColumnId resolves to empty."),
223
+ }).optional().describe("Auto-generation config"),
224
+ });
225
+ const progressionRowSchema = z.object({
226
+ level: z.number(),
227
+ values: z.record(z.union([z.number(), z.string()])),
228
+ });
229
+ const progressionTableFields = {
230
+ startLevel: z.number().default(1).describe("First level"),
231
+ endLevel: z.number().default(20).describe("Last level"),
232
+ columns: z.array(progressionColumnSchema).describe("Table columns"),
233
+ rows: z.array(progressionRowSchema).optional().describe("Row data (auto-generated if omitted)"),
234
+ overrides: z.record(z.record(z.number())).optional().describe("Manual cell overrides: overrides[levelString][columnId] = value. Cells with overrides are preserved when regenerating."),
235
+ };
236
+ pair("progression_table", "progressionTable", "progression/balance table", progressionTableFields, optional(progressionTableFields));
237
+ // ── 6. XP Balance ───────────────────────────────────────────────
238
+ const xpBalanceFields = {
239
+ mode: z.enum(["preset", "advanced"]).default("preset").describe("Formula mode"),
240
+ preset: z.enum(["linear", "exponential", "tiered", "softCap", "hardCap", "diminishingReturns", "piecewise"]).default("exponential").describe("Curve preset"),
241
+ expression: z.string().default("").describe("Custom expression (advanced mode)"),
242
+ startLevel: z.number().default(1).describe("First level"),
243
+ endLevel: z.number().default(100).describe("Last level"),
244
+ decimals: z.number().default(0).describe("Decimal places"),
245
+ clampMin: z.number().optional().describe("Minimum value clamp"),
246
+ clampMax: z.number().optional().describe("Maximum value clamp"),
247
+ startAtZero: z.boolean().optional().describe("When true, the first level costs 0 XP and the curve shifts one step (value = XP to reach this level from the previous). Default false."),
248
+ base: z.number().default(100).describe("Base XP value"),
249
+ growth: z.number().default(1.15).describe("Growth factor"),
250
+ offset: z.number().default(0).describe("Offset"),
251
+ tierStep: z.number().default(10).describe("Tier step size (tiered preset)"),
252
+ tierMultiplier: z.number().default(1.5).describe("Tier multiplier (tiered preset)"),
253
+ capValue: z.number().default(5000).describe("Cap value (softCap/hardCap/diminishingReturns presets)"),
254
+ capStrength: z.number().default(0.08).describe("Cap strength (softCap preset)"),
255
+ plateauStartLevel: z.number().default(60).describe("Plateau start level (piecewise preset)"),
256
+ plateauFactor: z.number().default(0.35).describe("Plateau factor (piecewise preset)"),
257
+ };
258
+ // For xpBalance, the params are nested under a `params` object in the API
259
+ server.tool("create_xp_balance_addon", "Create an XP balance curve addon", {
260
+ ...projSec,
261
+ name: z.string().describe("Display name"),
262
+ group: z.string().optional().describe("Optional group name"),
263
+ ...xpBalanceFields,
264
+ }, async ({ projectId, sectionId, name, group, base, growth, offset, tierStep, tierMultiplier, capValue, capStrength, plateauStartLevel, plateauFactor, ...rest }) => {
265
+ try {
266
+ return json(await client.createAddon(projectId, sectionId, {
267
+ type: "xpBalance",
268
+ name,
269
+ ...(group ? { group } : {}),
270
+ data: {
271
+ ...rest,
272
+ params: { base, growth, offset, tierStep, tierMultiplier, capValue, capStrength, plateauStartLevel, plateauFactor },
273
+ },
274
+ }));
275
+ }
276
+ catch (e) {
277
+ return err(e);
278
+ }
279
+ });
280
+ server.tool("update_xp_balance_addon", "Update an XP balance curve addon", {
281
+ ...projSecAddon,
282
+ name: z.string().optional().describe("New display name"),
283
+ group: z.string().optional().describe("New group name"),
284
+ mode: z.enum(["preset", "advanced"]).optional().describe("Formula mode"),
285
+ preset: z.enum(["linear", "exponential", "tiered", "softCap", "hardCap", "diminishingReturns", "piecewise"]).optional().describe("Curve preset"),
286
+ expression: z.string().optional().describe("Custom expression (advanced mode)"),
287
+ startLevel: z.number().optional().describe("First level"),
288
+ endLevel: z.number().optional().describe("Last level"),
289
+ decimals: z.number().optional().describe("Decimal places"),
290
+ clampMin: z.number().optional().describe("Minimum value clamp"),
291
+ clampMax: z.number().optional().describe("Maximum value clamp"),
292
+ startAtZero: z.boolean().optional().describe("When true, the first level costs 0 XP and the curve shifts one step. Default false."),
293
+ base: z.number().optional().describe("Base XP value"),
294
+ growth: z.number().optional().describe("Growth factor"),
295
+ offset: z.number().optional().describe("Offset"),
296
+ tierStep: z.number().optional().describe("Tier step size (tiered preset)"),
297
+ tierMultiplier: z.number().optional().describe("Tier multiplier (tiered preset)"),
298
+ capValue: z.number().optional().describe("Cap value (softCap/hardCap/diminishingReturns presets)"),
299
+ capStrength: z.number().optional().describe("Cap strength (softCap preset)"),
300
+ plateauStartLevel: z.number().optional().describe("Plateau start level (piecewise preset)"),
301
+ plateauFactor: z.number().optional().describe("Plateau factor (piecewise preset)"),
302
+ }, async ({ projectId, sectionId, addonId, name, group, base, growth, offset, tierStep, tierMultiplier, capValue, capStrength, plateauStartLevel, plateauFactor, ...rest }) => {
303
+ try {
304
+ const fields = {};
305
+ if (name !== undefined)
306
+ fields.name = name;
307
+ if (group !== undefined)
308
+ fields.group = group;
309
+ const data = { ...rest };
310
+ const params = {};
311
+ if (base !== undefined)
312
+ params.base = base;
313
+ if (growth !== undefined)
314
+ params.growth = growth;
315
+ if (offset !== undefined)
316
+ params.offset = offset;
317
+ if (tierStep !== undefined)
318
+ params.tierStep = tierStep;
319
+ if (tierMultiplier !== undefined)
320
+ params.tierMultiplier = tierMultiplier;
321
+ if (capValue !== undefined)
322
+ params.capValue = capValue;
323
+ if (capStrength !== undefined)
324
+ params.capStrength = capStrength;
325
+ if (plateauStartLevel !== undefined)
326
+ params.plateauStartLevel = plateauStartLevel;
327
+ if (plateauFactor !== undefined)
328
+ params.plateauFactor = plateauFactor;
329
+ if (Object.keys(params).length > 0)
330
+ data.params = params;
331
+ if (Object.keys(data).length > 0)
332
+ fields.data = data;
333
+ return json(await client.updateAddon(projectId, sectionId, addonId, fields));
334
+ }
335
+ catch (e) {
336
+ return err(e);
337
+ }
338
+ });
339
+ // ── 7. Production ───────────────────────────────────────────────
340
+ const ingredientSchema = z.object({
341
+ itemRef: z.string().describe("Section ID of the item"),
342
+ quantity: z.number().describe("Required quantity"),
343
+ });
344
+ const outputSchema = z.object({
345
+ itemRef: z.string().describe("Section ID of the output item"),
346
+ quantity: z.number().describe("Output quantity"),
347
+ });
348
+ // Production values (min/max output, interval, craft time) can optionally
349
+ // be scaled per-level by linking to a progression table column. When linked,
350
+ // the scalar field is populated from the progression row at runtime.
351
+ const productionProgressionLinkSchema = z.object({
352
+ progressionAddonId: z.string().describe("Outer ID of the progressionTable addon supplying values"),
353
+ columnId: z.string().describe("Column ID inside that progression table"),
354
+ columnName: z.string().describe("Cached column name for display"),
355
+ });
356
+ const productionFields = {
357
+ mode: z.enum(["passive", "recipe"]).default("passive").describe("Production mode"),
358
+ outputRef: z.string().optional().describe("Output item section ID (passive mode)"),
359
+ minOutput: z.number().optional().default(1).describe("Minimum output quantity"),
360
+ minOutputBinding: valueBinding.describe("Optional binding for minOutput (sheets | progressionColumn)."),
361
+ minOutputProgressionLink: productionProgressionLinkSchema.optional().describe("Link minOutput to a progression table column (level-scaled)."),
362
+ maxOutput: z.number().optional().default(1).describe("Maximum output quantity"),
363
+ maxOutputBinding: valueBinding.describe("Optional binding for maxOutput (sheets | progressionColumn)."),
364
+ maxOutputProgressionLink: productionProgressionLinkSchema.optional().describe("Link maxOutput to a progression table column (level-scaled)."),
365
+ intervalSeconds: z.number().optional().default(60).describe("Production interval in seconds"),
366
+ intervalSecondsBinding: valueBinding.describe("Optional binding for intervalSeconds (sheets | progressionColumn)."),
367
+ intervalSecondsProgressionLink: productionProgressionLinkSchema.optional().describe("Link intervalSeconds to a progression table column (level-scaled)."),
368
+ requiresCollection: z.boolean().optional().default(false).describe("Requires manual collection?"),
369
+ capacity: z.number().optional().describe("Storage capacity"),
370
+ capacityBinding: valueBinding.describe("Optional binding for capacity (sheets | progressionColumn)."),
371
+ ingredients: z.array(ingredientSchema).optional().default([]).describe("Recipe ingredients"),
372
+ outputs: z.array(outputSchema).optional().default([]).describe("Recipe outputs"),
373
+ craftTimeSeconds: z.number().optional().default(60).describe("Craft time in seconds"),
374
+ craftTimeSecondsBinding: valueBinding.describe("Optional binding for craftTimeSeconds (sheets | progressionColumn)."),
375
+ craftTimeSecondsProgressionLink: productionProgressionLinkSchema.optional().describe("Link craftTimeSeconds to a progression table column (level-scaled)."),
376
+ notes: z.string().optional().describe("Design notes"),
377
+ };
378
+ pair("production", "production", "production (passive or recipe)", productionFields, optional(productionFields));
379
+ // ── 7b. Craft Table ─────────────────────────────────────────────
380
+ const craftTableUnlockSchema = z.object({
381
+ level: z.object({
382
+ enabled: z.boolean(),
383
+ xpAddonRef: z.string().optional().describe("Section ID of the XP Balance addon"),
384
+ level: z.number().optional().describe("Required level"),
385
+ }).optional(),
386
+ currency: z.object({
387
+ enabled: z.boolean(),
388
+ currencyAddonRef: z.string().optional().describe("Section ID of the Currency addon"),
389
+ amount: z.number().optional().describe("Required amount"),
390
+ }).optional(),
391
+ item: z.object({
392
+ enabled: z.boolean(),
393
+ itemRef: z.string().optional().describe("Section ID of the item (Inventory addon)"),
394
+ quantity: z.number().optional().describe("Required quantity"),
395
+ }).optional(),
396
+ });
397
+ const craftTableEntrySchema = z.object({
398
+ id: z.string().optional().describe("Entry ID (auto-generated if omitted)"),
399
+ productionRef: z.string().optional().describe("Section ID of the Production addon (recipe)"),
400
+ category: z.string().optional().describe("Category label (free text; shared across entries in this table)"),
401
+ order: z.number().describe("Display order"),
402
+ unlock: craftTableUnlockSchema.optional().describe("Unlock conditions (AND of enabled slots; none enabled = always unlocked)"),
403
+ hidden: z.boolean().optional().describe("Hide entry without deleting"),
404
+ });
405
+ const craftTableFields = {
406
+ entries: z.array(craftTableEntrySchema).default([]).describe("Recipes available on this table"),
407
+ };
408
+ pair("craft_table", "craftTable", "craft table (station aggregating Production recipes with unlock conditions)", craftTableFields, optional(craftTableFields));
409
+ // ── 7c. Crop (Plantar e Colher) ─────────────────────────────────
410
+ const cropXpEventSchema = z.object({
411
+ xpAddonRef: z.string().optional().describe("Section ID of the XP Balance addon that tracks this XP pool"),
412
+ xp: z.number().optional().describe("XP amount awarded"),
413
+ xpBinding: valueBinding.describe("Optional binding for the XP amount (sheets | progressionColumn)."),
414
+ }).describe("XP event (plant or harvest)");
415
+ const cropStageSchema = z.object({
416
+ id: z.string().optional().describe("Stage ID (auto-generated if omitted)"),
417
+ label: z.string().describe("Stage label (e.g. 'Broto', 'Crescendo', 'Maduro')"),
418
+ secondsFromPlanting: z.number().describe("Seconds after planting when this stage begins"),
419
+ });
420
+ const cropOutputSchema = z.object({
421
+ id: z.string().optional().describe("Output row ID (auto-generated if omitted)"),
422
+ itemRef: z.string().optional().describe("Section ID of the harvested item"),
423
+ quantity: z.number().optional().describe("Base yield per harvest"),
424
+ quantityBinding: valueBinding.describe("Optional binding for the yield (sheets | progressionColumn)."),
425
+ quantityMin: z.number().optional().describe("Minimum yield"),
426
+ quantityMax: z.number().optional().describe("Maximum yield"),
427
+ });
428
+ const cropItemInputSchema = z.object({
429
+ id: z.string().optional().describe("Input row ID (auto-generated if omitted)"),
430
+ itemRef: z.string().optional().describe("Section ID of the consumable item"),
431
+ });
432
+ const cropFields = {
433
+ harvestMode: z.enum(["instant", "progressive"]).default("instant").describe("'instant' = single harvest, plant dies. 'progressive' = multiple harvest cycles over the same planting."),
434
+ growthSeconds: z.number().optional().describe("Base growth time in seconds"),
435
+ growthSecondsBinding: valueBinding.describe("Optional binding for growthSeconds (sheets | progressionColumn)."),
436
+ growthSecondsMin: z.number().optional().describe("Minimum growth time (lower bound)"),
437
+ growthSecondsMax: z.number().optional().describe("Maximum growth time (upper bound)"),
438
+ totalHarvest: z.number().optional().describe("Number of harvest cycles (progressive mode only)"),
439
+ totalHarvestBinding: valueBinding.describe("Optional binding for totalHarvest (sheets | progressionColumn)."),
440
+ totalHarvestMin: z.number().optional().describe("Minimum harvest cycles (progressive only)"),
441
+ totalHarvestMax: z.number().optional().describe("Maximum harvest cycles (progressive only)"),
442
+ stages: z.array(cropStageSchema).default([]).describe("Visual growth stages (progressive mode)"),
443
+ outputs: z.array(cropOutputSchema).default([]).describe("Items produced on each harvest"),
444
+ plantXp: cropXpEventSchema.optional().describe("XP awarded when planting"),
445
+ harvestXp: cropXpEventSchema.optional().describe("XP awarded when harvesting"),
446
+ spawnWitheredPlant: z.boolean().default(false).describe("Spawn a post-harvest page when the plant expires"),
447
+ witheredPlantRef: z.string().optional().describe("Section ID of the page to spawn after expiry"),
448
+ seedRef: z.string().optional().describe("Section ID of the seed item, or '__self__' to use this page as its own seed"),
449
+ seedQuantity: z.number().optional().describe("Base seed cost"),
450
+ seedQuantityBinding: valueBinding.describe("Optional binding for seedQuantity (sheets | progressionColumn)."),
451
+ seedQuantityMin: z.number().optional().describe("Minimum seed cost"),
452
+ seedQuantityMax: z.number().optional().describe("Maximum seed cost"),
453
+ plantEnergy: z.number().optional().describe("Energy consumed when planting"),
454
+ plantEnergyBinding: valueBinding.describe("Optional binding for plantEnergy (sheets | progressionColumn)."),
455
+ plantEnergyMin: z.number().optional().describe("Minimum energy cost"),
456
+ plantEnergyMax: z.number().optional().describe("Maximum energy cost"),
457
+ fertilizers: z.array(cropItemInputSchema).default([]).describe("Fertilizer items accepted by this crop"),
458
+ amendments: z.array(cropItemInputSchema).default([]).describe("Soil amendment items accepted by this crop"),
459
+ seasons: z.array(z.enum(["spring", "summer", "fall", "winter", "greenhouse"])).optional().describe("Seasons in which this crop can be planted"),
460
+ notes: z.string().optional().describe("Design notes"),
461
+ };
462
+ pair("crop", "crop", "crop / plant-and-harvest mechanic", cropFields, optional(cropFields));
463
+ // ── 8. Data Schema ──────────────────────────────────────────────
464
+ // Data schema entries can SOURCE their value from several places instead
465
+ // of storing it directly. At most one source should be set at a time.
466
+ const economyLinkFieldEnum = z.enum([
467
+ "buyValue",
468
+ "minBuyValue",
469
+ "maxBuyValue",
470
+ "sellValue",
471
+ "minSellValue",
472
+ "maxSellValue",
473
+ "unlockValue",
474
+ "unlockValueMin",
475
+ "unlockValueMax",
476
+ "buyCurrencyRef",
477
+ "sellCurrencyRef",
478
+ "buyCurrencyKey",
479
+ "sellCurrencyKey",
480
+ ]);
481
+ const productionFieldEnum = z.enum([
482
+ "minOutput",
483
+ "maxOutput",
484
+ "intervalSeconds",
485
+ "intervalSecondsMin",
486
+ "intervalSecondsMax",
487
+ "craftTimeSeconds",
488
+ "craftTimeSecondsMin",
489
+ "craftTimeSecondsMax",
490
+ "capacity",
491
+ "capacityMin",
492
+ "capacityMax",
493
+ "outputBuyEffective",
494
+ "outputMinBuyValue",
495
+ "outputSellEffective",
496
+ "outputMaxSellValue",
497
+ "outputUnlockValue",
498
+ ]);
499
+ const dataSchemaEntrySchema = z.object({
500
+ id: z.string().optional().describe("Entry ID (auto-generated if omitted)"),
501
+ key: z.string().describe("Data key (e.g. max_hp). Ignored when libraryRef is set — the Library entry's key takes precedence."),
502
+ label: z.string().describe("Display label. Ignored when libraryRef is set."),
503
+ libraryRef: z.object({
504
+ libraryAddonId: z.string().describe("Outer ID of the Field Library addon"),
505
+ entryId: z.string().describe("Entry ID inside the Field Library"),
506
+ }).optional().describe("Bind key/label to a Field Library entry — keeps names consistent across schemas."),
507
+ valueType: z.enum(["int", "float", "seconds", "percent", "boolean", "string"]).describe("Value type"),
508
+ value: z.union([z.number(), z.boolean(), z.string()]).describe("Default value (ignored when a source ref is set)"),
509
+ min: z.number().optional().describe("Minimum value"),
510
+ max: z.number().optional().describe("Maximum value"),
511
+ unit: z.string().optional().describe("Display unit (e.g. 'hp', 's')"),
512
+ unitXpRef: z.string().optional().describe("Section ID of an xpBalance addon — shows unit as XP per level."),
513
+ economyLinkRef: z.string().optional().describe("Section ID that has an economyLink addon — sources the value from it."),
514
+ economyLinkField: economyLinkFieldEnum.optional().describe("Which field from the Economy Link addon to pull."),
515
+ productionRef: z.string().optional().describe("Addon ID of a Production addon in the same section — sources the value from it."),
516
+ productionField: productionFieldEnum.optional().describe("Which field from the Production addon to pull."),
517
+ usePageDataId: z.boolean().optional().describe("When true, value is derived from the section's dataId field."),
518
+ notes: z.string().optional().describe("Design notes"),
519
+ });
520
+ const dataSchemaFields = {
521
+ entries: z.array(dataSchemaEntrySchema).describe("Data entries"),
522
+ };
523
+ pair("data_schema", "dataSchema", "data schema (key-value stats)", dataSchemaFields, optional(dataSchemaFields));
524
+ // ── 9. Attribute Definitions ────────────────────────────────────
525
+ const attrDefEntrySchema = z.object({
526
+ id: z.string().optional().describe("Attribute ID (auto-generated if omitted)"),
527
+ key: z.string().describe("Attribute key (e.g. strength)"),
528
+ label: z.string().describe("Display label"),
529
+ valueType: z.enum(["int", "float", "percent", "boolean"]).describe("Value type"),
530
+ defaultValue: z.union([z.number(), z.boolean()]).describe("Default value"),
531
+ min: z.number().optional().describe("Minimum value"),
532
+ max: z.number().optional().describe("Maximum value"),
533
+ unit: z.string().optional().describe("Display unit"),
534
+ });
535
+ const attrDefsFields = {
536
+ attributes: z.array(attrDefEntrySchema).describe("Attribute definitions"),
537
+ };
538
+ pair("attribute_definitions", "attributeDefinitions", "attribute definitions (STR, DEX, etc.)", attrDefsFields, optional(attrDefsFields));
539
+ // ── 10. Attribute Profile ───────────────────────────────────────
540
+ const attrProfileValueSchema = z.object({
541
+ id: z.string().optional().describe("Value entry ID"),
542
+ attributeKey: z.string().describe("Attribute key from definitions"),
543
+ value: z.union([z.number(), z.boolean()]).describe("Attribute value"),
544
+ });
545
+ const attrProfileFields = {
546
+ definitionsRef: z.string().optional().describe("Section ID of the attribute definitions addon"),
547
+ values: z.array(attrProfileValueSchema).describe("Attribute values"),
548
+ };
549
+ pair("attribute_profile", "attributeProfile", "attribute profile (character stats)", attrProfileFields, optional(attrProfileFields));
550
+ // ── 11. Attribute Modifiers ─────────────────────────────────────
551
+ const attrModEntrySchema = z.object({
552
+ id: z.string().optional().describe("Modifier ID"),
553
+ name: z.string().optional().describe("Optional display name (e.g. 'Fireball impact'). Falls back to auto-formatted label when empty."),
554
+ attributeKey: z.string().describe("Attribute key to modify"),
555
+ mode: z.enum(["add", "mult", "set"]).describe("Modifier mode (add, multiply, or set)"),
556
+ value: z.union([z.number(), z.boolean()]).describe("Modifier value"),
557
+ });
558
+ const attrModsFields = {
559
+ definitionsRef: z.string().optional().describe("Section ID of the attribute definitions addon"),
560
+ modifiers: z.array(attrModEntrySchema).describe("Attribute modifiers"),
561
+ };
562
+ pair("attribute_modifiers", "attributeModifiers", "attribute modifiers (+10 STR, x1.5 DEX)", attrModsFields, optional(attrModsFields));
563
+ // ── 12. Field Library ───────────────────────────────────────────
564
+ const fieldLibraryEntrySchema = z.object({
565
+ id: z.string().optional().describe("Entry ID (auto-generated if omitted)"),
566
+ key: z.string().describe("Field key (e.g. sell_price). Used in the exported JSON."),
567
+ label: z.string().describe("Display name for the field"),
568
+ description: z.string().optional().describe("Optional description of what this field means"),
569
+ });
570
+ const fieldLibraryFields = {
571
+ entries: z.array(fieldLibraryEntrySchema).describe("Reusable field definitions"),
572
+ };
573
+ pair("field_library", "fieldLibrary", "field library (reusable field definitions for progression tables and data schemas)", fieldLibraryFields, optional(fieldLibraryFields));
574
+ // ── 13. Export Schema ───────────────────────────────────────────
575
+ const exportSchemaBindingSchema = z.object({
576
+ source: z.enum([
577
+ "manual",
578
+ "dataSchema",
579
+ "rowLevel",
580
+ "rowColumn",
581
+ // craftTable-scoped
582
+ "entryField",
583
+ "productionField",
584
+ "itemField",
585
+ // skills-scoped
586
+ "skillField",
587
+ "skillCostField",
588
+ "skillEffectField",
589
+ ]).describe("Binding source. 'rowLevel' / 'rowColumn' are valid only inside a " +
590
+ "progressionTable array. 'entryField' is valid inside a craftTable array. " +
591
+ "'productionField' reads a scalar from a Production addon: inside a craftTable array it " +
592
+ "follows entry.productionRef (no addonId); standalone, set addonId to a Production addon " +
593
+ "on the page to export a recipe directly without a Craft Table. " +
594
+ "'itemField' is valid inside productionIngredients/productionOutputs arrays. " +
595
+ "'skillField' is valid inside a skills array (or any descendant of one). " +
596
+ "'skillCostField' is valid inside a skillCosts array. " +
597
+ "'skillEffectField' is valid inside a skillEffects array."),
598
+ // source: manual
599
+ value: z.union([z.string(), z.number(), z.boolean()]).optional(),
600
+ valueType: z.enum(["string", "number", "boolean"]).optional(),
601
+ // source: dataSchema
602
+ addonId: z.string().optional(),
603
+ addonName: z.string().optional(),
604
+ entryKey: z.string().optional(),
605
+ entryId: z.string().optional(),
606
+ // source: rowColumn
607
+ columnId: z.string().optional(),
608
+ // source: entryField | productionField | itemField | skill*Field
609
+ field: z.string().optional().describe("entryField: order|productionRef|category|hidden|unlockLevelEnabled|unlockLevel|" +
610
+ "unlockLevelXpRef|unlockCurrencyEnabled|unlockCurrencyAmount|unlockCurrencyRef|" +
611
+ "unlockItemEnabled|unlockItemQuantity|unlockItemRef. " +
612
+ "productionField: name|mode|craftTimeSeconds|outputItemRef|outputQuantity (first output row, " +
613
+ "for flat single-output recipes)|minOutput|maxOutput|intervalSeconds|" +
614
+ "capacity|requiresCollection|outputRef. " +
615
+ "itemField: itemRef|quantity. " +
616
+ "skillField: id|name|kind|description|cooldownSeconds|tagsCsv|unlockLevelEnabled|" +
617
+ "unlockLevel|unlockLevelXpRef|unlockCurrencyEnabled|unlockCurrencyAmount|" +
618
+ "unlockCurrencyRef|unlockItemEnabled|unlockItemQuantity|unlockItemRef. " +
619
+ "skillCostField: id|type|amount|currencyRef|definitionsRef|attributeKey. " +
620
+ "skillEffectField: id|attributeModifiersSectionId|attributeModifiersAddonId|" +
621
+ "modifierEntryId|resolvedName|resolvedMode|resolvedAttributeKey|resolvedDefinitionsRef|" +
622
+ "resolvedValue|resolvedTemporary|resolvedDurationSeconds|resolvedTickIntervalSeconds|" +
623
+ "resolvedStacking|resolvedCategory."),
624
+ }).describe("Value binding");
625
+ const exportSchemaArraySourceSchema = z.object({
626
+ type: z.enum([
627
+ "progressionTable",
628
+ "xpBalance",
629
+ "craftTable",
630
+ "productionIngredients",
631
+ "productionOutputs",
632
+ "skills",
633
+ "skillCosts",
634
+ "skillEffects",
635
+ "sections",
636
+ ]).describe("'progressionTable', 'xpBalance', 'craftTable' and 'skills' require addonId. " +
637
+ "'xpBalance' iterates the computed level→value curve of an XP Balance addon " +
638
+ "(use rowLevel for the level and rowColumn with columnId 'value' for the XP). " +
639
+ "'productionIngredients' and 'productionOutputs' iterate a Production addon's ingredient/" +
640
+ "output rows. With addonId set they read that Production addon directly (standalone Recipe " +
641
+ "export — no Craft Table needed); without addonId they follow the current craftTable entry's " +
642
+ "production (must then be nested inside a craftTable array node). " +
643
+ "'skillCosts' and 'skillEffects' follow the current skills entry and do not take an " +
644
+ "addonId (must be nested inside a skills array node). " +
645
+ "'sections' iterates the child sections of parentSectionId, resolving the itemTemplate " +
646
+ "against each child's own addons (one object per child page — e.g. aggregate every seed " +
647
+ "page under a parent into a single array). Bindings resolve via addonName + entryKey " +
648
+ "fallback, so a template authored against one child resolves across all siblings."),
649
+ addonId: z.string().optional().describe("Section ID of the target addon (progressionTable, xpBalance, craftTable, skills, or a Production addon for standalone productionIngredients/productionOutputs)"),
650
+ addonName: z.string().optional().describe("Fallback match by name when used in templates"),
651
+ parentSectionId: z.string().optional().describe("Parent section ID whose child sections are iterated (required for type 'sections')"),
652
+ parentSectionName: z.string().optional().describe("Display name of the parent section (optional, for readability)"),
653
+ }).describe("Array iteration source");
654
+ const exportSchemaNodeSchema = z.lazy(() => z.object({
655
+ id: z.string().optional().describe("Node ID"),
656
+ key: z.string().describe("JSON key"),
657
+ nodeType: z.enum(["object", "array", "value"]).describe("Node type"),
658
+ children: z.array(exportSchemaNodeSchema).optional().describe("Child nodes (for object type)"),
659
+ // array node
660
+ arraySource: exportSchemaArraySourceSchema.optional().describe("Iteration source (for array type)"),
661
+ itemTemplate: z.array(exportSchemaNodeSchema).optional().describe("Template applied per iteration (for array type)"),
662
+ // value node
663
+ binding: exportSchemaBindingSchema.optional(),
664
+ abs: z.boolean().optional().describe("Apply Math.abs to the resolved numeric value"),
665
+ multiplier: z.number().optional().describe("Multiply the resolved numeric value by this factor"),
666
+ }));
667
+ const exportSchemaFields = {
668
+ nodes: z.array(exportSchemaNodeSchema).describe("Export schema tree nodes"),
669
+ arrayFormat: z.enum(["rowMajor", "columnMajor", "keyedByLevel", "matrix"]).optional().describe("Array output format (only applies to progressionTable arrays; craftTable and production " +
670
+ "arrays are always rowMajor)."),
671
+ };
672
+ pair("export_schema", "exportSchema", "export/remote config schema", exportSchemaFields, optional(exportSchemaFields));
673
+ // ── 14. Rich Doc ────────────────────────────────────────────────
674
+ const richDocFields = {
675
+ blocks: z
676
+ .array(z.record(z.string(), z.unknown()))
677
+ .describe("BlockNote document blocks (each block is an object with id/type/props/content/children). Opaque — forwarded as-is."),
678
+ schemaVersion: z.literal(1).optional().describe("Schema version, always 1"),
679
+ };
680
+ pair("rich_doc", "richDoc", "rich document (Notion-style blocks: headings, lists, images, embeds, columns)", richDocFields, optional(richDocFields));
681
+ }
682
+ //# sourceMappingURL=addon-tools.js.map