@kradle/challenges-sdk 0.10.0 → 0.12.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 +47 -1
- package/LLM_README.md +235 -2333
- package/README.md +150 -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/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/dist/test/harness.d.ts +176 -0
- package/dist/test/harness.d.ts.map +1 -0
- package/dist/test/harness.js +441 -0
- package/dist/test/harness.js.map +1 -0
- package/dist/test/helpers.d.ts +7 -0
- package/dist/test/helpers.d.ts.map +1 -0
- package/dist/test/helpers.js +12 -0
- package/dist/test/helpers.js.map +1 -0
- package/dist/test/index.d.ts +3 -0
- package/dist/test/index.d.ts.map +1 -0
- package/dist/test/index.js +20 -0
- package/dist/test/index.js.map +1 -0
- package/package.json +69 -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
|
@@ -0,0 +1,1038 @@
|
|
|
1
|
+
import { type BlockMatch, type Equipment, type ChunkMessage, type Entity, type EntityFilter, type EntityMove, type EquipmentChange, type OtherPlayer, type WorldState } from "./world.js";
|
|
2
|
+
export type { BlockMatch, Chunk, ChunkMessage, Entity, EntityFilter, EntityMove, Equipment, EquipmentChange, EquipmentSlot, Equipped, OtherPlayer, Section, WorldState, } from "./world.js";
|
|
3
|
+
export { applyEquipment, blockIdOf, bounds, inBox } from "./world.js";
|
|
4
|
+
export type ParamSpec = {
|
|
5
|
+
type: "number";
|
|
6
|
+
default: number;
|
|
7
|
+
} | {
|
|
8
|
+
type: "string";
|
|
9
|
+
default: string;
|
|
10
|
+
} | {
|
|
11
|
+
type: "boolean";
|
|
12
|
+
default: boolean;
|
|
13
|
+
};
|
|
14
|
+
/** The shape of config.ts that the runtime reads. Roles, vote options, end states and params become literal types. */
|
|
15
|
+
export interface ChallengeConfig {
|
|
16
|
+
name: string;
|
|
17
|
+
roles: Record<string, {
|
|
18
|
+
description?: string;
|
|
19
|
+
specificTask?: string;
|
|
20
|
+
minParticipants?: number;
|
|
21
|
+
}>;
|
|
22
|
+
challengeConfig: {
|
|
23
|
+
votingOptions?: Record<string, readonly string[]>;
|
|
24
|
+
locations?: Record<string, unknown>;
|
|
25
|
+
[key: string]: unknown;
|
|
26
|
+
};
|
|
27
|
+
endStates?: readonly string[] | Record<string, string>;
|
|
28
|
+
params?: Record<string, ParamSpec>;
|
|
29
|
+
}
|
|
30
|
+
export type RoleOf<C extends ChallengeConfig> = keyof C["roles"] & string;
|
|
31
|
+
export type VotingOptionsOf<C extends ChallengeConfig> = C["challengeConfig"] extends {
|
|
32
|
+
votingOptions: infer V;
|
|
33
|
+
} ? V : Record<never, never>;
|
|
34
|
+
/**
|
|
35
|
+
* The vote `skills.voteForPlayer` casts in. Its options are the other participants' names, so it
|
|
36
|
+
* needs no `votingOptions` entry: a challenge opens it with `votes: { player: {...} }`.
|
|
37
|
+
*/
|
|
38
|
+
export declare const PLAYER_VOTE = "player";
|
|
39
|
+
export type VoteIdOf<C extends ChallengeConfig> = (keyof VotingOptionsOf<C> & string) | typeof PLAYER_VOTE;
|
|
40
|
+
export type OptionOf<C extends ChallengeConfig> = VotingOptionsOf<C>[keyof VotingOptionsOf<C>] extends readonly (infer O)[] ? O & string : never;
|
|
41
|
+
/** The option type of one vote: a participant name for the player vote, that vote's configured options otherwise. */
|
|
42
|
+
export type OptionFor<C extends ChallengeConfig, V> = V extends typeof PLAYER_VOTE ? string : V extends keyof VotingOptionsOf<C> ? VotingOptionsOf<C>[V] extends readonly (infer O)[] ? O & string : never : never;
|
|
43
|
+
export type EndStateOf<C extends ChallengeConfig> = C["endStates"] extends readonly (infer E)[] ? E & string : C["endStates"] extends Record<infer K, string> ? K & string : string;
|
|
44
|
+
export type LocationOf<C extends ChallengeConfig> = C["challengeConfig"] extends {
|
|
45
|
+
locations: infer L;
|
|
46
|
+
} ? keyof L & string : never;
|
|
47
|
+
export type ParamsOf<C extends ChallengeConfig> = C["params"] extends Record<string, ParamSpec> ? {
|
|
48
|
+
[K in keyof C["params"]]: C["params"][K] extends {
|
|
49
|
+
type: "number";
|
|
50
|
+
} ? number : C["params"][K] extends {
|
|
51
|
+
type: "string";
|
|
52
|
+
} ? string : boolean;
|
|
53
|
+
} : Record<string, never>;
|
|
54
|
+
declare const ticksBrand: unique symbol;
|
|
55
|
+
/** A number of server ticks (20 per second). Made with seconds(), minutes() or ticks(). */
|
|
56
|
+
export type Ticks = number & {
|
|
57
|
+
readonly [ticksBrand]: true;
|
|
58
|
+
};
|
|
59
|
+
/** "35s", "2m", "40t", or a Ticks value. A bare number is not a duration. */
|
|
60
|
+
export type Duration = Ticks | `${number}s` | `${number}m` | `${number}t`;
|
|
61
|
+
export declare const TICKS_PER_SECOND = 20;
|
|
62
|
+
export declare const ticks: (n: number) => Ticks;
|
|
63
|
+
export declare const seconds: (n: number) => Ticks;
|
|
64
|
+
export declare const minutes: (n: number) => Ticks;
|
|
65
|
+
/** The ticks of a duration: "35s", "2m", "40t" or a Ticks value. Throws on a negative, non-finite or malformed one. */
|
|
66
|
+
export declare function toTicks(d: Duration): Ticks;
|
|
67
|
+
/** `a + b`, as ticks. */
|
|
68
|
+
export declare const addDuration: (a: Duration, b: Duration) => Ticks;
|
|
69
|
+
/** `a - b`, as ticks, clamped at zero: a countdown that passed its deadline reads 0, not an error. */
|
|
70
|
+
export declare const subtractDuration: (a: Duration, b: Duration) => Ticks;
|
|
71
|
+
/** A duration in seconds (for messages): `asSeconds("90s")` is 90, `asSeconds(ticks(30))` is 1.5. */
|
|
72
|
+
export declare const asSeconds: (d: Duration) => number;
|
|
73
|
+
/** A duration in minutes (for messages): `asMinutes("90s")` is 1.5. */
|
|
74
|
+
export declare const asMinutes: (d: Duration) => number;
|
|
75
|
+
export type Vec3 = {
|
|
76
|
+
x: number;
|
|
77
|
+
y: number;
|
|
78
|
+
z: number;
|
|
79
|
+
};
|
|
80
|
+
/** Block coordinates, both corners inclusive. A player is in a box when the block at their feet is inside. */
|
|
81
|
+
export type Box = {
|
|
82
|
+
from: Vec3;
|
|
83
|
+
to: Vec3;
|
|
84
|
+
};
|
|
85
|
+
export type Json = null | boolean | number | string | Json[] | {
|
|
86
|
+
[key: string]: Json;
|
|
87
|
+
};
|
|
88
|
+
export type Block = `minecraft:${string}`;
|
|
89
|
+
export type Item = `minecraft:${string}`;
|
|
90
|
+
export type EntityType = `minecraft:${string}`;
|
|
91
|
+
export type EffectId = `minecraft:${string}`;
|
|
92
|
+
export type GameMode = "survival" | "creative" | "adventure" | "spectator";
|
|
93
|
+
export type Participant = {
|
|
94
|
+
name: string;
|
|
95
|
+
role: string;
|
|
96
|
+
human?: boolean;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* A tagged entity the world carries (armor stands, markers): where a challenge finds its spawns and chest spots.
|
|
100
|
+
* `tags` are the entity's scoreboard tags; `g.markers(tag)` returns the markers that carry exactly that tag.
|
|
101
|
+
*/
|
|
102
|
+
export type Marker = {
|
|
103
|
+
type: EntityType;
|
|
104
|
+
at: Vec3;
|
|
105
|
+
name?: string | undefined;
|
|
106
|
+
tags: string[];
|
|
107
|
+
};
|
|
108
|
+
export type ContainerItem = {
|
|
109
|
+
slot?: number | undefined;
|
|
110
|
+
item: Item;
|
|
111
|
+
count: number;
|
|
112
|
+
nbt?: string | undefined;
|
|
113
|
+
};
|
|
114
|
+
/** Title timing in server ticks (20 per second), as `/title times`. */
|
|
115
|
+
export type TitleTimes = {
|
|
116
|
+
fadeIn: number;
|
|
117
|
+
stay: number;
|
|
118
|
+
fadeOut: number;
|
|
119
|
+
};
|
|
120
|
+
/** What an engine can supply. The arena reports them in run_started; the SDK checks the challenge's needs against them. */
|
|
121
|
+
export type Capability = "move" | "damage" | "death" | "pickup" | "inventory" | "blocks" | "zones" | "entities" | "custom" | "batch" | "query" | "walk" | "mirror" | "chat" | "lifecycle" | "requests" | "containers";
|
|
122
|
+
export declare const ALL_CAPABILITIES: Capability[];
|
|
123
|
+
export type Event = {
|
|
124
|
+
type: "run_started";
|
|
125
|
+
seed: number;
|
|
126
|
+
participants: Participant[];
|
|
127
|
+
params: Record<string, Json>;
|
|
128
|
+
capabilities: Capability[];
|
|
129
|
+
locations?: Record<string, Vec3> | undefined;
|
|
130
|
+
markers?: Marker[] | undefined;
|
|
131
|
+
} | {
|
|
132
|
+
type: "tick";
|
|
133
|
+
serverTick: number;
|
|
134
|
+
/** Every online player whose sample changed since the previous tick; the first tick after configure has them all. */
|
|
135
|
+
players?: PlayerSample[] | undefined;
|
|
136
|
+
/** Entities that moved since the previous tick. */
|
|
137
|
+
entities?: EntityMove[] | undefined;
|
|
138
|
+
/** The world time of day, when it changed. */
|
|
139
|
+
time?: number | undefined;
|
|
140
|
+
} | {
|
|
141
|
+
type: "player_joined";
|
|
142
|
+
name: string;
|
|
143
|
+
participant?: boolean | undefined;
|
|
144
|
+
uuid?: string | undefined;
|
|
145
|
+
id?: number | undefined;
|
|
146
|
+
gamemode?: GameMode | undefined;
|
|
147
|
+
} | {
|
|
148
|
+
type: "gamemode";
|
|
149
|
+
name: string;
|
|
150
|
+
gamemode: GameMode;
|
|
151
|
+
uuid?: string | undefined;
|
|
152
|
+
participant?: boolean | undefined;
|
|
153
|
+
} | {
|
|
154
|
+
type: "player_left";
|
|
155
|
+
name: string;
|
|
156
|
+
participant?: boolean | undefined;
|
|
157
|
+
uuid?: string | undefined;
|
|
158
|
+
id?: number | undefined;
|
|
159
|
+
} | {
|
|
160
|
+
type: "player_moved";
|
|
161
|
+
name: string;
|
|
162
|
+
to: Vec3;
|
|
163
|
+
maxY: number;
|
|
164
|
+
minY: number;
|
|
165
|
+
} | {
|
|
166
|
+
type: "player_damaged";
|
|
167
|
+
name: string;
|
|
168
|
+
amount: number;
|
|
169
|
+
cause: string;
|
|
170
|
+
attacker?: string | undefined;
|
|
171
|
+
} | {
|
|
172
|
+
type: "player_died";
|
|
173
|
+
name: string;
|
|
174
|
+
cause: string;
|
|
175
|
+
killer?: string | undefined;
|
|
176
|
+
} | {
|
|
177
|
+
type: "player_respawned";
|
|
178
|
+
name: string;
|
|
179
|
+
} | {
|
|
180
|
+
type: "item_picked_up";
|
|
181
|
+
name: string;
|
|
182
|
+
item: Item;
|
|
183
|
+
count: number;
|
|
184
|
+
at: Vec3;
|
|
185
|
+
} | {
|
|
186
|
+
type: "inventory";
|
|
187
|
+
name: string;
|
|
188
|
+
slots: Array<{
|
|
189
|
+
slot: number;
|
|
190
|
+
item?: Item | undefined;
|
|
191
|
+
count?: number | undefined;
|
|
192
|
+
}>;
|
|
193
|
+
} | {
|
|
194
|
+
type: "block_broken";
|
|
195
|
+
name: string;
|
|
196
|
+
at: Vec3;
|
|
197
|
+
block: Block;
|
|
198
|
+
} | {
|
|
199
|
+
type: "block_placed";
|
|
200
|
+
name: string;
|
|
201
|
+
at: Vec3;
|
|
202
|
+
block: Block;
|
|
203
|
+
} | {
|
|
204
|
+
type: "block_used";
|
|
205
|
+
name: string;
|
|
206
|
+
at: Vec3;
|
|
207
|
+
block: Block;
|
|
208
|
+
item?: Item | undefined;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* The contents of a declared container changed (or its first snapshot, `initial`): the changed
|
|
212
|
+
* slots only, like `inventory`. `by` names the player who had it open, when the engine knows.
|
|
213
|
+
*/
|
|
214
|
+
| {
|
|
215
|
+
type: "container";
|
|
216
|
+
at: Vec3;
|
|
217
|
+
block: Block;
|
|
218
|
+
slots: Array<{
|
|
219
|
+
slot: number;
|
|
220
|
+
item?: Item | undefined;
|
|
221
|
+
count?: number | undefined;
|
|
222
|
+
}>;
|
|
223
|
+
by?: string | undefined;
|
|
224
|
+
initial?: boolean | undefined;
|
|
225
|
+
} | {
|
|
226
|
+
type: "entity_spawned";
|
|
227
|
+
id: string;
|
|
228
|
+
entity: EntityType;
|
|
229
|
+
at: Vec3;
|
|
230
|
+
tags?: string[] | undefined;
|
|
231
|
+
name?: string | undefined;
|
|
232
|
+
health?: number | undefined;
|
|
233
|
+
item?: Item | undefined;
|
|
234
|
+
count?: number | undefined;
|
|
235
|
+
uuid?: string | undefined;
|
|
236
|
+
yaw?: number | undefined;
|
|
237
|
+
pitch?: number | undefined;
|
|
238
|
+
headYaw?: number | undefined;
|
|
239
|
+
equipment?: EquipmentChange[] | undefined;
|
|
240
|
+
invisible?: boolean | undefined;
|
|
241
|
+
villager?: {
|
|
242
|
+
type: string;
|
|
243
|
+
profession: string;
|
|
244
|
+
level: number;
|
|
245
|
+
} | undefined;
|
|
246
|
+
} | {
|
|
247
|
+
type: "entity_changed";
|
|
248
|
+
id: string;
|
|
249
|
+
tags?: string[] | undefined;
|
|
250
|
+
name?: string | undefined;
|
|
251
|
+
health?: number | undefined;
|
|
252
|
+
item?: Item | undefined;
|
|
253
|
+
count?: number | undefined;
|
|
254
|
+
equipment?: EquipmentChange[] | undefined;
|
|
255
|
+
invisible?: boolean | undefined;
|
|
256
|
+
villager?: {
|
|
257
|
+
type: string;
|
|
258
|
+
profession: string;
|
|
259
|
+
level: number;
|
|
260
|
+
} | undefined;
|
|
261
|
+
} | {
|
|
262
|
+
type: "animation";
|
|
263
|
+
name: string;
|
|
264
|
+
animation: "swing" | "swing_off";
|
|
265
|
+
} | {
|
|
266
|
+
type: "entity_removed";
|
|
267
|
+
id: string;
|
|
268
|
+
cause: "died" | "removed";
|
|
269
|
+
killer?: string | undefined;
|
|
270
|
+
} | ({
|
|
271
|
+
type: "chunk";
|
|
272
|
+
} & ChunkMessage) | {
|
|
273
|
+
type: "chunk_unloaded";
|
|
274
|
+
x: number;
|
|
275
|
+
z: number;
|
|
276
|
+
} | {
|
|
277
|
+
type: "blocks_changed";
|
|
278
|
+
changes: Array<Vec3 & {
|
|
279
|
+
block: Block;
|
|
280
|
+
was?: Block | undefined;
|
|
281
|
+
cause?: string | undefined;
|
|
282
|
+
by?: string | undefined;
|
|
283
|
+
}>;
|
|
284
|
+
} | {
|
|
285
|
+
type: "blocks_filled";
|
|
286
|
+
from: Vec3;
|
|
287
|
+
to: Vec3;
|
|
288
|
+
block: Block;
|
|
289
|
+
} | {
|
|
290
|
+
type: "chat";
|
|
291
|
+
from: string;
|
|
292
|
+
text: string;
|
|
293
|
+
uuid?: string | undefined;
|
|
294
|
+
} | {
|
|
295
|
+
type: "vote_attempt";
|
|
296
|
+
attemptId: string;
|
|
297
|
+
name: string;
|
|
298
|
+
voteId: string;
|
|
299
|
+
option: string;
|
|
300
|
+
} | {
|
|
301
|
+
type: "effect_result";
|
|
302
|
+
id: string;
|
|
303
|
+
ok: boolean;
|
|
304
|
+
detail?: string | undefined;
|
|
305
|
+
} | {
|
|
306
|
+
type: "effect_failed";
|
|
307
|
+
kind: string;
|
|
308
|
+
detail: string;
|
|
309
|
+
} | {
|
|
310
|
+
type: "custom";
|
|
311
|
+
name: string;
|
|
312
|
+
player?: string | undefined;
|
|
313
|
+
data: Json;
|
|
314
|
+
};
|
|
315
|
+
/** One player's changed fields since the previous tick. `pos` comes with the extrema since the previous sample. */
|
|
316
|
+
export type PlayerSample = {
|
|
317
|
+
name: string;
|
|
318
|
+
participant: boolean;
|
|
319
|
+
/** The server's entity id and the player's uuid, on the first sample. */
|
|
320
|
+
id?: number | undefined;
|
|
321
|
+
uuid?: string | undefined;
|
|
322
|
+
pos?: Vec3 | undefined;
|
|
323
|
+
maxY?: number | undefined;
|
|
324
|
+
minY?: number | undefined;
|
|
325
|
+
/** Degrees, Minecraft convention; sent with `pos`. */
|
|
326
|
+
yaw?: number | undefined;
|
|
327
|
+
pitch?: number | undefined;
|
|
328
|
+
headYaw?: number | undefined;
|
|
329
|
+
health?: number | undefined;
|
|
330
|
+
food?: number | undefined;
|
|
331
|
+
gamemode?: GameMode | undefined;
|
|
332
|
+
held?: Item | null | undefined;
|
|
333
|
+
equipment?: EquipmentChange[] | undefined;
|
|
334
|
+
invisible?: boolean | undefined;
|
|
335
|
+
};
|
|
336
|
+
export type Effect = {
|
|
337
|
+
kind: "fill";
|
|
338
|
+
from: Vec3;
|
|
339
|
+
to: Vec3;
|
|
340
|
+
block: Block;
|
|
341
|
+
} | {
|
|
342
|
+
kind: "setblock";
|
|
343
|
+
at: Vec3;
|
|
344
|
+
block: Block;
|
|
345
|
+
nbt?: string | undefined;
|
|
346
|
+
} | {
|
|
347
|
+
kind: "give";
|
|
348
|
+
player: string;
|
|
349
|
+
item: Item;
|
|
350
|
+
count: number;
|
|
351
|
+
nbt?: string | undefined;
|
|
352
|
+
} | {
|
|
353
|
+
kind: "clear";
|
|
354
|
+
player: string;
|
|
355
|
+
item?: Item | undefined;
|
|
356
|
+
count?: number | undefined;
|
|
357
|
+
} | {
|
|
358
|
+
kind: "teleport";
|
|
359
|
+
player: string;
|
|
360
|
+
to: Vec3;
|
|
361
|
+
} | {
|
|
362
|
+
kind: "effect";
|
|
363
|
+
player: string;
|
|
364
|
+
effect: EffectId;
|
|
365
|
+
seconds: number;
|
|
366
|
+
amplifier: number;
|
|
367
|
+
} | {
|
|
368
|
+
kind: "summon";
|
|
369
|
+
entity: EntityType;
|
|
370
|
+
at: Vec3;
|
|
371
|
+
nbt?: string | undefined;
|
|
372
|
+
} | {
|
|
373
|
+
kind: "kill_entities";
|
|
374
|
+
entities?: EntityType[] | undefined;
|
|
375
|
+
} | {
|
|
376
|
+
kind: "kill";
|
|
377
|
+
player: string;
|
|
378
|
+
} | {
|
|
379
|
+
kind: "gamemode";
|
|
380
|
+
player: string;
|
|
381
|
+
mode: GameMode;
|
|
382
|
+
} | {
|
|
383
|
+
kind: "attribute";
|
|
384
|
+
player: string;
|
|
385
|
+
attribute: string;
|
|
386
|
+
value: number;
|
|
387
|
+
} | {
|
|
388
|
+
kind: "time";
|
|
389
|
+
value: "day" | "noon" | "night" | "midnight";
|
|
390
|
+
} | {
|
|
391
|
+
kind: "gamerule";
|
|
392
|
+
rule: string;
|
|
393
|
+
value: boolean | number;
|
|
394
|
+
} | {
|
|
395
|
+
kind: "title";
|
|
396
|
+
player?: string | undefined;
|
|
397
|
+
title: string;
|
|
398
|
+
subtitle?: string | undefined;
|
|
399
|
+
times?: TitleTimes | undefined;
|
|
400
|
+
} | {
|
|
401
|
+
kind: "worldborder";
|
|
402
|
+
center?: {
|
|
403
|
+
x: number;
|
|
404
|
+
z: number;
|
|
405
|
+
} | undefined;
|
|
406
|
+
size?: number | undefined;
|
|
407
|
+
seconds?: number | undefined;
|
|
408
|
+
} | {
|
|
409
|
+
kind: "container";
|
|
410
|
+
at: Vec3;
|
|
411
|
+
items: ContainerItem[];
|
|
412
|
+
} | {
|
|
413
|
+
kind: "spawn";
|
|
414
|
+
at: Vec3;
|
|
415
|
+
} | {
|
|
416
|
+
kind: "eliminate";
|
|
417
|
+
player: string;
|
|
418
|
+
} | {
|
|
419
|
+
kind: "announce";
|
|
420
|
+
message: string;
|
|
421
|
+
to?: string | undefined;
|
|
422
|
+
} | {
|
|
423
|
+
kind: "command";
|
|
424
|
+
cmd: string;
|
|
425
|
+
};
|
|
426
|
+
export type Action = {
|
|
427
|
+
type: "effect";
|
|
428
|
+
effect: Effect;
|
|
429
|
+
} | {
|
|
430
|
+
type: "batch";
|
|
431
|
+
id: string;
|
|
432
|
+
effects: Effect[];
|
|
433
|
+
} | {
|
|
434
|
+
type: "walk";
|
|
435
|
+
id: string;
|
|
436
|
+
player: string;
|
|
437
|
+
to: Vec3;
|
|
438
|
+
fallback: "teleport" | "none";
|
|
439
|
+
} | {
|
|
440
|
+
type: "vote_decision";
|
|
441
|
+
attemptId: string;
|
|
442
|
+
accepted: boolean;
|
|
443
|
+
feedback?: string | undefined;
|
|
444
|
+
} | {
|
|
445
|
+
type: "log";
|
|
446
|
+
player?: string | undefined;
|
|
447
|
+
message: string;
|
|
448
|
+
data?: Json | undefined;
|
|
449
|
+
} | {
|
|
450
|
+
type: "score";
|
|
451
|
+
player: string;
|
|
452
|
+
score: number;
|
|
453
|
+
} | {
|
|
454
|
+
type: "store";
|
|
455
|
+
name: string;
|
|
456
|
+
value: number;
|
|
457
|
+
player?: string | undefined;
|
|
458
|
+
} | {
|
|
459
|
+
type: "game_over";
|
|
460
|
+
winners: string[];
|
|
461
|
+
endState: string;
|
|
462
|
+
judgeContext?: Record<string, Json> | undefined;
|
|
463
|
+
};
|
|
464
|
+
export type Status = "LOBBY" | "SETUP" | "PLAYING" | "OVER";
|
|
465
|
+
/** The end states the SDK reports on its own: a failed setup, a thrown rule, facts the engine lacks. */
|
|
466
|
+
export type SdkEndState = "error_setup" | "error_challenge" | "error_unsupported_facts";
|
|
467
|
+
/** What `onOver` receives: the names on the wire, the same winners as players, and a membership check. */
|
|
468
|
+
export interface OverResult<C extends ChallengeConfig, PM extends PlayerMap<C>> {
|
|
469
|
+
/** The winners' names, as `game_over` carries them. */
|
|
470
|
+
winners: string[];
|
|
471
|
+
/** The same winners as player objects. */
|
|
472
|
+
winnerPlayers: Player<C, PM>[];
|
|
473
|
+
isWinner(player: {
|
|
474
|
+
name: string;
|
|
475
|
+
}): boolean;
|
|
476
|
+
/** A declared end state, an SDK error state, or "" when no end state was classified. */
|
|
477
|
+
endState: EndStateOf<C> | SdkEndState | "";
|
|
478
|
+
}
|
|
479
|
+
/** Fields the SDK keeps on every player. The challenge's P is merged on top; P may not reuse these names. */
|
|
480
|
+
export interface Built {
|
|
481
|
+
alive: boolean;
|
|
482
|
+
eliminated: boolean;
|
|
483
|
+
deaths: number;
|
|
484
|
+
kills: number;
|
|
485
|
+
/** Reported to the run result whenever it changes. */
|
|
486
|
+
score: number;
|
|
487
|
+
pos: Vec3 | null;
|
|
488
|
+
/** Degrees, Minecraft convention. */
|
|
489
|
+
yaw: number;
|
|
490
|
+
pitch: number;
|
|
491
|
+
headYaw: number;
|
|
492
|
+
/** The server's entity id and uuid, once the first sample arrived. */
|
|
493
|
+
entityId: number | null;
|
|
494
|
+
uuid: string | null;
|
|
495
|
+
/** Worn and held items by slot (main, off, head, chest, legs, feet). */
|
|
496
|
+
equipment: Equipment;
|
|
497
|
+
invisible: boolean;
|
|
498
|
+
health: number;
|
|
499
|
+
food: number;
|
|
500
|
+
gamemode: GameMode;
|
|
501
|
+
held: Item | null;
|
|
502
|
+
/** Inventory by slot: 0-35 main (0-8 hotbar), 36-39 armor, 40 offhand. */
|
|
503
|
+
slots: Record<number, {
|
|
504
|
+
item: Item;
|
|
505
|
+
count: number;
|
|
506
|
+
}>;
|
|
507
|
+
/** Item counts over every slot. */
|
|
508
|
+
inventory: Record<string, number>;
|
|
509
|
+
/** The declared zones the player stands in. */
|
|
510
|
+
zones: string[];
|
|
511
|
+
human: boolean;
|
|
512
|
+
}
|
|
513
|
+
export interface Roster<C extends ChallengeConfig, R extends RoleOf<C> = RoleOf<C>> {
|
|
514
|
+
name: string;
|
|
515
|
+
role: R;
|
|
516
|
+
number: number;
|
|
517
|
+
connected: boolean;
|
|
518
|
+
}
|
|
519
|
+
/** The challenge's per-player state, resolved for every role of the config. */
|
|
520
|
+
export type PlayerMap<C extends ChallengeConfig> = {
|
|
521
|
+
[R in RoleOf<C>]: object;
|
|
522
|
+
};
|
|
523
|
+
/** The map `player` (shared) and `players` (per role) declare: a role without its own entry gets the shared state. */
|
|
524
|
+
export type ResolvePlayers<C extends ChallengeConfig, P extends object, PS> = {
|
|
525
|
+
[R in RoleOf<C>]: R extends keyof PS ? (PS[R] extends object ? PS[R] : P) : P;
|
|
526
|
+
};
|
|
527
|
+
/** Read-only through every level: the engine writes the observations, a rule reads them. */
|
|
528
|
+
export type DeepReadonly<X> = X extends (infer E)[] ? ReadonlyArray<DeepReadonly<E>> : X extends (...args: never[]) => unknown ? X : X extends object ? {
|
|
529
|
+
readonly [K in keyof X]: DeepReadonly<X[K]>;
|
|
530
|
+
} : X;
|
|
531
|
+
/**
|
|
532
|
+
* The SDK's fields as a rule sees them: `score` is the challenge's to write,
|
|
533
|
+
* everything else is an observation the engine reported (or the SDK derived)
|
|
534
|
+
* and a rule cannot change it. `g.eliminate` sets `eliminated`.
|
|
535
|
+
*/
|
|
536
|
+
export type Observed = DeepReadonly<Omit<Built, "score">> & Pick<Built, "score">;
|
|
537
|
+
/** One participant of role `R`, as a rule sees it: the SDK's fields (observations read-only) plus the challenge's state for that role. */
|
|
538
|
+
export type PlayerOf<C extends ChallengeConfig, PM extends PlayerMap<C>, R extends RoleOf<C>> = Readonly<Roster<C, R>> & Observed & PM[R];
|
|
539
|
+
/** The same participant with the SDK's write access: the state's shape, never a rule's. */
|
|
540
|
+
export type StatePlayerOf<C extends ChallengeConfig, PM extends PlayerMap<C>, R extends RoleOf<C>> = Roster<C, R> & Built & PM[R];
|
|
541
|
+
export type StatePlayer<C extends ChallengeConfig, PM extends PlayerMap<C> = PlayerMap<C>> = {
|
|
542
|
+
[R in RoleOf<C>]: StatePlayerOf<C, PM, R>;
|
|
543
|
+
}[RoleOf<C>];
|
|
544
|
+
/**
|
|
545
|
+
* A participant: one member per role, discriminated by `role`, so `p.role === "p1"`
|
|
546
|
+
* narrows to that role's fields (an inferred type predicate does the same in `filter`).
|
|
547
|
+
*/
|
|
548
|
+
export type Player<C extends ChallengeConfig, PM extends PlayerMap<C> = PlayerMap<C>> = {
|
|
549
|
+
[R in RoleOf<C>]: PlayerOf<C, PM, R>;
|
|
550
|
+
}[RoleOf<C>];
|
|
551
|
+
/** The context type of a built challenge, for tests and helpers called from a rule: `ContextOfChallenge<typeof sky>`. */
|
|
552
|
+
export type ContextOfChallenge<Ch extends {
|
|
553
|
+
ctx: unknown;
|
|
554
|
+
}> = Ch["ctx"];
|
|
555
|
+
/** The player type a context works with: `type Pl = PlayerIn<typeof sky.ctx>`. */
|
|
556
|
+
export type PlayerIn<Cx> = Cx extends {
|
|
557
|
+
readonly players: ReadonlyArray<infer Pl>;
|
|
558
|
+
} ? Pl : never;
|
|
559
|
+
type SdkOwned = keyof Built | keyof Roster<ChallengeConfig>;
|
|
560
|
+
/** A compile-time check: the challenge's per-player fields may not reuse an SDK-owned name. */
|
|
561
|
+
export type NoBuilt<P> = [Extract<keyof P, SdkOwned>] extends [never] ? unknown : {
|
|
562
|
+
"error: this player field is owned by the SDK": Extract<keyof P, SdkOwned>;
|
|
563
|
+
};
|
|
564
|
+
/** The same check over every role of `players`: a required key per offending role, so the error names it. */
|
|
565
|
+
export type NoBuiltIn<PS> = {
|
|
566
|
+
[R in keyof PS as [Extract<keyof PS[R], SdkOwned>] extends [never] ? never : R]: {
|
|
567
|
+
"error: this player field is owned by the SDK": Extract<keyof PS[R], SdkOwned>;
|
|
568
|
+
};
|
|
569
|
+
};
|
|
570
|
+
export interface State<C extends ChallengeConfig, G, PM extends PlayerMap<C>, Ph extends string> {
|
|
571
|
+
seed: number;
|
|
572
|
+
rng: number;
|
|
573
|
+
params: ParamsOf<C>;
|
|
574
|
+
capabilities: Capability[];
|
|
575
|
+
locations: Record<string, Vec3>;
|
|
576
|
+
markers: Marker[];
|
|
577
|
+
status: Status;
|
|
578
|
+
tick: number;
|
|
579
|
+
dt: number;
|
|
580
|
+
startTick: number;
|
|
581
|
+
setupTick: number;
|
|
582
|
+
phase: Ph | "END";
|
|
583
|
+
phaseStartTick: number;
|
|
584
|
+
phaseEntries: number;
|
|
585
|
+
fired: Record<string, true>;
|
|
586
|
+
every: Record<string, number>;
|
|
587
|
+
timers: Record<string, number>;
|
|
588
|
+
players: Record<string, StatePlayer<C, PM>>;
|
|
589
|
+
game: G;
|
|
590
|
+
votes: Record<string, Record<string, string>>;
|
|
591
|
+
walks: Record<string, {
|
|
592
|
+
player: string;
|
|
593
|
+
fallback: "teleport" | "none";
|
|
594
|
+
} & Vec3>;
|
|
595
|
+
/** The declared containers' contents by "x,y,z": slot to item, as the engine reported them. */
|
|
596
|
+
containers: Record<string, {
|
|
597
|
+
block: Block;
|
|
598
|
+
slots: Record<number, {
|
|
599
|
+
item: Item;
|
|
600
|
+
count: number;
|
|
601
|
+
}>;
|
|
602
|
+
}>;
|
|
603
|
+
/** The world mirror. Updated by copy, so a reduction shares it with the state before it. */
|
|
604
|
+
world: WorldState;
|
|
605
|
+
nextId: number;
|
|
606
|
+
winners: string[];
|
|
607
|
+
endState: string;
|
|
608
|
+
}
|
|
609
|
+
export type To<C extends ChallengeConfig, PM extends PlayerMap<C>> = Player<C, PM> | Player<C, PM>[] | "all";
|
|
610
|
+
export interface Ctx<C extends ChallengeConfig, G = Record<string, never>, PM extends PlayerMap<C> = PlayerMap<C>, Ph extends string = string, Z extends string = never, T extends string = never> {
|
|
611
|
+
readonly game: G;
|
|
612
|
+
readonly params: ParamsOf<C>;
|
|
613
|
+
/** Participants in the order of the run's participant list. */
|
|
614
|
+
readonly players: Player<C, PM>[];
|
|
615
|
+
/** Participants still in the game. */
|
|
616
|
+
readonly active: Player<C, PM>[];
|
|
617
|
+
player(name: string): Player<C, PM> | undefined;
|
|
618
|
+
/** The participants of one role, typed with that role's fields. */
|
|
619
|
+
role<R extends RoleOf<C>>(role: R): PlayerOf<C, PM, R>[];
|
|
620
|
+
one<R extends RoleOf<C>>(role: R): PlayerOf<C, PM, R> | undefined;
|
|
621
|
+
readonly tick: number;
|
|
622
|
+
readonly dt: number;
|
|
623
|
+
readonly elapsed: number;
|
|
624
|
+
readonly phase: Ph | "END";
|
|
625
|
+
readonly phaseElapsed: number;
|
|
626
|
+
readonly phaseRemaining: number;
|
|
627
|
+
location(name: LocationOf<C>): Vec3;
|
|
628
|
+
/** Marker entities from `spec.markers`: all of them, or those carrying `tag`. */
|
|
629
|
+
markers(tag?: string): Marker[];
|
|
630
|
+
/**
|
|
631
|
+
* Item counts in a declared container, by its name in `containers` or by position;
|
|
632
|
+
* undefined before the engine reported it. (`g.container(at, items)` is the effect that fills one.)
|
|
633
|
+
*/
|
|
634
|
+
contents(target: string | Vec3): Readonly<Record<string, number>> | undefined;
|
|
635
|
+
/** A declared zone by name, or any box. */
|
|
636
|
+
inZone(player: Player<C, PM>, zone: NoInfer<Z> | Box): boolean;
|
|
637
|
+
count(player: Player<C, PM>, item: Item): number;
|
|
638
|
+
/** Players online who are not participants: the watcher, spectators, humans in the lobby. */
|
|
639
|
+
readonly others: ReadonlyArray<DeepReadonly<OtherPlayer>>;
|
|
640
|
+
/** The block id at `at` (`minecraft:oak_fence`); undefined outside the loaded chunks. */
|
|
641
|
+
blockAt(at: Vec3): Block | undefined;
|
|
642
|
+
/** The full block state at `at` (`minecraft:oak_fence[north=true,...]`). */
|
|
643
|
+
blockStateAt(at: Vec3): Block | undefined;
|
|
644
|
+
/** The world time of day, 0..24000. */
|
|
645
|
+
readonly worldTime: number;
|
|
646
|
+
blockCount(box: Box, block: BlockMatch): number;
|
|
647
|
+
findBlocks(box: Box, block: BlockMatch, limit?: number): Vec3[];
|
|
648
|
+
/** Non-player entities: all, or those in a box, of a type, or with a tag ("kradle.*" matches a prefix). */
|
|
649
|
+
entities(filter?: EntityFilter): ReadonlyArray<DeepReadonly<Entity>>;
|
|
650
|
+
entity(id: string): DeepReadonly<Entity> | undefined;
|
|
651
|
+
/** The accepted votes of one round: voter name to option. A participant without a vote is absent. */
|
|
652
|
+
votes<V extends VoteIdOf<C>>(voteId: V): Readonly<Record<string, OptionFor<C, V>>>;
|
|
653
|
+
vote<V extends VoteIdOf<C>>(player: Player<C, PM>, voteId: V): OptionFor<C, V> | undefined;
|
|
654
|
+
/** Forgets every vote in voteId: a new round. */
|
|
655
|
+
openVote(voteId: VoteIdOf<C>): void;
|
|
656
|
+
goTo(phase: Ph | "END"): void;
|
|
657
|
+
end(endState?: EndStateOf<C>): void;
|
|
658
|
+
eliminate(player: Player<C, PM>): void;
|
|
659
|
+
/** A named timer. `on.timer({ name })` fires when it is due; scheduling again resets it. */
|
|
660
|
+
schedule(name: NoInfer<T>, after: Duration): void;
|
|
661
|
+
cancel(name: NoInfer<T>): void;
|
|
662
|
+
rand(min: number, max: number): number;
|
|
663
|
+
pick<X>(items: readonly X[]): X;
|
|
664
|
+
shuffle<X>(items: readonly X[]): X[];
|
|
665
|
+
say(message: string): void;
|
|
666
|
+
tell(to: To<C, PM>, message: string): void;
|
|
667
|
+
title(to: To<C, PM>, title: string, subtitle?: string | undefined, times?: TitleTimes | undefined): void;
|
|
668
|
+
log(player: Player<C, PM> | null, message: string, data?: Json | undefined): void;
|
|
669
|
+
/** A stored variable: the last value per name reaches the run result (`runs get` shows them). Global without a player. */
|
|
670
|
+
store(name: string, value: number, player?: Player<C, PM> | undefined): void;
|
|
671
|
+
achievement(player: Player<C, PM>, id: string): void;
|
|
672
|
+
give(player: Player<C, PM>, item: Item, count?: number | undefined, nbt?: string | undefined): void;
|
|
673
|
+
clear(player: Player<C, PM>, item?: Item | undefined, count?: number | undefined): void;
|
|
674
|
+
effect(player: Player<C, PM>, effect: EffectId, seconds: number, amplifier?: number): void;
|
|
675
|
+
teleport(player: Player<C, PM>, to: Vec3): void;
|
|
676
|
+
/** The arena walks the bot; a failed walk teleports unless fallback is "none". `on.walkDone` reports. */
|
|
677
|
+
walk(player: Player<C, PM>, to: Vec3, opts?: {
|
|
678
|
+
fallback?: "teleport" | "none";
|
|
679
|
+
}): void;
|
|
680
|
+
kill(player: Player<C, PM>): void;
|
|
681
|
+
gamemode(player: Player<C, PM>, mode: GameMode): void;
|
|
682
|
+
attribute(player: Player<C, PM>, attribute: string, value: number): void;
|
|
683
|
+
fill(from: Vec3, to: Vec3, block: Block): void;
|
|
684
|
+
setblock(at: Vec3, block: Block, nbt?: string | undefined): void;
|
|
685
|
+
summon(entity: EntityType, at: Vec3, nbt?: string | undefined): void;
|
|
686
|
+
/** Removes entities in the run's world; players are never removed. */
|
|
687
|
+
killEntities(entities?: EntityType[] | undefined): void;
|
|
688
|
+
time(value: "day" | "noon" | "night" | "midnight"): void;
|
|
689
|
+
gamerule(rule: string, value: boolean | number): void;
|
|
690
|
+
/** Sets the world spawn: participants respawn there, the watcher and spectators are moved there. */
|
|
691
|
+
setSpawn(at: Vec3): void;
|
|
692
|
+
/** Moves and resizes the world border; `seconds` animates the size change. */
|
|
693
|
+
worldBorder(opts: {
|
|
694
|
+
center?: {
|
|
695
|
+
x: number;
|
|
696
|
+
z: number;
|
|
697
|
+
};
|
|
698
|
+
size?: number;
|
|
699
|
+
seconds?: number;
|
|
700
|
+
}): void;
|
|
701
|
+
/** Replaces the contents of a chest or other container block. */
|
|
702
|
+
container(at: Vec3, items: ContainerItem[]): void;
|
|
703
|
+
/**
|
|
704
|
+
* The escape hatch. Logged. Prefer a typed verb. The arena runs only the
|
|
705
|
+
* gameplay commands on its allowlist (no server administration, no `fill`,
|
|
706
|
+
* `setblock`, `clone`, `place`, `fillbiome`, `data` or `forceload`: a command
|
|
707
|
+
* fires no block event, so the mirror would miss it); a refused command
|
|
708
|
+
* comes back as `effectFailed`.
|
|
709
|
+
*/
|
|
710
|
+
command(cmd: string): void;
|
|
711
|
+
}
|
|
712
|
+
type H<Cx, E> = (g: Cx, e: E) => void;
|
|
713
|
+
/** Handlers receive resolved players. Events for names that are not participants never reach them. */
|
|
714
|
+
export interface Handlers<C extends ChallengeConfig, G, PM extends PlayerMap<C>, Ph extends string, Z extends string, T extends string> {
|
|
715
|
+
tick?: (g: Ctx<C, G, PM, Ph, Z, T>) => void;
|
|
716
|
+
joined?: H<Ctx<C, G, PM, Ph, Z, T>, {
|
|
717
|
+
player: Player<C, PM>;
|
|
718
|
+
}>;
|
|
719
|
+
left?: H<Ctx<C, G, PM, Ph, Z, T>, {
|
|
720
|
+
player: Player<C, PM>;
|
|
721
|
+
}>;
|
|
722
|
+
move?: H<Ctx<C, G, PM, Ph, Z, T>, {
|
|
723
|
+
player: Player<C, PM>;
|
|
724
|
+
from: Vec3 | null;
|
|
725
|
+
to: Vec3;
|
|
726
|
+
maxY: number;
|
|
727
|
+
minY: number;
|
|
728
|
+
}>;
|
|
729
|
+
damaged?: H<Ctx<C, G, PM, Ph, Z, T>, {
|
|
730
|
+
player: Player<C, PM>;
|
|
731
|
+
amount: number;
|
|
732
|
+
cause: string;
|
|
733
|
+
attacker?: Player<C, PM> | undefined;
|
|
734
|
+
}>;
|
|
735
|
+
died?: H<Ctx<C, G, PM, Ph, Z, T>, {
|
|
736
|
+
player: Player<C, PM>;
|
|
737
|
+
cause: string;
|
|
738
|
+
killer?: Player<C, PM> | undefined;
|
|
739
|
+
}>;
|
|
740
|
+
respawned?: H<Ctx<C, G, PM, Ph, Z, T>, {
|
|
741
|
+
player: Player<C, PM>;
|
|
742
|
+
}>;
|
|
743
|
+
entitySpawned?: H<Ctx<C, G, PM, Ph, Z, T>, {
|
|
744
|
+
entity: Entity;
|
|
745
|
+
}>;
|
|
746
|
+
entityRemoved?: H<Ctx<C, G, PM, Ph, Z, T>, {
|
|
747
|
+
entity: Entity;
|
|
748
|
+
cause: "died" | "removed";
|
|
749
|
+
killer?: Player<C, PM> | undefined;
|
|
750
|
+
}>;
|
|
751
|
+
pickup?: H<Ctx<C, G, PM, Ph, Z, T>, {
|
|
752
|
+
player: Player<C, PM>;
|
|
753
|
+
item: Item;
|
|
754
|
+
count: number;
|
|
755
|
+
at: Vec3;
|
|
756
|
+
}>;
|
|
757
|
+
/** A participant's count of one item changed. */
|
|
758
|
+
inventory?: H<Ctx<C, G, PM, Ph, Z, T>, {
|
|
759
|
+
player: Player<C, PM>;
|
|
760
|
+
item: Item;
|
|
761
|
+
count: number;
|
|
762
|
+
}>;
|
|
763
|
+
blockBroken?: H<Ctx<C, G, PM, Ph, Z, T>, {
|
|
764
|
+
player: Player<C, PM>;
|
|
765
|
+
at: Vec3;
|
|
766
|
+
block: Block;
|
|
767
|
+
}>;
|
|
768
|
+
blockPlaced?: H<Ctx<C, G, PM, Ph, Z, T>, {
|
|
769
|
+
player: Player<C, PM>;
|
|
770
|
+
at: Vec3;
|
|
771
|
+
block: Block;
|
|
772
|
+
}>;
|
|
773
|
+
blockUsed?: H<Ctx<C, G, PM, Ph, Z, T>, {
|
|
774
|
+
player: Player<C, PM>;
|
|
775
|
+
at: Vec3;
|
|
776
|
+
block: Block;
|
|
777
|
+
item?: Item | undefined;
|
|
778
|
+
}>;
|
|
779
|
+
/** A declared container's contents changed: the counts after the change and the deltas. */
|
|
780
|
+
container?: H<Ctx<C, G, PM, Ph, Z, T>, {
|
|
781
|
+
at: Vec3;
|
|
782
|
+
block: Block;
|
|
783
|
+
items: Readonly<Record<string, number>>;
|
|
784
|
+
changes: Array<{
|
|
785
|
+
item: Item;
|
|
786
|
+
delta: number;
|
|
787
|
+
}>;
|
|
788
|
+
by?: Player<C, PM> | undefined;
|
|
789
|
+
}>;
|
|
790
|
+
/** Every block that changed this tick, whatever the cause. */
|
|
791
|
+
blocksChanged?: H<Ctx<C, G, PM, Ph, Z, T>, {
|
|
792
|
+
changes: Array<Vec3 & {
|
|
793
|
+
block: Block;
|
|
794
|
+
was?: Block | undefined;
|
|
795
|
+
cause?: string | undefined;
|
|
796
|
+
by?: Player<C, PM> | undefined;
|
|
797
|
+
}>;
|
|
798
|
+
}>;
|
|
799
|
+
zoneEntered?: H<Ctx<C, G, PM, Ph, Z, T>, {
|
|
800
|
+
player: Player<C, PM>;
|
|
801
|
+
zone: Z;
|
|
802
|
+
}>;
|
|
803
|
+
zoneLeft?: H<Ctx<C, G, PM, Ph, Z, T>, {
|
|
804
|
+
player: Player<C, PM>;
|
|
805
|
+
zone: Z;
|
|
806
|
+
}>;
|
|
807
|
+
chat?: H<Ctx<C, G, PM, Ph, Z, T>, {
|
|
808
|
+
player: Player<C, PM>;
|
|
809
|
+
text: string;
|
|
810
|
+
}>;
|
|
811
|
+
/** A participant swung an arm: a mining or attack attempt (`swing`), or the off hand (`swing_off`). */
|
|
812
|
+
animation?: H<Ctx<C, G, PM, Ph, Z, T>, {
|
|
813
|
+
player: Player<C, PM>;
|
|
814
|
+
animation: "swing" | "swing_off";
|
|
815
|
+
}>;
|
|
816
|
+
/** A participant's game mode changed. */
|
|
817
|
+
gamemode?: H<Ctx<C, G, PM, Ph, Z, T>, {
|
|
818
|
+
player: Player<C, PM>;
|
|
819
|
+
gamemode: GameMode;
|
|
820
|
+
}>;
|
|
821
|
+
/** Accepted votes only. The SDK answered the rejected ones. */
|
|
822
|
+
vote?: H<Ctx<C, G, PM, Ph, Z, T>, {
|
|
823
|
+
[V in VoteIdOf<C>]: {
|
|
824
|
+
player: Player<C, PM>;
|
|
825
|
+
voteId: V;
|
|
826
|
+
option: OptionFor<C, V>;
|
|
827
|
+
};
|
|
828
|
+
}[VoteIdOf<C>]>;
|
|
829
|
+
walkDone?: H<Ctx<C, G, PM, Ph, Z, T>, {
|
|
830
|
+
player: Player<C, PM>;
|
|
831
|
+
ok: boolean;
|
|
832
|
+
}>;
|
|
833
|
+
timer?: H<Ctx<C, G, PM, Ph, Z, T>, {
|
|
834
|
+
name: T;
|
|
835
|
+
}>;
|
|
836
|
+
effectFailed?: H<Ctx<C, G, PM, Ph, Z, T>, {
|
|
837
|
+
kind: string;
|
|
838
|
+
detail: string;
|
|
839
|
+
}>;
|
|
840
|
+
custom?: H<Ctx<C, G, PM, Ph, Z, T>, {
|
|
841
|
+
name: string;
|
|
842
|
+
player?: Player<C, PM> | undefined;
|
|
843
|
+
data: Json;
|
|
844
|
+
}>;
|
|
845
|
+
}
|
|
846
|
+
export type PhaseStep<Cx> = ({
|
|
847
|
+
elapsed: Duration;
|
|
848
|
+
} | {
|
|
849
|
+
remaining: Duration;
|
|
850
|
+
}) & {
|
|
851
|
+
run: (g: Cx) => void;
|
|
852
|
+
};
|
|
853
|
+
export interface PhaseSpec<C extends ChallengeConfig, G, PM extends PlayerMap<C>, Ph extends string, Z extends string, T extends string> {
|
|
854
|
+
duration?: Duration;
|
|
855
|
+
enter?: (g: Ctx<C, G, PM, Ph, Z, T>) => void;
|
|
856
|
+
exit?: (g: Ctx<C, G, PM, Ph, Z, T>) => void;
|
|
857
|
+
/** Phase names come from the keys of `phases`, never from a `next` literal. */
|
|
858
|
+
next?: NoInfer<Ph> | "END" | ((g: Ctx<C, G, PM, Ph, Z, T>) => NoInfer<Ph> | "END");
|
|
859
|
+
/** Once per phase entry: `elapsed` since entry, or `remaining` before its end (needs `duration`). */
|
|
860
|
+
at?: Array<PhaseStep<Ctx<C, G, PM, Ph, Z, T>>>;
|
|
861
|
+
every?: Array<{
|
|
862
|
+
interval: Duration;
|
|
863
|
+
run: (g: Ctx<C, G, PM, Ph, Z, T>) => void;
|
|
864
|
+
}>;
|
|
865
|
+
/** Handlers that run only in this phase, after the global `on` for the same event. */
|
|
866
|
+
on?: Handlers<C, G, PM, Ph, Z, T>;
|
|
867
|
+
}
|
|
868
|
+
/** Cost knobs for the world mirror. Everything is on by default; a large world can narrow it. */
|
|
869
|
+
export interface Mirror {
|
|
870
|
+
/** "participants" leaves the watcher, spectators and lobby players out of the ticks. */
|
|
871
|
+
players?: "all" | "participants";
|
|
872
|
+
entities?: boolean | {
|
|
873
|
+
within: Box;
|
|
874
|
+
};
|
|
875
|
+
blocks?: boolean | {
|
|
876
|
+
within: Box;
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
/** World setup the SDK applies before the game. Every field but `spawn` has today's default (an unset `spawn` emits nothing); `false` on `world` skips all of it. */
|
|
880
|
+
export interface World {
|
|
881
|
+
/** The world spawn: participants respawn there, the watcher and spectators are moved there. */
|
|
882
|
+
spawn?: Vec3;
|
|
883
|
+
time?: "day" | "noon" | "night" | "midnight" | false;
|
|
884
|
+
daylightCycle?: boolean;
|
|
885
|
+
mobSpawning?: boolean;
|
|
886
|
+
naturalRegeneration?: boolean;
|
|
887
|
+
maxHealth?: number | false;
|
|
888
|
+
}
|
|
889
|
+
export declare const WORLD_DEFAULTS: Required<Omit<World, "spawn">>;
|
|
890
|
+
export interface Spec<C extends ChallengeConfig, G, PM extends PlayerMap<C>, Ph extends string, Z extends string, T extends string> {
|
|
891
|
+
/** Time from game start to the end. Omit for no time limit. */
|
|
892
|
+
duration?: Duration;
|
|
893
|
+
/** "single": one death eliminates (spectator). "respawn": deaths count, the player plays on. */
|
|
894
|
+
life: "single" | "respawn";
|
|
895
|
+
world?: World | false;
|
|
896
|
+
announce?: {
|
|
897
|
+
deaths?: boolean;
|
|
898
|
+
gameOver?: boolean;
|
|
899
|
+
};
|
|
900
|
+
mirror?: Mirror;
|
|
901
|
+
/** Named boxes, evaluated every tick from the players' positions: `on.zoneEntered/zoneLeft`, `g.inZone`, `player.zones`. */
|
|
902
|
+
zones?: Record<Z, Box>;
|
|
903
|
+
/** Timer names for `g.schedule` / `on.timer`. */
|
|
904
|
+
timers?: readonly T[];
|
|
905
|
+
/** Tagged entities to read from the world before the game (`g.markers`). Chunks in `within` are loaded first. */
|
|
906
|
+
markers?: {
|
|
907
|
+
tags: string[];
|
|
908
|
+
within?: Box;
|
|
909
|
+
};
|
|
910
|
+
/**
|
|
911
|
+
* Named containers (chests, barrels) whose contents the engine reports: `g.contents(name)`,
|
|
912
|
+
* `g.contents(at)`, `on.container`. The rules read what is in the chest, not what left a hand.
|
|
913
|
+
*/
|
|
914
|
+
containers?: Record<string, Vec3>;
|
|
915
|
+
/** World build. Its effects go out as ONE batch; the game starts after the engine confirms it. */
|
|
916
|
+
setup?: (g: Ctx<C, G, PM, Ph, Z, T>) => void;
|
|
917
|
+
/** Runs on the first tick of PLAYING, after the setup batch is confirmed. */
|
|
918
|
+
start?: (g: Ctx<C, G, PM, Ph, Z, T>) => void;
|
|
919
|
+
phases?: Record<Ph, PhaseSpec<C, G, PM, Ph, Z, T>>;
|
|
920
|
+
/** One-shot steps `at` after game start, `>=` semantics. */
|
|
921
|
+
at?: Array<{
|
|
922
|
+
at: Duration;
|
|
923
|
+
run: (g: Ctx<C, G, PM, Ph, Z, T>) => void;
|
|
924
|
+
}>;
|
|
925
|
+
/** Repeating steps. After a gap they run once, not once per missed interval. */
|
|
926
|
+
every?: Array<{
|
|
927
|
+
interval: Duration;
|
|
928
|
+
run: (g: Ctx<C, G, PM, Ph, Z, T>) => void;
|
|
929
|
+
}>;
|
|
930
|
+
/** Vote ids and options come from config.challengeConfig.votingOptions. */
|
|
931
|
+
votes?: {
|
|
932
|
+
[V in VoteIdOf<C>]?: {
|
|
933
|
+
/** `true` (the default, also for a vote with no rule): a participant votes once per round. `false`: a new vote replaces the old one. */
|
|
934
|
+
final?: boolean;
|
|
935
|
+
phases?: NoInfer<Ph>[];
|
|
936
|
+
/** The challenge's veto. `true` accepts; a string refuses with that feedback to the voter. */
|
|
937
|
+
open?: (g: Ctx<C, G, PM, Ph, Z, T>, player: Player<C, PM>, option: OptionFor<C, V>) => true | string;
|
|
938
|
+
};
|
|
939
|
+
};
|
|
940
|
+
on?: Handlers<C, G, PM, Ph, Z, T>;
|
|
941
|
+
/** Extra end condition, checked after every reduction. */
|
|
942
|
+
end?: (g: Ctx<C, G, PM, Ph, Z, T>) => boolean;
|
|
943
|
+
winners?: (g: Ctx<C, G, PM, Ph, Z, T>) => ReadonlyArray<{
|
|
944
|
+
name: string;
|
|
945
|
+
}>;
|
|
946
|
+
/** Runs once the winners and end state are final, before game_over: result titles, a last announcement. */
|
|
947
|
+
onOver?: (g: Ctx<C, G, PM, Ph, Z, T>, result: OverResult<C, PM>) => void;
|
|
948
|
+
endState?: (g: Ctx<C, G, PM, Ph, Z, T>) => EndStateOf<C>;
|
|
949
|
+
/** An object sent with `game_over` for the LLM judge (the challenge never calls the judge); the backend takes an object, not another JSON value. */
|
|
950
|
+
judgeContext?: (g: Ctx<C, G, PM, Ph, Z, T>) => Record<string, Json>;
|
|
951
|
+
}
|
|
952
|
+
/** What the arena reads before the run. */
|
|
953
|
+
export interface RuntimeManifest {
|
|
954
|
+
runtime: 1;
|
|
955
|
+
name: string;
|
|
956
|
+
life: "single" | "respawn";
|
|
957
|
+
/** The role ids the rules know: the backend checks them against the challenge's configuration at activation. */
|
|
958
|
+
roles: string[];
|
|
959
|
+
/** The location names the configuration declares, when it declares any. */
|
|
960
|
+
locations?: string[] | undefined;
|
|
961
|
+
mirror?: Mirror | undefined;
|
|
962
|
+
markers?: {
|
|
963
|
+
tags: string[];
|
|
964
|
+
within?: Box | undefined;
|
|
965
|
+
} | undefined;
|
|
966
|
+
/** The containers the rules read (`containers` in the spec): the engine reports their contents. */
|
|
967
|
+
containers?: Vec3[] | undefined;
|
|
968
|
+
}
|
|
969
|
+
export interface Challenge<C extends ChallengeConfig, G, PM extends PlayerMap<C>, Ph extends string, Z extends string = never, T extends string = never> {
|
|
970
|
+
config: C;
|
|
971
|
+
manifest: RuntimeManifest;
|
|
972
|
+
/** The declared zones, for tests. */
|
|
973
|
+
zones: Record<string, Box>;
|
|
974
|
+
/**
|
|
975
|
+
* A type, not a value: `type G = typeof sky.ctx` is the context a helper
|
|
976
|
+
* function takes. Reading it at run time throws. Call helpers from inside a
|
|
977
|
+
* function (`endState: (g) => classify(g)`); passing one as a property value
|
|
978
|
+
* (`endState: classify`) makes the challenge's type depend on itself.
|
|
979
|
+
*/
|
|
980
|
+
readonly ctx: Ctx<C, G, PM, Ph, Z, T>;
|
|
981
|
+
init(): State<C, G, PM, Ph>;
|
|
982
|
+
reduce(s: State<C, G, PM, Ph>, e: Event): {
|
|
983
|
+
state: State<C, G, PM, Ph>;
|
|
984
|
+
actions: Action[];
|
|
985
|
+
};
|
|
986
|
+
}
|
|
987
|
+
/** The shape every built challenge bundle exports as `default`. */
|
|
988
|
+
export type AnyChallenge = Challenge<any, any, any, any, any, any>;
|
|
989
|
+
/** The state initializers. `player` is the state of every role; `players` overrides it per role. */
|
|
990
|
+
export interface StateDecl<C extends ChallengeConfig, G, P, PS> {
|
|
991
|
+
game?: () => G;
|
|
992
|
+
player?: (roster: Roster<C>) => P;
|
|
993
|
+
players?: {
|
|
994
|
+
[R in keyof PS]: (roster: Roster<C, R & RoleOf<C>>) => PS[R];
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* The context type for helper functions, from the config and the object that
|
|
999
|
+
* holds the state initializers (and any zones or timers the helpers use),
|
|
1000
|
+
* before the challenge exists:
|
|
1001
|
+
*
|
|
1002
|
+
* const decl = { game: () => ({ chests: [] as Vec3[] }), player: () => ({ island: "" }) };
|
|
1003
|
+
* type G = ContextOf<typeof config, typeof decl>;
|
|
1004
|
+
* export default defineChallenge(config, { ...decl, ...rules });
|
|
1005
|
+
*
|
|
1006
|
+
* `typeof x.ctx` on the challenge gives the same type, but only helpers called
|
|
1007
|
+
* from inside a rule may use it (`endState: (g) => classify(g)`): a helper
|
|
1008
|
+
* passed as the property value (`endState: classify`) makes the challenge's
|
|
1009
|
+
* type circular.
|
|
1010
|
+
*/
|
|
1011
|
+
export type ContextOf<C extends ChallengeConfig, D> = Ctx<C, D extends {
|
|
1012
|
+
game: () => infer G;
|
|
1013
|
+
} ? G : Record<string, never>, ResolvePlayers<C, D extends {
|
|
1014
|
+
player: (...a: never[]) => infer P;
|
|
1015
|
+
} ? P extends object ? P : Record<string, never> : Record<string, never>, D extends {
|
|
1016
|
+
players: infer PS;
|
|
1017
|
+
} ? {
|
|
1018
|
+
[R in keyof PS]: PS[R] extends (...a: never[]) => infer S ? S : never;
|
|
1019
|
+
} : Record<never, never>>, string, D extends {
|
|
1020
|
+
zones: infer Zs;
|
|
1021
|
+
} ? keyof Zs & string : never, D extends {
|
|
1022
|
+
timers: readonly (infer T)[];
|
|
1023
|
+
} ? (T extends string ? T : never) : never>;
|
|
1024
|
+
type AnyCtx = Ctx<ChallengeConfig, unknown, PlayerMap<ChallengeConfig>, string, string, string>;
|
|
1025
|
+
type AnyPlayer = Player<ChallengeConfig>;
|
|
1026
|
+
export declare const End: {
|
|
1027
|
+
allEliminated: (g: AnyCtx) => boolean;
|
|
1028
|
+
};
|
|
1029
|
+
export declare const Win: {
|
|
1030
|
+
/** Highest score among the eligible players (default: still in the game), if it reaches `min` (default 1). Ties win together. */
|
|
1031
|
+
topScore: (opts?: {
|
|
1032
|
+
min?: number;
|
|
1033
|
+
eligible?: (p: AnyPlayer) => boolean;
|
|
1034
|
+
}) => (g: AnyCtx) => AnyPlayer[];
|
|
1035
|
+
scoreAtLeast: (min: number) => (g: AnyCtx) => AnyPlayer[];
|
|
1036
|
+
};
|
|
1037
|
+
export declare function defineChallenge<const C extends ChallengeConfig, G extends object = Record<string, never>, P extends object = Record<string, never>, PS extends Partial<Record<RoleOf<C>, object>> = Record<never, never>, Ph extends string = "MAIN", Z extends string = never, T extends string = never>(config: C, spec: Spec<C, G, ResolvePlayers<C, P, PS>, Ph, Z, T> & StateDecl<C, G, P, PS> & NoBuilt<P> & NoBuiltIn<PS>): Challenge<C, G, ResolvePlayers<C, P, PS>, Ph, Z, T>;
|
|
1038
|
+
//# sourceMappingURL=sdk.d.ts.map
|