@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,246 @@
1
+ import { kitPolicyJson, ownerMirrorProperty, toSnakeCase, trustedAuthorityFields, } from './core.js';
2
+ /** Compute the type/function names a matches blueprint (and its runtime helper) uses. */
3
+ export function matchesNames(typePrefix = '') {
4
+ const fnPrefix = typePrefix ? `${toSnakeCase(typePrefix)}_` : '';
5
+ return {
6
+ metaType: `${typePrefix}MatchMeta`,
7
+ scoreType: `${typePrefix}Score`,
8
+ startFn: `${fnPrefix}start_match`,
9
+ advanceRoundFn: `${fnPrefix}advance_round`,
10
+ scoreFn: `${fnPrefix}score_points`,
11
+ endFn: `${fnPrefix}end_match`,
12
+ turnTickFn: `${fnPrefix}turn_tick`,
13
+ turnTickAutomation: `${fnPrefix.replace(/_/g, '-')}match-turn-tick`,
14
+ };
15
+ }
16
+ /** The channel ping every lifecycle function emits post-commit (notify-to-pull). */
17
+ function matchChangedNotification() {
18
+ return {
19
+ kind: 'channel',
20
+ args: [
21
+ { name: 'channel_id', expression: 'self.channel_id' },
22
+ { name: 'payload', expression: '"match_changed"' },
23
+ ],
24
+ };
25
+ }
26
+ /**
27
+ * Blueprint for **matches** (lobbies, rounds, turns, scoring) — the
28
+ * session-layer wrapper every session game needs. Sessions ARE the match
29
+ * primitive (participants + current turn); this adds a session-scoped
30
+ * `MatchMeta` (lobby state, round, winner, notification channel) and
31
+ * per-player `Score` rows.
32
+ *
33
+ * Lifecycle functions (`start_match` / `advance_round` / `end_match`) are
34
+ * creator-or-host gated and declare a **channel notification** — Buddy pings
35
+ * every channel member post-commit with `"match_changed"`, and clients
36
+ * re-pull the meta (the notify-to-pull pattern; `kit.matches.onMatchChanged`
37
+ * wraps the subscription). Turn order itself uses the platform's
38
+ * `gameModelSetSessionTurn` (the runtime helper calls it — turn authority is
39
+ * enforced by the service, not an expression).
40
+ *
41
+ * End-of-match hooks: attach an event automation to `end_match`
42
+ * (`function_invoked`) to submit leaderboard scores or adjust ratings.
43
+ *
44
+ * Runtime counterpart: `client.kit(appId).matches`.
45
+ */
46
+ export function matchesBlueprint(options = {}) {
47
+ const { typePrefix = '', scoreAuthority = 'host', turnTick, ownerIdKind: kind = 'int', } = options;
48
+ const names = matchesNames(typePrefix);
49
+ const creatorOrHost = (stateCondition) => {
50
+ const rules = [
51
+ {
52
+ type: 'or',
53
+ rules: [
54
+ { type: 'is_host' },
55
+ { type: 'condition', expression: 'self.creator_user_id == $caller_user_id' },
56
+ ],
57
+ },
58
+ { type: 'condition', expression: stateCondition },
59
+ ];
60
+ return kitPolicyJson({ type: 'and', rules });
61
+ };
62
+ const propertyDefinitions = [
63
+ {
64
+ containerTypeName: names.metaType,
65
+ key: 'creator_user_id',
66
+ valueType: 'int',
67
+ defaultValueJson: '0',
68
+ description: 'The user who created the match (may start/advance/end it).',
69
+ },
70
+ {
71
+ containerTypeName: names.metaType,
72
+ key: 'mode',
73
+ valueType: 'string',
74
+ defaultValueJson: '""',
75
+ description: "App-defined mode label (e.g. 'ranked', 'ffa').",
76
+ },
77
+ {
78
+ containerTypeName: names.metaType,
79
+ key: 'state',
80
+ valueType: 'string',
81
+ defaultValueJson: '"lobby"',
82
+ description: "Match lifecycle: 'lobby' | 'active' | 'finished'.",
83
+ },
84
+ {
85
+ containerTypeName: names.metaType,
86
+ key: 'round',
87
+ valueType: 'int',
88
+ defaultValueJson: '0',
89
+ description: 'Current round (0 in the lobby).',
90
+ },
91
+ {
92
+ containerTypeName: names.metaType,
93
+ key: 'max_players',
94
+ valueType: 'int',
95
+ defaultValueJson: '0',
96
+ description: 'Advertised player cap (0 = unlimited).',
97
+ },
98
+ {
99
+ containerTypeName: names.metaType,
100
+ key: 'winner_user_id',
101
+ valueType: 'int',
102
+ defaultValueJson: '0',
103
+ description: 'Set by end_match (0 while unresolved).',
104
+ },
105
+ {
106
+ containerTypeName: names.metaType,
107
+ key: 'channel_id',
108
+ valueType: 'int',
109
+ defaultValueJson: '0',
110
+ description: 'The per-match channel lifecycle notifications ping (notify-to-pull).',
111
+ },
112
+ ...(turnTick
113
+ ? [
114
+ {
115
+ containerTypeName: names.metaType,
116
+ key: 'tick_count',
117
+ valueType: 'int',
118
+ defaultValueJson: '0',
119
+ description: 'Monotonic counter bumped by the turn-tick automation (the wall-clock-free timer).',
120
+ },
121
+ ]
122
+ : []),
123
+ ownerMirrorProperty(names.scoreType, kind),
124
+ {
125
+ containerTypeName: names.scoreType,
126
+ key: 'points',
127
+ valueType: 'int',
128
+ defaultValueJson: '0',
129
+ description: "One player's score in the match.",
130
+ },
131
+ ];
132
+ const functions = [
133
+ {
134
+ name: names.startFn,
135
+ containerTypeName: names.metaType,
136
+ returnType: 'string',
137
+ mutations: [
138
+ { target: 'self', property: 'state', expression: '"active"' },
139
+ { target: 'self', property: 'round', expression: '1' },
140
+ ],
141
+ returnExpression: 'self.state',
142
+ invokePolicyJson: creatorOrHost('self.state == "lobby"'),
143
+ notifications: [matchChangedNotification()],
144
+ description: 'Start the match (creator or host): lobby → active, round 1; pings the match channel post-commit.',
145
+ },
146
+ {
147
+ name: names.advanceRoundFn,
148
+ containerTypeName: names.metaType,
149
+ returnType: 'int',
150
+ mutations: [{ target: 'self', property: 'round', expression: 'self.round + 1' }],
151
+ returnExpression: 'self.round',
152
+ invokePolicyJson: creatorOrHost('self.state == "active"'),
153
+ notifications: [matchChangedNotification()],
154
+ description: 'Advance to the next round (creator or host); pings the match channel post-commit.',
155
+ },
156
+ {
157
+ name: names.endFn,
158
+ containerTypeName: names.metaType,
159
+ returnType: 'string',
160
+ parameters: [
161
+ {
162
+ name: 'winner_user_id',
163
+ valueType: 'int',
164
+ required: true,
165
+ description: 'The winning user (0 for a draw).',
166
+ },
167
+ ],
168
+ mutations: [
169
+ { target: 'self', property: 'state', expression: '"finished"' },
170
+ { target: 'self', property: 'winner_user_id', expression: '$winner_user_id' },
171
+ ],
172
+ returnExpression: 'self.state',
173
+ invokePolicyJson: creatorOrHost('self.state == "active"'),
174
+ notifications: [matchChangedNotification()],
175
+ description: 'Finish the match and record the winner (creator or host); attach an event automation to this function for rating/leaderboard hooks.',
176
+ },
177
+ {
178
+ name: names.scoreFn,
179
+ containerTypeName: names.scoreType,
180
+ returnType: 'int',
181
+ parameters: [
182
+ {
183
+ name: 'points',
184
+ valueType: 'int',
185
+ required: true,
186
+ description: 'Signed points to add.',
187
+ },
188
+ ],
189
+ mutations: [
190
+ { target: 'self', property: 'points', expression: 'self.points + $points' },
191
+ ],
192
+ returnExpression: 'self.points',
193
+ ...trustedAuthorityFields(scoreAuthority),
194
+ description: "Add points to a player's Score row — trusted (host-refereed by default).",
195
+ },
196
+ ];
197
+ const automations = [];
198
+ if (turnTick) {
199
+ functions.push({
200
+ name: names.turnTickFn,
201
+ containerTypeName: names.metaType,
202
+ returnType: 'int',
203
+ mutations: [
204
+ { target: 'self', property: 'tick_count', expression: 'self.tick_count + 1' },
205
+ ],
206
+ returnExpression: 'self.tick_count',
207
+ invokePolicyJson: kitPolicyJson({ type: 'is_automation' }),
208
+ autonomousInvocable: true,
209
+ description: 'Server-driven timer tick for active matches (counters instead of wall clocks — expressions have no now()).',
210
+ });
211
+ automations.push({
212
+ name: names.turnTickAutomation,
213
+ functionName: names.turnTickFn,
214
+ targetMode: 'type',
215
+ targetTypeName: names.metaType,
216
+ triggerType: 'schedule',
217
+ scheduleKind: 'interval',
218
+ intervalMs: turnTick.intervalMs,
219
+ maxTargets: 64,
220
+ selectorJson: JSON.stringify({
221
+ selfWhere: [{ key: 'state', op: '==', value: 'active' }],
222
+ }),
223
+ description: 'Bumps tick_count on active matches (turn-timeout timer source).',
224
+ });
225
+ }
226
+ return {
227
+ name: names.metaType,
228
+ containerTypes: [
229
+ {
230
+ typeName: names.metaType,
231
+ displayName: names.metaType,
232
+ instantiableBy: 'member',
233
+ description: 'Session-scoped match record: lobby state, round, winner, notification channel.',
234
+ },
235
+ {
236
+ typeName: names.scoreType,
237
+ displayName: names.scoreType,
238
+ instantiableBy: 'member',
239
+ description: "One player's session-scoped score row.",
240
+ },
241
+ ],
242
+ propertyDefinitions,
243
+ functions,
244
+ ...(automations.length ? { automations } : {}),
245
+ };
246
+ }
@@ -0,0 +1,71 @@
1
+ import type { FunctionMutationInput, FunctionParamInput, Scalars, SeedPropertyDefInput } from '../../generated/graphql.js';
2
+ import { type KitBlueprint, type KitSelectorSpec } from './core.js';
3
+ /** A trigger for an NPC behavior: a schedule (interval or cron) or a model event. */
4
+ export type NpcBehaviorTrigger = {
5
+ intervalMs: number;
6
+ } | {
7
+ cronExpr: string;
8
+ } | {
9
+ onEvent: 'function_invoked' | 'property_changed' | 'container_created';
10
+ functionName?: string;
11
+ containerTypeName?: string;
12
+ propertyKey?: string;
13
+ debounceMs?: number;
14
+ };
15
+ /** One server-driven NPC behavior: a model function plus the automation that drives it. */
16
+ export interface NpcBehaviorSpec {
17
+ /**
18
+ * Automation name (unique per app), e.g. `'npc-wander'`. Also derives the
19
+ * default function name (`npc_wander`).
20
+ */
21
+ name: string;
22
+ /** Entry-point function name. Defaults to the snake-cased behavior name. */
23
+ functionName?: string;
24
+ /** The property writes the behavior performs each tick. */
25
+ mutations: FunctionMutationInput[];
26
+ /** Typed parameters (bind them from a selector or static `paramsJson`). */
27
+ parameters?: FunctionParamInput[];
28
+ /** What makes the behavior run. */
29
+ trigger: NpcBehaviorTrigger;
30
+ /**
31
+ * Selector choosing/filtering targets and binding params (see the Game API
32
+ * "Autonomous Processes → Selectors" guide), including grid-permission
33
+ * predicates ({@link KitSelectorSpec}). JSON-encoded at deploy.
34
+ */
35
+ selector?: KitSelectorSpec;
36
+ /**
37
+ * Convenience: only NPCs whose `role` property equals this act (adds a
38
+ * `selfWhere` filter when no explicit `selector` is given).
39
+ */
40
+ role?: string;
41
+ /** Fan-out cap per run. Defaults to 8. */
42
+ maxTargets?: number;
43
+ /** Static params merged into every call. */
44
+ params?: Record<string, unknown>;
45
+ /** Identity the automation acts as; omit for a trusted server caller. */
46
+ runAsUserId?: Scalars['BigInt']['input'];
47
+ }
48
+ /** Options for {@link npcBlueprint}. */
49
+ export interface NpcBlueprintOptions {
50
+ /** NPC container type name. Defaults to `'Npc'`. */
51
+ typeName?: string;
52
+ /**
53
+ * Extra property definitions beyond the defaults (`role`, `x`, `y`, `z`,
54
+ * `behavior_state`, `health`). `containerTypeName` is filled in.
55
+ */
56
+ extraProperties?: Omit<SeedPropertyDefInput, 'containerTypeName'>[];
57
+ /** The server-driven behaviors this NPC archetype has. */
58
+ behaviors: NpcBehaviorSpec[];
59
+ }
60
+ /** Compute the function/automation names an NPC behavior deploys under. */
61
+ export declare function npcBehaviorFunctionName(behavior: NpcBehaviorSpec): string;
62
+ /**
63
+ * Blueprint for an **NPC archetype**: an admin-instantiable container type
64
+ * holding the NPC's durable state, one `autonomousInvocable` model function
65
+ * per behavior (gated `is_automation` so players cannot puppet them), and the
66
+ * automations + event triggers that drive those behaviors on the server.
67
+ *
68
+ * Runtime counterpart: `client.kit(appId).npcs`.
69
+ */
70
+ export declare function npcBlueprint(options: NpcBlueprintOptions): KitBlueprint;
71
+ //# sourceMappingURL=npcs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"npcs.d.ts","sourceRoot":"","sources":["../../../src/kit/blueprints/npcs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,kBAAkB,EAClB,OAAO,EAEP,oBAAoB,EACrB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAKL,KAAK,YAAY,EACjB,KAAK,eAAe,EACrB,MAAM,WAAW,CAAC;AAEnB,qFAAqF;AACrF,MAAM,MAAM,kBAAkB,GAC1B;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,GACtB;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GACpB;IACE,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;CACrB,CAAC;AAEN,2FAA2F;AAC3F,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,4EAA4E;IAC5E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,SAAS,EAAE,qBAAqB,EAAE,CAAC;IACnC,2EAA2E;IAC3E,UAAU,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAClC,mCAAmC;IACnC,OAAO,EAAE,kBAAkB,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,0CAA0C;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,yEAAyE;IACzE,WAAW,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC;CAC1C;AAED,wCAAwC;AACxC,MAAM,WAAW,mBAAmB;IAClC,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,eAAe,CAAC,EAAE,IAAI,CAAC,oBAAoB,EAAE,mBAAmB,CAAC,EAAE,CAAC;IACpE,0DAA0D;IAC1D,SAAS,EAAE,eAAe,EAAE,CAAC;CAC9B;AAED,2EAA2E;AAC3E,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,CAEzE;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,YAAY,CAmHvE"}
@@ -0,0 +1,122 @@
1
+ import { kitPolicyJson, toSnakeCase, } from './core.js';
2
+ /** Compute the function/automation names an NPC behavior deploys under. */
3
+ export function npcBehaviorFunctionName(behavior) {
4
+ return behavior.functionName ?? toSnakeCase(behavior.name);
5
+ }
6
+ /**
7
+ * Blueprint for an **NPC archetype**: an admin-instantiable container type
8
+ * holding the NPC's durable state, one `autonomousInvocable` model function
9
+ * per behavior (gated `is_automation` so players cannot puppet them), and the
10
+ * automations + event triggers that drive those behaviors on the server.
11
+ *
12
+ * Runtime counterpart: `client.kit(appId).npcs`.
13
+ */
14
+ export function npcBlueprint(options) {
15
+ const typeName = options.typeName ?? 'Npc';
16
+ if (options.behaviors.length === 0) {
17
+ throw new Error('npcBlueprint requires at least one behavior');
18
+ }
19
+ const propertyDefinitions = [
20
+ {
21
+ containerTypeName: typeName,
22
+ key: 'role',
23
+ valueType: 'string',
24
+ defaultValueJson: '""',
25
+ },
26
+ { containerTypeName: typeName, key: 'x', valueType: 'float', defaultValueJson: '0' },
27
+ { containerTypeName: typeName, key: 'y', valueType: 'float', defaultValueJson: '0' },
28
+ { containerTypeName: typeName, key: 'z', valueType: 'float', defaultValueJson: '0' },
29
+ {
30
+ containerTypeName: typeName,
31
+ key: 'behavior_state',
32
+ valueType: 'string',
33
+ defaultValueJson: '"idle"',
34
+ },
35
+ {
36
+ containerTypeName: typeName,
37
+ key: 'health',
38
+ valueType: 'int',
39
+ defaultValueJson: '100',
40
+ },
41
+ ...(options.extraProperties ?? []).map((p) => ({
42
+ ...p,
43
+ containerTypeName: typeName,
44
+ })),
45
+ ];
46
+ const functions = [];
47
+ const automations = [];
48
+ const automationTriggers = [];
49
+ for (const behavior of options.behaviors) {
50
+ const functionName = npcBehaviorFunctionName(behavior);
51
+ functions.push({
52
+ name: functionName,
53
+ containerTypeName: typeName,
54
+ parameters: behavior.parameters,
55
+ mutations: behavior.mutations,
56
+ invokePolicyJson: kitPolicyJson({ type: 'is_automation' }),
57
+ autonomousInvocable: true,
58
+ description: `Server-driven NPC behavior for the '${behavior.name}' automation.`,
59
+ });
60
+ const selector = behavior.selector ??
61
+ (behavior.role !== undefined
62
+ ? { selfWhere: [{ key: 'role', op: '==', value: behavior.role }] }
63
+ : undefined);
64
+ const automation = {
65
+ name: behavior.name,
66
+ functionName,
67
+ targetMode: 'type',
68
+ targetTypeName: typeName,
69
+ maxTargets: behavior.maxTargets ?? 8,
70
+ ...(selector ? { selectorJson: JSON.stringify(selector) } : {}),
71
+ ...(behavior.params ? { paramsJson: JSON.stringify(behavior.params) } : {}),
72
+ ...(behavior.runAsUserId !== undefined
73
+ ? { runAsUserId: behavior.runAsUserId }
74
+ : {}),
75
+ };
76
+ if ('intervalMs' in behavior.trigger) {
77
+ automation.triggerType = 'schedule';
78
+ automation.scheduleKind = 'interval';
79
+ automation.intervalMs = behavior.trigger.intervalMs;
80
+ }
81
+ else if ('cronExpr' in behavior.trigger) {
82
+ automation.triggerType = 'schedule';
83
+ automation.scheduleKind = 'cron';
84
+ automation.cronExpr = behavior.trigger.cronExpr;
85
+ }
86
+ else {
87
+ automation.triggerType = 'event';
88
+ automationTriggers.push({
89
+ automationName: behavior.name,
90
+ onEvent: behavior.trigger.onEvent,
91
+ ...(behavior.trigger.functionName !== undefined
92
+ ? { functionName: behavior.trigger.functionName }
93
+ : {}),
94
+ ...(behavior.trigger.containerTypeName !== undefined
95
+ ? { containerTypeName: behavior.trigger.containerTypeName }
96
+ : {}),
97
+ ...(behavior.trigger.propertyKey !== undefined
98
+ ? { propertyKey: behavior.trigger.propertyKey }
99
+ : {}),
100
+ ...(behavior.trigger.debounceMs !== undefined
101
+ ? { debounceMs: behavior.trigger.debounceMs }
102
+ : {}),
103
+ });
104
+ }
105
+ automations.push(automation);
106
+ }
107
+ return {
108
+ name: typeName,
109
+ containerTypes: [
110
+ {
111
+ typeName,
112
+ displayName: typeName,
113
+ instantiableBy: 'admin',
114
+ description: 'A server-driven non-player character.',
115
+ },
116
+ ],
117
+ propertyDefinitions,
118
+ functions,
119
+ automations,
120
+ automationTriggers,
121
+ };
122
+ }
@@ -0,0 +1,54 @@
1
+ import { type KitBlueprint, type KitInvokePolicy } from './core.js';
2
+ /** Options for {@link plotBlueprint}. */
3
+ export interface PlotBlueprintOptions {
4
+ /** Plot container type name. Defaults to `'Plot'`. */
5
+ typeName?: string;
6
+ /**
7
+ * Runtime permission keys buying/renting grants on the plot's grid.
8
+ * Defaults to `['access', 'update_voxel_data']`.
9
+ */
10
+ permissionKeys?: string[];
11
+ /** Currency property on the wallet container. Defaults to `'gold'`. */
12
+ currencyProperty?: string;
13
+ /**
14
+ * Property on the wallet container mirroring its owner's user id (the
15
+ * standard kit convention, since expressions can't read container
16
+ * ownership). Defaults to `'owner_user_id'`.
17
+ */
18
+ walletOwnerProperty?: string;
19
+ /**
20
+ * Also generate `rent_plot`: a TTL grant priced by the plot's `rent_price`
21
+ * property, expiring after `rent_ttl_seconds`. Defaults to false.
22
+ */
23
+ rentable?: boolean;
24
+ /**
25
+ * Extra policy rule AND'ed into `buy_plot` — e.g.
26
+ * `featureGate('land_owner')` to sell land only to a paid tier.
27
+ */
28
+ buyPolicyExtra?: KitInvokePolicy;
29
+ /** Extra policy rule AND'ed into `rent_plot`. */
30
+ rentPolicyExtra?: KitInvokePolicy;
31
+ }
32
+ /** Names derived by {@link plotBlueprint} for a given plot type. */
33
+ export interface PlotNames {
34
+ plotType: string;
35
+ buyFn: string;
36
+ rentFn: string;
37
+ evictFn: string;
38
+ }
39
+ /** Compute the type/function names a plot blueprint (and its runtime helper) uses. */
40
+ export declare function plotNames(typeName?: string): PlotNames;
41
+ /**
42
+ * Blueprint for **sellable/rentable land**: a `Plot` container mapping a world
43
+ * [grid](../../domains/gameApps.js) to a price, whose `buy`/`rent` functions
44
+ * consume currency AND grant runtime grid permissions in one transaction (via
45
+ * permission effects) — the canonical read+write permission loop. Buying sets
46
+ * the plot's `owner_user_id`; renting grants with a TTL; `evict` revokes.
47
+ * The grants are enforced by the replication layer on movement/voxel writes
48
+ * immediately at commit. Requires game-api v0.13.11+ (effects) — pair with
49
+ * chunk-permission locks (v0.13.12+) for doors that honor the purchase.
50
+ *
51
+ * Runtime counterpart: `client.kit(appId).plots`.
52
+ */
53
+ export declare function plotBlueprint(options?: PlotBlueprintOptions): KitBlueprint;
54
+ //# sourceMappingURL=plots.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plots.d.ts","sourceRoot":"","sources":["../../../src/kit/blueprints/plots.ts"],"names":[],"mappings":"AAKA,OAAO,EAIL,KAAK,YAAY,EACjB,KAAK,eAAe,EACrB,MAAM,WAAW,CAAC;AAEnB,yCAAyC;AACzC,MAAM,WAAW,oBAAoB;IACnC,sDAAsD;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,uEAAuE;IACvE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,cAAc,CAAC,EAAE,eAAe,CAAC;IACjC,iDAAiD;IACjD,eAAe,CAAC,EAAE,eAAe,CAAC;CACnC;AAED,oEAAoE;AACpE,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,sFAAsF;AACtF,wBAAgB,SAAS,CAAC,QAAQ,SAAS,GAAG,SAAS,CAQtD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,YAAY,CAyI9E"}
@@ -0,0 +1,146 @@
1
+ import { andPolicies, kitPolicyJson, toSnakeCase, } from './core.js';
2
+ /** Compute the type/function names a plot blueprint (and its runtime helper) uses. */
3
+ export function plotNames(typeName = 'Plot') {
4
+ const snake = toSnakeCase(typeName);
5
+ return {
6
+ plotType: typeName,
7
+ buyFn: `buy_${snake}`,
8
+ rentFn: `rent_${snake}`,
9
+ evictFn: `evict_${snake}`,
10
+ };
11
+ }
12
+ /**
13
+ * Blueprint for **sellable/rentable land**: a `Plot` container mapping a world
14
+ * [grid](../../domains/gameApps.js) to a price, whose `buy`/`rent` functions
15
+ * consume currency AND grant runtime grid permissions in one transaction (via
16
+ * permission effects) — the canonical read+write permission loop. Buying sets
17
+ * the plot's `owner_user_id`; renting grants with a TTL; `evict` revokes.
18
+ * The grants are enforced by the replication layer on movement/voxel writes
19
+ * immediately at commit. Requires game-api v0.13.11+ (effects) — pair with
20
+ * chunk-permission locks (v0.13.12+) for doors that honor the purchase.
21
+ *
22
+ * Runtime counterpart: `client.kit(appId).plots`.
23
+ */
24
+ export function plotBlueprint(options = {}) {
25
+ const names = plotNames(options.typeName);
26
+ const keys = options.permissionKeys ?? ['access', 'update_voxel_data'];
27
+ const currency = options.currencyProperty ?? 'gold';
28
+ const walletOwner = options.walletOwnerProperty ?? 'owner_user_id';
29
+ const rentable = options.rentable ?? false;
30
+ const walletParam = [
31
+ { name: 'wallet_id', valueType: 'container_ref', required: true },
32
+ ];
33
+ const walletGuard = (priceExpr, extra) => kitPolicyJson(andPolicies({
34
+ type: 'condition',
35
+ expression: `ref($wallet_id).${walletOwner} == $caller_user_id && ref($wallet_id).${currency} >= ${priceExpr}`,
36
+ }, extra));
37
+ const propertyDefinitions = [
38
+ { containerTypeName: names.plotType, key: 'grid_id', valueType: 'int' },
39
+ { containerTypeName: names.plotType, key: 'price', valueType: 'int', defaultValueJson: '0' },
40
+ {
41
+ containerTypeName: names.plotType,
42
+ key: 'owner_user_id',
43
+ valueType: 'int',
44
+ defaultValueJson: '0',
45
+ },
46
+ ];
47
+ if (rentable) {
48
+ propertyDefinitions.push({
49
+ containerTypeName: names.plotType,
50
+ key: 'rent_price',
51
+ valueType: 'int',
52
+ defaultValueJson: '0',
53
+ }, {
54
+ containerTypeName: names.plotType,
55
+ key: 'rent_ttl_seconds',
56
+ valueType: 'int',
57
+ defaultValueJson: '86400',
58
+ });
59
+ }
60
+ const functions = [
61
+ {
62
+ name: names.buyFn,
63
+ containerTypeName: names.plotType,
64
+ returnType: 'int',
65
+ parameters: walletParam,
66
+ mutations: [
67
+ {
68
+ target: 'ref($wallet_id)',
69
+ property: currency,
70
+ expression: `ref($wallet_id).${currency} - self.price`,
71
+ },
72
+ { target: 'self', property: 'owner_user_id', expression: '$caller_user_id' },
73
+ ],
74
+ permissionEffects: [
75
+ {
76
+ action: 'grant',
77
+ permissionKeys: keys,
78
+ userExpression: '$caller_user_id',
79
+ gridIdExpression: 'self.grid_id',
80
+ },
81
+ ],
82
+ returnExpression: `ref($wallet_id).${currency}`,
83
+ invokePolicyJson: walletGuard('self.price', options.buyPolicyExtra),
84
+ description: 'Buy this plot: spend the price AND receive grid permissions atomically.',
85
+ },
86
+ {
87
+ name: names.evictFn,
88
+ containerTypeName: names.plotType,
89
+ parameters: [{ name: 'target_user_id', valueType: 'int', required: true }],
90
+ mutations: [],
91
+ permissionEffects: [
92
+ {
93
+ action: 'revoke',
94
+ permissionKeys: keys,
95
+ userExpression: '$target_user_id',
96
+ gridIdExpression: 'self.grid_id',
97
+ },
98
+ ],
99
+ invokePolicyJson: kitPolicyJson({
100
+ type: 'condition',
101
+ expression: 'self.owner_user_id == $caller_user_id',
102
+ }),
103
+ description: "Revoke a user's permissions on this plot (owner-gated; admins bypass).",
104
+ },
105
+ ];
106
+ if (rentable) {
107
+ functions.push({
108
+ name: names.rentFn,
109
+ containerTypeName: names.plotType,
110
+ returnType: 'int',
111
+ parameters: walletParam,
112
+ mutations: [
113
+ {
114
+ target: 'ref($wallet_id)',
115
+ property: currency,
116
+ expression: `ref($wallet_id).${currency} - self.rent_price`,
117
+ },
118
+ ],
119
+ permissionEffects: [
120
+ {
121
+ action: 'grant',
122
+ permissionKeys: keys,
123
+ userExpression: '$caller_user_id',
124
+ gridIdExpression: 'self.grid_id',
125
+ ttlSecondsExpression: 'self.rent_ttl_seconds',
126
+ },
127
+ ],
128
+ returnExpression: `ref($wallet_id).${currency}`,
129
+ invokePolicyJson: walletGuard('self.rent_price', options.rentPolicyExtra),
130
+ description: 'Rent this plot: spend the rent AND receive an expiring grid grant atomically.',
131
+ });
132
+ }
133
+ return {
134
+ name: names.plotType,
135
+ containerTypes: [
136
+ {
137
+ typeName: names.plotType,
138
+ displayName: names.plotType,
139
+ instantiableBy: 'admin',
140
+ description: 'A sellable/rentable plot of land mapped to a world grid.',
141
+ },
142
+ ],
143
+ propertyDefinitions,
144
+ functions,
145
+ };
146
+ }