@crowdedkingdoms/crowdyjs 8.2.0 → 8.3.0

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.
Files changed (102) hide show
  1. package/README.md +21 -3
  2. package/dist/crowdy-client.d.ts.map +1 -1
  3. package/dist/crowdy-client.js +5 -1
  4. package/dist/index.d.ts +2 -2
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +2 -2
  7. package/dist/kit/blueprints/combat.d.ts +75 -0
  8. package/dist/kit/blueprints/combat.d.ts.map +1 -0
  9. package/dist/kit/blueprints/combat.js +351 -0
  10. package/dist/kit/blueprints/core.d.ts +222 -0
  11. package/dist/kit/blueprints/core.d.ts.map +1 -0
  12. package/dist/kit/blueprints/core.js +185 -0
  13. package/dist/kit/blueprints/decks.d.ts +50 -0
  14. package/dist/kit/blueprints/decks.d.ts.map +1 -0
  15. package/dist/kit/blueprints/decks.js +163 -0
  16. package/dist/kit/blueprints/economy.d.ts +88 -0
  17. package/dist/kit/blueprints/economy.d.ts.map +1 -0
  18. package/dist/kit/blueprints/economy.js +533 -0
  19. package/dist/kit/blueprints/guild.d.ts +51 -0
  20. package/dist/kit/blueprints/guild.d.ts.map +1 -0
  21. package/dist/kit/blueprints/guild.js +50 -0
  22. package/dist/kit/blueprints/index.d.ts +21 -0
  23. package/dist/kit/blueprints/index.d.ts.map +1 -0
  24. package/dist/kit/blueprints/index.js +20 -0
  25. package/dist/kit/blueprints/inventory.d.ts +38 -0
  26. package/dist/kit/blueprints/inventory.d.ts.map +1 -0
  27. package/dist/kit/blueprints/inventory.js +157 -0
  28. package/dist/kit/blueprints/leaderboards.d.ts +49 -0
  29. package/dist/kit/blueprints/leaderboards.d.ts.map +1 -0
  30. package/dist/kit/blueprints/leaderboards.js +126 -0
  31. package/dist/kit/blueprints/locks.d.ts +82 -0
  32. package/dist/kit/blueprints/locks.d.ts.map +1 -0
  33. package/dist/kit/blueprints/locks.js +155 -0
  34. package/dist/kit/blueprints/loot.d.ts +84 -0
  35. package/dist/kit/blueprints/loot.d.ts.map +1 -0
  36. package/dist/kit/blueprints/loot.js +228 -0
  37. package/dist/kit/blueprints/matches.d.ts +59 -0
  38. package/dist/kit/blueprints/matches.d.ts.map +1 -0
  39. package/dist/kit/blueprints/matches.js +246 -0
  40. package/dist/kit/blueprints/npcs.d.ts +71 -0
  41. package/dist/kit/blueprints/npcs.d.ts.map +1 -0
  42. package/dist/kit/blueprints/npcs.js +122 -0
  43. package/dist/kit/blueprints/plots.d.ts +54 -0
  44. package/dist/kit/blueprints/plots.d.ts.map +1 -0
  45. package/dist/kit/blueprints/plots.js +146 -0
  46. package/dist/kit/blueprints/progression.d.ts +68 -0
  47. package/dist/kit/blueprints/progression.d.ts.map +1 -0
  48. package/dist/kit/blueprints/progression.js +324 -0
  49. package/dist/kit/blueprints/quests.d.ts +65 -0
  50. package/dist/kit/blueprints/quests.d.ts.map +1 -0
  51. package/dist/kit/blueprints/quests.js +284 -0
  52. package/dist/kit/blueprints/worldsim.d.ts +84 -0
  53. package/dist/kit/blueprints/worldsim.d.ts.map +1 -0
  54. package/dist/kit/blueprints/worldsim.js +461 -0
  55. package/dist/kit/combat.d.ts +103 -0
  56. package/dist/kit/combat.d.ts.map +1 -0
  57. package/dist/kit/combat.js +151 -0
  58. package/dist/kit/decks.d.ts +123 -0
  59. package/dist/kit/decks.d.ts.map +1 -0
  60. package/dist/kit/decks.js +138 -0
  61. package/dist/kit/economy.d.ts +244 -0
  62. package/dist/kit/economy.d.ts.map +1 -0
  63. package/dist/kit/economy.js +365 -0
  64. package/dist/kit/features.d.ts +60 -0
  65. package/dist/kit/features.d.ts.map +1 -0
  66. package/dist/kit/features.js +65 -0
  67. package/dist/kit/index.d.ts +13 -2
  68. package/dist/kit/index.d.ts.map +1 -1
  69. package/dist/kit/index.js +12 -1
  70. package/dist/kit/inventory.d.ts +5 -0
  71. package/dist/kit/inventory.d.ts.map +1 -1
  72. package/dist/kit/inventory.js +14 -1
  73. package/dist/kit/kit.d.ts +58 -2
  74. package/dist/kit/kit.d.ts.map +1 -1
  75. package/dist/kit/kit.js +24 -2
  76. package/dist/kit/leaderboards.d.ts +66 -0
  77. package/dist/kit/leaderboards.d.ts.map +1 -0
  78. package/dist/kit/leaderboards.js +94 -0
  79. package/dist/kit/loot.d.ts +106 -0
  80. package/dist/kit/loot.d.ts.map +1 -0
  81. package/dist/kit/loot.js +120 -0
  82. package/dist/kit/matches.d.ts +140 -0
  83. package/dist/kit/matches.d.ts.map +1 -0
  84. package/dist/kit/matches.js +253 -0
  85. package/dist/kit/objects.js +1 -1
  86. package/dist/kit/plots.js +1 -1
  87. package/dist/kit/progression.d.ts +181 -0
  88. package/dist/kit/progression.d.ts.map +1 -0
  89. package/dist/kit/progression.js +265 -0
  90. package/dist/kit/quests.d.ts +110 -0
  91. package/dist/kit/quests.d.ts.map +1 -0
  92. package/dist/kit/quests.js +158 -0
  93. package/dist/kit/social.d.ts +267 -0
  94. package/dist/kit/social.d.ts.map +1 -0
  95. package/dist/kit/social.js +230 -0
  96. package/dist/kit/worldsim.d.ts +225 -0
  97. package/dist/kit/worldsim.d.ts.map +1 -0
  98. package/dist/kit/worldsim.js +231 -0
  99. package/package.json +1 -1
  100. package/dist/kit/blueprints.d.ts +0 -370
  101. package/dist/kit/blueprints.d.ts.map +0 -1
  102. package/dist/kit/blueprints.js +0 -643
