@crowdedkingdoms/crowdyjs 8.0.0 → 8.1.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.
- package/README.md +92 -15
- package/dist/client.d.ts +8 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +5 -0
- package/dist/crowdy-client.d.ts +22 -0
- package/dist/crowdy-client.d.ts.map +1 -1
- package/dist/crowdy-client.js +21 -0
- package/dist/domains/gameModel.d.ts +54 -3
- package/dist/domains/gameModel.d.ts.map +1 -1
- package/dist/domains/gameModel.js +67 -4
- package/dist/domains/host.d.ts +14 -3
- package/dist/domains/host.d.ts.map +1 -1
- package/dist/domains/host.js +17 -3
- package/dist/domains/organizations.d.ts +3 -3
- package/dist/domains/organizations.js +3 -3
- package/dist/domains/quotas.d.ts +1 -1
- package/dist/domains/quotas.js +1 -1
- package/dist/domains/udp.d.ts +28 -17
- package/dist/domains/udp.d.ts.map +1 -1
- package/dist/domains/udp.js +28 -17
- package/dist/generated/graphql.d.ts +366 -6
- package/dist/generated/graphql.d.ts.map +1 -1
- package/dist/generated/graphql.js +13 -8
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/kit/blueprints.d.ts +259 -0
- package/dist/kit/blueprints.d.ts.map +1 -0
- package/dist/kit/blueprints.js +480 -0
- package/dist/kit/index.d.ts +7 -0
- package/dist/kit/index.d.ts.map +1 -0
- package/dist/kit/index.js +6 -0
- package/dist/kit/inventory.d.ts +111 -0
- package/dist/kit/inventory.d.ts.map +1 -0
- package/dist/kit/inventory.js +158 -0
- package/dist/kit/kit.d.ts +96 -0
- package/dist/kit/kit.d.ts.map +1 -0
- package/dist/kit/kit.js +98 -0
- package/dist/kit/npcs.d.ts +182 -0
- package/dist/kit/npcs.d.ts.map +1 -0
- package/dist/kit/npcs.js +106 -0
- package/dist/kit/objects.d.ts +108 -0
- package/dist/kit/objects.d.ts.map +1 -0
- package/dist/kit/objects.js +110 -0
- package/dist/kit/shared.d.ts +32 -0
- package/dist/kit/shared.d.ts.map +1 -0
- package/dist/kit/shared.js +39 -0
- package/dist/lb-cookie-store.d.ts +20 -0
- package/dist/lb-cookie-store.d.ts.map +1 -0
- package/dist/lb-cookie-store.js +73 -0
- package/dist/realtime.d.ts +20 -5
- package/dist/realtime.d.ts.map +1 -1
- package/dist/realtime.js +38 -0
- package/dist/types.d.ts +22 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/world.d.ts +13 -8
- package/dist/world.d.ts.map +1 -1
- package/dist/world.js +13 -8
- package/package.json +6 -4
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
/** Serialize a {@link KitInvokePolicy} tree to the wire `invokePolicyJson`. */
|
|
2
|
+
export function kitPolicyJson(policy) {
|
|
3
|
+
return JSON.stringify(policy);
|
|
4
|
+
}
|
|
5
|
+
/** Convert `PascalCase`/`camelCase` to `snake_case` for derived names. */
|
|
6
|
+
export function toSnakeCase(name) {
|
|
7
|
+
return name
|
|
8
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
|
|
9
|
+
.replace(/[\s-]+/g, '_')
|
|
10
|
+
.toLowerCase();
|
|
11
|
+
}
|
|
12
|
+
/** Compute the type/function names an inventory blueprint (and its runtime helper) uses. */
|
|
13
|
+
export function inventoryNames(typePrefix = '') {
|
|
14
|
+
const fnPrefix = typePrefix ? `${toSnakeCase(typePrefix)}_` : '';
|
|
15
|
+
return {
|
|
16
|
+
inventoryType: `${typePrefix}Inventory`,
|
|
17
|
+
stackType: `${typePrefix}ItemStack`,
|
|
18
|
+
grantFn: `${fnPrefix}grant_stack`,
|
|
19
|
+
consumeFn: `${fnPrefix}consume_stack`,
|
|
20
|
+
moveFn: `${fnPrefix}move_stack`,
|
|
21
|
+
transferFn: `${fnPrefix}transfer_stack`,
|
|
22
|
+
containsEdge: 'inventory_contains',
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Blueprint for a server-authoritative **inventory**: a per-player
|
|
27
|
+
* `Inventory` container plus `ItemStack` containers (`item_id`, `quantity`,
|
|
28
|
+
* `slot`), and owner-gated functions to grant, consume, move, and transfer
|
|
29
|
+
* stacks. Quantity guards live in the invoke policies, so an untrusted client
|
|
30
|
+
* can never overdraw or touch another player's items.
|
|
31
|
+
*
|
|
32
|
+
* Runtime counterpart: `client.kit(appId).inventory`.
|
|
33
|
+
*/
|
|
34
|
+
export function inventoryBlueprint(options = {}) {
|
|
35
|
+
const { typePrefix = '', maxSlots = 24, slotCount = 64 } = options;
|
|
36
|
+
const names = inventoryNames(typePrefix);
|
|
37
|
+
const ownerOnly = kitPolicyJson({ type: 'owner_of_self' });
|
|
38
|
+
return {
|
|
39
|
+
name: names.inventoryType,
|
|
40
|
+
containerTypes: [
|
|
41
|
+
{
|
|
42
|
+
typeName: names.inventoryType,
|
|
43
|
+
displayName: names.inventoryType,
|
|
44
|
+
instantiableBy: 'member',
|
|
45
|
+
description: 'A bag of item stacks owned by one player.',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
typeName: names.stackType,
|
|
49
|
+
displayName: names.stackType,
|
|
50
|
+
instantiableBy: 'member',
|
|
51
|
+
description: 'One stack of a single item type in an inventory slot.',
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
propertyDefinitions: [
|
|
55
|
+
{
|
|
56
|
+
containerTypeName: names.inventoryType,
|
|
57
|
+
key: 'max_slots',
|
|
58
|
+
valueType: 'int',
|
|
59
|
+
defaultValueJson: String(maxSlots),
|
|
60
|
+
},
|
|
61
|
+
{ containerTypeName: names.stackType, key: 'item_id', valueType: 'string' },
|
|
62
|
+
{
|
|
63
|
+
containerTypeName: names.stackType,
|
|
64
|
+
key: 'quantity',
|
|
65
|
+
valueType: 'int',
|
|
66
|
+
defaultValueJson: '0',
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
containerTypeName: names.stackType,
|
|
70
|
+
key: 'slot',
|
|
71
|
+
valueType: 'int',
|
|
72
|
+
defaultValueJson: '0',
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
functions: [
|
|
76
|
+
{
|
|
77
|
+
name: names.grantFn,
|
|
78
|
+
containerTypeName: names.stackType,
|
|
79
|
+
returnType: 'int',
|
|
80
|
+
parameters: [{ name: 'amount', valueType: 'int', required: true }],
|
|
81
|
+
mutations: [
|
|
82
|
+
{
|
|
83
|
+
target: 'self',
|
|
84
|
+
property: 'quantity',
|
|
85
|
+
expression: 'self.quantity + max(0, $amount)',
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
returnExpression: 'self.quantity',
|
|
89
|
+
invokePolicyJson: ownerOnly,
|
|
90
|
+
description: 'Add items to a stack the caller owns.',
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: names.consumeFn,
|
|
94
|
+
containerTypeName: names.stackType,
|
|
95
|
+
returnType: 'int',
|
|
96
|
+
parameters: [{ name: 'amount', valueType: 'int', required: true }],
|
|
97
|
+
mutations: [
|
|
98
|
+
{ target: 'self', property: 'quantity', expression: 'self.quantity - $amount' },
|
|
99
|
+
],
|
|
100
|
+
returnExpression: 'self.quantity',
|
|
101
|
+
invokePolicyJson: kitPolicyJson({
|
|
102
|
+
type: 'and',
|
|
103
|
+
rules: [
|
|
104
|
+
{ type: 'owner_of_self' },
|
|
105
|
+
{
|
|
106
|
+
type: 'condition',
|
|
107
|
+
expression: '$amount > 0 && self.quantity >= $amount',
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
}),
|
|
111
|
+
description: 'Spend items from a stack; refuses to overdraw.',
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: names.moveFn,
|
|
115
|
+
containerTypeName: names.stackType,
|
|
116
|
+
returnType: 'int',
|
|
117
|
+
parameters: [{ name: 'to_slot', valueType: 'int', required: true }],
|
|
118
|
+
mutations: [
|
|
119
|
+
{
|
|
120
|
+
target: 'self',
|
|
121
|
+
property: 'slot',
|
|
122
|
+
expression: `clamp($to_slot, 0, ${slotCount - 1})`,
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
returnExpression: 'self.slot',
|
|
126
|
+
invokePolicyJson: ownerOnly,
|
|
127
|
+
description: 'Move a stack to another slot.',
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: names.transferFn,
|
|
131
|
+
containerTypeName: names.stackType,
|
|
132
|
+
returnType: 'int',
|
|
133
|
+
parameters: [
|
|
134
|
+
{ name: 'to_id', valueType: 'container_ref', required: true },
|
|
135
|
+
{ name: 'amount', valueType: 'int', required: true },
|
|
136
|
+
],
|
|
137
|
+
mutations: [
|
|
138
|
+
{ target: 'self', property: 'quantity', expression: 'self.quantity - $amount' },
|
|
139
|
+
{
|
|
140
|
+
target: 'ref($to_id)',
|
|
141
|
+
property: 'quantity',
|
|
142
|
+
expression: 'ref($to_id).quantity + $amount',
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
returnExpression: 'self.quantity',
|
|
146
|
+
invokePolicyJson: kitPolicyJson({
|
|
147
|
+
type: 'and',
|
|
148
|
+
rules: [
|
|
149
|
+
{ type: 'owner_of_self' },
|
|
150
|
+
{
|
|
151
|
+
type: 'condition',
|
|
152
|
+
expression: '$amount > 0 && self.quantity >= $amount && ref($to_id).item_id == self.item_id',
|
|
153
|
+
},
|
|
154
|
+
],
|
|
155
|
+
}),
|
|
156
|
+
description: 'Atomically move items between two stacks of the same item type.',
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
/** Compute the type/function names a lock blueprint (and its runtime helper) uses. */
|
|
162
|
+
export function lockNames(objectTypeName = 'Lockable', keyTypeName) {
|
|
163
|
+
const snake = toSnakeCase(objectTypeName);
|
|
164
|
+
return {
|
|
165
|
+
objectType: objectTypeName,
|
|
166
|
+
keyType: keyTypeName ?? `${objectTypeName}Key`,
|
|
167
|
+
openFn: `open_${snake}`,
|
|
168
|
+
closeFn: `close_${snake}`,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
function lockAuthorityRule(authority) {
|
|
172
|
+
switch (authority.kind) {
|
|
173
|
+
case 'owner':
|
|
174
|
+
return { type: 'owner_of_self' };
|
|
175
|
+
case 'key':
|
|
176
|
+
// Container ownership is not readable from expressions, so the key
|
|
177
|
+
// mirrors its owner into an `owner_user_id` property; the condition
|
|
178
|
+
// verifies both the match and the ownership server-side.
|
|
179
|
+
return {
|
|
180
|
+
type: 'condition',
|
|
181
|
+
expression: 'ref($key_id).key_id == self.required_key_id && ref($key_id).owner_user_id == $caller_user_id',
|
|
182
|
+
};
|
|
183
|
+
case 'gridPermission':
|
|
184
|
+
return {
|
|
185
|
+
type: 'grid_permission',
|
|
186
|
+
key: authority.key,
|
|
187
|
+
...(authority.gridId !== undefined ? { gridId: authority.gridId } : {}),
|
|
188
|
+
};
|
|
189
|
+
case 'groupPermission':
|
|
190
|
+
return {
|
|
191
|
+
type: 'group_permission',
|
|
192
|
+
groupId: authority.groupId,
|
|
193
|
+
...(authority.permission !== undefined
|
|
194
|
+
? { permission: authority.permission }
|
|
195
|
+
: {}),
|
|
196
|
+
};
|
|
197
|
+
case 'custom':
|
|
198
|
+
return authority.rule;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Blueprint for a **lockable game object** (door, chest, gate, switch) whose
|
|
203
|
+
* `open`/`close` functions are gated by a configurable authority source:
|
|
204
|
+
* ownership, a key item the caller must hold, a runtime grid permission, a
|
|
205
|
+
* team/group permission, or any custom policy rule. Multiple authorities are
|
|
206
|
+
* OR'd, so "the owner, or anyone with the right key" is one blueprint.
|
|
207
|
+
*
|
|
208
|
+
* Runtime counterpart: `client.kit(appId).objects`.
|
|
209
|
+
*/
|
|
210
|
+
export function lockBlueprint(options) {
|
|
211
|
+
const authorities = Array.isArray(options.authority)
|
|
212
|
+
? options.authority
|
|
213
|
+
: [options.authority];
|
|
214
|
+
if (authorities.length === 0) {
|
|
215
|
+
throw new Error('lockBlueprint requires at least one authority source');
|
|
216
|
+
}
|
|
217
|
+
const names = lockNames(options.objectTypeName, options.keyTypeName);
|
|
218
|
+
const usesKey = authorities.some((a) => a.kind === 'key');
|
|
219
|
+
const rules = authorities.map(lockAuthorityRule);
|
|
220
|
+
const policy = rules.length === 1 ? rules[0] : { type: 'or', rules };
|
|
221
|
+
const policyJson = kitPolicyJson(policy);
|
|
222
|
+
const parameters = usesKey
|
|
223
|
+
? [{ name: 'key_id', valueType: 'container_ref', required: true }]
|
|
224
|
+
: [];
|
|
225
|
+
const containerTypes = [
|
|
226
|
+
{
|
|
227
|
+
typeName: names.objectType,
|
|
228
|
+
displayName: names.objectType,
|
|
229
|
+
instantiableBy: 'admin',
|
|
230
|
+
description: 'A lockable world object operated through gated functions.',
|
|
231
|
+
},
|
|
232
|
+
];
|
|
233
|
+
const propertyDefinitions = [
|
|
234
|
+
{
|
|
235
|
+
containerTypeName: names.objectType,
|
|
236
|
+
key: 'is_open',
|
|
237
|
+
valueType: 'bool',
|
|
238
|
+
defaultValueJson: 'false',
|
|
239
|
+
},
|
|
240
|
+
];
|
|
241
|
+
if (usesKey) {
|
|
242
|
+
propertyDefinitions.push({
|
|
243
|
+
containerTypeName: names.objectType,
|
|
244
|
+
key: 'required_key_id',
|
|
245
|
+
valueType: 'string',
|
|
246
|
+
});
|
|
247
|
+
containerTypes.push({
|
|
248
|
+
typeName: names.keyType,
|
|
249
|
+
displayName: names.keyType,
|
|
250
|
+
instantiableBy: 'admin',
|
|
251
|
+
description: 'A key item granting access to matching lockable objects.',
|
|
252
|
+
});
|
|
253
|
+
propertyDefinitions.push({ containerTypeName: names.keyType, key: 'key_id', valueType: 'string' }, {
|
|
254
|
+
containerTypeName: names.keyType,
|
|
255
|
+
key: 'owner_user_id',
|
|
256
|
+
valueType: 'int',
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
const openMutation = {
|
|
260
|
+
target: 'self',
|
|
261
|
+
property: 'is_open',
|
|
262
|
+
expression: 'true',
|
|
263
|
+
};
|
|
264
|
+
const closeMutation = {
|
|
265
|
+
target: 'self',
|
|
266
|
+
property: 'is_open',
|
|
267
|
+
expression: 'false',
|
|
268
|
+
};
|
|
269
|
+
return {
|
|
270
|
+
name: names.objectType,
|
|
271
|
+
containerTypes,
|
|
272
|
+
propertyDefinitions,
|
|
273
|
+
functions: [
|
|
274
|
+
{
|
|
275
|
+
name: names.openFn,
|
|
276
|
+
containerTypeName: names.objectType,
|
|
277
|
+
returnType: 'bool',
|
|
278
|
+
parameters,
|
|
279
|
+
mutations: [openMutation],
|
|
280
|
+
returnExpression: 'self.is_open',
|
|
281
|
+
invokePolicyJson: policyJson,
|
|
282
|
+
description: `Open a ${names.objectType}; the invoke policy decides who may.`,
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
name: names.closeFn,
|
|
286
|
+
containerTypeName: names.objectType,
|
|
287
|
+
returnType: 'bool',
|
|
288
|
+
parameters,
|
|
289
|
+
mutations: [closeMutation],
|
|
290
|
+
returnExpression: 'self.is_open',
|
|
291
|
+
invokePolicyJson: policyJson,
|
|
292
|
+
description: `Close a ${names.objectType}; same authority as opening.`,
|
|
293
|
+
},
|
|
294
|
+
],
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
/** Compute the function/automation names an NPC behavior deploys under. */
|
|
298
|
+
export function npcBehaviorFunctionName(behavior) {
|
|
299
|
+
return behavior.functionName ?? toSnakeCase(behavior.name);
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Blueprint for an **NPC archetype**: an admin-instantiable container type
|
|
303
|
+
* holding the NPC's durable state, one `autonomousInvocable` model function
|
|
304
|
+
* per behavior (gated `is_automation` so players cannot puppet them), and the
|
|
305
|
+
* automations + event triggers that drive those behaviors on the server.
|
|
306
|
+
*
|
|
307
|
+
* Runtime counterpart: `client.kit(appId).npcs`.
|
|
308
|
+
*/
|
|
309
|
+
export function npcBlueprint(options) {
|
|
310
|
+
const typeName = options.typeName ?? 'Npc';
|
|
311
|
+
if (options.behaviors.length === 0) {
|
|
312
|
+
throw new Error('npcBlueprint requires at least one behavior');
|
|
313
|
+
}
|
|
314
|
+
const propertyDefinitions = [
|
|
315
|
+
{
|
|
316
|
+
containerTypeName: typeName,
|
|
317
|
+
key: 'role',
|
|
318
|
+
valueType: 'string',
|
|
319
|
+
defaultValueJson: '""',
|
|
320
|
+
},
|
|
321
|
+
{ containerTypeName: typeName, key: 'x', valueType: 'float', defaultValueJson: '0' },
|
|
322
|
+
{ containerTypeName: typeName, key: 'y', valueType: 'float', defaultValueJson: '0' },
|
|
323
|
+
{ containerTypeName: typeName, key: 'z', valueType: 'float', defaultValueJson: '0' },
|
|
324
|
+
{
|
|
325
|
+
containerTypeName: typeName,
|
|
326
|
+
key: 'behavior_state',
|
|
327
|
+
valueType: 'string',
|
|
328
|
+
defaultValueJson: '"idle"',
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
containerTypeName: typeName,
|
|
332
|
+
key: 'health',
|
|
333
|
+
valueType: 'int',
|
|
334
|
+
defaultValueJson: '100',
|
|
335
|
+
},
|
|
336
|
+
...(options.extraProperties ?? []).map((p) => ({
|
|
337
|
+
...p,
|
|
338
|
+
containerTypeName: typeName,
|
|
339
|
+
})),
|
|
340
|
+
];
|
|
341
|
+
const functions = [];
|
|
342
|
+
const automations = [];
|
|
343
|
+
const automationTriggers = [];
|
|
344
|
+
for (const behavior of options.behaviors) {
|
|
345
|
+
const functionName = npcBehaviorFunctionName(behavior);
|
|
346
|
+
functions.push({
|
|
347
|
+
name: functionName,
|
|
348
|
+
containerTypeName: typeName,
|
|
349
|
+
parameters: behavior.parameters,
|
|
350
|
+
mutations: behavior.mutations,
|
|
351
|
+
invokePolicyJson: kitPolicyJson({ type: 'is_automation' }),
|
|
352
|
+
autonomousInvocable: true,
|
|
353
|
+
description: `Server-driven NPC behavior for the '${behavior.name}' automation.`,
|
|
354
|
+
});
|
|
355
|
+
const selector = behavior.selector ??
|
|
356
|
+
(behavior.role !== undefined
|
|
357
|
+
? { selfWhere: [{ key: 'role', op: '==', value: behavior.role }] }
|
|
358
|
+
: undefined);
|
|
359
|
+
const automation = {
|
|
360
|
+
name: behavior.name,
|
|
361
|
+
functionName,
|
|
362
|
+
targetMode: 'type',
|
|
363
|
+
targetTypeName: typeName,
|
|
364
|
+
maxTargets: behavior.maxTargets ?? 8,
|
|
365
|
+
...(selector ? { selectorJson: JSON.stringify(selector) } : {}),
|
|
366
|
+
...(behavior.params ? { paramsJson: JSON.stringify(behavior.params) } : {}),
|
|
367
|
+
...(behavior.runAsUserId !== undefined
|
|
368
|
+
? { runAsUserId: behavior.runAsUserId }
|
|
369
|
+
: {}),
|
|
370
|
+
};
|
|
371
|
+
if ('intervalMs' in behavior.trigger) {
|
|
372
|
+
automation.triggerType = 'schedule';
|
|
373
|
+
automation.scheduleKind = 'interval';
|
|
374
|
+
automation.intervalMs = behavior.trigger.intervalMs;
|
|
375
|
+
}
|
|
376
|
+
else if ('cronExpr' in behavior.trigger) {
|
|
377
|
+
automation.triggerType = 'schedule';
|
|
378
|
+
automation.scheduleKind = 'cron';
|
|
379
|
+
automation.cronExpr = behavior.trigger.cronExpr;
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
automation.triggerType = 'event';
|
|
383
|
+
automationTriggers.push({
|
|
384
|
+
automationName: behavior.name,
|
|
385
|
+
onEvent: behavior.trigger.onEvent,
|
|
386
|
+
...(behavior.trigger.functionName !== undefined
|
|
387
|
+
? { functionName: behavior.trigger.functionName }
|
|
388
|
+
: {}),
|
|
389
|
+
...(behavior.trigger.containerTypeName !== undefined
|
|
390
|
+
? { containerTypeName: behavior.trigger.containerTypeName }
|
|
391
|
+
: {}),
|
|
392
|
+
...(behavior.trigger.propertyKey !== undefined
|
|
393
|
+
? { propertyKey: behavior.trigger.propertyKey }
|
|
394
|
+
: {}),
|
|
395
|
+
...(behavior.trigger.debounceMs !== undefined
|
|
396
|
+
? { debounceMs: behavior.trigger.debounceMs }
|
|
397
|
+
: {}),
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
automations.push(automation);
|
|
401
|
+
}
|
|
402
|
+
return {
|
|
403
|
+
name: typeName,
|
|
404
|
+
containerTypes: [
|
|
405
|
+
{
|
|
406
|
+
typeName,
|
|
407
|
+
displayName: typeName,
|
|
408
|
+
instantiableBy: 'admin',
|
|
409
|
+
description: 'A server-driven non-player character.',
|
|
410
|
+
},
|
|
411
|
+
],
|
|
412
|
+
propertyDefinitions,
|
|
413
|
+
functions,
|
|
414
|
+
automations,
|
|
415
|
+
automationTriggers,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Merge blueprints into a single `gameModelSeed` payload plus the automation
|
|
420
|
+
* upserts, rejecting duplicate type, property, function, or automation names
|
|
421
|
+
* across blueprints (a duplicate almost always means two blueprints need
|
|
422
|
+
* distinct prefixes/type names).
|
|
423
|
+
*/
|
|
424
|
+
export function mergeBlueprints(appId, blueprints, options = {}) {
|
|
425
|
+
const containerTypes = [];
|
|
426
|
+
const propertyDefinitions = [];
|
|
427
|
+
const functions = [];
|
|
428
|
+
const containers = [];
|
|
429
|
+
const edges = [];
|
|
430
|
+
const automations = [];
|
|
431
|
+
const automationTriggers = [];
|
|
432
|
+
const seenTypes = new Map();
|
|
433
|
+
const seenProps = new Map();
|
|
434
|
+
const seenFunctions = new Map();
|
|
435
|
+
const seenAutomations = new Map();
|
|
436
|
+
const seenTempIds = new Map();
|
|
437
|
+
const claim = (seen, key, blueprintName, kind) => {
|
|
438
|
+
const existing = seen.get(key);
|
|
439
|
+
if (existing !== undefined) {
|
|
440
|
+
throw new Error(`Blueprint '${blueprintName}' redefines ${kind} '${key}' already defined by blueprint '${existing}'`);
|
|
441
|
+
}
|
|
442
|
+
seen.set(key, blueprintName);
|
|
443
|
+
};
|
|
444
|
+
for (const blueprint of blueprints) {
|
|
445
|
+
for (const type of blueprint.containerTypes ?? []) {
|
|
446
|
+
claim(seenTypes, type.typeName, blueprint.name, 'container type');
|
|
447
|
+
containerTypes.push(type);
|
|
448
|
+
}
|
|
449
|
+
for (const prop of blueprint.propertyDefinitions ?? []) {
|
|
450
|
+
claim(seenProps, `${prop.containerTypeName}.${prop.key}`, blueprint.name, 'property');
|
|
451
|
+
propertyDefinitions.push(prop);
|
|
452
|
+
}
|
|
453
|
+
for (const fn of blueprint.functions ?? []) {
|
|
454
|
+
claim(seenFunctions, fn.name, blueprint.name, 'function');
|
|
455
|
+
functions.push(fn);
|
|
456
|
+
}
|
|
457
|
+
for (const container of blueprint.containers ?? []) {
|
|
458
|
+
claim(seenTempIds, container.tempId, blueprint.name, 'container tempId');
|
|
459
|
+
containers.push(container);
|
|
460
|
+
}
|
|
461
|
+
edges.push(...(blueprint.edges ?? []));
|
|
462
|
+
for (const automation of blueprint.automations ?? []) {
|
|
463
|
+
claim(seenAutomations, automation.name, blueprint.name, 'automation');
|
|
464
|
+
automations.push({ ...automation, appId });
|
|
465
|
+
}
|
|
466
|
+
for (const trigger of blueprint.automationTriggers ?? []) {
|
|
467
|
+
automationTriggers.push({ ...trigger, appId });
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
const seedInput = {
|
|
471
|
+
appId,
|
|
472
|
+
...(options.sessionId !== undefined ? { sessionId: options.sessionId } : {}),
|
|
473
|
+
...(containerTypes.length ? { containerTypes } : {}),
|
|
474
|
+
...(propertyDefinitions.length ? { propertyDefinitions } : {}),
|
|
475
|
+
...(functions.length ? { functions } : {}),
|
|
476
|
+
...(containers.length ? { containers } : {}),
|
|
477
|
+
...(edges.length ? { edges } : {}),
|
|
478
|
+
};
|
|
479
|
+
return { seedInput, automations, automationTriggers };
|
|
480
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { inventoryBlueprint, inventoryNames, kitPolicyJson, lockBlueprint, lockNames, mergeBlueprints, npcBehaviorFunctionName, npcBlueprint, toSnakeCase, type InventoryBlueprintOptions, type InventoryNames, type KitAutomationSpec, type KitAutomationTriggerSpec, type KitBlueprint, type KitInvokePolicy, type LockAuthority, type LockBlueprintOptions, type LockNames, type MergedBlueprints, type NpcBehaviorSpec, type NpcBehaviorTrigger, type NpcBlueprintOptions, } from './blueprints.js';
|
|
2
|
+
export { GameKitClient, type GameKitOptions, type KitDeployResult, } from './kit.js';
|
|
3
|
+
export { InventoryKit, type InventoryKitOptions, type KitItemStack } from './inventory.js';
|
|
4
|
+
export { ObjectsKit, type ObjectsKitOptions } from './objects.js';
|
|
5
|
+
export { NpcsKit, type NpcsKitOptions, type KitNpc } from './npcs.js';
|
|
6
|
+
export { kitInvoke, toKitInvokeResult, type KitInvokeResult, type RawInvokeResult, } from './shared.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/kit/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,aAAa,EACb,SAAS,EACT,eAAe,EACf,uBAAuB,EACvB,YAAY,EACZ,WAAW,EACX,KAAK,yBAAyB,EAC9B,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,wBAAwB,EAC7B,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,oBAAoB,EACzB,KAAK,SAAS,EACd,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,GACzB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,aAAa,EACb,KAAK,cAAc,EACnB,KAAK,eAAe,GACrB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC3F,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AACtE,OAAO,EACL,SAAS,EACT,iBAAiB,EACjB,KAAK,eAAe,EACpB,KAAK,eAAe,GACrB,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { inventoryBlueprint, inventoryNames, kitPolicyJson, lockBlueprint, lockNames, mergeBlueprints, npcBehaviorFunctionName, npcBlueprint, toSnakeCase, } from './blueprints.js';
|
|
2
|
+
export { GameKitClient, } from './kit.js';
|
|
3
|
+
export { InventoryKit } from './inventory.js';
|
|
4
|
+
export { ObjectsKit } from './objects.js';
|
|
5
|
+
export { NpcsKit } from './npcs.js';
|
|
6
|
+
export { kitInvoke, toKitInvokeResult, } from './shared.js';
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type { GameModelAPI } from '../domains/gameModel.js';
|
|
2
|
+
import type { Scalars } from '../generated/graphql.js';
|
|
3
|
+
import { type KitInvokeResult } from './shared.js';
|
|
4
|
+
/** Options for {@link InventoryKit}. Must match the deployed blueprint's options. */
|
|
5
|
+
export interface InventoryKitOptions {
|
|
6
|
+
/** The `typePrefix` the inventory blueprint was deployed with. */
|
|
7
|
+
typePrefix?: string;
|
|
8
|
+
}
|
|
9
|
+
/** A parsed view of one item stack. */
|
|
10
|
+
export interface KitItemStack {
|
|
11
|
+
containerId: string;
|
|
12
|
+
displayName: string;
|
|
13
|
+
ownerUserId: string | null;
|
|
14
|
+
itemId: string;
|
|
15
|
+
quantity: number;
|
|
16
|
+
slot: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Runtime helpers for the {@link inventoryBlueprint} conventions: find or
|
|
20
|
+
* create the player's inventory, list stacks, and mutate them through the
|
|
21
|
+
* owner-gated model functions. All state lives server-side; every mutation is
|
|
22
|
+
* authority-checked and atomic.
|
|
23
|
+
*
|
|
24
|
+
* Obtained via `client.kit(appId).inventory`.
|
|
25
|
+
*/
|
|
26
|
+
export declare class InventoryKit {
|
|
27
|
+
private readonly appId;
|
|
28
|
+
private readonly gameModel;
|
|
29
|
+
private readonly names;
|
|
30
|
+
constructor(appId: Scalars['BigInt']['input'], gameModel: GameModelAPI, options?: InventoryKitOptions);
|
|
31
|
+
/**
|
|
32
|
+
* Find the caller's inventory container, creating it when absent. The
|
|
33
|
+
* server assigns ownership to the caller (the type is member-instantiable
|
|
34
|
+
* and `ownerUserId` is omitted on create).
|
|
35
|
+
*
|
|
36
|
+
* @param ownerUserId - The calling player's user id (a decimal string, e.g.
|
|
37
|
+
* from `client.users.me()`), used to recognize an existing inventory.
|
|
38
|
+
*/
|
|
39
|
+
ensure(ownerUserId: Scalars['BigInt']['input'], options?: {
|
|
40
|
+
displayName?: string;
|
|
41
|
+
sessionId?: string;
|
|
42
|
+
}): Promise<{
|
|
43
|
+
__typename?: "GmContainer";
|
|
44
|
+
containerId: string;
|
|
45
|
+
appId: string;
|
|
46
|
+
sessionId: string | null;
|
|
47
|
+
typeName: string;
|
|
48
|
+
displayName: string;
|
|
49
|
+
description: string | null;
|
|
50
|
+
ownerUserId: string | null;
|
|
51
|
+
metadataJson: string;
|
|
52
|
+
}>;
|
|
53
|
+
/**
|
|
54
|
+
* List a player's item stacks with parsed properties (`itemId`, `quantity`,
|
|
55
|
+
* `slot`). Fetches each stack's visible state in parallel.
|
|
56
|
+
*/
|
|
57
|
+
stacks(ownerUserId: Scalars['BigInt']['input']): Promise<KitItemStack[]>;
|
|
58
|
+
/**
|
|
59
|
+
* Create a new stack owned by the caller (server-assigned ownership).
|
|
60
|
+
* Use {@link grant} afterwards for authority-checked increments; the initial
|
|
61
|
+
* quantity here is a seed value on a container the caller owns anyway.
|
|
62
|
+
*/
|
|
63
|
+
createStack(input: {
|
|
64
|
+
itemId: string;
|
|
65
|
+
quantity?: number;
|
|
66
|
+
slot?: number;
|
|
67
|
+
displayName?: string;
|
|
68
|
+
sessionId?: string;
|
|
69
|
+
}): Promise<{
|
|
70
|
+
__typename?: "GmContainer";
|
|
71
|
+
containerId: string;
|
|
72
|
+
appId: string;
|
|
73
|
+
sessionId: string | null;
|
|
74
|
+
typeName: string;
|
|
75
|
+
displayName: string;
|
|
76
|
+
description: string | null;
|
|
77
|
+
ownerUserId: string | null;
|
|
78
|
+
metadataJson: string;
|
|
79
|
+
}>;
|
|
80
|
+
/** Add items to a stack the caller owns. Resolves with the new quantity. */
|
|
81
|
+
grant(stackId: string, amount: number): Promise<KitInvokeResult<number>>;
|
|
82
|
+
/**
|
|
83
|
+
* Spend items from a stack the caller owns. The server refuses to overdraw
|
|
84
|
+
* (`success: false`, nothing written). Resolves with the new quantity.
|
|
85
|
+
*/
|
|
86
|
+
consume(stackId: string, amount: number): Promise<KitInvokeResult<number>>;
|
|
87
|
+
/** Move a stack to another slot. Resolves with the new (clamped) slot. */
|
|
88
|
+
move(stackId: string, toSlot: number): Promise<KitInvokeResult<number>>;
|
|
89
|
+
/**
|
|
90
|
+
* Atomically move items between two stacks of the same item type — both
|
|
91
|
+
* writes commit or neither does. The caller must own the source stack.
|
|
92
|
+
* Resolves with the source stack's remaining quantity.
|
|
93
|
+
*/
|
|
94
|
+
transfer(fromStackId: string, toStackId: string, amount: number): Promise<KitInvokeResult<number>>;
|
|
95
|
+
/**
|
|
96
|
+
* Record that a stack belongs to an inventory with an
|
|
97
|
+
* `inventory_contains` edge, so {@link contents} can read the whole bag in
|
|
98
|
+
* one traversal.
|
|
99
|
+
*/
|
|
100
|
+
linkStack(inventoryId: string, stackId: string): Promise<{
|
|
101
|
+
__typename?: "GmEdge";
|
|
102
|
+
edgeId: string;
|
|
103
|
+
fromContainerId: string;
|
|
104
|
+
toContainerId: string;
|
|
105
|
+
relationshipType: string;
|
|
106
|
+
weight: number | null;
|
|
107
|
+
}>;
|
|
108
|
+
/** Read every stack linked to an inventory (via `inventory_contains` edges). */
|
|
109
|
+
contents(inventoryId: string): Promise<KitItemStack[]>;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=inventory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inventory.d.ts","sourceRoot":"","sources":["../../src/kit/inventory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAEvD,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,aAAa,CAAC;AAErB,qFAAqF;AACrF,MAAM,WAAW,mBAAmB;IAClC,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,uCAAuC;AACvC,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;GAOG;AACH,qBAAa,YAAY;IAIrB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAJ5B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiB;gBAGpB,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,EACjC,SAAS,EAAE,YAAY,EACxC,OAAO,GAAE,mBAAwB;IAKnC;;;;;;;OAOG;IACG,MAAM,CACV,WAAW,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,EACvC,OAAO,GAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO;;;;;;;;;;;IAmB5D;;;OAGG;IACG,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IA2B9E;;;;OAIG;IACG,WAAW,CAAC,KAAK,EAAE;QACvB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB;;;;;;;;;;;IAcD,4EAA4E;IACtE,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;IAS9E;;;OAGG;IACG,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;IAShF,0EAA0E;IACpE,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;IAS7E;;;;OAIG;IACG,QAAQ,CACZ,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC;IASnC;;;;OAIG;IACG,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;;;;;;;IASpD,gFAAgF;IAC1E,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;CA4B7D"}
|