@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.
- package/README.md +21 -3
- package/dist/crowdy-client.d.ts.map +1 -1
- package/dist/crowdy-client.js +5 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/kit/blueprints/combat.d.ts +75 -0
- package/dist/kit/blueprints/combat.d.ts.map +1 -0
- package/dist/kit/blueprints/combat.js +351 -0
- package/dist/kit/blueprints/core.d.ts +222 -0
- package/dist/kit/blueprints/core.d.ts.map +1 -0
- package/dist/kit/blueprints/core.js +185 -0
- package/dist/kit/blueprints/decks.d.ts +50 -0
- package/dist/kit/blueprints/decks.d.ts.map +1 -0
- package/dist/kit/blueprints/decks.js +163 -0
- package/dist/kit/blueprints/economy.d.ts +88 -0
- package/dist/kit/blueprints/economy.d.ts.map +1 -0
- package/dist/kit/blueprints/economy.js +533 -0
- package/dist/kit/blueprints/guild.d.ts +51 -0
- package/dist/kit/blueprints/guild.d.ts.map +1 -0
- package/dist/kit/blueprints/guild.js +50 -0
- package/dist/kit/blueprints/index.d.ts +21 -0
- package/dist/kit/blueprints/index.d.ts.map +1 -0
- package/dist/kit/blueprints/index.js +20 -0
- package/dist/kit/blueprints/inventory.d.ts +38 -0
- package/dist/kit/blueprints/inventory.d.ts.map +1 -0
- package/dist/kit/blueprints/inventory.js +157 -0
- package/dist/kit/blueprints/leaderboards.d.ts +49 -0
- package/dist/kit/blueprints/leaderboards.d.ts.map +1 -0
- package/dist/kit/blueprints/leaderboards.js +126 -0
- package/dist/kit/blueprints/locks.d.ts +82 -0
- package/dist/kit/blueprints/locks.d.ts.map +1 -0
- package/dist/kit/blueprints/locks.js +155 -0
- package/dist/kit/blueprints/loot.d.ts +84 -0
- package/dist/kit/blueprints/loot.d.ts.map +1 -0
- package/dist/kit/blueprints/loot.js +228 -0
- package/dist/kit/blueprints/matches.d.ts +59 -0
- package/dist/kit/blueprints/matches.d.ts.map +1 -0
- package/dist/kit/blueprints/matches.js +246 -0
- package/dist/kit/blueprints/npcs.d.ts +71 -0
- package/dist/kit/blueprints/npcs.d.ts.map +1 -0
- package/dist/kit/blueprints/npcs.js +122 -0
- package/dist/kit/blueprints/plots.d.ts +54 -0
- package/dist/kit/blueprints/plots.d.ts.map +1 -0
- package/dist/kit/blueprints/plots.js +146 -0
- package/dist/kit/blueprints/progression.d.ts +68 -0
- package/dist/kit/blueprints/progression.d.ts.map +1 -0
- package/dist/kit/blueprints/progression.js +324 -0
- package/dist/kit/blueprints/quests.d.ts +65 -0
- package/dist/kit/blueprints/quests.d.ts.map +1 -0
- package/dist/kit/blueprints/quests.js +284 -0
- package/dist/kit/blueprints/worldsim.d.ts +84 -0
- package/dist/kit/blueprints/worldsim.d.ts.map +1 -0
- package/dist/kit/blueprints/worldsim.js +461 -0
- package/dist/kit/combat.d.ts +103 -0
- package/dist/kit/combat.d.ts.map +1 -0
- package/dist/kit/combat.js +151 -0
- package/dist/kit/decks.d.ts +123 -0
- package/dist/kit/decks.d.ts.map +1 -0
- package/dist/kit/decks.js +138 -0
- package/dist/kit/economy.d.ts +244 -0
- package/dist/kit/economy.d.ts.map +1 -0
- package/dist/kit/economy.js +365 -0
- package/dist/kit/features.d.ts +60 -0
- package/dist/kit/features.d.ts.map +1 -0
- package/dist/kit/features.js +65 -0
- package/dist/kit/index.d.ts +13 -2
- package/dist/kit/index.d.ts.map +1 -1
- package/dist/kit/index.js +12 -1
- package/dist/kit/inventory.d.ts +5 -0
- package/dist/kit/inventory.d.ts.map +1 -1
- package/dist/kit/inventory.js +14 -1
- package/dist/kit/kit.d.ts +58 -2
- package/dist/kit/kit.d.ts.map +1 -1
- package/dist/kit/kit.js +24 -2
- package/dist/kit/leaderboards.d.ts +66 -0
- package/dist/kit/leaderboards.d.ts.map +1 -0
- package/dist/kit/leaderboards.js +94 -0
- package/dist/kit/loot.d.ts +106 -0
- package/dist/kit/loot.d.ts.map +1 -0
- package/dist/kit/loot.js +120 -0
- package/dist/kit/matches.d.ts +140 -0
- package/dist/kit/matches.d.ts.map +1 -0
- package/dist/kit/matches.js +253 -0
- package/dist/kit/objects.js +1 -1
- package/dist/kit/plots.js +1 -1
- package/dist/kit/progression.d.ts +181 -0
- package/dist/kit/progression.d.ts.map +1 -0
- package/dist/kit/progression.js +265 -0
- package/dist/kit/quests.d.ts +110 -0
- package/dist/kit/quests.d.ts.map +1 -0
- package/dist/kit/quests.js +158 -0
- package/dist/kit/social.d.ts +267 -0
- package/dist/kit/social.d.ts.map +1 -0
- package/dist/kit/social.js +230 -0
- package/dist/kit/worldsim.d.ts +225 -0
- package/dist/kit/worldsim.d.ts.map +1 -0
- package/dist/kit/worldsim.js +231 -0
- package/package.json +1 -1
- package/dist/kit/blueprints.d.ts +0 -370
- package/dist/kit/blueprints.d.ts.map +0 -1
- 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
|
+
}
|