@kradle/challenges 0.0.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/README.md +1 -0
- package/biome.json +39 -0
- package/dist/actions.d.ts +203 -0
- package/dist/actions.d.ts.map +1 -0
- package/dist/actions.js +287 -0
- package/dist/actions.js.map +1 -0
- package/dist/api_utils.d.ts +5 -0
- package/dist/api_utils.d.ts.map +1 -0
- package/dist/api_utils.js +13 -0
- package/dist/api_utils.js.map +1 -0
- package/dist/challenge.d.ts +56 -0
- package/dist/challenge.d.ts.map +1 -0
- package/dist/challenge.js +462 -0
- package/dist/challenge.js.map +1 -0
- package/dist/commands.d.ts +38 -0
- package/dist/commands.d.ts.map +1 -0
- package/dist/commands.js +135 -0
- package/dist/commands.js.map +1 -0
- package/dist/constants.d.ts +70 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +112 -0
- package/dist/constants.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/testmode.d.ts +3 -0
- package/dist/testmode.d.ts.map +1 -0
- package/dist/testmode.js +19 -0
- package/dist/testmode.js.map +1 -0
- package/dist/types.d.ts +103 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +10 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +17 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +22 -0
- package/dist/utils.js.map +1 -0
- package/package.json +27 -0
- package/src/actions.ts +388 -0
- package/src/api_utils.ts +10 -0
- package/src/challenge.ts +553 -0
- package/src/commands.ts +141 -0
- package/src/constants.ts +121 -0
- package/src/index.ts +4 -0
- package/src/testmode.ts +18 -0
- package/src/types.ts +136 -0
- package/src/utils.ts +22 -0
- package/tsconfig.json +38 -0
package/src/actions.ts
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
abs,
|
|
4
|
+
// type Coordinates,
|
|
5
|
+
clear,
|
|
6
|
+
execute,
|
|
7
|
+
fill,
|
|
8
|
+
type GAMERULES,
|
|
9
|
+
gamerule,
|
|
10
|
+
give,
|
|
11
|
+
type JSONTextComponent,
|
|
12
|
+
kill,
|
|
13
|
+
LootTable,
|
|
14
|
+
type LootTableEntry,
|
|
15
|
+
type LootTableJSON,
|
|
16
|
+
raw,
|
|
17
|
+
rel,
|
|
18
|
+
type Score,
|
|
19
|
+
Selector,
|
|
20
|
+
SelectorClass,
|
|
21
|
+
// type SingleEntityArgument,
|
|
22
|
+
setblock,
|
|
23
|
+
summon,
|
|
24
|
+
teleport,
|
|
25
|
+
tellraw,
|
|
26
|
+
time as timeCmd,
|
|
27
|
+
} from "sandstone";
|
|
28
|
+
import type { ATTRIBUTES, BLOCKS, ENTITY_TYPES, ITEMS } from "sandstone/arguments/generated";
|
|
29
|
+
import { Commands } from "./commands";
|
|
30
|
+
import { ALL, DISPLAY_TAG } from "./constants";
|
|
31
|
+
import type { LiteralStringUnion } from "./utils";
|
|
32
|
+
|
|
33
|
+
/// Targets that can be used in Actions
|
|
34
|
+
export type TargetNames = LiteralStringUnion<"all" | "self"> | SelectorClass<any, any>;
|
|
35
|
+
|
|
36
|
+
export function mapTarget(target: string | SelectorClass): SelectorClass<any, any> | string {
|
|
37
|
+
switch (target) {
|
|
38
|
+
case "all":
|
|
39
|
+
return ALL;
|
|
40
|
+
case "self":
|
|
41
|
+
return "@s";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (target instanceof SelectorClass) {
|
|
45
|
+
return target;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// check if the target is a valid entity type
|
|
49
|
+
if (target.startsWith("minecraft:")) {
|
|
50
|
+
return Selector("@e", { type: target });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return Selector("@a", { tag: target }); // Assuming the target is a team name
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type Action = () => unknown;
|
|
57
|
+
|
|
58
|
+
export const Actions = {
|
|
59
|
+
/**
|
|
60
|
+
* Send a chat message to everyone.
|
|
61
|
+
* @param {JSONTextComponent} message - The message to send.
|
|
62
|
+
*/
|
|
63
|
+
announce: ({ message }: { message: JSONTextComponent }) => {
|
|
64
|
+
return () => {
|
|
65
|
+
tellraw("@a", ["\n", { text: DISPLAY_TAG, color: "aqua" }, " | ", message, "\n"]);
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Clear the inventory of a target.
|
|
71
|
+
* @param {TargetNames} target - The target to clear the inventory of.
|
|
72
|
+
*/
|
|
73
|
+
clear: ({ target }: { target: TargetNames }) => {
|
|
74
|
+
return () => clear(mapTarget(target));
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Custom action allowing to run any Sandstone command.
|
|
79
|
+
*
|
|
80
|
+
* @param {() => void} callback - The function to execute.
|
|
81
|
+
*/
|
|
82
|
+
custom: (callback: () => void) => {
|
|
83
|
+
return () => callback();
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Fill a region with a block.
|
|
88
|
+
* @param {BLOCKS} block - The block to fill the region with.
|
|
89
|
+
* @param {number} x1 - The x coordinate of the region.
|
|
90
|
+
* @param {number} y1 - The y coordinate of the region.
|
|
91
|
+
* @param {number} z1 - The z coordinate of the region.
|
|
92
|
+
* @param {number} x2 - The x coordinate of the region.
|
|
93
|
+
* @param {number} y2 - The y coordinate of the region.
|
|
94
|
+
* @param {number} z2 - The z coordinate of the region.
|
|
95
|
+
* @param {boolean} absolute - Whether the coordinates are absolute or relative.
|
|
96
|
+
*/
|
|
97
|
+
fill: ({
|
|
98
|
+
block,
|
|
99
|
+
x1,
|
|
100
|
+
y1,
|
|
101
|
+
z1,
|
|
102
|
+
x2,
|
|
103
|
+
y2,
|
|
104
|
+
z2,
|
|
105
|
+
absolute,
|
|
106
|
+
mode,
|
|
107
|
+
}: {
|
|
108
|
+
block: BLOCKS;
|
|
109
|
+
x1: number;
|
|
110
|
+
y1: number;
|
|
111
|
+
z1: number;
|
|
112
|
+
x2: number;
|
|
113
|
+
y2: number;
|
|
114
|
+
z2: number;
|
|
115
|
+
absolute: boolean;
|
|
116
|
+
mode: "fill" | "line" | "pyramid";
|
|
117
|
+
}) => {
|
|
118
|
+
return () => {
|
|
119
|
+
// fill the region with the block
|
|
120
|
+
if (mode === "fill") {
|
|
121
|
+
const coordinates1 = absolute ? abs(x1, y1, z1) : rel(x1, y1, z1);
|
|
122
|
+
const coordinates2 = absolute ? abs(x2, y2, z2) : rel(x2, y2, z2);
|
|
123
|
+
|
|
124
|
+
fill(coordinates1, coordinates2, block);
|
|
125
|
+
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// draw a line from the first coordinate to the second coordinate
|
|
130
|
+
if (mode === "line") {
|
|
131
|
+
const dx = x2 - x1;
|
|
132
|
+
const dy = y2 - y1;
|
|
133
|
+
const dz = z2 - z1;
|
|
134
|
+
|
|
135
|
+
const length = Math.max(Math.abs(dx), Math.abs(dy), Math.abs(dz));
|
|
136
|
+
const stepX = dx / length || 0;
|
|
137
|
+
const stepY = dy / length || 0;
|
|
138
|
+
const stepZ = dz / length || 0;
|
|
139
|
+
|
|
140
|
+
for (let i = 0; i <= length; i++) {
|
|
141
|
+
const x = Math.round(x1 + stepX * i);
|
|
142
|
+
const y = Math.round(y1 + stepY * i);
|
|
143
|
+
const z = Math.round(z1 + stepZ * i);
|
|
144
|
+
setblock(absolute ? abs(x, y, z) : rel(x, y, z), block);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (mode === "pyramid") {
|
|
151
|
+
const height = Math.abs(y2);
|
|
152
|
+
const direction = Math.sign(y2); // +1 for up, -1 for down
|
|
153
|
+
|
|
154
|
+
for (let i = 0; i < height; i++) {
|
|
155
|
+
const y = y1 + i * direction;
|
|
156
|
+
const layerRadius = (height - 1 - i) * 2;
|
|
157
|
+
|
|
158
|
+
const minX = x1 - layerRadius;
|
|
159
|
+
const maxX = x1 + layerRadius;
|
|
160
|
+
const minZ = z1 - layerRadius;
|
|
161
|
+
const maxZ = z1 + layerRadius;
|
|
162
|
+
|
|
163
|
+
for (let x = minX; x <= maxX; x++) {
|
|
164
|
+
for (let z = minZ; z <= maxZ; z++) {
|
|
165
|
+
setblock(absolute ? abs(x, y, z) : rel(x, y, z), block);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// if mode is not fill or line, throw an error
|
|
174
|
+
throw new Error(`Invalid fill mode: ${mode}`);
|
|
175
|
+
};
|
|
176
|
+
},
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Give an item to a target.
|
|
180
|
+
* @param {ITEMS} item - The item to give.
|
|
181
|
+
* @param {TargetNames} target - The target to give the item to.
|
|
182
|
+
* @param {number} count - The number of items to give.
|
|
183
|
+
*/
|
|
184
|
+
give: ({ item, target, count = 1 }: { item: ITEMS; target: TargetNames; count?: number }) => {
|
|
185
|
+
return () => give(mapTarget(target), item, count);
|
|
186
|
+
},
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Give loot to a target with a weighted chance for selecting one of the items.
|
|
190
|
+
* @param {{ name: ITEMS, count: number, weight: number }[]} items - The items to give.
|
|
191
|
+
* @param {TargetNames} target - The target to give the item to.
|
|
192
|
+
*/
|
|
193
|
+
giveLoot: ({ items, target }: { items: [{ name: ITEMS; count: number; weight: number }]; target: TargetNames }) => {
|
|
194
|
+
// sort incoming items and create a hash for table re-use
|
|
195
|
+
const lootItemsSorted = items.sort((a, b) => a.name.localeCompare(b.name));
|
|
196
|
+
const lootItemsJson = JSON.stringify(lootItemsSorted);
|
|
197
|
+
const lootItemsHash = crypto.createHash("sha256").update(lootItemsJson).digest("hex");
|
|
198
|
+
const lootTableName = `loot_${lootItemsHash}`.slice(0, 16);
|
|
199
|
+
|
|
200
|
+
// create the entries for the loot table
|
|
201
|
+
const entries: LootTableEntry[] = items.map((item) => ({
|
|
202
|
+
type: "minecraft:item",
|
|
203
|
+
name: item.name,
|
|
204
|
+
weight: item.weight,
|
|
205
|
+
functions: [
|
|
206
|
+
{
|
|
207
|
+
function: "set_count",
|
|
208
|
+
count: item.count,
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
}));
|
|
212
|
+
|
|
213
|
+
// create the loot table with simple settings
|
|
214
|
+
const lootTable: LootTableJSON = {
|
|
215
|
+
type: "minecraft:generic",
|
|
216
|
+
pools: [
|
|
217
|
+
{
|
|
218
|
+
rolls: 1,
|
|
219
|
+
entries,
|
|
220
|
+
},
|
|
221
|
+
],
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// on conflict, ignore because we can re-use duplicate loot tables
|
|
225
|
+
return () => LootTable(lootTableName, lootTable, { onConflict: "ignore" }).give(mapTarget(target));
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Set a gamerule.
|
|
230
|
+
* @param {GAMERULES} rule - The name of the gamerule.
|
|
231
|
+
* @param {boolean | number} value - The value to set the gamerule to.
|
|
232
|
+
*/
|
|
233
|
+
gamerule: ({ rule, value }: { rule: GAMERULES; value: boolean | number }) => {
|
|
234
|
+
return () => gamerule(rule, value);
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Kill entities matching a selector.
|
|
239
|
+
* @param {SelectorArgument} selector - The entities to kill.
|
|
240
|
+
*/
|
|
241
|
+
kill: ({ selector }: { selector: TargetNames }) => {
|
|
242
|
+
return () => kill(mapTarget(selector));
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Set an attribute for a target.
|
|
247
|
+
* @param {ATTRIBUTES} attribute_ - The attribute to set.
|
|
248
|
+
* @param {number} value - The value to set the attribute to.
|
|
249
|
+
* @param {TargetNames} target - The target to set the attribute for.
|
|
250
|
+
*/
|
|
251
|
+
setAttribute: ({ attribute_, value, target }: { attribute_: ATTRIBUTES; value: number; target: TargetNames }) => {
|
|
252
|
+
return () => {
|
|
253
|
+
execute.as(mapTarget(target)).run.attribute("@s", attribute_).baseSet(value);
|
|
254
|
+
};
|
|
255
|
+
},
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Set the time of day.
|
|
259
|
+
* @param {'day' | 'night'} time_ - The time to set.
|
|
260
|
+
*/
|
|
261
|
+
setTime: ({ time }: { time: "day" | "night" }) => {
|
|
262
|
+
return () => timeCmd.set(time);
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Summon multiple entities at a specific location.
|
|
267
|
+
*/
|
|
268
|
+
summonMultiple: (params: {
|
|
269
|
+
entity: ENTITY_TYPES;
|
|
270
|
+
count: number;
|
|
271
|
+
x: number;
|
|
272
|
+
y: number;
|
|
273
|
+
z: number;
|
|
274
|
+
absolute: boolean;
|
|
275
|
+
}) => {
|
|
276
|
+
return () => {
|
|
277
|
+
const coordinates = params.absolute ? abs(params.x, params.y, params.z) : rel(params.x, params.y, params.z);
|
|
278
|
+
for (let i = 0; i < params.count; i++) {
|
|
279
|
+
summon(params.entity, coordinates);
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Set a block at a specific location.
|
|
286
|
+
*/
|
|
287
|
+
setBlock: (params: { block: BLOCKS; x: number; y: number; z: number; absolute: boolean }) => {
|
|
288
|
+
return () => {
|
|
289
|
+
const coordinates = params.absolute ? abs(params.x, params.y, params.z) : rel(params.x, params.y, params.z);
|
|
290
|
+
setblock(coordinates, params.block);
|
|
291
|
+
};
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
// teleport
|
|
295
|
+
// TODO: allow destination to be a SingleEntityArgument | Coordinates
|
|
296
|
+
// - removed for now to allow XYZ from UI until we implement better form field for nested objects
|
|
297
|
+
/**
|
|
298
|
+
* Teleport entities to a specific location.
|
|
299
|
+
* @param {TargetNames} target - The entities to teleport.
|
|
300
|
+
* @param {number} x - The x coordinate of the destination.
|
|
301
|
+
* @param {number} y - The y coordinate of the destination.
|
|
302
|
+
* @param {number} z - The z coordinate of the destination.
|
|
303
|
+
* @param {boolean} absolute - Whether the coordinates are absolute or relative.
|
|
304
|
+
*/
|
|
305
|
+
teleport: ({
|
|
306
|
+
target,
|
|
307
|
+
x,
|
|
308
|
+
y,
|
|
309
|
+
z,
|
|
310
|
+
absolute = true,
|
|
311
|
+
}: {
|
|
312
|
+
target: TargetNames;
|
|
313
|
+
x: number;
|
|
314
|
+
y: number;
|
|
315
|
+
z: number;
|
|
316
|
+
absolute: boolean;
|
|
317
|
+
}) => {
|
|
318
|
+
const coordinates = absolute ? abs(x, y, z) : rel(x, y, z);
|
|
319
|
+
return () => teleport(mapTarget(target), coordinates);
|
|
320
|
+
},
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Send a chat message to a target.
|
|
324
|
+
* @param {string[]} message - The message to send.
|
|
325
|
+
* @param {TargetNames} target - The target to send the message to.
|
|
326
|
+
*/
|
|
327
|
+
tellraw: ({ message, target }: { message: string[]; target: TargetNames }) => {
|
|
328
|
+
return () => tellraw(mapTarget(target), message);
|
|
329
|
+
},
|
|
330
|
+
|
|
331
|
+
// Score operations
|
|
332
|
+
/**
|
|
333
|
+
* Increment a score variable by 1.
|
|
334
|
+
* @param variable - The score variable to increment.
|
|
335
|
+
*/
|
|
336
|
+
increment: ({ variable }: { variable: Score }) => {
|
|
337
|
+
return () => {
|
|
338
|
+
variable.add(1);
|
|
339
|
+
};
|
|
340
|
+
},
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Decrement a score variable by 1.
|
|
344
|
+
* @param variable - The score variable to decrement.
|
|
345
|
+
*/
|
|
346
|
+
decrement: ({ variable }: { variable: Score }) => {
|
|
347
|
+
return () => {
|
|
348
|
+
variable.remove(1);
|
|
349
|
+
};
|
|
350
|
+
},
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Set a score variable to a specific value.
|
|
354
|
+
* @param variable - The score variable to set.
|
|
355
|
+
* @param value - The value to set the score variable to, which can be a number or another score variable.
|
|
356
|
+
*/
|
|
357
|
+
set: ({ variable, value }: { variable: Score; value: number | Score }) => {
|
|
358
|
+
return () => {
|
|
359
|
+
variable.set(value);
|
|
360
|
+
};
|
|
361
|
+
},
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* log a message with the watcher
|
|
365
|
+
* @param {string} message - The message to send.
|
|
366
|
+
* @param {Score} variable - The variable to log.
|
|
367
|
+
* @param {boolean} store - Whether to store the variable in the backend.
|
|
368
|
+
*/
|
|
369
|
+
// WARNING: the logs must have precisely this structure to be read by the watcher. DO NOT CHANGE THE STRUCTURE.
|
|
370
|
+
log_variable: ({ message, variable, store }: { message: string; variable: Score; store: boolean }) => {
|
|
371
|
+
return () => Commands.logVariable(message, variable, store);
|
|
372
|
+
},
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Summon an item at a specific location.
|
|
376
|
+
* @param {ITEMS} item - The item to summon.
|
|
377
|
+
* @param {number} x - The x coordinate of the location.
|
|
378
|
+
* @param {number} y - The y coordinate of the location.
|
|
379
|
+
* @param {number} z - The z coordinate of the location.
|
|
380
|
+
* @param {boolean} absolute - Whether the coordinates are absolute or relative.
|
|
381
|
+
*/
|
|
382
|
+
summonItem: ({ item, x, y, z, absolute }: { item: ITEMS; x: number; y: number; z: number; absolute: boolean }) => {
|
|
383
|
+
return () => {
|
|
384
|
+
const abs = absolute ? "" : "~";
|
|
385
|
+
raw(`summon item ${abs}${x} ${abs}${y} ${abs}${z} {Item:{id:"${item}",Count:1b}}`);
|
|
386
|
+
};
|
|
387
|
+
},
|
|
388
|
+
} satisfies Record<string, (...args: any[]) => Action>;
|
package/src/api_utils.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/// Utilities for the external API - not necessarily used by the API itself, but useful for users of the API.
|
|
2
|
+
import { execute, Selector } from "sandstone";
|
|
3
|
+
import { KRADLE_PARTICIPANT_TAG } from "./constants";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Execute a callback for every player in the game, at their position.
|
|
7
|
+
*/
|
|
8
|
+
export function forEveryPlayer(callback: () => void) {
|
|
9
|
+
return execute.as(Selector("@a", { tag: KRADLE_PARTICIPANT_TAG })).at("@s").run(callback);
|
|
10
|
+
}
|