@kradle/challenges-sdk 0.10.0 → 0.11.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/CHANGELOG.md +32 -1
- package/LLM_README.md +235 -2333
- package/README.md +151 -570
- package/dist/index.d.ts +1 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -6
- package/dist/index.js.map +1 -1
- package/dist/runtime/examples/capture-the-flag.d.ts +33 -0
- package/dist/runtime/examples/capture-the-flag.d.ts.map +1 -0
- package/dist/runtime/examples/capture-the-flag.js +79 -0
- package/dist/runtime/examples/capture-the-flag.js.map +1 -0
- package/dist/runtime/examples/climb.d.ts +34 -0
- package/dist/runtime/examples/climb.d.ts.map +1 -0
- package/dist/runtime/examples/climb.js +54 -0
- package/dist/runtime/examples/climb.js.map +1 -0
- package/dist/runtime/examples/four-bridges.d.ts +107 -0
- package/dist/runtime/examples/four-bridges.d.ts.map +1 -0
- package/dist/runtime/examples/four-bridges.js +294 -0
- package/dist/runtime/examples/four-bridges.js.map +1 -0
- package/dist/runtime/examples/herding.d.ts +59 -0
- package/dist/runtime/examples/herding.d.ts.map +1 -0
- package/dist/runtime/examples/herding.js +181 -0
- package/dist/runtime/examples/herding.js.map +1 -0
- package/dist/runtime/examples/skywars.d.ts +116 -0
- package/dist/runtime/examples/skywars.d.ts.map +1 -0
- package/dist/runtime/examples/skywars.js +253 -0
- package/dist/runtime/examples/skywars.js.map +1 -0
- package/dist/runtime/harness.d.ts +176 -0
- package/dist/runtime/harness.d.ts.map +1 -0
- package/dist/runtime/harness.js +441 -0
- package/dist/runtime/harness.js.map +1 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +18 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/sandbox-registry.d.ts +7 -0
- package/dist/runtime/sandbox-registry.d.ts.map +1 -0
- package/dist/runtime/sandbox-registry.js +16 -0
- package/dist/runtime/sandbox-registry.js.map +1 -0
- package/dist/runtime/sandbox.d.ts +29 -0
- package/dist/runtime/sandbox.d.ts.map +1 -0
- package/dist/runtime/sandbox.js +70 -0
- package/dist/runtime/sandbox.js.map +1 -0
- package/dist/runtime/sdk.d.ts +1038 -0
- package/dist/runtime/sdk.d.ts.map +1 -0
- package/dist/runtime/sdk.js +1271 -0
- package/dist/runtime/sdk.js.map +1 -0
- package/dist/runtime/world.d.ts +141 -0
- package/dist/runtime/world.d.ts.map +1 -0
- package/dist/runtime/world.js +344 -0
- package/dist/runtime/world.js.map +1 -0
- package/package.json +76 -40
- package/dist/actions.d.ts +0 -901
- package/dist/actions.d.ts.map +0 -1
- package/dist/actions.js +0 -1155
- package/dist/actions.js.map +0 -1
- package/dist/api_utils.d.ts +0 -25
- package/dist/api_utils.d.ts.map +0 -1
- package/dist/api_utils.js +0 -48
- package/dist/api_utils.js.map +0 -1
- package/dist/challenge.d.ts +0 -58
- package/dist/challenge.d.ts.map +0 -1
- package/dist/challenge.js +0 -582
- package/dist/challenge.js.map +0 -1
- package/dist/commands.d.ts +0 -57
- package/dist/commands.d.ts.map +0 -1
- package/dist/commands.js +0 -200
- package/dist/commands.js.map +0 -1
- package/dist/constants.d.ts +0 -78
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js +0 -109
- package/dist/constants.js.map +0 -1
- package/dist/testmode.d.ts +0 -3
- package/dist/testmode.d.ts.map +0 -1
- package/dist/testmode.js +0 -19
- package/dist/testmode.js.map +0 -1
- package/dist/types.d.ts +0 -195
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -38
- package/dist/types.js.map +0 -1
- package/dist/utils.d.ts +0 -17
- package/dist/utils.d.ts.map +0 -1
- package/dist/utils.js +0 -22
- package/dist/utils.js.map +0 -1
package/dist/actions.js
DELETED
|
@@ -1,1155 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.Actions = void 0;
|
|
7
|
-
exports.mapTarget = mapTarget;
|
|
8
|
-
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
9
|
-
const sandstone_1 = require("sandstone");
|
|
10
|
-
const commands_1 = require("./commands");
|
|
11
|
-
const constants_1 = require("./constants");
|
|
12
|
-
function mapTarget(target) {
|
|
13
|
-
switch (target) {
|
|
14
|
-
case "all":
|
|
15
|
-
return constants_1.ALL;
|
|
16
|
-
case "self":
|
|
17
|
-
return "@s";
|
|
18
|
-
}
|
|
19
|
-
if (target instanceof sandstone_1.SelectorClass) {
|
|
20
|
-
return target;
|
|
21
|
-
}
|
|
22
|
-
// Raw selectors ("@s", "@a", "@p", "@e", "@r") are a footgun: they fall
|
|
23
|
-
// through to the role-tag branch as Selector("@a", { tag: "@s" }) and
|
|
24
|
-
// silently match nobody. Require "self", "all", a role name, or a Selector.
|
|
25
|
-
if (target.startsWith("@")) {
|
|
26
|
-
throw new Error(`mapTarget: raw selector "${target}" is not allowed — use "self", "all", a role name, or a Selector(...).`);
|
|
27
|
-
}
|
|
28
|
-
// check if the target is a valid entity type
|
|
29
|
-
if (target.startsWith("minecraft:")) {
|
|
30
|
-
return (0, sandstone_1.Selector)("@e", { type: target });
|
|
31
|
-
}
|
|
32
|
-
return (0, sandstone_1.Selector)("@a", { tag: target }); // Treat unknown string targets as a role name (roles are stored as player tags)
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Throws if `voteId` — or, when given, `option` — isn't declared in config.ts's
|
|
36
|
-
* `challengeConfig.votingOptions` (passed at build time as
|
|
37
|
-
* `KRADLE_CHALLENGE_VOTING_OPTIONS`), mirroring `getLocation`/`setEndState`.
|
|
38
|
-
* No-op when the env var is unset (e.g. running `challenge.ts` outside the CLI).
|
|
39
|
-
*/
|
|
40
|
-
function assertDeclaredVote(voteId, option) {
|
|
41
|
-
const declared = process.env.KRADLE_CHALLENGE_VOTING_OPTIONS;
|
|
42
|
-
if (!declared)
|
|
43
|
-
return;
|
|
44
|
-
const votes = JSON.parse(declared);
|
|
45
|
-
const options = votes[voteId];
|
|
46
|
-
if (!options) {
|
|
47
|
-
throw new Error(`Invalid vote "${voteId}". Valid votes are: ${Object.keys(votes).join(", ")}. ` +
|
|
48
|
-
`Make sure the vote is defined in config.ts under "challengeConfig.votingOptions".`);
|
|
49
|
-
}
|
|
50
|
-
if (option !== undefined && !options.includes(option)) {
|
|
51
|
-
throw new Error(`Invalid option "${option}" for vote "${voteId}". Valid options are: ${options.join(", ")}.`);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Selects participants who haven't voted in `voteId` yet (missing the
|
|
56
|
-
* `kradle.voteOptions.<voteId>` tag). The multi-tag, `!`-negated form needs
|
|
57
|
-
* `as never` because Sandstone's Selector `tag` type doesn't accept a string[]
|
|
58
|
-
* (same workaround as api_utils.ts).
|
|
59
|
-
*/
|
|
60
|
-
function participantsMissingVote(voteId) {
|
|
61
|
-
return (0, sandstone_1.Selector)("@a", { tag: [constants_1.KRADLE_PARTICIPANT_TAG, `!kradle.voteOptions.${voteId}`] });
|
|
62
|
-
}
|
|
63
|
-
exports.Actions = {
|
|
64
|
-
/**
|
|
65
|
-
* Send a chat message to everyone.
|
|
66
|
-
* @param {JSONTextComponent} message - The message to send.
|
|
67
|
-
*/
|
|
68
|
-
announce: ({ message }) => {
|
|
69
|
-
(0, sandstone_1.tellraw)("@a", ["\n", { text: constants_1.DISPLAY_TAG, color: "aqua" }, " | ", message, "\n"]);
|
|
70
|
-
},
|
|
71
|
-
/**
|
|
72
|
-
* Pick `count` random participants and tag them. Used to assign hidden
|
|
73
|
-
* roles in social-deduction challenges — combine with `Actions.tellraw` to
|
|
74
|
-
* privately notify each tagged participant of their assignment.
|
|
75
|
-
*
|
|
76
|
-
* Uses Minecraft's `sort=random,limit=N` selector behavior. The selection
|
|
77
|
-
* happens at command time (typically `start_challenge`), so it's stable
|
|
78
|
-
* for the rest of the run.
|
|
79
|
-
*
|
|
80
|
-
* @example Saboteur assignment in a 4-player game:
|
|
81
|
-
* ```ts
|
|
82
|
-
* start_challenge: () => {
|
|
83
|
-
* Actions.assignRandomTag({ tag: "saboteur", count: 1 });
|
|
84
|
-
* // Then DM each tagged participant their secret role:
|
|
85
|
-
* Actions.tellraw({
|
|
86
|
-
* target: Selector("@a", { tag: "saboteur" }),
|
|
87
|
-
* message: [{ text: "🔪 You're the saboteur. Stop the miners.", color: "red" }],
|
|
88
|
-
* });
|
|
89
|
-
* }
|
|
90
|
-
* ```
|
|
91
|
-
*
|
|
92
|
-
* `count` is capped to the number of participants — over-asking just tags
|
|
93
|
-
* everyone available.
|
|
94
|
-
*/
|
|
95
|
-
assignRandomTag: ({ tag: tagName, count }) => {
|
|
96
|
-
sandstone_1.execute.as((0, sandstone_1.Selector)("@a", { tag: constants_1.KRADLE_PARTICIPANT_TAG, sort: "random", limit: count })).run(() => {
|
|
97
|
-
(0, sandstone_1.tag)("@s").add(tagName);
|
|
98
|
-
});
|
|
99
|
-
},
|
|
100
|
-
/**
|
|
101
|
-
* Pick a uniform random integer in `[min, max]` (both inclusive) and
|
|
102
|
-
* return it as an anonymous `Score`. Wraps Minecraft's `random value`
|
|
103
|
-
* command, which Sandstone does not expose as a typed primitive — this
|
|
104
|
-
* helper is the abstraction so that user code never has to write `raw`
|
|
105
|
-
* for randomness.
|
|
106
|
-
*
|
|
107
|
-
* Use to randomize challenge state at `start_challenge` (which goal block
|
|
108
|
-
* is "the" goal, which player a saboteur is paired with for a side-quest,
|
|
109
|
-
* etc.) so behavior isn't gameable across runs.
|
|
110
|
-
*
|
|
111
|
-
* @example Pick one of 4 goal directions per run:
|
|
112
|
-
* ```ts
|
|
113
|
-
* goal_index: { type: "global", default: 0 },
|
|
114
|
-
*
|
|
115
|
-
* start_challenge: () => {
|
|
116
|
-
* variables.goal_index.score.set(Actions.randomInt({ min: 0, max: 3 }));
|
|
117
|
-
* // Then branch: _.if(goal_index.equalTo(0), () => place at N), etc.
|
|
118
|
-
* }
|
|
119
|
-
* ```
|
|
120
|
-
*/
|
|
121
|
-
randomInt: ({ min, max }) => {
|
|
122
|
-
const result = (0, sandstone_1.Variable)(0);
|
|
123
|
-
// Sandstone has no typed wrapper for `random value <min>..<max>`.
|
|
124
|
-
// This is the single legitimate `raw` in the SDK — encapsulating it
|
|
125
|
-
// here means user code never has to. Once Sandstone gains the
|
|
126
|
-
// command, swap the body for `execute.store.result.score(result).run.random.value(...)`.
|
|
127
|
-
(0, sandstone_1.raw)(`execute store result score ${result.target} ${result.objective.name} run random value ${min}..${max}`);
|
|
128
|
-
return result;
|
|
129
|
-
},
|
|
130
|
-
/**
|
|
131
|
-
* Clear the inventory of a target.
|
|
132
|
-
* @param {TargetNames} target - The target to clear the inventory of.
|
|
133
|
-
*/
|
|
134
|
-
clear: ({ target }) => {
|
|
135
|
-
(0, sandstone_1.clear)(mapTarget(target));
|
|
136
|
-
},
|
|
137
|
-
/**
|
|
138
|
-
* Fill a region with a block.
|
|
139
|
-
* @param {BLOCKS} block - The block to fill the region with.
|
|
140
|
-
* @param {number} x1 - The x coordinate of the region.
|
|
141
|
-
* @param {number} y1 - The y coordinate of the region.
|
|
142
|
-
* @param {number} z1 - The z coordinate of the region.
|
|
143
|
-
* @param {number} x2 - The x coordinate of the region.
|
|
144
|
-
* @param {number} y2 - The y coordinate of the region.
|
|
145
|
-
* @param {number} z2 - The z coordinate of the region.
|
|
146
|
-
* @param {boolean} absolute - Whether the coordinates are absolute or relative.
|
|
147
|
-
* @param {"fill" | "line" | "pyramid"} mode - Required. How to fill the region: `"fill"` (solid box between the two points), `"line"` (1-wide line from point 1 to point 2), or `"pyramid"` (pyramid `|y2|` blocks tall, stepping in by 2 per layer). Omitting it throws `Invalid fill mode` at build time.
|
|
148
|
-
*/
|
|
149
|
-
fill: ({ block, x1, y1, z1, x2, y2, z2, absolute, mode, }) => {
|
|
150
|
-
// fill the region with the block
|
|
151
|
-
if (mode === "fill") {
|
|
152
|
-
const coordinates1 = absolute ? (0, sandstone_1.abs)(x1, y1, z1) : (0, sandstone_1.rel)(x1, y1, z1);
|
|
153
|
-
const coordinates2 = absolute ? (0, sandstone_1.abs)(x2, y2, z2) : (0, sandstone_1.rel)(x2, y2, z2);
|
|
154
|
-
(0, sandstone_1.fill)(coordinates1, coordinates2, block);
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
// draw a line from the first coordinate to the second coordinate
|
|
158
|
-
if (mode === "line") {
|
|
159
|
-
const dx = x2 - x1;
|
|
160
|
-
const dy = y2 - y1;
|
|
161
|
-
const dz = z2 - z1;
|
|
162
|
-
const length = Math.max(Math.abs(dx), Math.abs(dy), Math.abs(dz));
|
|
163
|
-
const stepX = dx / length || 0;
|
|
164
|
-
const stepY = dy / length || 0;
|
|
165
|
-
const stepZ = dz / length || 0;
|
|
166
|
-
for (let i = 0; i <= length; i++) {
|
|
167
|
-
const x = Math.round(x1 + stepX * i);
|
|
168
|
-
const y = Math.round(y1 + stepY * i);
|
|
169
|
-
const z = Math.round(z1 + stepZ * i);
|
|
170
|
-
(0, sandstone_1.setblock)(absolute ? (0, sandstone_1.abs)(x, y, z) : (0, sandstone_1.rel)(x, y, z), block);
|
|
171
|
-
}
|
|
172
|
-
return;
|
|
173
|
-
}
|
|
174
|
-
if (mode === "pyramid") {
|
|
175
|
-
const height = Math.abs(y2);
|
|
176
|
-
const direction = Math.sign(y2); // +1 for up, -1 for down
|
|
177
|
-
for (let i = 0; i < height; i++) {
|
|
178
|
-
const y = y1 + i * direction;
|
|
179
|
-
const layerRadius = (height - 1 - i) * 2;
|
|
180
|
-
const minX = x1 - layerRadius;
|
|
181
|
-
const maxX = x1 + layerRadius;
|
|
182
|
-
const minZ = z1 - layerRadius;
|
|
183
|
-
const maxZ = z1 + layerRadius;
|
|
184
|
-
for (let x = minX; x <= maxX; x++) {
|
|
185
|
-
for (let z = minZ; z <= maxZ; z++) {
|
|
186
|
-
(0, sandstone_1.setblock)(absolute ? (0, sandstone_1.abs)(x, y, z) : (0, sandstone_1.rel)(x, y, z), block);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
// if mode is not fill or line, throw an error
|
|
193
|
-
throw new Error(`Invalid fill mode: ${mode}`);
|
|
194
|
-
},
|
|
195
|
-
/**
|
|
196
|
-
* Give an item to a target.
|
|
197
|
-
* @param {ITEMS} item - The item to give.
|
|
198
|
-
* @param {TargetNames} target - The target to give the item to.
|
|
199
|
-
* @param {number} count - The number of items to give.
|
|
200
|
-
*/
|
|
201
|
-
give: ({ item, target, count = 1 }) => {
|
|
202
|
-
(0, sandstone_1.give)(mapTarget(target), item, count);
|
|
203
|
-
},
|
|
204
|
-
/**
|
|
205
|
-
* Give loot to a target with a weighted chance for selecting one of the items.
|
|
206
|
-
* @param {{ name: ITEMS, count: number, weight: number }[]} items - The items to give.
|
|
207
|
-
* @param {TargetNames} target - The target to give the item to.
|
|
208
|
-
*/
|
|
209
|
-
giveLoot: ({ items, target }) => {
|
|
210
|
-
// sort incoming items and create a hash for table re-use
|
|
211
|
-
const lootItemsSorted = [...items].sort((a, b) => a.name.localeCompare(b.name));
|
|
212
|
-
const lootItemsJson = JSON.stringify(lootItemsSorted);
|
|
213
|
-
const lootItemsHash = node_crypto_1.default.createHash("sha256").update(lootItemsJson).digest("hex");
|
|
214
|
-
const lootTableName = `loot_${lootItemsHash}`.slice(0, 16);
|
|
215
|
-
// create the entries for the loot table
|
|
216
|
-
const entries = items.map((item) => ({
|
|
217
|
-
type: "minecraft:item",
|
|
218
|
-
name: item.name,
|
|
219
|
-
weight: item.weight,
|
|
220
|
-
functions: [
|
|
221
|
-
{
|
|
222
|
-
function: "set_count",
|
|
223
|
-
count: item.count,
|
|
224
|
-
},
|
|
225
|
-
],
|
|
226
|
-
}));
|
|
227
|
-
// create the loot table with simple settings
|
|
228
|
-
const lootTable = {
|
|
229
|
-
type: "minecraft:generic",
|
|
230
|
-
pools: [
|
|
231
|
-
{
|
|
232
|
-
rolls: 1,
|
|
233
|
-
entries,
|
|
234
|
-
},
|
|
235
|
-
],
|
|
236
|
-
};
|
|
237
|
-
// on conflict, ignore because we can re-use duplicate loot tables
|
|
238
|
-
(0, sandstone_1.LootTable)(lootTableName, lootTable, { onConflict: "ignore" }).give(mapTarget(target));
|
|
239
|
-
},
|
|
240
|
-
/**
|
|
241
|
-
* Set a gamerule.
|
|
242
|
-
* @param {GAMERULES} rule - The name of the gamerule.
|
|
243
|
-
* @param {boolean | number} value - The value to set the gamerule to.
|
|
244
|
-
*/
|
|
245
|
-
gamerule: ({ rule, value }) => {
|
|
246
|
-
(0, sandstone_1.gamerule)(rule, value);
|
|
247
|
-
},
|
|
248
|
-
/**
|
|
249
|
-
* Kill entities matching a selector.
|
|
250
|
-
* @param {SelectorArgument} selector - The entities to kill.
|
|
251
|
-
*/
|
|
252
|
-
kill: ({ selector }) => {
|
|
253
|
-
(0, sandstone_1.kill)(mapTarget(selector));
|
|
254
|
-
},
|
|
255
|
-
/**
|
|
256
|
-
* Set the gamemode of a target. Useful for moving an eliminated player
|
|
257
|
-
* to spectator without killing them (preserves the entity, lets them
|
|
258
|
-
* watch the rest of the run).
|
|
259
|
-
*
|
|
260
|
-
* @example
|
|
261
|
-
* Actions.setGamemode({ target: "self", mode: "spectator" });
|
|
262
|
-
* Actions.setGamemode({ target: "all", mode: "adventure" });
|
|
263
|
-
*/
|
|
264
|
-
setGamemode: ({ target, mode, }) => {
|
|
265
|
-
(0, sandstone_1.gamemode)(mode, mapTarget(target));
|
|
266
|
-
},
|
|
267
|
-
/**
|
|
268
|
-
* Eliminate a participant from the active round: switch them to
|
|
269
|
-
* spectator mode (so they can still observe but can't act). Sister
|
|
270
|
-
* helper to `kill`, intended for vote-out / king-of-the-hill / battle
|
|
271
|
-
* royale style games where elimination shouldn't end the agent's
|
|
272
|
-
* connection.
|
|
273
|
-
*
|
|
274
|
-
* Note: this only flips gamemode. The challenge author is responsible
|
|
275
|
-
* for any per-player tracking variable (e.g. `is_alive`).
|
|
276
|
-
*
|
|
277
|
-
* @example
|
|
278
|
-
* Actions.eliminate({ target: "self" });
|
|
279
|
-
*/
|
|
280
|
-
eliminate: ({ target }) => {
|
|
281
|
-
(0, sandstone_1.gamemode)("spectator", mapTarget(target));
|
|
282
|
-
},
|
|
283
|
-
/**
|
|
284
|
-
* Set an attribute for a target.
|
|
285
|
-
* @param {ATTRIBUTES} attribute_ - The attribute to set.
|
|
286
|
-
* @param {number} value - The value to set the attribute to.
|
|
287
|
-
* @param {TargetNames} target - The target to set the attribute for.
|
|
288
|
-
*/
|
|
289
|
-
setAttribute: ({ attribute_, value, target }) => {
|
|
290
|
-
sandstone_1.execute.as(mapTarget(target)).run.attribute("@s", attribute_).baseSet(value);
|
|
291
|
-
},
|
|
292
|
-
/**
|
|
293
|
-
* Set the time of day.
|
|
294
|
-
* @param {'day' | 'night'} time_ - The time to set.
|
|
295
|
-
*/
|
|
296
|
-
setTime: ({ time }) => {
|
|
297
|
-
sandstone_1.time.set(time);
|
|
298
|
-
},
|
|
299
|
-
/**
|
|
300
|
-
* Summon multiple entities at a specific location.
|
|
301
|
-
*/
|
|
302
|
-
summonMultiple: (params) => {
|
|
303
|
-
const coordinates = params.absolute ? (0, sandstone_1.abs)(params.x, params.y, params.z) : (0, sandstone_1.rel)(params.x, params.y, params.z);
|
|
304
|
-
for (let i = 0; i < params.count; i++) {
|
|
305
|
-
(0, sandstone_1.summon)(params.entity, coordinates);
|
|
306
|
-
}
|
|
307
|
-
},
|
|
308
|
-
/**
|
|
309
|
-
* Set the end state of the challenge.
|
|
310
|
-
* @param {string} end_state - The end state to set.
|
|
311
|
-
* @throws {Error} If KRADLE_CHALLENGE_END_STATES is set and endState is not in the list.
|
|
312
|
-
*/
|
|
313
|
-
setEndState: ({ endState }) => {
|
|
314
|
-
// Validate end state against allowed values if env var is set
|
|
315
|
-
const allowedEndStates = process.env.KRADLE_CHALLENGE_END_STATES;
|
|
316
|
-
if (allowedEndStates) {
|
|
317
|
-
const validEndStates = JSON.parse(allowedEndStates);
|
|
318
|
-
if (!validEndStates.includes(endState)) {
|
|
319
|
-
throw new Error(`Invalid end state "${endState}". Valid end states are: ${validEndStates.join(", ")}. ` +
|
|
320
|
-
`Make sure the end state is defined in config.ts under "endStates".`);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
sandstone_1.data.modify.storage(constants_1.KRADLE_STORAGE, constants_1.END_STATE_KEY).set.value(endState);
|
|
324
|
-
},
|
|
325
|
-
/**
|
|
326
|
-
* For each `(name, condition)` pair, emit `_.if(condition, () =>
|
|
327
|
-
* setEndState(name))` at the current codegen location. Where (and how
|
|
328
|
-
* often) those checks run is up to the caller — `events.on_tick` for a
|
|
329
|
-
* per-tick check, `custom_events.actions` for a single fire, etc.
|
|
330
|
-
*
|
|
331
|
-
* Returns a `ConditionType` that is true when at least one of the input
|
|
332
|
-
* conditions is true (i.e. an end-state was applied). Hand it to
|
|
333
|
-
* `.end_condition()` if you want the game to end as soon as any end-state
|
|
334
|
-
* has been set; ignore it if the game's end is gated on something else
|
|
335
|
-
* (timer, separate condition).
|
|
336
|
-
*
|
|
337
|
-
* Note: conditions are evaluated in whatever execution context the caller
|
|
338
|
-
* is in. From `on_tick` that's the server entity, where per-player score
|
|
339
|
-
* reads silently fail — see `Actions.maxPlayerScore` for the per-player →
|
|
340
|
-
* global bridge.
|
|
341
|
-
*
|
|
342
|
-
* @example
|
|
343
|
-
* let endReached: ConditionType | undefined;
|
|
344
|
-
* const challenge = createChallenge({...});
|
|
345
|
-
* challenge
|
|
346
|
-
* .events((vars) => ({
|
|
347
|
-
* on_tick: () => {
|
|
348
|
-
* endReached = Actions.setEndStates({
|
|
349
|
-
* winner_found: vars.top_kills.greaterOrEqualThan(20),
|
|
350
|
-
* all_dead: vars.alive_players.equalTo(0),
|
|
351
|
-
* });
|
|
352
|
-
* },
|
|
353
|
-
* }))
|
|
354
|
-
* .end_condition(() => endReached!); // game ends as soon as any branch fires
|
|
355
|
-
*/
|
|
356
|
-
setEndStates: (endStates) => {
|
|
357
|
-
const entries = Object.entries(endStates);
|
|
358
|
-
if (entries.length === 0) {
|
|
359
|
-
throw new Error("Actions.setEndStates: at least one end-state must be provided.");
|
|
360
|
-
}
|
|
361
|
-
for (const [name, condition] of entries) {
|
|
362
|
-
sandstone_1._.if(condition, () => {
|
|
363
|
-
exports.Actions.setEndState({ endState: name });
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
const conditions = entries.map(([, condition]) => condition);
|
|
367
|
-
return conditions.length === 1 ? conditions[0] : sandstone_1._.or(...conditions);
|
|
368
|
-
},
|
|
369
|
-
/**
|
|
370
|
-
* Set a block at a specific location.
|
|
371
|
-
*/
|
|
372
|
-
setBlock: (params) => {
|
|
373
|
-
const coordinates = params.absolute ? (0, sandstone_1.abs)(params.x, params.y, params.z) : (0, sandstone_1.rel)(params.x, params.y, params.z);
|
|
374
|
-
(0, sandstone_1.setblock)(coordinates, params.block);
|
|
375
|
-
},
|
|
376
|
-
/**
|
|
377
|
-
* Teleport entities to a specific location, or to another entity.
|
|
378
|
-
* @param {TargetNames} target - The entities to teleport.
|
|
379
|
-
* @param {SingleEntityArgument} toEntity - The entity to teleport to.
|
|
380
|
-
* @param {number} x - The x coordinate of the destination.
|
|
381
|
-
* @param {number} y - The y coordinate of the destination.
|
|
382
|
-
* @param {number} z - The z coordinate of the destination.
|
|
383
|
-
* @param {boolean} absolute - Whether the coordinates are absolute or relative.
|
|
384
|
-
*/
|
|
385
|
-
teleport: (args) => {
|
|
386
|
-
if ("x" in args) {
|
|
387
|
-
const { target, x, y, z, absolute } = args;
|
|
388
|
-
const coordinates = absolute ? (0, sandstone_1.abs)(x, y, z) : (0, sandstone_1.rel)(x, y, z);
|
|
389
|
-
(0, sandstone_1.teleport)(mapTarget(target), coordinates);
|
|
390
|
-
}
|
|
391
|
-
else {
|
|
392
|
-
const { target, toEntity } = args;
|
|
393
|
-
(0, sandstone_1.teleport)(mapTarget(target), toEntity);
|
|
394
|
-
}
|
|
395
|
-
},
|
|
396
|
-
/**
|
|
397
|
-
* Force the target player(s) to immediately interrupt whatever code they are
|
|
398
|
-
* running and walk to the given absolute coordinates. Server-initiated: the
|
|
399
|
-
* challenge forces the move, unlike normal agent movement skills. The agents
|
|
400
|
-
* always walk to the destination — they are never teleported, even in cheat
|
|
401
|
-
* mode. Only the interrupt plus a single pathfind are guaranteed; an agent
|
|
402
|
-
* may decide to walk away again on its next action.
|
|
403
|
-
* @param {TargetNames} target - "self", "all", a role name, or a Selector.
|
|
404
|
-
* "self" is the acting agent and only resolves inside a per-player
|
|
405
|
-
* context; a forceMoveTo("self") reachable from a contextless server
|
|
406
|
-
* function is caught by the build context-safety check. Raw "@..."
|
|
407
|
-
* selector strings are rejected by mapTarget — use "self" or a Selector.
|
|
408
|
-
* @param {number} x - Absolute x coordinate to move to.
|
|
409
|
-
* @param {number} y - Absolute y coordinate to move to.
|
|
410
|
-
* @param {number} z - Absolute z coordinate to move to.
|
|
411
|
-
*/
|
|
412
|
-
forceMoveTo: ({ target, x, y, z }) => {
|
|
413
|
-
const t = mapTarget(target);
|
|
414
|
-
// mapTarget returns the "@s" string for "self"; wrap it as a Selector so
|
|
415
|
-
// the command can emit it. @s only resolves per-player, so a misplaced
|
|
416
|
-
// forceMoveTo("self") is caught by the context-safety check, not here.
|
|
417
|
-
const selector = t instanceof sandstone_1.SelectorClass ? t : (0, sandstone_1.Selector)("@s");
|
|
418
|
-
commands_1.Commands.forceMoveTo(selector, x, y, z);
|
|
419
|
-
},
|
|
420
|
-
/**
|
|
421
|
-
* Send a chat message to a target.
|
|
422
|
-
* @param {JSONTextComponent} message - The message to send.
|
|
423
|
-
* @param {TargetNames} target - The target to send the message to.
|
|
424
|
-
*/
|
|
425
|
-
tellraw: ({ message, target }) => {
|
|
426
|
-
(0, sandstone_1.tellraw)(mapTarget(target), ["\n", { text: constants_1.DISPLAY_TAG, color: "aqua" }, " | ", message, "\n"]);
|
|
427
|
-
},
|
|
428
|
-
// Score operations
|
|
429
|
-
/**
|
|
430
|
-
* Increment a score variable by 1.
|
|
431
|
-
* @param variable - The score variable to increment.
|
|
432
|
-
*/
|
|
433
|
-
increment: ({ variable }) => {
|
|
434
|
-
variable.add(1);
|
|
435
|
-
},
|
|
436
|
-
/**
|
|
437
|
-
* Decrement a score variable by 1.
|
|
438
|
-
* @param variable - The score variable to decrement.
|
|
439
|
-
*/
|
|
440
|
-
decrement: ({ variable }) => {
|
|
441
|
-
variable.remove(1);
|
|
442
|
-
},
|
|
443
|
-
/**
|
|
444
|
-
* Set a score variable to a specific value.
|
|
445
|
-
* @param variable - The score variable to set.
|
|
446
|
-
* @param value - The value to set the score variable to, which can be a number or another score variable.
|
|
447
|
-
*/
|
|
448
|
-
set: ({ variable, value }) => {
|
|
449
|
-
variable.set(value);
|
|
450
|
-
},
|
|
451
|
-
/**
|
|
452
|
-
* Selector that matches `@s` only when standing on the block at the
|
|
453
|
-
* given absolute coordinate. Use this anywhere you'd otherwise have
|
|
454
|
-
* written `Selector("@s", { x, y, z })` — that bare form does NOT
|
|
455
|
-
* constrain position (Minecraft treats x/y/z as the selector origin
|
|
456
|
-
* for distance math, not as a position filter), so the condition
|
|
457
|
-
* silently passes for everyone. This helper bakes in the right
|
|
458
|
-
* `dx: 0, dy: 1, dz: 0` volume covering the block above and the
|
|
459
|
-
* head space, so the selector matches only when the player's feet
|
|
460
|
-
* are inside the 1×1×1 column at (x, y+1, z).
|
|
461
|
-
*
|
|
462
|
-
* @param {number} x - X of the block being stood on.
|
|
463
|
-
* @param {number} y - Y of the block being stood on (the player's feet are at y+1).
|
|
464
|
-
* @param {number} z - Z of the block being stood on.
|
|
465
|
-
* @param {string} [role] - Optional role tag the player must also have.
|
|
466
|
-
*
|
|
467
|
-
* @example
|
|
468
|
-
* // Trigger alice_shared when Alice steps on her pad block.
|
|
469
|
-
* alice_shared: {
|
|
470
|
-
* type: "global",
|
|
471
|
-
* default: 0,
|
|
472
|
-
* updater: (value) => {
|
|
473
|
-
* _.if(value.equalTo(0), () => {
|
|
474
|
-
* forEveryPlayer(() => {
|
|
475
|
-
* _.if(Actions.standingOnBlock({ x: ALICE_PAD.x, y: ALICE_PAD.y, z: ALICE_PAD.z, role: "alice" }), () => {
|
|
476
|
-
* value.set(1);
|
|
477
|
-
* });
|
|
478
|
-
* });
|
|
479
|
-
* });
|
|
480
|
-
* },
|
|
481
|
-
* },
|
|
482
|
-
*/
|
|
483
|
-
standingOnBlock: ({ x, y, z, role }) => (0, sandstone_1.Selector)("@s", {
|
|
484
|
-
...(role ? { tag: role } : {}),
|
|
485
|
-
x,
|
|
486
|
-
y: y + 1,
|
|
487
|
-
z,
|
|
488
|
-
dx: 0,
|
|
489
|
-
dy: 1,
|
|
490
|
-
dz: 0,
|
|
491
|
-
}),
|
|
492
|
-
/**
|
|
493
|
-
* Condition that matches `@s` when the player has voted in `voteId` (any
|
|
494
|
-
* option), or — when `option` is given — only when they voted for that
|
|
495
|
-
* specific option. Agents cast votes with skills.voteForOption; the arena
|
|
496
|
-
* records each pick as the player tags kradle.voteOptions.<voteId> and
|
|
497
|
-
* kradle.voteOptions.<voteId>.<option>.
|
|
498
|
-
*
|
|
499
|
-
* It matches `@s`, so use it as a CONDITION in a player context — inside
|
|
500
|
-
* `forEveryPlayer(...)`, or in `setEndStates` / win conditions. Don't hand it
|
|
501
|
-
* to `execute.as(...)` from a tick/load context: `@s` resolves to no one
|
|
502
|
-
* there, so the command runs for nobody (the context-safety check flags this).
|
|
503
|
-
*
|
|
504
|
-
* @param {string} voteId - The vote's id, e.g. "selectroom".
|
|
505
|
-
* @param {string} [option] - Optional specific option, e.g. "red".
|
|
506
|
-
* @returns {ConditionType} a per-player condition for `_.if` / win conditions.
|
|
507
|
-
* @throws {Error} If the vote or option isn't declared in config.ts's votingOptions.
|
|
508
|
-
*
|
|
509
|
-
* @example
|
|
510
|
-
* // Teleport everyone who picked the red room.
|
|
511
|
-
* forEveryPlayer(() => {
|
|
512
|
-
* _.if(Actions.votedFor("selectroom", "red"), () => {
|
|
513
|
-
* Actions.teleport({ target: "self", x: 10, y: -60, z: 10, absolute: true });
|
|
514
|
-
* });
|
|
515
|
-
* });
|
|
516
|
-
*/
|
|
517
|
-
votedFor: (voteId, option) => {
|
|
518
|
-
assertDeclaredVote(voteId, option);
|
|
519
|
-
return (0, sandstone_1.Selector)("@s", {
|
|
520
|
-
tag: `kradle.voteOptions.${voteId}${option ? `.${option}` : ""}`,
|
|
521
|
-
});
|
|
522
|
-
},
|
|
523
|
-
/**
|
|
524
|
-
* Announces a vote to all participants over the `***KRADLE***` channel agents
|
|
525
|
-
* read, naming the vote and its options. Agents respond with
|
|
526
|
-
* skills.voteForOption(voteId, option). Compose with `allVotesIn`,
|
|
527
|
-
* `assignDefaultVotes`, and `votedFor` to build a full vote phase.
|
|
528
|
-
*
|
|
529
|
-
* @param {string} voteId - The vote's id, e.g. "selectroom".
|
|
530
|
-
* @param {string} prompt - Human-readable instruction, e.g. "Pick a room!".
|
|
531
|
-
* @param {string[]} options - Valid options, e.g. ["red", "green", "blue"].
|
|
532
|
-
* @throws {Error} If the vote or any option isn't declared in config.ts's votingOptions.
|
|
533
|
-
*
|
|
534
|
-
* @example
|
|
535
|
-
* Actions.promptVote({ voteId: "selectroom", prompt: "Pick a room!", options: ["red", "green"] });
|
|
536
|
-
*/
|
|
537
|
-
promptVote: ({ voteId, prompt, options }) => {
|
|
538
|
-
assertDeclaredVote(voteId);
|
|
539
|
-
for (const option of options)
|
|
540
|
-
assertDeclaredVote(voteId, option);
|
|
541
|
-
exports.Actions.announce({
|
|
542
|
-
message: `${prompt} Vote with skills.voteForOption("${voteId}", option) — options: ${options.join(", ")}.`,
|
|
543
|
-
});
|
|
544
|
-
},
|
|
545
|
-
/**
|
|
546
|
-
* Condition that is true once every participant has voted in `voteId` — i.e.
|
|
547
|
-
* no participant is still missing the `kradle.voteOptions.<voteId>` tag the
|
|
548
|
-
* arena writes on each vote. Combine with a timer to end a vote phase as soon
|
|
549
|
-
* as everyone is in.
|
|
550
|
-
*
|
|
551
|
-
* @param {string} voteId - The vote's id, e.g. "selectroom".
|
|
552
|
-
* @returns {ConditionType} true when all participants have voted.
|
|
553
|
-
* @throws {Error} If the vote isn't declared in config.ts's votingOptions.
|
|
554
|
-
*
|
|
555
|
-
* @example
|
|
556
|
-
* _.if(_.or(Actions.allVotesIn("selectroom"), vars.voteTimer.greaterOrEqualThan(600)), () => { ... });
|
|
557
|
-
*/
|
|
558
|
-
allVotesIn: (voteId) => {
|
|
559
|
-
assertDeclaredVote(voteId);
|
|
560
|
-
return sandstone_1._.not(participantsMissingVote(voteId));
|
|
561
|
-
},
|
|
562
|
-
/**
|
|
563
|
-
* Records `default` for every participant who has not voted in `voteId` yet —
|
|
564
|
-
* call this when a vote phase times out so non-voters are still moved by
|
|
565
|
-
* `votedFor`. Writes the same tags the arena would (kradle.voteOptions.<voteId>
|
|
566
|
-
* and kradle.voteOptions.<voteId>.<option>).
|
|
567
|
-
*
|
|
568
|
-
* @param {string} voteId - The vote's id, e.g. "selectroom".
|
|
569
|
-
* @param {string} default - Option assigned to non-voters, e.g. "red".
|
|
570
|
-
* @throws {Error} If the vote or default option isn't declared in config.ts's votingOptions.
|
|
571
|
-
*
|
|
572
|
-
* @example
|
|
573
|
-
* Actions.assignDefaultVotes({ voteId: "selectroom", default: "red" });
|
|
574
|
-
*/
|
|
575
|
-
assignDefaultVotes: ({ voteId, default: defaultOption }) => {
|
|
576
|
-
assertDeclaredVote(voteId, defaultOption);
|
|
577
|
-
sandstone_1.execute.as(participantsMissingVote(voteId)).run(() => {
|
|
578
|
-
(0, sandstone_1.tag)("@s").add(`kradle.voteOptions.${voteId}`);
|
|
579
|
-
(0, sandstone_1.tag)("@s").add(`kradle.voteOptions.${voteId}.${defaultOption}`);
|
|
580
|
-
});
|
|
581
|
-
},
|
|
582
|
-
/**
|
|
583
|
-
* log a message with the watcher
|
|
584
|
-
* @param {string} message - The message to send.
|
|
585
|
-
* @param {Score} variable - The variable to log.
|
|
586
|
-
* @param {boolean} store - Whether to store the variable in the backend.
|
|
587
|
-
*/
|
|
588
|
-
// WARNING: the logs must have precisely this structure to be read by the watcher. DO NOT CHANGE THE STRUCTURE.
|
|
589
|
-
log_variable: ({ message, variable, store }) => {
|
|
590
|
-
commands_1.Commands.logVariable(message, variable, store);
|
|
591
|
-
},
|
|
592
|
-
/**
|
|
593
|
-
* Summon an item at a specific location.
|
|
594
|
-
* @param {ITEMS} item - The item to summon.
|
|
595
|
-
* @param {number} x - The x coordinate of the location.
|
|
596
|
-
* @param {number} y - The y coordinate of the location.
|
|
597
|
-
* @param {number} z - The z coordinate of the location.
|
|
598
|
-
* @param {boolean} absolute - Whether the coordinates are absolute or relative.
|
|
599
|
-
*/
|
|
600
|
-
summonItem: ({ item, x, y, z, absolute }) => {
|
|
601
|
-
const pos = absolute ? (0, sandstone_1.abs)(x, y, z) : (0, sandstone_1.rel)(x, y, z);
|
|
602
|
-
(0, sandstone_1.summon)("minecraft:item", pos, { Item: { id: item, Count: sandstone_1.NBT.byte(1) } });
|
|
603
|
-
},
|
|
604
|
-
/**
|
|
605
|
-
* Count the number of a specific item in a target's inventory.
|
|
606
|
-
* @param params - The parameters object.
|
|
607
|
-
* @param params.target - The target to count items for.
|
|
608
|
-
* @param params.item - The item to count.
|
|
609
|
-
* @returns The variable containing the item count.
|
|
610
|
-
*/
|
|
611
|
-
countItems: ({ target, item }) => {
|
|
612
|
-
const variable = (0, sandstone_1.Variable)();
|
|
613
|
-
sandstone_1.execute.store.result.score(variable).run.clear(mapTarget(target), item, 0);
|
|
614
|
-
return variable;
|
|
615
|
-
},
|
|
616
|
-
/**
|
|
617
|
-
* Compute the maximum value of an individual score across all
|
|
618
|
-
* participants and store it in a global Variable. Returns the variable.
|
|
619
|
-
*
|
|
620
|
-
* Why this helper exists: end-state and end-condition checks evaluated
|
|
621
|
-
* from `events.on_tick` run in server context (the caller is a non-player
|
|
622
|
-
* entity), so an `if score @s X matches Y` check on a per-player score
|
|
623
|
-
* silently fails because @s has no entry on the per-player objective.
|
|
624
|
-
* Wrapping the comparison in this helper materializes a global "max
|
|
625
|
-
* across players" value that IS safe to reference from those checks.
|
|
626
|
-
*
|
|
627
|
-
* @param {Score} score - The per-player score to aggregate.
|
|
628
|
-
* @param {number} [min=0] - Lower bound to seed the result with. Pass a
|
|
629
|
-
* negative value if your per-player score can go below 0 (e.g. climb's
|
|
630
|
-
* `current_height` defaulting to -1000); otherwise the default `0`
|
|
631
|
-
* would mask negative actual maxes.
|
|
632
|
-
* @returns {Score} An anonymous Score containing the max.
|
|
633
|
-
*
|
|
634
|
-
* @example
|
|
635
|
-
* // In a custom_variables global updater:
|
|
636
|
-
* max_diamonds: {
|
|
637
|
-
* type: "global",
|
|
638
|
-
* default: 0,
|
|
639
|
-
* updater: (value, { diamonds }) => {
|
|
640
|
-
* value.set(Actions.maxPlayerScore({ score: diamonds }));
|
|
641
|
-
* },
|
|
642
|
-
* },
|
|
643
|
-
*
|
|
644
|
-
* // Then end-states can reference the global:
|
|
645
|
-
* .events(({ max_diamonds }) => ({
|
|
646
|
-
* on_tick: () => Actions.setEndStates({
|
|
647
|
-
* winner_found: max_diamonds.greaterOrEqualThan(5),
|
|
648
|
-
* }),
|
|
649
|
-
* }))
|
|
650
|
-
*
|
|
651
|
-
* @example
|
|
652
|
-
* // Climb's max_height — y can go below 0:
|
|
653
|
-
* max_height_global: {
|
|
654
|
-
* type: "global",
|
|
655
|
-
* default: -1000,
|
|
656
|
-
* updater: (value, { max_height }) => {
|
|
657
|
-
* value.set(Actions.maxPlayerScore({ score: max_height, min: -1000 }));
|
|
658
|
-
* },
|
|
659
|
-
* },
|
|
660
|
-
*/
|
|
661
|
-
maxPlayerScore: ({ score, min = 0 }) => {
|
|
662
|
-
const result = (0, sandstone_1.Variable)(min);
|
|
663
|
-
// Iterate over participants; for each, if their `score` exceeds the
|
|
664
|
-
// running max, copy it in. Same pattern an author would write by
|
|
665
|
-
// hand with forEveryPlayer + _.if(score.greaterThan(result), ...).
|
|
666
|
-
sandstone_1.execute.as(constants_1.ALL).run(() => {
|
|
667
|
-
sandstone_1._.if(score.greaterThan(result), () => {
|
|
668
|
-
result.set(score);
|
|
669
|
-
});
|
|
670
|
-
});
|
|
671
|
-
return result;
|
|
672
|
-
},
|
|
673
|
-
/**
|
|
674
|
-
* Returns a Sandstone condition that's true when the executing player
|
|
675
|
-
* (in `as @s` context) is standing on `block`. The default is a
|
|
676
|
-
* single-cell check (block at `~ ~-1 ~`), but `slop` widens the check
|
|
677
|
-
* to a `(2*slop+1)×(2*slop+1)` patch around the player's foot position
|
|
678
|
-
* — useful when the agent's pathfinder leaves them slightly off-center
|
|
679
|
-
* from a target block (e.g. mineflayer's `goToNearestBlock` reports
|
|
680
|
-
* success once within ~1.5 blocks of the target, so the agent is
|
|
681
|
-
* commonly off by a fractional block in x/z).
|
|
682
|
-
*
|
|
683
|
-
* Compiles to: `block ~ ~-1 ~ <block>` for slop=0, or an `_.or(...)` of
|
|
684
|
-
* 9 / 25 / etc. cell checks for slop=1 / 2 / etc.
|
|
685
|
-
*
|
|
686
|
-
* @example
|
|
687
|
-
* // PD-style "did the agent step on the cooperate pad?" check
|
|
688
|
-
* choice: {
|
|
689
|
-
* type: "individual",
|
|
690
|
-
* default: 0,
|
|
691
|
-
* updater: (value) => {
|
|
692
|
-
* _.if(Actions.isStandingOn({ block: "minecraft:lime_wool", slop: 1 }), () => {
|
|
693
|
-
* value.set(COOPERATE);
|
|
694
|
-
* });
|
|
695
|
-
* },
|
|
696
|
-
* },
|
|
697
|
-
*/
|
|
698
|
-
isStandingOn: ({ block, slop = 0 }) => {
|
|
699
|
-
if (slop < 0 || !Number.isInteger(slop)) {
|
|
700
|
-
throw new Error(`Actions.isStandingOn: slop must be a non-negative integer, got ${slop}`);
|
|
701
|
-
}
|
|
702
|
-
if (slop === 0) {
|
|
703
|
-
return sandstone_1._.block((0, sandstone_1.rel)(0, -1, 0), block);
|
|
704
|
-
}
|
|
705
|
-
const conditions = [];
|
|
706
|
-
for (let dx = -slop; dx <= slop; dx++) {
|
|
707
|
-
for (let dz = -slop; dz <= slop; dz++) {
|
|
708
|
-
conditions.push(sandstone_1._.block((0, sandstone_1.rel)(dx, -1, dz), block));
|
|
709
|
-
}
|
|
710
|
-
}
|
|
711
|
-
return sandstone_1._.or(...conditions);
|
|
712
|
-
},
|
|
713
|
-
/**
|
|
714
|
-
* Count instances of a block within an axis-aligned region. Useful for
|
|
715
|
-
* detecting agent-built structures without pinning detection to a single
|
|
716
|
-
* coordinate (which breaks the moment the agent moves before building).
|
|
717
|
-
*
|
|
718
|
-
* Implementation note: emits one `execute if block` check per cell in the
|
|
719
|
-
* region at codegen time, so the region is hard-capped at 4096 cells
|
|
720
|
-
* (~64×8×8) to keep the generated datapack reasonable.
|
|
721
|
-
*
|
|
722
|
-
* @param block The block ID to count (e.g. "minecraft:cobblestone").
|
|
723
|
-
* @param from First corner of the region (inclusive).
|
|
724
|
-
* @param to Opposite corner (inclusive).
|
|
725
|
-
* @param absolute Whether `from` / `to` are absolute world coordinates.
|
|
726
|
-
* @returns A Sandstone `Score` containing the count.
|
|
727
|
-
*
|
|
728
|
-
* @example
|
|
729
|
-
* // Detect any 5+ cobblestone block tower built within a 12×6×12 zone:
|
|
730
|
-
* const count = Actions.countBlocksInRegion({
|
|
731
|
-
* block: "minecraft:cobblestone",
|
|
732
|
-
* from: { x: -6, y: -60, z: -6 },
|
|
733
|
-
* to: { x: 6, y: -55, z: 6 },
|
|
734
|
-
* absolute: true,
|
|
735
|
-
* });
|
|
736
|
-
* _.if(count.greaterOrEqualThan(5), () => Actions.announce({ message: "Tower built!" }));
|
|
737
|
-
*/
|
|
738
|
-
countBlocksInRegion: ({ block, from, to, absolute, }) => {
|
|
739
|
-
const x1 = Math.min(from.x, to.x);
|
|
740
|
-
const x2 = Math.max(from.x, to.x);
|
|
741
|
-
const y1 = Math.min(from.y, to.y);
|
|
742
|
-
const y2 = Math.max(from.y, to.y);
|
|
743
|
-
const z1 = Math.min(from.z, to.z);
|
|
744
|
-
const z2 = Math.max(from.z, to.z);
|
|
745
|
-
const cellCount = (x2 - x1 + 1) * (y2 - y1 + 1) * (z2 - z1 + 1);
|
|
746
|
-
const MAX_CELLS = 4096;
|
|
747
|
-
if (cellCount > MAX_CELLS) {
|
|
748
|
-
throw new Error(`Actions.countBlocksInRegion: region has ${cellCount} cells, exceeding the safety cap of ${MAX_CELLS}. ` +
|
|
749
|
-
`Reduce the bounding box or use a smaller scan area.`);
|
|
750
|
-
}
|
|
751
|
-
const counter = (0, sandstone_1.Variable)();
|
|
752
|
-
counter.set(0);
|
|
753
|
-
for (let x = x1; x <= x2; x++) {
|
|
754
|
-
for (let y = y1; y <= y2; y++) {
|
|
755
|
-
for (let z = z1; z <= z2; z++) {
|
|
756
|
-
const coords = absolute ? (0, sandstone_1.abs)(x, y, z) : (0, sandstone_1.rel)(x, y, z);
|
|
757
|
-
sandstone_1.execute.if.block(coords, block).run(() => {
|
|
758
|
-
counter.add(1);
|
|
759
|
-
});
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
return counter;
|
|
764
|
-
},
|
|
765
|
-
/**
|
|
766
|
-
* Count instances of a block in a region centered on a target. Same idea
|
|
767
|
-
* as `countBlocksInRegion` but the bounds are *relative to the target's
|
|
768
|
-
* current position* — useful when the region of interest moves with a
|
|
769
|
-
* player (e.g. "how many X blocks are around the agent right now").
|
|
770
|
-
*
|
|
771
|
-
* What this is NOT: it does not verify a structure (the matches don't
|
|
772
|
-
* have to be contiguous), and it does not prove the target placed them.
|
|
773
|
-
* Treat the result as "presence count in a volume." Turn it into "the
|
|
774
|
-
* agent placed N of these" only when the block type doesn't spawn
|
|
775
|
-
* naturally in your world (and your arena init didn't pre-place any), or
|
|
776
|
-
* by clearing the volume first and counting the delta.
|
|
777
|
-
*
|
|
778
|
-
* Implementation note: emits one `execute as <target> at @s if block`
|
|
779
|
-
* check per cell at codegen time, so the region is hard-capped at 4096
|
|
780
|
-
* cells just like `countBlocksInRegion`.
|
|
781
|
-
*
|
|
782
|
-
* @example
|
|
783
|
-
* // Count diamond_block instances within 6 blocks of the agent. Diamond
|
|
784
|
-
* // blocks don't generate naturally, so in a flat-world challenge whose
|
|
785
|
-
* // arena init doesn't pre-place any, this is equivalently the count of
|
|
786
|
-
* // diamond blocks the agent has placed within reach.
|
|
787
|
-
* diamond_blocks_nearby: {
|
|
788
|
-
* type: "individual",
|
|
789
|
-
* default: 0,
|
|
790
|
-
* updater: (value) => {
|
|
791
|
-
* value.set(Actions.countBlocksRelativeTo({
|
|
792
|
-
* target: "self",
|
|
793
|
-
* block: "minecraft:diamond_block",
|
|
794
|
-
* dxRange: [-6, 6],
|
|
795
|
-
* dyRange: [-2, 4],
|
|
796
|
-
* dzRange: [-6, 6],
|
|
797
|
-
* }));
|
|
798
|
-
* },
|
|
799
|
-
* }
|
|
800
|
-
*/
|
|
801
|
-
countBlocksRelativeTo: ({ target, block, dxRange, dyRange, dzRange, }) => {
|
|
802
|
-
const [dx1, dx2] = [Math.min(...dxRange), Math.max(...dxRange)];
|
|
803
|
-
const [dy1, dy2] = [Math.min(...dyRange), Math.max(...dyRange)];
|
|
804
|
-
const [dz1, dz2] = [Math.min(...dzRange), Math.max(...dzRange)];
|
|
805
|
-
const cellCount = (dx2 - dx1 + 1) * (dy2 - dy1 + 1) * (dz2 - dz1 + 1);
|
|
806
|
-
const MAX_CELLS = 4096;
|
|
807
|
-
if (cellCount > MAX_CELLS) {
|
|
808
|
-
throw new Error(`Actions.countBlocksRelativeTo: region has ${cellCount} cells, exceeding the safety cap of ${MAX_CELLS}. ` +
|
|
809
|
-
`Reduce dx/dy/dzRange or scan in slices.`);
|
|
810
|
-
}
|
|
811
|
-
const counter = (0, sandstone_1.Variable)();
|
|
812
|
-
counter.set(0);
|
|
813
|
-
sandstone_1.execute
|
|
814
|
-
.as(mapTarget(target))
|
|
815
|
-
.at("@s")
|
|
816
|
-
.run(() => {
|
|
817
|
-
for (let dx = dx1; dx <= dx2; dx++) {
|
|
818
|
-
for (let dy = dy1; dy <= dy2; dy++) {
|
|
819
|
-
for (let dz = dz1; dz <= dz2; dz++) {
|
|
820
|
-
sandstone_1.execute.if.block((0, sandstone_1.rel)(dx, dy, dz), block).run(() => {
|
|
821
|
-
counter.add(1);
|
|
822
|
-
});
|
|
823
|
-
}
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
});
|
|
827
|
-
return counter;
|
|
828
|
-
},
|
|
829
|
-
/**
|
|
830
|
-
* Get the current player's position as x, y, z Score variables.
|
|
831
|
-
* Must be called in a player context (e.g., inside forEveryPlayer or when @s is a player).
|
|
832
|
-
* @returns An object with x, y, z Score variables containing the player's coordinates.
|
|
833
|
-
*/
|
|
834
|
-
getCurrentPlayerPosition: () => {
|
|
835
|
-
const x = (0, sandstone_1.Variable)();
|
|
836
|
-
const y = (0, sandstone_1.Variable)();
|
|
837
|
-
const z = (0, sandstone_1.Variable)();
|
|
838
|
-
sandstone_1.execute.store.result.score(x).run.data.get.entity("@s", "Pos[0]");
|
|
839
|
-
sandstone_1.execute.store.result.score(y).run.data.get.entity("@s", "Pos[1]");
|
|
840
|
-
sandstone_1.execute.store.result.score(z).run.data.get.entity("@s", "Pos[2]");
|
|
841
|
-
return { x, y, z };
|
|
842
|
-
},
|
|
843
|
-
/**
|
|
844
|
-
* Get a location marker entity by name.
|
|
845
|
-
* Returns a selector that targets the marker entity with the specified location tag.
|
|
846
|
-
* Locations must be defined in config.ts under "challengeConfig.locations".
|
|
847
|
-
* @param {string} name - The name of the location (must match a key in config.locations).
|
|
848
|
-
* @returns A selector targeting the location marker entity.
|
|
849
|
-
* @throws {Error} If KRADLE_CHALLENGE_LOCATIONS is set and the location name is not in the list.
|
|
850
|
-
*/
|
|
851
|
-
getLocation: ({ name }) => {
|
|
852
|
-
// Validate location against allowed values if env var is set
|
|
853
|
-
const allowedLocations = process.env.KRADLE_CHALLENGE_LOCATIONS;
|
|
854
|
-
if (allowedLocations) {
|
|
855
|
-
const validLocations = JSON.parse(allowedLocations);
|
|
856
|
-
if (!validLocations.includes(name)) {
|
|
857
|
-
throw new Error(`Invalid location "${name}". Valid locations are: ${validLocations.join(", ")}. ` +
|
|
858
|
-
`Make sure the location is defined in config.ts under "challengeConfig.locations".`);
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
return (0, sandstone_1.Selector)("@e", { type: "minecraft:marker", tag: `kradle.location.${name}`, limit: 1 });
|
|
862
|
-
},
|
|
863
|
-
/**
|
|
864
|
-
* Build a box (4 walls + optional floor/roof) in a single call. Replaces
|
|
865
|
-
* the typical 5+ `Actions.fill` calls per arena.
|
|
866
|
-
*
|
|
867
|
-
* @param block The wall block (e.g. "minecraft:stone").
|
|
868
|
-
* @param from First corner (inclusive).
|
|
869
|
-
* @param to Opposite corner (inclusive).
|
|
870
|
-
* @param absolute Whether coordinates are absolute or player-relative.
|
|
871
|
-
* @param hollow When true (default), only walls are placed; the interior is left untouched.
|
|
872
|
-
* When false, the entire volume is filled with `block`.
|
|
873
|
-
* @param floor Optional block to fill the floor with (overrides `block` for floor only).
|
|
874
|
-
* @param roof Optional block to fill the roof with.
|
|
875
|
-
* @param clearInside When true and `hollow=true`, the interior volume is set to air
|
|
876
|
-
* (useful for clearing existing terrain inside the new box).
|
|
877
|
-
*
|
|
878
|
-
* @example
|
|
879
|
-
* // 13x13x5 stone arena with a stone-bricks floor, no roof, interior cleared
|
|
880
|
-
* Actions.box({
|
|
881
|
-
* block: "minecraft:stone",
|
|
882
|
-
* from: { x: -6, y: -60, z: -6 },
|
|
883
|
-
* to: { x: 6, y: -55, z: 6 },
|
|
884
|
-
* absolute: true,
|
|
885
|
-
* hollow: true,
|
|
886
|
-
* floor: "minecraft:stone_bricks",
|
|
887
|
-
* clearInside: true,
|
|
888
|
-
* });
|
|
889
|
-
*/
|
|
890
|
-
box: ({ block, from, to, absolute, hollow = true, floor, roof, clearInside = false, }) => {
|
|
891
|
-
const x1 = Math.min(from.x, to.x);
|
|
892
|
-
const x2 = Math.max(from.x, to.x);
|
|
893
|
-
const y1 = Math.min(from.y, to.y);
|
|
894
|
-
const y2 = Math.max(from.y, to.y);
|
|
895
|
-
const z1 = Math.min(from.z, to.z);
|
|
896
|
-
const z2 = Math.max(from.z, to.z);
|
|
897
|
-
const fillRegion = (b, ax, ay, az, bx, by, bz) => {
|
|
898
|
-
const c1 = absolute ? (0, sandstone_1.abs)(ax, ay, az) : (0, sandstone_1.rel)(ax, ay, az);
|
|
899
|
-
const c2 = absolute ? (0, sandstone_1.abs)(bx, by, bz) : (0, sandstone_1.rel)(bx, by, bz);
|
|
900
|
-
(0, sandstone_1.fill)(c1, c2, b);
|
|
901
|
-
};
|
|
902
|
-
if (!hollow) {
|
|
903
|
-
fillRegion(block, x1, y1, z1, x2, y2, z2);
|
|
904
|
-
if (floor)
|
|
905
|
-
fillRegion(floor, x1, y1, z1, x2, y1, z2);
|
|
906
|
-
if (roof)
|
|
907
|
-
fillRegion(roof, x1, y2, z1, x2, y2, z2);
|
|
908
|
-
return;
|
|
909
|
-
}
|
|
910
|
-
// Walls: two pairs of opposite faces.
|
|
911
|
-
// North/south
|
|
912
|
-
fillRegion(block, x1, y1, z1, x2, y2, z1);
|
|
913
|
-
fillRegion(block, x1, y1, z2, x2, y2, z2);
|
|
914
|
-
// East/west
|
|
915
|
-
fillRegion(block, x1, y1, z1, x1, y2, z2);
|
|
916
|
-
fillRegion(block, x2, y1, z1, x2, y2, z2);
|
|
917
|
-
if (floor)
|
|
918
|
-
fillRegion(floor, x1, y1, z1, x2, y1, z2);
|
|
919
|
-
if (roof)
|
|
920
|
-
fillRegion(roof, x1, y2, z1, x2, y2, z2);
|
|
921
|
-
if (clearInside) {
|
|
922
|
-
const interiorBlock = clearInside === true ? "minecraft:air" : clearInside;
|
|
923
|
-
// Only fill the interior if there's actually room (>= 3 in each axis).
|
|
924
|
-
if (x2 - x1 >= 2 && y2 - y1 >= 2 && z2 - z1 >= 2) {
|
|
925
|
-
fillRegion(interiorBlock, x1 + 1, y1 + 1, z1 + 1, x2 - 1, y2 - 1, z2 - 1);
|
|
926
|
-
}
|
|
927
|
-
}
|
|
928
|
-
},
|
|
929
|
-
/**
|
|
930
|
-
* Give every participant a list of items in one call. Replaces the
|
|
931
|
-
* sequence of `Actions.give({ target: "all", item: …, count: … })` calls
|
|
932
|
-
* that opens nearly every multi-player start_challenge (castle-ctf has
|
|
933
|
-
* six of them for the iron-sword + 4 armor pieces + food loadout).
|
|
934
|
-
*
|
|
935
|
-
* @example PvP standard kit:
|
|
936
|
-
* ```ts
|
|
937
|
-
* Actions.giveAll([
|
|
938
|
-
* { item: "minecraft:iron_sword" },
|
|
939
|
-
* { item: "minecraft:iron_helmet" },
|
|
940
|
-
* { item: "minecraft:iron_chestplate" },
|
|
941
|
-
* { item: "minecraft:iron_leggings" },
|
|
942
|
-
* { item: "minecraft:iron_boots" },
|
|
943
|
-
* { item: "minecraft:cooked_beef", count: 5 },
|
|
944
|
-
* ]);
|
|
945
|
-
* ```
|
|
946
|
-
*
|
|
947
|
-
* `count` defaults to 1. For role-specific equipment, use plain
|
|
948
|
-
* `Actions.give({ target: roleName, … })`.
|
|
949
|
-
*/
|
|
950
|
-
giveAll: (items) => {
|
|
951
|
-
for (const it of items) {
|
|
952
|
-
(0, sandstone_1.give)(mapTarget("all"), it.item, it.count ?? 1);
|
|
953
|
-
}
|
|
954
|
-
},
|
|
955
|
-
/**
|
|
956
|
-
* "When the block at this absolute coordinate stops being `expected`, set
|
|
957
|
-
* `into` to 1." The standard goal_broken / flag_broken global-variable
|
|
958
|
-
* updater idiom. Latches at 1 — once tripped, stays tripped (so the
|
|
959
|
-
* end_state fires consistently even if the block is later replaced).
|
|
960
|
-
*
|
|
961
|
-
* Replaces the 4-line `_.if(_.not(_.block(abs(...), ...)), () => value.set(1))`
|
|
962
|
-
* pattern that appears in ~10 challenges' custom_variable updaters
|
|
963
|
-
* (castle-ctf×2, castle-siege×4, defender-striker, beanstalk, tower-trio,
|
|
964
|
-
* tunnel-rush, sky-walker, …). Removes the need to import `abs` from
|
|
965
|
-
* sandstone for this purpose.
|
|
966
|
-
*
|
|
967
|
-
* For multi-block checks ("all N landmarks broken" — diamond-hunt,
|
|
968
|
-
* return-home), keep using the manual `_.if(_.and(...conditions), …)`
|
|
969
|
-
* pattern — this helper is single-block only.
|
|
970
|
-
*
|
|
971
|
-
* @example castle-ctf red flag (latching — once broken, stays broken):
|
|
972
|
-
* ```ts
|
|
973
|
-
* red_flag_broken: {
|
|
974
|
-
* type: "global",
|
|
975
|
-
* default: 0,
|
|
976
|
-
* updater: (value) => {
|
|
977
|
-
* // Latch: only flip 0→1, never reset to 0. The returned Score is
|
|
978
|
-
* // itself a Condition (truthy = non-zero), so no `.equalTo(1)` needed.
|
|
979
|
-
* _.if(Actions.detectBlockMissing({
|
|
980
|
-
* at: { x: RED_BASE.x, y: RED_BASE.y + 1, z: RED_BASE.z },
|
|
981
|
-
* expected: "minecraft:red_wool",
|
|
982
|
-
* }), () => value.set(1));
|
|
983
|
-
* },
|
|
984
|
-
* }
|
|
985
|
-
* ```
|
|
986
|
-
*
|
|
987
|
-
* @returns {Score} An anonymous Score: 1 if the block at `at` is NOT
|
|
988
|
-
* `expected`, 0 if it is. Non-latching — recomputed every call. Wrap in
|
|
989
|
-
* `_.if(missing, () => value.set(1))` (the Score is itself a Condition,
|
|
990
|
-
* truthy = non-zero) for the latching "once broken, stays broken" pattern.
|
|
991
|
-
*/
|
|
992
|
-
detectBlockMissing: ({ at, expected }) => {
|
|
993
|
-
const result = (0, sandstone_1.Variable)(0);
|
|
994
|
-
sandstone_1._.if(sandstone_1._.not(sandstone_1._.block((0, sandstone_1.abs)(at.x, at.y, at.z), expected)), () => {
|
|
995
|
-
result.set(1);
|
|
996
|
-
});
|
|
997
|
-
return result;
|
|
998
|
-
},
|
|
999
|
-
/**
|
|
1000
|
-
* Summon a single configurable entity with optional health, attack damage,
|
|
1001
|
-
* movement speed, and tags. Uses Sandstone's typed `summon` + NBT helpers
|
|
1002
|
-
* (`NBT.byte`, `NBT.float`) so that booleans and NBT primitives serialize
|
|
1003
|
-
* correctly — sandstone otherwise emits JS `true` as `{}` (an invalid NBT
|
|
1004
|
-
* token), which broke boss-brawl's `CustomNameVisible: true` summon early on.
|
|
1005
|
-
*
|
|
1006
|
-
* For boss-style configured mobs (boss-solo, boss-brawl). Use
|
|
1007
|
-
* `Actions.summonMultiple` for plain entities without stat overrides.
|
|
1008
|
-
*
|
|
1009
|
-
* @example A 60-HP fast-moving boss zombie:
|
|
1010
|
-
* ```ts
|
|
1011
|
-
* Actions.summonEntityWithStats({
|
|
1012
|
-
* entity: "minecraft:zombie",
|
|
1013
|
-
* x: 0, y: -60, z: 0, absolute: true,
|
|
1014
|
-
* health: 60,
|
|
1015
|
-
* attackDamage: 6,
|
|
1016
|
-
* movementSpeed: 0.18,
|
|
1017
|
-
* tags: ["boss"], // for `Actions.countEntities({ tag: "boss", … })`
|
|
1018
|
-
* });
|
|
1019
|
-
* ```
|
|
1020
|
-
*/
|
|
1021
|
-
summonEntityWithStats: ({ entity, x, y, z, absolute, health, attackDamage, movementSpeed, tags, }) => {
|
|
1022
|
-
const attributes = [];
|
|
1023
|
-
if (health !== undefined)
|
|
1024
|
-
attributes.push({ Name: "generic.max_health", Base: health });
|
|
1025
|
-
if (attackDamage !== undefined)
|
|
1026
|
-
attributes.push({ Name: "generic.attack_damage", Base: attackDamage });
|
|
1027
|
-
if (movementSpeed !== undefined)
|
|
1028
|
-
attributes.push({ Name: "generic.movement_speed", Base: movementSpeed });
|
|
1029
|
-
const nbt = {};
|
|
1030
|
-
if (health !== undefined)
|
|
1031
|
-
nbt.Health = sandstone_1.NBT.float(health);
|
|
1032
|
-
if (attributes.length > 0)
|
|
1033
|
-
nbt.Attributes = attributes;
|
|
1034
|
-
if (tags !== undefined && tags.length > 0)
|
|
1035
|
-
nbt.Tags = tags;
|
|
1036
|
-
const pos = absolute ? (0, sandstone_1.abs)(x, y, z) : (0, sandstone_1.rel)(x, y, z);
|
|
1037
|
-
(0, sandstone_1.summon)(entity, pos, nbt);
|
|
1038
|
-
},
|
|
1039
|
-
/**
|
|
1040
|
-
* Count entities matching any combination of `entity` type, `tag`, and/or
|
|
1041
|
-
* `volume`, returning the count as an anonymous `Score`. At least one
|
|
1042
|
-
* filter must be provided (counting "every entity in the world" is almost
|
|
1043
|
-
* always a bug — guarded at runtime).
|
|
1044
|
-
*
|
|
1045
|
-
* Use this in a global custom_variable updater whose value tracks "how
|
|
1046
|
-
* many Xs are currently in the world", e.g. boss alive (zombie count),
|
|
1047
|
-
* chickens left, sheep in a pen.
|
|
1048
|
-
*
|
|
1049
|
-
* @param entity Optional entity type (e.g. `"minecraft:zombie"`).
|
|
1050
|
-
* @param tag Optional tag filter (Minecraft `@e[tag=…]`). Used by e.g.
|
|
1051
|
-
* boss-brawl, which counts zombies tagged `"boss"` to ignore wave-spawned mooks.
|
|
1052
|
-
* @param volume Optional bounding box (Minecraft-style: corner + dx/dy/dz
|
|
1053
|
-
* extents, absolute coords). Omit to count anywhere in the world.
|
|
1054
|
-
* @returns {Score} An anonymous Score holding the count.
|
|
1055
|
-
*
|
|
1056
|
-
* @example boss_alive — counts zombies anywhere
|
|
1057
|
-
* ```ts
|
|
1058
|
-
* boss_alive: {
|
|
1059
|
-
* type: "global",
|
|
1060
|
-
* default: 1,
|
|
1061
|
-
* updater: (value) => {
|
|
1062
|
-
* value.set(Actions.countEntities({ entity: "minecraft:zombie" }));
|
|
1063
|
-
* },
|
|
1064
|
-
* }
|
|
1065
|
-
* ```
|
|
1066
|
-
*
|
|
1067
|
-
* @example boss-brawl — count only the tagged boss zombies
|
|
1068
|
-
* ```ts
|
|
1069
|
-
* value.set(Actions.countEntities({ entity: "minecraft:zombie", tag: "boss" }));
|
|
1070
|
-
* ```
|
|
1071
|
-
*
|
|
1072
|
-
* @example sheep_in_pen — counts sheep inside a 3×1×3 box
|
|
1073
|
-
* ```ts
|
|
1074
|
-
* value.set(Actions.countEntities({
|
|
1075
|
-
* entity: "minecraft:sheep",
|
|
1076
|
-
* volume: { x: 14, y: -59, z: -1, dx: 2, dy: 1, dz: 2 },
|
|
1077
|
-
* }));
|
|
1078
|
-
* ```
|
|
1079
|
-
*/
|
|
1080
|
-
countEntities: ({ entity, tag, volume, }) => {
|
|
1081
|
-
if (!entity && !tag && !volume) {
|
|
1082
|
-
throw new Error("Actions.countEntities requires at least one of `entity`, `tag`, or `volume` — counting every entity in the world is almost always a bug.");
|
|
1083
|
-
}
|
|
1084
|
-
const result = (0, sandstone_1.Variable)(0);
|
|
1085
|
-
const selectorParams = {};
|
|
1086
|
-
if (entity)
|
|
1087
|
-
selectorParams.type = entity;
|
|
1088
|
-
if (tag)
|
|
1089
|
-
selectorParams.tag = tag;
|
|
1090
|
-
if (volume) {
|
|
1091
|
-
selectorParams.x = volume.x;
|
|
1092
|
-
selectorParams.y = volume.y;
|
|
1093
|
-
selectorParams.z = volume.z;
|
|
1094
|
-
selectorParams.dx = volume.dx;
|
|
1095
|
-
selectorParams.dy = volume.dy;
|
|
1096
|
-
selectorParams.dz = volume.dz;
|
|
1097
|
-
}
|
|
1098
|
-
sandstone_1.execute.as((0, sandstone_1.Selector)("@e", selectorParams)).run(() => {
|
|
1099
|
-
result.add(1);
|
|
1100
|
-
});
|
|
1101
|
-
return result;
|
|
1102
|
-
},
|
|
1103
|
-
/**
|
|
1104
|
-
* Build a flat open arena: a single-block-thick floor at `center.y` with
|
|
1105
|
-
* cleared airspace above up to `headroom` blocks tall. The "no walls,
|
|
1106
|
-
* just open ground for things to move on" pattern shared by sumo-arena,
|
|
1107
|
-
* boss-brawl, wave-survival, stalker, and one half of defender-striker.
|
|
1108
|
-
*
|
|
1109
|
-
* For arenas with walls, use `Actions.box({ hollow: true, floor: … })`.
|
|
1110
|
-
*
|
|
1111
|
-
* @example
|
|
1112
|
-
* Actions.flatArena({
|
|
1113
|
-
* block: "minecraft:stone",
|
|
1114
|
-
* center: { x: 0, y: -60, z: 0 },
|
|
1115
|
-
* radius: 10, // 21×21 footprint
|
|
1116
|
-
* headroom: 4, // 4 blocks of air above the floor (default 4)
|
|
1117
|
-
* });
|
|
1118
|
-
*/
|
|
1119
|
-
flatArena: ({ block, center, radius, headroom = 4, }) => {
|
|
1120
|
-
const x_min = center.x - radius;
|
|
1121
|
-
const x_max = center.x + radius;
|
|
1122
|
-
const z_min = center.z - radius;
|
|
1123
|
-
const z_max = center.z + radius;
|
|
1124
|
-
// Floor at center.y.
|
|
1125
|
-
(0, sandstone_1.fill)((0, sandstone_1.abs)(x_min, center.y, z_min), (0, sandstone_1.abs)(x_max, center.y, z_max), block);
|
|
1126
|
-
// Clear air above (skip if headroom <= 0).
|
|
1127
|
-
if (headroom > 0) {
|
|
1128
|
-
(0, sandstone_1.fill)((0, sandstone_1.abs)(x_min, center.y + 1, z_min), (0, sandstone_1.abs)(x_max, center.y + headroom, z_max), "minecraft:air");
|
|
1129
|
-
}
|
|
1130
|
-
},
|
|
1131
|
-
/**
|
|
1132
|
-
* Apply a non-lethal knockback / displacement to a target via Minecraft's
|
|
1133
|
-
* `effect` command. The default effect is a brief levitation (lifts the
|
|
1134
|
-
* target straight up without dealing damage).
|
|
1135
|
-
*
|
|
1136
|
-
* Use `mode: "slowfall"` to combine levitation with slow falling so the
|
|
1137
|
-
* target doesn't take fall damage on landing.
|
|
1138
|
-
*
|
|
1139
|
-
* @example
|
|
1140
|
-
* // Periodic knockback in King-of-the-Hill style:
|
|
1141
|
-
* Actions.knockbackWithLevitation({ target: "all", durationSeconds: 2, amplifier: 6 });
|
|
1142
|
-
*
|
|
1143
|
-
* // Knock with slow-fall for safe landing:
|
|
1144
|
-
* Actions.knockbackWithLevitation({ target: "self", mode: "slowfall", durationSeconds: 3 });
|
|
1145
|
-
*/
|
|
1146
|
-
knockbackWithLevitation: ({ target, durationSeconds = 1, amplifier = 5, mode = "levitation", }) => {
|
|
1147
|
-
const t = mapTarget(target);
|
|
1148
|
-
sandstone_1.effect.give(t, "minecraft:levitation", durationSeconds, amplifier, true);
|
|
1149
|
-
if (mode === "slowfall") {
|
|
1150
|
-
// 8s of slow-falling — covers most levitation falls without damage.
|
|
1151
|
-
sandstone_1.effect.give(t, "minecraft:slow_falling", durationSeconds + 8, 0, true);
|
|
1152
|
-
}
|
|
1153
|
-
},
|
|
1154
|
-
};
|
|
1155
|
-
//# sourceMappingURL=actions.js.map
|