@crowdedkingdoms/crowdyjs 17.14.0-dev.1 → 18.0.0-dev.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/MIGRATION.md +92 -1
- package/README.md +93 -162
- package/dist/crowdy-client.d.ts +12 -31
- package/dist/crowdy-client.d.ts.map +1 -1
- package/dist/crowdy-client.js +15 -37
- package/dist/crowdy-dsh/bridge.d.ts.map +1 -1
- package/dist/crowdy-dsh/bridge.js +3 -6
- package/dist/crowdy-studio/controller.d.ts +34 -59
- package/dist/crowdy-studio/controller.d.ts.map +1 -1
- package/dist/crowdy-studio/controller.js +96 -225
- package/dist/crowdy-studio/diagnostics.d.ts +3 -12
- package/dist/crowdy-studio/diagnostics.d.ts.map +1 -1
- package/dist/crowdy-studio/dom-shell.d.ts +2 -4
- package/dist/crowdy-studio/dom-shell.d.ts.map +1 -1
- package/dist/crowdy-studio/dom-shell.js +16 -49
- package/dist/crowdy-studio/embed/hud-layer.d.ts +1 -2
- package/dist/crowdy-studio/embed/hud-layer.d.ts.map +1 -1
- package/dist/crowdy-studio/embed/panel.d.ts +3 -13
- package/dist/crowdy-studio/embed/panel.d.ts.map +1 -1
- package/dist/crowdy-studio/embed/panel.js +3 -6
- package/dist/crowdy-studio/index.d.ts +1 -3
- package/dist/crowdy-studio/index.d.ts.map +1 -1
- package/dist/crowdy-studio/index.js +0 -2
- package/dist/crowdy-studio/models.d.ts +3 -4
- package/dist/crowdy-studio/models.d.ts.map +1 -1
- package/dist/crowdy-studio/starter-projects.d.ts +8 -12
- package/dist/crowdy-studio/starter-projects.d.ts.map +1 -1
- package/dist/crowdy-studio/starter-projects.js +12 -103
- package/dist/domains/appAccess.d.ts +14 -1
- package/dist/domains/appAccess.d.ts.map +1 -1
- package/dist/domains/appAccess.js +37 -1
- package/dist/domains/host.d.ts +3 -3
- package/dist/domains/host.js +3 -3
- package/dist/domains/marketplace.d.ts +9 -70
- package/dist/domains/marketplace.d.ts.map +1 -1
- package/dist/domains/marketplace.js +9 -120
- package/dist/domains/playerWallet.d.ts +6 -12
- package/dist/domains/playerWallet.d.ts.map +1 -1
- package/dist/domains/playerWallet.js +6 -21
- package/dist/errors.d.ts +7 -13
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +6 -10
- package/dist/generated/graphql.d.ts +141 -7083
- package/dist/generated/graphql.d.ts.map +1 -1
- package/dist/generated/graphql.js +5 -313
- package/dist/grid-mods/exec-client-halves.d.ts +19 -3
- package/dist/grid-mods/exec-client-halves.d.ts.map +1 -1
- package/dist/grid-mods/exec-client-halves.js +17 -2
- package/dist/grid-mods/grid-host-calls.d.ts +29 -17
- package/dist/grid-mods/grid-host-calls.d.ts.map +1 -1
- package/dist/grid-mods/grid-host-calls.js +40 -46
- package/dist/grid-mods/mod-runtime.d.ts +18 -11
- package/dist/grid-mods/mod-runtime.d.ts.map +1 -1
- package/dist/grid-mods/mod-runtime.js +4 -2
- package/dist/grid-scope.d.ts +0 -189
- package/dist/grid-scope.d.ts.map +1 -1
- package/dist/grid-scope.js +0 -70
- package/dist/index.d.ts +11 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -16
- package/dist/kit/actions.d.ts +5 -9
- package/dist/kit/actions.d.ts.map +1 -1
- package/dist/kit/actions.js +5 -9
- package/dist/kit/index.d.ts +2 -33
- package/dist/kit/index.d.ts.map +1 -1
- package/dist/kit/index.js +1 -32
- package/dist/kit/kit.d.ts +8 -208
- package/dist/kit/kit.d.ts.map +1 -1
- package/dist/kit/kit.js +5 -163
- package/dist/kit/social.d.ts +1 -3
- package/dist/kit/social.d.ts.map +1 -1
- package/dist/kit/social.js +1 -3
- package/dist/kit/wire.d.ts +16 -5
- package/dist/kit/wire.d.ts.map +1 -1
- package/dist/kit/wire.js +19 -5
- package/dist/live-coding/assets/browser-authoring-index.json +1 -1
- package/dist/live-coding/assets/manifest.json +1 -1
- package/dist/live-coding/browser-authoring-index.generated.d.ts.map +1 -1
- package/dist/live-coding/browser-authoring-index.generated.js +1496 -4386
- package/dist/live-coding/rust-analysis.d.ts.map +1 -1
- package/dist/live-coding/rust-analysis.js +30 -34
- package/dist/player-runtime/client-host-calls.d.ts +8 -0
- package/dist/player-runtime/client-host-calls.d.ts.map +1 -0
- package/dist/player-runtime/client-host-calls.js +28 -0
- package/dist/player-runtime/glue-runtime.d.ts +24 -20
- package/dist/player-runtime/glue-runtime.d.ts.map +1 -1
- package/dist/player-runtime/glue-runtime.js +26 -55
- package/dist/player-runtime/glue-sab.d.ts +2 -2
- package/dist/player-runtime/glue-sab.js +2 -2
- package/dist/player-runtime/player-code-broker.d.ts +70 -43
- package/dist/player-runtime/player-code-broker.d.ts.map +1 -1
- package/dist/player-runtime/player-code-broker.js +159 -64
- package/dist/player-runtime/player-glue-worker.d.ts +8 -9
- package/dist/player-runtime/player-glue-worker.d.ts.map +1 -1
- package/dist/player-runtime/player-glue-worker.js +26 -11
- package/dist/stores/inbox.d.ts +1 -1
- package/dist/stores/inbox.d.ts.map +1 -1
- package/dist/stores/index.d.ts +0 -11
- package/dist/stores/index.d.ts.map +1 -1
- package/dist/stores/index.js +0 -4
- package/dist/stores/session.d.ts +0 -2
- package/dist/stores/session.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/crowdy-studio/model-lint-log.d.ts +0 -76
- package/dist/crowdy-studio/model-lint-log.d.ts.map +0 -1
- package/dist/crowdy-studio/model-lint-log.js +0 -93
- package/dist/crowdy-studio/model-lint.d.ts +0 -61
- package/dist/crowdy-studio/model-lint.d.ts.map +0 -1
- package/dist/crowdy-studio/model-lint.js +0 -45
- package/dist/domains/compute.d.ts +0 -208
- package/dist/domains/compute.d.ts.map +0 -1
- package/dist/domains/compute.js +0 -274
- package/dist/domains/controlPlane.d.ts +0 -38
- package/dist/domains/controlPlane.d.ts.map +0 -1
- package/dist/domains/controlPlane.js +0 -43
- package/dist/domains/gameModel.d.ts +0 -1174
- package/dist/domains/gameModel.d.ts.map +0 -1
- package/dist/domains/gameModel.js +0 -1333
- package/dist/domains/playerCompute.d.ts +0 -91
- package/dist/domains/playerCompute.d.ts.map +0 -1
- package/dist/domains/playerCompute.js +0 -136
- package/dist/domains/playerModel.d.ts +0 -20
- package/dist/domains/playerModel.d.ts.map +0 -1
- package/dist/domains/playerModel.js +0 -54
- package/dist/kit/abilities.d.ts +0 -66
- package/dist/kit/abilities.d.ts.map +0 -1
- package/dist/kit/abilities.js +0 -71
- package/dist/kit/blueprints/combat.d.ts +0 -81
- package/dist/kit/blueprints/combat.d.ts.map +0 -1
- package/dist/kit/blueprints/combat.js +0 -357
- package/dist/kit/blueprints/core.d.ts +0 -222
- package/dist/kit/blueprints/core.d.ts.map +0 -1
- package/dist/kit/blueprints/core.js +0 -185
- package/dist/kit/blueprints/decks.d.ts +0 -50
- package/dist/kit/blueprints/decks.d.ts.map +0 -1
- package/dist/kit/blueprints/decks.js +0 -163
- package/dist/kit/blueprints/economy.d.ts +0 -88
- package/dist/kit/blueprints/economy.d.ts.map +0 -1
- package/dist/kit/blueprints/economy.js +0 -533
- package/dist/kit/blueprints/guild.d.ts +0 -51
- package/dist/kit/blueprints/guild.d.ts.map +0 -1
- package/dist/kit/blueprints/guild.js +0 -50
- package/dist/kit/blueprints/index.d.ts +0 -23
- package/dist/kit/blueprints/index.d.ts.map +0 -1
- package/dist/kit/blueprints/index.js +0 -22
- package/dist/kit/blueprints/inventory.d.ts +0 -84
- package/dist/kit/blueprints/inventory.d.ts.map +0 -1
- package/dist/kit/blueprints/inventory.js +0 -257
- package/dist/kit/blueprints/leaderboards.d.ts +0 -49
- package/dist/kit/blueprints/leaderboards.d.ts.map +0 -1
- package/dist/kit/blueprints/leaderboards.js +0 -126
- package/dist/kit/blueprints/liveops.d.ts +0 -42
- package/dist/kit/blueprints/liveops.d.ts.map +0 -1
- package/dist/kit/blueprints/liveops.js +0 -170
- package/dist/kit/blueprints/locks.d.ts +0 -82
- package/dist/kit/blueprints/locks.d.ts.map +0 -1
- package/dist/kit/blueprints/locks.js +0 -155
- package/dist/kit/blueprints/loot.d.ts +0 -90
- package/dist/kit/blueprints/loot.d.ts.map +0 -1
- package/dist/kit/blueprints/loot.js +0 -229
- package/dist/kit/blueprints/matches.d.ts +0 -84
- package/dist/kit/blueprints/matches.d.ts.map +0 -1
- package/dist/kit/blueprints/matches.js +0 -349
- package/dist/kit/blueprints/moderation.d.ts +0 -32
- package/dist/kit/blueprints/moderation.d.ts.map +0 -1
- package/dist/kit/blueprints/moderation.js +0 -140
- package/dist/kit/blueprints/npcs.d.ts +0 -78
- package/dist/kit/blueprints/npcs.d.ts.map +0 -1
- package/dist/kit/blueprints/npcs.js +0 -125
- package/dist/kit/blueprints/plots.d.ts +0 -63
- package/dist/kit/blueprints/plots.d.ts.map +0 -1
- package/dist/kit/blueprints/plots.js +0 -151
- package/dist/kit/blueprints/progression.d.ts +0 -68
- package/dist/kit/blueprints/progression.d.ts.map +0 -1
- package/dist/kit/blueprints/progression.js +0 -324
- package/dist/kit/blueprints/quests.d.ts +0 -72
- package/dist/kit/blueprints/quests.d.ts.map +0 -1
- package/dist/kit/blueprints/quests.js +0 -285
- package/dist/kit/blueprints/worldsim.d.ts +0 -91
- package/dist/kit/blueprints/worldsim.d.ts.map +0 -1
- package/dist/kit/blueprints/worldsim.js +0 -468
- package/dist/kit/combat.d.ts +0 -141
- package/dist/kit/combat.d.ts.map +0 -1
- package/dist/kit/combat.js +0 -196
- package/dist/kit/decks.d.ts +0 -157
- package/dist/kit/decks.d.ts.map +0 -1
- package/dist/kit/decks.js +0 -186
- package/dist/kit/director.d.ts +0 -88
- package/dist/kit/director.d.ts.map +0 -1
- package/dist/kit/director.js +0 -86
- package/dist/kit/economy.d.ts +0 -289
- package/dist/kit/economy.d.ts.map +0 -1
- package/dist/kit/economy.js +0 -427
- package/dist/kit/engine.d.ts +0 -48
- package/dist/kit/engine.d.ts.map +0 -1
- package/dist/kit/engine.js +0 -113
- package/dist/kit/features.d.ts +0 -60
- package/dist/kit/features.d.ts.map +0 -1
- package/dist/kit/features.js +0 -65
- package/dist/kit/instances.d.ts +0 -52
- package/dist/kit/instances.d.ts.map +0 -1
- package/dist/kit/instances.js +0 -60
- package/dist/kit/inventory.d.ts +0 -129
- package/dist/kit/inventory.d.ts.map +0 -1
- package/dist/kit/inventory.js +0 -200
- package/dist/kit/leaderboards.d.ts +0 -86
- package/dist/kit/leaderboards.d.ts.map +0 -1
- package/dist/kit/leaderboards.js +0 -133
- package/dist/kit/liveops.d.ts +0 -112
- package/dist/kit/liveops.d.ts.map +0 -1
- package/dist/kit/liveops.js +0 -183
- package/dist/kit/loot.d.ts +0 -128
- package/dist/kit/loot.d.ts.map +0 -1
- package/dist/kit/loot.js +0 -160
- package/dist/kit/matches.d.ts +0 -301
- package/dist/kit/matches.d.ts.map +0 -1
- package/dist/kit/matches.js +0 -458
- package/dist/kit/matchmaking.d.ts +0 -57
- package/dist/kit/matchmaking.d.ts.map +0 -1
- package/dist/kit/matchmaking.js +0 -59
- package/dist/kit/minigames.d.ts +0 -30
- package/dist/kit/minigames.d.ts.map +0 -1
- package/dist/kit/minigames.js +0 -38
- package/dist/kit/mobs.d.ts +0 -90
- package/dist/kit/mobs.d.ts.map +0 -1
- package/dist/kit/mobs.js +0 -98
- package/dist/kit/moderation.d.ts +0 -73
- package/dist/kit/moderation.d.ts.map +0 -1
- package/dist/kit/moderation.js +0 -103
- package/dist/kit/movement.d.ts +0 -78
- package/dist/kit/movement.d.ts.map +0 -1
- package/dist/kit/movement.js +0 -103
- package/dist/kit/notifications.d.ts +0 -45
- package/dist/kit/notifications.d.ts.map +0 -1
- package/dist/kit/notifications.js +0 -67
- package/dist/kit/npcs.d.ts +0 -226
- package/dist/kit/npcs.d.ts.map +0 -1
- package/dist/kit/npcs.js +0 -141
- package/dist/kit/objects.d.ts +0 -116
- package/dist/kit/objects.d.ts.map +0 -1
- package/dist/kit/objects.js +0 -119
- package/dist/kit/pets.d.ts +0 -85
- package/dist/kit/pets.d.ts.map +0 -1
- package/dist/kit/pets.js +0 -96
- package/dist/kit/plots.d.ts +0 -85
- package/dist/kit/plots.d.ts.map +0 -1
- package/dist/kit/plots.js +0 -113
- package/dist/kit/progression.d.ts +0 -181
- package/dist/kit/progression.d.ts.map +0 -1
- package/dist/kit/progression.js +0 -265
- package/dist/kit/quests.d.ts +0 -171
- package/dist/kit/quests.d.ts.map +0 -1
- package/dist/kit/quests.js +0 -237
- package/dist/kit/racing.d.ts +0 -85
- package/dist/kit/racing.d.ts.map +0 -1
- package/dist/kit/racing.js +0 -99
- package/dist/kit/shared.d.ts +0 -73
- package/dist/kit/shared.d.ts.map +0 -1
- package/dist/kit/shared.js +0 -101
- package/dist/kit/telemetry.d.ts +0 -66
- package/dist/kit/telemetry.d.ts.map +0 -1
- package/dist/kit/telemetry.js +0 -133
- package/dist/kit/territory.d.ts +0 -97
- package/dist/kit/territory.d.ts.map +0 -1
- package/dist/kit/territory.js +0 -114
- package/dist/kit/worldsim.d.ts +0 -263
- package/dist/kit/worldsim.d.ts.map +0 -1
- package/dist/kit/worldsim.js +0 -268
- package/dist/stores/model.d.ts +0 -81
- package/dist/stores/model.d.ts.map +0 -1
- package/dist/stores/model.js +0 -163
|
@@ -1,229 +0,0 @@
|
|
|
1
|
-
import { kitPolicyJson, ownerEqualsCaller, ownerMirrorProperty, toSnakeCase, trustedAuthorityFields, } from './core.js';
|
|
2
|
-
/** Compute the type/function names a loot blueprint (and its runtime helper) uses. */
|
|
3
|
-
export function lootNames(typePrefix = '') {
|
|
4
|
-
const fnPrefix = typePrefix ? `${toSnakeCase(typePrefix)}_` : '';
|
|
5
|
-
return {
|
|
6
|
-
rollType: `${typePrefix}LootRoll`,
|
|
7
|
-
claimFn: `${fnPrefix}claim_roll`,
|
|
8
|
-
fnPrefix,
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
|
-
/** The roll function name for one table. */
|
|
12
|
-
export function lootRollFn(tableId, typePrefix = '') {
|
|
13
|
-
const fnPrefix = typePrefix ? `${toSnakeCase(typePrefix)}_` : '';
|
|
14
|
-
return `${fnPrefix}roll_${toSnakeCase(tableId)}`;
|
|
15
|
-
}
|
|
16
|
-
/** Format a probability threshold as an expression float literal. */
|
|
17
|
-
function floatLiteral(n) {
|
|
18
|
-
const s = String(n);
|
|
19
|
-
return s.includes('.') || s.includes('e') ? s : `${s}.0`;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Unroll a weighted table into a nested-if chain over the stored roll seed:
|
|
23
|
-
* `if(self.seed < t1, "a", if(self.seed < t2, "b", "c"))` with cumulative
|
|
24
|
-
* normalized thresholds. Expressions are loop-free, so the selection chain
|
|
25
|
-
* is generated at BUILD time — one `rand()` is stored first and every branch
|
|
26
|
-
* reads that same seed, keeping the distribution exact.
|
|
27
|
-
*/
|
|
28
|
-
function unrollItemChain(entries) {
|
|
29
|
-
const total = entries.reduce((sum, e) => sum + e.weight, 0);
|
|
30
|
-
let expr = JSON.stringify(entries[entries.length - 1].itemId);
|
|
31
|
-
for (let i = entries.length - 2; i >= 0; i--) {
|
|
32
|
-
// Threshold below which entries[0..i] win; nested back-to-front.
|
|
33
|
-
const cumulative = entries.slice(0, i + 1).reduce((sum, e) => sum + e.weight, 0);
|
|
34
|
-
expr = `if(self.seed < ${floatLiteral(cumulative / total)}, ${JSON.stringify(entries[i].itemId)}, ${expr})`;
|
|
35
|
-
}
|
|
36
|
-
return expr;
|
|
37
|
-
}
|
|
38
|
-
/** Unroll the per-item quantity chain, keyed on the already-rolled item id. */
|
|
39
|
-
function unrollQtyChain(entries) {
|
|
40
|
-
const qtyExpr = (e) => {
|
|
41
|
-
const min = e.minQty ?? 1;
|
|
42
|
-
const max = e.maxQty ?? min;
|
|
43
|
-
return max > min ? `rand_int(${min}, ${max})` : String(min);
|
|
44
|
-
};
|
|
45
|
-
let expr = qtyExpr(entries[entries.length - 1]);
|
|
46
|
-
for (let i = entries.length - 2; i >= 0; i--) {
|
|
47
|
-
expr = `if(self.rolled_item_id == ${JSON.stringify(entries[i].itemId)}, ${qtyExpr(entries[i])}, ${expr})`;
|
|
48
|
-
}
|
|
49
|
-
return expr;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Blueprint for **loot tables & drops**: weighted tables are unrolled into
|
|
53
|
-
* pure expressions at blueprint-build time (the expression language is
|
|
54
|
-
* loop-free), rolled server-side into durable `LootRoll` containers, and
|
|
55
|
-
* claimed atomically into an item stack. Covers chest rolls, mob drops, and
|
|
56
|
-
* gacha pulls.
|
|
57
|
-
*
|
|
58
|
-
* Flow: create a `LootRoll` (member) for a table → `roll_<table>` (trusted:
|
|
59
|
-
* server scope by default, or an event automation via `drops`) stores a
|
|
60
|
-
* single `rand()` seed and resolves item + quantity from it in one
|
|
61
|
-
* transaction → the owner claims with `claim_roll`, which marks the roll
|
|
62
|
-
* claimed AND grants the stack in the same invoke (no double-claim, no
|
|
63
|
-
* client-chosen rewards).
|
|
64
|
-
*
|
|
65
|
-
* Runtime counterpart: `client.kit(appId).loot`.
|
|
66
|
-
*/
|
|
67
|
-
export function lootBlueprint(options) {
|
|
68
|
-
const { typePrefix = '', tables, rollAuthority = 'server', drops = [], ownerIdKind: kind = 'int', } = options;
|
|
69
|
-
if (!tables.length) {
|
|
70
|
-
throw new Error('lootBlueprint requires at least one table');
|
|
71
|
-
}
|
|
72
|
-
for (const table of tables) {
|
|
73
|
-
if (table.entries.length < 1 || table.entries.length > 16) {
|
|
74
|
-
throw new Error(`lootBlueprint table '${table.tableId}' must have 1-16 entries (parser-limit-aware unrolling)`);
|
|
75
|
-
}
|
|
76
|
-
if (table.entries.some((e) => !(e.weight > 0))) {
|
|
77
|
-
throw new Error(`lootBlueprint table '${table.tableId}' entries must have weight > 0`);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
const tableIds = new Set(tables.map((t) => t.tableId));
|
|
81
|
-
for (const drop of drops) {
|
|
82
|
-
if (!tableIds.has(drop.tableId)) {
|
|
83
|
-
throw new Error(`lootBlueprint drop '${drop.name}' references unknown table '${drop.tableId}'`);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
const names = lootNames(typePrefix);
|
|
87
|
-
const droppedTables = new Set(drops.map((d) => d.tableId));
|
|
88
|
-
const propertyDefinitions = [
|
|
89
|
-
ownerMirrorProperty(names.rollType, kind),
|
|
90
|
-
{
|
|
91
|
-
containerTypeName: names.rollType,
|
|
92
|
-
key: 'table_id',
|
|
93
|
-
valueType: 'string',
|
|
94
|
-
defaultValueJson: '""',
|
|
95
|
-
description: 'The loot table this roll draws from.',
|
|
96
|
-
},
|
|
97
|
-
{
|
|
98
|
-
containerTypeName: names.rollType,
|
|
99
|
-
key: 'seed',
|
|
100
|
-
valueType: 'float',
|
|
101
|
-
defaultValueJson: '0',
|
|
102
|
-
description: 'The stored rand() seed the weighted selection reads (audit-friendly).',
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
containerTypeName: names.rollType,
|
|
106
|
-
key: 'rolled_item_id',
|
|
107
|
-
valueType: 'string',
|
|
108
|
-
defaultValueJson: '""',
|
|
109
|
-
description: 'The rolled item (empty until rolled; a roll cannot re-roll).',
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
containerTypeName: names.rollType,
|
|
113
|
-
key: 'rolled_qty',
|
|
114
|
-
valueType: 'int',
|
|
115
|
-
defaultValueJson: '0',
|
|
116
|
-
description: 'The rolled quantity.',
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
containerTypeName: names.rollType,
|
|
120
|
-
key: 'claimed',
|
|
121
|
-
valueType: 'bool',
|
|
122
|
-
defaultValueJson: 'false',
|
|
123
|
-
description: 'True once claimed into a stack (single-claim guard).',
|
|
124
|
-
},
|
|
125
|
-
];
|
|
126
|
-
const functions = tables.map((table) => {
|
|
127
|
-
const auth = trustedAuthorityFields(rollAuthority, `self.table_id == ${JSON.stringify(table.tableId)} && self.rolled_item_id == ""`);
|
|
128
|
-
return {
|
|
129
|
-
name: lootRollFn(table.tableId, typePrefix),
|
|
130
|
-
containerTypeName: names.rollType,
|
|
131
|
-
returnType: 'string',
|
|
132
|
-
mutations: [
|
|
133
|
-
{ target: 'self', property: 'seed', expression: 'rand()' },
|
|
134
|
-
{
|
|
135
|
-
target: 'self',
|
|
136
|
-
property: 'rolled_item_id',
|
|
137
|
-
expression: unrollItemChain(table.entries),
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
target: 'self',
|
|
141
|
-
property: 'rolled_qty',
|
|
142
|
-
expression: unrollQtyChain(table.entries),
|
|
143
|
-
},
|
|
144
|
-
],
|
|
145
|
-
returnExpression: 'self.rolled_item_id',
|
|
146
|
-
...auth,
|
|
147
|
-
...(droppedTables.has(table.tableId) ? { autonomousInvocable: true } : {}),
|
|
148
|
-
description: `Roll the '${table.tableId}' loot table: stores one rand() seed, then resolves item and quantity from it in one transaction (unrolled weighted selection).`,
|
|
149
|
-
};
|
|
150
|
-
});
|
|
151
|
-
functions.push({
|
|
152
|
-
name: names.claimFn,
|
|
153
|
-
containerTypeName: names.rollType,
|
|
154
|
-
returnType: 'int',
|
|
155
|
-
parameters: [
|
|
156
|
-
{
|
|
157
|
-
name: 'to_stack_id',
|
|
158
|
-
valueType: 'container_ref',
|
|
159
|
-
required: true,
|
|
160
|
-
description: "A caller-owned stack of the rolled item that receives the quantity.",
|
|
161
|
-
},
|
|
162
|
-
],
|
|
163
|
-
mutations: [
|
|
164
|
-
{ target: 'self', property: 'claimed', expression: 'true' },
|
|
165
|
-
{
|
|
166
|
-
target: 'ref($to_stack_id)',
|
|
167
|
-
property: 'quantity',
|
|
168
|
-
expression: 'ref($to_stack_id).quantity + self.rolled_qty',
|
|
169
|
-
},
|
|
170
|
-
],
|
|
171
|
-
returnExpression: 'self.rolled_qty',
|
|
172
|
-
invokePolicyJson: kitPolicyJson({
|
|
173
|
-
type: 'condition',
|
|
174
|
-
expression: [
|
|
175
|
-
ownerEqualsCaller('self.owner_user_id', kind),
|
|
176
|
-
'not(self.claimed)',
|
|
177
|
-
'self.rolled_item_id != ""',
|
|
178
|
-
'ref($to_stack_id).item_id == self.rolled_item_id',
|
|
179
|
-
ownerEqualsCaller('ref($to_stack_id).owner_user_id', kind),
|
|
180
|
-
].join(' && '),
|
|
181
|
-
}),
|
|
182
|
-
description: 'Claim a rolled loot into a stack: the claimed flag and the grant commit atomically, so a roll can never be claimed twice.',
|
|
183
|
-
});
|
|
184
|
-
const automations = [];
|
|
185
|
-
const automationTriggers = [];
|
|
186
|
-
for (const drop of drops) {
|
|
187
|
-
automations.push({
|
|
188
|
-
name: drop.name,
|
|
189
|
-
functionName: lootRollFn(drop.tableId, typePrefix),
|
|
190
|
-
targetMode: 'type',
|
|
191
|
-
targetTypeName: names.rollType,
|
|
192
|
-
triggerType: 'event',
|
|
193
|
-
maxTargets: drop.maxTargets ?? 1,
|
|
194
|
-
selectorJson: JSON.stringify({
|
|
195
|
-
selfWhere: [
|
|
196
|
-
{ key: 'table_id', op: '==', value: drop.tableId },
|
|
197
|
-
{ key: 'rolled_item_id', op: '==', value: '' },
|
|
198
|
-
],
|
|
199
|
-
pick: 'random',
|
|
200
|
-
}),
|
|
201
|
-
description: `Event-triggered '${drop.tableId}' drop: rolls a pooled unrolled LootRoll when the event fires.`,
|
|
202
|
-
});
|
|
203
|
-
automationTriggers.push({
|
|
204
|
-
automationName: drop.name,
|
|
205
|
-
onEvent: drop.onEvent,
|
|
206
|
-
...(drop.functionName !== undefined ? { functionName: drop.functionName } : {}),
|
|
207
|
-
...(drop.containerTypeName !== undefined
|
|
208
|
-
? { containerTypeName: drop.containerTypeName }
|
|
209
|
-
: {}),
|
|
210
|
-
...(drop.propertyKey !== undefined ? { propertyKey: drop.propertyKey } : {}),
|
|
211
|
-
...(drop.writeSource !== undefined ? { writeSource: drop.writeSource } : {}),
|
|
212
|
-
...(drop.debounceMs !== undefined ? { debounceMs: drop.debounceMs } : {}),
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
return {
|
|
216
|
-
name: names.rollType,
|
|
217
|
-
containerTypes: [
|
|
218
|
-
{
|
|
219
|
-
typeName: names.rollType,
|
|
220
|
-
displayName: names.rollType,
|
|
221
|
-
instantiableBy: 'member',
|
|
222
|
-
description: 'One durable loot roll: seeded, resolved, and claimed entirely server-side.',
|
|
223
|
-
},
|
|
224
|
-
],
|
|
225
|
-
propertyDefinitions,
|
|
226
|
-
functions,
|
|
227
|
-
...(automations.length ? { automations, automationTriggers } : {}),
|
|
228
|
-
};
|
|
229
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { type KitBlueprint, type KitOwnerIdKind, type KitTrustedAuthority } from './core.js';
|
|
2
|
-
/** Options for {@link matchesBlueprint}. */
|
|
3
|
-
export interface MatchesBlueprintOptions {
|
|
4
|
-
/** Prefix for the type/function names. Defaults to none. */
|
|
5
|
-
typePrefix?: string;
|
|
6
|
-
/**
|
|
7
|
-
* Who may submit points to a `Score` row. Defaults to `'host'` (the
|
|
8
|
-
* elected host referees); use `'server'` or `'automation'` for
|
|
9
|
-
* server-refereed modes. Never plain players.
|
|
10
|
-
*/
|
|
11
|
-
scoreAuthority?: KitTrustedAuthority;
|
|
12
|
-
/**
|
|
13
|
-
* When set, gives each turn a real wall-clock deadline. `begin_turn` bumps
|
|
14
|
-
* `turn_seq` and arms a one-shot timer — deduped per match, so arming the
|
|
15
|
-
* next turn replaces the previous deadline — that invokes `expire_turn`
|
|
16
|
-
* after `delayMs`. `expire_turn` records `turn_expired_seq` and pings the
|
|
17
|
-
* match channel, so the current turn is out of time exactly when
|
|
18
|
-
* `turn_expired_seq >= turn_seq`.
|
|
19
|
-
*
|
|
20
|
-
* That comparison is also the stale-fire guard: a timer already claimed
|
|
21
|
-
* when the player beat the clock fires against an older `turn_seq`, writes
|
|
22
|
-
* a lower `turn_expired_seq`, and can never satisfy it.
|
|
23
|
-
*
|
|
24
|
-
* Prefer this to {@link turnTick} — one timer per active match beats
|
|
25
|
-
* re-scanning every match on an interval, and the deadline is exact rather
|
|
26
|
-
* than rounded to the tick.
|
|
27
|
-
*/
|
|
28
|
-
turnTimer?: {
|
|
29
|
-
delayMs: number;
|
|
30
|
-
};
|
|
31
|
-
/**
|
|
32
|
-
* Adds a `turn_tick` interval automation that increments each active
|
|
33
|
-
* match's `tick_count`, so turn logic can compare
|
|
34
|
-
* `tick_count - turn_started_tick >= N`.
|
|
35
|
-
*
|
|
36
|
-
* @deprecated Counters were a workaround for expressions having no
|
|
37
|
-
* `now()`; one-shot timers made that unnecessary. Use {@link turnTimer},
|
|
38
|
-
* which gives an exact deadline and arms work per match instead of
|
|
39
|
-
* scanning all of them every interval. Still honoured, and slated for
|
|
40
|
-
* removal in the next major.
|
|
41
|
-
*/
|
|
42
|
-
turnTick?: {
|
|
43
|
-
intervalMs: number;
|
|
44
|
-
};
|
|
45
|
-
/** Owner-mirror typing (see the kit convention). Defaults to `'int'`. */
|
|
46
|
-
ownerIdKind?: KitOwnerIdKind;
|
|
47
|
-
}
|
|
48
|
-
/** Names derived by {@link matchesBlueprint} for a given prefix. */
|
|
49
|
-
export interface MatchesNames {
|
|
50
|
-
metaType: string;
|
|
51
|
-
scoreType: string;
|
|
52
|
-
startFn: string;
|
|
53
|
-
advanceRoundFn: string;
|
|
54
|
-
scoreFn: string;
|
|
55
|
-
endFn: string;
|
|
56
|
-
beginTurnFn: string;
|
|
57
|
-
expireTurnFn: string;
|
|
58
|
-
turnTickFn: string;
|
|
59
|
-
turnTickAutomation: string;
|
|
60
|
-
}
|
|
61
|
-
/** Compute the type/function names a matches blueprint (and its runtime helper) uses. */
|
|
62
|
-
export declare function matchesNames(typePrefix?: string): MatchesNames;
|
|
63
|
-
/**
|
|
64
|
-
* Blueprint for **matches** (lobbies, rounds, turns, scoring) — the
|
|
65
|
-
* session-layer wrapper every session game needs. Sessions ARE the match
|
|
66
|
-
* primitive (participants + current turn); this adds a session-scoped
|
|
67
|
-
* `MatchMeta` (lobby state, round, winner, notification channel) and
|
|
68
|
-
* per-player `Score` rows.
|
|
69
|
-
*
|
|
70
|
-
* Lifecycle functions (`start_match` / `advance_round` / `end_match`) are
|
|
71
|
-
* creator-or-host gated and declare a **channel notification** — Buddy pings
|
|
72
|
-
* every channel member post-commit with `"match_changed"`, and clients
|
|
73
|
-
* re-pull the meta (the notify-to-pull pattern; `kit.matches.onMatchChanged`
|
|
74
|
-
* wraps the subscription). Turn order itself uses the platform's
|
|
75
|
-
* `gameModelSetSessionTurn` (the runtime helper calls it — turn authority is
|
|
76
|
-
* enforced by the service, not an expression).
|
|
77
|
-
*
|
|
78
|
-
* End-of-match hooks: attach an event automation to `end_match`
|
|
79
|
-
* (`function_invoked`) to submit leaderboard scores or adjust ratings.
|
|
80
|
-
*
|
|
81
|
-
* Runtime counterpart: `client.kit(appId).matches`.
|
|
82
|
-
*/
|
|
83
|
-
export declare function matchesBlueprint(options?: MatchesBlueprintOptions): KitBlueprint;
|
|
84
|
-
//# sourceMappingURL=matches.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"matches.d.ts","sourceRoot":"","sources":["../../../src/kit/blueprints/matches.ts"],"names":[],"mappings":"AAMA,OAAO,EAML,KAAK,YAAY,EAEjB,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACzB,MAAM,WAAW,CAAC;AAEnB,4CAA4C;AAC5C,MAAM,WAAW,uBAAuB;IACtC,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,cAAc,CAAC,EAAE,mBAAmB,CAAC;IACrC;;;;;;;;;;;;;;;OAeG;IACH,SAAS,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAChC;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IAClC,yEAAyE;IACzE,WAAW,CAAC,EAAE,cAAc,CAAC;CAC9B;AAED,oEAAoE;AACpE,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,yFAAyF;AACzF,wBAAgB,YAAY,CAAC,UAAU,SAAK,GAAG,YAAY,CAc1D;AA4BD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,GAAE,uBAA4B,GAAG,YAAY,CAuTpF"}
|
|
@@ -1,349 +0,0 @@
|
|
|
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
|
-
beginTurnFn: `${fnPrefix}begin_turn`,
|
|
13
|
-
expireTurnFn: `${fnPrefix}expire_turn`,
|
|
14
|
-
turnTickFn: `${fnPrefix}turn_tick`,
|
|
15
|
-
turnTickAutomation: `${fnPrefix.replace(/_/g, '-')}match-turn-tick`,
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
/** The channel ping every lifecycle function emits post-commit (notify-to-pull). */
|
|
19
|
-
function matchChangedNotification() {
|
|
20
|
-
return {
|
|
21
|
-
kind: 'channel',
|
|
22
|
-
args: [
|
|
23
|
-
{ name: 'channel_id', expression: 'self.channel_id' },
|
|
24
|
-
{ name: 'payload', expression: '"match_changed"' },
|
|
25
|
-
],
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* The one-shot deadline a turn arms for itself. Deduped per match container,
|
|
30
|
-
* so opening the next turn replaces the pending deadline rather than stacking
|
|
31
|
-
* another one; the seq travels as a parameter so the fire can tell whether the
|
|
32
|
-
* turn it was armed for is still open.
|
|
33
|
-
*/
|
|
34
|
-
function turnDeadlineTimer(names, delayMs) {
|
|
35
|
-
return {
|
|
36
|
-
functionName: names.expireTurnFn,
|
|
37
|
-
delayMsExpression: String(delayMs),
|
|
38
|
-
dedupeKeyExpression: 'concat("match_turn:", $self_container_id)',
|
|
39
|
-
params: [{ name: 'turn_seq', expression: 'self.turn_seq' }],
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Blueprint for **matches** (lobbies, rounds, turns, scoring) — the
|
|
44
|
-
* session-layer wrapper every session game needs. Sessions ARE the match
|
|
45
|
-
* primitive (participants + current turn); this adds a session-scoped
|
|
46
|
-
* `MatchMeta` (lobby state, round, winner, notification channel) and
|
|
47
|
-
* per-player `Score` rows.
|
|
48
|
-
*
|
|
49
|
-
* Lifecycle functions (`start_match` / `advance_round` / `end_match`) are
|
|
50
|
-
* creator-or-host gated and declare a **channel notification** — Buddy pings
|
|
51
|
-
* every channel member post-commit with `"match_changed"`, and clients
|
|
52
|
-
* re-pull the meta (the notify-to-pull pattern; `kit.matches.onMatchChanged`
|
|
53
|
-
* wraps the subscription). Turn order itself uses the platform's
|
|
54
|
-
* `gameModelSetSessionTurn` (the runtime helper calls it — turn authority is
|
|
55
|
-
* enforced by the service, not an expression).
|
|
56
|
-
*
|
|
57
|
-
* End-of-match hooks: attach an event automation to `end_match`
|
|
58
|
-
* (`function_invoked`) to submit leaderboard scores or adjust ratings.
|
|
59
|
-
*
|
|
60
|
-
* Runtime counterpart: `client.kit(appId).matches`.
|
|
61
|
-
*/
|
|
62
|
-
export function matchesBlueprint(options = {}) {
|
|
63
|
-
const { typePrefix = '', scoreAuthority = 'host', turnTimer, turnTick, ownerIdKind: kind = 'int', } = options;
|
|
64
|
-
const names = matchesNames(typePrefix);
|
|
65
|
-
const creatorOrHost = (stateCondition) => {
|
|
66
|
-
const rules = [
|
|
67
|
-
{
|
|
68
|
-
type: 'or',
|
|
69
|
-
rules: [
|
|
70
|
-
{ type: 'is_host' },
|
|
71
|
-
{ type: 'condition', expression: 'self.creator_user_id == $caller_user_id' },
|
|
72
|
-
],
|
|
73
|
-
},
|
|
74
|
-
{ type: 'condition', expression: stateCondition },
|
|
75
|
-
];
|
|
76
|
-
return kitPolicyJson({ type: 'and', rules });
|
|
77
|
-
};
|
|
78
|
-
const propertyDefinitions = [
|
|
79
|
-
{
|
|
80
|
-
containerTypeName: names.metaType,
|
|
81
|
-
key: 'creator_user_id',
|
|
82
|
-
valueType: 'int',
|
|
83
|
-
defaultValueJson: '0',
|
|
84
|
-
description: 'The user who created the match (may start/advance/end it).',
|
|
85
|
-
},
|
|
86
|
-
{
|
|
87
|
-
containerTypeName: names.metaType,
|
|
88
|
-
key: 'mode',
|
|
89
|
-
valueType: 'string',
|
|
90
|
-
defaultValueJson: '""',
|
|
91
|
-
description: "App-defined mode label (e.g. 'ranked', 'ffa').",
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
containerTypeName: names.metaType,
|
|
95
|
-
key: 'state',
|
|
96
|
-
valueType: 'string',
|
|
97
|
-
defaultValueJson: '"lobby"',
|
|
98
|
-
description: "Match lifecycle: 'lobby' | 'active' | 'finished'.",
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
containerTypeName: names.metaType,
|
|
102
|
-
key: 'round',
|
|
103
|
-
valueType: 'int',
|
|
104
|
-
defaultValueJson: '0',
|
|
105
|
-
description: 'Current round (0 in the lobby).',
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
containerTypeName: names.metaType,
|
|
109
|
-
key: 'max_players',
|
|
110
|
-
valueType: 'int',
|
|
111
|
-
defaultValueJson: '0',
|
|
112
|
-
description: 'Advertised player cap (0 = unlimited).',
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
containerTypeName: names.metaType,
|
|
116
|
-
key: 'winner_user_id',
|
|
117
|
-
valueType: 'int',
|
|
118
|
-
defaultValueJson: '0',
|
|
119
|
-
description: 'Set by end_match (0 while unresolved).',
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
containerTypeName: names.metaType,
|
|
123
|
-
key: 'channel_id',
|
|
124
|
-
valueType: 'int',
|
|
125
|
-
defaultValueJson: '0',
|
|
126
|
-
description: 'The per-match channel lifecycle notifications ping (notify-to-pull).',
|
|
127
|
-
},
|
|
128
|
-
...(turnTimer
|
|
129
|
-
? [
|
|
130
|
-
{
|
|
131
|
-
containerTypeName: names.metaType,
|
|
132
|
-
key: 'turn_seq',
|
|
133
|
-
valueType: 'int',
|
|
134
|
-
defaultValueJson: '0',
|
|
135
|
-
description: 'Monotonic turn counter bumped by begin_turn; each turn arms a deadline tagged with this value.',
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
containerTypeName: names.metaType,
|
|
139
|
-
key: 'turn_expired_seq',
|
|
140
|
-
valueType: 'int',
|
|
141
|
-
defaultValueJson: '0',
|
|
142
|
-
description: 'Highest turn_seq whose deadline elapsed. The turn is out of time iff turn_expired_seq >= turn_seq.',
|
|
143
|
-
},
|
|
144
|
-
]
|
|
145
|
-
: []),
|
|
146
|
-
...(turnTick
|
|
147
|
-
? [
|
|
148
|
-
{
|
|
149
|
-
containerTypeName: names.metaType,
|
|
150
|
-
key: 'tick_count',
|
|
151
|
-
valueType: 'int',
|
|
152
|
-
defaultValueJson: '0',
|
|
153
|
-
description: 'Monotonic counter bumped by the turn-tick automation (the wall-clock-free timer).',
|
|
154
|
-
},
|
|
155
|
-
]
|
|
156
|
-
: []),
|
|
157
|
-
ownerMirrorProperty(names.scoreType, kind),
|
|
158
|
-
{
|
|
159
|
-
containerTypeName: names.scoreType,
|
|
160
|
-
key: 'points',
|
|
161
|
-
valueType: 'int',
|
|
162
|
-
defaultValueJson: '0',
|
|
163
|
-
description: "One player's score in the match.",
|
|
164
|
-
},
|
|
165
|
-
];
|
|
166
|
-
// expire_turn is declared before anything arms it, so the seed resolves the
|
|
167
|
-
// timer target in one pass instead of warning about a forward reference.
|
|
168
|
-
const expireTurnFunction = turnTimer
|
|
169
|
-
? [
|
|
170
|
-
{
|
|
171
|
-
name: names.expireTurnFn,
|
|
172
|
-
containerTypeName: names.metaType,
|
|
173
|
-
returnType: 'int',
|
|
174
|
-
parameters: [
|
|
175
|
-
{
|
|
176
|
-
name: 'turn_seq',
|
|
177
|
-
valueType: 'int',
|
|
178
|
-
required: true,
|
|
179
|
-
description: 'The turn this deadline was armed for.',
|
|
180
|
-
},
|
|
181
|
-
],
|
|
182
|
-
mutations: [
|
|
183
|
-
{
|
|
184
|
-
target: 'self',
|
|
185
|
-
property: 'turn_expired_seq',
|
|
186
|
-
expression: 'max(self.turn_expired_seq, $turn_seq)',
|
|
187
|
-
},
|
|
188
|
-
],
|
|
189
|
-
returnExpression: 'self.turn_expired_seq',
|
|
190
|
-
invokePolicyJson: kitPolicyJson({ type: 'is_automation' }),
|
|
191
|
-
autonomousInvocable: true,
|
|
192
|
-
notifications: [matchChangedNotification()],
|
|
193
|
-
description: 'Fired by the turn deadline: record that turn $turn_seq ran out and ping the match channel. max() keeps turn_expired_seq monotonic, so a deadline that was already claimed when the player beat the clock lands below turn_seq and is ignored.',
|
|
194
|
-
},
|
|
195
|
-
]
|
|
196
|
-
: [];
|
|
197
|
-
const functions = [
|
|
198
|
-
...expireTurnFunction,
|
|
199
|
-
{
|
|
200
|
-
name: names.startFn,
|
|
201
|
-
containerTypeName: names.metaType,
|
|
202
|
-
returnType: 'string',
|
|
203
|
-
mutations: [
|
|
204
|
-
{ target: 'self', property: 'state', expression: '"active"' },
|
|
205
|
-
{ target: 'self', property: 'round', expression: '1' },
|
|
206
|
-
...(turnTimer
|
|
207
|
-
? [{ target: 'self', property: 'turn_seq', expression: '1' }]
|
|
208
|
-
: []),
|
|
209
|
-
],
|
|
210
|
-
returnExpression: 'self.state',
|
|
211
|
-
...(turnTimer ? { timers: [turnDeadlineTimer(names, turnTimer.delayMs)] } : {}),
|
|
212
|
-
invokePolicyJson: creatorOrHost('self.state == "lobby"'),
|
|
213
|
-
notifications: [matchChangedNotification()],
|
|
214
|
-
description: turnTimer
|
|
215
|
-
? 'Start the match (creator or host): lobby → active, round 1, and arm the first turn’s deadline; pings the match channel post-commit.'
|
|
216
|
-
: 'Start the match (creator or host): lobby → active, round 1; pings the match channel post-commit.',
|
|
217
|
-
},
|
|
218
|
-
{
|
|
219
|
-
name: names.advanceRoundFn,
|
|
220
|
-
containerTypeName: names.metaType,
|
|
221
|
-
returnType: 'int',
|
|
222
|
-
mutations: [{ target: 'self', property: 'round', expression: 'self.round + 1' }],
|
|
223
|
-
returnExpression: 'self.round',
|
|
224
|
-
invokePolicyJson: creatorOrHost('self.state == "active"'),
|
|
225
|
-
notifications: [matchChangedNotification()],
|
|
226
|
-
description: 'Advance to the next round (creator or host); pings the match channel post-commit.',
|
|
227
|
-
},
|
|
228
|
-
{
|
|
229
|
-
name: names.endFn,
|
|
230
|
-
containerTypeName: names.metaType,
|
|
231
|
-
returnType: 'string',
|
|
232
|
-
parameters: [
|
|
233
|
-
{
|
|
234
|
-
name: 'winner_user_id',
|
|
235
|
-
valueType: 'int',
|
|
236
|
-
required: true,
|
|
237
|
-
description: 'The winning user (0 for a draw).',
|
|
238
|
-
},
|
|
239
|
-
],
|
|
240
|
-
mutations: [
|
|
241
|
-
{ target: 'self', property: 'state', expression: '"finished"' },
|
|
242
|
-
{ target: 'self', property: 'winner_user_id', expression: '$winner_user_id' },
|
|
243
|
-
// Advancing the seq strands any deadline still pending: it was armed
|
|
244
|
-
// for the previous turn, so its write can no longer reach turn_seq and
|
|
245
|
-
// a finished match never reads as out of time.
|
|
246
|
-
...(turnTimer
|
|
247
|
-
? [{ target: 'self', property: 'turn_seq', expression: 'self.turn_seq + 1' }]
|
|
248
|
-
: []),
|
|
249
|
-
],
|
|
250
|
-
returnExpression: 'self.state',
|
|
251
|
-
invokePolicyJson: creatorOrHost('self.state == "active"'),
|
|
252
|
-
notifications: [matchChangedNotification()],
|
|
253
|
-
description: 'Finish the match and record the winner (creator or host); attach an event automation to this function for rating/leaderboard hooks.',
|
|
254
|
-
},
|
|
255
|
-
{
|
|
256
|
-
name: names.scoreFn,
|
|
257
|
-
containerTypeName: names.scoreType,
|
|
258
|
-
returnType: 'int',
|
|
259
|
-
parameters: [
|
|
260
|
-
{
|
|
261
|
-
name: 'points',
|
|
262
|
-
valueType: 'int',
|
|
263
|
-
required: true,
|
|
264
|
-
description: 'Signed points to add.',
|
|
265
|
-
},
|
|
266
|
-
],
|
|
267
|
-
mutations: [
|
|
268
|
-
{ target: 'self', property: 'points', expression: 'self.points + $points' },
|
|
269
|
-
],
|
|
270
|
-
returnExpression: 'self.points',
|
|
271
|
-
...trustedAuthorityFields(scoreAuthority),
|
|
272
|
-
description: "Add points to a player's Score row — trusted (host-refereed by default).",
|
|
273
|
-
},
|
|
274
|
-
];
|
|
275
|
-
if (turnTimer) {
|
|
276
|
-
functions.push({
|
|
277
|
-
name: names.beginTurnFn,
|
|
278
|
-
containerTypeName: names.metaType,
|
|
279
|
-
returnType: 'int',
|
|
280
|
-
mutations: [{ target: 'self', property: 'turn_seq', expression: 'self.turn_seq + 1' }],
|
|
281
|
-
returnExpression: 'self.turn_seq',
|
|
282
|
-
timers: [turnDeadlineTimer(names, turnTimer.delayMs)],
|
|
283
|
-
invokePolicyJson: kitPolicyJson({
|
|
284
|
-
type: 'and',
|
|
285
|
-
rules: [
|
|
286
|
-
{
|
|
287
|
-
type: 'or',
|
|
288
|
-
rules: [
|
|
289
|
-
{ type: 'is_host' },
|
|
290
|
-
{ type: 'condition', expression: 'self.creator_user_id == $caller_user_id' },
|
|
291
|
-
{ type: 'condition', expression: '$current_turn_user_id == $caller_user_id' },
|
|
292
|
-
],
|
|
293
|
-
},
|
|
294
|
-
{ type: 'condition', expression: 'self.state == "active"' },
|
|
295
|
-
],
|
|
296
|
-
}),
|
|
297
|
-
description: "Open a turn: bump turn_seq and arm this turn's deadline (host, creator, or the outgoing turn holder). Call it before handing the turn over so the incoming player is never left without a clock.",
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
const automations = [];
|
|
301
|
-
if (turnTick) {
|
|
302
|
-
functions.push({
|
|
303
|
-
name: names.turnTickFn,
|
|
304
|
-
containerTypeName: names.metaType,
|
|
305
|
-
returnType: 'int',
|
|
306
|
-
mutations: [
|
|
307
|
-
{ target: 'self', property: 'tick_count', expression: 'self.tick_count + 1' },
|
|
308
|
-
],
|
|
309
|
-
returnExpression: 'self.tick_count',
|
|
310
|
-
invokePolicyJson: kitPolicyJson({ type: 'is_automation' }),
|
|
311
|
-
autonomousInvocable: true,
|
|
312
|
-
description: 'Server-driven timer tick for active matches (counters instead of wall clocks — expressions have no now()).',
|
|
313
|
-
});
|
|
314
|
-
automations.push({
|
|
315
|
-
name: names.turnTickAutomation,
|
|
316
|
-
functionName: names.turnTickFn,
|
|
317
|
-
targetMode: 'type',
|
|
318
|
-
targetTypeName: names.metaType,
|
|
319
|
-
triggerType: 'schedule',
|
|
320
|
-
scheduleKind: 'interval',
|
|
321
|
-
intervalMs: turnTick.intervalMs,
|
|
322
|
-
maxTargets: 64,
|
|
323
|
-
selectorJson: JSON.stringify({
|
|
324
|
-
selfWhere: [{ key: 'state', op: '==', value: 'active' }],
|
|
325
|
-
}),
|
|
326
|
-
description: 'Bumps tick_count on active matches (turn-timeout timer source).',
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
return {
|
|
330
|
-
name: names.metaType,
|
|
331
|
-
containerTypes: [
|
|
332
|
-
{
|
|
333
|
-
typeName: names.metaType,
|
|
334
|
-
displayName: names.metaType,
|
|
335
|
-
instantiableBy: 'member',
|
|
336
|
-
description: 'Session-scoped match record: lobby state, round, winner, notification channel.',
|
|
337
|
-
},
|
|
338
|
-
{
|
|
339
|
-
typeName: names.scoreType,
|
|
340
|
-
displayName: names.scoreType,
|
|
341
|
-
instantiableBy: 'member',
|
|
342
|
-
description: "One player's session-scoped score row.",
|
|
343
|
-
},
|
|
344
|
-
],
|
|
345
|
-
propertyDefinitions,
|
|
346
|
-
functions,
|
|
347
|
-
...(automations.length ? { automations } : {}),
|
|
348
|
-
};
|
|
349
|
-
}
|