yog_core/server.rs
1//! The handle Rust mods use to act on the running server — the Rust → Minecraft
2//! path.
3
4use crate::{BlockHit, BlockInfo, BlockPos, EntityInfo, Face, PlayerInfo};
5
6/// Low-level capabilities the Yog runtime exposes to mods (the engine contract).
7///
8/// The runtime provides the concrete implementation, backed by JNI calls into
9/// the Java host; this crate stays JVM-free. Higher-level, ergonomic wrappers
10/// live in the domain crates (e.g. `yog-world`'s `World`).
11///
12/// Dimensions are identified by their registry id string, e.g.
13/// `"minecraft:overworld"`.
14pub trait Server {
15 /// Broadcast a chat message to all players on the server.
16 fn broadcast(&self, message: &str);
17
18 /// Registry id of the block at `pos` in `dimension`
19 /// (e.g. `"minecraft:stone"`), or `None` if the dimension/position is
20 /// unavailable. Call from the server thread (e.g. an event handler).
21 fn get_block(&self, dimension: &str, pos: BlockPos) -> Option<String>;
22
23 /// Set the block at `pos` in `dimension` to `block_id` — a registry id
24 /// or, like `/setblock`, a state (`"minecraft:water_cauldron[level=3]"`).
25 /// Returns whether the change was applied. Call from the server thread.
26 fn set_block(&self, dimension: &str, pos: BlockPos, block_id: &str) -> bool;
27
28 /// Give `count` of `item_id` to the named player. Returns whether it worked
29 /// (player online, item valid). Call from the server thread.
30 fn give_item(&self, player: &str, item_id: &str, count: u32) -> bool;
31
32 /// Teleport the named player to `(x, y, z)` in their current world. Returns
33 /// whether it worked. Call from the server thread.
34 fn teleport(&self, player: &str, x: f64, y: f64, z: f64) -> bool;
35
36 /// Send a raw-byte packet to the named player on `channel` (server → client).
37 /// Returns whether the player was online. Payload is opaque bytes — no NBT.
38 fn send_to_player(&self, player: &str, channel: &str, payload: &[u8]) -> bool;
39
40 /// Send a raw-byte packet to the server on `channel` (client → server).
41 /// Only works in a client context; returns whether it was sent.
42 fn send_to_server(&self, channel: &str, payload: &[u8]) -> bool;
43
44 // ── entity (universal, by UUID) ─────────────────────────────────────────
45
46 /// Teleport any entity (by UUID) within its current world.
47 fn entity_teleport(&self, uuid: &str, x: f64, y: f64, z: f64) -> bool;
48
49 /// Position of an entity, or `None` if not loaded.
50 fn entity_position(&self, uuid: &str) -> Option<(f64, f64, f64)>;
51
52 /// Health of a living entity, or `None`.
53 fn entity_health(&self, uuid: &str) -> Option<f32>;
54
55 /// Set a living entity's health; returns whether it applied.
56 fn entity_set_health(&self, uuid: &str, health: f32) -> bool;
57
58 /// Remove/kill an entity.
59 fn entity_kill(&self, uuid: &str) -> bool;
60
61 /// Spawn an entity of `entity_type` (e.g. `minecraft:pig`) at a position;
62 /// returns its UUID, or `None` on failure.
63 fn spawn_entity(
64 &self,
65 entity_type: &str,
66 dimension: &str,
67 x: f64,
68 y: f64,
69 z: f64,
70 ) -> Option<String>;
71
72 // ── status effects ──────────────────────────────────────────────────────
73
74 /// Apply a status effect to a living entity. `effect_id` is a registry id
75 /// such as `"minecraft:speed"` or `"minecraft:regeneration"`. `amplifier` is
76 /// 0-based (0 = level I). Returns `false` if the entity or effect is unknown.
77 fn entity_add_effect(
78 &self,
79 uuid: &str,
80 effect_id: &str,
81 duration_ticks: i32,
82 amplifier: u8,
83 show_particles: bool,
84 ) -> bool;
85
86 /// Remove a single status effect from a living entity.
87 fn entity_remove_effect(&self, uuid: &str, effect_id: &str) -> bool;
88
89 /// Clear all active status effects from a living entity.
90 fn entity_clear_effects(&self, uuid: &str) -> bool;
91
92 // ── loot tables ─────────────────────────────────────────────────────────
93
94 /// Roll a loot table and spawn the resulting item entities in the world.
95 /// `table_id` is the namespaced id, e.g. `"minecraft:entities/zombie"`.
96 fn drop_loot(&self, table_id: &str, dimension: &str, x: f64, y: f64, z: f64) -> bool;
97
98 // ── tag queries ─────────────────────────────────────────────────────────
99
100 /// Returns whether `item_id` (e.g. `"minecraft:stone"`) belongs to `tag_id`
101 /// (e.g. `"minecraft:planks"`).
102 fn has_item_tag(&self, item_id: &str, tag_id: &str) -> bool;
103
104 /// Returns whether `block_id` belongs to `tag_id`.
105 fn has_block_tag(&self, block_id: &str, tag_id: &str) -> bool;
106
107 // ── world state ─────────────────────────────────────────────────────────
108
109 /// Game time in ticks since world creation (never wraps, keeps counting).
110 fn world_time(&self, dimension: &str) -> Option<i64>;
111
112 /// Set the time-of-day (0 = dawn, 6000 = noon, 12000 = dusk, 18000 = midnight).
113 /// Only changes the visual time, not the absolute world age.
114 fn world_set_time(&self, dimension: &str, time: i64) -> bool;
115
116 /// Whether it is currently raining in the given dimension.
117 fn world_is_raining(&self, dimension: &str) -> bool;
118
119 /// Start or stop rain. `duration_ticks` controls how long the weather lasts
120 /// (use 0 for a server-chosen default duration).
121 fn world_set_weather(&self, dimension: &str, raining: bool, duration_ticks: i32) -> bool;
122
123 // ── entity velocity ─────────────────────────────────────────────────────
124
125 /// Current velocity `(vx, vy, vz)` of any entity, or `None` if not loaded.
126 fn entity_velocity(&self, uuid: &str) -> Option<(f64, f64, f64)>;
127
128 /// Set the velocity of any entity. Returns `false` if not loaded.
129 fn entity_set_velocity(&self, uuid: &str, vx: f64, vy: f64, vz: f64) -> bool;
130
131 /// Add a velocity impulse (cumulative with existing velocity).
132 fn entity_add_velocity(&self, uuid: &str, vx: f64, vy: f64, vz: f64) -> bool;
133
134 // ── scoreboard ──────────────────────────────────────────────────────────
135
136 /// Score of `player` on `objective`, or `None` if the objective doesn't exist.
137 fn scoreboard_get(&self, objective: &str, player: &str) -> Option<i32>;
138
139 /// Set the score; returns `false` if the objective doesn't exist.
140 fn scoreboard_set(&self, objective: &str, player: &str, score: i32) -> bool;
141
142 /// Add `delta` to the score (negative = subtract). Returns the new score,
143 /// or `None` if the objective doesn't exist.
144 fn scoreboard_add(&self, objective: &str, player: &str, delta: i32) -> Option<i32>;
145
146 // ── sound ───────────────────────────────────────────────────────────────
147
148 /// Play a sound at `(x, y, z)` in `dimension`. `sound_id` is a registry id
149 /// (e.g. `"minecraft:entity.player.levelup"`). All players within range hear
150 /// it. Returns `false` if the dimension is unknown.
151 fn play_sound(
152 &self,
153 dimension: &str,
154 x: f64,
155 y: f64,
156 z: f64,
157 sound_id: &str,
158 volume: f32,
159 pitch: f32,
160 ) -> bool;
161
162 /// Play a sound at the named player's current position. All players nearby
163 /// (including the target) hear it. Returns `false` if the player is offline.
164 fn play_sound_to_player(&self, player: &str, sound_id: &str, volume: f32, pitch: f32) -> bool;
165
166 // ── title / actionbar ───────────────────────────────────────────────────
167
168 /// Send a title+subtitle screen to a player. Pass empty strings to omit
169 /// either line. Timings are in ticks (20 ticks = 1 second).
170 fn send_title(
171 &self,
172 player: &str,
173 title: &str,
174 subtitle: &str,
175 fadein: i32,
176 stay: i32,
177 fadeout: i32,
178 ) -> bool;
179
180 /// Send a short message to the action-bar (the line just above the hotbar).
181 fn send_actionbar(&self, player: &str, message: &str) -> bool;
182
183 /// Yaw and pitch (degrees) of an entity by UUID, or `None` if it does not
184 /// exist. Minecraft convention: yaw 0 = south, positive pitch looks down.
185 fn entity_rotation(&self, uuid: &str) -> Option<(f32, f32)>;
186
187 // ── player management ───────────────────────────────────────────────────
188
189 /// Disconnect `player` with the given `reason` message.
190 fn kick_player(&self, player: &str, reason: &str) -> bool;
191
192 /// Change a player's game mode. `gamemode` is one of `"survival"`,
193 /// `"creative"`, `"adventure"`, `"spectator"` (or the abbreviations
194 /// `"s"`, `"c"`, `"a"`, `"sp"`). Returns `false` if the player is offline
195 /// or `gamemode` is unrecognised.
196 fn set_gamemode(&self, player: &str, gamemode: &str) -> bool;
197
198 // ── boss bar ────────────────────────────────────────────────────────────
199
200 /// Create a new boss bar identified by `id` (a namespaced id such as
201 /// `"mymod:progress"`). `color`: `"pink"` / `"blue"` / `"red"` / `"green"` /
202 /// `"yellow"` / `"purple"` / `"white"`. `style`: `"progress"` /
203 /// `"notched_6"` / `"notched_10"` / `"notched_12"` / `"notched_20"`.
204 /// Returns `false` if a bar with that id already exists.
205 fn bossbar_create(&self, id: &str, title: &str, color: &str, style: &str) -> bool;
206
207 /// Remove a boss bar (also removes it from all players). Returns `false` if
208 /// the bar doesn't exist.
209 fn bossbar_remove(&self, id: &str) -> bool;
210
211 /// Update the displayed title of a boss bar.
212 fn bossbar_set_title(&self, id: &str, title: &str) -> bool;
213
214 /// Set the fill level of a boss bar (0.0 = empty, 1.0 = full).
215 fn bossbar_set_progress(&self, id: &str, progress: f32) -> bool;
216
217 /// Change the color of a boss bar (same color names as [`bossbar_create`]).
218 fn bossbar_set_color(&self, id: &str, color: &str) -> bool;
219
220 /// Add an online player to the boss bar's audience.
221 fn bossbar_add_player(&self, id: &str, player: &str) -> bool;
222
223 /// Remove a player from the boss bar's audience.
224 fn bossbar_remove_player(&self, id: &str, player: &str) -> bool;
225
226 /// Show or hide a boss bar for all its current audience members.
227 fn bossbar_set_visible(&self, id: &str, visible: bool) -> bool;
228
229 // ── misc ────────────────────────────────────────────────────────────────
230
231 /// Absolute path of the game / server root directory: what belongs to
232 /// the install (configs), not to a world.
233 fn game_dir(&self) -> String;
234
235 /// Absolute path of the directory of the world being played (its save) —
236 /// where what belongs to this world is kept ([`crate`]'s users open
237 /// their world storage here). The game's directory where a host can't
238 /// say (an older one).
239 fn world_dir(&self) -> String {
240 self.game_dir()
241 }
242
243 /// Names of all currently connected players.
244 fn online_players(&self) -> Vec<String>;
245
246 // ── block entity ─────────────────────────────────────────────────────────
247
248 /// SNBT string of the block entity at `pos` (e.g. chest contents, furnace
249 /// state, sign text). Returns `None` if there is no block entity there.
250 fn get_block_nbt(&self, dimension: &str, pos: BlockPos) -> Option<String>;
251
252 /// Write `snbt` data into the block entity at `pos` and mark it dirty.
253 /// Returns `false` if there is no block entity at that position.
254 fn set_block_nbt(&self, dimension: &str, pos: BlockPos, snbt: &str) -> bool;
255
256 // ── inventory-backed block slots (yog-inventory) ─────────────────────────
257
258 /// `(item_id, count, snbt)` of one slot of the block container at `pos`
259 /// — a Yog inventory block (see `yog_inventory::InventoryDef`), a chest,
260 /// any container. `snbt` is `"{}"` when the item carries no data.
261 /// `None` if the slot is empty or there's no container there.
262 fn get_inventory_slot(
263 &self,
264 dimension: &str,
265 pos: BlockPos,
266 slot: u32,
267 ) -> Option<(String, u32, String)>;
268
269 /// Puts `count` of `item_id`, carrying `snbt` (`""` for none), into one
270 /// slot of the block container at `pos`; `count == 0` empties it.
271 /// `false` if there's no container there (or the item id is unknown).
272 fn set_inventory_slot(
273 &self,
274 dimension: &str,
275 pos: BlockPos,
276 slot: u32,
277 item_id: &str,
278 count: u32,
279 snbt: &str,
280 ) -> bool;
281
282 // ── inventory ────────────────────────────────────────────────────────────
283
284 /// All occupied inventory slots of an online player.
285 /// Returns one entry per occupied slot: `(slot_index, item_id, count)`.
286 fn player_inventory(&self, player: &str) -> Vec<(u32, String, u32)>;
287
288 /// Set (or clear when `count == 0`) one inventory slot of an online player.
289 fn player_set_slot(&self, player: &str, slot: u32, item_id: &str, count: u32) -> bool;
290
291 // ── cross-dimension teleport ─────────────────────────────────────────────
292
293 /// Teleport a player to `(x, y, z)` in a different (or same) dimension.
294 fn teleport_to_dim(&self, player: &str, dimension: &str, x: f64, y: f64, z: f64) -> bool;
295
296 /// Teleport any entity (by UUID) to `(x, y, z)` in `dimension`.
297 fn entity_teleport_to_dim(&self, uuid: &str, dimension: &str, x: f64, y: f64, z: f64) -> bool;
298
299 // ── entity query ─────────────────────────────────────────────────────────
300
301 /// Number of loaded entities of `entity_type` (e.g. `"minecraft:zombie"`)
302 /// in `dimension`. Returns `-1` if the dimension or entity type is unknown.
303 fn world_entity_count(&self, dimension: &str, entity_type: &str) -> i32;
304
305 // ── entity NBT ───────────────────────────────────────────────────────────
306
307 /// SNBT string of the entity's persistent NBT, or `None` if not found.
308 fn entity_get_nbt(&self, uuid: &str) -> Option<String>;
309
310 /// Merge SNBT data into the entity's persistent NBT. Returns `false` if not found.
311 fn entity_set_nbt(&self, uuid: &str, snbt: &str) -> bool;
312
313 // ── particles ────────────────────────────────────────────────────────────
314
315 /// Spawn `count` particles at `(x, y, z)` in `dimension`.
316 /// `particle_type` is a particle id or, like `/particle`, an id with its
317 /// options (`"minecraft:dust 1 0.5 0 1"`).
318 /// `dx/dy/dz` control the spatial spread; `speed` controls particle velocity.
319 fn spawn_particles(
320 &self,
321 dimension: &str,
322 x: f64,
323 y: f64,
324 z: f64,
325 particle_type: &str,
326 count: i32,
327 dx: f64,
328 dy: f64,
329 dz: f64,
330 speed: f64,
331 ) -> bool;
332
333 // ── attributes ───────────────────────────────────────────────────────────
334
335 /// Get the base value of an attribute on a living entity.
336 /// `attribute_id` e.g. `"minecraft:generic.max_health"`.
337 /// Returns `None` if the entity or attribute is not found.
338 fn entity_attribute_get(&self, uuid: &str, attribute_id: &str) -> Option<f64>;
339
340 /// Set the base value of an attribute. Returns false if not found.
341 fn entity_attribute_set(&self, uuid: &str, attribute_id: &str, value: f64) -> bool;
342
343 // ── held item NBT (ABI minor 11) ─────────────────────────────────────────
344
345 /// SNBT string of the item currently held in the player's main hand.
346 /// Returns `None` if the player is offline or holding air.
347 fn get_held_item_nbt(&self, player: &str) -> Option<String>;
348
349 /// Merge `snbt` data into the NBT of the item in the player's main hand.
350 /// Returns `false` if the player is offline or holding air.
351 fn set_held_item_nbt(&self, player: &str, snbt: &str) -> bool;
352
353 // ── item stack query (ABI minor 12) ──────────────────────────────────────
354
355 /// SNBT of the item in the player's off hand.
356 /// Returns `None` if offline or holding air.
357 fn get_offhand_item_nbt(&self, player: &str) -> Option<String>;
358
359 /// Merge `snbt` into the NBT of the player's off-hand item.
360 /// Returns `false` if offline or holding air.
361 fn set_offhand_item_nbt(&self, player: &str, snbt: &str) -> bool;
362
363 /// Full item stack at inventory `slot`: `(item_id, count, nbt_snbt)`.
364 /// `nbt_snbt` is `"{}"` when the item has no NBT.
365 /// Returns `None` if the player is offline or the slot is empty.
366 fn get_slot_item(&self, player: &str, slot: u32) -> Option<(String, u32, String)>;
367
368 /// Replace inventory `slot`. Pass `count == 0` to clear the slot.
369 /// `snbt` is merged into the new item's NBT (pass `""` for no NBT).
370 fn set_slot_item(&self, player: &str, slot: u32, item_id: &str, count: u32, snbt: &str)
371 -> bool;
372
373 // ── world queries & effects (ABI minor 1) ────────────────────────────────
374
375 /// Type, position, size and kind of a loaded entity, or `None` if no
376 /// entity has that uuid.
377 fn entity_info(&self, uuid: &str) -> Option<EntityInfo>;
378
379 /// Uuids of the entities whose bounding boxes touch the box `min`–`max`
380 /// in `dimension`.
381 fn entities_in_box(
382 &self,
383 dimension: &str,
384 min: (f64, f64, f64),
385 max: (f64, f64, f64),
386 ) -> Vec<String>;
387
388 /// The first block with a collision shape on the segment `from`–`to`
389 /// (fluids are ignored).
390 fn raycast_block(
391 &self,
392 dimension: &str,
393 from: (f64, f64, f64),
394 to: (f64, f64, f64),
395 ) -> Option<BlockHit>;
396
397 /// The nearest pickable entity whose (slightly inflated) bounding box the
398 /// segment `from`–`to` crosses, other than `exclude` (a uuid, or `""`)
399 /// and whatever it rides.
400 fn raycast_entity(
401 &self,
402 dimension: &str,
403 from: (f64, f64, f64),
404 to: (f64, f64, f64),
405 exclude: &str,
406 ) -> Option<String>;
407
408 /// State, hardness, fluid and redstone power of the block at `pos`.
409 fn block_info(&self, dimension: &str, pos: BlockPos) -> Option<BlockInfo>;
410
411 /// Break the block at `pos` as `breaker` (a player name, or `""`):
412 /// plays the break effects and, if `drop`, drops its loot as if mined
413 /// with an empty hand. Returns `false` for air and unbreakable blocks.
414 fn break_block(&self, dimension: &str, pos: BlockPos, drop: bool, breaker: &str) -> bool;
415
416 /// Place the block item in the player's inventory `slot` at `pos`, as if
417 /// the player clicked the block below it: the item's own placement
418 /// rules apply and one item is used up (unless creative).
419 fn place_block_from_slot(
420 &self,
421 player: &str,
422 slot: u32,
423 dimension: &str,
424 pos: BlockPos,
425 ) -> bool;
426
427 /// Apply bone meal to the block at `pos`. Returns whether it grew.
428 fn bonemeal(&self, dimension: &str, pos: BlockPos) -> bool;
429
430 /// An explosion of `power` (TNT is 4) at `pos`, caused by entity
431 /// `source` (a uuid, or `""`); `fire` sets fires like a ghast fireball.
432 #[allow(clippy::too_many_arguments)]
433 fn explode(
434 &self,
435 dimension: &str,
436 x: f64,
437 y: f64,
438 z: f64,
439 power: f32,
440 fire: bool,
441 source: &str,
442 ) -> bool;
443
444 /// A system chat message to one player, like `/tellraw`: `json` is a
445 /// JSON text component, so it can hold translation keys
446 /// (`{"translate":"mymod.msg","with":["x"]}`) that each client shows in
447 /// its own language.
448 fn tellraw(&self, player: &str, json: &str) -> bool;
449
450 /// Hurt an entity. `damage_type` is a damage type id
451 /// (`"minecraft:magic"`, `"minecraft:drown"`, …; `""` picks one from
452 /// the attacker: player attack, mob attack, or magic without one), and
453 /// `attacker` the uuid of the entity responsible (or `""`). An unknown
454 /// damage type falls back to magic. Returns whether it took the damage.
455 fn entity_damage(&self, uuid: &str, amount: f32, damage_type: &str, attacker: &str) -> bool;
456
457 /// Set an entity on fire for `ticks` (`0` puts it out).
458 fn entity_set_fire_ticks(&self, uuid: &str, ticks: i32) -> bool;
459
460 /// Give (or, negative, take) experience points.
461 fn player_add_xp(&self, player: &str, amount: i32) -> bool;
462
463 /// Let a player fly (creative-style) or take it away (also stops them
464 /// flying). Game mode changes reset it.
465 fn player_set_may_fly(&self, player: &str, may_fly: bool) -> bool;
466
467 /// Game mode, selected slot, experience, air and flight of a player.
468 fn player_info(&self, player: &str) -> Option<PlayerInfo>;
469
470 /// Drop an item stack into the world with velocity `vel`; `snbt` is the
471 /// stack's NBT (`""` for none). Returns the item entity's uuid.
472 #[allow(clippy::too_many_arguments)]
473 fn spawn_item(
474 &self,
475 dimension: &str,
476 pos: (f64, f64, f64),
477 vel: (f64, f64, f64),
478 item_id: &str,
479 count: u32,
480 snbt: &str,
481 ) -> Option<String>;
482
483 /// The stack inside a dropped item entity: `(item_id, count, snbt)`.
484 fn item_entity_stack(&self, uuid: &str) -> Option<(String, u32, String)>;
485
486 /// Replace the stack inside a dropped item entity (`count == 0` removes
487 /// the entity).
488 fn item_entity_set_stack(&self, uuid: &str, item_id: &str, count: u32, snbt: &str) -> bool;
489
490 /// An action-bar message (the line above the hotbar) from a JSON text
491 /// component, like [`tellraw`](Self::tellraw): translation keys show
492 /// in each client's own language.
493 fn actionbar_json(&self, player: &str, json: &str) -> bool;
494
495 /// Use one `item_id` (a fresh single item, nothing is taken from anyone)
496 /// on the `face` of the block at `pos`, as `player` (a name, or `""` for
497 /// nobody): flint and steel lights it, a shovel puts out a campfire, a
498 /// hoe tills it… Returns whether the item did something.
499 fn use_item_on_block(
500 &self,
501 dimension: &str,
502 pos: BlockPos,
503 face: Face,
504 item_id: &str,
505 player: &str,
506 ) -> bool;
507
508 /// Empty the fluid of a bucket item (`"minecraft:water_bucket"`) at `pos`
509 /// the way a player would: it waterlogs a waterloggable block, replaces
510 /// a replaceable one, and evaporates where water can't exist. Returns
511 /// whether any fluid was placed.
512 fn pour_fluid(&self, dimension: &str, pos: BlockPos, bucket_item_id: &str) -> bool;
513
514 /// Whether an entity type (`"minecraft:zombie"`) is in `tag_id`
515 /// (`"minecraft:skeletons"`).
516 fn has_entity_tag(&self, entity_type: &str, tag_id: &str) -> bool;
517
518 /// The block an item places (`"minecraft:torch"` for the torch item), or
519 /// `None` if it isn't a block item.
520 fn item_block(&self, item_id: &str) -> Option<String>;
521
522 /// Place a configured feature (`"minecraft:oak"`, or a datapack's own
523 /// `data/<ns>/worldgen/configured_feature/*.json`) at `pos`, the way
524 /// a growing sapling places its tree. Returns whether it was placed.
525 fn place_feature(&self, dimension: &str, pos: BlockPos, feature_id: &str) -> bool;
526
527 // ── ABI minor 3 ──────────────────────────────────────────────────────────
528
529 /// Puts `item_id` on cooldown for an online player for `ticks` (the
530 /// grey sweep over it in the hotbar; using it waits). `false` if the
531 /// player is offline or the item unknown.
532 fn set_item_cooldown(&self, player: &str, item_id: &str, ticks: u32) -> bool;
533
534 /// What an entity rides — a boat, a horse, a minecart — if anything.
535 fn entity_vehicle(&self, uuid: &str) -> Option<String>;
536
537 /// What rides an entity directly.
538 fn entity_passengers(&self, uuid: &str) -> Vec<String>;
539
540 /// Seats `rider` on `vehicle`; `force`: even on one that wouldn't take
541 /// it (a full boat, a mob that doesn't carry riders).
542 fn entity_mount(&self, rider: &str, vehicle: &str, force: bool) -> bool;
543
544 /// Gets an entity off what it rides; `false` if it rode nothing.
545 fn entity_dismount(&self, uuid: &str) -> bool;
546
547 /// Gives the entity its own hitbox — `Some((width, height, eye
548 /// height))` in blocks — or vanilla's again (`None`); it stays till
549 /// changed, through pose changes. The server side: the clients are told
550 /// with `yog_api::body::set_dimensions` (the mod's own packet). `false`
551 /// if there's no such entity.
552 fn entity_set_dimensions(&self, uuid: &str, size: Option<(f32, f32, f32)>) -> bool;
553
554 /// The value the entity keeps under `key` (`set_entity_data`).
555 fn entity_data(&self, uuid: &str, key: &str) -> Option<Vec<u8>>;
556
557 /// Sets (`Some`) or removes the value the entity keeps under `key`
558 /// (`mymod:morph`) — the loader sends it to every player who sees the
559 /// entity, now and when they start seeing it; their clients read it in
560 /// body frames (`Frame::data`) and with `yog_api::body::entity_data`.
561 /// `persist` saves it with the entity. Only the server writes these: a
562 /// client asks with its mod's packet. `false` for an empty uuid or key.
563 fn set_entity_data(&self, uuid: &str, key: &str, value: Option<&[u8]>, persist: bool) -> bool;
564
565 /// Sets how far the entity has fallen — fall damage counts it when it
566 /// lands (0: none). `false` if there's no such entity.
567 fn entity_set_fall_distance(&self, uuid: &str, distance: f32) -> bool;
568
569 /// The strongest signal the block at `pos` gives off (a lever, a torch,
570 /// dust: its power); `None` if it isn't a redstone source.
571 fn redstone_signal(&self, dimension: &str, pos: BlockPos) -> Option<u8>;
572
573 /// The redstone power the block at `pos` receives from its neighbours
574 /// (0–15); `None` if the dimension doesn't exist.
575 fn redstone_power(&self, dimension: &str, pos: BlockPos) -> Option<u8>;
576
577 /// Sets the power (0–15) the block at `pos` gives off — a block
578 /// declared a redstone source (`BlockDef::redstone_source`,
579 /// `"redstone": true` in its `.yblock`). Neighbours update.
580 fn set_redstone_output(&self, dimension: &str, pos: BlockPos, level: u8) -> bool;
581
582 /// The full state of the block at `pos`: its id and properties,
583 /// `"minecraft:repeater[delay=2,facing=east,locked=false,powered=false]"`.
584 fn get_block_state(&self, dimension: &str, pos: BlockPos) -> Option<String>;
585
586 /// Places a block in a full state, as `/setblock` writes it
587 /// (`"minecraft:repeater[delay=2,facing=east]"`; unset properties take
588 /// their defaults). `false` if it doesn't parse.
589 fn set_block_state(&self, dimension: &str, pos: BlockPos, state: &str) -> bool;
590
591 /// Property `name` of the block at `pos` (`"energized"` → `"true"`).
592 fn block_property(&self, dimension: &str, pos: BlockPos, name: &str) -> Option<String> {
593 let state = self.get_block_state(dimension, pos)?;
594 split_state(&state).1.into_iter().find(|(k, _)| *k == name).map(|(_, v)| v.to_string())
595 }
596
597 /// Sets some properties of the block at `pos` (`"energized=true,powered=false"`),
598 /// keeping the rest. `false` if there's no block or the state doesn't take.
599 fn set_block_properties(&self, dimension: &str, pos: BlockPos, properties: &str) -> bool {
600 let Some(state) = self.get_block_state(dimension, pos) else { return false };
601 let (id, mut props) = split_state(&state);
602 for pair in properties.split(',').filter(|p| !p.is_empty()) {
603 let Some((k, v)) = pair.split_once('=') else { return false };
604 match props.iter_mut().find(|(n, _)| *n == k) {
605 Some(p) => p.1 = v,
606 None => props.push((k, v)),
607 }
608 }
609 let props: Vec<String> = props.iter().map(|(k, v)| format!("{k}={v}")).collect();
610 self.set_block_state(dimension, pos, &format!("{id}[{}]", props.join(",")))
611 }
612
613 /// Whether a thunderstorm is going on in `dimension`.
614 fn world_is_thundering(&self, dimension: &str) -> bool;
615
616 /// Starts (`true`) or ends a thunderstorm; `duration_ticks` 0 = a
617 /// server-chosen length.
618 fn world_set_thunder(&self, dimension: &str, thundering: bool, duration_ticks: i32) -> bool;
619
620 /// Sets every block of the box between `from` and `to` (inclusive, any
621 /// corners) to `state` (`"minecraft:barrier"`, `"minecraft:air"`, a full
622 /// state) in one call. How many blocks changed; `None` if the dimension
623 /// or state is unknown.
624 fn fill_region(&self, dimension: &str, from: BlockPos, to: BlockPos, state: &str) -> Option<u32>;
625
626 /// Every non-air block of the box between `from` and `to` with its full
627 /// state (see [`Server::get_block_state`]) — one call for a whole area.
628 fn scan_region(&self, dimension: &str, from: BlockPos, to: BlockPos) -> Vec<(BlockPos, String)>;
629}
630
631/// A block state (`"ns:id[k=v,...]"`) as its id and its properties.
632pub fn split_state(state: &str) -> (&str, Vec<(&str, &str)>) {
633 let (id, rest) = state.split_once('[').unwrap_or((state, ""));
634 let props = rest
635 .trim_end_matches(']')
636 .split(',')
637 .filter_map(|p| p.split_once('='))
638 .collect();
639 (id, props)
640}
641
642#[cfg(test)]
643mod state_tests {
644 use super::split_state;
645
646 #[test]
647 fn a_state_splits_into_its_id_and_properties() {
648 assert_eq!(split_state("minecraft:stone"), ("minecraft:stone", vec![]));
649 assert_eq!(
650 split_state("hexcasting:impetus/look[energized=false,facing=up]"),
651 ("hexcasting:impetus/look", vec![("energized", "false"), ("facing", "up")])
652 );
653 }
654}