@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.
Files changed (49) hide show
  1. package/README.md +1 -0
  2. package/biome.json +39 -0
  3. package/dist/actions.d.ts +203 -0
  4. package/dist/actions.d.ts.map +1 -0
  5. package/dist/actions.js +287 -0
  6. package/dist/actions.js.map +1 -0
  7. package/dist/api_utils.d.ts +5 -0
  8. package/dist/api_utils.d.ts.map +1 -0
  9. package/dist/api_utils.js +13 -0
  10. package/dist/api_utils.js.map +1 -0
  11. package/dist/challenge.d.ts +56 -0
  12. package/dist/challenge.d.ts.map +1 -0
  13. package/dist/challenge.js +462 -0
  14. package/dist/challenge.js.map +1 -0
  15. package/dist/commands.d.ts +38 -0
  16. package/dist/commands.d.ts.map +1 -0
  17. package/dist/commands.js +135 -0
  18. package/dist/commands.js.map +1 -0
  19. package/dist/constants.d.ts +70 -0
  20. package/dist/constants.d.ts.map +1 -0
  21. package/dist/constants.js +112 -0
  22. package/dist/constants.js.map +1 -0
  23. package/dist/index.d.ts +5 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +23 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/testmode.d.ts +3 -0
  28. package/dist/testmode.d.ts.map +1 -0
  29. package/dist/testmode.js +19 -0
  30. package/dist/testmode.js.map +1 -0
  31. package/dist/types.d.ts +103 -0
  32. package/dist/types.d.ts.map +1 -0
  33. package/dist/types.js +10 -0
  34. package/dist/types.js.map +1 -0
  35. package/dist/utils.d.ts +17 -0
  36. package/dist/utils.d.ts.map +1 -0
  37. package/dist/utils.js +22 -0
  38. package/dist/utils.js.map +1 -0
  39. package/package.json +27 -0
  40. package/src/actions.ts +388 -0
  41. package/src/api_utils.ts +10 -0
  42. package/src/challenge.ts +553 -0
  43. package/src/commands.ts +141 -0
  44. package/src/constants.ts +121 -0
  45. package/src/index.ts +4 -0
  46. package/src/testmode.ts +18 -0
  47. package/src/types.ts +136 -0
  48. package/src/utils.ts +22 -0
  49. 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>;
@@ -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
+ }