@@ -0,0 +1,155 @@
1
+ import { andPolicies, kitPolicyJson, toSnakeCase, } from './core.js';
2
+ /** Compute the type/function names a lock blueprint (and its runtime helper) uses. */
3
+ export function lockNames(objectTypeName = 'Lockable', keyTypeName) {
4
+ const snake = toSnakeCase(objectTypeName);
5
+ return {
6
+ objectType: objectTypeName,
7
+ keyType: keyTypeName ?? `${objectTypeName}Key`,
8
+ openFn: `open_${snake}`,
9
+ closeFn: `close_${snake}`,
10
+ };
11
+ }
12
+ function lockAuthorityRule(authority) {
13
+ switch (authority.kind) {
14
+ case 'owner':
15
+ return { type: 'owner_of_self' };
16
+ case 'key':
17
+ // Container ownership is not readable from expressions, so the key
18
+ // mirrors its owner into an `owner_user_id` property; the condition
19
+ // verifies both the match and the ownership server-side.
20
+ return {
21
+ type: 'condition',
22
+ expression: 'ref($key_id).key_id == self.required_key_id && ref($key_id).owner_user_id == $caller_user_id',
23
+ };
24
+ case 'gridPermission':
25
+ return {
26
+ type: 'grid_permission',
27
+ key: authority.key,
28
+ ...(authority.gridId !== undefined ? { gridId: authority.gridId } : {}),
29
+ };
30
+ case 'chunkPermission':
31
+ // Resolved per-invocation against the grid covering the object's chunk.
32
+ return {
33
+ type: 'condition',
34
+ expression: `has_chunk_permission($caller_user_id, "${authority.key}", self.cx, self.cy, self.cz, "${authority.mode ?? 'first'}")`,
35
+ };
36
+ case 'groupPermission':
37
+ return {
38
+ type: 'group_permission',
39
+ groupId: authority.groupId,
40
+ ...(authority.permission !== undefined
41
+ ? { permission: authority.permission }
42
+ : {}),
43
+ };
44
+ case 'custom':
45
+ return authority.rule;
46
+ }
47
+ }
48
+ /**
49
+ * Blueprint for a **lockable game object** (door, chest, gate, switch) whose
50
+ * `open`/`close` functions are gated by a configurable authority source:
51
+ * ownership, a key item the caller must hold, a runtime grid permission, a
52
+ * team/group permission, or any custom policy rule. Multiple authorities are
53
+ * OR'd, so "the owner, or anyone with the right key" is one blueprint.
54
+ *
55
+ * Runtime counterpart: `client.kit(appId).objects`.
56
+ */
57
+ export function lockBlueprint(options) {
58
+ const authorities = Array.isArray(options.authority)
59
+ ? options.authority
60
+ : [options.authority];
61
+ if (authorities.length === 0) {
62
+ throw new Error('lockBlueprint requires at least one authority source');
63
+ }
64
+ const names = lockNames(options.objectTypeName, options.keyTypeName);
65
+ const usesKey = authorities.some((a) => a.kind === 'key');
66
+ const usesChunk = authorities.some((a) => a.kind === 'chunkPermission');
67
+ const rules = authorities.map(lockAuthorityRule);
68
+ const policy = rules.length === 1 ? rules[0] : { type: 'or', rules };
69
+ const policyJson = kitPolicyJson(andPolicies(policy, options.policyExtra));
70
+ const parameters = usesKey
71
+ ? [{ name: 'key_id', valueType: 'container_ref', required: true }]
72
+ : [];
73
+ const containerTypes = [
74
+ {
75
+ typeName: names.objectType,
76
+ displayName: names.objectType,
77
+ instantiableBy: 'admin',
78
+ description: 'A lockable world object operated through gated functions.',
79
+ },
80
+ ];
81
+ const propertyDefinitions = [
82
+ {
83
+ containerTypeName: names.objectType,
84
+ key: 'is_open',
85
+ valueType: 'bool',
86
+ defaultValueJson: 'false',
87
+ },
88
+ ];
89
+ if (usesChunk) {
90
+ // The chunk the object stands in, read by has_chunk_permission().
91
+ for (const axis of ['cx', 'cy', 'cz']) {
92
+ propertyDefinitions.push({
93
+ containerTypeName: names.objectType,
94
+ key: axis,
95
+ valueType: 'int',
96
+ defaultValueJson: '0',
97
+ });
98
+ }
99
+ }
100
+ if (usesKey) {
101
+ propertyDefinitions.push({
102
+ containerTypeName: names.objectType,
103
+ key: 'required_key_id',
104
+ valueType: 'string',
105
+ });
106
+ containerTypes.push({
107
+ typeName: names.keyType,
108
+ displayName: names.keyType,
109
+ instantiableBy: 'admin',
110
+ description: 'A key item granting access to matching lockable objects.',
111
+ });
112
+ propertyDefinitions.push({ containerTypeName: names.keyType, key: 'key_id', valueType: 'string' }, {
113
+ containerTypeName: names.keyType,
114
+ key: 'owner_user_id',
115
+ valueType: 'int',
116
+ });
117
+ }
118
+ const openMutation = {
119
+ target: 'self',
120
+ property: 'is_open',
121
+ expression: 'true',
122
+ };
123
+ const closeMutation = {
124
+ target: 'self',
125
+ property: 'is_open',
126
+ expression: 'false',
127
+ };
128
+ return {
129
+ name: names.objectType,
130
+ containerTypes,
131
+ propertyDefinitions,
132
+ functions: [
133
+ {
134
+ name: names.openFn,
135
+ containerTypeName: names.objectType,
136
+ returnType: 'bool',
137
+ parameters,
138
+ mutations: [openMutation],
139
+ returnExpression: 'self.is_open',
140
+ invokePolicyJson: policyJson,
141
+ description: `Open a ${names.objectType}; the invoke policy decides who may.`,
142
+ },
143
+ {
144
+ name: names.closeFn,
145
+ containerTypeName: names.objectType,
146
+ returnType: 'bool',
147
+ parameters,
148
+ mutations: [closeMutation],
149
+ returnExpression: 'self.is_open',
150
+ invokePolicyJson: policyJson,
151
+ description: `Close a ${names.objectType}; same authority as opening.`,
152
+ },
153
+ ],
154
+ };
155
+ }
@@ -0,0 +1,84 @@
1
+ import { type KitBlueprint, type KitOwnerIdKind, type KitTrustedAuthority } from './core.js';
2
+ /** One weighted entry of a loot table. */
3
+ export interface LootEntrySpec {
4
+ itemId: string;
5
+ /** Relative weight (> 0); probabilities are weights normalized per table. */
6
+ weight: number;
7
+ /** Minimum quantity granted. Defaults to 1. */
8
+ minQty?: number;
9
+ /** Maximum quantity granted. Defaults to `minQty`. */
10
+ maxQty?: number;
11
+ }
12
+ /** One loot table, unrolled into a single expression at blueprint-build time. */
13
+ export interface LootTableSpec {
14
+ /** Stable table identifier; also derives the roll function name. */
15
+ tableId: string;
16
+ /** 1–16 weighted entries (the expression parser caps chain length). */
17
+ entries: LootEntrySpec[];
18
+ }
19
+ /** An event-triggered drop: rolls a pooled LootRoll when a model event fires. */
20
+ export interface LootDropSpec {
21
+ /** Automation name (unique per app), e.g. `'goblin-drop'`. */
22
+ name: string;
23
+ /** The table to roll. */
24
+ tableId: string;
25
+ /** The model event that triggers the drop (e.g. a `mob_died` function). */
26
+ onEvent: 'function_invoked' | 'property_changed' | 'container_created';
27
+ functionName?: string;
28
+ containerTypeName?: string;
29
+ propertyKey?: string;
30
+ debounceMs?: number;
31
+ /** Rolls per event. Defaults to 1. */
32
+ maxTargets?: number;
33
+ }
34
+ /** Options for {@link lootBlueprint}. */
35
+ export interface LootBlueprintOptions {
36
+ /** Prefix for the type/function names. Defaults to none. */
37
+ typePrefix?: string;
38
+ /** The loot tables; entries are unrolled into expressions at build time. */
39
+ tables: LootTableSpec[];
40
+ /**
41
+ * Who may roll. Defaults to `'server'` (app admins / studio backend);
42
+ * automations configured via `drops` may always roll (trusted automations
43
+ * bypass invoke policies, and the roll functions are marked
44
+ * `autonomousInvocable` when drops reference their table).
45
+ */
46
+ rollAuthority?: KitTrustedAuthority;
47
+ /**
48
+ * Event-triggered drops: each rolls an UNROLLED pooled `LootRoll` container
49
+ * of its table when the event fires (automations mutate — they cannot
50
+ * create containers — so keep a small pool of pre-created rolls per table).
51
+ */
52
+ drops?: LootDropSpec[];
53
+ /** Owner-mirror typing (see the kit convention). Defaults to `'int'`. */
54
+ ownerIdKind?: KitOwnerIdKind;
55
+ }
56
+ /** Names derived by {@link lootBlueprint} for a given prefix. */
57
+ export interface LootNames {
58
+ rollType: string;
59
+ claimFn: string;
60
+ /** Snake-cased function-name prefix (empty without a `typePrefix`). */
61
+ fnPrefix: string;
62
+ }
63
+ /** Compute the type/function names a loot blueprint (and its runtime helper) uses. */
64
+ export declare function lootNames(typePrefix?: string): LootNames;
65
+ /** The roll function name for one table. */
66
+ export declare function lootRollFn(tableId: string, typePrefix?: string): string;
67
+ /**
68
+ * Blueprint for **loot tables & drops**: weighted tables are unrolled into
69
+ * pure expressions at blueprint-build time (the expression language is
70
+ * loop-free), rolled server-side into durable `LootRoll` containers, and
71
+ * claimed atomically into an item stack. Covers chest rolls, mob drops, and
72
+ * gacha pulls.
73
+ *
74
+ * Flow: create a `LootRoll` (member) for a table → `roll_<table>` (trusted:
75
+ * server scope by default, or an event automation via `drops`) stores a
76
+ * single `rand()` seed and resolves item + quantity from it in one
77
+ * transaction → the owner claims with `claim_roll`, which marks the roll
78
+ * claimed AND grants the stack in the same invoke (no double-claim, no
79
+ * client-chosen rewards).
80
+ *
81
+ * Runtime counterpart: `client.kit(appId).loot`.
82
+ */
83
+ export declare function lootBlueprint(options: LootBlueprintOptions): KitBlueprint;
84
+ //# sourceMappingURL=loot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loot.d.ts","sourceRoot":"","sources":["../../../src/kit/blueprints/loot.ts"],"names":[],"mappings":"AAIA,OAAO,EAQL,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACzB,MAAM,WAAW,CAAC;AAEnB,0CAA0C;AAC1C,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,iFAAiF;AACjF,MAAM,WAAW,aAAa;IAC5B,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AAED,iFAAiF;AACjF,MAAM,WAAW,YAAY;IAC3B,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,OAAO,EAAE,kBAAkB,GAAG,kBAAkB,GAAG,mBAAmB,CAAC;IACvE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sCAAsC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,yCAAyC;AACzC,MAAM,WAAW,oBAAoB;IACnC,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4EAA4E;IAC5E,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC;;;;OAIG;IACH,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;IACvB,yEAAyE;IACzE,WAAW,CAAC,EAAE,cAAc,CAAC;CAC9B;AAED,iEAAiE;AACjE,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,sFAAsF;AACtF,wBAAgB,SAAS,CAAC,UAAU,SAAK,GAAG,SAAS,CAOpD;AAED,4CAA4C;AAC5C,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,SAAK,GAAG,MAAM,CAGnE;AAwCD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,YAAY,CAuLzE"}
@@ -0,0 +1,228 @@
1
+ import { kitPolicyJson, ownerEqualsCaller, ownerMirrorProperty, toSnakeCase, trustedAuthorityFields, } from './core.js';
2
+ /** Compute the type/function names a loot blueprint (and its runtime helper) uses. */
3
+ export function lootNames(typePrefix = '') {
4
+ const fnPrefix = typePrefix ? `${toSnakeCase(typePrefix)}_` : '';
5
+ return {
6
+ rollType: `${typePrefix}LootRoll`,
7
+ claimFn: `${fnPrefix}claim_roll`,
8
+ fnPrefix,
9
+ };
10
+ }
11
+ /** The roll function name for one table. */
12
+ export function lootRollFn(tableId, typePrefix = '') {
13
+ const fnPrefix = typePrefix ? `${toSnakeCase(typePrefix)}_` : '';
14
+ return `${fnPrefix}roll_${toSnakeCase(tableId)}`;
15
+ }
16
+ /** Format a probability threshold as an expression float literal. */
17
+ function floatLiteral(n) {
18
+ const s = String(n);
19
+ return s.includes('.') || s.includes('e') ? s : `${s}.0`;
20
+ }
21
+ /**
22
+ * Unroll a weighted table into a nested-if chain over the stored roll seed:
23
+ * `if(self.seed < t1, "a", if(self.seed < t2, "b", "c"))` with cumulative
24
+ * normalized thresholds. Expressions are loop-free, so the selection chain
25
+ * is generated at BUILD time — one `rand()` is stored first and every branch
26
+ * reads that same seed, keeping the distribution exact.
27
+ */
28
+ function unrollItemChain(entries) {
29
+ const total = entries.reduce((sum, e) => sum + e.weight, 0);
30
+ let expr = JSON.stringify(entries[entries.length - 1].itemId);
31
+ for (let i = entries.length - 2; i >= 0; i--) {
32
+ // Threshold below which entries[0..i] win; nested back-to-front.
33
+ const cumulative = entries.slice(0, i + 1).reduce((sum, e) => sum + e.weight, 0);
34
+ expr = `if(self.seed < ${floatLiteral(cumulative / total)}, ${JSON.stringify(entries[i].itemId)}, ${expr})`;
35
+ }
36
+ return expr;
37
+ }
38
+ /** Unroll the per-item quantity chain, keyed on the already-rolled item id. */
39
+ function unrollQtyChain(entries) {
40
+ const qtyExpr = (e) => {
41
+ const min = e.minQty ?? 1;
42
+ const max = e.maxQty ?? min;
43
+ return max > min ? `rand_int(${min}, ${max})` : String(min);
44
+ };
45
+ let expr = qtyExpr(entries[entries.length - 1]);
46
+ for (let i = entries.length - 2; i >= 0; i--) {
47
+ expr = `if(self.rolled_item_id == ${JSON.stringify(entries[i].itemId)}, ${qtyExpr(entries[i])}, ${expr})`;
48
+ }
49
+ return expr;
50
+ }
51
+ /**
52
+ * Blueprint for **loot tables & drops**: weighted tables are unrolled into
53
+ * pure expressions at blueprint-build time (the expression language is
54
+ * loop-free), rolled server-side into durable `LootRoll` containers, and
55
+ * claimed atomically into an item stack. Covers chest rolls, mob drops, and
56
+ * gacha pulls.
57
+ *
58
+ * Flow: create a `LootRoll` (member) for a table → `roll_<table>` (trusted:
59
+ * server scope by default, or an event automation via `drops`) stores a
60
+ * single `rand()` seed and resolves item + quantity from it in one
61
+ * transaction → the owner claims with `claim_roll`, which marks the roll
62
+ * claimed AND grants the stack in the same invoke (no double-claim, no
63
+ * client-chosen rewards).
64
+ *
65
+ * Runtime counterpart: `client.kit(appId).loot`.
66
+ */
67
+ export function lootBlueprint(options) {
68
+ const { typePrefix = '', tables, rollAuthority = 'server', drops = [], ownerIdKind: kind = 'int', } = options;
69
+ if (!tables.length) {
70
+ throw new Error('lootBlueprint requires at least one table');
71
+ }
72
+ for (const table of tables) {
73
+ if (table.entries.length < 1 || table.entries.length > 16) {
74
+ throw new Error(`lootBlueprint table '${table.tableId}' must have 1-16 entries (parser-limit-aware unrolling)`);
75
+ }
76
+ if (table.entries.some((e) => !(e.weight > 0))) {
77
+ throw new Error(`lootBlueprint table '${table.tableId}' entries must have weight > 0`);
78
+ }
79
+ }
80
+ const tableIds = new Set(tables.map((t) => t.tableId));
81
+ for (const drop of drops) {
82
+ if (!tableIds.has(drop.tableId)) {
83
+ throw new Error(`lootBlueprint drop '${drop.name}' references unknown table '${drop.tableId}'`);
84
+ }
85
+ }
86
+ const names = lootNames(typePrefix);
87
+ const droppedTables = new Set(drops.map((d) => d.tableId));
88
+ const propertyDefinitions = [
89
+ ownerMirrorProperty(names.rollType, kind),
90
+ {
91
+ containerTypeName: names.rollType,
92
+ key: 'table_id',
93
+ valueType: 'string',
94
+ defaultValueJson: '""',
95
+ description: 'The loot table this roll draws from.',
96
+ },
97
+ {
98
+ containerTypeName: names.rollType,
99
+ key: 'seed',
100
+ valueType: 'float',
101
+ defaultValueJson: '0',
102
+ description: 'The stored rand() seed the weighted selection reads (audit-friendly).',
103
+ },
104
+ {
105
+ containerTypeName: names.rollType,
106
+ key: 'rolled_item_id',
107
+ valueType: 'string',
108
+ defaultValueJson: '""',
109
+ description: 'The rolled item (empty until rolled; a roll cannot re-roll).',
110
+ },
111
+ {
112
+ containerTypeName: names.rollType,
113
+ key: 'rolled_qty',
114
+ valueType: 'int',
115
+ defaultValueJson: '0',
116
+ description: 'The rolled quantity.',
117
+ },
118
+ {
119
+ containerTypeName: names.rollType,
120
+ key: 'claimed',
121
+ valueType: 'bool',
122
+ defaultValueJson: 'false',
123
+ description: 'True once claimed into a stack (single-claim guard).',
124
+ },
125
+ ];
126
+ const functions = tables.map((table) => {
127
+ const auth = trustedAuthorityFields(rollAuthority, `self.table_id == ${JSON.stringify(table.tableId)} && self.rolled_item_id == ""`);
128
+ return {
129
+ name: lootRollFn(table.tableId, typePrefix),
130
+ containerTypeName: names.rollType,
131
+ returnType: 'string',
132
+ mutations: [
133
+ { target: 'self', property: 'seed', expression: 'rand()' },
134
+ {
135
+ target: 'self',
136
+ property: 'rolled_item_id',
137
+ expression: unrollItemChain(table.entries),
138
+ },
139
+ {
140
+ target: 'self',
141
+ property: 'rolled_qty',
142
+ expression: unrollQtyChain(table.entries),
143
+ },
144
+ ],
145
+ returnExpression: 'self.rolled_item_id',
146
+ ...auth,
147
+ ...(droppedTables.has(table.tableId) ? { autonomousInvocable: true } : {}),
148
+ description: `Roll the '${table.tableId}' loot table: stores one rand() seed, then resolves item and quantity from it in one transaction (unrolled weighted selection).`,
149
+ };
150
+ });
151
+ functions.push({
152
+ name: names.claimFn,
153
+ containerTypeName: names.rollType,
154
+ returnType: 'int',
155
+ parameters: [
156
+ {
157
+ name: 'to_stack_id',
158
+ valueType: 'container_ref',
159
+ required: true,
160
+ description: "A caller-owned stack of the rolled item that receives the quantity.",
161
+ },
162
+ ],
163
+ mutations: [
164
+ { target: 'self', property: 'claimed', expression: 'true' },
165
+ {
166
+ target: 'ref($to_stack_id)',
167
+ property: 'quantity',
168
+ expression: 'ref($to_stack_id).quantity + self.rolled_qty',
169
+ },
170
+ ],
171
+ returnExpression: 'self.rolled_qty',
172
+ invokePolicyJson: kitPolicyJson({
173
+ type: 'condition',
174
+ expression: [
175
+ ownerEqualsCaller('self.owner_user_id', kind),
176
+ 'not(self.claimed)',
177
+ 'self.rolled_item_id != ""',
178
+ 'ref($to_stack_id).item_id == self.rolled_item_id',
179
+ ownerEqualsCaller('ref($to_stack_id).owner_user_id', kind),
180
+ ].join(' && '),
181
+ }),
182
+ description: 'Claim a rolled loot into a stack: the claimed flag and the grant commit atomically, so a roll can never be claimed twice.',
183
+ });
184
+ const automations = [];
185
+ const automationTriggers = [];
186
+ for (const drop of drops) {
187
+ automations.push({
188
+ name: drop.name,
189
+ functionName: lootRollFn(drop.tableId, typePrefix),
190
+ targetMode: 'type',
191
+ targetTypeName: names.rollType,
192
+ triggerType: 'event',
193
+ maxTargets: drop.maxTargets ?? 1,
194
+ selectorJson: JSON.stringify({
195
+ selfWhere: [
196
+ { key: 'table_id', op: '==', value: drop.tableId },
197
+ { key: 'rolled_item_id', op: '==', value: '' },
198
+ ],
199
+ pick: 'random',
200
+ }),
201
+ description: `Event-triggered '${drop.tableId}' drop: rolls a pooled unrolled LootRoll when the event fires.`,
202
+ });
203
+ automationTriggers.push({
204
+ automationName: drop.name,
205
+ onEvent: drop.onEvent,
206
+ ...(drop.functionName !== undefined ? { functionName: drop.functionName } : {}),
207
+ ...(drop.containerTypeName !== undefined
208
+ ? { containerTypeName: drop.containerTypeName }
209
+ : {}),
210
+ ...(drop.propertyKey !== undefined ? { propertyKey: drop.propertyKey } : {}),
211
+ ...(drop.debounceMs !== undefined ? { debounceMs: drop.debounceMs } : {}),
212
+ });
213
+ }
214
+ return {
215
+ name: names.rollType,
216
+ containerTypes: [
217
+ {
218
+ typeName: names.rollType,
219
+ displayName: names.rollType,
220
+ instantiableBy: 'member',
221
+ description: 'One durable loot roll: seeded, resolved, and claimed entirely server-side.',
222
+ },
223
+ ],
224
+ propertyDefinitions,
225
+ functions,
226
+ ...(automations.length ? { automations, automationTriggers } : {}),
227
+ };
228
+ }
@@ -0,0 +1,59 @@
1
+ import { type KitBlueprint, type KitOwnerIdKind, type KitTrustedAuthority } from './core.js';
2
+ /** Options for {@link matchesBlueprint}. */
3
+ export interface MatchesBlueprintOptions {
4
+ /** Prefix for the type/function names. Defaults to none. */
5
+ typePrefix?: string;
6
+ /**
7
+ * Who may submit points to a `Score` row. Defaults to `'host'` (the
8
+ * elected host referees); use `'server'` or `'automation'` for
9
+ * server-refereed modes. Never plain players.
10
+ */
11
+ scoreAuthority?: KitTrustedAuthority;
12
+ /**
13
+ * When set, adds a `turn_tick` interval automation that increments each
14
+ * active match's `tick_count` — the wall-clock-free timer primitive: store
15
+ * the tick at turn start and treat `tick_count - turn_started_tick >= N`
16
+ * as a timeout (there is no `now()` in expressions; counters replace
17
+ * clocks).
18
+ */
19
+ turnTick?: {
20
+ intervalMs: number;
21
+ };
22
+ /** Owner-mirror typing (see the kit convention). Defaults to `'int'`. */
23
+ ownerIdKind?: KitOwnerIdKind;
24
+ }
25
+ /** Names derived by {@link matchesBlueprint} for a given prefix. */
26
+ export interface MatchesNames {
27
+ metaType: string;
28
+ scoreType: string;
29
+ startFn: string;
30
+ advanceRoundFn: string;
31
+ scoreFn: string;
32
+ endFn: string;
33
+ turnTickFn: string;
34
+ turnTickAutomation: string;
35
+ }
36
+ /** Compute the type/function names a matches blueprint (and its runtime helper) uses. */
37
+ export declare function matchesNames(typePrefix?: string): MatchesNames;
38
+ /**
39
+ * Blueprint for **matches** (lobbies, rounds, turns, scoring) — the
40
+ * session-layer wrapper every session game needs. Sessions ARE the match
41
+ * primitive (participants + current turn); this adds a session-scoped
42
+ * `MatchMeta` (lobby state, round, winner, notification channel) and
43
+ * per-player `Score` rows.
44
+ *
45
+ * Lifecycle functions (`start_match` / `advance_round` / `end_match`) are
46
+ * creator-or-host gated and declare a **channel notification** — Buddy pings
47
+ * every channel member post-commit with `"match_changed"`, and clients
48
+ * re-pull the meta (the notify-to-pull pattern; `kit.matches.onMatchChanged`
49
+ * wraps the subscription). Turn order itself uses the platform's
50
+ * `gameModelSetSessionTurn` (the runtime helper calls it — turn authority is
51
+ * enforced by the service, not an expression).
52
+ *
53
+ * End-of-match hooks: attach an event automation to `end_match`
54
+ * (`function_invoked`) to submit leaderboard scores or adjust ratings.
55
+ *
56
+ * Runtime counterpart: `client.kit(appId).matches`.
57
+ */
58
+ export declare function matchesBlueprint(options?: MatchesBlueprintOptions): KitBlueprint;
59
+ //# sourceMappingURL=matches.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matches.d.ts","sourceRoot":"","sources":["../../../src/kit/blueprints/matches.ts"],"names":[],"mappings":"AAKA,OAAO,EAML,KAAK,YAAY,EAEjB,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACzB,MAAM,WAAW,CAAC;AAEnB,4CAA4C;AAC5C,MAAM,WAAW,uBAAuB;IACtC,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,cAAc,CAAC,EAAE,mBAAmB,CAAC;IACrC;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IAClC,yEAAyE;IACzE,WAAW,CAAC,EAAE,cAAc,CAAC;CAC9B;AAED,oEAAoE;AACpE,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,yFAAyF;AACzF,wBAAgB,YAAY,CAAC,UAAU,SAAK,GAAG,YAAY,CAY1D;AAaD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,uBAA4B,GAAG,YAAY,CA0NpF"}