diff --git a/.gitattributes b/.gitattributes index 585f03f060..f9e1922d78 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,8 +4,3 @@ docs/launcher-commands.md text eol=lf docs/horizon-ctl-commands.md text eol=lf crates/horizon-theme/tokens.css text eol=lf docs/api/*-reference.md text eol=lf -# Generated by `cargo run -p xtask -- mod-api`; `--check` compares them byte for byte. -crates/horizon-client/src/mod_api/generated.rs linguist-generated=true text eol=lf -crates/horizon-client/src/mod_api/generated_host.rs linguist-generated=true text eol=lf -crates/horizon-client/src/game/mod_api_host_generated.rs linguist-generated=true text eol=lf -crates/horizon-client-render/src/gfx/native_render/generated.rs linguist-generated=true text eol=lf diff --git a/Cargo.lock b/Cargo.lock index 1a366f524c..fa542b34e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1324,6 +1324,7 @@ dependencies = [ "horizon-friends", "horizon-hdr", "horizon-items", + "horizon-mod-api-gen", "horizon-mods", "horizon-narrator", "horizon-optifine", @@ -1455,6 +1456,7 @@ dependencies = [ "horizon-cosmetics", "horizon-hdr", "horizon-items", + "horizon-mod-api-gen", "horizon-narrator", "horizon-optifine", "horizon-protocol", @@ -1566,6 +1568,16 @@ dependencies = [ "serde_json", ] +[[package]] +name = "horizon-mod-api-gen" +version = "0.1.0" +dependencies = [ + "quote", + "serde", + "serde_json", + "syn 2.0.119", +] + [[package]] name = "horizon-mods" version = "0.1.0" @@ -5144,10 +5156,9 @@ checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7" name = "xtask" version = "0.1.0" dependencies = [ + "horizon-mod-api-gen", "horizon-theme", - "quote", "serde_json", - "syn 2.0.119", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 5b0c2e402b..35deb105ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,7 @@ members = [ "crates/horizon-friends", "crates/horizon-relay", "crates/horizon-mods", + "crates/horizon-mod-api-gen", "crates/cargo-horizon-mod", "crates/horizon-port-pack", "crates/horizon-shaders", diff --git a/crates/horizon-client-render/Cargo.toml b/crates/horizon-client-render/Cargo.toml index 9a1c2dced1..b20eef9a1d 100644 --- a/crates/horizon-client-render/Cargo.toml +++ b/crates/horizon-client-render/Cargo.toml @@ -71,6 +71,9 @@ audio = ["dep:cpal"] # Helpers horizon-client's tests build their fixtures with. test-support = [] +[build-dependencies] +horizon-mod-api-gen = { path = "../horizon-mod-api-gen" } + [lints] workspace = true diff --git a/crates/horizon-client-render/build.rs b/crates/horizon-client-render/build.rs new file mode 100644 index 0000000000..77aa914cdb --- /dev/null +++ b/crates/horizon-client-render/build.rs @@ -0,0 +1,7 @@ +fn main() { + let root = + std::path::PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("../.."); + let out_dir = std::path::PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + horizon_mod_api_gen::generate(&root, &out_dir, horizon_mod_api_gen::Target::Render) + .expect("generating native render mod API"); +} diff --git a/crates/horizon-client-render/src/gfx/native_render.rs b/crates/horizon-client-render/src/gfx/native_render.rs index 9d4ac6c2c2..908e969248 100644 --- a/crates/horizon-client-render/src/gfx/native_render.rs +++ b/crates/horizon-client-render/src/gfx/native_render.rs @@ -11,7 +11,9 @@ use std::sync::{ atomic::{AtomicBool, Ordering}, }; -mod generated; +mod generated { + include!(concat!(env!("OUT_DIR"), "/native_render_generated.rs")); +} pub use generated::{HandDrawnEvent, RenderHooks, RenderNeedsEvent, WorldDrawnEvent}; pub use wgpu; diff --git a/crates/horizon-client-render/src/gfx/native_render/generated.rs b/crates/horizon-client-render/src/gfx/native_render/generated.rs deleted file mode 100644 index 53828c2581..0000000000 --- a/crates/horizon-client-render/src/gfx/native_render/generated.rs +++ /dev/null @@ -1,52 +0,0 @@ -// @generated by cargo run -p xtask -- mod-api; do not edit. -use super::*; -pub trait RenderHooks: Send + Sync { - /// The world is drawn (see [`WorldDrawn`]). Not called on the title screen's panorama. - fn world_drawn(&self, event: &mut WorldDrawnEvent<'_, '_>) { - let _ = event; - } - /// Asked as a frame is built and drawn: set what your mod needs. - /// - /// Every mod gets `event.needs` in order and may add, change or remove needs. - fn render_needs(&self, event: &mut RenderNeedsEvent<'_>) { - let _ = event; - } - /// The first-person hand is drawn (see [`HandDrawn`]), on every frame [`native_world_drawn`] - /// runs, with no hand too. API @API:hand_drawn@. - fn hand_drawn(&self, event: &mut HandDrawnEvent<'_, '_>) { - let _ = event; - } -} -/// What [`RenderHooks::world_drawn`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct WorldDrawnEvent<'a, 'b> { - pub world: &'a mut WorldDrawn<'b>, -} -pub fn dispatch_world_drawn(mods: &[&dyn RenderHooks], world: &mut WorldDrawn<'_>) { - let mut event = WorldDrawnEvent { world }; - for mod_hook in mods { - mod_hook.world_drawn(&mut event); - } -} -/// What [`RenderHooks::render_needs`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct RenderNeedsEvent<'a> { - pub needs: &'a mut RenderNeeds, -} -pub fn dispatch_render_needs(mods: &[&dyn RenderHooks], needs: &mut RenderNeeds) { - let mut event = RenderNeedsEvent { needs }; - for mod_hook in mods { - mod_hook.render_needs(&mut event); - } -} -/// What [`RenderHooks::hand_drawn`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct HandDrawnEvent<'a, 'b> { - pub hand: &'a mut HandDrawn<'b>, -} -pub fn dispatch_hand_drawn(mods: &[&dyn RenderHooks], hand: &mut HandDrawn<'_>) { - let mut event = HandDrawnEvent { hand }; - for mod_hook in mods { - mod_hook.hand_drawn(&mut event); - } -} diff --git a/crates/horizon-client/Cargo.toml b/crates/horizon-client/Cargo.toml index 33f3d5f5e6..3fab033ba1 100644 --- a/crates/horizon-client/Cargo.toml +++ b/crates/horizon-client/Cargo.toml @@ -142,6 +142,9 @@ path = "tests/launch_api.rs" name = "mod_api_hooks" path = "tests/mod_api_hooks.rs" +[build-dependencies] +horizon-mod-api-gen = { path = "../horizon-mod-api-gen" } + [lints] workspace = true diff --git a/crates/horizon-client/build.rs b/crates/horizon-client/build.rs index de50c33f11..dcd1a0063a 100644 --- a/crates/horizon-client/build.rs +++ b/crates/horizon-client/build.rs @@ -2,6 +2,11 @@ //! resource 1: Explorer and the task bar show it, and the window takes it as its icon. //! The optional Segment Heap manifest is embedded in the same resource compilation. fn main() { + let root = + std::path::PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("../.."); + let out_dir = std::path::PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + horizon_mod_api_gen::generate(&root, &out_dir, horizon_mod_api_gen::Target::Client) + .expect("generating native mod API"); let dir = std::env::var("CARGO_MANIFEST_DIR").unwrap_or_default(); let ico = format!("{dir}/../../packaging/icons/horizon.ico"); println!("cargo:rerun-if-changed={ico}"); diff --git a/crates/horizon-client/src/game/mod_api_host.rs b/crates/horizon-client/src/game/mod_api_host.rs index 39830ea73f..c9ab5f5c6e 100644 --- a/crates/horizon-client/src/game/mod_api_host.rs +++ b/crates/horizon-client/src/game/mod_api_host.rs @@ -151,8 +151,9 @@ impl Game<'_> { } } -#[path = "mod_api_host_generated.rs"] -mod generated; +mod generated { + include!(concat!(env!("OUT_DIR"), "/mod_api_host_generated.rs")); +} impl horizon_client::mod_api::LevelHost for super::GameLevel<'_> { fn block_state(&self, pos: [i32; 3]) -> BlockStateId { diff --git a/crates/horizon-client/src/game/mod_api_host_generated.rs b/crates/horizon-client/src/game/mod_api_host_generated.rs deleted file mode 100644 index e6743c8f31..0000000000 --- a/crates/horizon-client/src/game/mod_api_host_generated.rs +++ /dev/null @@ -1,567 +0,0 @@ -// @generated by cargo run -p xtask -- mod-api; do not edit. -use horizon_client::mod_api::*; -use std::path::PathBuf; -use std::sync::Arc; -impl Host for super::Game<'_> { - fn draw_quad( - &self, - draw: &mut BlockEntityDraw, - sprite: &str, - corners: [[f32; 3]; 4], - uv: [f32; 4], - tint: u32, - ) -> bool { - self.native_draw_quad(draw, sprite, corners, uv, tint) - } - fn draw_cube( - &self, - draw: &mut BlockEntityDraw, - from: [f32; 3], - to: [f32; 3], - faces: [Option>; 6], - tint: u32, - ) -> bool { - self.native_draw_cube(draw, from, to, faces, tint) - } - fn draw_block( - &self, - draw: &mut BlockEntityDraw, - block: &str, - properties: &[(&str, &str)], - ) -> bool { - self.native_draw_block(draw, block, properties) - } - fn draw_item( - &self, - draw: &mut BlockEntityDraw, - stack: &ItemStack, - display: DisplayContext, - owner_yaw: Option, - seed: i32, - ) -> bool { - self.native_draw_item(draw, stack, display, owner_yaw, seed) - } - fn attachments(&mut self, holder: Holder) -> AttachmentResult<&mut Attachments> { - self.native_attachments(holder) - } - fn teleport_mob(&mut self, dimension: &str, id: u32, pos: [f64; 3]) -> bool { - self.native_teleport_mob(dimension, id, pos) - } - fn push_mob(&mut self, dimension: &str, id: u32, velocity: [f64; 3]) -> bool { - self.native_push_mob(dimension, id, velocity) - } - fn set_mob_health(&mut self, dimension: &str, id: u32, health: f32) -> bool { - self.native_set_mob_health(dimension, id, health) - } - fn kill_mob(&mut self, dimension: &str, id: u32) -> bool { - self.native_kill_mob(dimension, id) - } - fn remove_mob(&mut self, dimension: &str, id: u32) -> bool { - self.native_remove_mob(dimension, id) - } - fn summon(&mut self, dimension: &str, kind: &str, pos: [f64; 3], nbt: Compound) -> Option { - self.native_summon(dimension, kind, pos, nbt) - } - fn move_entity( - &mut self, - dimension: &str, - id: u32, - pos: [f64; 3], - yaw: f32, - pitch: f32, - ) -> bool { - self.native_move_entity(dimension, id, pos, yaw, pitch) - } - fn remove_entity(&mut self, dimension: &str, id: u32) -> bool { - self.native_remove_entity(dimension, id) - } - fn server_players(&self) -> Vec { - self.native_server_players() - } - fn tell(&mut self, uuid: u128, line: Text) -> bool { - self.native_tell(uuid, line) - } - fn action_bar(&mut self, uuid: u128, line: Text) -> bool { - self.native_action_bar(uuid, line) - } - fn tab_list(&mut self, uuid: u128, header: &Text, footer: &Text) -> bool { - self.native_tab_list(uuid, header, footer) - } - fn tick_stats(&self) -> (f32, f64) { - self.native_tick_stats() - } - fn sleep_ignored(&mut self, uuid: u128, ignored: bool) -> bool { - self.native_sleep_ignored(uuid, ignored) - } - fn set_nickname(&mut self, uuid: u128, nickname: Option<&str>) -> bool { - self.native_set_nickname(uuid, nickname) - } - fn text_allowed(&self, text: &str) -> Option { - self.native_text_allowed(text) - } - fn broadcast(&self, line: Text) { - self.native_broadcast(line) - } - fn feedback(&mut self, line: Text) { - self.native_feedback(line) - } - fn acting_player(&self) -> Option { - self.native_acting_player() - } - fn run_command(&mut self, line: &str) -> bool { - self.native_run_command(line) - } - fn world_dir(&self) -> Option { - self.native_world_dir() - } - fn held(&self) -> Option<(String, u32, bool)> { - self.native_held() - } - fn count_items(&mut self, item: &str) -> u32 { - self.native_count_items(item) - } - fn carried_items(&self) -> Vec<(String, u32)> { - self.native_carried_items() - } - fn take_items(&mut self, item: &str, count: u32) -> u32 { - self.native_take_items(item, count) - } - fn room_for(&mut self, item: &str) -> u32 { - self.native_room_for(item) - } - fn give_items(&mut self, item: &str, count: u32) -> u32 { - self.native_give_items(item, count) - } - fn give_stack(&mut self, uuid: u128, stack: ItemStack) -> Option { - self.native_give_stack(uuid, stack) - } - fn give_experience(&mut self, uuid: u128, points: u32) -> bool { - self.native_give_experience(uuid, points) - } - fn give_levels(&mut self, uuid: u128, levels: i32) -> bool { - self.native_give_levels(uuid, levels) - } - fn lit_block_name(&self, pos: [i32; 3]) -> Option<&str> { - self.native_lit_block_name(pos) - } - fn flame_particle(&mut self, at: [f64; 3], soul: bool) { - self.native_flame_particle(at, soul) - } - fn carried_stack(&mut self, slot: i32) -> Option<&mut ItemStack> { - self.native_carried_stack(slot) - } - fn set_block( - &mut self, - dimension: &str, - pos: [i32; 3], - state: BlockStateId, - flags: i32, - ) -> bool { - self.native_set_block(dimension, pos, state, flags) - } - fn edit_blocks(&mut self, dimension: &str, edit: &NativeBlockEdit) -> Result { - self.native_edit_blocks(dimension, edit) - } - fn surface_heights( - &self, - dimension: &str, - columns: &[(i32, i32)], - heightmap: NativeHeightmap, - ) -> Result>, String> { - self.native_surface_heights(dimension, columns, heightmap) - } - fn read_columns( - &self, - dimension: &str, - columns: &[(i32, i32)], - heightmap: NativeHeightmap, - range: (u32, u32), - ) -> Result { - self.native_read_columns(dimension, columns, heightmap, range) - } - fn refresh_biome_colors(&mut self) { - self.native_refresh_biome_colors() - } - fn neighbor_signal(&mut self, dimension: &str, pos: [i32; 3]) -> i32 { - self.native_neighbor_signal(dimension, pos) - } - fn light(&self, pos: [i32; 3]) -> (u8, u8) { - self.native_light(pos) - } - fn weather(&self) -> (f32, f32) { - self.native_weather() - } - fn day_time(&self) -> i64 { - self.native_day_time() - } - fn world_height(&self) -> (i32, i32) { - self.native_world_height() - } - fn max_stack_size(&self, item: &str) -> Option { - self.native_max_stack_size(item) - } - fn item_model( - &self, - batch: &mut HudBatch, - item: &str, - model: &str, - at: [f32; 2], - size: f32, - ) -> bool { - self.native_item_model(batch, item, model, at, size) - } - fn quad(&self, batch: &mut HudBatch, points: [[f32; 2]; 4], argb: u32) { - self.native_quad(batch, points, argb) - } - fn mod_screen_open(&mut self, mod_id: &str) -> bool { - self.native_mod_screen_open(mod_id) - } - fn mod_screen_close(&mut self, mod_id: &str) { - self.native_mod_screen_close(mod_id) - } - fn mod_screen(&self) -> Option<&str> { - self.native_mod_screen() - } - fn set_experience_orb( - &mut self, - dimension: &str, - id: u32, - value: Option, - count: Option, - age: Option, - ) -> bool { - self.native_set_experience_orb(dimension, id, value, count, age) - } - fn experience_orbs( - &self, - dimension: &str, - min: [f64; 3], - max: [f64; 3], - ) -> Vec { - self.native_experience_orbs(dimension, min, max) - } - fn clock(&self) -> Option { - self.native_clock() - } - fn spawn_falling_block( - &mut self, - dimension: &str, - state: BlockStateId, - pos: [f64; 3], - velocity: [f64; 3], - falling: NativeFalling, - ) -> bool { - self.native_spawn_falling_block(dimension, state, pos, velocity, falling) - } - fn block_state_property(&self, state: BlockStateId, key: &str) -> Option<&str> { - self.native_block_state_property(state, key) - } - fn raycast(&self, distance: f32) -> Option { - self.native_raycast(distance) - } - fn view_hit(&self) -> Option<(f32, NativeHit)> { - self.native_view_hit() - } - fn claim_items(&mut self, mod_id: &str, ids: &[String]) { - self.native_claim_items(mod_id, ids) - } - fn poll(&mut self, mod_id: &str) -> Vec { - self.native_poll(mod_id) - } - fn set_item_entity(&mut self, dimension: &str, id: u32, stack: ItemStack) -> bool { - self.native_set_item_entity(dimension, id, stack) - } - fn update_output_neighbors(&mut self, dimension: &str, pos: [i32; 3]) -> bool { - self.native_update_output_neighbors(dimension, pos) - } - fn update_signal_neighbors(&mut self, dimension: &str, pos: [i32; 3]) -> bool { - self.native_update_signal_neighbors(dimension, pos) - } - fn block_entity_sync(&mut self, pos: [i32; 3]) { - self.native_block_entity_sync(pos) - } - fn play_local_sound( - &mut self, - sound: &str, - at: [f64; 3], - volume: f32, - pitch: f32, - delay: i32, - ) -> bool { - self.native_play_local_sound(sound, at, volume, pitch, delay) - } - fn fill(&self, batch: &mut HudBatch, rect: [f32; 4], argb: u32) { - self.native_fill(batch, rect, argb) - } - fn sprite(&self, batch: &mut HudBatch, name: &str, src: [f32; 4], dst: [f32; 4]) -> bool { - self.native_sprite(batch, name, src, dst) - } - fn item(&self, batch: &mut HudBatch, item: &str, at: [f32; 2], size: f32) -> bool { - self.native_item(batch, item, at, size) - } - fn screen_title(&self) -> Option { - self.native_screen_title() - } - fn data(&self, path: &str) -> Option> { - self.native_data(path) - } - fn block(&self, pos: [i32; 3]) -> String { - self.native_block(pos) - } - fn block_property(&self, pos: [i32; 3], key: &str) -> Option { - self.native_block_property(pos, key) - } - fn insert_item( - &mut self, - pos: [i32; 3], - face: Direction, - stack: ItemStack, - ) -> Option { - self.native_insert_item(pos, face, stack) - } - fn cursor(&self) -> [f32; 2] { - self.native_cursor() - } - fn text(&self, batch: &mut HudBatch, text: &str, at: [f32; 2], rgb: u32, shadow: bool) { - self.native_text(batch, text, at, rgb, shadow) - } - fn built_data(&self, mod_id: &str) -> Option { - self.native_built_data(mod_id) - } - fn on(&self, mod_id: &str) -> bool { - self.native_on(mod_id) - } - fn mod_key(&self, mod_id: &str, name: &str, default: &str) -> String { - self.native_mod_key(mod_id, name, default) - } - fn setting(&self, mod_id: &str, key: &str) -> Option { - self.native_setting(mod_id, key) - } - fn set_setting(&self, mod_id: &str, key: &str, value: &str) { - self.native_set_setting(mod_id, key, value) - } - fn set_block_property(&mut self, pos: [i32; 3], key: &str, value: &str) -> bool { - self.native_set_block_property(pos, key, value) - } - fn set_block_property_with( - &mut self, - pos: [i32; 3], - key: &str, - value: &str, - flags: i32, - ) -> bool { - self.native_set_block_property_with(pos, key, value, flags) - } - fn config_dir(&self) -> Result { - Self::native_config_dir() - } - fn panel_items(&mut self, mod_id: &str, items: Vec) { - self.native_panel_items(mod_id, items) - } - fn recipes_of_type(&self, kind: &str) -> &[CustomRecipe] { - self.native_recipes_of_type(kind) - } - fn recipe_ingredient(&self, json: &serde_json::Value) -> Option { - self.native_recipe_ingredient(json) - } - fn recipe_result(&self, json: &serde_json::Value) -> Option { - self.native_recipe_result(json) - } - fn known_recipes(&mut self) -> Arc<[i32]> { - self.native_known_recipes() - } - fn place_recipe(&mut self, book_id: i32, all: bool) -> Result<(), String> { - self.native_place_recipe(book_id, all) - } - fn give(&mut self, item: &str, count: u32) -> Result<(), String> { - self.native_give(item, count) - } - fn push_player(&mut self, kick: [f32; 3]) { - self.native_push_player(kick) - } - fn set_flying_speed(&mut self, player: u128, speed: Option) -> bool { - self.native_set_flying_speed(player, speed) - } - fn net_send_to_player( - &mut self, - player: u128, - channel: &str, - data: &[u8], - ) -> Result<(), String> { - self.native_net_send_to_player(player, channel, data) - } - fn net_send_to_server(&mut self, channel: &str, data: &[u8]) -> Result<(), String> { - self.native_net_send_to_server(channel, data) - } - fn dispense_default( - &mut self, - dimension: &str, - pos: [i32; 3], - facing: Direction, - stack: ItemStack, - ) -> Option { - self.native_dispense_default(dimension, pos, facing, stack) - } - fn open_menu( - &mut self, - uuid: u128, - rows: u8, - title: Text, - items: Vec>, - ) -> Option { - self.native_open_menu(uuid, rows, title, items) - } - fn set_menu_items(&mut self, uuid: u128, items: Vec>) { - self.native_set_menu_items(uuid, items) - } - fn close_menu(&mut self, uuid: u128) { - self.native_close_menu(uuid) - } - fn player_stacks(&self, uuid: u128) -> Option>> { - self.native_player_stacks(uuid) - } - fn exchange_items( - &mut self, - a: u128, - a_offer: &[(usize, ItemStack)], - b: u128, - b_offer: &[(usize, ItemStack)], - ) -> Result<(), String> { - self.native_exchange_items(a, a_offer, b, b_offer) - } - fn fluid_insert(&mut self, pos: [i32; 3], face: Direction, fluid: &str, amount: i32) -> i32 { - self.native_fluid_insert(pos, face, fluid, amount) - } - fn fluid_extract( - &mut self, - dimension: &str, - pos: [i32; 3], - face: Direction, - amount: i32, - ) -> Option<(String, i32)> { - self.native_fluid_extract(dimension, pos, face, amount) - } - fn move_fluids( - &mut self, - dimension: &str, - from: [i32; 3], - from_face: Direction, - to: [i32; 3], - to_face: Direction, - max: i32, - ) -> Option<(String, i32)> { - self.native_move_fluids(dimension, from, from_face, to, to_face, max) - } - fn dimension(&self) -> &'static str { - self.native_dimension() - } - fn extract_item( - &mut self, - dimension: &str, - pos: [i32; 3], - face: Direction, - slot: usize, - count: u32, - ) -> Option { - self.native_extract_item(dimension, pos, face, slot, count) - } - fn move_items( - &mut self, - dimension: &str, - from: [i32; 3], - from_face: Direction, - to: [i32; 3], - to_face: Direction, - item: Option<&str>, - max: u32, - ) -> u32 { - self.native_move_items(dimension, from, from_face, to, to_face, item, max) - } - fn hide_player(&mut self, uuid: u128, level: Option) -> bool { - self.native_hide_player(uuid, level) - } - fn item_stack(&self, batch: &mut HudBatch, stack: &ItemStack, at: [f32; 2], size: f32) -> bool { - self.native_item_stack(batch, stack, at, size) - } - fn mod_image(&mut self, width: u32, height: u32, rgba: &[u8]) -> bool { - self.native_mod_image(width, height, rgba) - } - fn instance_dir(&self) -> PathBuf { - self.native_instance_dir() - } - fn camera(&self) -> Option { - self.native_camera() - } - fn block_collision(&self, pos: [i32; 3]) -> Vec<[f64; 6]> { - self.native_block_collision(pos) - } - fn entity(&self, id: u32) -> Option { - self.native_entity(id) - } - fn level(&self, dimension: &str) -> Option { - self.native_level(dimension) - } - fn entity_position(&self, entity: EntityRef) -> Option<[f64; 3]> { - self.native_entity_position(entity) - } - fn entity_block_position(&self, entity: EntityRef) -> Option<[i32; 3]> { - self.native_entity_block_position(entity) - } - fn entity_rotation(&self, entity: EntityRef) -> Option<[f32; 2]> { - self.native_entity_rotation(entity) - } - fn entity_kind(&self, entity: EntityRef) -> Option { - self.native_entity_kind(entity) - } - fn entity_uuid(&mut self, entity: EntityRef) -> Option { - self.native_entity_uuid(entity) - } - fn entity_level(&self, entity: EntityRef) -> Option { - self.native_entity_level(entity) - } - fn entity_velocity(&self, entity: EntityRef) -> Option<[f64; 3]> { - self.native_entity_velocity(entity) - } - fn entity_set_velocity(&mut self, entity: EntityRef, velocity: [f64; 3]) -> bool { - self.native_entity_set_velocity(entity, velocity) - } - fn entity_on_ground(&self, entity: EntityRef) -> Option { - self.native_entity_on_ground(entity) - } - fn entity_bounding_box(&self, entity: EntityRef) -> Option<[[f64; 3]; 2]> { - self.native_entity_bounding_box(entity) - } - fn entity_eye_height(&self, entity: EntityRef) -> Option { - self.native_entity_eye_height(entity) - } - fn entity_eye_position(&self, entity: EntityRef) -> Option<[f64; 3]> { - self.native_entity_eye_position(entity) - } - fn entity_view_vector(&self, entity: EntityRef) -> Option<[f64; 3]> { - self.native_entity_view_vector(entity) - } - fn entity_distance_to_sqr(&self, entity: EntityRef, pos: [f64; 3]) -> Option { - self.native_entity_distance_to_sqr(entity, pos) - } - fn item_container_slots(&self, stack: &ItemStack) -> Option>> { - self.native_item_container_slots(stack) - } - fn item_container_insert( - &self, - stack: &ItemStack, - item: &ItemStack, - ) -> Option<(u32, ItemStack)> { - self.native_item_container_insert(stack, item) - } - fn item_container_extract( - &self, - stack: &ItemStack, - slot: usize, - max: u32, - ) -> Option<(Option, ItemStack)> { - self.native_item_container_extract(stack, slot, max) - } - fn recipe_book_category(&mut self, kind: &str) -> Option { - self.native_recipe_book_category(kind) - } -} diff --git a/crates/horizon-client/src/mod_api.rs b/crates/horizon-client/src/mod_api.rs index 0c899f4756..f953affc4e 100644 --- a/crates/horizon-client/src/mod_api.rs +++ b/crates/horizon-client/src/mod_api.rs @@ -2,7 +2,7 @@ //! The executable passes the ordered `Mods` to `entry::main_with`. //! Trait mods are process-wide, like mixin module statics. //! Hooks use `&self + Send + Sync` for shared and reentrant calls; see `docs/native-mods.md`. -//! Generate the trait and dispatch with `cargo run -p xtask -- mod-api`. +//! Cargo generates the trait and dispatch from the catalog and source signatures into OUT_DIR. use std::sync::{Arc, OnceLock}; pub use crate::biome::BiomeColors; @@ -528,7 +528,9 @@ pub enum Flow { Cancel, } -mod generated_host; +mod generated_host { + include!(concat!(env!("OUT_DIR"), "/mod_api_generated_host.rs")); +} pub use generated_host::Host; /// Result of accessing or decoding an attachment. @@ -607,7 +609,9 @@ macro_rules! level_attachment_host { } level_attachment_host!(LevelRandomHost, LevelWriteHost); -mod generated; +mod generated { + include!(concat!(env!("OUT_DIR"), "/mod_api_generated.rs")); +} pub use generated::Hooks; #[doc(hidden)] pub use generated::*; diff --git a/crates/horizon-client/src/mod_api/generated.rs b/crates/horizon-client/src/mod_api/generated.rs deleted file mode 100644 index 3bdd4a9403..0000000000 --- a/crates/horizon-client/src/mod_api/generated.rs +++ /dev/null @@ -1,14334 +0,0 @@ -// @generated by cargo run -p xtask -- mod-api; do not edit. -use super::*; -pub trait Hooks: Send + Sync { - fn render(&self) -> Option<&dyn RenderHooks> { - None - } - /// Once a frame, in a world or not, after Horizon's extras. - fn frame(&self, host: &mut dyn Host, event: &mut FrameEvent) { - let _ = host; - let _ = event; - } - /// Once a tick, in every `Game`: the window's (in single player, on LAN - /// and on servers), and the server's own (`native_window` is false - /// there: the single player server thread, a dedicated server). - fn tick(&self, host: &mut dyn Host, event: &mut TickEvent) { - let _ = host; - let _ = event; - } - /// The world frame's local visual sources. Push requests; Horizon validates - /// and limits them before rendering. Called only while a world is visible. - fn visuals(&self, host: &dyn Host, event: &mut VisualsEvent<'_>) { - let _ = host; - let _ = event; - } - /// At most 32 pairs of already-loaded base and transparent overlay sprites. - /// Per-pixel alpha in the overlay marks full-bright material, without - /// illuminating the surrounding block or changing gameplay light. - /// - /// Every mod gets `event.layers` in order and may add, change or remove layers. - fn emissive(&self, host: &dyn Host, event: &mut EmissiveEvent<'_>) { - let _ = host; - let _ = event; - } - /// The world's phases, read-only; `error_text` carries the engine's - /// own open-failure text, empty otherwise. Server-side. A server loop or final - /// save failure fires `WorldCloseFailed` with the error text. - fn world_lifecycle(&self, host: &mut dyn Host, event: &mut WorldLifecycleEvent<'_>) { - let _ = host; - let _ = event; - } - /// The server is about to open its worlds: nothing of the world is - /// loaded yet (NeoForge's `ServerAboutToStartEvent`, "called before the - /// server begins loading anything"). Vanilla's `MinecraftServer.loadLevel` - /// start. Server-side: the single player server thread once Play is - /// pressed, a dedicated server once its settings are read. Each is - /// followed by `native_world_lifecycle`'s `WorldOpenReady` or - /// `WorldOpenFailed`. `dedicated` tells a dedicated server from the single - /// player one. API 44. - fn server_about_to_start(&self, host: &mut dyn Host, event: &mut ServerAboutToStartEvent) { - let _ = host; - let _ = event; - } - /// The server begins an orderly stop (NeoForge's `ServerStoppingEvent`): - /// the start of vanilla's `MinecraftServer.stopServer`, with the worlds - /// still loaded and the players still connected and not yet saved, so a - /// mod can save its own data or tell them. `native_world_lifecycle`'s - /// `WorldCloseFinal` follows once the worlds are saved. A server whose loop - /// fails stops without this (`WorldCloseFailed` reports it). API 44. - fn server_stopping(&self, host: &mut dyn Host, event: &mut ServerStoppingEvent) { - let _ = host; - let _ = event; - } - /// The authoritative server tick's phases: `ServerTickStart` before any - /// level is ticked (NeoForge's `ServerTickEvent.Pre`), `ServerTickEnd` - /// after the last (`.Post`). Read blocks with `native_server_block`, by - /// dimension: which level `self.world` holds here is not defined. A paused - /// pass fires nothing; a frozen world carries `world_running` false except - /// during a requested step. - fn server_tick(&self, host: &mut dyn Host, event: &mut ServerTickEvent<'_>) { - let _ = host; - let _ = event; - } - /// Before an authoritative server level's tick pass. `dimension` is its ID. - fn level_tick_pre(&self, host: &mut dyn Host, event: &mut LevelTickPreEvent<'_>) { - let _ = host; - let _ = event; - } - /// After a successful authoritative server level tick pass. - fn level_tick_post(&self, host: &mut dyn Host, event: &mut LevelTickPostEvent<'_>) { - let _ = host; - let _ = event; - } - /// An authoritative server committed a changed state through `Level.setBlock`. - /// The world contains `new`; block side effects and neighbour updates follow. - fn block_changed(&self, level: &dyn LevelHost, event: &mut BlockChangedEvent) { - let _ = level; - let _ = event; - } - /// An authoritative server is about to deliver `neighborChanged`. - /// State and block IDs are valid in this level's registries for this call. - fn neighbor_changed(&self, level: &dyn LevelHost, event: &mut NeighborChangedEvent) { - let _ = level; - let _ = event; - } - /// An authoritative server's `ServerLevel.tickChunk` is about to - /// random-tick the block `state` at `pos` (`BlockState.randomTick`): - /// only blocks that tick randomly, a mod's as its vanilla template block - /// does. Bone meal's random ticks (`StemBlock.performBonemeal`) don't - /// fire it. Change the block with `native_set_block_property` and set - /// `handled` to skip the block's own random tick (a mod crop growing its - /// own way); a block changed without `handled` takes its own random tick - /// as it is now, if it still ticks randomly. Lava's fluid random tick - /// runs either way. - /// - /// Return `Flow::Cancel` to take it: this sets `handled`, and later mods are skipped. - fn random_tick(&self, level: &mut dyn LevelWriteHost, event: &mut RandomTickEvent) -> Flow { - let _ = level; - let _ = event; - Flow::Continue - } - /// An authoritative server's `ServerLevel.tickBlock` is about to run a - /// due scheduled tick of the block `state` at `pos` (`BlockState.tick`): - /// the block is still the one the tick was scheduled for. Change the - /// block with `native_set_block_property` and set `handled` to skip the - /// block's own tick; a block changed without `handled` takes its own - /// tick as it is now, if it is still the tick's block. - /// - /// Return `Flow::Cancel` to take it: this sets `handled`, and later mods are skipped. - fn tick_block(&self, level: &mut dyn LevelWriteHost, event: &mut TickBlockEvent) -> Flow { - let _ = level; - let _ = event; - Flow::Continue - } - /// An authoritative server is about to compute the state the block - /// `state` at `pos` takes now that its neighbour in `direction` is - /// `neighbour` (`BlockBehaviour.updateShape`). `state` may not be in the - /// world yet (a piston landing it): decide from `state`, not from the - /// block at `pos`. Answer only for your own blocks: put the new state in - /// `new` (`state` keeps the block as it is; air breaks it, with its drops, - /// on a neighbour update) and the block's own `updateShape` doesn't run, - /// nor the ticks it would schedule. The level is read-only, as vanilla's - /// `LevelReader`; ticks go through `native_schedule_block_tick` and - /// `native_schedule_fluid_tick` with `ticks` (`ScheduledTickAccess`). It - /// runs often, at least six times per `setBlock` that updates shapes - /// (more for redstone wire's diagonals and knock-on updates): keep it - /// short. - /// - /// Return `Some` to set `new`; the first mod returning `Some` wins. - fn update_shape( - &self, - level: &mut dyn LevelTicksHost, - event: &mut UpdateShapeEvent, - ) -> Option { - let _ = level; - let _ = event; - None - } - /// The HUD (not drawn under F1), after the minimap: place a box with - /// `corners.place` (boxes stack away from their edge) and draw it with - /// `native_fill` and `native_text`. Skip a corner `native_corner_shown` - /// says is taken. - fn hud(&self, host: &mut dyn Host, event: &mut HudEvent<'_>) { - let _ = host; - let _ = event; - } - /// A key or mouse button went down or up, by its vanilla name - /// (`key.keyboard.z`, `key.mouse.left`): keys on any screen (check - /// `native_playing`), mouse buttons while playing. Repeats are left out. - fn key(&self, host: &mut dyn Host, event: &mut KeyEvent<'_>) { - let _ = host; - let _ = event; - } - /// Wheel steps while playing: set `claimed` to keep them from the hotbar. - /// - /// Return `Flow::Cancel` to take it: this sets `claimed`, and later mods are skipped. - fn wheel(&self, host: &mut dyn Host, event: &mut WheelEvent) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A command typed or clicked in chat, without its slash, before the - /// server and Horizon's commands see it: set `ran` when the mod ran it. - /// - /// Return `Flow::Cancel` to take it: this sets `ran`, and later mods are skipped. - fn command(&self, host: &mut dyn Host, event: &mut CommandEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// The commands `native_command` runs, by name and without their slash: - /// push the ones that are yours. Asked when a connection starts and - /// again when the server sends its own tree, so the chat box colours, - /// completes and lists them as the game's own; a name the game already - /// has keeps that command's behaviour. API 35. - /// - /// Every mod gets `event.commands` in order and may add, change or remove commands. - fn commands(&self, host: &mut dyn Host, event: &mut CommandsEvent<'_>) { - let _ = host; - let _ = event; - } - /// The zoom a native mod asks for, applied while playing (the spyglass's - /// own view wins): set `zoom` when it is none yet. - /// - /// Return `Some` to set `zoom`; the first mod returning `Some` wins. - fn zoom(&self, host: &dyn Host, event: &mut ZoomEvent) -> Option { - let _ = host; - let _ = event; - None - } - /// The minimap a native mod shows: set `minimap` when it is none yet. - /// - /// Return `Some` to set `minimap`; the first mod returning `Some` wins. - fn minimap(&self, host: &dyn Host, event: &mut MinimapEvent) -> Option { - let _ = host; - let _ = event; - None - } - /// Options > Mods lists the native mods: push yours. - /// - /// Every mod gets `event.mods` in order and may add, change or remove mods. - fn mods(&self, host: &mut dyn Host, event: &mut ModsEvent<'_>) { - let _ = host; - let _ = event; - } - /// The buttons of mod `mod_id`'s settings page, under its keys: push - /// their labels when it is yours. Asked again each time the page opens - /// and after each click. - /// - /// Every mod gets `event.buttons` in order and may add, change or remove buttons. - fn settings(&self, host: &mut dyn Host, event: &mut SettingsEvent<'_>) { - let _ = host; - let _ = event; - } - /// Button `button` of mod `mod_id`'s settings page was clicked. - fn settings_click(&self, host: &mut dyn Host, event: &mut SettingsClickEvent<'_>) { - let _ = host; - let _ = event; - } - /// The player did `event` on mod `mod_id`'s panel (`native_panel_items`): - /// act on it when it is yours. API 2. - fn panel_event(&self, host: &mut dyn Host, event_: &mut PanelEventEvent<'_>) { - let _ = host; - let _ = event_; - } - /// Block `block` (`mymod:machine`) gets a block entity - /// (`EntityBlock.newBlockEntity`): set `entity` to its id and saved - /// compound when the block is yours and `entity` is none yet. It works on - /// any template: the engine removes the block entity when its block is - /// broken or replaced, no piston moves it, and a mod that draws it - /// (`native_block_entity_submit`) is asked for it. API 15; before - /// 129 the block's template had to have a - /// block entity of its own (a chest, a furnace...), or the block got none. - /// - /// Return `Some` to set `entity`; the first mod returning `Some` wins. - fn block_entity( - &self, - host: &mut dyn Host, - event: &mut BlockEntityEvent<'_>, - ) -> Option<(String, Compound)> { - let _ = host; - let _ = event; - None - } - /// Whether block `block`'s block entity ticks (`EntityBlock.getTicker`): - /// set `ticks` when it is yours. Asked for each of the block's states - /// when a world opens. API 15. - /// Set `ticks` to true to make it tick, and never clear a value an earlier mod or mixin set. - fn block_entity_ticks(&self, host: &dyn Host, event: &mut BlockEntityTicksEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server tick of the block entity at `pos`, in vanilla's order among - /// the others (`Level.tickBlockEntities`), when its `id` is yours: read - /// and change its saved compound `nbt`, its item slots `items` (keep - /// their count: as many as its container has, none without one) and the - /// data slots its menu shows (`data`). It is out of the world during the - /// call (the block entity at `pos` reads as a `Mod` with an empty id, and - /// a save leaves it out); if the call removes or replaces it, `entity` is - /// dropped. Clients, the window's own in single player included, get - /// what `native_block_entity_update_tag` gives when its chunk is sent or - /// its block changes. API 17. - fn block_entity_tick(&self, host: &mut dyn Host, event: &mut BlockEntityTickEvent<'_>) { - let _ = host; - let _ = event; - } - /// What clients get of the block entity at `pos` when its `id` is - /// yours (`BlockEntity.getUpdateTag`): set `tag` to it, read from - /// `entity` alone. Asked on the server whenever a chunk holding it is - /// sent and whenever its block changes, on the server thread or, for a - /// saved chunk sent past the view distance (`lan::far`), another one; a client's copy of the block - /// entity (made by `native_block_entity` there) merges it into its `nbt`. - /// Leave it unset to send nothing. API 45. - /// - /// Return `Some` to set `tag`; the first mod returning `Some` wins. - fn block_entity_update_tag( - &self, - event: &mut BlockEntityUpdateTagEvent<'_>, - ) -> Option { - let _ = event; - None - } - /// Block `block`'s block entity has item slots (a `WorldlyContainer`): - /// set `container` when the block is yours. Asked once for each mod - /// block when the ticking table is built; at most 256 slots, and slot - /// numbers past the count are left out. Its `menu`, one of - /// `containers::MOD_MENUS` or a content mod's menu type - /// (`data//horizon/menu/`, API 85) with as many - /// slots, opens when the block is used (its template must open a menu, as - /// a chest or a furnace does); another is left out. A mod menu type shows - /// on Horizon clients through the mod's `native_screen` only. API 17. - /// - /// Return `Some` to set `container`; the first mod returning `Some` wins. - fn block_entity_container( - &self, - host: &dyn Host, - event: &mut BlockEntityContainerEvent<'_>, - ) -> Option { - let _ = host; - let _ = event; - None - } - /// A menu opens on this client for block `block`, a mod's: set `screen` - /// when the block is yours, to lay its screen out (`native_screen_draw` - /// draws it). The server asks it too: a slot it hides is not in the menu - /// there either, so shift-clicks never fill it. API 19. - /// - /// Return `Some` to set `screen`; the first mod returning `Some` wins. - fn screen(&self, host: &dyn Host, event: &mut ScreenEvent<'_>) -> Option { - let _ = host; - let _ = event; - None - } - /// Draw block `block`'s screen at `origin` (its top left, GUI pixels), - /// over a plain panel with its slots' frames and under the items and - /// labels, from the menu's data slots `data` (the block entity's `data` - /// on the server): data slots are 16-bit on the wire as in vanilla (a - /// bigger value wraps), and `data` is empty until the server's first - /// values arrive, so read it with `get`. Draw with `native_sprite`, `native_fill` and - /// `native_text`. API 19. - fn screen_draw(&self, host: &dyn Host, event: &mut ScreenDrawEvent<'_>) { - let _ = host; - let _ = event; - } - /// A left or right (`right`) press at `at` (GUI pixels from the screen's - /// top left, so outside it too) on block `block`'s screen, a mod's, over - /// no slot: set `button` to a menu button id when it is on one of yours. - /// Horizon sends it to the server (`ContainerButtonClick`, - /// `native_menu_button` there) and the press goes no further. API 20. - /// - /// Return `Some` to set `button`; the first mod returning `Some` wins. - fn screen_click(&self, host: &dyn Host, event: &mut ScreenClickEvent<'_>) -> Option { - let _ = host; - let _ = event; - None - } - /// The tooltip at `at` (as in `native_screen_click`) on block `block`'s - /// screen, a mod's, over no item: push its lines when it is over - /// something of yours. Legacy colour codes (§) work. API 20. - /// - /// Every mod gets `event.lines` in order and may add, change or remove lines. - fn screen_tooltip(&self, host: &dyn Host, event: &mut ScreenTooltipEvent<'_>) { - let _ = host; - let _ = event; - } - /// A player pressed menu button `id` on the menu of the mod block entity - /// at `pos` (`AbstractContainerMenu.clickMenuButton`), on the server: - /// change `entity` when its id is yours. It is out of the world during - /// the call, as in `native_block_entity_tick`. API 20. - fn menu_button(&self, host: &mut dyn Host, event: &mut MenuButtonEvent<'_>) { - let _ = host; - let _ = event; - } - /// The commands native mods add to the server's command tree: push - /// yours. Asked once when a world opens to players (a dedicated - /// server, a LAN host, single player's server thread); every player's - /// chat box then completes them as vanilla's, within their level. A - /// name vanilla has is left out. API 8. - /// - /// Every mod gets `event.commands` in order and may add, change or remove commands. - fn register_commands(&self, host: &mut dyn Host, event: &mut RegisterCommandsEvent<'_>) { - let _ = host; - let _ = event; - } - /// A player's command on the server (`Commands.performCommand`), - /// without its slash, for a command a mod registered and the player's - /// level allows: set `ran` when it is yours. The player's state is - /// swapped in for the call, so `native_player` and the other helpers - /// read that player; `native_feedback` answers them. A single player's - /// owner console (`horizon-ctl command`) reaches it too, as the owner at - /// level 4. API 8. - /// - /// Return `Flow::Cancel` to take it: this sets `ran`, and later mods are skipped. - fn perform_command(&self, host: &mut dyn Host, event: &mut PerformCommandEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A click in a menu a mod opened (`native_open_menu`), which moved - /// nothing: the menu's `slot` (its own first, then the player's - /// inventory, `native_menu_inventory_slot`; -999 outside), the `button` - /// and the click's `mode` (`ClickType`'s id) of the player `uuid`. - /// No player's state is swapped in: the menu helpers take the player's - /// UUID. API 10. - fn menu_clicked(&self, host: &mut dyn Host, event: &mut MenuClickedEvent) { - let _ = host; - let _ = event; - } - /// A menu a mod opened closed: the player closed it, the server or a mod - /// opened another, or the player left. API 10. - fn menu_closed(&self, host: &mut dyn Host, event: &mut MenuClosedEvent) { - let _ = host; - let _ = event; - } - /// An item's tooltip, wherever the game shows one (slots, the creative - /// tabs, the recipe book, chat's hover): the stack, with its components - /// (`stack.data()`: a shulker box's contents...), and its lines, the name - /// first, each with a colour as `rgba` packs it (`u32::MAX` keeps the - /// text's own). Add, change or take out lines; `Text::legacy` reads `§` - /// codes. Asked each frame the tooltip shows, never for one the stack - /// hides. API 12. - /// - /// Every mod gets `event.lines` in order and may add, change or remove lines. - fn tooltip(&self, host: &dyn Host, event: &mut TooltipEvent<'_>) { - let _ = host; - let _ = event; - } - /// A player uses `stack` (right click) with `self.hand` on the block at - /// `pos`, `face` the face clicked (`[0, 1, 0]` is the top), once the - /// block itself let the click through (a chest still opens): where - /// `ItemStack.useOn` starts. Set `claimed` to take the use: the hand - /// swings and neither the item's `useOn` nor its `use` runs. - /// Server-side, for every player of the world (the helpers read the - /// acting one); an item on cooldown is never used. API 13. - /// - /// Return `Flow::Cancel` to take it: this sets `claimed`, and later mods are skipped. - fn use_item_on(&self, host: &mut dyn Host, event: &mut UseItemOnEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A player uses `stack` (right click) with `self.hand`: in the air, or - /// on a block once `native_use_item_on` let it through, where `Item.use` - /// starts. Each click reaches it once. Set `claimed` to take the use: the - /// hand swings and the item's own use doesn't run. Server-side, for every - /// player of the world; an item on cooldown is never used. API 13. - /// - /// Return `Flow::Cancel` to take it: this sets `claimed`, and later mods are skipped. - fn use_item(&self, host: &mut dyn Host, event: &mut UseItemEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A player breaks the block `state` at `pos`, once the game allowed it - /// (the held item can break blocks, the debug stick didn't take the - /// click): where `ServerPlayerGameMode.destroyBlock` starts. Set - /// `canceled` to keep the block: nothing is destroyed or dropped, and a - /// guest sees it back. Server-side, for every player of the world (the - /// helpers read the acting one). API 14. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn destroy_block(&self, host: &mut dyn Host, event: &mut DestroyBlockEvent) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A player placed `stack` as the block `state` at `pos`: where - /// `BlockItem.place` set it, before the stack shrinks. It only hears; a - /// mod stops a placement by claiming `native_use_item_on`, which runs - /// before it. Server-side, for every player of the world. API 14. - fn place_block(&self, host: &mut dyn Host, event: &mut PlaceBlockEvent<'_>) { - let _ = host; - let _ = event; - } - /// A player right clicks the block at `pos` with `self.hand`, `face` the - /// face clicked: where `ServerPlayerGameMode.useItemOn` starts, before - /// the block's own use (a chest, a door, a lever) and before - /// `native_use_item_on`. Set `canceled` to stop the click: neither the - /// block nor the item acts, the hand doesn't swing, and a guest sees the - /// block as it is. Server-side, for every player of the world - /// (`native_acting_player` says who), once per hand. API 21. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn right_click_block(&self, host: &mut dyn Host, event: &mut RightClickBlockEvent) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// On an authoritative server, a fluid flows or fire spreads from `from` - /// into `to` (`kind` is `fluid` or `fire`): `FlowingFluid.spreadTo`, and - /// `FireBlock.tick` setting fire to `to` or burning it (`checkBurnOut`). - /// Set `canceled` to leave the block at `to` as it is. Fluids ask on - /// every flow: answer fast. API 22. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn block_spread(&self, level: &dyn LevelHost, event: &mut BlockSpreadEvent<'_>) -> Flow { - let _ = level; - let _ = event; - Flow::Continue - } - /// On an authoritative server, the piston at `piston` is about to push - /// the blocks at `push` one step `direction` (`[0, 1, 0]` is up; pulling - /// when it retracts) and destroy those at `destroy`: - /// `PistonBaseBlock.moveBlocks`, once its structure resolved. Set - /// `canceled` and nothing moves, as when the structure can't; a sticky - /// piston then retracts without its block, and clients are sent the - /// blocks rather than the retraction to replay. API 22. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn piston_move(&self, level: &dyn LevelHost, event: &mut PistonMoveEvent<'_>) -> Flow { - let _ = level; - let _ = event; - Flow::Continue - } - /// An explosion at `center` in `dimension` (`minecraft:overworld`) is - /// about to break `blocks`: `ServerExplosion.explode` with its `toBlow`, - /// before any block is hit. Take out the positions to spare; they keep - /// their block and get no fire. Entities are hurt as before. Server-side. - /// API 22. - /// - /// Every mod gets `event.blocks` in order and may add, change or remove blocks. - fn explosion_detonate(&self, host: &mut dyn Host, event: &mut ExplosionDetonateEvent<'_>) { - let _ = host; - let _ = event; - } - /// A player fills (`stack` an empty bucket) or empties a bucket at `pos`: - /// `BucketItem.use`, where it asks `Level.mayInteract`. Set `canceled` - /// and the bucket does nothing. Server-side, for every player of the - /// world (`native_acting_player` says who). API 22. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn fill_bucket(&self, host: &mut dyn Host, event: &mut FillBucketEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A player attacks the entity `entity` (`Entity.getId`) of type `kind` - /// (`minecraft:item_frame`, `minecraft:cow`, `minecraft:player`) standing - /// at `pos`: `ServerGamePacketListenerImpl.handleInteract`'s attack, once - /// in reach, where `Player.attack` starts. Set `canceled` and the blow - /// does nothing (no damage, no knockback, the frame keeps its item). - /// Server-side, for every player of the world (`native_acting_player` - /// says who). API 22. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn attack_entity(&self, host: &mut dyn Host, event: &mut AttackEntityEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A message from `player`'s client on `channel` (`mod_id:name`), within - /// the caps (`native_net.rs`): check `channel`, and treat `data` as the - /// player's own bytes, checked by nothing. Runs in the tick after it - /// arrives, with no player's state swapped in. Server-side. API 40. - fn net_from_client(&self, host: &mut dyn Host, event: &mut NetFromClientEvent<'_>) { - let _ = host; - let _ = event; - } - /// A message from the server on `channel` (`mod_id:name`), whose `data` - /// is the sender's own bytes. The window's game only. API 40. - fn net_from_server(&self, host: &mut dyn Host, event: &mut NetFromServerEvent<'_>) { - let _ = host; - let _ = event; - } - /// `player` died: where `ServerPlayer.die` starts, with the death - /// message's key when the game knows it (`death.attack.lava`; none for a - /// message it builds from the combat). It only hears. Server-side, for - /// every player of the world, whether death messages are shown or not. - /// API 40. - fn player_died(&self, host: &mut dyn Host, event: &mut PlayerDiedEvent<'_>) { - let _ = host; - let _ = event; - } - /// A new mob of `kind` joined `dimension` at `pos`: `natural` when the - /// world's spawning made it, false when something else did (a breed, a - /// raid, a spawner, a conversion, `/summon`, and the second mob of a - /// jockey the world spawned). One call a mob: a `/summon` with passengers - /// tells each. Not told of a mob loaded with its chunk, leaving a hive or - /// changing level. It only hears; the mob is already among the game's - /// mobs. Server-side. API 40. - fn mob_spawned(&self, host: &mut dyn Host, event: &mut MobSpawnedEvent<'_>) { - let _ = host; - let _ = event; - } - /// `player`'s melee blow killed the mob `entity` of `kind` (other deaths - /// aren't told yet). It only hears, after the blow landed. Server-side. - /// API 40. - fn mob_killed(&self, host: &mut dyn Host, event: &mut MobKilledEvent<'_>) { - let _ = host; - let _ = event; - } - /// `player` threw `count` of `item` (`Player.drop`: the drop key, a - /// menu's throw...), not what scatters when they die. It only hears. - /// Server-side. API 40. - fn item_dropped(&self, host: &mut dyn Host, event: &mut ItemDroppedEvent<'_>) { - let _ = host; - let _ = event; - } - /// `player` is about to take `count` of `item` from the ground - /// (`ItemEntity.playerTouch`). Set `canceled` to leave it there: the - /// stack isn't offered again for 10 ticks. Server-side. API 40. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn item_picked_up(&self, host: &mut dyn Host, event: &mut ItemPickedUpEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A player's hit landed on the mob `entity` of type `kind` - /// (`minecraft:zombie`), holding `stack`: where `Item.hurtEnemy` is called, - /// before the post-attack enchantments and the weapon's wear. It only - /// hears. Another player as the target isn't told yet. Server-side, for - /// every player of the world (the helpers read the acting one). API 39. - fn hurt_enemy(&self, host: &mut dyn Host, event: &mut HurtEnemyEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server tick of the stack a player carries in `slot`: where - /// `Inventory.tick` calls `Item.inventoryTick`, for every stack, each tick, - /// `/tick freeze` included (it stops the world, not the player). `slot` is - /// as `Inventory` saves it (0 to 35; the armour 100 feet to 103 head; -106 - /// the off hand), and `selected` says it is the held slot. - /// `native_carried_stack` reads and changes the stack, which stays in its - /// slot, so the item helpers see it: a count of 0 empties the slot, and the - /// player's client gets the change. This runs often, so check the item - /// first. Server-side, for every living player of the world (the helpers - /// read the acting one). API 39. - fn inventory_tick(&self, host: &mut dyn Host, event: &mut InventoryTickEvent) { - let _ = host; - let _ = event; - } - /// A player's equipment slot `slot` (`head`, `chest`, `legs`, `feet`, - /// `offhand`, and `mainhand` on a swap of the hands or `/item`) took a different - /// item, `new` (none when it was emptied): `LivingEntity.onEquipItem`, - /// before the equip sound. It only hears. Never for a spectator. - /// Server-side, for every player of the world (the helpers read the - /// acting one). API 39. - fn on_equip(&self, host: &mut dyn Host, event: &mut OnEquipEvent<'_>) { - let _ = host; - let _ = event; - } - /// The redstone signal the block `state` at `pos` gives on its `face`, the - /// side that touches the block reading it: `BlockState.getDirectSignal` - /// when `direct` (what passes through a block it touches, as a lever's - /// does), else `getSignal`. `signal` holds vanilla's answer: change it when - /// the block is yours (0 to 15; anything else is clamped). Asked very often, - /// from inside the level's update, so answer from your own state: check the - /// block first, and don't read or change the world here. When the answer - /// changes, and once the block is placed, call - /// `native_update_signal_neighbors`. Authoritative servers only. API 38. - fn block_signal(&self, level: &dyn LevelHost, event: &mut BlockSignalEvent) { - let _ = level; - let _ = event; - } - /// `BlockState.isSignalSource`: set `source` for a block of yours that - /// gives a signal, so redstone wire beside it connects to it. Authoritative - /// servers only. API 38. - fn is_signal_source(&self, level: &dyn LevelHost, event: &mut IsSignalSourceEvent) { - let _ = level; - let _ = event; - } - /// The player-health HUD rows, once a frame the HUD draws them - /// (`HealthHud::layout`, not under F1): `inputs` carries the health, - /// food, air and vehicle state the layout reads, `wi`/`hi` the screen's - /// GUI size. A mod that sets `draws` replaces every row — armour, - /// hearts, food, air and the heights (`left_height`/`right_height`) - /// above the hotbar are the answer's to give; `bubble_pop_sound` too. - /// Nothing set draws Horizon's own. - /// - /// Return `Some` to set `draws`; the first mod returning `Some` wins. - fn health_hud( - &self, - host: &mut dyn Host, - event: &mut HealthHudEvent<'_>, - ) -> Option> { - let _ = host; - let _ = event; - None - } - /// A player right-clicks the entity `entity` (`Entity.getId`) of type - /// `kind` (`minecraft:cow`) with `self.hand`, holding `stack` (none in an - /// empty hand): where `Player.interactOn` starts, before the entity's own - /// `interact` and the item's `interactLivingEntity`. Set `claimed` to take - /// the click: the hand swings and neither runs. Asked once per hand, as - /// vanilla tries the main hand then the off hand. Server-side, for every - /// player of the world (the helpers read the acting one). API 29. - /// - /// Return `Flow::Cancel` to take it: this sets `claimed`, and later mods are skipped. - fn interact_entity(&self, host: &mut dyn Host, event: &mut InteractEntityEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A player left-clicks the block `state` at `pos`, `face` the face hit - /// (`[0, 1, 0]` is the top): where `ServerPlayerGameMode.handleBlockBreakAction` - /// takes a dig's start, after its reach, spawn protection and world border - /// checks, before `Block.attack` (a note block plays) or a creative break. - /// Set `canceled` to ignore the click: the block isn't attacked, dug or - /// broken on the server, and the player's client gets it back (its own - /// predicted crack still shows to the clicking player). Server-side, for - /// every player of the world (the helpers read the acting one). API 30. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn left_click_block(&self, host: &mut dyn Host, event: &mut LeftClickBlockEvent) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A living player's server tick ends (`ServerPlayer.tick`): once a tick - /// for every player in the world, after their digging, item use, - /// cooldowns, statistics and advancements. The helpers read that player. - /// Players still tick under `/tick freeze`; a dead player doesn't, nor - /// does one killed earlier in the same tick. API 31. - fn player_tick(&self, host: &mut dyn Host, event: &mut PlayerTickEvent) { - let _ = host; - let _ = event; - } - /// An entity joins the server level `dimension` (NeoForge's - /// `EntityJoinLevelEvent`): vanilla's `PersistentEntitySectionManager.addEntity`. - /// `loaded_from_disk` is true for an entity coming back with its chunk, - /// false for one added fresh (`addFreshEntity`: a drop, an orb, `/summon`, - /// an arrival through a portal). `entity` is its ID and `kind` its type - /// (`minecraft:item`). It is already in the level's lists, so helpers that - /// take an entity ID find it. Set `canceled` to keep it out: it is taken - /// back out as if never added, and a `/summon` of it fails. Tells every - /// kind in the game's entity lists (items, orbs, mobs, boats, minecarts, - /// armor stands, primed TNT, falling blocks, projectiles, rockets, - /// tridents, end crystals, the dragon, cushions, paintings, item frames), - /// once each, in the order they were created; check `kind`. A drop, an - /// orb, a mob, a `/summon` and a chunk's entities are told at once, any - /// other spawn between the tick's phases (it is checked after the blocks, - /// the vehicles, the projectiles, the items and the mobs, and at the - /// tick's end). A mod that cancels one of the others sees it removed - /// before it ticks again. Not told: the entities Horizon keeps without - /// simulating (displays, markers, interactions). Server-side. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn entity_join_level(&self, host: &mut dyn Host, event: &mut EntityJoinLevelEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// `LivingEntity.hurtServer`, past its guards (invulnerable, dead, fire - /// with fire resistance), is about to hurt entity `entity` of type `kind` - /// (`minecraft:zombie`, `minecraft:player`) by `amount` of `damage_type` - /// (`minecraft:fall`), from the source's `causing` entity - /// (`DamageSource.getEntity`: the shooter of an arrow) and `direct` one - /// (`getDirectEntity`: the arrow), 0 for none or not known yet - /// (docs/native-mods.md, API 50). Change `amount`, or set `canceled`, - /// and nothing of `hurtServer` itself happens (no damage, hurt sound, - /// knockback, invulnerable time, statistics or criteria); the caller acts - /// as on a refused hit, except that a canceled hit on the ender dragon - /// still counts as landed (`EnderDragon.hurt` returns true whatever - /// `reallyHurt` did: the arrow is spent, the melee effects run). It runs - /// on the thread that ticks the world (the server thread): state shared with render thread hooks needs synchronising. - /// No `self`: the game is in the middle of the hit. API 50. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn living_incoming_damage(&self, event: &mut LivingIncomingDamageEvent<'_>) -> Flow { - let _ = event; - Flow::Continue - } - /// `AbstractContainerMenu.clickMenuButton(Player, int)`'s other half: - /// the acting player's cursor stack, `getCarried()`/`setCarried()`, - /// which `native_menu_button` cannot reach. Fires right after it, same - /// moment, same `pos`/`entity`/`id`; change `carried` when its id is - /// yours. On the server. API 51. - fn menu_button_v2(&self, host: &mut dyn Host, event: &mut MenuButtonV2Event<'_>) { - let _ = host; - let _ = event; - } - /// An entity leaves the server level `dimension` (NeoForge's - /// `EntityLeaveLevelEvent`): vanilla's `onTrackingEnd`, when it is removed - /// for any reason (picked up, merged, despawned, burnt, blown up, killed, - /// taken by a hopper or a mob, gone through a portal) or its chunk unloads, - /// a level closing with nobody in it included. Not for the entities still - /// loaded when the server stops, as in vanilla. `entity` is its ID and - /// `kind` its type (`minecraft:item`); it is no longer in the level's lists. - /// It only hears. 53 told item entities, experience orbs and mobs; it now - /// tells every kind `native_entity_join_level` does (`kind` is the type), each - /// once, when the game next notices it gone (the tick or action that removed - /// it). Check `kind`. Server-side. - fn entity_leave_level(&self, host: &mut dyn Host, event: &mut EntityLeaveLevelEvent<'_>) { - let _ = host; - let _ = event; - } - /// Block `block`'s block entity has fluid tanks: set `fluids` when the - /// block is yours, in the container's shape — each tank's capacity in - /// millibuckets, the tanks each face reaches, and the tanks a fill may - /// reach and a drain may take. Asked once for each mod block when the - /// ticking table is built; at most 16 tanks, capacities clamped to - /// 1..=1 000 000, and tank numbers past the count are left out. The tanks' - /// contents are kept in the entity's saved compound, under `fluids`, and - /// the fluid helpers below read and write them. API 54. - /// - /// Return `Some` to set `fluids`; the first mod returning `Some` wins. - fn block_entity_fluids( - &self, - host: &dyn Host, - event: &mut BlockEntityFluidsEvent<'_>, - ) -> Option { - let _ = host; - let _ = event; - None - } - /// `MobEffect.shouldApplyEffectTickThisTick` for a content mod's effect - /// `effect` (`ruby:bleeding`), each tick a player or a mob has it, in - /// `MobEffectInstance.tickServer` before it counts down: `tick_count` is - /// the ticks left (the entity's tick count when infinite). Set `apply` - /// and `native_apply_effect_tick` follows. Server-side. API 56. - fn should_apply_effect_tick_this_tick( - &self, - host: &dyn Host, - event: &mut ShouldApplyEffectTickThisTickEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// `MobEffect.applyEffectTick`: the content mod's effect `effect` acts on - /// `entity` in `dimension` (`minecraft:overworld`). Clear `keep` and the - /// effect ends now, before it counts down. `entity` is a mob's - /// `Entity.getId`, None for a player (`native_acting_player` says who). - /// It runs inside the entity's tick: a mob's world borrows the game - /// there, so the game is read-only, and it does not hold the ticking - /// mobs, nor the player's effects. Server-side. API 56. - fn apply_effect_tick(&self, host: &dyn Host, event: &mut ApplyEffectTickEvent<'_>) { - let _ = host; - let _ = event; - } - /// `MobEffect.onEffectAdded`: `LivingEntity.addEffect` gave `entity` - /// (as in `native_apply_effect_tick`) the content mod's effect `effect`, - /// which it didn't have. Server-side. API 56. - fn on_effect_added(&self, host: &mut dyn Host, event: &mut OnEffectAddedEvent<'_>) { - let _ = host; - let _ = event; - } - /// NeoForge's `MobEffectEvent.Added`, vanilla's `MobEffect.onEffectStarted`: - /// every `LivingEntity.addEffect` of the content mod's effect `effect`, - /// new or not, changed or not, after `native_on_effect_added`. - /// Server-side. API 56. - fn mob_effect_added(&self, host: &mut dyn Host, event: &mut MobEffectAddedEvent<'_>) { - let _ = host; - let _ = event; - } - /// Chunk `chunk` (`[x, z]`, in chunks) of `dimension` came into the world - /// (NeoForge's `ChunkEvent.Load`): `new` when it was just generated, false - /// when read from the save. A flat world's chunk that leaves before it's - /// first lit isn't saved, so it's generated, and new, again; one generated - /// as the players left is new when it next comes, unless the server - /// restarts first, or the level closes for being empty, which drops its - /// memory of what never saved. Its blocks and block entities are there, - /// but its saved entities join afterwards and its light may not be ready: - /// change the world from a later tick. The chunks around the spawn come - /// before `WorldOpenReady`. Not told of a chunk a client receives. - /// Server-side. API 57. - fn chunk_load(&self, host: &mut dyn Host, event: &mut ChunkLoadEvent<'_>) { - let _ = host; - let _ = event; - } - /// Chunk `chunk` of `dimension` left the world (NeoForge's - /// `ChunkEvent.Unload`), with a level that closes for being empty too. Don't - /// read its blocks. Its block entities are still there for this call, so - /// `native_with_mod_block_entity` can change one and it's saved with the - /// chunk. Not told when the world closes, which `native_world_lifecycle` - /// tells. Server-side. API 57. - fn chunk_unload(&self, host: &mut dyn Host, event: &mut ChunkUnloadEvent<'_>) { - let _ = host; - let _ = event; - } - /// `player` (`name`) joined the game (NeoForge's - /// `PlayerLoggedInEvent`): placed in the world and listed in - /// `native_server_players`, so `native_net_send_to_player` reaches them. - /// Once a join, with no player's state swapped in. Server-side. API 57. - fn player_logged_in(&self, host: &mut dyn Host, event: &mut PlayerLoggedInEvent<'_>) { - let _ = host; - let _ = event; - } - /// `player` (`name`) left the game (NeoForge's `PlayerLoggedOutEvent`), by - /// any path: a lost connection, a kick, the server stopping or failing. - /// Once for each player `native_player_logged_in` told of. They've already - /// left `native_server_players`, except when the server stops, which tells - /// before it saves, with them still listed and able to get a last message. - /// Server-side. API 57. - fn player_logged_out(&self, host: &mut dyn Host, event: &mut PlayerLoggedOutEvent<'_>) { - let _ = host; - let _ = event; - } - /// The small bar `GuiGraphicsExtractor.itemDecorations` draws under an - /// item, wherever the game shows one: `bar` arrives holding vanilla's value - /// (the damage bar, or a bundle's fullness already applied) and may be - /// replaced (width `0..=13`, colour `rgb`) or set to `None` to hide it. - /// Asked each frame a stack is drawn. Client-side. 60. - fn item_bar(&self, host: &dyn Host, event: &mut ItemBarEvent<'_>) { - let _ = host; - let _ = event; - } - /// `BlockState.hasAnalogOutputSignal`: set `output` for a block of yours - /// that comparators read (how full or far along it is, as a composter's - /// level), so they ask `native_analog_output`. Asked from inside the - /// level's update: answer from `state` alone. Authoritative servers only. - /// API 64. - /// - /// Every mod gets `event.output` in order and may add, change or remove output. - fn has_analog_output(&self, level: &dyn LevelHost, event: &mut HasAnalogOutputEvent) { - let _ = level; - let _ = event; - } - /// `BlockState.getAnalogOutputSignal`: the signal a comparator reads from - /// the block `state` at `pos`, `face` the side that touches the comparator - /// (or the conductor in front of it). `output` holds vanilla's answer: - /// change it when the block is yours (0 to 15; anything else is clamped). - /// Asked only for blocks `native_has_analog_output` answered for, from - /// inside the level's update: don't change the world here. When the - /// answer changes without the block's state changing, call - /// `native_update_output_neighbors`; a change of state through - /// `native_set_block` or `native_set_block_property` (neighbour updates - /// on) tells the comparators itself, as vanilla's `Level.setBlock` does. - /// Authoritative servers only. API 64. - /// - /// Every mod gets `event.output` in order and may add, change or remove output. - fn analog_output(&self, level: &dyn LevelHost, event: &mut AnalogOutputEvent) { - let _ = level; - let _ = event; - } - /// Register namespaced NBT component types before content loads. - /// - /// Every mod gets `event.types` in order and may add, change or remove types. - fn component_types(&self, event: &mut ComponentTypesEvent<'_>) { - let _ = event; - } - /// `DispenserBlock.getDispenseMethod`: choose a behaviour for any item, - /// including vanilla items, on the authoritative server (not droppers). - /// Leave answer None for vanilla. Otherwise answer `(remainder, event)`: - /// a remainder of None empties the slot, `Some(stack)` replaces it (the hook - /// owns consumption: no extra item is removed afterwards). `event` is the - /// level event the dispenser plays with its smoke: 1000 the click, 1001 the - /// fail sound (`OptionalDispenseItemBehavior` without success), or 0 for - /// nothing at all (`DispenseItemBehavior.NOOP`). To drop one normally, - /// answer `(native_dispense_default(..), 1000)`. API 67. - /// - /// Return `Some` to set `answer`; the first mod returning `Some` wins. - fn dispense_item( - &self, - host: &mut dyn Host, - event: &mut DispenseItemEvent<'_>, - ) -> Option { - let _ = host; - let _ = event; - None - } - /// Client-side image component for this stack's tooltip. Answer its width/height - /// in GUI pixels; finite positive dimensions reserve space after text and any - /// vanilla bundle component. None keeps the vanilla tooltip unchanged. - /// - /// Return `Some` to set `size`; the first mod returning `Some` wins. - fn tooltip_component( - &self, - host: &dyn Host, - event: &mut TooltipComponentEvent<'_>, - ) -> Option<[f32; 2]> { - let _ = host; - let _ = event; - None - } - /// Render the measured component at origin, inside the tooltip background. - /// Stack is the component's data. Use native_sprite/native_fill/native_text; - /// size is the measurement returned by native_tooltip_component this frame. - fn tooltip_component_draw(&self, host: &dyn Host, event: &mut TooltipComponentDrawEvent<'_>) { - let _ = host; - let _ = event; - } - /// On an authoritative server, `BlockState.canSurvive` for `state` at - /// `pos` (`BlockBehaviour.canSurvive`), before the block answers it: as - /// it is placed (`BlockItem.canPlace`), from a shape update of a block - /// whose template checks it (a plant, a torch, a sign: false breaks the - /// block with its drops), and wherever else vanilla asks. `state` may not - /// be in the world yet, so decide from `state`. Answer only for your own - /// blocks: `Some(false)` refuses the spot, `Some(true)` allows it, and - /// the block's own rule doesn't run. The level is read-only. Asked on - /// every shape update of such a block: keep it short. API 70. - /// - /// Return `Some` to set `survives`; the first mod returning `Some` wins. - fn can_survive(&self, level: &dyn LevelHost, event: &mut CanSurviveEvent) -> Option { - let _ = level; - let _ = event; - None - } - /// On an authoritative server, `VegetationBlock.mayPlaceOn` for the - /// plant `state` on `ground` at `ground_pos`, before the plant answers - /// it: the blocks a mod plant (one whose template is a vanilla plant, - /// such as a crop) can stand on. Its template's `canSurvive` asks it, so - /// the plant is refused there and pops off when its ground changes to - /// one it can't stand on. Answer only for your own blocks. The level is - /// read-only. API 70. - /// - /// Return `Some` to set `may`; the first mod returning `Some` wins. - fn may_place_on(&self, level: &dyn LevelHost, event: &mut MayPlaceOnEvent) -> Option { - let _ = level; - let _ = event; - None - } - /// Draw the mod block entity at `pos`, of block `block` - /// (`mymod:machine`), on this client: once a frame while it is in view - /// (vanilla's `BlockEntityRenderer.submit`: within 64 blocks, its chunk - /// faded in, and its bounds, `native_block_entity_render_bounding_box`, - /// in the frustum), when its `id` is yours. `state` is the block's state - /// (`blockEntity.getBlockState()`: read its `facing` with - /// `native_block_state_property`), `entity` holds what the server sent - /// (`native_block_entity_update_tag`). Add geometry with - /// `native_draw_quad`, `native_draw_cube`, `native_draw_block` and - /// `native_draw_item` under `draw`'s pose stack, which starts at the - /// block's lower north-west corner, in blocks; `draw` also has the partial - /// tick, the game time and the block's light. API 71. - fn block_entity_submit(&self, host: &dyn Host, event: &mut BlockEntitySubmitEvent<'_>) { - let _ = host; - let _ = event; - } - /// Where the mod block entity at `pos` draws, for culling - /// (NeoForge's `getRenderBoundingBox`): `bounds` is min x, y, z then max - /// x, y, z, in blocks from the block's lower north-west corner. It starts - /// as the block with a block of margin around it; widen it when your - /// `native_block_entity_submit` draws further (a tall shaft, a beam), or - /// set an infinite value for one never culled. Asked once a frame for each - /// within 64 blocks whose chunk is drawn. API 71. - /// - /// Every mod gets `event.bounds` in order and may add, change or remove bounds. - fn block_entity_render_bounding_box( - &self, - host: &dyn Host, - event: &mut BlockEntityRenderBoundingBoxEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// A block nothing in Horizon knows a `BonemealableBlock` for is about to - /// take bone meal: vanilla's `BonemealableBlock.isValidBonemealTarget`, - /// `state` the block's own state at `pos`. Answer `valid` for your own - /// blocks: the meal is used up on one as vanilla uses it up (the growth - /// particles and the sound follow, whether or not the roll below grows - /// it), from a player's `BoneMealItem.useOn`, from a dispenser and, on a - /// crop (a villager bonemeals only a `CropBlock`), from a villager. Asked - /// on both sides, as vanilla asks it on both, so a client predicts the - /// use with your answer: say no where you can only grow on a server - /// (`is_client_side`). `Some(false)` refuses the meal, even on a block - /// whose template takes it (vanilla's `growCrop` then keeps the meal); - /// left `None`, the block keeps the `BonemealableBlock` its template gives - /// it, or takes no bone meal. - /// 72. - /// - /// Return `Some` to set `valid`; the first mod returning `Some` wins. - fn is_valid_bonemeal_target( - &self, - level: &dyn LevelHost, - event: &mut IsValidBonemealTargetEvent, - ) -> Option { - let _ = level; - let _ = event; - None - } - /// `BonemealableBlock.isBonemealSuccess` for a block that answered - /// `native_is_valid_bonemeal_target`, after it did: set `success` to grow - /// it this time. Vanilla draws the level's random here - /// (`isBonemealSuccess(Level, RandomSource, BlockPos, BlockState)`), so - /// draw from `native_level_random` to keep vanilla's order. - /// Authoritative servers only. 72. - /// - /// Every mod gets `event.success` in order and may add, change or remove success. - fn is_bonemeal_success( - &self, - level: &mut dyn LevelRandomHost, - event: &mut IsBonemealSuccessEvent, - ) { - let _ = level; - let _ = event; - } - /// `BonemealableBlock.performBonemeal` for a block that answered - /// `native_is_valid_bonemeal_target` and whose `native_is_bonemeal_success` - /// said yes: grow it here, `state` its state at `pos`. The meal is used up - /// either way. Change the block with the level's - /// `native_set_block_property`, and schedule with `native_schedule_block_tick` - /// (`ticks` is the hook's). Authoritative servers only. 72. - fn perform_bonemeal(&self, level: &mut dyn LevelWriteHost, event: &mut PerformBonemealEvent) { - let _ = level; - let _ = event; - } - /// `Block.stepOn` for a mod block, on its authoritative server level. - /// No self: the simulation owns the entity during this callback. - fn block_step_on(&self, event: &mut BlockStepOnEvent<'_>) { - let _ = event; - } - /// `Block.entityInside` for each mod block touched along the entity's movement. - /// Repeated sweep hits in one contact pass are suppressed; idle overlap still fires. - fn block_entity_inside(&self, event: &mut BlockEntityInsideEvent<'_>) { - let _ = event; - } - /// `Block.fallOn` for a mod block. Change calculated damage before generic fall sounds - /// and hurt handling; zero prevents both. Creative and fall-immune entities - /// notify with zero damage and retain their immunity. Server-side only. - /// - /// Every mod gets `event.damage` in order and may add, change or remove damage. - fn block_fall_on(&self, event: &mut BlockFallOnEvent<'_>) { - let _ = event; - } - /// A consumable's effect of a mod's own kind: the item's `on_consume_effects` - /// entry is of a type in a namespace of its own, and the loader kept it - /// for this call. The kind and the JSON the file wrote are here; the acting - /// player is the one consuming, and the game's own effects, - /// sounds, teleports and drops are open. An unclaimed kind does - /// nothing. API 74. - fn consume_effect(&self, host: &mut dyn Host, event: &mut ConsumeEffectEvent<'_>) { - let _ = host; - let _ = event; - } - /// A native mod's hook point (docs/native-mods.md), listed in `native-hooks.json` with the game's: - /// the loot table `id` (`minecraft:chests/simple_dungeon`, `minecraft:blocks/stone`, - /// `minecraft:entities/zombie`) as it loads, its JSON after the data packs', before anything - /// rolls it (NeoForge's `LootTableLoadEvent`, Fabric's `LootTableEvents.MODIFY`). Add pools to - /// `table["pools"]` or entries to a pool's `entries`, change or replace it (no pools: it drops - /// nothing; keep its `random_sequence` when replacing it, as a mob's table without one drops - /// nothing). Called once per table and load, in id order, wherever the tables load: the server, - /// and a client too, so make the same change each time, and don't load loot tables from here. - /// Two loads may run at once on two threads. A `/reload` or a world opened with other packs - /// may load them again, or keep the tables already loaded from the same files. API - /// 75. - /// - /// Every mod gets `event.table` in order and may add, change or remove table. - fn loot_table_load(&self, event: &mut LootTableLoadEvent<'_>) { - let _ = event; - } - /// A block broke or a mob died, and `drops` are the items about to drop, in - /// the order they drop: what the loot tables rolled (after - /// `native_loot_table_load`'s changes), and for a mob what it wore, held or - /// carried (a fox's mouth item, an allay's, piglin's or horse's inventory), - /// but not an enderman's carried block or a copper golem's preserved - /// equipment, which drop after. A charged creeper's head and a fox's mouth - /// item come before the mob's own table. A block's stacks get its block - /// entity's data (a shulker box's items) after this. Add, change or take out - /// stacks; empty ones don't drop (NeoForge's global loot modifiers, Fabric's - /// `LootTableEvents.MODIFY_DROPS`); `context` says what dropped them and - /// how, and it is called even when `drops` is empty. A player's use drops - /// through it too (`NativeLootDrops::used`: shears, a brush, an item - /// frame), with that player named. It hears blocks that a - /// player, an explosion or a mob breaks, not other breaks (leaves decaying, - /// a torch losing its wall, a piston, `/setblock ... destroy`), nor a - /// creative break or a tool that can't harvest the block, nor experience. - /// Server-side, for every player. API - /// 75. - /// - /// Every mod gets `event.drops` in order and may add, change or remove drops. - fn loot_drops(&self, host: &mut dyn Host, event: &mut LootDropsEvent<'_>) { - let _ = host; - let _ = event; - } - /// A block is about to take the state it is placed in - /// (`Block.getStateForPlacement(BlockPlaceContext)`), `state` the block's - /// own default state. Answer only for your blocks: put the state you want - /// in `answer` as `Some(Some(state))` — a state of that block, as vanilla - /// sets properties on its own `defaultBlockState` (another block's is - /// ignored) — or refuse the placement with `Some(None)`: the block goes - /// nowhere and the stack is kept, as vanilla's `null`. Left empty, the - /// block's own placement runs. `context` is - /// vanilla's `BlockPlaceContext` (the clicked face, the placer's rotation, - /// the clicked position, whether the clicked block is being replaced, the - /// item in hand) and `fluid` the fluid in the position the block would go - /// in, which a waterlogged block reads. Build the state from `state` with - /// `native_state_with_property`, and know whether `state` is yours with - /// `native_state_block`. It is asked for every placer that goes - /// through vanilla's placement (a player's `BlockItem.place`, a - /// dispenser's `DirectionalPlaceContext`), and the answer still takes its - /// survival and entity obstruction checks. A trait mod returns - /// `Some(Some(state))` to choose a state, `Some(None)` to refuse, or - /// `None` to leave the answer to the next mod or vanilla. - /// Authoritative servers only. 76. - /// - /// Return `Some` to set `answer`; the first mod returning `Some` wins. - fn state_for_placement( - &self, - level: &dyn LevelHost, - event: &mut StateForPlacementEvent<'_>, - ) -> Option> { - let _ = level; - let _ = event; - None - } - /// A `minecraft:post_attack` effect of a mod's own kind, on a mob a - /// player's weapon hit: the entry says `enchanted` attacker and `affected` - /// victim, its `requirements` passed, and its `effect` names a `type` in a - /// namespace outside `minecraft:`. Told the enchantment id, the kind, the - /// `effect` JSON, the level and the mob's id (it may have just died). The - /// acting player is the one whose weapon carries the enchantment. Each - /// such entry is told once a hit, and once for each mob a sweep lands on. - /// A hit on a player, and an entry inside `minecraft:all_of`, aren't told - /// yet. An unclaimed kind does nothing. API 77. - fn enchant_effect(&self, host: &mut dyn Host, event: &mut EnchantEffectEvent<'_>) { - let _ = host; - let _ = event; - } - /// The open screen has drawn itself and its tooltips (`Screen.render`, - /// the point of `ScreenEvent.Render.Post`): draw over it with - /// `native_fill`, `native_sprite` and `native_text`, `screen.rect` being - /// its container panel (or the whole window) and `screen.id` the screen - /// it is. Once a frame on the window, never while playing with no screen; - /// `&self`, so it reads and draws only. The Win and Options screens call - /// it on their own path, after their drawing. - /// It does not fire for the screens drawn over gameplay after it (a - /// server dialog, the server pack, link and code-of-conduct prompts, a - /// book, a sign editor): those keep `Screen::Playing`, which it skips (the - /// server pack prompt hides an open screen too, and that screen does not - /// fire under it), and draw over what it drew, while vanilla's - /// `ScreenEvent.Render.Post` fires for them. The title's own screens - /// (`title`, `world_select`, `world_create`, `multiplayer`…) fire it too, - /// their `screen.id` from the title state machine and the whole window - /// their `rect` (API 326), and no dialog or prompt over them. - /// API 78. - fn screen_render_post(&self, host: &dyn Host, event: &mut ScreenRenderPostEvent<'_>) { - let _ = host; - let _ = event; - } - /// Hook point for native mods (docs/native-mods.md, listed in `native-hooks.json` - /// with those of `horizon-client/src/game/native_hooks.rs`): `components` holds - /// `item`'s default components in `items.json`'s form (`"minecraft:max_stack_size": 16`, - /// `"minecraft:food": {"nutrition": 4, "saturation": 2.4}`); insert or replace one to - /// set it, remove it to take it away (a stack size outside 1 to `i32::MAX` is - /// brought back into it, and one that is not a number leaves the data pack's - /// own). Asked for every item each time the game reads the defaults, from any - /// thread, several times as resources load and again when a world's data packs - /// load: answer the same each time. Client and server each ask their own mods, - /// as NeoForge does, so both sides need the mod. - /// 79. - /// - /// Every mod gets `event.components` in order and may add, change or remove components. - fn modify_default_components(&self, event: &mut ModifyDefaultComponentsEvent<'_>) { - let _ = event; - } - /// A mouse button went down over the open screen - /// (`ScreenEvent.MouseButtonPressed.Pre`, vanilla's - /// `Screen.mouseClicked`): `screen` is the screen it is, `screen.mouse` - /// where the pointer is (so there is no position of its own), `button` the - /// button's GLFW index (0 left, 1 right, 2 middle, 3 back, 4 forward; a - /// button past those is the platform's own number, which differs between - /// systems) and `modifiers` GLFW's bits (shift 1, control 2, - /// alt 4, super 8). Set `canceled` and the press goes no further: no slot, - /// no screen button, no drag, no hotbar key, no sort or header button. - /// It fires before anything the screen does with the press, on the window - /// only, and while a screen of the game's or the title's is open (Options - /// and the pack list opened from the title screen count, with no player - /// and no `menu`; the title's own screens since API 326, their - /// `screen.id` from the title state machine). It does not fire on the - /// connect and loading screens, nor for a server - /// dialog, the server pack, link and code-of-conduct prompts, a book or a - /// sign editor drawn over gameplay, nor while a world loads or saves: the - /// click there stays the game's own. A gamepad click or a click the - /// keyboard's focus sends is not the mouse and fires nothing. - /// API 80. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn screen_mouse_button_pressed_pre( - &self, - host: &dyn Host, - event: &mut ScreenMouseButtonPressedPreEvent<'_>, - ) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A mouse button went up over the open screen - /// (`ScreenEvent.MouseButtonReleased.Pre`, vanilla's - /// `Screen.mouseReleased`), with the same `screen`, `button` and - /// `modifiers` as `native_screen_mouse_button_pressed_pre` and fired at - /// the same point. Set `canceled` and the release goes no further: a - /// pressed slot keeps what it took and no drag, slider or scrollbar lets - /// go. - /// Cancel a release only when the press it ends was cancelled too: a - /// release the screen took ends the drag or the scroll it began, as it - /// does in vanilla, and no guard here un-cancels it. Same screens as the - /// press; a press that opened another screen ends on that one. - /// API 80. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn screen_mouse_button_released_pre( - &self, - host: &dyn Host, - event: &mut ScreenMouseButtonReleasedPreEvent<'_>, - ) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// The camera shake native mods ask for in this frame, `partial` ticks - /// past the last tick (0 to 1): add your pitch, yaw and roll in degrees - /// and your offset in blocks, along the camera's right, up and back, to - /// `shake`, so several mods' shakes add up. Read while playing only; - /// Horizon scales the sum by Distortion Effects and bounds it - /// (`native_shake_matrix`). API 82. - fn camera_shake(&self, host: &dyn Host, event: &mut CameraShakeEvent<'_>) { - let _ = host; - let _ = event; - } - /// The values native mods hand their shader packs in this frame, - /// `partial` ticks past the last tick (0 to 1): a pack's WGSL reads - /// them as `frame.mods` (eight `vec4`). Write the slots your own - /// pack reads; a mod that bundles its pack as `native-` is the - /// one that pack is for. Asked once a frame while a pack is on; a value - /// that isn't finite reads as 0 (`native_shader_params_now`). API - /// 84. - fn shader_params(&self, host: &dyn Host, event: &mut ShaderParamsEvent<'_>) { - let _ = host; - let _ = event; - } - /// Before an eligible targeted block's selection outline is built. - /// Cancel skips only the default block highlight, after normal HUD and game-mode gates. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn block_highlight(&self, host: &dyn Host, event: &mut BlockHighlightEvent) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// Before one first-person hand and its displayed stack are drawn (RenderHandEvent). - /// Equip progress is the inverse arm height, including the item's swap animation scale. - /// Cancel skips that hand's default geometry, including its map and arm geometry. - /// It only hides: the hook has no way to draw a replacement. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn render_hand(&self, host: &dyn Host, event: &mut RenderHandEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// Render-thread notification after a resource-pack reload has applied successfully. - /// Atlas, models, renderer and client caches already refer to the new resources. - /// Selection/F3+T and server-pack reloads share this point; initial startup does not. - fn client_resources_reloaded( - &self, - host: &mut dyn Host, - event: &mut ClientResourcesReloadedEvent, - ) { - let _ = host; - let _ = event; - } - /// Append an item's custom decorations after its default bar, cooldown and count. - /// Coordinates are the icon's top-left in GUI units, as the batch takes them; - /// no item translation is active. - /// Runs wherever the shared item-decoration path is used (hotbar, menus, tooltips, - /// and the other screens that draw item icons: advancements, beacon, stats, trading). - fn item_decorations(&self, host: &dyn Host, event: &mut ItemDecorationsEvent<'_>) { - let _ = host; - let _ = event; - } - /// `Item.getUseDuration` of `stack`, in ticks: `duration` arrives with - /// vanilla's (a consumable's time, 72000 for a shield, a spear, a bow, - /// 0 for an item with no use over time). A mod's own item (outside - /// `minecraft:`) with no `consumable`, `blocks_attacks` or kinetic - /// weapon that answers above 0 is used over time on a right click, as an - /// `Item.use` that calls `startUsingItem`, until it is let go or the time - /// runs out (`native_living_entity_use_item_finish`). A consumable's time - /// changes too, potions and the other vanilla items keep their own. - /// Asked when a use starts, each tick a mod's item is in use, and each - /// frame the hand draws one in use, on both sides. It has no user, as - /// it is asked so often: answer from the stack alone. API 90. - fn use_duration(&self, host: &dyn Host, event: &mut UseDurationEvent<'_>) { - let _ = host; - let _ = event; - } - /// `Item.getUseAnimation` of `stack` in use, for the first person hand: - /// `animation` arrives with its consumable's (`eat`, `drink`), else - /// `None`. `Eat` and `Drink` bring the food to the mouth, `Bow` draws, - /// `Trident` raises, `Brush` sweeps; the rest hold the item still. - /// Asked each frame the hand draws an item in use, for an item with no - /// pose of its own (bows, crossbows, tridents, brushes and spears keep - /// theirs). Client-side. API 90. - fn use_animation(&self, host: &dyn Host, event: &mut UseAnimationEvent<'_>) { - let _ = host; - let _ = event; - } - /// `Player.canEat` for the food `stack`, from `Consumable.canConsume` - /// when eating starts: `can_eat` arrives with vanilla's (creative, - /// `can_always_eat`, or hungry). Clear it to refuse the food, set it to - /// let a full player eat. Both sides (the client starts eating as the - /// server does), so answer from the stack and the player alone. API 90. - fn can_eat(&self, host: &dyn Host, event: &mut CanEatEvent<'_>) { - let _ = host; - let _ = event; - } - /// NeoForge's `LivingEntityUseItemEvent.Start`: `entity` starts using - /// `stack` in `hand` (`LivingEntity.startUsingItem`), for `duration` - /// ticks (`native_use_duration`'s answer). `entity` is a mob's - /// `Entity.getId`, None for a player (`native_acting_player` says who); - /// only players fire it so far. Change `duration` (consumables, potions, - /// spyglasses and mods' items count to it; below 0 refuses the use, and - /// 0 runs it out on its first tick, as `native_living_entity_use_item_tick` - /// running it out, unless the item's own is 0 already), or set - /// `canceled` and the use doesn't start. Both sides, as - /// vanilla's: on the server for every player of the world, and on a - /// server's client for the uses it predicts. Answer the same on both, - /// and change the world only on the server (`native_window` is false - /// there), not on a server's client. API 90. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn living_entity_use_item_start( - &self, - host: &mut dyn Host, - event: &mut LivingEntityUseItemStartEvent<'_>, - ) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// NeoForge's `LivingEntityUseItemEvent.Finish`: `entity`'s use of - /// `stack` in `hand` ran its time (`LivingEntity.completeUsingItem`), - /// `duration` the ticks left, always 0. `entity` is as in - /// `native_living_entity_use_item_start`. `result` arrives with what - /// `finishUsingItem` left in the hand (the bowl of a stew, none for the - /// last apple; a mod's own item is kept) and may be replaced. Fired for - /// consumables, potions, spyglasses and mods' items. Server-side, for every player - /// of the world (the helpers read the acting one). API 90. - fn living_entity_use_item_finish( - &self, - host: &mut dyn Host, - event: &mut LivingEntityUseItemFinishEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// The wheel turned over the open screen (`ScreenEvent.MouseScrolled.Pre`, - /// vanilla's `Screen.mouseScrolled`): `screen` is the screen it is, - /// `screen.mouse` where the pointer is (so there is no position of its - /// own), `scroll` the wheel's movement in x and y as the game's own scroll - /// handler scales it (a line times the wheel sensitivity: with Discrete - /// Scrolling on, a notch is ±1, its sign only; a touchpad's pixels are - /// divided by 30), and - /// `modifiers` GLFW's bits (shift 1, control 2, alt 4, super 8). Set - /// `canceled` and nothing the screen would scroll moves: not the bundle - /// under the pointer, nor a list, slider, scrollbar or map. Nothing else - /// reads the wheel while a screen of the game's is open, so no hotbar slot - /// changes either. - /// It fires before the screen scrolls anything, for the window's wheel - /// (horizon-ctl's `scroll-at` sends it through the window too), and - /// while a screen of the game's or the title's is open (Options and the - /// pack list opened from the title screen count, with no player and no - /// `menu`; the title's own since API 326). It - /// does not fire on the connect and loading - /// screens, nor for a server dialog, the server pack, link and - /// code-of-conduct prompts, a book or a sign editor drawn over gameplay, - /// nor while a world loads or saves: the scroll there stays the game's - /// own. A wheel a gamepad or the keyboard's focus sends is not the mouse - /// and fires nothing. - /// API 93. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn screen_mouse_scrolled_pre( - &self, - host: &dyn Host, - event: &mut ScreenMouseScrolledPreEvent<'_>, - ) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A command a player typed (NeoForge's `CommandEvent`, before - /// `Commands.performCommand` runs it), without its slash: any command, - /// vanilla's and the mods' own, also one the player may not run. Set - /// `canceled` to stop it: nothing runs and nothing is answered but the - /// lines pushed to `replies`, which go to the player as system chat. - /// No player's state is swapped in and the server's player list is busy - /// with this one, so `native_tell` and the other helpers that take a UUID - /// reach nobody here: answer through `replies`. Once for each command a - /// player sends, on a server, and for each line of a quiet command batch - /// (`horizon:quiet_commands`, whose replies are dropped). A command the - /// server refuses before reading it (unsigned chat arguments, the chat - /// rate limit) never reaches it. 95. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn player_command(&self, host: &mut dyn Host, event: &mut PlayerCommandEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A command a mod registered, typed on the dedicated server's console - /// or sent through `horizon-server console` - /// (`DedicatedServer.handleConsoleInputs`), without its slash: set `ran` - /// when it is yours. There is no player: the source is the console, at - /// permission level 4, so `native_acting_player` is None and the - /// player-only helpers give nothing; `native_feedback` answers on the - /// console. A name no mod registered goes to vanilla's dispatcher as - /// before. 99. - /// - /// Return `Flow::Cancel` to take it: this sets `ran`, and later mods are skipped. - fn perform_console_command( - &self, - host: &mut dyn Host, - event: &mut PerformConsoleCommandEvent<'_>, - ) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A key went down over the open screen (`ScreenEvent.KeyPressed.Pre`, - /// vanilla's `Screen.keyPressed`): `screen` is the screen it is, `key` - /// vanilla's name for the key (`key.keyboard.e`, the name `native_key` - /// hands over) — a key with no name fires nothing — `repeat` whether it is - /// the keyboard's auto-repeat of a held press (Horizon's extra; NeoForge - /// folds repeats into the event), and `modifiers` GLFW's bits (shift 1, - /// control 2, alt 4, super 8). Set `canceled` and the press goes no - /// further: no focus move, no Escape-close, no inventory or hotbar key, no - /// typing by the key itself, no `native_key`. - /// It fires for the window's presses and their repeats, and for the - /// control socket's `key` (a release fires nothing), after the window's - /// own shortcuts have run — the fullscreen and screenshot keys, the debug - /// keys and the narrator — and while a screen of the game's or the title's - /// is open (Options and the pack list opened from the title screen count, - /// with no player and no `menu`; the title's own since API 326). It does - /// not fire on the connect and loading screens, nor for a server dialog, the server pack, - /// link and code-of-conduct prompts, a book or a sign editor drawn over - /// gameplay, nor while a world loads or saves: the key there stays the - /// game's own. A cancelled press's release still runs, so a `native_key` - /// listener sees that release without its press, and its characters are - /// GLFW's separate char callback and still come; see - /// `native_screen_char_typed_pre`. - /// API 104. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn screen_key_pressed_pre( - &self, - host: &dyn Host, - event: &mut ScreenKeyPressedPreEvent<'_>, - ) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A character was typed at the open screen - /// (`ScreenEvent.CharacterTyped.Pre`, vanilla's `Screen.charTyped`): - /// `screen` is the screen it is, `c` the character, and `modifiers` GLFW's - /// bits (shift 1, control 2, alt 4, super 8). Set `canceled` and that one - /// character never reaches a text box. - /// It fires once per printable character of a key press's text and once - /// per character of the input method's committed text, never for a control - /// character (Enter, Backspace, Tab, Delete, Escape, a Control letter): - /// those are keys, and the game reads them as keys, not as text. A key's - /// characters fire after the key has acted, as GLFW's char callback comes - /// after its key callback (`KeyboardHandler.charTyped`): `screen` is the - /// screen open then, so a key that closes the screen (the inventory key) - /// leaves none to hear its character, where vanilla's closed screen hears - /// nothing, and one that moves the focus types into the box it moved to. - /// They fire even when `native_screen_key_pressed_pre` cancelled the press - /// that carried the text, as NeoForge delivers `CharacterTyped.Pre` for a - /// cancelled `KeyPressed.Pre`: the characters left reach the text box - /// without the key's own effects. Cancelling one character drops only that - /// one. The control socket's `type` fires it too, but not for its `\b` - /// backspace. Same screens as the key press, and it fires with no text box - /// focused (there is nothing to take the character then). - /// API 104. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn screen_char_typed_pre( - &self, - host: &dyn Host, - event: &mut ScreenCharTypedPreEvent<'_>, - ) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// The server is assembling its data tables from the world's data packs - /// (`ReloadableServerResources`): a world is opening, or `/reload` was - /// run, and the recipes, tags, loot and advancements are built after - /// this. Push `(path, bytes)` pack files and they join this load. `path` - /// is the overlay's key, `/:` without `.json`, over - /// the kinds the tables read: `recipe/mymod:thing`, - /// `tags/mymod:block/thing`, `loot_table/…`, `advancement/…`, - /// `trade_set/…`, `villager_trade/…`, `dialog/…`. A file that isn't JSON - /// is skipped with a warning. Asked once per load on both paths, so this - /// is where a mod recomputes them. - /// Contributed files sit below the world's own data packs: a pack's file - /// for the same path still overrides a mod's. Server-side (the single - /// player's integrated server included). API 106. - /// - /// Every mod gets `event.files` in order and may add, change or remove files. - fn data_pack_files(&self, host: &dyn Host, event: &mut DataPackFilesEvent<'_>) { - let _ = host; - let _ = event; - } - /// Change the colours of `biome` (`minecraft:plains`) on this client: the - /// grass, foliage (leaves, vines), dry foliage (leaf litter) and water - /// tints, 0xRRGGBB, as its biome file and the colormaps set them. Asked - /// for every biome when a mod calls `native_refresh_biome_colors`, which - /// rebuilds the terrain with them; a season mod answers from its season - /// and calls that when the season turns. The dark forest's modifier - /// still applies to the grass set here; the swamp's noise colours - /// replace it. API 107. - fn biome_colors(&self, host: &dyn Host, event: &mut BiomeColorsEvent<'_>) { - let _ = host; - let _ = event; - } - /// The server's data tables are built (NeoForge's `TagsUpdatedEvent` - /// with `SERVER_DATA_LOAD`): the recipes, tags, loot and advancements of - /// the world's data packs, and the files mods added through - /// `native_data_pack_files`, are what the helpers read from now on. - /// `reload` is false on a world's first open, before its chunks and - /// players are loaded, and true after `/reload` rebuilt them, once the - /// players were sent the new data. The place to - /// drop caches derived from the data and to rebind what depends on it. - /// Server-side (the single player's integrated server included); a client - /// connected to a server never fires it. API 108. - fn server_data_loaded(&self, host: &mut dyn Host, event: &mut ServerDataLoadedEvent) { - let _ = host; - let _ = event; - } - /// World field of view, after normal calculation and zoom and before projection/Nausea. - /// Called for world rendering and near-plane overlays; hand/HUD FOV uses its own path. - /// Only finite values in 1..=179 degrees replace the computed FOV. - fn compute_fov(&self, host: &dyn Host, event: &mut ComputeFovEvent) { - let _ = host; - let _ = event; - } - /// Draw the mob `mob` (its id, kind, custom name and the frame's body - /// and head turns and walk cycle, `LivingEntityRenderState`) on this - /// client with a model of your own, once a frame while it is in view - /// (vanilla's `EntityRenderer.submit`), and set `drawn` to leave out its - /// vanilla model, armour and held items (NeoForge's `RenderLivingEvent.Pre`, - /// cancelled). Add geometry with `native_draw_cube`, `native_draw_quad`, - /// `native_draw_block` and `native_draw_item` under `draw`'s pose stack, - /// which starts at the mob's feet turned with its body: +z is the way it - /// faces, +x its left, +y up, in blocks. `draw` also has the partial tick, - /// the game time and the mob's light. Its name tag, shadow, flames and - /// glowing outline draw as for any mob. Asked for every mob in view but - /// players; a mob your server half spawned (`native_spawn_mob`) is told - /// apart by its id, sent with mod messages, or by its custom name. - /// A mob is drawn afresh each frame: `draw.keep()` does nothing here. - /// API 110. - /// - /// Return `Flow::Cancel` to take it: this sets `drawn`, and later mods are skipped. - fn mob_submit(&self, host: &dyn Host, event: &mut MobSubmitEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// Final scaled third-person camera distance, before visual-shape collision clipping. - /// `entity` includes its living scale/distance and optional mount scale/distance. - /// Only finite requests in 0..=1024 blocks replace the vanilla distance. - fn camera_distance(&self, host: &dyn Host, event: &mut CameraDistanceEvent<'_>) { - let _ = host; - let _ = event; - } - /// Computed world fog RGB, after ordinary effects and before upload. Title panoramas skip it. - /// Only finite RGB components in 0..=1 are accepted; alpha and distances stay unchanged. - fn compute_fog_color(&self, host: &dyn Host, event: &mut ComputeFogColorEvent<'_>) { - let _ = host; - let _ = event; - } - /// Raw mouse button input on any screen, before dispatch; cancel skips dispatch and Post. - /// Button/modifiers use the same GLFW encoding as screen hooks. Synthetic clicks skip it. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn mouse_button_pre(&self, host: &mut dyn Host, event: &mut MouseButtonPreEvent) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// An accepted raw button event finished dispatch, including a screen handler's early return. - fn mouse_button_post(&self, host: &mut dyn Host, event: &mut MouseButtonPostEvent) { - let _ = host; - let _ = event; - } - /// `player` (`name`) respawned (NeoForge's `PlayerRespawnEvent`): at the - /// end of `PlayerList.respawn`, once they are placed and their health, - /// food and experience reset, with their state swapped in. Once for each - /// death that ends in a respawn, on a server; not for the End-exit one. - /// 115. - fn player_respawned(&self, host: &mut dyn Host, event: &mut PlayerRespawnedEvent<'_>) { - let _ = host; - let _ = event; - } - /// Final world fog, after the computed color hook, ordinary calculations and Horizon's fog - /// option, before upload. Title panoramas skip it. Invalid color or distance values retain the - /// original whole fog. - fn render_fog(&self, host: &dyn Host, event: &mut RenderFogEvent<'_>) { - let _ = host; - let _ = event; - } - /// Before an open screen handles a named key's release (same screen gate as key presses). - /// Cancel bypasses screen handling; physical keys and mapped keyboard hold/toggle state release. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn screen_key_released_pre( - &self, - host: &dyn Host, - event: &mut ScreenKeyReleasedPreEvent<'_>, - ) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// Choose this client's music, once a client tick: `music` and `volume` - /// hold what vanilla chose (`Minecraft.getSituationalMusic` and - /// `getMusicVolume`: the biome's track, the creative or underwater one, a - /// boss's, the menu's; None for silence). Set `music` to a track of your - /// own, a sound event of your content mod's `sounds.json` such as - /// `Music::new("mymod:music.spring", 1200, 6000, false)` (the delays in - /// ticks between songs, and whether it cuts off a different song playing), - /// None to keep vanilla's quiet, or change `volume` (0 to 1) to fade it. API - /// 119. - fn music(&self, host: &dyn Host, event: &mut MusicEvent<'_>) { - let _ = host; - let _ = event; - } - /// The tracks to play over (or instead of) the music on this client, - /// asked once a client tick: push a `MusicLayer` (a sound event, looped, - /// and its volume, 0 to 1) for each. A layer eases to a new volume over up - /// to two seconds, and one left out fades out and stops; layers that - /// first appear in the same tick start together, so list every stem of a - /// piece from the start, the silent ones at volume 0, and raise them (a - /// fuller arrangement as a village grows). Their sound events should be - /// `stream: true` in `sounds.json`. API 119. - /// - /// Every mod gets `event.layers` in order and may add, change or remove layers. - fn music_layers(&self, host: &dyn Host, event: &mut MusicLayersEvent<'_>) { - let _ = host; - let _ = event; - } - /// The server is about to send its data to the players (NeoForge's - /// `OnDatapackSyncEvent`, its `sendRecipes`): push the recipe types - /// (`minecraft:smelting`, `mymod:crusher`) whose recipes your client half - /// needs. Horizon sends their final recipes — the world's data packs - /// merged, load conditions held — to every Horizon client when it joins - /// and again after each `/reload`; a vanilla client is sent nothing new. - /// Asked once per load, when the server's data tables are built, and the - /// same types go to every player: unlike NeoForge, which asks again for - /// each joining player, it can't differ between players. Push nothing and - /// nothing is sent; push nothing after a load that pushed some and the - /// clients go back to their own recipes. Server-side (the single - /// player's integrated server included). API 120. - /// - /// Every mod gets `event.recipe_types` in order and may add, change or remove recipe_types. - fn datapack_sync(&self, host: &mut dyn Host, event: &mut DatapackSyncEvent<'_>) { - let _ = host; - let _ = event; - } - /// The server's recipes of `recipe_types` arrived (NeoForge's - /// `RecipesReceivedEvent`): from now on the recipe helpers - /// (`native_recipes`, `native_recipes_of_type`) give the server's recipes - /// of those types in place of the window's own, so rebuild what you made - /// of them. It comes after a join, once the window has its player, and - /// after every `/reload`, on a server whose mods asked for them - /// (`native_datapack_sync`), single player included; with no types when - /// the server stopped sending them and the window is back on its own - /// recipes. A recipe the - /// window's own recipe book doesn't know has `book_id` -1. Client-side. - /// API 120. - fn recipes_received(&self, host: &mut dyn Host, event: &mut RecipesReceivedEvent<'_>) { - let _ = host; - let _ = event; - } - /// A real window key event after handling, including repeats and consumed shortcuts. - /// Receives the original physical/logical key, text, location, state and repeat flag. - /// Modifier bits use the screen hooks' GLFW encoding. Not cancellable. - fn input_key(&self, host: &mut dyn Host, event_: &mut InputKeyEvent<'_>) { - let _ = host; - let _ = event_; - } - /// Gameplay scrolling after whole steps accumulate, before hotbar/spectator handling. - /// `scroll` is scaled x/y, `accumulated` whole x/y steps, `buttons` left/middle/right, - /// and `cursor` the GUI position. Cancel skips gameplay scrolling; synthetic input skips it. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn mouse_scrolling(&self, host: &mut dyn Host, event: &mut MouseScrollingEvent) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// Before a real held-button movement reaches screen drag handlers. - /// `screen.mouse` is the current GUI position; `delta` is its GUI-space movement. - /// Cancel keeps the new cursor position but skips all screen drag handlers. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn screen_mouse_dragged_pre( - &self, - host: &dyn Host, - event: &mut ScreenMouseDraggedPreEvent<'_>, - ) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// The number of players with the mod container at `pos` of `dimension` - /// open went from `previous` to `viewers` (vanilla's - /// `ContainerOpenersCounter`, as a barrel keeps it): 0 to more than 0 is - /// `onOpen`, back to 0 is `onClose`, any change `openerCountChanged`, so a - /// block can show itself open while `viewers` is not 0. Counted every - /// tick from the players' open menus, so a player who leaves without - /// closing (a lost connection, death, another dimension, out of reach, the - /// block gone) is heard too (`recheckOpeners`). Once for each change, with - /// no player's state swapped in. Server-side. - /// 124. - fn mod_container_viewers(&self, host: &mut dyn Host, event: &mut ModContainerViewersEvent<'_>) { - let _ = host; - let _ = event; - } - /// `LivingEntity.knockback` is about to push entity `entity` of type `kind` - /// (`minecraft:zombie`, `minecraft:player`) `strength` away from the direction (`x`, `z`), - /// on the raw strength, before the entity's knockback resistance takes its share and its motion - /// changes - /// (docs/native-mods.md, API 125). Vanilla's direction points at the - /// source of a hit (the attacker's side), and the entity is pushed the other way. Change - /// `strength`, `x` or `z`, or set `canceled`, and the push follows them: canceled, the - /// entity's motion is left alone. A `strength` of 0 or less after the hook is no push, and a - /// direction too short to tell draws a random one after it, as in vanilla. A value a mod - /// leaves that is not a finite number is put back as it was. `damage_type` - /// (`minecraft:player_attack`), `causing` (`DamageSource.getEntity`) and `direct` - /// (`getDirectEntity`) are the push's `DamageSource`, named as `native_living_incoming_damage` - /// names a hit's, with the same entity ids left at 0. It fires for the players and mobs the - /// game knocks back: a hit's own push, a sprinting or Knockback hit, a sweep, a spear, a - /// shield's push on its holder and a goat's ram. It does not fire for a sulfur cube with a - /// block that a hit with a causing entity knocks back (`SulfurCube.knockback` does not call - /// `super` there), for the pushes vanilla doesn't make with `LivingEntity.knockback` (the - /// ender dragon's wings, Punch arrows, explosions, wind charges, a mace smash's area, a - /// hoglin's throw), nor yet for the ones Horizon makes another way: a mob's melee push from - /// its attack knockback or a Knockback weapon, an armor stand's, the ender dragon's. It runs - /// on the thread that ticks the world (the server thread): state - /// shared with render thread hooks needs synchronising. No `self`: the game is in the middle - /// of the hit. API 125. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn living_knockback(&self, event: &mut LivingKnockbackEvent<'_>) -> Flow { - let _ = event; - Flow::Continue - } - /// Whether the experience orb `into` takes the orb `from` in (vanilla's - /// `ExperienceOrb.canMerge`): the same value, and ids a multiple of 40 apart. Asked - /// for each pair whose boxes meet, when an orb spawns (`tryMergeToExisting`: `from` is - /// 0, the new orb isn't an entity yet, and only `from_value` is known) and every 20 - /// ticks as an orb scans for neighbours (`scanForMerges`). `can_merge` holds vanilla's - /// answer; change it either way. A merged orb counts as `count` orbs of `into`'s - /// value, so forcing orbs of different values together drops points unless the mod - /// raises `into`'s value with `native_set_experience_orb` in `native_experience_orb_merge`. - /// Server-side. API 128. - fn experience_orb_can_merge( - &self, - host: &mut dyn Host, - event: &mut ExperienceOrbCanMergeEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// The experience orb `into` is about to take `from` in, after - /// `native_experience_orb_can_merge` said yes (`from` is 0 and `from_value` the - /// points of a new orb that is spawning). Set `canceled` to stop it: both orbs stay, and - /// a spawning orb tries the next orb in reach, then spawns on its own. Server-side. - /// API 128. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn experience_orb_merge( - &self, - host: &mut dyn Host, - event: &mut ExperienceOrbMergeEvent<'_>, - ) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// Hook point for native mods (docs/native-mods.md, listed in `native-hooks.json` - /// with the game's): container slot `slot` of the content mod menu type `menu` - /// (`data//horizon/menu/`) is asked whether it takes `stack` - /// (`Slot.mayPlace`) and how many of it it holds (`Slot.getMaxStackSize`). - /// `may_place` and `max_stack` hold the type's own answer (false on one of its - /// `results`, true elsewhere, and 99): change them for your own menu types. - /// `max_stack` is kept to 1 to 99, - /// and the item's own stack size still caps it. Asked on the server, for each - /// click, drag and shift-click that puts an item in a slot, several times for - /// one shift-click: answer from `stack` alone, and fast. API - /// 130. - fn menu_slot_rules(&self, event: &mut MenuSlotRulesEvent<'_>) { - let _ = event; - } - /// NeoForge's `LivingEntityUseItemEvent.Tick`: each tick `entity` uses - /// `stack` in `hand`, where `LivingEntity.updatingUsingItem` starts, - /// `duration` the ticks left. `entity` is as in - /// `native_living_entity_use_item_start`. Change `duration` to move the - /// end (consumables, potions, spyglasses and mods' items count to it, - /// and so do a client's eating crumbs and first person pose, as the - /// started duration of `native_living_entity_use_item_start` does). A - /// cancel sets it to -1. Once a mod set it, the use goes on - /// only while it is above 0: at 0 or less, by a cancel or set, the - /// item's tick is skipped and the server finishes the use - /// (`completeUsingItem`: `finishUsingItem`, the food eaten, - /// `native_living_entity_use_item_finish`), while an item used on - /// release (a crossbow) stays frozen until let go. NeoForge's javadoc - /// says a cancel stops the use, but its code finishes it; this follows - /// the code. On a server's client a run-out use stays in use, frozen, - /// so letting go still tells the server. Both sides, as vanilla's - /// `updatingUsingItem`: answer the same on both, and change the world - /// only on the server (`native_window` is false there), not on a - /// server's client. Runs every tick of every use: - /// check the item first. API 131. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn living_entity_use_item_tick( - &self, - host: &mut dyn Host, - event: &mut LivingEntityUseItemTickEvent<'_>, - ) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// `Item.onUseTick` of `stack`, used by `entity` with `remaining` ticks - /// left, always above 0: what a mod's own item does each tick it is used - /// (cook, charge, drain). It comes where `ItemStack.onUseTick` calls the - /// item, once `native_living_entity_use_item_tick` let the use go on: - /// after a consumable's crumbs, for consumables, shields and mods' items - /// (vanilla's other items do their own, and a kinetic weapon has none on - /// the server). Both sides, as `native_living_entity_use_item_tick`: - /// change the world only where this `Game` runs the world, not on a - /// server's client. Runs every tick of every use: check the item first. - /// API 131. - fn on_use_tick(&self, host: &mut dyn Host, event: &mut OnUseTickEvent<'_>) { - let _ = host; - let _ = event; - } - /// NeoForge's `LivingEntityUseItemEvent.Stop`: `entity` lets go of - /// `stack` in `hand` before its use ran its time - /// (`LivingEntity.releaseUsingItem`: the use button let go, a screen - /// opened), `duration` the ticks left. Set `canceled` to skip the item's - /// release (a bow doesn't shoot, a spyglass makes no sound, - /// `native_release_using` isn't called); the use stops anyway. Not when - /// the hand changed item, which stops the use without a release. Both - /// sides, as `native_living_entity_use_item_tick`. API 131. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn living_entity_use_item_stop( - &self, - host: &mut dyn Host, - event: &mut LivingEntityUseItemStopEvent<'_>, - ) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// `Item.releaseUsing` of `stack`: `entity` let it go with `time_left` - /// ticks of its `native_use_duration` left, so it was held for - /// `duration - time_left` (a bow-like item shoots by that charge). Set - /// `used` as vanilla's return, and the stack's use cooldown starts - /// (`ItemStack.releaseUsing`). After the vanilla item's own release, once - /// `native_living_entity_use_item_stop` let it through. Both sides, as - /// vanilla's: change the world only where this `Game` runs the world, - /// not on a server's client. API 131. - fn release_using(&self, host: &mut dyn Host, event: &mut ReleaseUsingEvent<'_>) { - let _ = host; - let _ = event; - } - /// A loot function of a mod's own kind: the entry named a `type` in a - /// namespace outside `minecraft:`, and the roll kept it for this call. - /// The kind, the JSON, the context and the stack to change are here. - /// API 133. - /// - /// Every mod gets `event.stack` in order and may add, change or remove stack. - fn loot_function(&self, event: &mut LootFunctionEvent<'_>) { - let _ = event; - } - /// A loot condition of a mod's own kind: the entry named a `type` in a - /// namespace outside `minecraft:`. The kind, the JSON, the context and - /// whether it holds are here (`holds` starts `false`; a mod that claims - /// the kind sets it). API 133. - /// - /// Every mod gets `event.holds` in order and may add, change or remove holds. - fn loot_condition(&self, event: &mut LootConditionEvent<'_>) { - let _ = event; - } - /// `dimension`'s level opened on the server (NeoForge's `LevelEvent.Load`, - /// posted in `MinecraftServer.createLevels`): before any of its chunks - /// (`native_chunk_load`), so a mod sets up what it keeps for the level - /// here. The level the world opens in comes before `WorldOpenReady`; the - /// others open when someone first goes there (a player, a portal, an - /// `execute in` command), with the level in place. Once for each open: - /// `native_level_unload` follows. Server-side. API 134. - fn level_load(&self, host: &mut dyn Host, event: &mut LevelLoadEvent<'_>) { - let _ = host; - let _ = event; - } - /// `dimension`'s level closed (NeoForge's `LevelEvent.Unload`, posted after - /// `level.close()` in `MinecraftServer.stopServer`), after its last save: - /// drop what the mod kept for it. A level other than the Overworld closes - /// once nobody has been in it for 300 ticks, after its chunks' unloads; - /// every level still open closes when the server stops, before - /// `WorldCloseFinal`, and when its open or its loop fails, before - /// `WorldOpenFailed` or `WorldCloseFailed`. Once for each - /// `native_level_load`. Don't read its blocks. Server-side. API - /// 134. - fn level_unload(&self, host: &mut dyn Host, event: &mut LevelUnloadEvent<'_>) { - let _ = host; - let _ = event; - } - /// The client made a new level for `dimension` (NeoForge's - /// `LevelEvent.Load` from the `ClientLevel` constructor): on joining a - /// server, single player's own included, and on changing dimension. - /// Its chunks come after. When the dimension changes, the old level's - /// `native_client_level_unload` follows this, as `Minecraft.setLevel` - /// tells it after the new level is made. Client-side, on the window; the - /// server's levels have `native_level_load`. API 135. - fn client_level_load(&self, host: &mut dyn Host, event: &mut ClientLevelLoadEvent<'_>) { - let _ = host; - let _ = event; - } - /// The client dropped its level for `dimension` (`LevelEvent.Unload` - /// from `Minecraft.setLevel` and `disconnect`): replaced by another - /// dimension's, or on leaving the server. Once for each - /// `native_client_level_load`. Not told when the game itself quits. - /// Client-side. API 135. - fn client_level_unload(&self, host: &mut dyn Host, event: &mut ClientLevelUnloadEvent<'_>) { - let _ = host; - let _ = event; - } - /// Whether a mod hears projectiles' impacts (`native_projectile_impact`): - /// set `hears`. Asked once a server tick before the projectiles fly; only - /// then does the tick keep the copy of each flying projectile that a - /// canceled impact flies on from, so a game without such a mod pays - /// nothing. Server-side. API 136. - /// - /// Every mod gets `event.hears` in order and may add, change or remove hears. - fn hears_projectile_impacts(&self, host: &dyn Host, event: &mut HearsProjectileImpactsEvent) { - let _ = host; - let _ = event; - } - /// A projectile hit something (NeoForge's `ProjectileImpactEvent`, posted - /// where vanilla's projectile has found what it meets, before the hit - /// acts): `projectile` (its entity id) of type `kind` (`minecraft:arrow`), - /// shot by `owner` (a mob's or a player's entity id, 0 for none) in the level - /// `dimension`. It hit the entity `entity` of type `entity_kind` (a player is - /// `minecraft:player`; a dragon's part is `minecraft:ender_dragon`), or, with - /// `entity` None, the block at `block` on its `face` (the face's normal, - /// `[0, 1, 0]` for the top), at the point `at`. Set `canceled` and the impact - /// does not happen: no damage, no knockback, no sound, no block reaction, the - /// arrow does not stick, a pearl does not teleport, a potion or a fireball - /// does not burst. The projectile flies on and is not discarded, so a mod - /// that cancels also decides what becomes of it. It continues through its - /// whole motion and may hit something on a later tick. Covers what - /// `tick_projectiles` flies: arrows, tridents, snowballs, - /// eggs, pearls, potions, fireballs, wither skulls, wind charges, llama spit and - /// shulker bullets, from players, mobs and dispensers. It does not fire for - /// a breeze's deflection, for a hit on a wind charge, for fireworks or for - /// fishing bobbers. A dragon part or End crystal is told by the id of the - /// dragon or crystal. Heard only when a mod also sets - /// `native_hears_projectile_impacts`. Server-side. API 136. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn projectile_impact( - &self, - host: &mut dyn Host, - event: &mut ProjectileImpactEvent<'_>, - ) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// An entity mounts or dismounts a vehicle (NeoForge's `EntityMountEvent`, posted from - /// `Entity.startRiding` and `removeVehicle`): `entity` of type `kind` (`minecraft:player`, - /// `minecraft:zombie`) in the server level `dimension`, getting on (`mounting`) or off the - /// `vehicle` of type `vehicle_kind` (`minecraft:oak_boat`, `minecraft:minecart`, - /// `minecraft:horse`, `minecraft:cushion`). Set `canceled` and it does not: a player stays - /// where they were, a mob does not board, a `/ride` fails, and a rider whose dismount is - /// canceled keeps riding. Told for a player boarding a boat, minecart, cushion or a mount (pig, - /// strider, horse and its kin, llama, nautilus, happy ghast) and getting off one by their - /// own input, `/ride`, a stab, a buck, a teleport or spectator mode; and for the mobs - /// Horizon seats on a vehicle (jockeys, raiders on a ravager, `/ride`) and lets off one - /// (`/ride`, a stab). It is not told for a rider whose vehicle was removed from the - /// level (a boat or minecart destroyed, a broken cushion), nor for riders loaded with a - /// save. The acting player is the one whose action runs, a guest on a server. Server-side. - /// API 137. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn entity_mount(&self, host: &mut dyn Host, event: &mut EntityMountEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// An interaction key acts on what the crosshair points at - /// (`InputEvent.InteractionKeyMappingTriggered`), while playing: `button` 0 is - /// the attack key (`key_mapping` `key.attack`), 1 the use key (`key.use`, once - /// for each hand it tries, the main hand first), 2 pick block (`key.pickItem`, - /// only with something aimed at). The attack fires when an attack is allowed - /// and again each tick a held attack key keeps digging a block. Set `canceled` - /// to skip the action: a cancelled use also skips the other hand. A cancelled - /// attack or use still swings `hand` unless the mod clears `swing_hand` (it - /// starts true). A server's player and a single player's window only; a mod - /// that claims an item's use (`mod_use_start`) sees the event first. API - /// 138. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn interaction_key(&self, host: &mut dyn Host, event: &mut InteractionKeyEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// Before one of vanilla's HUD layers is drawn (`RenderGuiLayerEvent.Pre`): - /// draw here to put something below the layer, or set `canceled` to hide - /// it. `layer` is the layer's id, in the order Horizon draws them: - /// `minecraft:crosshair`, `hotbar`, `effects`, `boss_overlay`, - /// `vehicle_health` (only riding a mount), `contextual_info_bar` (the - /// mount's jump bar, the locator bar or the experience bar, with the level - /// number), `player_health` (the health, armor, food and air rows, drawn in - /// one pass), `selected_item_name`, `sleep_overlay`, `overlay_message` - /// (the action bar), `title`, `scoreboard_sidebar`, `subtitle_overlay`, - /// `chat` and `tab_list`. A layer F1 hides fires nothing, and the chat - /// screen draws the focused chat itself, so `chat` is only the unfocused - /// lines. The camera overlays and the debug screen are not layers here: - /// they are not drawn in this pass. Draw with `native_fill`, `native_text` - /// and the sprite helpers; `batch.width` and `batch.height` are the GUI - /// size. Together with `native_gui_layer_post` this is NeoForge's - /// `RegisterGuiLayersEvent`: a mod keeps its own list of what it draws - /// above or below which layer. API 139. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn gui_layer_pre(&self, host: &mut dyn Host, event: &mut GuiLayerPreEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// After one of vanilla's HUD layers (or after the layer a mod hid), with - /// the ids of `native_gui_layer_pre`: draw here to put something above - /// the layer (`RenderGuiLayerEvent.Post`). API 139. - fn gui_layer_post(&self, host: &mut dyn Host, event: &mut GuiLayerPostEvent<'_>) { - let _ = host; - let _ = event; - } - /// Icons a mod puts on map `map` each time it is drawn: held, in an - /// item frame or on the cartography table's screen (a map's pixels are - /// its own; nothing is saved in the map). Push `NativeMapDecoration`s - /// (an atlas sprite, a position in half map pixels, a rotation, and - /// whether it shows in a frame and on the screen like vanilla's markers - /// do); they are drawn after the vanilla ones with the same pose, and a - /// sprite the atlas lacks is left out. `map.center`, `map.scale` and - /// `map.dimension` say which blocks the map shows, and - /// `NativeMapDecoration::x` has the formula. Vanilla's decorations are - /// not passed in. Asked every frame a map is drawn, so keep it cheap. - /// The client half of `MapDecorationType` and `addDecoration`: a type - /// registered on the server and saved in the map's data is not here. - /// API 140. - /// - /// Every mod gets `event.decorations` in order and may add, change or remove decorations. - fn map_decorations(&self, host: &dyn Host, event: &mut MapDecorationsEvent<'_>) { - let _ = host; - let _ = event; - } - /// A client started tracking an entity (NeoForge's - /// `PlayerEvent.StartTracking`, posted at the end of - /// `ServerEntity.addPairing`): `player` (`name`) is now sent `entity` - /// (its ID, `uuid` its UUID, `kind` its type, `minecraft:zombie`) and its - /// changes. It comes into range, its client's chunks reach it, or it is - /// put in the level; another player is an entity too, and a player never - /// tracks their own. A mod sends the player what they need to show it - /// (`native_net_send_to_player`). The entity is as it was last tick: it - /// may be gone by now. Heard after the tick's players and entities have - /// moved, in order, with no player's state swapped in. Server-side. - /// API 141. - fn player_start_tracking(&self, host: &mut dyn Host, event: &mut PlayerStartTrackingEvent<'_>) { - let _ = host; - let _ = event; - } - /// A client stopped tracking an entity (NeoForge's - /// `PlayerEvent.StopTracking`, posted at the end of - /// `ServerEntity.removePairing`): it went out of range, left the level or - /// died, or `player` (`name`) left, changed level or respawned, which - /// stops everything they tracked. `entity`, `uuid` and `kind` are as - /// `native_player_start_tracking` told. Once for each start, a leaving - /// player's after `native_player_logged_out`. The entity is not - /// necessarily still there, and a player who left is gone: no player's - /// state is swapped in. Server-side. API 141. - fn player_stop_tracking(&self, host: &mut dyn Host, event: &mut PlayerStopTrackingEvent<'_>) { - let _ = host; - let _ = event; - } - /// Block `block`'s block entity has an energy buffer - /// (`Capabilities.Energy.BLOCK`): set `energy` when the block is yours, - /// as `SimpleEnergyHandler` is built: its capacity, the most one insert - /// and one extract moves, and the faces a fill may come through and a - /// drain may leave by. Asked once for each mod block when the ticking - /// table is built; a capacity under 1 leaves the buffer out, and the - /// limits are clamped to 0..=capacity. The amount is kept in the entity's - /// saved compound, as the int `energy`, and the energy helpers below read - /// and write it. API 142. - /// - /// Return `Some` to set `energy`; the first mod returning `Some` wins. - fn block_entity_energy( - &self, - host: &dyn Host, - event: &mut BlockEntityEnergyEvent<'_>, - ) -> Option { - let _ = host; - let _ = event; - None - } - /// The server is assembling its data tables, as for - /// `native_data_pack_files` (a world's first open and every `/reload`): - /// push the data map types your mod registers (NeoForge's - /// `RegisterDataMapTypesEvent`). A type is an `id` (`mod_id:name`, not in - /// `minecraft`) for a `registry`, `minecraft:item` or `minecraft:block` - /// so far. Its values come from the world's data packs' - /// `data//data_maps//.json`, and from files - /// you push through `native_data_pack_files` under the key - /// `data_maps/:/`: `replace`, `values` - /// (entry ids and `#tags` to JSON values) and `remove`, a newer value - /// overriding an older. Read them with `native_data_map` and - /// `native_data_map_entries`, in `native_data_maps.rs`. Server-side, not - /// synced to clients. API 143. - /// - /// Every mod gets `event.types` in order and may add, change or remove types. - fn data_map_types(&self, host: &dyn Host, event: &mut DataMapTypesEvent<'_>) { - let _ = host; - let _ = event; - } - /// An entity's breathing this tick (`LivingBreatheEvent`, posted from - /// `LivingEntity.baseTick` every tick for a living entity with the generic - /// air logic): `entity` is its ID and `kind` its type (`minecraft:player`, - /// `minecraft:zombie`). `can_breathe`, `consume` and `refill` come in as - /// Horizon does it today (a mod-free game changes nothing); set them to - /// change this tick. With `can_breathe` the air rises by `refill`, up to its - /// maximum (300); without, it falls by `consume`, and at -20 it is set to 0 - /// and the entity takes 2 drowning damage. Negative amounts count as 0. - /// Asked on servers for players and for mobs, except those with a - /// `WaterAnimal`'s or an axolotl's own air logic. API 144. - fn living_breathe(&self, event: &mut LivingBreatheEvent<'_>) { - let _ = event; - } - /// Blocks this mod adds, declared in code rather than one file each - /// (NeoForge's `DeferredRegister.Blocks` drained by `RegisterEvent`): - /// push a `horizon_mods::content::Block` per block, the same declaration - /// a content mod's `data//horizon/block/.json` holds (a - /// vanilla `template`, `properties`, `state_properties`, `shapes`...), - /// with its id in your mod's namespace. `item: true` gives it a block - /// item, as a file does unless told not to (`..Default::default()` - /// leaves it false). They join the content mods' blocks: the one whose - /// id is that namespace when it is on (your models, textures, loot and - /// recipes are its files), else one of their own, with the template's - /// look. Ids, saves and the check that a joining client has the same - /// blocks are the files' (docs/mods.md). Asked once at start, on every - /// side, before any resource loads: push the same ones every time, from - /// nothing but your code. Declarations that don't pass the files' checks - /// are logged and that namespace's are left out. API - /// 145. - /// - /// Every mod gets `event.blocks` in order and may add, change or remove blocks. - fn blocks(&self, event: &mut BlocksEvent<'_>) { - let _ = event; - } - /// Items this mod adds, declared in code (NeoForge's - /// `DeferredRegister.Items`), as `native_blocks` declares blocks: a - /// `horizon_mods::content::Item` per item, a content mod's - /// `data//horizon/item/.json` (a vanilla `template`, - /// `max_stack_size`, `rarity`, `components`, `crafting_remainder`). A - /// block's item comes with its block (`item: true`), not from here. - /// Asked once at start, on every side. API 145. - /// - /// Every mod gets `event.items` in order and may add, change or remove items. - fn items(&self, event: &mut ItemsEvent<'_>) { - let _ = event; - } - /// An entity of type `kind` (`minecraft:zombie`, `minecraft:item`) with ID `entity` is - /// about to tick in the server level `dimension` (NeoForge's `EntityTickEvent.Pre`, posted - /// around `Entity.commonTick` and `tick` in `ServerLevel.tickNonPassenger`). Set `canceled` - /// and it does not tick this tick: a mob has no AI, physics or effects, an item no movement, - /// ageing or merging, a minecart or boat no motion, and `native_entity_tick_post` is not told. - /// Horizon ticks a family of entities together, so mobs and items hear every Pre before the - /// first of them ticks; minecarts and boats hear theirs one by one. Told for the mobs, item - /// entities, minecarts and boats that tick: those in an entity-ticking chunk, not a player's - /// boat or cart (it ticks with the player). Later APIs add other kinds, so check `kind`. It - /// runs for every ticking entity each tick, so keep it cheap. Server-side. API - /// 146. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn entity_tick_pre(&self, host: &mut dyn Host, event: &mut EntityTickPreEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// The entity `entity` of type `kind` ticked in the server level `dimension` (NeoForge's - /// `EntityTickEvent.Post`): for each entity `native_entity_tick_pre` let through, after it - /// ticked, also one that died or was removed in its tick. Mobs and items hear their Posts - /// once the whole family has ticked; minecarts and boats right after their own tick. Server-side. - /// API 146. - fn entity_tick_post(&self, host: &mut dyn Host, event: &mut EntityTickPostEvent<'_>) { - let _ = host; - let _ = event; - } - /// Whether a mod listens to `native_entity_tick_pre` and `native_entity_tick_post`: a mod - /// that hooks them hooks this one too and sets `listened` (HEAD, `*listened = true`). - /// Without it the game tells no entity's tick, and keeps no list of them. Asked once for - /// each family (mobs, items, minecarts, boats) each tick. Server-side. API 146. - /// - /// Every mod gets `event.listened` in order and may add, change or remove listened. - fn entity_tick_listened(&self, host: &mut dyn Host, event: &mut EntityTickListenedEvent) { - let _ = host; - let _ = event; - } - /// Item `item` (`mod_id:name`) is a fluid container - /// (`Capabilities.FluidHandler.ITEM`): set `capacity`, in millibuckets, - /// when the item is yours (clamped to 1..=1 000 000; vanilla's buckets - /// are built in and cannot be changed). Asked at each call of the item - /// fluid helpers (`native_item_fluid`, `native_item_fluid_fill`, - /// `native_item_fluid_drain`, in `native_item_fluids.rs`). What the - /// container holds is kept in the stack's `minecraft:custom_data`, under - /// `horizon_fluid`. API 147. - /// - /// Return `Some` to set `capacity`; the first mod returning `Some` wins. - fn item_fluid_tank(&self, host: &dyn Host, event: &mut ItemFluidTankEvent<'_>) -> Option { - let _ = host; - let _ = event; - None - } - /// A mob's or an armor stand's equipment slot took a different stack - /// (NeoForge's `LivingEquipmentChangeEvent`, posted in - /// `LivingEntity.collectEquipmentChanges`): `entity` is its ID and `kind` - /// its type (`minecraft:zombie`), `slot` the `EquipmentSlot` (`mainhand`, - /// `offhand`, `feet`, `legs`, `chest`, `head`, `body`, `saddle`), `from` - /// what the slot held when this was last heard (none for empty) and `to` - /// what it holds now. Once for each slot that changed, in that order, - /// after the mobs' tick, so a mob that joins the level wearing something - /// counts from empty on its first tick. It only hears: change the mob with - /// its own helpers. Players are heard by `native_on_equip`. Server-side. - /// API 148. - fn living_equipment_change( - &self, - host: &mut dyn Host, - event: &mut LivingEquipmentChangeEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// Whether a mod listens to the mobs' `native_living_equipment_change`: a - /// mod that hooks it hooks this one too and sets `listened` (HEAD, - /// `*listened = true`). Without it the game does not read every mob's - /// equipment each tick, and mobs are not told (armor stands still are). A - /// mob that ticks once it is set counts from empty. Asked once a tick, after - /// the mobs' tick. Server-side. API 148. - /// - /// Every mod gets `event.listened` in order and may add, change or remove listened. - fn living_equipment_listened( - &self, - host: &mut dyn Host, - event: &mut LivingEquipmentListenedEvent, - ) { - let _ = host; - let _ = event; - } - /// Draw the player `player` (`who`: its profile name, and whether it is - /// this window's own player) with a model of your own, once a frame while - /// it is in view (`RenderPlayerEvent.Pre`), and set `drawn` to leave out its - /// vanilla model, armour, held items, cape and the other layers. This - /// window's player is drawn only in third person (and F5 front view); an - /// invisible player is not asked. `draw` is the frame of `native_mob_submit`: - /// at the player's feet, turned with the body. The name tag, shadow, - /// flames and glowing outline draw as before. API 149. - /// - /// Return `Flow::Cancel` to take it: this sets `drawn`, and later mods are skipped. - fn player_submit(&self, host: &dyn Host, event: &mut PlayerSubmitEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A layer on every mob and player in view, once a frame after its - /// vanilla model and layers (`EntityRenderersEvent.AddLayers`, with - /// `RenderLivingEvent.Post` and `RenderPlayerEvent.Post`): add geometry - /// in the entity's frame, which starts at its feet turned with the body - /// (`mob.head_yaw`, `mob.pitch` and the walk cycle place a part, the eyes - /// being at the entity type's eye height). `who` is set for a player. - /// Not asked for what a mod drew itself (`native_mob_submit`, - /// `native_player_submit`) nor for a mob that isn't drawn at all. - /// API 149. - fn mob_layer(&self, host: &dyn Host, event: &mut MobLayerEvent<'_>) { - let _ = host; - let _ = event; - } - /// Whether a mod listens to `native_player_submit` or `native_mob_layer`: a - /// mod that hooks either hooks this one too and sets `listened` (HEAD, - /// `*listened = true`). Without it the game poses no player or mob for - /// them, each frame. Client-side. API 149. - /// - /// Every mod gets `event.listened` in order and may add, change or remove listened. - fn player_render_listened(&self, host: &dyn Host, event: &mut PlayerRenderListenedEvent) { - let _ = host; - let _ = event; - } - /// A player finished editing a sign: `ServerGamePacketListenerImpl.updateSignText` - /// has taken their update packet and every check has passed (they were - /// editing that sign, within reach, the sign is not waxed, the chat filter - /// let the text through), right before the lines go on the sign. `pos` in - /// `dimension`, the `front` or back side, and the four `lines` as written, - /// plain text with the formatting codes still in (vanilla strips them - /// afterwards). Rewrite `lines` (made four lines again) and that is what - /// the sign gets, without a second look from the chat filter, or set - /// `cancel` and the sign keeps its old text. With the player's state - /// swapped in, so `native_tell` and the helpers that act on the player - /// reach them. Once for each update a player sends on a server. Not told: - /// the host's own editing of a sign on a LAN or single-player world - /// (the editor writes into their world directly), and the sign changes - /// that are no edit of the text (dyeing, glow ink, waxing, `/data`, - /// `/setblock`, mods' `native_with_mod_block_entity`). - /// 150. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn sign_changed(&self, host: &mut dyn Host, event: &mut SignChangedEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// Models no blockstate names that this client bakes beside the block - /// models (NeoForge's `ModelEvent.RegisterStandalone`): push their ids - /// (`mymod:block/shaft_half`, the model at - /// `assets/mymod/models/block/shaft_half.json`). Parts a block entity or - /// a mob draws, with `native_draw_model`, so that no block is registered - /// only to bake its model. Asked once at start, before the resources - /// load; every resource load, a pack reload included, bakes them again - /// from that pack's files. A model that doesn't read is logged and left - /// out; an id without a namespace is dropped. Every side asks it, but - /// only a window bakes models. API 151. - /// - /// Every mod gets `event.models` in order and may add, change or remove models. - fn standalone_models(&self, event: &mut StandaloneModelsEvent<'_>) { - let _ = event; - } - /// `player` (`name`) opened a menu (NeoForge's `PlayerContainerEvent.Open`, - /// posted when `ServerPlayer.openMenu` sets the new `containerMenu`): any - /// menu, vanilla or a mod's. `menu` is the menu type's name - /// (`minecraft:generic_9x3`, `minecraft:furnace`, `minecraft:merchant`, - /// `minecraft:crafting`, `minecraft:lectern`, a mod's own), `pos` the - /// block it is of in `dimension` (none for a trade, a vehicle's, a mod's - /// native menu or a mount's inventory, which has no menu type in vanilla - /// and is told as `horizon:mount_inventory`). The player's own inventory - /// is not one. Heard once a tick, so a menu opened and closed again - /// inside one tick is not; one that replaces another comes after that - /// one's close. The player's state is swapped in. `native_player_container_close` - /// follows. Server-side. API 152. - fn player_container_open(&self, host: &mut dyn Host, event: &mut PlayerContainerOpenEvent<'_>) { - let _ = host; - let _ = event; - } - /// `player` (`name`) closed the menu `native_player_container_open` told - /// of (NeoForge's `PlayerContainerEvent.Close`, posted in - /// `ServerPlayer.doCloseContainer` after the menu's `removed`): they - /// closed it, walked out of reach, died, changed level, or another menu - /// opened. A player who leaves closes it before `native_player_logged_out` - /// (vanilla posts nothing for a disconnect; Horizon does, so each open has - /// its close). Once for each open; not told when the server stops. The - /// menu's items are where it left them: the block's, and the cursor stack - /// back in the inventory. State swapped in, when the player has a body. - /// Server-side. API 152. - fn player_container_close( - &self, - host: &mut dyn Host, - event: &mut PlayerContainerCloseEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// A player's client started (`flying`) or stopped flying (vanilla's - /// `handlePlayerAbilities`, which honours it only for a player who may - /// fly: creative or spectator), heard once for each change, on a server, - /// with the player's state swapped in, so `native_tell` and the other - /// helpers reach them. The player is already flying or not as asked, as - /// vanilla leaves them; set `cancel` to refuse it: they keep the state - /// they had and the server sends their abilities back, as vanilla does - /// for what it refuses, so the client lands or flies again. Only what a - /// client asks for is heard: not the flight a game-mode change starts or - /// ends (`/gamemode`), and not a LAN host's own player. - /// 153. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn player_toggle_flight( - &self, - host: &mut dyn Host, - event: &mut PlayerToggleFlightEvent<'_>, - ) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// An anvil at `pos` took damage, changing its block from `from` to `to`. - /// `to` is air when the anvil broke. Server-side. - /// 183. - fn anvil_damaged(&self, host: &mut dyn Host, event: &mut AnvilDamagedEvent<'_>) { - let _ = host; - let _ = event; - } - /// NeoForge's `ArrowLooseEvent`: the player releases a bow `stack` with - /// `has_ammo` and `charge` ticks held. Change `charge` or set `canceled` - /// to prevent the shot; negative charge also prevents it. Unlike - /// `native_living_entity_use_item_stop`, this does not skip the whole item - /// release; `native_entity_shoot_bow` reports a spawned arrow afterward - /// with read-only force. Fires on the Game running the bow release, including - /// LAN guests; a remote client's own release path is not covered. Crossbows - /// are not covered; `native_arrow_nock` is the bow's start. 318. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn arrow_loose(&self, host: &mut dyn Host, event: &mut ArrowLooseEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A beacon at `pos` activated or deactivated after its beam was checked. - /// `active` says whether it now has a complete pyramid. Server-side. - /// 184. - fn beacon_state_change(&self, host: &mut dyn Host, event: &mut BeaconStateChangeEvent<'_>) { - let _ = host; - let _ = event; - } - /// On an authoritative server, a bell at `pos` is about to ring. Set - /// `cancel` to stop the ring and its raider reveal. 185. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn bell_ring(&self, level: &dyn LevelHost, event: &mut BellRingEvent) -> Flow { - let _ = level; - let _ = event; - Flow::Continue - } - /// A server guest's block-breaking animation changed to `progress` at - /// `pos` (0 through 9). 186. - fn block_break_progress_update( - &self, - host: &mut dyn Host, - event: &mut BlockBreakProgressUpdateEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// On an authoritative server, fire is about to burn the block at `pos` - /// away. Set `cancel` to keep that block. 187. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn block_burn(&self, level: &dyn LevelHost, event: &mut BlockBurnEvent) -> Flow { - let _ = level; - let _ = event; - Flow::Continue - } - /// A block finished cooking `item` into `result` at `pos`. 188. - fn block_cook(&self, host: &dyn Host, event: &mut BlockCookEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server player stopped digging `pos` before the block broke, after - /// the server cleared the dig and sent the block back to the client. - /// The player's state is not swapped in. 329. - fn block_damage_abort(&self, host: &mut dyn Host, event: &mut BlockDamageAbortEvent<'_>) { - let _ = host; - let _ = event; - } - /// A dispenser equipped `item` on the first eligible `target` in front - /// of its block at `pos`. `target` is `player`, `mob` or `armor_stand`. - /// The equip, item consumption, sound and game event have completed. - /// Server-side. 189. - fn block_dispense_armor(&self, host: &mut dyn Host, event: &mut BlockDispenseArmorEvent<'_>) { - let _ = host; - let _ = event; - } - /// A broken block is about to drop `experience` points at `pos`. The - /// vanilla block drop rule has been checked; change the amount before its - /// orbs are awarded. Server-side. 190. - fn block_exp(&self, level: &dyn LevelHost, event: &mut BlockExpEvent<'_>) { - let _ = level; - let _ = event; - } - /// A block at `pos` is about to fade from `from` to `to` because of - /// melting or coral death. Set `cancel` to keep it. Server-side. - /// 191. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn block_fade(&self, level: &dyn LevelHost, event: &mut BlockFadeEvent) -> Flow { - let _ = level; - let _ = event; - Flow::Continue - } - /// A dispenser at `pos` failed to dispense `item`. Server-side. - /// 192. - fn block_failed_dispense(&self, host: &mut dyn Host, event: &mut BlockFailedDispenseEvent<'_>) { - let _ = host; - let _ = event; - } - /// Precipitation is about to form or add to a block at `pos`. `from` and - /// `to` are block state IDs. Set `cancel` to keep the old state. Server-side. - /// 193. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn block_form(&self, level: &dyn LevelHost, event: &mut BlockFormEvent) -> Flow { - let _ = level; - let _ = event; - Flow::Continue - } - /// On an authoritative server, fire is about to be placed at `pos` by - /// flint and steel, a fire charge, lava, lightning or fire spread. Set - /// `player` is the acting server player when there is one. Set `cancel` to - /// leave the position unchanged. 194. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn block_ignite(&self, level: &dyn LevelHost, event: &mut BlockIgniteEvent<'_>) -> Flow { - let _ = level; - let _ = event; - Flow::Continue - } - /// A guest's block inventory lock check at `pos`. `locked` is the - /// vanilla result and may be changed. The guest's state is not swapped in. - /// 195. - /// - /// Return `Flow::Cancel` to take it: this sets `locked`, and later mods are skipped. - fn block_lock_check(&self, host: &mut dyn Host, event: &mut BlockLockCheckEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A sculk sensor or shrieker received a vibration for `event` at `pos`. - /// Set `cancel` to suppress its response. Server-side. - /// 196. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn block_receive_game( - &self, - host: &mut dyn Host, - event_: &mut BlockReceiveGameEvent<'_>, - ) -> Flow { - let _ = host; - let _ = event_; - Flow::Continue - } - /// A dispenser sheared `entity` of kind `kind` at the block in front of - /// its own block at `pos`, after the shearing effects. Server-side. - /// 197. - fn block_shear_entity(&self, host: &mut dyn Host, event: &mut BlockShearEntityEvent<'_>) { - let _ = host; - let _ = event; - } - /// A brewing stand finished brewing at `pos`, after its bottle contents - /// were changed. Server-side. 198. - fn brewing_complete(&self, host: &dyn Host, event: &mut BrewingCompleteEvent) { - let _ = host; - let _ = event; - } - /// A brewing stand at `pos` consumed `fuel` and gained brewing fuel. Server-side. 199. - fn brewing_fuel(&self, host: &dyn Host, event: &mut BrewingFuelEvent<'_>) { - let _ = host; - let _ = event; - } - /// A brewing stand at `pos` began brewing `ingredient`, after fuel and the timer were set. Server-side. 200. - fn brewing_start(&self, host: &dyn Host, event: &mut BrewingStartEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server broadcast line is about to be logged and sent. Change `line` - /// or set `cancel` to drop it. 201. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn broadcast_message(&self, host: &dyn Host, event: &mut BroadcastMessageEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// An item was placed on a campfire at `pos` and began cooking. 202. - fn campfire_start(&self, host: &dyn Host, event: &mut CampfireStartEvent<'_>) { - let _ = host; - let _ = event; - } - /// On an authoritative server, a cauldron at `pos` is about to change - /// fill level from `from_level` to `to_level` for `reason`. Set `cancel` - /// to leave it unchanged. 203. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn cauldron_level_changed( - &self, - level: &dyn LevelHost, - event: &mut CauldronLevelChangedEvent<'_>, - ) -> Flow { - let _ = level; - let _ = event; - Flow::Continue - } - /// A server player who ate chorus fruit (or another item with the - /// `teleport_randomly` consume effect) is about to try a random spot - /// (NeoForge's `EntityTeleportEvent.ChorusFruit`, vanilla's - /// `TeleportRandomlyConsumeEffect.apply`): once per attempt (up to 16), - /// after they stopped riding. `from` is their feet, `target` the spot - /// drawn; a mod may move it (its y kept inside the level's height, a - /// value that is not a number ignored), and the game still looks down - /// from it for ground it may land on. Canceling ends the effect: no teleport and no - /// more attempts. The player's state is swapped in - /// (`native_acting_player`). Foxes are not heard. 311. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn chorus_fruit_teleport( - &self, - host: &mut dyn Host, - event: &mut ChorusFruitTeleportEvent<'_>, - ) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A compostable item was added to the composter at `pos`. 204. - fn compost_item(&self, host: &dyn Host, event: &mut CompostItemEvent<'_>) { - let _ = host; - let _ = event; - } - /// A crafter at `pos` completed recipe `recipe`. 205. - fn crafter_craft(&self, host: &dyn Host, event: &mut CrafterCraftEvent<'_>) { - let _ = host; - let _ = event; - } - /// A player ignited a creeper with an item. 206. - fn creeper_ignite(&self, host: &mut dyn Host, event: &mut CreeperIgniteEvent<'_>) { - let _ = host; - let _ = event; - } - /// Lightning charged a creeper. 207. - fn creeper_power(&self, host: &mut dyn Host, event: &mut CreeperPowerEvent) { - let _ = host; - let _ = event; - } - /// NeoForge's `CriticalHitEvent`: a player's melee hit on a living - /// `target_kind` (`minecraft:zombie`, `minecraft:armor_stand`) that deals - /// damage, where `Player.attack` decides whether it is critical. - /// `vanilla_critical` is vanilla's answer; `critical` starts as it and may - /// be forced or denied (as in vanilla a critical hit prevents the sweep, so - /// a forced crit loses it; a denied crit loses the crit sound and - /// particles); `damage_multiplier` starts at 1.5 for vanilla's critical and - /// 1.0 otherwise, and scales the damage only while `critical` ends true. Not fired for a - /// target that is no living entity (a dragon part) or when the hit does - /// no damage. Fires on the Game that runs the attack. 316. - fn critical_hit(&self, host: &mut dyn Host, event: &mut CriticalHitEvent<'_>) { - let _ = host; - let _ = event; - } - /// The server's difficulty changed, from `from` to `to` (`peaceful`, - /// `easy`, `normal`, `hard`; NeoForge's `DifficultyChangeEvent`). It is - /// vanilla's `MinecraftServer.setDifficulty`, which `/difficulty`, a - /// dedicated server's management and a test environment's difficulty all - /// go through, and it is told only when the difficulty is another one. It - /// only hears: the change has already happened. Server-side. API - /// 159. - fn difficulty_changed(&self, host: &mut dyn Host, event: &mut DifficultyChangedEvent<'_>) { - let _ = host; - let _ = event; - } - /// The Ender Dragon fight is about to place its first dragon egg at `pos`. - /// Set `cancel` to leave the block unchanged. Server-side. 208. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn dragon_egg_form(&self, level: &dyn LevelHost, event: &mut DragonEggFormEvent) -> Flow { - let _ = level; - let _ = event; - Flow::Continue - } - /// A server player applied an enchantment at `pos`; `item` is the finished item and `levels` the levels charged. The player's state is not swapped in. 209. - fn enchant_item(&self, host: &mut dyn Host, event: &mut EnchantItemEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server player's ender pearl landed and is about to teleport them - /// (NeoForge's `EntityTeleportEvent.EnderPearl`, vanilla's - /// `ThrownEnderpearl.onHit` for a player owner): `pearl` is the pearl's - /// entity id, `from` their feet now, `target` where the pearl started - /// its last tick and `damage` the fall damage they then take (5). A mod - /// may move `target` (a value that is not a number is ignored) or change - /// `damage` (0 or less: none). Canceling - /// keeps the player where they are: no endermite roll, no teleport, no - /// damage and no sound; the pearl is used up either way. The player's - /// state is swapped in (`native_acting_player`). The hit itself is - /// heard by `native_projectile_impact`. 312. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn ender_pearl_teleport( - &self, - host: &mut dyn Host, - event: &mut EnderPearlTeleportEvent<'_>, - ) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// Entities from `chunk` finished loading into the server world. `count` - /// is the number of saved entity records read. 210. - fn entities_load(&self, host: &mut dyn Host, event: &mut EntitiesLoadEvent<'_>) { - let _ = host; - let _ = event; - } - /// Entities in `chunk` left the active server world and were queued for - /// saving. `count` is the number of entity records. 211. - fn entities_unload(&self, host: &mut dyn Host, event: &mut EntitiesUnloadEvent<'_>) { - let _ = host; - let _ = event; - } - /// A mob broke the door at `pos`. 212. - fn entity_break_door(&self, host: &mut dyn Host, event: &mut EntityBreakDoorEvent<'_>) { - let _ = host; - let _ = event; - } - /// Two mobs completed a breeding action. `child` is absent when vanilla - /// lays an egg instead of creating a baby. 213. - fn entity_breed(&self, host: &mut dyn Host, event: &mut EntityBreedEvent) { - let _ = host; - let _ = event; - } - /// A mob changed a block at `pos`, after the new state was applied. - /// 214. - fn entity_change_block(&self, host: &mut dyn Host, event: &mut EntityChangeBlockEvent<'_>) { - let _ = host; - let _ = event; - } - /// A mob is about to drop `count` of `item` at `pos` (Bukkit's - /// `EntityDropItemEvent`). 330. - fn entity_drop_item(&self, host: &mut dyn Host, event: &mut EntityDropItemEvent<'_>) { - let _ = host; - let _ = event; - } - /// A bee entered the hive at `pos` and was stored there. - /// 215. - fn entity_enter_block(&self, host: &mut dyn Host, event: &mut EntityEnterBlockEvent<'_>) { - let _ = host; - let _ = event; - } - /// A mob entered love mode after player interaction. `player` is the - /// feeder when known. 216. - fn entity_enter_love_mode( - &self, - host: &mut dyn Host, - event: &mut EntityEnterLoveModeEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// A server entity finished its portal wait and is beginning to leave - /// `dimension` through `pos`. The destination follows the portal rules. - /// 217. - fn entity_portal(&self, host: &mut dyn Host, event: &mut EntityPortalEvent<'_>) { - let _ = host; - let _ = event; - } - /// A guest player's synchronized pose changed from `from` to `to`. - /// 218. - fn entity_pose_change(&self, host: &mut dyn Host, event: &mut EntityPoseChangeEvent<'_>) { - let _ = host; - let _ = event; - } - /// A player bow or crossbow launched an arrow with `force`, after the - /// projectile entered the level. 219. - fn entity_shoot_bow(&self, host: &mut dyn Host, event: &mut EntityShootBowEvent<'_>) { - let _ = host; - let _ = event; - } - /// A mob became tamed after its owner and persistent state were set. - /// 220. - fn entity_tame(&self, host: &mut dyn Host, event: &mut EntityTameEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server entity was moved by a same-level teleport command, after its - /// position changed. `entity` is its network id; `from` and `to` are - /// coordinates in `dimension`. This hook reports command teleports that - /// use Horizon's same-level target path; it does not change vanilla's - /// movement. 221. - fn entity_teleport(&self, host: &mut dyn Host, event: &mut EntityTeleportEvent<'_>) { - let _ = host; - let _ = event; - } - /// A guest's fall flying state changed on the server. The change has - /// already been applied. 222. - fn entity_toggle_glide(&self, host: &mut dyn Host, event: &mut EntityToggleGlideEvent<'_>) { - let _ = host; - let _ = event; - } - /// A mob conversion completed; both ids and entity types are reported. - /// 223. - fn entity_transform(&self, host: &mut dyn Host, event: &mut EntityTransformEvent<'_>) { - let _ = host; - let _ = event; - } - /// A leashed mob's leash was removed, before any lead drop is handled - /// (Bukkit's `EntityUnleashEvent`). 331. - fn entity_unleash(&self, host: &mut dyn Host, event: &mut EntityUnleashEvent<'_>) { - let _ = host; - let _ = event; - } - /// An experience bottle broke and its experience was awarded. - /// 224. - fn exp_bottle(&self, host: &mut dyn Host, event: &mut ExpBottleEvent) { - let _ = host; - let _ = event; - } - /// Whether a mod hears `native_experience_orb_pickup`: set `listened` - /// (a HEAD injection), or the server skips swapping the player in for it, - /// and with this empty the pickup costs nothing more without mods. - /// Server-side. 155. - /// - /// Every mod gets `event.listened` in order and may add, change or remove listened. - fn experience_orb_pickup_listened( - &self, - host: &dyn Host, - event: &mut ExperienceOrbPickupListenedEvent, - ) { - let _ = host; - let _ = event; - } - /// Before `player` picks up orb `orb` in `dimension`. Mutations to `value` - /// and `count` take effect only when not canceled; value is clamped to - /// 1..=i32::MAX and count to at least one. - /// Canceling leaves the orb and pickup delay unchanged. Server-side. - /// 155. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn experience_orb_pickup( - &self, - host: &mut dyn Host, - event: &mut ExperienceOrbPickupEvent<'_>, - ) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// The selected damaged Mending `stack` and the proposed durability repair - /// for `experience` points. The amount is clamped to the stack's current damage. Zero leaves the - /// experience unspent when it overrides a positive proposal. If the selected - /// stack changes, the repair is skipped. - /// Server-side, including `native_give_experience`. - /// 155. - fn mending_repair(&self, host: &mut dyn Host, event: &mut MendingRepairEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server explosion is about to begin at `center`, with `radius` (Bukkit's - /// `ExplosionPrimeEvent`). This is listen-only: the explosion proceeds as - /// usual. Server-side, including the single-player server. 332. - fn explosion_prime(&self, host: &mut dyn Host, event: &mut ExplosionPrimeEvent) { - let _ = host; - let _ = event; - } - /// On an authoritative server, how fire treats the block `state` - /// (`FireBlock.setFlammable(block, igniteOdds, burnOdds)`): put - /// `(ignite, burn)` in `odds` for your own blocks, and vanilla's table - /// isn't read. Ignite odds above zero let fire stand beside the block and - /// spread into the air around it (planks 5, wool 30, leaves 30); burn odds - /// are how fast fire destroys it (planks 20, wool 60, leaves 60). `(0, 0)` - /// never burns; keep both within vanilla's 0 to 100. A waterlogged state - /// never burns and isn't asked. Answer only for your own blocks and leave - /// `odds` empty otherwise; the level is read-only. Asked hundreds of times - /// per fire tick: keep it short. API 157. - /// - /// Return `Some` to set `odds`; the first mod returning `Some` wins. - fn fire_odds(&self, level: &dyn LevelHost, event: &mut FireOddsEvent) -> Option<(i32, i32)> { - let _ = level; - let _ = event; - None - } - /// On an authoritative server, `CampfireBlock.isSmokeSource` for the - /// block `state` under a campfire, as the campfire is placed or the block - /// under it changes: `Some(true)` for your own blocks makes the campfire a - /// signal fire whose smoke rises higher, as over a hay bale. The level is - /// read-only. API 157. - /// - /// Return `Some` to set `smoke`; the first mod returning `Some` wins. - fn smoke_source(&self, level: &dyn LevelHost, event: &mut SmokeSourceEvent) -> Option { - let _ = level; - let _ = event; - None - } - /// A firework rocket completed its explosion effects. 225. - fn firework_explode(&self, host: &mut dyn Host, event: &mut FireworkExplodeEvent) { - let _ = host; - let _ = event; - } - /// A flowing fluid at `pos` is about to change level. `fluid` is `water` - /// or `lava`; the levels are 0 through 8. Set `cancel` to keep the old - /// level. Server-side. 226. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn fluid_level_change( - &self, - level: &dyn LevelHost, - event: &mut FluidLevelChangeEvent<'_>, - ) -> Flow { - let _ = level; - let _ = event; - Flow::Continue - } - /// The frame rate this window is about to be held to: Max Framerate after the - /// inactivity throttle and Battery Mode (260 is unlimited). `focused` is whether the - /// window has focus, `minimized` whether it is minimised and `in_world` whether a world - /// is open. A mod may lower `limit`, as Dynamic FPS does for a window in the background: - /// Horizon keeps the lower of its own limit and the mod's, and ignores values below 1. - /// API 167. - fn framerate_limit(&self, host: &dyn Host, event: &mut FramerateLimitEvent) { - let _ = host; - let _ = event; - } - /// A furnace at `pos` consumed fuel and began or resumed burning it. - /// `fuel` is the item consumed from its fuel slot. Server-side. - /// 227. - fn furnace_burn(&self, host: &dyn Host, event: &mut FurnaceBurnEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server player took an output from a furnace at `pos`; `item` is the - /// output item id and `count` is how many were taken. The player's state is - /// not swapped in, so helpers that look them up through `self.lan` may find - /// nothing during guest packet handling. 228. - fn furnace_extract(&self, host: &mut dyn Host, event: &mut FurnaceExtractEvent<'_>) { - let _ = host; - let _ = event; - } - /// A furnace finished smelting `item` into `result` at `pos`. Server-side. - /// 229. - fn furnace_smelt(&self, host: &dyn Host, event: &mut FurnaceSmeltEvent<'_>) { - let _ = host; - let _ = event; - } - /// A furnace at `pos` began cooking `item` (its cooking timer advanced - /// from zero). Server-side. - /// 230. - fn furnace_start_smelt(&self, host: &dyn Host, event: &mut FurnaceStartSmeltEvent<'_>) { - let _ = host; - let _ = event; - } - /// A generic world game event was fired at `pos`. 231. - fn game_event(&self, host: &mut dyn Host, event_: &mut GameEventEvent<'_>) { - let _ = host; - let _ = event_; - } - /// A hopper selected a container while searching for items to push or - /// pull. Exactly one of `container` and `entity` identifies the target. - /// 333. - fn hopper_inventory_search(&self, host: &dyn Host, event: &mut HopperInventorySearchEvent) { - let _ = host; - let _ = event; - } - /// A hopper at `pos` is about to pick up `count` of `item`. Set `cancel` to leave the dropped stack in the world. Server-side. 232. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn hopper_pickup_item( - &self, - level: &dyn LevelHost, - event: &mut HopperPickupItemEvent<'_>, - ) -> Flow { - let _ = level; - let _ = event; - Flow::Continue - } - /// A recipe ingredient of a mod's own kind: the entry named a type in a - /// namespace outside `minecraft:`, and the loader kept it for this call. - /// The kind and the JSON the file wrote are here; fill `items` with the - /// item ids the ingredient matches (item ids only: components are not - /// checked here; a match-time hook follows separately). Duplicates are - /// removed keeping first-seen order. The hook may be called more than - /// once per recipe; the answer must be the same. An unclaimed kind - /// leaves the list empty and the recipe fails, as an unknown kind does. - /// API 166. - /// - /// Every mod gets `event.items` in order and may add, change or remove items. - fn ingredient(&self, event: &mut IngredientEvent<'_>) { - let _ = event; - } - /// A server player is about to open an inventory supplied by a block at - /// `pos`. Set `cancel` to leave the inventory closed. The player's state - /// is not swapped in. 233. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn inventory_block_start( - &self, - host: &mut dyn Host, - event: &mut InventoryBlockStartEvent<'_>, - ) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A hopper is about to move one item between containers, a composter's - /// bone meal included. Set `cancel` to leave it in its source slot. A chest - /// boat or minecart is reported at the block it is in. 334. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn inventory_move_item(&self, host: &dyn Host, event: &mut InventoryMoveItemEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A player took a crafting grid's result (`ResultSlot.onTake`, NeoForge's - /// `PlayerEvent.ItemCraftedEvent`): `player` is the player's uuid, `recipe` - /// the recipe the grid matched (`minecraft:oak_planks`) and `stack` the - /// result it took, with its components. It only hears: the take has - /// already happened. It fires for the crafting grid of an inventory or a - /// crafting table, on the integrated server and a dedicated server, guests - /// included, and not for a stonecutter's, a smithing table's or a - /// furnace's result. Server-side. API 161. - fn item_crafted(&self, host: &mut dyn Host, event: &mut ItemCraftedEvent<'_>) { - let _ = host; - let _ = event; - } - /// A dropped item entity reached its 6000-tick lifetime and is about to be - /// removed (Bukkit's `ItemDespawnEvent`). 335. - fn item_despawn(&self, host: &mut dyn Host, event: &mut ItemDespawnEvent<'_>) { - let _ = host; - let _ = event; - } - /// Item `item` (`mod_id:name`) holds energy (`Capabilities.Energy.ITEM`): - /// set `buffer` when the item is yours, as `SimpleEnergyHandler` is built: - /// its capacity and the most one insert and one extract move. A capacity - /// under 1 leaves the buffer out, and the limits are clamped to - /// 0..=capacity; vanilla's items have none. Asked at each call of the item - /// energy helpers (`native_item_energy`, `native_item_energy_insert`, - /// `native_item_energy_extract`, in `native_item_energy.rs`). The amount - /// is kept in the stack's `minecraft:custom_data`, as the int - /// `horizon_energy`. API 307. - /// - /// Return `Some` to set `buffer`; the first mod returning `Some` wins. - fn item_energy_buffer( - &self, - host: &dyn Host, - event: &mut ItemEnergyBufferEvent<'_>, - ) -> Option { - let _ = host; - let _ = event; - None - } - /// A dropped item reaches its lifetime on the server (`ItemEntity.tick`, - /// NeoForge's `ItemExpireEvent`). Every mod may set or add to `extra_life`, - /// in ticks, which starts at zero; a positive total keeps the item that much - /// longer, zero or less lets it expire. Extensions are capped at 32766 - /// ticks. 325. - fn item_expire(&self, host: &mut dyn Host, event: &mut ItemExpireEvent<'_>) { - let _ = host; - let _ = event; - } - /// A dropped item entity was successfully added to the server level. - /// 234. - fn item_spawn(&self, host: &mut dyn Host, event: &mut ItemSpawnEvent<'_>) { - let _ = host; - let _ = event; - } - /// A non-persistent leaf block at distance 7 is about to decay at `pos`. - /// Set `cancel` to keep it. Server-side. 235. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn leaves_decay(&self, level: &dyn LevelHost, event: &mut LeavesDecayEvent) -> Flow { - let _ = level; - let _ = event; - Flow::Continue - } - /// A server lightning bolt is about to be created. `cause` is `weather`, - /// `trident` or `command`; `player` is the responsible player when known. - /// Set `cancel` to prevent the bolt and its effects. 236. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn lightning_strike(&self, host: &mut dyn Host, event: &mut LightningStrikeEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A player shift-clicked slot `slot` of the menu of the mod block - /// entity at `pos` (`AbstractContainerMenu.quickMoveStack`, the method a - /// Java menu overrides), on the server, before the game's own routing: - /// move the stack where your block wants it. `slot` is the menu's index: - /// below `container_slots` it is the block's own slot (`entity.items`), - /// from there on the player's inventory (27 main slots, then the hotbar). - /// `stack` is a copy of what the slot holds; take what you move out of - /// it (put it in `entity.items`, or in another block with - /// `native_insert_item`) and set `handled`: the slot then holds what is - /// left of `stack` (whatever you wrote to that slot of `entity.items`), - /// the game's routing is skipped, and the player's client gets the menu - /// again. Left unhandled, `entity.items` goes back to what it held and - /// the game moves the stack as for any container; a later mod sees what - /// an earlier one did to `stack`. - /// For a mod container on a vanilla menu type and on a mod menu type, - /// the left or right button, a slot that holds something and that the - /// mod's screen shows; not for a spectator or a dead player, a menu no - /// longer valid, nor during a drag. - /// Asked once per click, where vanilla asks `quickMoveStack` again while - /// it keeps moving the same item. The player's state is swapped in, so - /// `native_acting_player` names them; `entity` is out of the world - /// during the call, as in `native_menu_button`. - /// API 309. - /// - /// Return `Flow::Cancel` to take it: this sets `handled`, and later mods are skipped. - fn menu_quick_move(&self, host: &mut dyn Host, event: &mut MenuQuickMoveEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// Farmland at `pos` is about to change moisture from `from` to `to`. - /// Set `cancel` to keep its old moisture. Server-side. - /// 237. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn moisture_change(&self, level: &dyn LevelHost, event: &mut MoistureChangeEvent) -> Flow { - let _ = level; - let _ = event; - Flow::Continue - } - /// A player took an anvil's result at `pos` and the levels, inputs and anvil wear are done (`AnvilCraftEvent.Post`); a broken anvil is already air. `craft` holds the inputs and the result as they were before. Not called when `native_anvil_craft_pre` cancelled. 327. - fn anvil_craft_post(&self, host: &mut dyn Host, event: &mut AnvilCraftPostEvent<'_>) { - let _ = host; - let _ = event; - } - /// A player took an anvil's result at `pos` (`AnvilCraftEvent.Pre`), before the levels are charged, the inputs used up and the anvil worn. `craft` holds the inputs and the result as they were before. Set `cancel` to refuse the take: the result stays in its slot and the inputs, levels, inventory and cursor are as before the click. 328. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn anvil_craft_pre(&self, host: &mut dyn Host, event: &mut AnvilCraftPreEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A player takes an item from an armor stand or puts one on it - /// (`ArmorStand.interactAt`, after the stand's disabled-slot checks and - /// `swapItem`'s take/put locks, before anything moves): `stand_id` is the - /// stand's `Entity.getId` and `slot` the one the click reached - /// (`mainhand`, `offhand`, `feet`, `legs`, `chest` or `head`). Set - /// `cancel` and nothing changes: neither the stand nor the player's hand, - /// and the click is spent. The player's state is swapped in for the call; - /// `player` is the UUID the helpers take. Not told of a click that changes - /// nothing (an empty hand on an empty slot). Not covered, other ways a - /// stand's equipment moves: breaking or blasting the stand (its items - /// drop), a dispenser putting armour on one, a mod's own changes. - /// Server-side, for every player of the world. - /// 175. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn armor_stand_manipulate( - &self, - host: &mut dyn Host, - event: &mut ArmorStandManipulateEvent<'_>, - ) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A server player empties a bucket at `pos` (`fluid`: `minecraft:water`, - /// `minecraft:lava`, `minecraft:powder_snow`; a fish or axolotl bucket - /// empties `minecraft:water`): where `BucketItem.use` has found the block - /// the fluid goes into and the player may build there, right before it is - /// placed. Set `cancel` and nothing is poured and the bucket stays full. - /// The player's state is swapped in, so `native_tell` answers them. Only - /// for guests of a server. `native_fill_bucket` hears this click too (and - /// the filling one) without the player and the fluid. Not heard: a - /// dispenser, a cauldron, and a fluid a block places itself. - /// 179. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn bucket_empty(&self, host: &mut dyn Host, event: &mut BucketEmptyEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A player opens one of the game's own menus (`ServerPlayer.openMenu`; the - /// job of NeoForge's `PlayerContainerEvent.Open`, which comes after the - /// menu is open and cannot stop it): `kind` is the menu type - /// (`minecraft:generic_9x3`, `minecraft:furnace`, `minecraft:crafting`, - /// `minecraft:merchant`, `minecraft:anvil`, `minecraft:lectern`...; a - /// horse's inventory is `minecraft:horse_inventory`) and `pos` the block - /// it belongs to, none for a villager, a mount or a vehicle's chest. - /// Heard after the checks that refuse the menu (a locked container, a - /// spectator) and before the menu that was open closes. Set `cancel` and - /// nothing opens: the open menu stays as it was and the click is spent. - /// The player's state is swapped in for the call, as for - /// `native_perform_command`, so `native_acting_player`, `native_tell` and - /// the item helpers reach them. Not heard: a menu a mod opens - /// (`native_open_menu`), the inventory screen, and the editors (sign, - /// book, command block). Server-side, for every player of the world. - /// 176. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn container_opened(&self, host: &mut dyn Host, event: &mut ContainerOpenedEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A creative-mode player sets a slot of their inventory from the - /// creative screen (`ServerGamePacketListenerImpl.handleSetCreativeModeSlot`): - /// `slot` is the inventory menu's slot (5 to 8 armour, 9 to 35 the - /// inventory, 36 to 44 the hotbar, 45 the off hand; below 0 is a stack - /// thrown out of the screen, which is dropped) and `item` (`minecraft:air` - /// for an emptied slot) and `count` what is put there. Heard once the - /// packet passed vanilla's checks (a creative player, a real slot, a valid - /// stack) and before the slot changes or the stack drops. Set `cancel` - /// and the packet is ignored. The player's state is swapped in for the - /// call, as for `native_perform_command`, so `native_acting_player`, - /// `native_tell` and the item helpers reach them. Not heard: stacks a - /// creative player gets other ways (middle-click pick block, `/give`). - /// Server-side, for every player of the world. - /// 177. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn creative_slot(&self, host: &mut dyn Host, event: &mut CreativeSlotEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// The food level of the server player `player` (`name`) is about to change - /// from `from` to `to` (Bukkit's `FoodLevelChangeEvent`): - /// eating, hunger and saturation effects, the exhaustion that costs a - /// point, starving. Change `to` (kept between 0 and 20; saturation never - /// exceeds it) or set `cancel` to leave the level at `from`, saturation - /// included. Heard once for the change a player's turn made as a whole, so - /// two changes in one tick arrive as one. Not heard: the full food bar a - /// respawn gives, the food a player joins with and the host of a LAN - /// world. The player's state is swapped in, so `native_acting_player`, - /// `native_tell` and the item helpers reach them. On a server. - /// 169. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn food_level_changed( - &self, - host: &mut dyn Host, - event: &mut FoodLevelChangedEvent<'_>, - ) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// An item frame (`minecraft:item_frame`, `minecraft:glow_item_frame`) or - /// a painting (`minecraft:painting`), `kind` its entity type and `pos` the - /// block it hangs in, is about to break: a server player's hit - /// (`player`, their state swapped in, so `native_tell` answers them), a - /// blast, or the wall it hangs on gone (`player` None for both). Where - /// `BlockAttachedEntity.hurtServer` and its survival check would kill - /// it, before anything drops or sounds. Set `cancel` and it stays, as it - /// was. A hit that only takes the item out of a frame does not break the - /// frame and is not asked. The game's own player's hit is not asked, nor - /// is `/kill`. 319. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn hanging_break(&self, host: &mut dyn Host, event: &mut HangingBreakEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A server player places a hanging entity: an item frame - /// (`minecraft:item_frame`, `minecraft:glow_item_frame`), a painting - /// (`minecraft:painting`) or a leash knot (`minecraft:leash_knot`, when a - /// lead is tied to a fence that has none yet), `kind` its entity type and - /// `pos` the block it takes (the fence's for a knot). Where - /// `HangingEntityItem.useOn` and `LeadItem.useOn` create it, once it is - /// known to fit and before anything is placed or used up. Set `cancel` - /// and nothing is placed: the item stays in the player's hand. The - /// player's state is swapped in, so `native_tell` answers them. Only for - /// guests of a server; `/summon`, a dispenser and the game's own player - /// are not heard, nor is a lead tied to a mob or a boat. - /// 180. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn hanging_place(&self, host: &mut dyn Host, event: &mut HangingPlaceEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// The item `item` of a server player is about to lose `amount` points of - /// durability (`ItemStack.hurtAndBreak`; Bukkit's `PlayerItemDamageEvent`): - /// the tool that mines or hits, the armour that takes a hit, a shield, a - /// rod, an elytra. `amount` is what is left after Unbreaking; change it - /// (0 or less leaves the item whole) or set `cancel` for no wear at all. It - /// is not heard for a creative player's tools (they take no wear) nor when - /// Unbreaking already saved the item, nor for the wear of a mob's armour or - /// of a tool a dispenser uses, nor the host of a LAN world. The player's - /// state is swapped in, so `native_acting_player`, `native_tell` and the - /// item helpers reach them. Once for each wear, on a server. - /// 162. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn item_damaged(&self, host: &mut dyn Host, event: &mut ItemDamagedEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// `player` (`name`) changed dimension (NeoForge's - /// `PlayerChangedDimensionEvent`): `from` and `to` are dimension ids - /// (`minecraft:overworld`, `minecraft:the_nether`, `minecraft:the_end`). - /// Heard once per move, when it has ended: the portal's exit is found - /// and the player stands there (after the destination has loaded), a - /// command's teleport has landed, or a respawn in another level than the - /// death's is placed for good (a bed or anchor, or the world spawn's - /// free column, after the fallback when a respawn block was gone). The - /// level of `to` is the one in place, and the player's state is swapped - /// in, as for `native_perform_command`: `native_acting_player`, - /// `native_tell`, the item helpers and the rest reach them. Heard for: - /// Nether and End portals (also riding a vehicle, and leaving the End as - /// its winner), `/execute in ... run tp` and any other command that - /// teleports a player into another level, and the death respawn. Not - /// heard: a move within a level (`/tp`, an End gateway), a player who - /// leaves before the move ends, a move that ends where it started (a - /// respawn in the dimension of the death), and anything outside a - /// dedicated server. 154. - fn player_changed_dimension( - &self, - host: &mut dyn Host, - event: &mut PlayerChangedDimensionEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// A player's data was copied to their new player (NeoForge's - /// `PlayerEvent.Clone`, from `ServerPlayer.restoreFrom`): `was_death` is - /// true after a death, false when they left the End after the credits - /// (`respawn` with `keepAllPlayerData`). The uuid stays the same, so the - /// old and the new player share `player`. Heard before the new player is - /// placed, on a server, with no player's state swapped in. Not - /// cancellable. - fn player_clone(&self, host: &mut dyn Host, event: &mut PlayerCloneEvent<'_>) { - let _ = host; - let _ = event; - } - /// The command tree a server sends a player (NeoForge's and Paper's - /// "commands sent to a player" stage): when they join and when their - /// level changes (`/op`, `/deop`, the LAN settings). `names` are the - /// root commands their client would get, vanilla's they may use and the - /// mods'; take a name out and that client gets the tree without it - /// (and without what it leads to), so it neither completes nor - /// highlights it. This hides the command from the client only: the - /// server still runs a typed one, `native_player_command` stops it. - /// For a player who is already on, their state is swapped in and the - /// helpers (`native_tell`...) work; for one who is joining it is not, and - /// their client has no world to tell yet. The tree the hub sends again by - /// itself (after a respawn or a change of level) keeps the last answer - /// and does not ask again; the integrated server's owner is not asked - /// when the world's cheats are switched. 171. - /// - /// Every mod gets `event.names` in order and may add, change or remove names. - fn player_commands_sent(&self, host: &mut dyn Host, event: &mut PlayerCommandsSentEvent<'_>) { - let _ = host; - let _ = event; - } - /// The item `item` of the server player `player` (`name`) broke in `slot` - /// (`mainhand`, `offhand`, `head`, `chest`, `legs`, `feet`, `body` or - /// `saddle`): `LivingEntity.onEquippedItemBroken`, Bukkit's - /// `PlayerItemBreakEvent`. Listen only; armour has a slot, not a hand. - /// The player's state is swapped in. Once for each break, on a server; a - /// creative player and the host of a LAN world are not heard. - /// 315. - fn player_destroy_item(&self, host: &mut dyn Host, event: &mut PlayerDestroyItemEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server player's fishing rod, with `state`: 0 the cast - /// (`FishingRodItem.use` throwing the bobber), 1 a fish biting - /// (`FishingHook.catchingFish`), 2 the reel-in of a bite, which gives the - /// catch, 3 the reel-in with nothing on the line, 4 the reel-in of a - /// bobber hooked to an entity or item (like Bukkit's `PlayerFishEvent`; - /// NeoForge's `ItemFishedEvent` is later, in `FishingHook.retrieve`). Set `cancel` on the cast and nothing - /// is thrown; on a bite the fish gets away and the wait starts over; on - /// a reel-in (2 to 4) nothing happens, the bobber stays out and the rod - /// takes no wear. The bobber ending by itself (too far, the rod put - /// away, the owner dying), nor the host of a LAN world's rod. The - /// player's state is swapped in, so `native_acting_player`, `native_tell` - /// and the item helpers reach them. Once for each cast, bite and reel-in, - /// on a server. 160. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn player_fish(&self, host: &mut dyn Host, event: &mut PlayerFishEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A server player put a plant into a flower pot, after the potted block - /// state was stored and the item consumed. `plant` is the item id and - /// `pos` the pot position. Helpers looking up the guest through `self.lan` - /// may find nothing during packet handling. Server-side. 238. - fn player_flower_pot_manipulate( - &self, - host: &mut dyn Host, - event: &mut PlayerFlowerPotManipulateEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// A server player's game mode is about to change (NeoForge's - /// `PlayerChangeGameModeEvent`): from `from` to `to` (0 survival, 1 - /// creative, 2 adventure, 3 spectator, as `NativeServerPlayer`'s - /// `game_mode`), `player` being the UUID and `name` the name. Heard for - /// the `/gamemode` command, the F3+F4 switcher's packet and a hardcore - /// world's respawn into spectator, once for each change to a different - /// mode; not for the world's default mode a player joins with. Set - /// `cancel` and the player keeps their mode (nothing is sent, the - /// command counts it as no change). For a change the game makes inside - /// a player's turn their state is swapped in; for the hardcore respawn - /// it is not. 158. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn player_game_mode_changed( - &self, - host: &mut dyn Host, - event: &mut PlayerGameModeChangedEvent<'_>, - ) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A server player inserted a book into a lectern, after the block entity - /// and inventory changed. `book` is the item id. Helpers looking up a - /// guest through `self.lan` may find nothing during packet handling. - /// Server-side. 239. - fn player_insert_lectern_book( - &self, - host: &mut dyn Host, - event: &mut PlayerInsertLecternBookEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// A server player changed an item frame after its contents or rotation - /// changed. `action` is `put`, `rotate`, `remove_item` or `break`; `item` - /// is empty when no item remains, and `rotation` is the resulting step. - /// Helpers looking up the guest through `self.lan` may find nothing during - /// packet handling. Server-side. 240. - fn player_item_frame_change( - &self, - host: &mut dyn Host, - event: &mut PlayerItemFrameChangeEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// Mending repaired a server player's item with experience from an orb. - /// `repair` is durability restored and `experience` is the orb's value. - /// Helpers looking up the guest through `self.lan` may find nothing during - /// packet handling. Server-side. 241. - fn player_item_mend(&self, host: &mut dyn Host, event: &mut PlayerItemMendEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server player changed the page of a lectern, after its page and - /// redstone pulse changed. `page` is the zero-based new page. Helpers - /// looking up a guest through `self.lan` may find nothing during packet - /// handling. Server-side. 242. - fn player_lectern_page_change( - &self, - host: &mut dyn Host, - event: &mut PlayerLecternPageChangeEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// A player was loaded from the world's save (NeoForge's - /// `PlayerEvent.LoadFromFile`, `PlayerList.loadPlayerData`): their `.dat` - /// has been read and applied, and they are not placed in a level yet. - /// `directory` is the world's player directory (`players/data`): a mod - /// reads its own file there, named `.` as - /// `getPlayerFile(suffix)` names it. Heard once for each join, for the - /// owner of an integrated server as for anyone else, on a server, with no - /// player's state swapped in. Not cancellable. - fn player_load_from_file(&self, host: &mut dyn Host, event: &mut PlayerLoadFromFileEvent<'_>) { - let _ = host; - let _ = event; - } - /// A player whose login the server has accepted (NeoForge's - /// `PlayerNegotiationEvent` stage): the name, whitelist, ban and - /// server-full checks have passed and the player holds a place, but their - /// saved state is not loaded and they are not placed in a level yet. Set - /// `cancel` to refuse them: they are disconnected with `reason` (a plain - /// "Disconnected" if it is left unset) and nothing else of the join (the - /// join message, `native_player_logged_in`) happens. Once for each player - /// who joins, on a server. The player is not placed yet and their client - /// has no world to show a message in, so no player's state is swapped in - /// and `native_tell` reaches nobody: the refusal `reason` is the answer. - /// The checks that need no mod (name, whitelist, bans, a full server, - /// the proxy's forwarding) refuse earlier, on the connection, and this - /// hook does not hear those players. 172. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn player_login(&self, host: &mut dyn Host, event: &mut PlayerLoginEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A player was saved to the world's store (NeoForge's - /// `PlayerEvent.SaveToFile`, `PlayerList.save`): when they leave or the - /// world closes, when the dedicated server saves everyone, and for an - /// integrated server's owner when the world saves them. LAN guests are - /// not heard on integrated autosaves. Once for each save. `directory` - /// is the world's player directory (`players/data`): write your own file - /// there, named `.`, never the player's `.dat`. The player - /// may be leaving, so no player's state is swapped in. Not cancellable. - fn player_save_to_file(&self, host: &mut dyn Host, event: &mut PlayerSaveToFileEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server player's sprint started or stopped: their client sent - /// `START_SPRINTING` or `STOP_SPRINTING` (the sprint key, a double tap - /// forward, or letting go) and the server's player is now `sprinting` - /// (vanilla's `setSprinting` in `handlePlayerCommand`). Heard only when it - /// changes. It cannot be stopped, and it hears what the client asks, not - /// what the server does: a sprint that ends by itself (a hit's knockback, - /// hunger, a wall) is not reported. The player's state is swapped in, so - /// the helpers and `native_tell` work. Once for each change, on a server. - /// 173. - fn player_sprint(&self, host: &mut dyn Host, event: &mut PlayerSprintEvent<'_>) { - let _ = host; - let _ = event; - } - /// `player` (`name`) pressed the swap-hands key: heard before the main and - /// off hand items swap (the `SWAP_ITEM_WITH_OFFHAND` player action that - /// `ServerGamePacketListenerImpl.handlePlayerAction` answers; Bukkit's - /// `PlayerSwapHandItemsEvent`), not for a spectator. Set `cancel` to keep - /// the items where they are: the player's client, which swapped them at - /// once, gets its slots back. The player's state is swapped in, so - /// `native_acting_player`, `native_tell` and the item helpers reach them. - /// Once for each press, on a server. Not heard: the same swap made in an - /// open inventory (hovering a slot and pressing the key, a menu click) and - /// the host of a LAN world. 168. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn player_swap_hands(&self, host: &mut dyn Host, event: &mut PlayerSwapHandsEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A server player took the book from a lectern, after the menu and block - /// entity changed. `book` is the removed item id. Helpers looking up a - /// guest through `self.lan` may find nothing during packet handling. - /// Server-side. 243. - fn player_take_lectern_book( - &self, - host: &mut dyn Host, - event: &mut PlayerTakeLecternBookEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// A server player a command teleported (NeoForge's - /// `EntityTeleportEvent`, for a player): `/tp` and `/teleport`, inside - /// their level or into another one. `from` and `to` are their feet before - /// and after, `dimension` the level they are in now. Heard after the move - /// has reached the player's state and their client (an inter-level - /// teleport: when the new level is entered, before it has loaded around - /// them), so it cannot stop it. The player's state is swapped in, so the - /// helpers that read the acting player and `native_tell` work. Once for - /// each teleported player, on a server. Ender pearls and chorus fruit - /// have their own hooks; a spectator's teleport menu is not heard yet; - /// portals and respawns are not teleports here. 170. - fn player_teleported(&self, host: &mut dyn Host, event: &mut PlayerTeleportedEvent<'_>) { - let _ = host; - let _ = event; - } - /// A totem of undying saved the server player `player` (`name`): their - /// hurt would have killed them and the totem held in either hand fired - /// (`LivingEntity.checkTotemDeathProtection`; Bukkit's - /// `EntityResurrectEvent`), heard just before it is used up and its - /// effects apply. Listen only: whether the totem saves is decided inside - /// the health state machine, before a mod is asked, so a mod cannot veto - /// it from here. The player's state is swapped in, so - /// `native_acting_player`, `native_tell` and the item helpers reach them. - /// Once for each totem, on a server; the host of a LAN world's totem is - /// not heard. 182. - fn player_totem(&self, host: &mut dyn Host, event: &mut PlayerTotemEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server player's client answered a resource pack in play (vanilla's - /// `ServerboundResourcePackPacket`): `status` is `Action`'s number, 0 - /// successfully loaded, 1 declined, 2 failed download, 3 accepted, 4 - /// downloaded, 5 invalid URL, 6 failed reload, 7 discarded. Heard when the - /// packet arrives, after the server has handled it; it cannot be stopped. - /// The player's state is swapped in, so the helpers and `native_tell` - /// work. The pack a dedicated server offers in the configuration - /// (`resource-pack` in `server.properties`) is answered before the - /// player has joined, and those answers are not heard. Once for each - /// answer, on a server. 174. - fn resource_pack_status(&self, host: &mut dyn Host, event: &mut ResourcePackStatusEvent<'_>) { - let _ = host; - let _ = event; - } - /// The world's spawn point changed to `pos` and `angle`. 244. - fn spawn_change(&self, host: &mut dyn Host, event: &mut SpawnChangeEvent) { - let _ = host; - let _ = event; - } - /// TNT at `pos` is about to be lit (primed): a flint and steel or a fire - /// charge on it, fire burning it, a redstone signal, an arrow, or a - /// dispenser's flint and steel or TNT item. `player` is the server player - /// whose click lit it, with their state swapped in (so `native_tell` - /// answers them), None when no player is behind it (fire, redstone, an - /// arrow, a dispenser). Set `cancel` and it is not primed: a TNT block - /// stays where it is (one that fire or a signal had already taken out is - /// put back, without a block update), a dispenser's TNT item stays in it. - /// Not asked: TNT set off by a blast (`native_explosion_detonate` spares - /// blocks), a TNT minecart and a sulfur cube. Server-side and in single - /// player. 181. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn tnt_primed(&self, host: &mut dyn Host, event: &mut TntPrimedEvent) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A boat or minecart was created at `pos`, after it joined the level. - /// `kind` is the vehicle type. Server-side. 336. - fn vehicle_create(&self, host: &mut dyn Host, event: &mut VehicleCreateEvent<'_>) { - let _ = host; - let _ = event; - } - /// A vehicle is about to take damage from a player's attack. `kind` is - /// `boat` or the minecart type; mods may change `damage` or set `cancel` - /// to leave its damage and wobble unchanged. The attack itself still - /// completes. Server-side. 337. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn vehicle_damage(&self, host: &mut dyn Host, event: &mut VehicleDamageEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A boat or minecart is about to be destroyed at `pos`. TNT minecarts - /// that prime instead of breaking are not heard. Server-side. - /// 338. - fn vehicle_destroy(&self, host: &mut dyn Host, event: &mut VehicleDestroyEvent<'_>) { - let _ = host; - let _ = event; - } - /// A boat or minecart (`kind`, `minecraft:oak_boat`) moved since the last - /// tick (`VehicleMoveEvent`): `from` and `to` are its positions, heard - /// after the tick moved it. Once a tick for each vehicle that carries a - /// server player, whose state is then swapped in (as for - /// `native_perform_command`: `native_acting_player`, `native_tell` and - /// the item helpers reach them; `player` is their UUID), and for one a - /// server player rode and left, until it has stood still for ten - /// seconds (`player` is none, nobody is swapped in). It only hears. - /// Cheap when nothing moves: one position compare a tick for each watched - /// vehicle. Not heard: a vehicle moved before anyone rode it, a mount - /// (horse, pig...) or a vehicle carrying only a mob. Server-side, in the - /// level the vehicle is in. 178. - fn vehicle_moved(&self, host: &mut dyn Host, event: &mut VehicleMovedEvent<'_>) { - let _ = host; - let _ = event; - } - /// The server world's border bounds changed, either immediately or when - /// its timed size change was requested. 245. - fn world_border_bounds_changed( - &self, - host: &mut dyn Host, - event: &mut WorldBorderBoundsChangedEvent, - ) { - let _ = host; - let _ = event; - } - /// The server world's border center changed. 246. - fn world_border_center_changed( - &self, - host: &mut dyn Host, - event: &mut WorldBorderCenterChangedEvent, - ) { - let _ = host; - let _ = event; - } - /// A server game rule changed to its final `value`. 247. - fn world_game_rule_change( - &self, - host: &mut dyn Host, - event: &mut WorldGameRuleChangeEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// On an authoritative server, a note block at `pos` is about to sound. - /// `player` is the acting server player when there is one. Set `cancel` to silence it. - /// 248. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn note_play(&self, level: &dyn LevelHost, event: &mut NotePlayEvent<'_>) -> Flow { - let _ = level; - let _ = event; - Flow::Continue - } - /// A server player earned one criterion of an advancement, after the - /// progress was recorded. 339. - fn player_advancement_criterion_grant( - &self, - host: &mut dyn Host, - event: &mut PlayerAdvancementCriterionGrantEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// A server player completed an advancement, after progress was recorded. - /// Helpers looking up the guest through `self.lan` may find nothing during - /// its packet. 249. - fn player_advancement_done( - &self, - host: &mut dyn Host, - event: &mut PlayerAdvancementDoneEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// A criterion of a server player's advancement was granted or revoked, - /// after its progress changed and before any completion rewards, except - /// the `minecraft:tick` criteria granted on a network player's first tick - /// or a reload, which are heard after that tick's rewards and packets. A - /// grant or revoke that changes nothing is not heard. `granted` is false for a - /// revoke. Helpers looking up the guest through `self.lan` may find - /// nothing during its packet. 320. - fn player_advancement_progress( - &self, - host: &mut dyn Host, - event: &mut PlayerAdvancementProgressEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// A guest's arm swing was handled by the server. `hand` is `mainhand`. Server-side. 340. - fn player_arm_swing(&self, host: &mut dyn Host, event: &mut PlayerArmSwingEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server player reset their attack strength by swinging (`handlePunch`). - /// The swing and reset have already happened. The player's state is not - /// swapped in, and helpers looking them up through `self.lan` may find - /// nothing during a guest packet. Server-side. 250. - fn player_attack_cooldown_reset( - &self, - host: &mut dyn Host, - event: &mut PlayerAttackCooldownResetEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// A server player is about to enter a bed after the sleep checks pass. - /// Set `cancel` to keep them out. The player's state is swapped in, but - /// helpers that look them up through `self.lan` may find nothing during a - /// guest packet. 251. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn player_bed_enter(&self, host: &mut dyn Host, event: &mut PlayerBedEnterEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// while a guest packet is handled. 252. - fn player_bed_leave(&self, host: &mut dyn Host, event: &mut PlayerBedLeaveEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server player caught a mob in a bucket, after it was removed and the - /// filled bucket was given. Helpers looking up the guest through - /// `self.lan` may find nothing during its packet. Server-side. - /// 253. - fn player_bucket_entity(&self, host: &mut dyn Host, event: &mut PlayerBucketEntityEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server player selected a bundle item in `slot` at `index`. The guest's - /// state is not swapped in during packet handling. 341. - fn player_bundle_item_select( - &self, - host: &mut dyn Host, - event: &mut PlayerBundleItemSelectEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// A server player changed a beacon's selected effects, after they were - /// stored. Empty effect ids mean no effect. Helpers looking up the guest - /// through `self.lan` may find nothing during its packet. Server-side. - /// 254. - fn player_change_beacon_effect( - &self, - host: &mut dyn Host, - event: &mut PlayerChangeBeaconEffectEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// A server player changed their client option for the dominant hand. `left` is true for - /// left-handed. Their state is not swapped in, and helpers looking them up through `self.lan` - /// may find nothing during guest packet handling. Server-side. - /// 255. - fn player_change_main_hand( - &self, - host: &mut dyn Host, - event: &mut PlayerChangeMainHandEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// A server player finished loading the world around them after joining, - /// respawning or changing dimension. The guest's state is not swapped in, - /// so helpers that look them up through `self.lan` may find nothing while - /// a guest packet is handled. Server-side. 342. - fn player_client_loaded_world( - &self, - host: &mut dyn Host, - event: &mut PlayerClientLoadedWorldEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// A server player has slept for 100 ticks in a bed and now counts as - /// deeply asleep for the night-skip check. Helpers looking the guest up - /// through `self.lan` may find nothing during its tick. 256. - fn player_deep_sleep(&self, host: &mut dyn Host, event: &mut PlayerDeepSleepEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server player's book edit or signing packet is about to be applied. - /// `pages` is its page count; `signing` means the packet has a title. Set - /// `cancel` to drop the edit. Helpers looking up the guest through - /// `self.lan` may find nothing during the packet. 257. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn player_edit_book(&self, host: &mut dyn Host, event: &mut PlayerEditBookEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A server player threw an egg, after its projectile was launched and - /// the use was applied. Helpers looking up the guest through `self.lan` - /// may find nothing during its packet. Server-side. 258. - fn player_egg_throw(&self, host: &mut dyn Host, event: &mut PlayerEggThrowEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server player used a firework rocket to boost while gliding, after - /// the rocket was launched and consumed. Helpers looking up the guest - /// through `self.lan` may find nothing during its packet. Server-side. - /// 259. - fn player_elytra_boost(&self, host: &mut dyn Host, event: &mut PlayerElytraBoostEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server player's experience points are about to increase from an orb pickup, - /// after mending has taken its share. Change `amount` to change the points - /// added. The player's state is not swapped in, so helpers that look them up - /// through `self.lan` may find nothing while a guest packet is handled. - /// Server-side. 260. - fn player_exp_change(&self, host: &mut dyn Host, event: &mut PlayerExpChangeEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server player's XP orb pickup cooldown changed to `ticks`. It is - /// reported after the value changes; the player's state may not be found - /// through `self.lan` while a guest packet is handled. Server-side. - /// 261. - fn player_exp_cooldown_change( - &self, - host: &mut dyn Host, - event: &mut PlayerExpCooldownChangeEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// A guest sent the server's current `PlayerInput` flags, after they were applied to the hub. The guest's state is swapped in. Server-side. 343. - fn player_input(&self, host: &mut dyn Host, event: &mut PlayerInputEvent<'_>) { - let _ = host; - let _ = event; - } - /// An item cooldown is about to start for a server player. `item` is the - /// item id and `ticks` its duration. The player's state is not swapped in, - /// so helpers that look them up through `self.lan` may find nothing while a - /// guest packet is handled. Set `cancel` to skip starting the cooldown. - /// Server-side. 262. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn player_item_cooldown( - &self, - host: &mut dyn Host, - event: &mut PlayerItemCooldownEvent<'_>, - ) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A server player selected a new hotbar slot. The player's state is not - /// swapped in, and helpers that look them up through `self.lan` may find - /// nothing while a guest packet is being handled. Set `cancel` to keep - /// the old slot; the client is sent that slot again. 263. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn player_item_held(&self, host: &mut dyn Host, event: &mut PlayerItemHeldEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A guest's accepted movement made the server jump them from the ground. Server-side. 344. - fn player_jump(&self, host: &mut dyn Host, event: &mut PlayerJumpEvent<'_>) { - let _ = host; - let _ = event; - } - /// A guest was disconnected by a server kick, after the hub recorded the rendered `reason`. The guest's state is - /// swapped in unless they were already removed in the same poll. 345. - fn player_kicked(&self, host: &mut dyn Host, event: &mut PlayerKickedEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server player launched a projectile item, after it was created. - /// `item` is the projectile's held item. 346. - fn player_launch_projectile( - &self, - host: &mut dyn Host, - event: &mut PlayerLaunchProjectileEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// A server player's experience level changed, after the new level was applied. 347. - fn player_level_change(&self, host: &mut dyn Host, event: &mut PlayerLevelChangeEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server player changed their client language option to `locale`. - /// The player's state may not be found through `self.lan` during a guest - /// packet. Server-side. 264. - fn player_locale_change(&self, host: &mut dyn Host, event: &mut PlayerLocaleChangeEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server player selected a loom pattern, after the loom menu accepted - /// the button. `pattern` is the zero-based button index. Helpers looking - /// up the guest through `self.lan` may find nothing during packet handling. - /// Server-side. 265. - fn player_loom_pattern_select( - &self, - host: &mut dyn Host, - event: &mut PlayerLoomPatternSelectEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// A server player filled an empty map, after the filled map and its saved - /// data were created. Guest lookup helpers may find nothing during packet - /// handling. Server-side. 266. - fn player_map_filled(&self, host: &mut dyn Host, event: &mut PlayerMapFilledEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server player named a mob with a name tag, after its name and - /// persistence changed and the tag was consumed. Helpers looking up the - /// guest through `self.lan` may find nothing during packet handling. - /// Server-side. 267. - fn player_name_entity(&self, host: &mut dyn Host, event: &mut PlayerNameEntityEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server player opened a sign for editing, after the editor state was - /// made. `front` selects the front or back side. Guest lookup helpers may - /// find nothing during packet handling. Server-side. - /// 268. - fn player_open_sign(&self, host: &mut dyn Host, event: &mut PlayerOpenSignEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server player picked an item from a block or entity, before it enters - /// the hotbar. `pos` is zero for an entity; `entity_id` is zero for a - /// block. The player's state is swapped in, though helpers that look them - /// up through `self.lan` may find nothing while a guest packet is handled. - /// Set `cancel` to leave the inventory and client unchanged. 269. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn player_pick_item(&self, host: &mut dyn Host, event: &mut PlayerPickItemEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A server player picked up an arrow after it was removed and its pickup - /// was applied. `item` is the arrow's pickup item id. Guest lookup helpers - /// may find nothing during packet handling. Server-side. - /// 270. - fn player_pickup_arrow(&self, host: &mut dyn Host, event: &mut PlayerPickupArrowEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server player is about to travel through a Nether portal, End portal - /// or End gateway. `cause` is `nether_portal`, `end_portal` or `end_gateway`. - /// The player's state is not swapped in, so helpers that look them up - /// through `self.lan` may find nothing while a guest packet is handled. - /// Set `cancel` to keep them in their current place. Server-side. - /// 271. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn player_portal(&self, host: &mut dyn Host, event: &mut PlayerPortalEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A server player released a drawn bow with `arrow` selected. The guest's - /// state is not swapped in during packet handling. 348. - fn player_ready_arrow(&self, host: &mut dyn Host, event: &mut PlayerReadyArrowEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server player's recipe book requested a known recipe be placed in - /// their crafting grid. `recipe` is the recipe id; `all` is the shift - /// click form. The player's state may not be available through `self.lan` - /// while a guest packet is handled. Server-side. 272. - fn player_recipe_book_click( - &self, - host: &mut dyn Host, - event: &mut PlayerRecipeBookClickEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// A server player's recipe book open or filter setting changed. `book` - /// is the book type's protocol id. The guest's state is not swapped in. - /// 349. - fn player_recipe_book_settings_change( - &self, - host: &mut dyn Host, - event: &mut PlayerRecipeBookSettingsChangeEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// A recipe is about to be unlocked for a server player. Set `cancel` to - /// keep it locked. The player's state is not swapped in, so helpers that - /// look them up through `self.lan` may find nothing while a guest packet - /// is handled. Server-side. 273. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn player_recipe_discovered( - &self, - host: &mut dyn Host, - event: &mut PlayerRecipeDiscoveredEvent<'_>, - ) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A server player launched with a riptide trident, after the velocity, - /// spin attack and grounded lift have been applied. The player's state is - /// swapped in; during a guest packet, helpers that look them up through - /// `self.lan` may find nothing. No reply is needed. 274. - fn player_riptide(&self, host: &mut dyn Host, event: &mut PlayerRiptideEvent<'_>) { - let _ = host; - let _ = event; - } - /// A player shearing a mob is about to do so. Set `cancel` to leave the - /// mob and shears unchanged. The guest's state is swapped in, though - /// helpers looking them up through `self.lan` may find nothing during a - /// guest packet. 275. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn player_shear_entity( - &self, - host: &mut dyn Host, - event: &mut PlayerShearEntityEvent<'_>, - ) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A server player's shield was disabled by a blocked melee hit. The - /// shield cooldown is set immediately after this call. Helpers looking up - /// the guest through `self.lan` may find nothing during packet handling. - /// Server-side. 276. - fn player_shield_disable(&self, host: &mut dyn Host, event: &mut PlayerShieldDisableEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server player's respawn point is about to change. `cause` is `bed`, - /// `respawn_anchor`, `command` or `death_reset`; `pos` is its block - /// position. The player's state is not swapped in, so helpers that look - /// them up through `self.lan` may find nothing while a guest packet is - /// handled. Set `cancel` to keep the old point. Server-side. - /// 277. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn player_spawn_point_set( - &self, - host: &mut dyn Host, - event: &mut PlayerSpawnPointSetEvent<'_>, - ) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A statistic is about to be awarded to a server player, before its count - /// and scoreboard criteria change. `stat_type` and `stat` are ids such as - /// `minecraft:mined` and `minecraft:stone`, and `value` the amount. Set - /// `cancel` to award nothing. Helpers looking up the guest through - /// `self.lan` may find nothing during its packet. Server-side. - /// 321. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn player_stat_award(&self, host: &mut dyn Host, event: &mut PlayerStatAwardEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A server player selected a stonecutter recipe, after the menu accepted - /// its button. `recipe` is the zero-based button index. Guest lookup - /// helpers may find nothing during packet handling. Server-side. - /// 278. - fn player_stonecutter_recipe_select( - &self, - host: &mut dyn Host, - event: &mut PlayerStonecutterRecipeSelectEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// A server player stopped spectating `entity_id`, after their camera - /// returned to the player. Guest lookup helpers may find nothing. - /// 279. - fn player_stop_spectating_entity( - &self, - host: &mut dyn Host, - event: &mut PlayerStopSpectatingEntityEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// A server player released the use-item key before the item finished - /// being used. The guest's state is not swapped in, so helpers that look - /// them up through `self.lan` may find nothing during packet handling. - /// 350. - fn player_stop_using_item( - &self, - host: &mut dyn Host, - event: &mut PlayerStopUsingItemEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// A server player swapped their main and off hands, after both inventory - /// slots changed. Guest lookup helpers may find nothing during packet - /// handling. Server-side. 280. - fn player_swap_with_equipment_slot( - &self, - host: &mut dyn Host, - event: &mut PlayerSwapWithEquipmentSlotEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// A server player is about to travel through an End gateway at `gateway`. - /// Set `cancel` to leave them at the entrance; the gateway is not consumed - /// and its cooldown does not start. Helpers may find no guest through - /// `self.lan` during its packet. Server-side. 281. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn player_teleport_end_gateway( - &self, - host: &mut dyn Host, - event: &mut PlayerTeleportEndGatewayEvent<'_>, - ) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A server player toggled an ageable mob's age lock with a golden - /// dandelion, after the lock and item use changed. 282. - fn player_toggle_entity_age_lock( - &self, - host: &mut dyn Host, - event: &mut PlayerToggleEntityAgeLockEvent<'_>, - ) { - let _ = host; - let _ = event; - } - /// A guest's client changed sneaking in its player input packet, after the hub applied the new state. - /// The guest's state is swapped in. Server-side. 351. - fn player_toggle_sneak(&self, host: &mut dyn Host, event: &mut PlayerToggleSneakEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server player completed a villager or wandering trader trade. - /// `entity_id` is the merchant network id. Helpers looking up the guest - /// through `self.lan` may find nothing during packet handling. Server-side. - /// 283. - fn player_trade(&self, host: &mut dyn Host, event: &mut PlayerTradeEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server player removed an entity's leash, after it was cleared. - /// Guest lookup helpers may find nothing during packet handling. - /// 284. - fn player_unleash_entity(&self, host: &mut dyn Host, event: &mut PlayerUnleashEntityEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server applied `velocity` to a player, when syncing changed motion to - /// that player's client. Guest lookup helpers may find nothing. - /// 285. - fn player_velocity(&self, host: &mut dyn Host, event: &mut PlayerVelocityEvent<'_>) { - let _ = host; - let _ = event; - } - /// A Nether portal was built for a dimension travel, or an End platform - /// was made for an arrival. `pos` is the lower corner of the Nether - /// portal rectangle or the End platform origin. The completed blocks are - /// already in the world. 286. - fn portal_create(&self, host: &mut dyn Host, event: &mut PortalCreateEvent<'_>) { - let _ = host; - let _ = event; - } - /// A thrown splash or lingering potion applied its hit effects. - /// 287. - fn potion_splash(&self, host: &mut dyn Host, event: &mut PotionSplashEvent<'_>) { - let _ = host; - let _ = event; - } - /// An anvil result was prepared for a server player at `pos`. Change `result` or its level `cost` before it is sent. 288. - /// - /// Return `Some` to set `result`; the first mod returning `Some` wins. - fn prepare_anvil( - &self, - host: &mut dyn Host, - event: &mut PrepareAnvilEvent<'_>, - ) -> Option { - let _ = host; - let _ = event; - None - } - /// A grindstone result was prepared for a server player at `pos`. Change it before it is sent. 289. - /// - /// Return `Some` to set `result`; the first mod returning `Some` wins. - fn prepare_grindstone( - &self, - host: &mut dyn Host, - event: &mut PrepareGrindstoneEvent<'_>, - ) -> Option { - let _ = host; - let _ = event; - None - } - /// A hopper at `pos` began an item transfer attempt. This runs after its - /// cooldown and enabled checks, before pushing or pulling any items. - /// Server-side, including the single-player server. 352. - fn prepare_hopper_transfer(&self, host: &dyn Host, event: &mut PrepareHopperTransferEvent) { - let _ = host; - let _ = event; - } - /// A crafting-grid result was prepared for a server player; `pos` is the crafting table, or None for the inventory grid. Change `result` before it is sent. 290. - /// - /// Return `Some` to set `result`; the first mod returning `Some` wins. - fn prepare_item_craft( - &self, - host: &mut dyn Host, - event: &mut PrepareItemCraftEvent<'_>, - ) -> Option { - let _ = host; - let _ = event; - None - } - /// Enchanting offers were calculated for a server player at `pos`. Change `costs` before they are sent to the menu. 291. - fn prepare_item_enchant(&self, host: &mut dyn Host, event: &mut PrepareItemEnchantEvent<'_>) { - let _ = host; - let _ = event; - } - /// A smithing result was prepared for a server player at `pos`. Change it before it is sent. 292. - /// - /// Return `Some` to set `result`; the first mod returning `Some` wins. - fn prepare_smithing( - &self, - host: &mut dyn Host, - event: &mut PrepareSmithingEvent<'_>, - ) -> Option { - let _ = host; - let _ = event; - None - } - /// A server projectile was added to the level. `player` is set only for - /// a player launch; other sources have an empty `name`. 293. - fn projectile_launch(&self, host: &mut dyn Host, event: &mut ProjectileLaunchEvent<'_>) { - let _ = host; - let _ = event; - } - /// A raid wave has been spawned at `pos`; `wave` is its one-based wave - /// number and `raiders` counts the raiders, not the mobs they ride. 294. - fn raid_spawn_wave(&self, host: &mut dyn Host, event: &mut RaidSpawnWaveEvent) { - let _ = host; - let _ = event; - } - /// A raid at `pos` changed to `state` (`triggered`, `victory`, `loss` or - /// `stopped`). 295. - fn raid_state_change(&self, host: &mut dyn Host, event: &mut RaidStateChangeEvent<'_>) { - let _ = host; - let _ = event; - } - /// A sculk catalyst at `pos` has begun blooming after taking experience. - /// The catalyst block state has changed. Server-side. - /// 296. - fn sculk_bloom(&self, host: &mut dyn Host, event: &mut SculkBloomEvent<'_>) { - let _ = host; - let _ = event; - } - /// Enough players have slept through the night in level `dimension` - /// and the clock is about to move to the morning (NeoForge's - /// `SleepFinishedTimeEvent`, vanilla's `ServerLevel.tick` when it moves - /// the clock to `wake_up_from_sleep`): `new_time` comes in as the next - /// morning's day time and may be changed to wake everyone at another - /// time. A time before `current` (the day time now) is refused, as - /// `setTimeAddition` refuses one, and the morning stays. Unlike - /// NeoForge, which refuses a bad value as it is set, the check runs once - /// after every mod, so a later mod sees the value an earlier one left. - /// On the server, only when the `advance_time` rule is on; - /// `native_time_skip` then hears the jump to the time chosen here and - /// may still cancel it. The players wake and the weather resets as - /// usual either way. - /// API 308. - fn sleep_finished_time(&self, host: &mut dyn Host, event: &mut SleepFinishedTimeEvent<'_>) { - let _ = host; - let _ = event; - } - /// A sponge at `pos` absorbed water and became wet. Server-side. - /// 297. - fn sponge_absorb(&self, level: &dyn LevelHost, event: &mut SpongeAbsorbEvent) { - let _ = level; - let _ = event; - } - /// A guest asked to complete `command`; `suggestions` may be added to or - /// filtered. `range` is the replaced byte range. 298. - /// - /// Every mod gets `event.suggestions` in order and may add, change or remove suggestions. - fn tab_complete(&self, host: &mut dyn Host, event: &mut TabCompleteEvent<'_>) { - let _ = host; - let _ = event; - } - /// A projectile hit the target block at `pos`, which now has `strength` - /// output power. Server-side. 299. - fn target_hit(&self, host: &mut dyn Host, event: &mut TargetHitEvent<'_>) { - let _ = host; - let _ = event; - } - /// The server changed its thunder state through `/weather`. Set `cancel` - /// to keep the previous thunder state and timer. Server-side. - /// 300. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn thunder_change(&self, host: &mut dyn Host, event: &mut ThunderChangeEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// The overworld clock is about to jump, from sleep or `/time`. Set - /// `cancel` to keep `from`. 301. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn time_skip(&self, host: &mut dyn Host, event: &mut TimeSkipEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// A server player selected trade `index` from `villager` in the trading menu. 302. - fn trade_select(&self, host: &mut dyn Host, event: &mut TradeSelectEvent<'_>) { - let _ = host; - let _ = event; - } - /// A server player traded with a villager or a wandering trader - /// (NeoForge's `TradeWithVillagerEvent`, at vanilla's - /// `MerchantResultSlot.onTake` once the offer is taken): `merchant` is - /// the trader's entity id and `kind` its type (`minecraft:villager`, - /// `minecraft:wandering_trader`), `offer` the trade as it is after this - /// one (its `uses` counted, `result` what the player got). Once per - /// trade, so a shift-click that trades several times is heard for each, - /// after the trade's statistic and `villager_trade` trigger. Listen - /// only: the trade is made. The player's state is swapped in - /// (`native_acting_player`). API 310. - fn trade_with_villager(&self, host: &mut dyn Host, event: &mut TradeWithVillagerEvent<'_>) { - let _ = host; - let _ = event; - } - /// A vault at `pos` changed to `state`. Its block state is updated. - /// Server-side. 303. - fn vault_change_state(&self, host: &mut dyn Host, event: &mut VaultChangeStateEvent<'_>) { - let _ = host; - let _ = event; - } - /// A vault at `pos` is about to eject a loot item. 353. - fn vault_loot(&self, host: &dyn Host, event: &mut VaultLootEvent<'_>) { - let _ = host; - let _ = event; - } - /// The server changed its weather parameters. `raining` and `thundering` - /// are the requested final states. Set `cancel` to keep the previous - /// weather parameters. Server-side. 304. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn weather_change(&self, host: &mut dyn Host, event: &mut WeatherChangeEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// The dedicated server's whitelist changed to `enabled`. 305. - fn whitelist_state_update(&self, host: &mut dyn Host, event: &mut WhitelistStateUpdateEvent) { - let _ = host; - let _ = event; - } - /// A timed world-border size change finished on the server, after its - /// final size was applied. Server-side, including the single-player - /// server. 354. - fn world_border_bounds_change_finish( - &self, - host: &mut dyn Host, - event: &mut WorldBorderBoundsChangeFinishEvent, - ) { - let _ = host; - let _ = event; - } - /// The server completed a world save cycle. `wait` says chunk writes were - /// flushed before returning. 306. - fn world_saved(&self, host: &mut dyn Host, event: &mut WorldSavedEvent) { - let _ = host; - let _ = event; - } - /// An entity that can't breathe and has no air (`LivingDrownEvent`, posted - /// from `LivingEntity.baseTick`), with vanilla's answers as the defaults: it - /// drowns at -20 air, for 2 damage and 8 bubbles. Bubble count is read-only; - /// changes to the event field do not affect the vanilla entity event. Asked on - /// servers, for the entities `native_living_breathe` is. API 356. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn living_drown(&self, event: &mut LivingDrownEvent<'_>) -> Flow { - let _ = event; - Flow::Continue - } - /// A living entity is about to handle a fall (`LivingFallEvent`). Mods may - /// change the distance and damage multiplier, or cancel fall handling. - /// 357. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn living_fall(&self, event: &mut LivingFallEvent<'_>) -> Flow { - let _ = event; - Flow::Continue - } - /// A living entity is about to heal. Mods may change the amount or cancel. - /// `entity` identifies the affected entity. 358. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn living_heal(&self, event: &mut LivingHealEvent) -> Flow { - let _ = event; - Flow::Continue - } - /// A living entity jumped (`LivingEvent.LivingJumpEvent`). 359. - fn living_jump(&self, event: &mut LivingJumpEvent<'_>) { - let _ = event; - } - /// A player is about to nock `bow` with `hand` (`BowItem.use`, NeoForge's - /// `ArrowNockEvent`): `has_ammo` is whether `getProjectile` found ammunition - /// or the player is in creative. Set `cancel` to fail the use. - /// @API:arrow_nock@. - /// - /// Return `Flow::Cancel` to take it: this sets `cancel`, and later mods are skipped. - fn arrow_nock(&self, host: &mut dyn Host, event: &mut ArrowNockEvent<'_>) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// Player FOV scale, after vanilla effects and before zoom and easing. - /// Invalid values or values outside 0.01..=10 revert to base. API @API:compute_fov_modifier@. - fn compute_fov_modifier(&self, host: &dyn Host, event: &mut ComputeFovModifierEvent<'_>) { - let _ = host; - let _ = event; - } - /// An explosion at `center` in `dimension`, about to break `blocks`, pushes - /// `entity` (None for a player, whom `native_acting_player` names): - /// `ServerExplosion.hurtEntities`, after the knockback is calculated. Change `knockback_velocity` to change - /// what is added to the entity's velocity; a player's client is sent the - /// same. Wind bursts too, except dragons and end crystals. End crystals - /// are also omitted from ordinary blasts: Horizon removes them without a - /// velocity to change. Server-side. @API:explosion_knockback@. - /// - /// Every mod gets `event.knockback_velocity` in order and may add, change or remove knockback_velocity. - fn explosion_knockback(&self, host: &mut dyn Host, event: &mut ExplosionKnockbackEvent<'_>) { - let _ = host; - let _ = event; - } - /// A mod's own ingredient kind, matching a concrete stack at craft time: - /// the ingredient's `test` calls here with the kind, its JSON and the - /// stack being matched, `accept` already true (the load-time list named - /// the item). Set `accept` to false to refuse the stack — its components - /// are the usual reason. The hook may be called many times a craft; the - /// answer must be the same for the same stack. API @API:ingredient_test@. - fn ingredient_test(&self, event: &mut IngredientTestEvent<'_>) { - let _ = event; - } - /// Item `item` (`mod_id:name`) holds items (`Capabilities.Item.ITEM`): set - /// `slots` when the item is yours, for a backpack, a storage cell or a - /// toolbox. Clamped to 1..=256, `ItemContainerContents`' limit; 0 leaves - /// the item without slots, and vanilla's items have none. Asked at each - /// call of the item container helpers (`native_item_container_slots`, - /// `native_item_container_insert`, `native_item_container_extract`, in - /// `native_item_container.rs`). The contents are kept in the stack's - /// `minecraft:container`, as a shulker box item keeps them. - /// API @API:item_container@. - /// - /// Return `Some` to set `slots`; the first mod returning `Some` wins. - fn item_container(&self, host: &dyn Host, event: &mut ItemContainerEvent<'_>) -> Option { - let _ = host; - let _ = event; - None - } - /// `LivingEntity.getProjectile(weapon)` (NeoForge's - /// `LivingGetProjectileEvent`) for a player (`player` and `name`) or a - /// mob (`entity`, `kind`): a skeleton or illusioner's shot, or the crossbow - /// load of a pillager or piglin. `projectile` is the vanilla - /// ammunition; replace it to choose another. A replaced stack is not taken - /// from the inventory. @API:living_get_projectile@. - /// - /// Every mod gets `event.projectile` in order and may add, change or remove projectile. - fn living_get_projectile(&self, host: &mut dyn Host, event: &mut LivingGetProjectileEvent<'_>) { - let _ = host; - let _ = event; - } - /// Local player movement input, after reading it and before applying it. - fn movement_input(&self, host: &dyn Host, event: &mut MovementInputEvent) { - let _ = host; - let _ = event; - } - /// Declare categories for custom recipe types, with registered item icons. - /// Asked lazily once per recipe table by `native_recipe_book_category`. - /// Invalid declarations are logged and skipped; the first valid category - /// owns its id and type. This does not yet add tabs or book displays. - fn recipe_book_categories(&self, host: &dyn Host, event: &mut RecipeBookCategoriesEvent<'_>) { - let _ = host; - let _ = event; - } - /// Additional first-person hand and item pose, including empty arms and maps. - /// Add offsets in blocks and pitch, yaw and roll in degrees; multiply scale. - /// API @API:render_hand_pose@. - fn render_hand_pose(&self, host: &dyn Host, event: &mut RenderHandPoseEvent<'_>) { - let _ = host; - let _ = event; - } - /// Before a visible item frame's item or map is drawn. Cancel hides the item, not the frame. - /// - /// Return `Flow::Cancel` to take it: this sets `canceled`, and later mods are skipped. - fn render_item_in_frame( - &self, - host: &dyn Host, - event: &mut RenderItemInFrameEvent<'_>, - ) -> Flow { - let _ = host; - let _ = event; - Flow::Continue - } - /// Name tag visibility and optional replacement text. API @API:render_name_tag@. - fn render_name_tag(&self, host: &dyn Host, event: &mut RenderNameTagEvent<'_>) { - let _ = host; - let _ = event; - } - /// A screen was observed closing, at most once per frame. - fn screen_closing(&self, host: &dyn Host, event: &mut ScreenClosingEvent<'_>) { - let _ = host; - let _ = event; - } - /// A screen was observed opening or changing rectangle, at most once per frame. - fn screen_init_post(&self, host: &dyn Host, event: &mut ScreenInitPostEvent<'_>) { - let _ = host; - let _ = event; - } - /// A screen was observed opening, at most once per frame. - fn screen_opening(&self, host: &dyn Host, event: &mut ScreenOpeningEvent<'_>) { - let _ = host; - let _ = event; - } - /// Change a tooltip box's background and border gradient colors, as ARGB. - /// Stack is None for tooltips not associated with an item. - fn tooltip_color(&self, host: &dyn Host, event: &mut TooltipColorEvent<'_>) { - let _ = host; - let _ = event; - } -} -/// What [`Hooks::frame`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct FrameEvent { - pub elapsed: Duration, -} -pub fn dispatch_frame(host: &mut dyn Host, mods: &[Box], elapsed: Duration) { - let mut event = FrameEvent { elapsed }; - for mod_hook in super::entered(mods, "frame") { - mod_hook.frame(host, &mut event); - } -} -/// What [`Hooks::tick`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct TickEvent {} -pub fn dispatch_tick(host: &mut dyn Host, mods: &[Box]) { - let mut event = TickEvent {}; - for mod_hook in super::entered(mods, "tick") { - mod_hook.tick(host, &mut event); - } -} -/// What [`Hooks::visuals`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct VisualsEvent<'a> { - pub rims: &'a mut Vec, - pub lights: &'a mut Vec, -} -pub fn dispatch_visuals( - host: &dyn Host, - mods: &[Box], - rims: &mut Vec, - lights: &mut Vec, -) { - let mut event = VisualsEvent { rims, lights }; - for mod_hook in super::entered(mods, "visuals") { - mod_hook.visuals(host, &mut event); - } -} -/// What [`Hooks::emissive`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct EmissiveEvent<'a> { - pub layers: &'a mut Vec<(&'static str, &'static str)>, -} -pub fn dispatch_emissive( - host: &dyn Host, - mods: &[Box], - layers: &mut Vec<(&'static str, &'static str)>, -) { - let mut event = EmissiveEvent { layers }; - for mod_hook in super::entered(mods, "emissive") { - mod_hook.emissive(host, &mut event); - } -} -/// What [`Hooks::world_lifecycle`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct WorldLifecycleEvent<'a> { - pub context: &'a NativeLifecycleContext, - pub phase: WorldPhase, - pub error_text: &'a str, -} -pub fn dispatch_world_lifecycle( - host: &mut dyn Host, - mods: &[Box], - context: &NativeLifecycleContext, - phase: WorldPhase, - error_text: &str, -) { - let mut event = WorldLifecycleEvent { - context, - phase, - error_text, - }; - for mod_hook in super::entered(mods, "world_lifecycle") { - mod_hook.world_lifecycle(host, &mut event); - } -} -/// What [`Hooks::server_about_to_start`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ServerAboutToStartEvent { - pub dedicated: bool, -} -pub fn dispatch_server_about_to_start( - host: &mut dyn Host, - mods: &[Box], - dedicated: bool, -) { - let mut event = ServerAboutToStartEvent { dedicated }; - for mod_hook in super::entered(mods, "server_about_to_start") { - mod_hook.server_about_to_start(host, &mut event); - } -} -/// What [`Hooks::server_stopping`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ServerStoppingEvent { - pub dedicated: bool, -} -pub fn dispatch_server_stopping(host: &mut dyn Host, mods: &[Box], dedicated: bool) { - let mut event = ServerStoppingEvent { dedicated }; - for mod_hook in super::entered(mods, "server_stopping") { - mod_hook.server_stopping(host, &mut event); - } -} -/// What [`Hooks::server_tick`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ServerTickEvent<'a> { - pub context: &'a NativeLifecycleContext, - pub phase: ServerTickPhase, -} -pub fn dispatch_server_tick( - host: &mut dyn Host, - mods: &[Box], - context: &NativeLifecycleContext, - phase: ServerTickPhase, -) { - let mut event = ServerTickEvent { context, phase }; - for mod_hook in super::entered(mods, "server_tick") { - mod_hook.server_tick(host, &mut event); - } -} -/// What [`Hooks::level_tick_pre`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct LevelTickPreEvent<'a> { - pub dimension: &'a str, -} -pub fn dispatch_level_tick_pre(host: &mut dyn Host, mods: &[Box], dimension: &str) { - let mut event = LevelTickPreEvent { dimension }; - for mod_hook in super::entered(mods, "level_tick_pre") { - mod_hook.level_tick_pre(host, &mut event); - } -} -/// What [`Hooks::level_tick_post`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct LevelTickPostEvent<'a> { - pub dimension: &'a str, -} -pub fn dispatch_level_tick_post(host: &mut dyn Host, mods: &[Box], dimension: &str) { - let mut event = LevelTickPostEvent { dimension }; - for mod_hook in super::entered(mods, "level_tick_post") { - mod_hook.level_tick_post(host, &mut event); - } -} -/// What [`Hooks::block_changed`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlockChangedEvent { - pub dimension: LevelDimension, - pub pos: BlockPos, - pub old: BlockStateId, - pub new: BlockStateId, -} -pub fn dispatch_block_changed( - level: &dyn LevelHost, - mods: &[Box], - dimension: LevelDimension, - pos: BlockPos, - old: BlockStateId, - new: BlockStateId, -) { - let mut event = BlockChangedEvent { - dimension, - pos, - old, - new, - }; - for mod_hook in super::entered(mods, "block_changed") { - mod_hook.block_changed(level, &mut event); - } -} -/// What [`Hooks::neighbor_changed`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct NeighborChangedEvent { - pub dimension: LevelDimension, - pub pos: BlockPos, - pub state: BlockStateId, - pub changed: BlockId, - pub moved: bool, -} -pub fn dispatch_neighbor_changed( - level: &dyn LevelHost, - mods: &[Box], - dimension: LevelDimension, - pos: BlockPos, - state: BlockStateId, - changed: BlockId, - moved: bool, -) { - let mut event = NeighborChangedEvent { - dimension, - pos, - state, - changed, - moved, - }; - for mod_hook in super::entered(mods, "neighbor_changed") { - mod_hook.neighbor_changed(level, &mut event); - } -} -/// What [`Hooks::random_tick`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct RandomTickEvent { - pub dimension: LevelDimension, - pub pos: BlockPos, - pub state: BlockStateId, -} -pub fn dispatch_random_tick( - level: &mut dyn LevelWriteHost, - mods: &[Box], - dimension: LevelDimension, - pos: BlockPos, - state: BlockStateId, - handled: &mut bool, -) { - if *handled { - return; - } - let mut event = RandomTickEvent { - dimension, - pos, - state, - }; - for mod_hook in super::entered(mods, "random_tick") { - if matches!(mod_hook.random_tick(level, &mut event), Flow::Cancel) { - *handled = true; - break; - } - } -} -/// What [`Hooks::tick_block`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct TickBlockEvent { - pub dimension: LevelDimension, - pub pos: BlockPos, - pub state: BlockStateId, -} -pub fn dispatch_tick_block( - level: &mut dyn LevelWriteHost, - mods: &[Box], - dimension: LevelDimension, - pos: BlockPos, - state: BlockStateId, - handled: &mut bool, -) { - if *handled { - return; - } - let mut event = TickBlockEvent { - dimension, - pos, - state, - }; - for mod_hook in super::entered(mods, "tick_block") { - if matches!(mod_hook.tick_block(level, &mut event), Flow::Cancel) { - *handled = true; - break; - } - } -} -/// What [`Hooks::update_shape`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct UpdateShapeEvent { - pub dimension: LevelDimension, - pub pos: BlockPos, - pub state: BlockStateId, - pub direction: Direction, - pub neighbour_pos: BlockPos, - pub neighbour: BlockStateId, -} -pub fn dispatch_update_shape( - level: &mut dyn LevelTicksHost, - mods: &[Box], - dimension: LevelDimension, - pos: BlockPos, - state: BlockStateId, - direction: Direction, - neighbour_pos: BlockPos, - neighbour: BlockStateId, - new: &mut Option, -) { - if new.is_some() { - return; - } - let mut event = UpdateShapeEvent { - dimension, - pos, - state, - direction, - neighbour_pos, - neighbour, - }; - for mod_hook in super::entered(mods, "update_shape") { - if let Some(replacement) = mod_hook.update_shape(level, &mut event) { - *new = Some(replacement); - break; - } - } -} -/// What [`Hooks::hud`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct HudEvent<'a> { - pub batch: &'a mut HudBatch, - pub corners: &'a mut Corners, -} -pub fn dispatch_hud( - host: &mut dyn Host, - mods: &[Box], - batch: &mut HudBatch, - corners: &mut Corners, -) { - let mut event = HudEvent { batch, corners }; - for mod_hook in super::entered(mods, "hud") { - mod_hook.hud(host, &mut event); - } -} -/// What [`Hooks::key`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct KeyEvent<'a> { - pub key: &'a str, - pub pressed: bool, -} -pub fn dispatch_key(host: &mut dyn Host, mods: &[Box], key: &str, pressed: bool) { - let mut event = KeyEvent { key, pressed }; - for mod_hook in super::entered(mods, "key") { - mod_hook.key(host, &mut event); - } -} -/// What [`Hooks::wheel`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct WheelEvent { - pub steps: f32, -} -pub fn dispatch_wheel( - host: &mut dyn Host, - mods: &[Box], - steps: f32, - claimed: &mut bool, -) { - if *claimed { - return; - } - let mut event = WheelEvent { steps }; - for mod_hook in super::entered(mods, "wheel") { - if matches!(mod_hook.wheel(host, &mut event), Flow::Cancel) { - *claimed = true; - break; - } - } -} -/// What [`Hooks::command`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct CommandEvent<'a> { - pub command: &'a str, -} -pub fn dispatch_command( - host: &mut dyn Host, - mods: &[Box], - command: &str, - ran: &mut bool, -) { - if *ran { - return; - } - let mut event = CommandEvent { command }; - for mod_hook in super::entered(mods, "command") { - if matches!(mod_hook.command(host, &mut event), Flow::Cancel) { - *ran = true; - break; - } - } -} -/// What [`Hooks::commands`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct CommandsEvent<'a> { - pub commands: &'a mut Vec<&'static str>, -} -pub fn dispatch_commands( - host: &mut dyn Host, - mods: &[Box], - commands: &mut Vec<&'static str>, -) { - let mut event = CommandsEvent { commands }; - for mod_hook in super::entered(mods, "commands") { - mod_hook.commands(host, &mut event); - } -} -/// What [`Hooks::zoom`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ZoomEvent {} -pub fn dispatch_zoom(host: &dyn Host, mods: &[Box], zoom: &mut Option) { - if zoom.is_some() { - return; - } - let mut event = ZoomEvent {}; - for mod_hook in super::entered(mods, "zoom") { - if let Some(replacement) = mod_hook.zoom(host, &mut event) { - *zoom = Some(replacement); - break; - } - } -} -/// What [`Hooks::minimap`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct MinimapEvent {} -pub fn dispatch_minimap( - host: &dyn Host, - mods: &[Box], - minimap: &mut Option, -) { - if minimap.is_some() { - return; - } - let mut event = MinimapEvent {}; - for mod_hook in super::entered(mods, "minimap") { - if let Some(replacement) = mod_hook.minimap(host, &mut event) { - *minimap = Some(replacement); - break; - } - } -} -/// What [`Hooks::mods`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ModsEvent<'a> { - pub mods: &'a mut Vec, -} -pub fn dispatch_mods(host: &mut dyn Host, mods_: &[Box], mods: &mut Vec) { - let mut event = ModsEvent { mods }; - for mod_hook in super::entered(mods_, "mods") { - mod_hook.mods(host, &mut event); - } -} -/// What [`Hooks::settings`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct SettingsEvent<'a> { - pub mod_id: &'a str, - pub buttons: &'a mut Vec, -} -pub fn dispatch_settings( - host: &mut dyn Host, - mods: &[Box], - mod_id: &str, - buttons: &mut Vec, -) { - let mut event = SettingsEvent { mod_id, buttons }; - for mod_hook in super::entered(mods, "settings") { - mod_hook.settings(host, &mut event); - } -} -/// What [`Hooks::settings_click`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct SettingsClickEvent<'a> { - pub mod_id: &'a str, - pub button: usize, -} -pub fn dispatch_settings_click( - host: &mut dyn Host, - mods: &[Box], - mod_id: &str, - button: usize, -) { - let mut event = SettingsClickEvent { mod_id, button }; - for mod_hook in super::entered(mods, "settings_click") { - mod_hook.settings_click(host, &mut event); - } -} -/// What [`Hooks::panel_event`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PanelEventEvent<'a> { - pub mod_id: &'a str, - pub event: &'a NativePanelEvent, -} -pub fn dispatch_panel_event( - host: &mut dyn Host, - mods: &[Box], - mod_id: &str, - event: &NativePanelEvent, -) { - let mut event_ = PanelEventEvent { mod_id, event }; - for mod_hook in super::entered(mods, "panel_event") { - mod_hook.panel_event(host, &mut event_); - } -} -/// What [`Hooks::block_entity`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlockEntityEvent<'a> { - pub block: &'a str, -} -pub fn dispatch_block_entity( - host: &mut dyn Host, - mods: &[Box], - block: &str, - entity: &mut Option<(String, Compound)>, -) { - if entity.is_some() { - return; - } - let mut event = BlockEntityEvent { block }; - for mod_hook in super::entered(mods, "block_entity") { - if let Some(replacement) = mod_hook.block_entity(host, &mut event) { - *entity = Some(replacement); - break; - } - } -} -/// What [`Hooks::block_entity_ticks`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlockEntityTicksEvent<'a> { - pub block: &'a str, - pub ticks: bool, -} -pub fn dispatch_block_entity_ticks( - host: &dyn Host, - mods: &[Box], - block: &str, - ticks: &mut bool, -) { - let mut event = BlockEntityTicksEvent { - block, - ticks: *ticks, - }; - for mod_hook in super::entered(mods, "block_entity_ticks") { - mod_hook.block_entity_ticks(host, &mut event); - } - *ticks = event.ticks; -} -/// What [`Hooks::block_entity_tick`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlockEntityTickEvent<'a> { - pub pos: [i32; 3], - pub entity: &'a mut ModBlockEntity, -} -pub fn dispatch_block_entity_tick( - host: &mut dyn Host, - mods: &[Box], - pos: [i32; 3], - entity: &mut ModBlockEntity, -) { - let mut event = BlockEntityTickEvent { pos, entity }; - for mod_hook in super::entered(mods, "block_entity_tick") { - mod_hook.block_entity_tick(host, &mut event); - } -} -/// What [`Hooks::block_entity_update_tag`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlockEntityUpdateTagEvent<'a> { - pub pos: [i32; 3], - pub entity: &'a ModBlockEntity, -} -pub fn dispatch_block_entity_update_tag( - mods: &[Box], - pos: [i32; 3], - entity: &ModBlockEntity, - tag: &mut Option, -) { - if tag.is_some() { - return; - } - let mut event = BlockEntityUpdateTagEvent { pos, entity }; - for mod_hook in super::entered(mods, "block_entity_update_tag") { - if let Some(replacement) = mod_hook.block_entity_update_tag(&mut event) { - *tag = Some(replacement); - break; - } - } -} -/// What [`Hooks::block_entity_container`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlockEntityContainerEvent<'a> { - pub block: &'a str, -} -pub fn dispatch_block_entity_container( - host: &dyn Host, - mods: &[Box], - block: &str, - container: &mut Option, -) { - if container.is_some() { - return; - } - let mut event = BlockEntityContainerEvent { block }; - for mod_hook in super::entered(mods, "block_entity_container") { - if let Some(replacement) = mod_hook.block_entity_container(host, &mut event) { - *container = Some(replacement); - break; - } - } -} -/// What [`Hooks::screen`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ScreenEvent<'a> { - pub block: &'a str, -} -pub fn dispatch_screen( - host: &dyn Host, - mods: &[Box], - block: &str, - screen: &mut Option, -) { - if screen.is_some() { - return; - } - let mut event = ScreenEvent { block }; - for mod_hook in super::entered(mods, "screen") { - if let Some(replacement) = mod_hook.screen(host, &mut event) { - *screen = Some(replacement); - break; - } - } -} -/// What [`Hooks::screen_draw`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ScreenDrawEvent<'a> { - pub block: &'a str, - pub data: &'a [i32], - pub batch: &'a mut HudBatch, - pub origin: [f32; 2], -} -pub fn dispatch_screen_draw( - host: &dyn Host, - mods: &[Box], - block: &str, - data: &[i32], - batch: &mut HudBatch, - origin: [f32; 2], -) { - let mut event = ScreenDrawEvent { - block, - data, - batch, - origin, - }; - for mod_hook in super::entered(mods, "screen_draw") { - mod_hook.screen_draw(host, &mut event); - } -} -/// What [`Hooks::screen_click`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ScreenClickEvent<'a> { - pub block: &'a str, - pub data: &'a [i32], - pub at: [f32; 2], - pub right: bool, -} -pub fn dispatch_screen_click( - host: &dyn Host, - mods: &[Box], - block: &str, - data: &[i32], - at: [f32; 2], - right: bool, - button: &mut Option, -) { - if button.is_some() { - return; - } - let mut event = ScreenClickEvent { - block, - data, - at, - right, - }; - for mod_hook in super::entered(mods, "screen_click") { - if let Some(replacement) = mod_hook.screen_click(host, &mut event) { - *button = Some(replacement); - break; - } - } -} -/// What [`Hooks::screen_tooltip`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ScreenTooltipEvent<'a> { - pub block: &'a str, - pub data: &'a [i32], - pub at: [f32; 2], - pub lines: &'a mut Vec, -} -pub fn dispatch_screen_tooltip( - host: &dyn Host, - mods: &[Box], - block: &str, - data: &[i32], - at: [f32; 2], - lines: &mut Vec, -) { - let mut event = ScreenTooltipEvent { - block, - data, - at, - lines, - }; - for mod_hook in super::entered(mods, "screen_tooltip") { - mod_hook.screen_tooltip(host, &mut event); - } -} -/// What [`Hooks::menu_button`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct MenuButtonEvent<'a> { - pub pos: [i32; 3], - pub entity: &'a mut ModBlockEntity, - pub id: i32, -} -pub fn dispatch_menu_button( - host: &mut dyn Host, - mods: &[Box], - pos: [i32; 3], - entity: &mut ModBlockEntity, - id: i32, -) { - let mut event = MenuButtonEvent { pos, entity, id }; - for mod_hook in super::entered(mods, "menu_button") { - mod_hook.menu_button(host, &mut event); - } -} -/// What [`Hooks::register_commands`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct RegisterCommandsEvent<'a> { - pub commands: &'a mut Vec, -} -pub fn dispatch_register_commands( - host: &mut dyn Host, - mods: &[Box], - commands: &mut Vec, -) { - let mut event = RegisterCommandsEvent { commands }; - for mod_hook in super::entered(mods, "register_commands") { - mod_hook.register_commands(host, &mut event); - } -} -/// What [`Hooks::perform_command`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PerformCommandEvent<'a> { - pub player: &'a NativeServerPlayer, - pub command: &'a str, -} -pub fn dispatch_perform_command( - host: &mut dyn Host, - mods: &[Box], - player: &NativeServerPlayer, - command: &str, - ran: &mut bool, -) { - if *ran { - return; - } - let mut event = PerformCommandEvent { player, command }; - for mod_hook in super::entered(mods, "perform_command") { - if matches!(mod_hook.perform_command(host, &mut event), Flow::Cancel) { - *ran = true; - break; - } - } -} -/// What [`Hooks::menu_clicked`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct MenuClickedEvent { - pub uuid: u128, - pub window: i32, - pub slot: i32, - pub button: i32, - pub mode: i32, -} -pub fn dispatch_menu_clicked( - host: &mut dyn Host, - mods: &[Box], - uuid: u128, - window: i32, - slot: i32, - button: i32, - mode: i32, -) { - let mut event = MenuClickedEvent { - uuid, - window, - slot, - button, - mode, - }; - for mod_hook in super::entered(mods, "menu_clicked") { - mod_hook.menu_clicked(host, &mut event); - } -} -/// What [`Hooks::menu_closed`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct MenuClosedEvent { - pub uuid: u128, - pub window: i32, -} -pub fn dispatch_menu_closed(host: &mut dyn Host, mods: &[Box], uuid: u128, window: i32) { - let mut event = MenuClosedEvent { uuid, window }; - for mod_hook in super::entered(mods, "menu_closed") { - mod_hook.menu_closed(host, &mut event); - } -} -/// What [`Hooks::tooltip`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct TooltipEvent<'a> { - pub stack: &'a ItemStack, - pub lines: &'a mut Vec<(Text, u32)>, -} -pub fn dispatch_tooltip( - host: &dyn Host, - mods: &[Box], - stack: &ItemStack, - lines: &mut Vec<(Text, u32)>, -) { - let mut event = TooltipEvent { stack, lines }; - for mod_hook in super::entered(mods, "tooltip") { - mod_hook.tooltip(host, &mut event); - } -} -/// What [`Hooks::use_item_on`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct UseItemOnEvent<'a> { - pub stack: &'a ItemStack, - pub pos: [i32; 3], - pub face: [i32; 3], -} -pub fn dispatch_use_item_on( - host: &mut dyn Host, - mods: &[Box], - stack: &ItemStack, - pos: [i32; 3], - face: [i32; 3], - claimed: &mut bool, -) { - if *claimed { - return; - } - let mut event = UseItemOnEvent { stack, pos, face }; - for mod_hook in super::entered(mods, "use_item_on") { - if matches!(mod_hook.use_item_on(host, &mut event), Flow::Cancel) { - *claimed = true; - break; - } - } -} -/// What [`Hooks::use_item`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct UseItemEvent<'a> { - pub stack: &'a ItemStack, -} -pub fn dispatch_use_item( - host: &mut dyn Host, - mods: &[Box], - stack: &ItemStack, - claimed: &mut bool, -) { - if *claimed { - return; - } - let mut event = UseItemEvent { stack }; - for mod_hook in super::entered(mods, "use_item") { - if matches!(mod_hook.use_item(host, &mut event), Flow::Cancel) { - *claimed = true; - break; - } - } -} -/// What [`Hooks::destroy_block`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct DestroyBlockEvent { - pub pos: [i32; 3], - pub state: BlockStateId, -} -pub fn dispatch_destroy_block( - host: &mut dyn Host, - mods: &[Box], - pos: [i32; 3], - state: BlockStateId, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = DestroyBlockEvent { pos, state }; - for mod_hook in super::entered(mods, "destroy_block") { - if matches!(mod_hook.destroy_block(host, &mut event), Flow::Cancel) { - *canceled = true; - break; - } - } -} -/// What [`Hooks::place_block`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlaceBlockEvent<'a> { - pub stack: &'a ItemStack, - pub pos: [i32; 3], - pub state: BlockStateId, -} -pub fn dispatch_place_block( - host: &mut dyn Host, - mods: &[Box], - stack: &ItemStack, - pos: [i32; 3], - state: BlockStateId, -) { - let mut event = PlaceBlockEvent { stack, pos, state }; - for mod_hook in super::entered(mods, "place_block") { - mod_hook.place_block(host, &mut event); - } -} -/// What [`Hooks::right_click_block`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct RightClickBlockEvent { - pub pos: [i32; 3], - pub face: [i32; 3], -} -pub fn dispatch_right_click_block( - host: &mut dyn Host, - mods: &[Box], - pos: [i32; 3], - face: [i32; 3], - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = RightClickBlockEvent { pos, face }; - for mod_hook in super::entered(mods, "right_click_block") { - if matches!(mod_hook.right_click_block(host, &mut event), Flow::Cancel) { - *canceled = true; - break; - } - } -} -/// What [`Hooks::block_spread`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlockSpreadEvent<'a> { - pub dimension: LevelDimension, - pub kind: &'a str, - pub from: BlockPos, - pub to: BlockPos, -} -pub fn dispatch_block_spread( - level: &dyn LevelHost, - mods: &[Box], - dimension: LevelDimension, - kind: &str, - from: BlockPos, - to: BlockPos, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = BlockSpreadEvent { - dimension, - kind, - from, - to, - }; - for mod_hook in super::entered(mods, "block_spread") { - if matches!(mod_hook.block_spread(level, &mut event), Flow::Cancel) { - *canceled = true; - break; - } - } -} -/// What [`Hooks::piston_move`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PistonMoveEvent<'a> { - pub dimension: LevelDimension, - pub piston: BlockPos, - pub direction: [i32; 3], - pub push: &'a [BlockPos], - pub destroy: &'a [BlockPos], -} -pub fn dispatch_piston_move( - level: &dyn LevelHost, - mods: &[Box], - dimension: LevelDimension, - piston: BlockPos, - direction: [i32; 3], - push: &[BlockPos], - destroy: &[BlockPos], - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = PistonMoveEvent { - dimension, - piston, - direction, - push, - destroy, - }; - for mod_hook in super::entered(mods, "piston_move") { - if matches!(mod_hook.piston_move(level, &mut event), Flow::Cancel) { - *canceled = true; - break; - } - } -} -/// What [`Hooks::explosion_detonate`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ExplosionDetonateEvent<'a> { - pub dimension: &'a str, - pub center: [f64; 3], - pub blocks: &'a mut Vec<[i32; 3]>, -} -pub fn dispatch_explosion_detonate( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - center: [f64; 3], - blocks: &mut Vec<[i32; 3]>, -) { - let mut event = ExplosionDetonateEvent { - dimension, - center, - blocks, - }; - for mod_hook in super::entered(mods, "explosion_detonate") { - mod_hook.explosion_detonate(host, &mut event); - } -} -/// What [`Hooks::fill_bucket`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct FillBucketEvent<'a> { - pub stack: &'a ItemStack, - pub pos: [i32; 3], -} -pub fn dispatch_fill_bucket( - host: &mut dyn Host, - mods: &[Box], - stack: &ItemStack, - pos: [i32; 3], - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = FillBucketEvent { stack, pos }; - for mod_hook in super::entered(mods, "fill_bucket") { - if matches!(mod_hook.fill_bucket(host, &mut event), Flow::Cancel) { - *canceled = true; - break; - } - } -} -/// What [`Hooks::attack_entity`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct AttackEntityEvent<'a> { - pub entity: i32, - pub kind: &'a str, - pub pos: [f64; 3], -} -pub fn dispatch_attack_entity( - host: &mut dyn Host, - mods: &[Box], - entity: i32, - kind: &str, - pos: [f64; 3], - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = AttackEntityEvent { entity, kind, pos }; - for mod_hook in super::entered(mods, "attack_entity") { - if matches!(mod_hook.attack_entity(host, &mut event), Flow::Cancel) { - *canceled = true; - break; - } - } -} -/// What [`Hooks::net_from_client`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct NetFromClientEvent<'a> { - pub player: u128, - pub channel: &'a str, - pub data: &'a [u8], -} -pub fn dispatch_net_from_client( - host: &mut dyn Host, - mods: &[Box], - player: u128, - channel: &str, - data: &[u8], -) { - let mut event = NetFromClientEvent { - player, - channel, - data, - }; - for mod_hook in super::entered(mods, "net_from_client") { - mod_hook.net_from_client(host, &mut event); - } -} -/// What [`Hooks::net_from_server`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct NetFromServerEvent<'a> { - pub channel: &'a str, - pub data: &'a [u8], -} -pub fn dispatch_net_from_server( - host: &mut dyn Host, - mods: &[Box], - channel: &str, - data: &[u8], -) { - let mut event = NetFromServerEvent { channel, data }; - for mod_hook in super::entered(mods, "net_from_server") { - mod_hook.net_from_server(host, &mut event); - } -} -/// What [`Hooks::player_died`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerDiedEvent<'a> { - pub player: u128, - pub cause: Option<&'a str>, -} -pub fn dispatch_player_died( - host: &mut dyn Host, - mods: &[Box], - player: u128, - cause: Option<&str>, -) { - let mut event = PlayerDiedEvent { player, cause }; - for mod_hook in super::entered(mods, "player_died") { - mod_hook.player_died(host, &mut event); - } -} -/// What [`Hooks::mob_spawned`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct MobSpawnedEvent<'a> { - pub dimension: &'a str, - pub kind: &'a str, - pub pos: [f64; 3], - pub natural: bool, -} -pub fn dispatch_mob_spawned( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - kind: &str, - pos: [f64; 3], - natural: bool, -) { - let mut event = MobSpawnedEvent { - dimension, - kind, - pos, - natural, - }; - for mod_hook in super::entered(mods, "mob_spawned") { - mod_hook.mob_spawned(host, &mut event); - } -} -/// What [`Hooks::mob_killed`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct MobKilledEvent<'a> { - pub player: u128, - pub entity: u32, - pub kind: &'a str, -} -pub fn dispatch_mob_killed( - host: &mut dyn Host, - mods: &[Box], - player: u128, - entity: u32, - kind: &str, -) { - let mut event = MobKilledEvent { - player, - entity, - kind, - }; - for mod_hook in super::entered(mods, "mob_killed") { - mod_hook.mob_killed(host, &mut event); - } -} -/// What [`Hooks::item_dropped`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ItemDroppedEvent<'a> { - pub player: u128, - pub item: &'a str, - pub count: u32, -} -pub fn dispatch_item_dropped( - host: &mut dyn Host, - mods: &[Box], - player: u128, - item: &str, - count: u32, -) { - let mut event = ItemDroppedEvent { - player, - item, - count, - }; - for mod_hook in super::entered(mods, "item_dropped") { - mod_hook.item_dropped(host, &mut event); - } -} -/// What [`Hooks::item_picked_up`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ItemPickedUpEvent<'a> { - pub player: u128, - pub item: &'a str, - pub count: u32, -} -pub fn dispatch_item_picked_up( - host: &mut dyn Host, - mods: &[Box], - player: u128, - item: &str, - count: u32, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = ItemPickedUpEvent { - player, - item, - count, - }; - for mod_hook in super::entered(mods, "item_picked_up") { - if matches!(mod_hook.item_picked_up(host, &mut event), Flow::Cancel) { - *canceled = true; - break; - } - } -} -/// What [`Hooks::hurt_enemy`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct HurtEnemyEvent<'a> { - pub stack: &'a ItemStack, - pub entity: u32, - pub kind: &'a str, -} -pub fn dispatch_hurt_enemy( - host: &mut dyn Host, - mods: &[Box], - stack: &ItemStack, - entity: u32, - kind: &str, -) { - let mut event = HurtEnemyEvent { - stack, - entity, - kind, - }; - for mod_hook in super::entered(mods, "hurt_enemy") { - mod_hook.hurt_enemy(host, &mut event); - } -} -/// What [`Hooks::inventory_tick`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct InventoryTickEvent { - pub slot: i32, - pub selected: bool, -} -pub fn dispatch_inventory_tick( - host: &mut dyn Host, - mods: &[Box], - slot: i32, - selected: bool, -) { - let mut event = InventoryTickEvent { slot, selected }; - for mod_hook in super::entered(mods, "inventory_tick") { - mod_hook.inventory_tick(host, &mut event); - } -} -/// What [`Hooks::on_equip`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct OnEquipEvent<'a> { - pub slot: &'a str, - pub new: Option<&'a ItemStack>, -} -pub fn dispatch_on_equip( - host: &mut dyn Host, - mods: &[Box], - slot: &str, - new: Option<&ItemStack>, -) { - let mut event = OnEquipEvent { slot, new }; - for mod_hook in super::entered(mods, "on_equip") { - mod_hook.on_equip(host, &mut event); - } -} -/// What [`Hooks::block_signal`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlockSignalEvent { - pub dimension: LevelDimension, - pub pos: BlockPos, - pub state: BlockStateId, - pub face: Direction, - pub direct: bool, - pub signal: i32, -} -pub fn dispatch_block_signal( - level: &dyn LevelHost, - mods: &[Box], - dimension: LevelDimension, - pos: BlockPos, - state: BlockStateId, - face: Direction, - direct: bool, - signal: &mut i32, -) { - let mut event = BlockSignalEvent { - dimension, - pos, - state, - face, - direct, - signal: *signal, - }; - for mod_hook in super::entered(mods, "block_signal") { - mod_hook.block_signal(level, &mut event); - } - *signal = event.signal; -} -/// What [`Hooks::is_signal_source`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct IsSignalSourceEvent { - pub state: BlockStateId, - pub source: bool, -} -pub fn dispatch_is_signal_source( - level: &dyn LevelHost, - mods: &[Box], - state: BlockStateId, - source: &mut bool, -) { - let mut event = IsSignalSourceEvent { - state, - source: *source, - }; - for mod_hook in super::entered(mods, "is_signal_source") { - mod_hook.is_signal_source(level, &mut event); - } - *source = event.source; -} -/// What [`Hooks::health_hud`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct HealthHudEvent<'a> { - pub inputs: &'a horizon_client::hud_layout::HealthInputs, - pub wi: i32, - pub hi: i32, -} -pub fn dispatch_health_hud( - host: &mut dyn Host, - mods: &[Box], - inputs: &horizon_client::hud_layout::HealthInputs, - wi: i32, - hi: i32, - draws: &mut Option>, -) { - if draws.is_some() { - return; - } - let mut event = HealthHudEvent { inputs, wi, hi }; - for mod_hook in super::entered(mods, "health_hud") { - if let Some(replacement) = mod_hook.health_hud(host, &mut event) { - *draws = Some(replacement); - break; - } - } -} -/// What [`Hooks::interact_entity`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct InteractEntityEvent<'a> { - pub entity: i32, - pub kind: &'a str, - pub stack: Option<&'a ItemStack>, -} -pub fn dispatch_interact_entity( - host: &mut dyn Host, - mods: &[Box], - entity: i32, - kind: &str, - stack: Option<&ItemStack>, - claimed: &mut bool, -) { - if *claimed { - return; - } - let mut event = InteractEntityEvent { - entity, - kind, - stack, - }; - for mod_hook in super::entered(mods, "interact_entity") { - if matches!(mod_hook.interact_entity(host, &mut event), Flow::Cancel) { - *claimed = true; - break; - } - } -} -/// What [`Hooks::left_click_block`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct LeftClickBlockEvent { - pub pos: [i32; 3], - pub face: [i32; 3], - pub state: BlockStateId, -} -pub fn dispatch_left_click_block( - host: &mut dyn Host, - mods: &[Box], - pos: [i32; 3], - face: [i32; 3], - state: BlockStateId, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = LeftClickBlockEvent { pos, face, state }; - for mod_hook in super::entered(mods, "left_click_block") { - if matches!(mod_hook.left_click_block(host, &mut event), Flow::Cancel) { - *canceled = true; - break; - } - } -} -/// What [`Hooks::player_tick`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerTickEvent {} -pub fn dispatch_player_tick(host: &mut dyn Host, mods: &[Box]) { - let mut event = PlayerTickEvent {}; - for mod_hook in super::entered(mods, "player_tick") { - mod_hook.player_tick(host, &mut event); - } -} -/// What [`Hooks::entity_join_level`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct EntityJoinLevelEvent<'a> { - pub dimension: &'a str, - pub entity: u32, - pub kind: &'a str, - pub pos: [f64; 3], - pub loaded_from_disk: bool, -} -pub fn dispatch_entity_join_level( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - entity: u32, - kind: &str, - pos: [f64; 3], - loaded_from_disk: bool, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = EntityJoinLevelEvent { - dimension, - entity, - kind, - pos, - loaded_from_disk, - }; - for mod_hook in super::entered(mods, "entity_join_level") { - if matches!(mod_hook.entity_join_level(host, &mut event), Flow::Cancel) { - *canceled = true; - break; - } - } -} -/// What [`Hooks::living_incoming_damage`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct LivingIncomingDamageEvent<'a> { - pub entity: i32, - pub kind: &'a str, - pub damage_type: &'a str, - pub causing: i32, - pub direct: i32, - pub amount: f32, -} -pub fn dispatch_living_incoming_damage( - mods: &[Box], - entity: i32, - kind: &str, - damage_type: &str, - causing: i32, - direct: i32, - amount: &mut f32, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = LivingIncomingDamageEvent { - entity, - kind, - damage_type, - causing, - direct, - amount: *amount, - }; - for mod_hook in super::entered(mods, "living_incoming_damage") { - if matches!(mod_hook.living_incoming_damage(&mut event), Flow::Cancel) { - *canceled = true; - break; - } - } - *amount = event.amount; -} -/// What [`Hooks::menu_button_v2`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct MenuButtonV2Event<'a> { - pub pos: [i32; 3], - pub entity: &'a mut ModBlockEntity, - pub id: i32, - pub carried: &'a mut Option, -} -pub fn dispatch_menu_button_v2( - host: &mut dyn Host, - mods: &[Box], - pos: [i32; 3], - entity: &mut ModBlockEntity, - id: i32, - carried: &mut Option, -) { - let mut event = MenuButtonV2Event { - pos, - entity, - id, - carried, - }; - for mod_hook in super::entered(mods, "menu_button_v2") { - mod_hook.menu_button_v2(host, &mut event); - } -} -/// What [`Hooks::entity_leave_level`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct EntityLeaveLevelEvent<'a> { - pub dimension: &'a str, - pub entity: u32, - pub kind: &'a str, -} -pub fn dispatch_entity_leave_level( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - entity: u32, - kind: &str, -) { - let mut event = EntityLeaveLevelEvent { - dimension, - entity, - kind, - }; - for mod_hook in super::entered(mods, "entity_leave_level") { - mod_hook.entity_leave_level(host, &mut event); - } -} -/// What [`Hooks::block_entity_fluids`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlockEntityFluidsEvent<'a> { - pub block: &'a str, -} -pub fn dispatch_block_entity_fluids( - host: &dyn Host, - mods: &[Box], - block: &str, - fluids: &mut Option, -) { - if fluids.is_some() { - return; - } - let mut event = BlockEntityFluidsEvent { block }; - for mod_hook in super::entered(mods, "block_entity_fluids") { - if let Some(replacement) = mod_hook.block_entity_fluids(host, &mut event) { - *fluids = Some(replacement); - break; - } - } -} -/// What [`Hooks::should_apply_effect_tick_this_tick`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ShouldApplyEffectTickThisTickEvent<'a> { - pub effect: &'a str, - pub tick_count: i32, - pub amplifier: i32, - pub apply: bool, -} -pub fn dispatch_should_apply_effect_tick_this_tick( - host: &dyn Host, - mods: &[Box], - effect: &str, - tick_count: i32, - amplifier: i32, - apply: &mut bool, -) { - let mut event = ShouldApplyEffectTickThisTickEvent { - effect, - tick_count, - amplifier, - apply: *apply, - }; - for mod_hook in super::entered(mods, "should_apply_effect_tick_this_tick") { - mod_hook.should_apply_effect_tick_this_tick(host, &mut event); - } - *apply = event.apply; -} -/// What [`Hooks::apply_effect_tick`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ApplyEffectTickEvent<'a> { - pub effect: &'a str, - pub dimension: &'a str, - pub entity: Option, - pub amplifier: i32, - pub keep: bool, -} -pub fn dispatch_apply_effect_tick( - host: &dyn Host, - mods: &[Box], - effect: &str, - dimension: &str, - entity: Option, - amplifier: i32, - keep: &mut bool, -) { - let mut event = ApplyEffectTickEvent { - effect, - dimension, - entity, - amplifier, - keep: *keep, - }; - for mod_hook in super::entered(mods, "apply_effect_tick") { - mod_hook.apply_effect_tick(host, &mut event); - } - *keep = event.keep; -} -/// What [`Hooks::on_effect_added`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct OnEffectAddedEvent<'a> { - pub effect: &'a str, - pub entity: Option, - pub amplifier: i32, -} -pub fn dispatch_on_effect_added( - host: &mut dyn Host, - mods: &[Box], - effect: &str, - entity: Option, - amplifier: i32, -) { - let mut event = OnEffectAddedEvent { - effect, - entity, - amplifier, - }; - for mod_hook in super::entered(mods, "on_effect_added") { - mod_hook.on_effect_added(host, &mut event); - } -} -/// What [`Hooks::mob_effect_added`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct MobEffectAddedEvent<'a> { - pub effect: &'a str, - pub entity: Option, - pub amplifier: i32, -} -pub fn dispatch_mob_effect_added( - host: &mut dyn Host, - mods: &[Box], - effect: &str, - entity: Option, - amplifier: i32, -) { - let mut event = MobEffectAddedEvent { - effect, - entity, - amplifier, - }; - for mod_hook in super::entered(mods, "mob_effect_added") { - mod_hook.mob_effect_added(host, &mut event); - } -} -/// What [`Hooks::chunk_load`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ChunkLoadEvent<'a> { - pub dimension: &'a str, - pub chunk: [i32; 2], - pub new: bool, -} -pub fn dispatch_chunk_load( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - chunk: [i32; 2], - new: bool, -) { - let mut event = ChunkLoadEvent { - dimension, - chunk, - new, - }; - for mod_hook in super::entered(mods, "chunk_load") { - mod_hook.chunk_load(host, &mut event); - } -} -/// What [`Hooks::chunk_unload`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ChunkUnloadEvent<'a> { - pub dimension: &'a str, - pub chunk: [i32; 2], -} -pub fn dispatch_chunk_unload( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - chunk: [i32; 2], -) { - let mut event = ChunkUnloadEvent { dimension, chunk }; - for mod_hook in super::entered(mods, "chunk_unload") { - mod_hook.chunk_unload(host, &mut event); - } -} -/// What [`Hooks::player_logged_in`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerLoggedInEvent<'a> { - pub player: u128, - pub name: &'a str, -} -pub fn dispatch_player_logged_in( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, -) { - let mut event = PlayerLoggedInEvent { player, name }; - for mod_hook in super::entered(mods, "player_logged_in") { - mod_hook.player_logged_in(host, &mut event); - } -} -/// What [`Hooks::player_logged_out`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerLoggedOutEvent<'a> { - pub player: u128, - pub name: &'a str, -} -pub fn dispatch_player_logged_out( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, -) { - let mut event = PlayerLoggedOutEvent { player, name }; - for mod_hook in super::entered(mods, "player_logged_out") { - mod_hook.player_logged_out(host, &mut event); - } -} -/// What [`Hooks::item_bar`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ItemBarEvent<'a> { - pub stack: &'a ItemStack, - pub bar: &'a mut Option<(i32, u32)>, -} -pub fn dispatch_item_bar( - host: &dyn Host, - mods: &[Box], - stack: &ItemStack, - bar: &mut Option<(i32, u32)>, -) { - let mut event = ItemBarEvent { stack, bar }; - for mod_hook in super::entered(mods, "item_bar") { - mod_hook.item_bar(host, &mut event); - } -} -/// What [`Hooks::has_analog_output`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct HasAnalogOutputEvent { - pub state: BlockStateId, - pub output: bool, -} -pub fn dispatch_has_analog_output( - level: &dyn LevelHost, - mods: &[Box], - state: BlockStateId, - output: &mut bool, -) { - let mut event = HasAnalogOutputEvent { - state, - output: *output, - }; - for mod_hook in super::entered(mods, "has_analog_output") { - mod_hook.has_analog_output(level, &mut event); - } - *output = event.output; -} -/// What [`Hooks::analog_output`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct AnalogOutputEvent { - pub dimension: LevelDimension, - pub pos: BlockPos, - pub state: BlockStateId, - pub face: Direction, - pub output: i32, -} -pub fn dispatch_analog_output( - level: &dyn LevelHost, - mods: &[Box], - dimension: LevelDimension, - pos: BlockPos, - state: BlockStateId, - face: Direction, - output: &mut i32, -) { - let mut event = AnalogOutputEvent { - dimension, - pos, - state, - face, - output: *output, - }; - for mod_hook in super::entered(mods, "analog_output") { - mod_hook.analog_output(level, &mut event); - } - *output = event.output; -} -/// What [`Hooks::component_types`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ComponentTypesEvent<'a> { - pub types: &'a mut Vec, -} -pub fn dispatch_component_types(mods: &[Box], types: &mut Vec) { - let mut event = ComponentTypesEvent { types }; - for mod_hook in super::entered(mods, "component_types") { - mod_hook.component_types(&mut event); - } -} -/// What [`Hooks::dispense_item`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct DispenseItemEvent<'a> { - pub dimension: &'a str, - pub pos: [i32; 3], - pub facing: Direction, - pub stack: &'a ItemStack, -} -pub fn dispatch_dispense_item( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - pos: [i32; 3], - facing: Direction, - stack: &ItemStack, - answer: &mut Option, -) { - if answer.is_some() { - return; - } - let mut event = DispenseItemEvent { - dimension, - pos, - facing, - stack, - }; - for mod_hook in super::entered(mods, "dispense_item") { - if let Some(replacement) = mod_hook.dispense_item(host, &mut event) { - *answer = Some(replacement); - break; - } - } -} -/// What [`Hooks::tooltip_component`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct TooltipComponentEvent<'a> { - pub stack: &'a ItemStack, -} -pub fn dispatch_tooltip_component( - host: &dyn Host, - mods: &[Box], - stack: &ItemStack, - size: &mut Option<[f32; 2]>, -) { - if size.is_some() { - return; - } - let mut event = TooltipComponentEvent { stack }; - for mod_hook in super::entered(mods, "tooltip_component") { - if let Some(replacement) = mod_hook.tooltip_component(host, &mut event) { - *size = Some(replacement); - break; - } - } -} -/// What [`Hooks::tooltip_component_draw`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct TooltipComponentDrawEvent<'a> { - pub stack: &'a ItemStack, - pub batch: &'a mut HudBatch, - pub origin: [f32; 2], - pub size: [f32; 2], -} -pub fn dispatch_tooltip_component_draw( - host: &dyn Host, - mods: &[Box], - stack: &ItemStack, - batch: &mut HudBatch, - origin: [f32; 2], - size: [f32; 2], -) { - let mut event = TooltipComponentDrawEvent { - stack, - batch, - origin, - size, - }; - for mod_hook in super::entered(mods, "tooltip_component_draw") { - mod_hook.tooltip_component_draw(host, &mut event); - } -} -/// What [`Hooks::can_survive`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct CanSurviveEvent { - pub dimension: LevelDimension, - pub pos: BlockPos, - pub state: BlockStateId, -} -pub fn dispatch_can_survive( - level: &dyn LevelHost, - mods: &[Box], - dimension: LevelDimension, - pos: BlockPos, - state: BlockStateId, - survives: &mut Option, -) { - if survives.is_some() { - return; - } - let mut event = CanSurviveEvent { - dimension, - pos, - state, - }; - for mod_hook in super::entered(mods, "can_survive") { - if let Some(replacement) = mod_hook.can_survive(level, &mut event) { - *survives = Some(replacement); - break; - } - } -} -/// What [`Hooks::may_place_on`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct MayPlaceOnEvent { - pub dimension: LevelDimension, - pub state: BlockStateId, - pub ground: BlockStateId, - pub ground_pos: BlockPos, -} -pub fn dispatch_may_place_on( - level: &dyn LevelHost, - mods: &[Box], - dimension: LevelDimension, - state: BlockStateId, - ground: BlockStateId, - ground_pos: BlockPos, - may: &mut Option, -) { - if may.is_some() { - return; - } - let mut event = MayPlaceOnEvent { - dimension, - state, - ground, - ground_pos, - }; - for mod_hook in super::entered(mods, "may_place_on") { - if let Some(replacement) = mod_hook.may_place_on(level, &mut event) { - *may = Some(replacement); - break; - } - } -} -/// What [`Hooks::block_entity_submit`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlockEntitySubmitEvent<'a> { - pub pos: [i32; 3], - pub block: &'a str, - pub state: BlockStateId, - pub entity: &'a ModBlockEntity, - pub draw: &'a mut BlockEntityDraw, -} -pub fn dispatch_block_entity_submit( - host: &dyn Host, - mods: &[Box], - pos: [i32; 3], - block: &str, - state: BlockStateId, - entity: &ModBlockEntity, - draw: &mut BlockEntityDraw, -) { - let mut event = BlockEntitySubmitEvent { - pos, - block, - state, - entity, - draw, - }; - for mod_hook in super::entered(mods, "block_entity_submit") { - mod_hook.block_entity_submit(host, &mut event); - } -} -/// What [`Hooks::block_entity_render_bounding_box`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlockEntityRenderBoundingBoxEvent<'a> { - pub pos: [i32; 3], - pub block: &'a str, - pub state: BlockStateId, - pub entity: &'a ModBlockEntity, - pub bounds: &'a mut [f32; 6], -} -pub fn dispatch_block_entity_render_bounding_box( - host: &dyn Host, - mods: &[Box], - pos: [i32; 3], - block: &str, - state: BlockStateId, - entity: &ModBlockEntity, - bounds: &mut [f32; 6], -) { - let mut event = BlockEntityRenderBoundingBoxEvent { - pos, - block, - state, - entity, - bounds, - }; - for mod_hook in super::entered(mods, "block_entity_render_bounding_box") { - mod_hook.block_entity_render_bounding_box(host, &mut event); - } -} -/// What [`Hooks::is_valid_bonemeal_target`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct IsValidBonemealTargetEvent { - pub dimension: LevelDimension, - pub pos: BlockPos, - pub state: BlockStateId, -} -pub fn dispatch_is_valid_bonemeal_target( - level: &dyn LevelHost, - mods: &[Box], - dimension: LevelDimension, - pos: BlockPos, - state: BlockStateId, - valid: &mut Option, -) { - if valid.is_some() { - return; - } - let mut event = IsValidBonemealTargetEvent { - dimension, - pos, - state, - }; - for mod_hook in super::entered(mods, "is_valid_bonemeal_target") { - if let Some(replacement) = mod_hook.is_valid_bonemeal_target(level, &mut event) { - *valid = Some(replacement); - break; - } - } -} -/// What [`Hooks::is_bonemeal_success`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct IsBonemealSuccessEvent { - pub dimension: LevelDimension, - pub pos: BlockPos, - pub state: BlockStateId, - pub success: bool, -} -pub fn dispatch_is_bonemeal_success( - level: &mut dyn LevelRandomHost, - mods: &[Box], - dimension: LevelDimension, - pos: BlockPos, - state: BlockStateId, - success: &mut bool, -) { - let mut event = IsBonemealSuccessEvent { - dimension, - pos, - state, - success: *success, - }; - for mod_hook in super::entered(mods, "is_bonemeal_success") { - mod_hook.is_bonemeal_success(level, &mut event); - } - *success = event.success; -} -/// What [`Hooks::perform_bonemeal`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PerformBonemealEvent { - pub dimension: LevelDimension, - pub pos: BlockPos, - pub state: BlockStateId, -} -pub fn dispatch_perform_bonemeal( - level: &mut dyn LevelWriteHost, - mods: &[Box], - dimension: LevelDimension, - pos: BlockPos, - state: BlockStateId, -) { - let mut event = PerformBonemealEvent { - dimension, - pos, - state, - }; - for mod_hook in super::entered(mods, "perform_bonemeal") { - mod_hook.perform_bonemeal(level, &mut event); - } -} -/// What [`Hooks::block_step_on`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlockStepOnEvent<'a> { - pub dimension: &'a str, - pub entity: i32, - pub kind: &'a str, - pub state: BlockStateId, - pub position: [i32; 3], -} -pub fn dispatch_block_step_on( - mods: &[Box], - dimension: &str, - entity: i32, - kind: &str, - state: BlockStateId, - position: [i32; 3], -) { - let mut event = BlockStepOnEvent { - dimension, - entity, - kind, - state, - position, - }; - for mod_hook in super::entered(mods, "block_step_on") { - mod_hook.block_step_on(&mut event); - } -} -/// What [`Hooks::block_entity_inside`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlockEntityInsideEvent<'a> { - pub dimension: &'a str, - pub entity: i32, - pub kind: &'a str, - pub state: BlockStateId, - pub position: [i32; 3], -} -pub fn dispatch_block_entity_inside( - mods: &[Box], - dimension: &str, - entity: i32, - kind: &str, - state: BlockStateId, - position: [i32; 3], -) { - let mut event = BlockEntityInsideEvent { - dimension, - entity, - kind, - state, - position, - }; - for mod_hook in super::entered(mods, "block_entity_inside") { - mod_hook.block_entity_inside(&mut event); - } -} -/// What [`Hooks::block_fall_on`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlockFallOnEvent<'a> { - pub dimension: &'a str, - pub entity: i32, - pub kind: &'a str, - pub state: BlockStateId, - pub position: [i32; 3], - pub distance: f64, - pub damage: f32, -} -pub fn dispatch_block_fall_on( - mods: &[Box], - dimension: &str, - entity: i32, - kind: &str, - state: BlockStateId, - position: [i32; 3], - distance: f64, - damage: &mut f32, -) { - let mut event = BlockFallOnEvent { - dimension, - entity, - kind, - state, - position, - distance, - damage: *damage, - }; - for mod_hook in super::entered(mods, "block_fall_on") { - mod_hook.block_fall_on(&mut event); - } - *damage = event.damage; -} -/// What [`Hooks::consume_effect`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ConsumeEffectEvent<'a> { - pub kind: &'a str, - pub data: &'a serde_json::Value, -} -pub fn dispatch_consume_effect( - host: &mut dyn Host, - mods: &[Box], - kind: &str, - data: &serde_json::Value, -) { - let mut event = ConsumeEffectEvent { kind, data }; - for mod_hook in super::entered(mods, "consume_effect") { - mod_hook.consume_effect(host, &mut event); - } -} -/// What [`Hooks::loot_table_load`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct LootTableLoadEvent<'a> { - pub id: &'a str, - pub table: &'a mut Value, -} -pub fn dispatch_loot_table_load(mods: &[Box], id: &str, table: &mut Value) { - let mut event = LootTableLoadEvent { id, table }; - for mod_hook in super::entered(mods, "loot_table_load") { - mod_hook.loot_table_load(&mut event); - } -} -/// What [`Hooks::loot_drops`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct LootDropsEvent<'a> { - pub context: &'a NativeLootDrops<'a>, - pub drops: &'a mut Vec, -} -pub fn dispatch_loot_drops( - host: &mut dyn Host, - mods: &[Box], - context: &NativeLootDrops<'_>, - drops: &mut Vec, -) { - let mut event = LootDropsEvent { context, drops }; - for mod_hook in super::entered(mods, "loot_drops") { - mod_hook.loot_drops(host, &mut event); - } -} -/// What [`Hooks::state_for_placement`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct StateForPlacementEvent<'a> { - pub dimension: LevelDimension, - pub state: BlockStateId, - pub context: &'a PlacementContext, - pub fluid: FluidState, -} -pub fn dispatch_state_for_placement( - level: &dyn LevelHost, - mods: &[Box], - dimension: LevelDimension, - state: BlockStateId, - context: &PlacementContext, - fluid: FluidState, - answer: &mut Option>, -) { - if answer.is_some() { - return; - } - let mut event = StateForPlacementEvent { - dimension, - state, - context, - fluid, - }; - for mod_hook in super::entered(mods, "state_for_placement") { - if let Some(replacement) = mod_hook.state_for_placement(level, &mut event) { - *answer = Some(replacement); - break; - } - } -} -/// What [`Hooks::enchant_effect`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct EnchantEffectEvent<'a> { - pub enchantment: &'a str, - pub kind: &'a str, - pub data: &'a serde_json::Value, - pub level: u32, - pub victim: u32, -} -pub fn dispatch_enchant_effect( - host: &mut dyn Host, - mods: &[Box], - enchantment: &str, - kind: &str, - data: &serde_json::Value, - level: u32, - victim: u32, -) { - let mut event = EnchantEffectEvent { - enchantment, - kind, - data, - level, - victim, - }; - for mod_hook in super::entered(mods, "enchant_effect") { - mod_hook.enchant_effect(host, &mut event); - } -} -/// What [`Hooks::screen_render_post`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ScreenRenderPostEvent<'a> { - pub batch: &'a mut HudBatch, - pub screen: &'a NativeScreenView<'a>, -} -pub fn dispatch_screen_render_post( - host: &dyn Host, - mods: &[Box], - batch: &mut HudBatch, - screen: &NativeScreenView<'_>, -) { - let mut event = ScreenRenderPostEvent { batch, screen }; - for mod_hook in super::entered(mods, "screen_render_post") { - mod_hook.screen_render_post(host, &mut event); - } -} -/// What [`Hooks::modify_default_components`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ModifyDefaultComponentsEvent<'a> { - pub item: &'a str, - pub components: &'a mut Map, -} -pub fn dispatch_modify_default_components( - mods: &[Box], - item: &str, - components: &mut Map, -) { - let mut event = ModifyDefaultComponentsEvent { item, components }; - for mod_hook in super::entered(mods, "modify_default_components") { - mod_hook.modify_default_components(&mut event); - } -} -/// What [`Hooks::screen_mouse_button_pressed_pre`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ScreenMouseButtonPressedPreEvent<'a> { - pub screen: &'a NativeScreenView<'a>, - pub button: i32, - pub modifiers: u32, -} -pub fn dispatch_screen_mouse_button_pressed_pre( - host: &dyn Host, - mods: &[Box], - screen: &NativeScreenView<'_>, - button: i32, - modifiers: u32, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = ScreenMouseButtonPressedPreEvent { - screen, - button, - modifiers, - }; - for mod_hook in super::entered(mods, "screen_mouse_button_pressed_pre") { - if matches!( - mod_hook.screen_mouse_button_pressed_pre(host, &mut event), - Flow::Cancel - ) { - *canceled = true; - break; - } - } -} -/// What [`Hooks::screen_mouse_button_released_pre`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ScreenMouseButtonReleasedPreEvent<'a> { - pub screen: &'a NativeScreenView<'a>, - pub button: i32, - pub modifiers: u32, -} -pub fn dispatch_screen_mouse_button_released_pre( - host: &dyn Host, - mods: &[Box], - screen: &NativeScreenView<'_>, - button: i32, - modifiers: u32, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = ScreenMouseButtonReleasedPreEvent { - screen, - button, - modifiers, - }; - for mod_hook in super::entered(mods, "screen_mouse_button_released_pre") { - if matches!( - mod_hook.screen_mouse_button_released_pre(host, &mut event), - Flow::Cancel - ) { - *canceled = true; - break; - } - } -} -/// What [`Hooks::camera_shake`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct CameraShakeEvent<'a> { - pub partial: f32, - pub shake: &'a mut NativeShake, -} -pub fn dispatch_camera_shake( - host: &dyn Host, - mods: &[Box], - partial: f32, - shake: &mut NativeShake, -) { - let mut event = CameraShakeEvent { partial, shake }; - for mod_hook in super::entered(mods, "camera_shake") { - mod_hook.camera_shake(host, &mut event); - } -} -/// What [`Hooks::shader_params`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ShaderParamsEvent<'a> { - pub partial: f32, - pub params: &'a mut [[f32; 4]; 8], -} -pub fn dispatch_shader_params( - host: &dyn Host, - mods: &[Box], - partial: f32, - params: &mut [[f32; 4]; 8], -) { - let mut event = ShaderParamsEvent { partial, params }; - for mod_hook in super::entered(mods, "shader_params") { - mod_hook.shader_params(host, &mut event); - } -} -/// What [`Hooks::block_highlight`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlockHighlightEvent { - pub pos: [i32; 3], - pub state: BlockStateId, - pub partial: f32, -} -pub fn dispatch_block_highlight( - host: &dyn Host, - mods: &[Box], - pos: [i32; 3], - state: BlockStateId, - partial: f32, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = BlockHighlightEvent { - pos, - state, - partial, - }; - for mod_hook in super::entered(mods, "block_highlight") { - if matches!(mod_hook.block_highlight(host, &mut event), Flow::Cancel) { - *canceled = true; - break; - } - } -} -/// What [`Hooks::render_hand`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct RenderHandEvent<'a> { - pub hand: horizon_client::first_person::Hand, - pub stack: Option<&'a ItemStack>, - pub partial: f32, - pub swing: f32, - pub equip: f32, -} -pub fn dispatch_render_hand( - host: &dyn Host, - mods: &[Box], - hand: horizon_client::first_person::Hand, - stack: Option<&ItemStack>, - partial: f32, - swing: f32, - equip: f32, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = RenderHandEvent { - hand, - stack, - partial, - swing, - equip, - }; - for mod_hook in super::entered(mods, "render_hand") { - if matches!(mod_hook.render_hand(host, &mut event), Flow::Cancel) { - *canceled = true; - break; - } - } -} -/// What [`Hooks::client_resources_reloaded`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ClientResourcesReloadedEvent {} -pub fn dispatch_client_resources_reloaded(host: &mut dyn Host, mods: &[Box]) { - let mut event = ClientResourcesReloadedEvent {}; - for mod_hook in super::entered(mods, "client_resources_reloaded") { - mod_hook.client_resources_reloaded(host, &mut event); - } -} -/// What [`Hooks::item_decorations`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ItemDecorationsEvent<'a> { - pub stack: &'a ItemStack, - pub x: f32, - pub y: f32, - pub batch: &'a mut HudBatch, -} -pub fn dispatch_item_decorations( - host: &dyn Host, - mods: &[Box], - stack: &ItemStack, - x: f32, - y: f32, - batch: &mut HudBatch, -) { - let mut event = ItemDecorationsEvent { stack, x, y, batch }; - for mod_hook in super::entered(mods, "item_decorations") { - mod_hook.item_decorations(host, &mut event); - } -} -/// What [`Hooks::use_duration`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct UseDurationEvent<'a> { - pub stack: &'a ItemStack, - pub duration: i32, -} -pub fn dispatch_use_duration( - host: &dyn Host, - mods: &[Box], - stack: &ItemStack, - duration: &mut i32, -) { - let mut event = UseDurationEvent { - stack, - duration: *duration, - }; - for mod_hook in super::entered(mods, "use_duration") { - mod_hook.use_duration(host, &mut event); - } - *duration = event.duration; -} -/// What [`Hooks::use_animation`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct UseAnimationEvent<'a> { - pub stack: &'a ItemStack, - pub animation: &'a mut UseAnimation, -} -pub fn dispatch_use_animation( - host: &dyn Host, - mods: &[Box], - stack: &ItemStack, - animation: &mut UseAnimation, -) { - let mut event = UseAnimationEvent { stack, animation }; - for mod_hook in super::entered(mods, "use_animation") { - mod_hook.use_animation(host, &mut event); - } -} -/// What [`Hooks::can_eat`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct CanEatEvent<'a> { - pub stack: &'a ItemStack, - pub can_eat: bool, -} -pub fn dispatch_can_eat( - host: &dyn Host, - mods: &[Box], - stack: &ItemStack, - can_eat: &mut bool, -) { - let mut event = CanEatEvent { - stack, - can_eat: *can_eat, - }; - for mod_hook in super::entered(mods, "can_eat") { - mod_hook.can_eat(host, &mut event); - } - *can_eat = event.can_eat; -} -/// What [`Hooks::living_entity_use_item_start`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct LivingEntityUseItemStartEvent<'a> { - pub stack: &'a ItemStack, - pub entity: Option, - pub hand: Hand, - pub duration: i32, -} -pub fn dispatch_living_entity_use_item_start( - host: &mut dyn Host, - mods: &[Box], - stack: &ItemStack, - entity: Option, - hand: Hand, - duration: &mut i32, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = LivingEntityUseItemStartEvent { - stack, - entity, - hand, - duration: *duration, - }; - for mod_hook in super::entered(mods, "living_entity_use_item_start") { - if matches!( - mod_hook.living_entity_use_item_start(host, &mut event), - Flow::Cancel - ) { - *canceled = true; - break; - } - } - *duration = event.duration; -} -/// What [`Hooks::living_entity_use_item_finish`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct LivingEntityUseItemFinishEvent<'a> { - pub stack: &'a ItemStack, - pub entity: Option, - pub hand: Hand, - pub duration: i32, - pub result: &'a mut Option, -} -pub fn dispatch_living_entity_use_item_finish( - host: &mut dyn Host, - mods: &[Box], - stack: &ItemStack, - entity: Option, - hand: Hand, - duration: i32, - result: &mut Option, -) { - let mut event = LivingEntityUseItemFinishEvent { - stack, - entity, - hand, - duration, - result, - }; - for mod_hook in super::entered(mods, "living_entity_use_item_finish") { - mod_hook.living_entity_use_item_finish(host, &mut event); - } -} -/// What [`Hooks::screen_mouse_scrolled_pre`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ScreenMouseScrolledPreEvent<'a> { - pub screen: &'a NativeScreenView<'a>, - pub scroll: [f64; 2], - pub modifiers: u32, -} -pub fn dispatch_screen_mouse_scrolled_pre( - host: &dyn Host, - mods: &[Box], - screen: &NativeScreenView<'_>, - scroll: [f64; 2], - modifiers: u32, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = ScreenMouseScrolledPreEvent { - screen, - scroll, - modifiers, - }; - for mod_hook in super::entered(mods, "screen_mouse_scrolled_pre") { - if matches!( - mod_hook.screen_mouse_scrolled_pre(host, &mut event), - Flow::Cancel - ) { - *canceled = true; - break; - } - } -} -/// What [`Hooks::player_command`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerCommandEvent<'a> { - pub player: u128, - pub name: &'a str, - pub command: &'a str, - pub replies: &'a mut Vec, -} -pub fn dispatch_player_command( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - command: &str, - replies: &mut Vec, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = PlayerCommandEvent { - player, - name, - command, - replies, - }; - for mod_hook in super::entered(mods, "player_command") { - if matches!(mod_hook.player_command(host, &mut event), Flow::Cancel) { - *canceled = true; - break; - } - } -} -/// What [`Hooks::perform_console_command`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PerformConsoleCommandEvent<'a> { - pub command: &'a str, -} -pub fn dispatch_perform_console_command( - host: &mut dyn Host, - mods: &[Box], - command: &str, - ran: &mut bool, -) { - if *ran { - return; - } - let mut event = PerformConsoleCommandEvent { command }; - for mod_hook in super::entered(mods, "perform_console_command") { - if matches!( - mod_hook.perform_console_command(host, &mut event), - Flow::Cancel - ) { - *ran = true; - break; - } - } -} -/// What [`Hooks::screen_key_pressed_pre`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ScreenKeyPressedPreEvent<'a> { - pub screen: &'a NativeScreenView<'a>, - pub key: &'a str, - pub repeat: bool, - pub modifiers: u32, -} -pub fn dispatch_screen_key_pressed_pre( - host: &dyn Host, - mods: &[Box], - screen: &NativeScreenView<'_>, - key: &str, - repeat: bool, - modifiers: u32, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = ScreenKeyPressedPreEvent { - screen, - key, - repeat, - modifiers, - }; - for mod_hook in super::entered(mods, "screen_key_pressed_pre") { - if matches!( - mod_hook.screen_key_pressed_pre(host, &mut event), - Flow::Cancel - ) { - *canceled = true; - break; - } - } -} -/// What [`Hooks::screen_char_typed_pre`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ScreenCharTypedPreEvent<'a> { - pub screen: &'a NativeScreenView<'a>, - pub c: char, - pub modifiers: u32, -} -pub fn dispatch_screen_char_typed_pre( - host: &dyn Host, - mods: &[Box], - screen: &NativeScreenView<'_>, - c: char, - modifiers: u32, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = ScreenCharTypedPreEvent { - screen, - c, - modifiers, - }; - for mod_hook in super::entered(mods, "screen_char_typed_pre") { - if matches!( - mod_hook.screen_char_typed_pre(host, &mut event), - Flow::Cancel - ) { - *canceled = true; - break; - } - } -} -/// What [`Hooks::data_pack_files`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct DataPackFilesEvent<'a> { - pub files: &'a mut Vec<(String, Vec)>, -} -pub fn dispatch_data_pack_files( - host: &dyn Host, - mods: &[Box], - files: &mut Vec<(String, Vec)>, -) { - let mut event = DataPackFilesEvent { files }; - for mod_hook in super::entered(mods, "data_pack_files") { - mod_hook.data_pack_files(host, &mut event); - } -} -/// What [`Hooks::biome_colors`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BiomeColorsEvent<'a> { - pub biome: &'a str, - pub colors: &'a mut BiomeColors, -} -pub fn dispatch_biome_colors( - host: &dyn Host, - mods: &[Box], - biome: &str, - colors: &mut BiomeColors, -) { - let mut event = BiomeColorsEvent { biome, colors }; - for mod_hook in super::entered(mods, "biome_colors") { - mod_hook.biome_colors(host, &mut event); - } -} -/// What [`Hooks::server_data_loaded`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ServerDataLoadedEvent { - pub reload: bool, -} -pub fn dispatch_server_data_loaded(host: &mut dyn Host, mods: &[Box], reload: bool) { - let mut event = ServerDataLoadedEvent { reload }; - for mod_hook in super::entered(mods, "server_data_loaded") { - mod_hook.server_data_loaded(host, &mut event); - } -} -/// What [`Hooks::compute_fov`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ComputeFovEvent { - pub partial: f32, - pub camera: [f64; 3], - pub degrees: f32, -} -pub fn dispatch_compute_fov( - host: &dyn Host, - mods: &[Box], - partial: f32, - camera: [f64; 3], - degrees: &mut f32, -) { - let mut event = ComputeFovEvent { - partial, - camera, - degrees: *degrees, - }; - for mod_hook in super::entered(mods, "compute_fov") { - mod_hook.compute_fov(host, &mut event); - } - *degrees = event.degrees; -} -/// What [`Hooks::mob_submit`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct MobSubmitEvent<'a> { - pub mob: &'a NativeMobRender<'a>, - pub draw: &'a mut BlockEntityDraw, -} -pub fn dispatch_mob_submit( - host: &dyn Host, - mods: &[Box], - mob: &NativeMobRender<'_>, - draw: &mut BlockEntityDraw, - drawn: &mut bool, -) { - if *drawn { - return; - } - let mut event = MobSubmitEvent { mob, draw }; - for mod_hook in super::entered(mods, "mob_submit") { - if matches!(mod_hook.mob_submit(host, &mut event), Flow::Cancel) { - *drawn = true; - break; - } - } -} -/// What [`Hooks::camera_distance`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct CameraDistanceEvent<'a> { - pub partial: f32, - pub mirrored: bool, - pub entity: &'a horizon_client::camera::CameraEntity, - pub distance: f32, -} -pub fn dispatch_camera_distance( - host: &dyn Host, - mods: &[Box], - partial: f32, - mirrored: bool, - entity: &horizon_client::camera::CameraEntity, - distance: &mut f32, -) { - let mut event = CameraDistanceEvent { - partial, - mirrored, - entity, - distance: *distance, - }; - for mod_hook in super::entered(mods, "camera_distance") { - mod_hook.camera_distance(host, &mut event); - } - *distance = event.distance; -} -/// What [`Hooks::compute_fog_color`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ComputeFogColorEvent<'a> { - pub partial: f32, - pub camera: [f64; 3], - pub fog_type: horizon_client::fog::FogType, - pub rgb: &'a mut [f32; 3], -} -pub fn dispatch_compute_fog_color( - host: &dyn Host, - mods: &[Box], - partial: f32, - camera: [f64; 3], - fog_type: horizon_client::fog::FogType, - rgb: &mut [f32; 3], -) { - let mut event = ComputeFogColorEvent { - partial, - camera, - fog_type, - rgb, - }; - for mod_hook in super::entered(mods, "compute_fog_color") { - mod_hook.compute_fog_color(host, &mut event); - } -} -/// What [`Hooks::mouse_button_pre`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct MouseButtonPreEvent { - pub button: i32, - pub pressed: bool, - pub modifiers: u32, -} -pub fn dispatch_mouse_button_pre( - host: &mut dyn Host, - mods: &[Box], - button: i32, - pressed: bool, - modifiers: u32, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = MouseButtonPreEvent { - button, - pressed, - modifiers, - }; - for mod_hook in super::entered(mods, "mouse_button_pre") { - if matches!(mod_hook.mouse_button_pre(host, &mut event), Flow::Cancel) { - *canceled = true; - break; - } - } -} -/// What [`Hooks::mouse_button_post`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct MouseButtonPostEvent { - pub button: i32, - pub pressed: bool, - pub modifiers: u32, -} -pub fn dispatch_mouse_button_post( - host: &mut dyn Host, - mods: &[Box], - button: i32, - pressed: bool, - modifiers: u32, -) { - let mut event = MouseButtonPostEvent { - button, - pressed, - modifiers, - }; - for mod_hook in super::entered(mods, "mouse_button_post") { - mod_hook.mouse_button_post(host, &mut event); - } -} -/// What [`Hooks::player_respawned`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerRespawnedEvent<'a> { - pub player: u128, - pub name: &'a str, -} -pub fn dispatch_player_respawned( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, -) { - let mut event = PlayerRespawnedEvent { player, name }; - for mod_hook in super::entered(mods, "player_respawned") { - mod_hook.player_respawned(host, &mut event); - } -} -/// What [`Hooks::render_fog`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct RenderFogEvent<'a> { - pub partial: f32, - pub camera: [f64; 3], - pub fog_type: horizon_client::fog::FogType, - pub fog: &'a mut horizon_client::fog::FogData, -} -pub fn dispatch_render_fog( - host: &dyn Host, - mods: &[Box], - partial: f32, - camera: [f64; 3], - fog_type: horizon_client::fog::FogType, - fog: &mut horizon_client::fog::FogData, -) { - let mut event = RenderFogEvent { - partial, - camera, - fog_type, - fog, - }; - for mod_hook in super::entered(mods, "render_fog") { - mod_hook.render_fog(host, &mut event); - } -} -/// What [`Hooks::screen_key_released_pre`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ScreenKeyReleasedPreEvent<'a> { - pub screen: &'a NativeScreenView<'a>, - pub key: &'a str, - pub modifiers: u32, -} -pub fn dispatch_screen_key_released_pre( - host: &dyn Host, - mods: &[Box], - screen: &NativeScreenView<'_>, - key: &str, - modifiers: u32, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = ScreenKeyReleasedPreEvent { - screen, - key, - modifiers, - }; - for mod_hook in super::entered(mods, "screen_key_released_pre") { - if matches!( - mod_hook.screen_key_released_pre(host, &mut event), - Flow::Cancel - ) { - *canceled = true; - break; - } - } -} -/// What [`Hooks::music`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct MusicEvent<'a> { - pub music: &'a mut Option, - pub volume: f32, -} -pub fn dispatch_music( - host: &dyn Host, - mods: &[Box], - music: &mut Option, - volume: &mut f32, -) { - let mut event = MusicEvent { - music, - volume: *volume, - }; - for mod_hook in super::entered(mods, "music") { - mod_hook.music(host, &mut event); - } - *volume = event.volume; -} -/// What [`Hooks::music_layers`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct MusicLayersEvent<'a> { - pub layers: &'a mut Vec, -} -pub fn dispatch_music_layers( - host: &dyn Host, - mods: &[Box], - layers: &mut Vec, -) { - let mut event = MusicLayersEvent { layers }; - for mod_hook in super::entered(mods, "music_layers") { - mod_hook.music_layers(host, &mut event); - } -} -/// What [`Hooks::datapack_sync`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct DatapackSyncEvent<'a> { - pub recipe_types: &'a mut Vec, -} -pub fn dispatch_datapack_sync( - host: &mut dyn Host, - mods: &[Box], - recipe_types: &mut Vec, -) { - let mut event = DatapackSyncEvent { recipe_types }; - for mod_hook in super::entered(mods, "datapack_sync") { - mod_hook.datapack_sync(host, &mut event); - } -} -/// What [`Hooks::recipes_received`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct RecipesReceivedEvent<'a> { - pub recipe_types: &'a [String], -} -pub fn dispatch_recipes_received( - host: &mut dyn Host, - mods: &[Box], - recipe_types: &[String], -) { - let mut event = RecipesReceivedEvent { recipe_types }; - for mod_hook in super::entered(mods, "recipes_received") { - mod_hook.recipes_received(host, &mut event); - } -} -/// What [`Hooks::input_key`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct InputKeyEvent<'a> { - pub event: &'a winit::event::KeyEvent, - pub modifiers: u32, -} -pub fn dispatch_input_key( - host: &mut dyn Host, - mods: &[Box], - event: &winit::event::KeyEvent, - modifiers: u32, -) { - let mut event_ = InputKeyEvent { event, modifiers }; - for mod_hook in super::entered(mods, "input_key") { - mod_hook.input_key(host, &mut event_); - } -} -/// What [`Hooks::mouse_scrolling`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct MouseScrollingEvent { - pub scroll: [f64; 2], - pub accumulated: [i32; 2], - pub buttons: [bool; 3], - pub cursor: [f32; 2], - pub modifiers: u32, -} -pub fn dispatch_mouse_scrolling( - host: &mut dyn Host, - mods: &[Box], - scroll: [f64; 2], - accumulated: [i32; 2], - buttons: [bool; 3], - cursor: [f32; 2], - modifiers: u32, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = MouseScrollingEvent { - scroll, - accumulated, - buttons, - cursor, - modifiers, - }; - for mod_hook in super::entered(mods, "mouse_scrolling") { - if matches!(mod_hook.mouse_scrolling(host, &mut event), Flow::Cancel) { - *canceled = true; - break; - } - } -} -/// What [`Hooks::screen_mouse_dragged_pre`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ScreenMouseDraggedPreEvent<'a> { - pub screen: &'a NativeScreenView<'a>, - pub button: i32, - pub delta: [f32; 2], - pub modifiers: u32, -} -pub fn dispatch_screen_mouse_dragged_pre( - host: &dyn Host, - mods: &[Box], - screen: &NativeScreenView<'_>, - button: i32, - delta: [f32; 2], - modifiers: u32, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = ScreenMouseDraggedPreEvent { - screen, - button, - delta, - modifiers, - }; - for mod_hook in super::entered(mods, "screen_mouse_dragged_pre") { - if matches!( - mod_hook.screen_mouse_dragged_pre(host, &mut event), - Flow::Cancel - ) { - *canceled = true; - break; - } - } -} -/// What [`Hooks::mod_container_viewers`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ModContainerViewersEvent<'a> { - pub dimension: &'a str, - pub pos: [i32; 3], - pub viewers: u32, - pub previous: u32, -} -pub fn dispatch_mod_container_viewers( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - pos: [i32; 3], - viewers: u32, - previous: u32, -) { - let mut event = ModContainerViewersEvent { - dimension, - pos, - viewers, - previous, - }; - for mod_hook in super::entered(mods, "mod_container_viewers") { - mod_hook.mod_container_viewers(host, &mut event); - } -} -/// What [`Hooks::living_knockback`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct LivingKnockbackEvent<'a> { - pub entity: i32, - pub kind: &'a str, - pub damage_type: &'a str, - pub causing: i32, - pub direct: i32, - pub strength: f64, - pub x: f64, - pub z: f64, -} -pub fn dispatch_living_knockback( - mods: &[Box], - entity: i32, - kind: &str, - damage_type: &str, - causing: i32, - direct: i32, - strength: &mut f64, - x: &mut f64, - z: &mut f64, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = LivingKnockbackEvent { - entity, - kind, - damage_type, - causing, - direct, - strength: *strength, - x: *x, - z: *z, - }; - for mod_hook in super::entered(mods, "living_knockback") { - if matches!(mod_hook.living_knockback(&mut event), Flow::Cancel) { - *canceled = true; - break; - } - } - *strength = event.strength; - *x = event.x; - *z = event.z; -} -/// What [`Hooks::experience_orb_can_merge`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ExperienceOrbCanMergeEvent<'a> { - pub dimension: &'a str, - pub into: u32, - pub from: u32, - pub into_value: u32, - pub from_value: u32, - pub can_merge: bool, -} -pub fn dispatch_experience_orb_can_merge( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - into: u32, - from: u32, - into_value: u32, - from_value: u32, - can_merge: &mut bool, -) { - let mut event = ExperienceOrbCanMergeEvent { - dimension, - into, - from, - into_value, - from_value, - can_merge: *can_merge, - }; - for mod_hook in super::entered(mods, "experience_orb_can_merge") { - mod_hook.experience_orb_can_merge(host, &mut event); - } - *can_merge = event.can_merge; -} -/// What [`Hooks::experience_orb_merge`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ExperienceOrbMergeEvent<'a> { - pub dimension: &'a str, - pub into: u32, - pub from: u32, - pub from_value: u32, -} -pub fn dispatch_experience_orb_merge( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - into: u32, - from: u32, - from_value: u32, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = ExperienceOrbMergeEvent { - dimension, - into, - from, - from_value, - }; - for mod_hook in super::entered(mods, "experience_orb_merge") { - if matches!( - mod_hook.experience_orb_merge(host, &mut event), - Flow::Cancel - ) { - *canceled = true; - break; - } - } -} -/// What [`Hooks::menu_slot_rules`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct MenuSlotRulesEvent<'a> { - pub menu: &'a str, - pub slot: usize, - pub stack: &'a ItemStack, - pub may_place: bool, - pub max_stack: u32, -} -pub fn dispatch_menu_slot_rules( - mods: &[Box], - menu: &str, - slot: usize, - stack: &ItemStack, - may_place: &mut bool, - max_stack: &mut u32, -) { - let mut event = MenuSlotRulesEvent { - menu, - slot, - stack, - may_place: *may_place, - max_stack: *max_stack, - }; - for mod_hook in super::entered(mods, "menu_slot_rules") { - mod_hook.menu_slot_rules(&mut event); - } - *may_place = event.may_place; - *max_stack = event.max_stack; -} -/// What [`Hooks::living_entity_use_item_tick`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct LivingEntityUseItemTickEvent<'a> { - pub stack: &'a ItemStack, - pub entity: Option, - pub hand: Hand, - pub duration: i32, -} -pub fn dispatch_living_entity_use_item_tick( - host: &mut dyn Host, - mods: &[Box], - stack: &ItemStack, - entity: Option, - hand: Hand, - duration: &mut i32, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = LivingEntityUseItemTickEvent { - stack, - entity, - hand, - duration: *duration, - }; - for mod_hook in super::entered(mods, "living_entity_use_item_tick") { - if matches!( - mod_hook.living_entity_use_item_tick(host, &mut event), - Flow::Cancel - ) { - *canceled = true; - break; - } - } - *duration = event.duration; -} -/// What [`Hooks::on_use_tick`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct OnUseTickEvent<'a> { - pub stack: &'a ItemStack, - pub entity: Option, - pub remaining: i32, -} -pub fn dispatch_on_use_tick( - host: &mut dyn Host, - mods: &[Box], - stack: &ItemStack, - entity: Option, - remaining: i32, -) { - let mut event = OnUseTickEvent { - stack, - entity, - remaining, - }; - for mod_hook in super::entered(mods, "on_use_tick") { - mod_hook.on_use_tick(host, &mut event); - } -} -/// What [`Hooks::living_entity_use_item_stop`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct LivingEntityUseItemStopEvent<'a> { - pub stack: &'a ItemStack, - pub entity: Option, - pub hand: Hand, - pub duration: i32, -} -pub fn dispatch_living_entity_use_item_stop( - host: &mut dyn Host, - mods: &[Box], - stack: &ItemStack, - entity: Option, - hand: Hand, - duration: i32, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = LivingEntityUseItemStopEvent { - stack, - entity, - hand, - duration, - }; - for mod_hook in super::entered(mods, "living_entity_use_item_stop") { - if matches!( - mod_hook.living_entity_use_item_stop(host, &mut event), - Flow::Cancel - ) { - *canceled = true; - break; - } - } -} -/// What [`Hooks::release_using`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ReleaseUsingEvent<'a> { - pub stack: &'a ItemStack, - pub entity: Option, - pub time_left: i32, - pub used: bool, -} -pub fn dispatch_release_using( - host: &mut dyn Host, - mods: &[Box], - stack: &ItemStack, - entity: Option, - time_left: i32, - used: &mut bool, -) { - let mut event = ReleaseUsingEvent { - stack, - entity, - time_left, - used: *used, - }; - for mod_hook in super::entered(mods, "release_using") { - mod_hook.release_using(host, &mut event); - } - *used = event.used; -} -/// What [`Hooks::loot_function`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct LootFunctionEvent<'a> { - pub kind: &'a str, - pub data: &'a Value, - pub ctx: &'a NativeLootContext<'a>, - pub stack: &'a mut LootStack, -} -pub fn dispatch_loot_function( - mods: &[Box], - kind: &str, - data: &Value, - ctx: &NativeLootContext<'_>, - stack: &mut LootStack, -) { - let mut event = LootFunctionEvent { - kind, - data, - ctx, - stack, - }; - for mod_hook in super::entered(mods, "loot_function") { - mod_hook.loot_function(&mut event); - } -} -/// What [`Hooks::loot_condition`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct LootConditionEvent<'a> { - pub kind: &'a str, - pub data: &'a Value, - pub ctx: &'a NativeLootContext<'a>, - pub holds: bool, -} -pub fn dispatch_loot_condition( - mods: &[Box], - kind: &str, - data: &Value, - ctx: &NativeLootContext<'_>, - holds: &mut bool, -) { - let mut event = LootConditionEvent { - kind, - data, - ctx, - holds: *holds, - }; - for mod_hook in super::entered(mods, "loot_condition") { - mod_hook.loot_condition(&mut event); - } - *holds = event.holds; -} -/// What [`Hooks::level_load`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct LevelLoadEvent<'a> { - pub dimension: &'a str, -} -pub fn dispatch_level_load(host: &mut dyn Host, mods: &[Box], dimension: &str) { - let mut event = LevelLoadEvent { dimension }; - for mod_hook in super::entered(mods, "level_load") { - mod_hook.level_load(host, &mut event); - } -} -/// What [`Hooks::level_unload`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct LevelUnloadEvent<'a> { - pub dimension: &'a str, -} -pub fn dispatch_level_unload(host: &mut dyn Host, mods: &[Box], dimension: &str) { - let mut event = LevelUnloadEvent { dimension }; - for mod_hook in super::entered(mods, "level_unload") { - mod_hook.level_unload(host, &mut event); - } -} -/// What [`Hooks::client_level_load`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ClientLevelLoadEvent<'a> { - pub dimension: &'a str, -} -pub fn dispatch_client_level_load(host: &mut dyn Host, mods: &[Box], dimension: &str) { - let mut event = ClientLevelLoadEvent { dimension }; - for mod_hook in super::entered(mods, "client_level_load") { - mod_hook.client_level_load(host, &mut event); - } -} -/// What [`Hooks::client_level_unload`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ClientLevelUnloadEvent<'a> { - pub dimension: &'a str, -} -pub fn dispatch_client_level_unload(host: &mut dyn Host, mods: &[Box], dimension: &str) { - let mut event = ClientLevelUnloadEvent { dimension }; - for mod_hook in super::entered(mods, "client_level_unload") { - mod_hook.client_level_unload(host, &mut event); - } -} -/// What [`Hooks::hears_projectile_impacts`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct HearsProjectileImpactsEvent { - pub hears: bool, -} -pub fn dispatch_hears_projectile_impacts( - host: &dyn Host, - mods: &[Box], - hears: &mut bool, -) { - let mut event = HearsProjectileImpactsEvent { hears: *hears }; - for mod_hook in super::entered(mods, "hears_projectile_impacts") { - mod_hook.hears_projectile_impacts(host, &mut event); - } - *hears = event.hears; -} -/// What [`Hooks::projectile_impact`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ProjectileImpactEvent<'a> { - pub dimension: &'a str, - pub projectile: u32, - pub kind: &'a str, - pub owner: u32, - pub entity: Option, - pub entity_kind: &'a str, - pub block: [i32; 3], - pub face: [i32; 3], - pub at: [f64; 3], -} -pub fn dispatch_projectile_impact( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - projectile: u32, - kind: &str, - owner: u32, - entity: Option, - entity_kind: &str, - block: [i32; 3], - face: [i32; 3], - at: [f64; 3], - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = ProjectileImpactEvent { - dimension, - projectile, - kind, - owner, - entity, - entity_kind, - block, - face, - at, - }; - for mod_hook in super::entered(mods, "projectile_impact") { - if matches!(mod_hook.projectile_impact(host, &mut event), Flow::Cancel) { - *canceled = true; - break; - } - } -} -/// What [`Hooks::entity_mount`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct EntityMountEvent<'a> { - pub dimension: &'a str, - pub entity: u32, - pub kind: &'a str, - pub vehicle: u32, - pub vehicle_kind: &'a str, - pub mounting: bool, -} -pub fn dispatch_entity_mount( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - entity: u32, - kind: &str, - vehicle: u32, - vehicle_kind: &str, - mounting: bool, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = EntityMountEvent { - dimension, - entity, - kind, - vehicle, - vehicle_kind, - mounting, - }; - for mod_hook in super::entered(mods, "entity_mount") { - if matches!(mod_hook.entity_mount(host, &mut event), Flow::Cancel) { - *canceled = true; - break; - } - } -} -/// What [`Hooks::interaction_key`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct InteractionKeyEvent<'a> { - pub button: i32, - pub key_mapping: &'a str, - pub hand: horizon_client::first_person::Hand, - pub swing_hand: bool, -} -pub fn dispatch_interaction_key( - host: &mut dyn Host, - mods: &[Box], - button: i32, - key_mapping: &str, - hand: horizon_client::first_person::Hand, - swing_hand: &mut bool, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = InteractionKeyEvent { - button, - key_mapping, - hand, - swing_hand: *swing_hand, - }; - for mod_hook in super::entered(mods, "interaction_key") { - if matches!(mod_hook.interaction_key(host, &mut event), Flow::Cancel) { - *canceled = true; - break; - } - } - *swing_hand = event.swing_hand; -} -/// What [`Hooks::gui_layer_pre`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct GuiLayerPreEvent<'a> { - pub layer: &'a str, - pub batch: &'a mut HudBatch, -} -pub fn dispatch_gui_layer_pre( - host: &mut dyn Host, - mods: &[Box], - layer: &str, - batch: &mut HudBatch, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = GuiLayerPreEvent { layer, batch }; - for mod_hook in super::entered(mods, "gui_layer_pre") { - if matches!(mod_hook.gui_layer_pre(host, &mut event), Flow::Cancel) { - *canceled = true; - break; - } - } -} -/// What [`Hooks::gui_layer_post`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct GuiLayerPostEvent<'a> { - pub layer: &'a str, - pub batch: &'a mut HudBatch, -} -pub fn dispatch_gui_layer_post( - host: &mut dyn Host, - mods: &[Box], - layer: &str, - batch: &mut HudBatch, -) { - let mut event = GuiLayerPostEvent { layer, batch }; - for mod_hook in super::entered(mods, "gui_layer_post") { - mod_hook.gui_layer_post(host, &mut event); - } -} -/// What [`Hooks::map_decorations`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct MapDecorationsEvent<'a> { - pub map: &'a NativeMapInfo<'a>, - pub decorations: &'a mut Vec, -} -pub fn dispatch_map_decorations( - host: &dyn Host, - mods: &[Box], - map: &NativeMapInfo<'_>, - decorations: &mut Vec, -) { - let mut event = MapDecorationsEvent { map, decorations }; - for mod_hook in super::entered(mods, "map_decorations") { - mod_hook.map_decorations(host, &mut event); - } -} -/// What [`Hooks::player_start_tracking`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerStartTrackingEvent<'a> { - pub player: u128, - pub name: &'a str, - pub entity: u32, - pub uuid: u128, - pub kind: &'a str, -} -pub fn dispatch_player_start_tracking( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - entity: u32, - uuid: u128, - kind: &str, -) { - let mut event = PlayerStartTrackingEvent { - player, - name, - entity, - uuid, - kind, - }; - for mod_hook in super::entered(mods, "player_start_tracking") { - mod_hook.player_start_tracking(host, &mut event); - } -} -/// What [`Hooks::player_stop_tracking`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerStopTrackingEvent<'a> { - pub player: u128, - pub name: &'a str, - pub entity: u32, - pub uuid: u128, - pub kind: &'a str, -} -pub fn dispatch_player_stop_tracking( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - entity: u32, - uuid: u128, - kind: &str, -) { - let mut event = PlayerStopTrackingEvent { - player, - name, - entity, - uuid, - kind, - }; - for mod_hook in super::entered(mods, "player_stop_tracking") { - mod_hook.player_stop_tracking(host, &mut event); - } -} -/// What [`Hooks::block_entity_energy`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlockEntityEnergyEvent<'a> { - pub block: &'a str, -} -pub fn dispatch_block_entity_energy( - host: &dyn Host, - mods: &[Box], - block: &str, - energy: &mut Option, -) { - if energy.is_some() { - return; - } - let mut event = BlockEntityEnergyEvent { block }; - for mod_hook in super::entered(mods, "block_entity_energy") { - if let Some(replacement) = mod_hook.block_entity_energy(host, &mut event) { - *energy = Some(replacement); - break; - } - } -} -/// What [`Hooks::data_map_types`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct DataMapTypesEvent<'a> { - pub types: &'a mut Vec, -} -pub fn dispatch_data_map_types( - host: &dyn Host, - mods: &[Box], - types: &mut Vec, -) { - let mut event = DataMapTypesEvent { types }; - for mod_hook in super::entered(mods, "data_map_types") { - mod_hook.data_map_types(host, &mut event); - } -} -/// What [`Hooks::living_breathe`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct LivingBreatheEvent<'a> { - pub entity: u32, - pub kind: &'a str, - pub can_breathe: bool, - pub consume: i32, - pub refill: i32, -} -pub fn dispatch_living_breathe( - mods: &[Box], - entity: u32, - kind: &str, - can_breathe: &mut bool, - consume: &mut i32, - refill: &mut i32, -) { - let mut event = LivingBreatheEvent { - entity, - kind, - can_breathe: *can_breathe, - consume: *consume, - refill: *refill, - }; - for mod_hook in super::entered(mods, "living_breathe") { - mod_hook.living_breathe(&mut event); - } - *can_breathe = event.can_breathe; - *consume = event.consume; - *refill = event.refill; -} -/// What [`Hooks::blocks`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlocksEvent<'a> { - pub blocks: &'a mut Vec, -} -pub fn dispatch_blocks(mods: &[Box], blocks: &mut Vec) { - let mut event = BlocksEvent { blocks }; - for mod_hook in super::entered(mods, "blocks") { - mod_hook.blocks(&mut event); - } -} -/// What [`Hooks::items`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ItemsEvent<'a> { - pub items: &'a mut Vec, -} -pub fn dispatch_items(mods: &[Box], items: &mut Vec) { - let mut event = ItemsEvent { items }; - for mod_hook in super::entered(mods, "items") { - mod_hook.items(&mut event); - } -} -/// What [`Hooks::entity_tick_pre`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct EntityTickPreEvent<'a> { - pub dimension: &'a str, - pub entity: u32, - pub kind: &'a str, -} -pub fn dispatch_entity_tick_pre( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - entity: u32, - kind: &str, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = EntityTickPreEvent { - dimension, - entity, - kind, - }; - for mod_hook in super::entered(mods, "entity_tick_pre") { - if matches!(mod_hook.entity_tick_pre(host, &mut event), Flow::Cancel) { - *canceled = true; - break; - } - } -} -/// What [`Hooks::entity_tick_post`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct EntityTickPostEvent<'a> { - pub dimension: &'a str, - pub entity: u32, - pub kind: &'a str, -} -pub fn dispatch_entity_tick_post( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - entity: u32, - kind: &str, -) { - let mut event = EntityTickPostEvent { - dimension, - entity, - kind, - }; - for mod_hook in super::entered(mods, "entity_tick_post") { - mod_hook.entity_tick_post(host, &mut event); - } -} -/// What [`Hooks::entity_tick_listened`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct EntityTickListenedEvent { - pub listened: bool, -} -pub fn dispatch_entity_tick_listened( - host: &mut dyn Host, - mods: &[Box], - listened: &mut bool, -) { - let mut event = EntityTickListenedEvent { - listened: *listened, - }; - for mod_hook in super::entered(mods, "entity_tick_listened") { - mod_hook.entity_tick_listened(host, &mut event); - } - *listened = event.listened; -} -/// What [`Hooks::item_fluid_tank`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ItemFluidTankEvent<'a> { - pub item: &'a str, -} -pub fn dispatch_item_fluid_tank( - host: &dyn Host, - mods: &[Box], - item: &str, - capacity: &mut Option, -) { - if capacity.is_some() { - return; - } - let mut event = ItemFluidTankEvent { item }; - for mod_hook in super::entered(mods, "item_fluid_tank") { - if let Some(replacement) = mod_hook.item_fluid_tank(host, &mut event) { - *capacity = Some(replacement); - break; - } - } -} -/// What [`Hooks::living_equipment_change`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct LivingEquipmentChangeEvent<'a> { - pub entity: u32, - pub kind: &'a str, - pub slot: &'a str, - pub from: Option<&'a ItemStack>, - pub to: Option<&'a ItemStack>, -} -pub fn dispatch_living_equipment_change( - host: &mut dyn Host, - mods: &[Box], - entity: u32, - kind: &str, - slot: &str, - from: Option<&ItemStack>, - to: Option<&ItemStack>, -) { - let mut event = LivingEquipmentChangeEvent { - entity, - kind, - slot, - from, - to, - }; - for mod_hook in super::entered(mods, "living_equipment_change") { - mod_hook.living_equipment_change(host, &mut event); - } -} -/// What [`Hooks::living_equipment_listened`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct LivingEquipmentListenedEvent { - pub listened: bool, -} -pub fn dispatch_living_equipment_listened( - host: &mut dyn Host, - mods: &[Box], - listened: &mut bool, -) { - let mut event = LivingEquipmentListenedEvent { - listened: *listened, - }; - for mod_hook in super::entered(mods, "living_equipment_listened") { - mod_hook.living_equipment_listened(host, &mut event); - } - *listened = event.listened; -} -/// What [`Hooks::player_submit`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerSubmitEvent<'a> { - pub player: &'a NativeMobRender<'a>, - pub who: &'a NativePlayerRender<'a>, - pub draw: &'a mut BlockEntityDraw, -} -pub fn dispatch_player_submit( - host: &dyn Host, - mods: &[Box], - player: &NativeMobRender<'_>, - who: &NativePlayerRender<'_>, - draw: &mut BlockEntityDraw, - drawn: &mut bool, -) { - if *drawn { - return; - } - let mut event = PlayerSubmitEvent { player, who, draw }; - for mod_hook in super::entered(mods, "player_submit") { - if matches!(mod_hook.player_submit(host, &mut event), Flow::Cancel) { - *drawn = true; - break; - } - } -} -/// What [`Hooks::mob_layer`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct MobLayerEvent<'a> { - pub mob: &'a NativeMobRender<'a>, - pub who: Option<&'a NativePlayerRender<'a>>, - pub draw: &'a mut BlockEntityDraw, -} -pub fn dispatch_mob_layer( - host: &dyn Host, - mods: &[Box], - mob: &NativeMobRender<'_>, - who: Option<&NativePlayerRender<'_>>, - draw: &mut BlockEntityDraw, -) { - let mut event = MobLayerEvent { mob, who, draw }; - for mod_hook in super::entered(mods, "mob_layer") { - mod_hook.mob_layer(host, &mut event); - } -} -/// What [`Hooks::player_render_listened`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerRenderListenedEvent { - pub listened: bool, -} -pub fn dispatch_player_render_listened( - host: &dyn Host, - mods: &[Box], - listened: &mut bool, -) { - let mut event = PlayerRenderListenedEvent { - listened: *listened, - }; - for mod_hook in super::entered(mods, "player_render_listened") { - mod_hook.player_render_listened(host, &mut event); - } - *listened = event.listened; -} -/// What [`Hooks::sign_changed`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct SignChangedEvent<'a> { - pub player: u128, - pub name: &'a str, - pub dimension: &'a str, - pub pos: [i32; 3], - pub front: bool, - pub lines: &'a mut Vec, -} -pub fn dispatch_sign_changed( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - dimension: &str, - pos: [i32; 3], - front: bool, - lines: &mut Vec, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = SignChangedEvent { - player, - name, - dimension, - pos, - front, - lines, - }; - for mod_hook in super::entered(mods, "sign_changed") { - if matches!(mod_hook.sign_changed(host, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::standalone_models`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct StandaloneModelsEvent<'a> { - pub models: &'a mut Vec, -} -pub fn dispatch_standalone_models(mods: &[Box], models: &mut Vec) { - let mut event = StandaloneModelsEvent { models }; - for mod_hook in super::entered(mods, "standalone_models") { - mod_hook.standalone_models(&mut event); - } -} -/// What [`Hooks::player_container_open`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerContainerOpenEvent<'a> { - pub player: u128, - pub name: &'a str, - pub dimension: &'a str, - pub menu: &'a str, - pub pos: Option<[i32; 3]>, -} -pub fn dispatch_player_container_open( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - dimension: &str, - menu: &str, - pos: Option<[i32; 3]>, -) { - let mut event = PlayerContainerOpenEvent { - player, - name, - dimension, - menu, - pos, - }; - for mod_hook in super::entered(mods, "player_container_open") { - mod_hook.player_container_open(host, &mut event); - } -} -/// What [`Hooks::player_container_close`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerContainerCloseEvent<'a> { - pub player: u128, - pub name: &'a str, - pub dimension: &'a str, - pub menu: &'a str, - pub pos: Option<[i32; 3]>, -} -pub fn dispatch_player_container_close( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - dimension: &str, - menu: &str, - pos: Option<[i32; 3]>, -) { - let mut event = PlayerContainerCloseEvent { - player, - name, - dimension, - menu, - pos, - }; - for mod_hook in super::entered(mods, "player_container_close") { - mod_hook.player_container_close(host, &mut event); - } -} -/// What [`Hooks::player_toggle_flight`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerToggleFlightEvent<'a> { - pub player: u128, - pub name: &'a str, - pub flying: bool, -} -pub fn dispatch_player_toggle_flight( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - flying: bool, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = PlayerToggleFlightEvent { - player, - name, - flying, - }; - for mod_hook in super::entered(mods, "player_toggle_flight") { - if matches!( - mod_hook.player_toggle_flight(host, &mut event), - Flow::Cancel - ) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::anvil_damaged`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct AnvilDamagedEvent<'a> { - pub dimension: &'a str, - pub pos: [i32; 3], - pub from: &'a str, - pub to: &'a str, -} -pub fn dispatch_anvil_damaged( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - pos: [i32; 3], - from: &str, - to: &str, -) { - let mut event = AnvilDamagedEvent { - dimension, - pos, - from, - to, - }; - for mod_hook in super::entered(mods, "anvil_damaged") { - mod_hook.anvil_damaged(host, &mut event); - } -} -/// What [`Hooks::arrow_loose`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ArrowLooseEvent<'a> { - pub stack: &'a ItemStack, - pub has_ammo: bool, - pub charge: i32, -} -pub fn dispatch_arrow_loose( - host: &mut dyn Host, - mods: &[Box], - stack: &ItemStack, - has_ammo: bool, - charge: &mut i32, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = ArrowLooseEvent { - stack, - has_ammo, - charge: *charge, - }; - for mod_hook in super::entered(mods, "arrow_loose") { - if matches!(mod_hook.arrow_loose(host, &mut event), Flow::Cancel) { - *canceled = true; - break; - } - } - *charge = event.charge; -} -/// What [`Hooks::beacon_state_change`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BeaconStateChangeEvent<'a> { - pub dimension: &'a str, - pub pos: [i32; 3], - pub active: bool, -} -pub fn dispatch_beacon_state_change( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - pos: [i32; 3], - active: bool, -) { - let mut event = BeaconStateChangeEvent { - dimension, - pos, - active, - }; - for mod_hook in super::entered(mods, "beacon_state_change") { - mod_hook.beacon_state_change(host, &mut event); - } -} -/// What [`Hooks::bell_ring`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BellRingEvent { - pub dimension: LevelDimension, - pub player: Option, - pub pos: BlockPos, -} -pub fn dispatch_bell_ring( - level: &dyn LevelHost, - mods: &[Box], - dimension: LevelDimension, - player: Option, - pos: BlockPos, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = BellRingEvent { - dimension, - player, - pos, - }; - for mod_hook in super::entered(mods, "bell_ring") { - if matches!(mod_hook.bell_ring(level, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::block_break_progress_update`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlockBreakProgressUpdateEvent<'a> { - pub player: u128, - pub name: &'a str, - pub pos: [i32; 3], - pub progress: i32, -} -pub fn dispatch_block_break_progress_update( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - pos: [i32; 3], - progress: i32, -) { - let mut event = BlockBreakProgressUpdateEvent { - player, - name, - pos, - progress, - }; - for mod_hook in super::entered(mods, "block_break_progress_update") { - mod_hook.block_break_progress_update(host, &mut event); - } -} -/// What [`Hooks::block_burn`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlockBurnEvent { - pub dimension: LevelDimension, - pub pos: BlockPos, -} -pub fn dispatch_block_burn( - level: &dyn LevelHost, - mods: &[Box], - dimension: LevelDimension, - pos: BlockPos, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = BlockBurnEvent { dimension, pos }; - for mod_hook in super::entered(mods, "block_burn") { - if matches!(mod_hook.block_burn(level, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::block_cook`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlockCookEvent<'a> { - pub pos: [i32; 3], - pub item: &'a str, - pub result: &'a str, -} -pub fn dispatch_block_cook( - host: &dyn Host, - mods: &[Box], - pos: [i32; 3], - item: &str, - result: &str, -) { - let mut event = BlockCookEvent { pos, item, result }; - for mod_hook in super::entered(mods, "block_cook") { - mod_hook.block_cook(host, &mut event); - } -} -/// What [`Hooks::block_damage_abort`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlockDamageAbortEvent<'a> { - pub player: u128, - pub name: &'a str, - pub pos: [i32; 3], -} -pub fn dispatch_block_damage_abort( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - pos: [i32; 3], -) { - let mut event = BlockDamageAbortEvent { player, name, pos }; - for mod_hook in super::entered(mods, "block_damage_abort") { - mod_hook.block_damage_abort(host, &mut event); - } -} -/// What [`Hooks::block_dispense_armor`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlockDispenseArmorEvent<'a> { - pub dimension: &'a str, - pub pos: [i32; 3], - pub item: &'a str, - pub target: &'a str, -} -pub fn dispatch_block_dispense_armor( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - pos: [i32; 3], - item: &str, - target: &str, -) { - let mut event = BlockDispenseArmorEvent { - dimension, - pos, - item, - target, - }; - for mod_hook in super::entered(mods, "block_dispense_armor") { - mod_hook.block_dispense_armor(host, &mut event); - } -} -/// What [`Hooks::block_exp`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlockExpEvent<'a> { - pub dimension: LevelDimension, - pub pos: BlockPos, - pub block: &'a str, - pub experience: u32, -} -pub fn dispatch_block_exp( - level: &dyn LevelHost, - mods: &[Box], - dimension: LevelDimension, - pos: BlockPos, - block: &str, - experience: &mut u32, -) { - let mut event = BlockExpEvent { - dimension, - pos, - block, - experience: *experience, - }; - for mod_hook in super::entered(mods, "block_exp") { - mod_hook.block_exp(level, &mut event); - } - *experience = event.experience; -} -/// What [`Hooks::block_fade`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlockFadeEvent { - pub dimension: LevelDimension, - pub pos: BlockPos, - pub from: BlockStateId, - pub to: BlockStateId, -} -pub fn dispatch_block_fade( - level: &dyn LevelHost, - mods: &[Box], - dimension: LevelDimension, - pos: BlockPos, - from: BlockStateId, - to: BlockStateId, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = BlockFadeEvent { - dimension, - pos, - from, - to, - }; - for mod_hook in super::entered(mods, "block_fade") { - if matches!(mod_hook.block_fade(level, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::block_failed_dispense`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlockFailedDispenseEvent<'a> { - pub dimension: &'a str, - pub pos: [i32; 3], - pub item: &'a str, -} -pub fn dispatch_block_failed_dispense( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - pos: [i32; 3], - item: &str, -) { - let mut event = BlockFailedDispenseEvent { - dimension, - pos, - item, - }; - for mod_hook in super::entered(mods, "block_failed_dispense") { - mod_hook.block_failed_dispense(host, &mut event); - } -} -/// What [`Hooks::block_form`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlockFormEvent { - pub dimension: LevelDimension, - pub pos: BlockPos, - pub from: BlockStateId, - pub to: BlockStateId, -} -pub fn dispatch_block_form( - level: &dyn LevelHost, - mods: &[Box], - dimension: LevelDimension, - pos: BlockPos, - from: BlockStateId, - to: BlockStateId, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = BlockFormEvent { - dimension, - pos, - from, - to, - }; - for mod_hook in super::entered(mods, "block_form") { - if matches!(mod_hook.block_form(level, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::block_ignite`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlockIgniteEvent<'a> { - pub dimension: LevelDimension, - pub player: Option, - pub pos: BlockPos, - pub cause: &'a str, -} -pub fn dispatch_block_ignite( - level: &dyn LevelHost, - mods: &[Box], - dimension: LevelDimension, - player: Option, - pos: BlockPos, - cause: &str, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = BlockIgniteEvent { - dimension, - player, - pos, - cause, - }; - for mod_hook in super::entered(mods, "block_ignite") { - if matches!(mod_hook.block_ignite(level, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::block_lock_check`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlockLockCheckEvent<'a> { - pub player: u128, - pub name: &'a str, - pub pos: [i32; 3], -} -pub fn dispatch_block_lock_check( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - pos: [i32; 3], - locked: &mut bool, -) { - if *locked { - return; - } - let mut event = BlockLockCheckEvent { player, name, pos }; - for mod_hook in super::entered(mods, "block_lock_check") { - if matches!(mod_hook.block_lock_check(host, &mut event), Flow::Cancel) { - *locked = true; - break; - } - } -} -/// What [`Hooks::block_receive_game`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlockReceiveGameEvent<'a> { - pub dimension: &'a str, - pub pos: [i32; 3], - pub block: &'a str, - pub event: &'a str, -} -pub fn dispatch_block_receive_game( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - pos: [i32; 3], - block: &str, - event: &str, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event_ = BlockReceiveGameEvent { - dimension, - pos, - block, - event, - }; - for mod_hook in super::entered(mods, "block_receive_game") { - if matches!(mod_hook.block_receive_game(host, &mut event_), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::block_shear_entity`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BlockShearEntityEvent<'a> { - pub dimension: &'a str, - pub pos: [i32; 3], - pub entity: u32, - pub kind: &'a str, -} -pub fn dispatch_block_shear_entity( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - pos: [i32; 3], - entity: u32, - kind: &str, -) { - let mut event = BlockShearEntityEvent { - dimension, - pos, - entity, - kind, - }; - for mod_hook in super::entered(mods, "block_shear_entity") { - mod_hook.block_shear_entity(host, &mut event); - } -} -/// What [`Hooks::brewing_complete`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BrewingCompleteEvent { - pub pos: [i32; 3], -} -pub fn dispatch_brewing_complete(host: &dyn Host, mods: &[Box], pos: [i32; 3]) { - let mut event = BrewingCompleteEvent { pos }; - for mod_hook in super::entered(mods, "brewing_complete") { - mod_hook.brewing_complete(host, &mut event); - } -} -/// What [`Hooks::brewing_fuel`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BrewingFuelEvent<'a> { - pub pos: [i32; 3], - pub fuel: &'a str, -} -pub fn dispatch_brewing_fuel(host: &dyn Host, mods: &[Box], pos: [i32; 3], fuel: &str) { - let mut event = BrewingFuelEvent { pos, fuel }; - for mod_hook in super::entered(mods, "brewing_fuel") { - mod_hook.brewing_fuel(host, &mut event); - } -} -/// What [`Hooks::brewing_start`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BrewingStartEvent<'a> { - pub pos: [i32; 3], - pub ingredient: &'a str, -} -pub fn dispatch_brewing_start( - host: &dyn Host, - mods: &[Box], - pos: [i32; 3], - ingredient: &str, -) { - let mut event = BrewingStartEvent { pos, ingredient }; - for mod_hook in super::entered(mods, "brewing_start") { - mod_hook.brewing_start(host, &mut event); - } -} -/// What [`Hooks::broadcast_message`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BroadcastMessageEvent<'a> { - pub line: &'a mut Text, -} -pub fn dispatch_broadcast_message( - host: &dyn Host, - mods: &[Box], - line: &mut Text, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = BroadcastMessageEvent { line }; - for mod_hook in super::entered(mods, "broadcast_message") { - if matches!(mod_hook.broadcast_message(host, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::campfire_start`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct CampfireStartEvent<'a> { - pub pos: [i32; 3], - pub item: &'a str, -} -pub fn dispatch_campfire_start( - host: &dyn Host, - mods: &[Box], - pos: [i32; 3], - item: &str, -) { - let mut event = CampfireStartEvent { pos, item }; - for mod_hook in super::entered(mods, "campfire_start") { - mod_hook.campfire_start(host, &mut event); - } -} -/// What [`Hooks::cauldron_level_changed`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct CauldronLevelChangedEvent<'a> { - pub dimension: LevelDimension, - pub player: Option, - pub pos: BlockPos, - pub from_level: i32, - pub to_level: i32, - pub reason: &'a str, -} -pub fn dispatch_cauldron_level_changed( - level: &dyn LevelHost, - mods: &[Box], - dimension: LevelDimension, - player: Option, - pos: BlockPos, - from_level: i32, - to_level: i32, - reason: &str, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = CauldronLevelChangedEvent { - dimension, - player, - pos, - from_level, - to_level, - reason, - }; - for mod_hook in super::entered(mods, "cauldron_level_changed") { - if matches!( - mod_hook.cauldron_level_changed(level, &mut event), - Flow::Cancel - ) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::chorus_fruit_teleport`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ChorusFruitTeleportEvent<'a> { - pub player: u128, - pub name: &'a str, - pub from: [f64; 3], - pub target: &'a mut [f64; 3], -} -pub fn dispatch_chorus_fruit_teleport( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - from: [f64; 3], - target: &mut [f64; 3], - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = ChorusFruitTeleportEvent { - player, - name, - from, - target, - }; - for mod_hook in super::entered(mods, "chorus_fruit_teleport") { - if matches!( - mod_hook.chorus_fruit_teleport(host, &mut event), - Flow::Cancel - ) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::compost_item`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct CompostItemEvent<'a> { - pub pos: [i32; 3], - pub item: &'a str, -} -pub fn dispatch_compost_item(host: &dyn Host, mods: &[Box], pos: [i32; 3], item: &str) { - let mut event = CompostItemEvent { pos, item }; - for mod_hook in super::entered(mods, "compost_item") { - mod_hook.compost_item(host, &mut event); - } -} -/// What [`Hooks::crafter_craft`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct CrafterCraftEvent<'a> { - pub pos: [i32; 3], - pub recipe: &'a str, -} -pub fn dispatch_crafter_craft( - host: &dyn Host, - mods: &[Box], - pos: [i32; 3], - recipe: &str, -) { - let mut event = CrafterCraftEvent { pos, recipe }; - for mod_hook in super::entered(mods, "crafter_craft") { - mod_hook.crafter_craft(host, &mut event); - } -} -/// What [`Hooks::creeper_ignite`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct CreeperIgniteEvent<'a> { - pub entity: u32, - pub player: u128, - pub item: &'a str, -} -pub fn dispatch_creeper_ignite( - host: &mut dyn Host, - mods: &[Box], - entity: u32, - player: u128, - item: &str, -) { - let mut event = CreeperIgniteEvent { - entity, - player, - item, - }; - for mod_hook in super::entered(mods, "creeper_ignite") { - mod_hook.creeper_ignite(host, &mut event); - } -} -/// What [`Hooks::creeper_power`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct CreeperPowerEvent { - pub entity: u32, - pub lightning: [f64; 3], -} -pub fn dispatch_creeper_power( - host: &mut dyn Host, - mods: &[Box], - entity: u32, - lightning: [f64; 3], -) { - let mut event = CreeperPowerEvent { entity, lightning }; - for mod_hook in super::entered(mods, "creeper_power") { - mod_hook.creeper_power(host, &mut event); - } -} -/// What [`Hooks::critical_hit`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct CriticalHitEvent<'a> { - pub target_kind: &'a str, - pub vanilla_critical: bool, - pub critical: bool, - pub damage_multiplier: f32, -} -pub fn dispatch_critical_hit( - host: &mut dyn Host, - mods: &[Box], - target_kind: &str, - vanilla_critical: bool, - critical: &mut bool, - damage_multiplier: &mut f32, -) { - let mut event = CriticalHitEvent { - target_kind, - vanilla_critical, - critical: *critical, - damage_multiplier: *damage_multiplier, - }; - for mod_hook in super::entered(mods, "critical_hit") { - mod_hook.critical_hit(host, &mut event); - } - *critical = event.critical; - *damage_multiplier = event.damage_multiplier; -} -/// What [`Hooks::difficulty_changed`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct DifficultyChangedEvent<'a> { - pub from: &'a str, - pub to: &'a str, -} -pub fn dispatch_difficulty_changed( - host: &mut dyn Host, - mods: &[Box], - from: &str, - to: &str, -) { - let mut event = DifficultyChangedEvent { from, to }; - for mod_hook in super::entered(mods, "difficulty_changed") { - mod_hook.difficulty_changed(host, &mut event); - } -} -/// What [`Hooks::dragon_egg_form`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct DragonEggFormEvent { - pub dimension: LevelDimension, - pub pos: BlockPos, -} -pub fn dispatch_dragon_egg_form( - level: &dyn LevelHost, - mods: &[Box], - dimension: LevelDimension, - pos: BlockPos, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = DragonEggFormEvent { dimension, pos }; - for mod_hook in super::entered(mods, "dragon_egg_form") { - if matches!(mod_hook.dragon_egg_form(level, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::enchant_item`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct EnchantItemEvent<'a> { - pub player: u128, - pub name: &'a str, - pub pos: [i32; 3], - pub item: &'a ItemStack, - pub levels: i32, -} -pub fn dispatch_enchant_item( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - pos: [i32; 3], - item: &ItemStack, - levels: i32, -) { - let mut event = EnchantItemEvent { - player, - name, - pos, - item, - levels, - }; - for mod_hook in super::entered(mods, "enchant_item") { - mod_hook.enchant_item(host, &mut event); - } -} -/// What [`Hooks::ender_pearl_teleport`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct EnderPearlTeleportEvent<'a> { - pub player: u128, - pub name: &'a str, - pub pearl: u32, - pub from: [f64; 3], - pub target: &'a mut [f64; 3], - pub damage: f32, -} -pub fn dispatch_ender_pearl_teleport( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - pearl: u32, - from: [f64; 3], - target: &mut [f64; 3], - damage: &mut f32, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = EnderPearlTeleportEvent { - player, - name, - pearl, - from, - target, - damage: *damage, - }; - for mod_hook in super::entered(mods, "ender_pearl_teleport") { - if matches!( - mod_hook.ender_pearl_teleport(host, &mut event), - Flow::Cancel - ) { - *cancel = true; - break; - } - } - *damage = event.damage; -} -/// What [`Hooks::entities_load`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct EntitiesLoadEvent<'a> { - pub dimension: &'a str, - pub chunk: [i32; 2], - pub count: usize, -} -pub fn dispatch_entities_load( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - chunk: [i32; 2], - count: usize, -) { - let mut event = EntitiesLoadEvent { - dimension, - chunk, - count, - }; - for mod_hook in super::entered(mods, "entities_load") { - mod_hook.entities_load(host, &mut event); - } -} -/// What [`Hooks::entities_unload`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct EntitiesUnloadEvent<'a> { - pub dimension: &'a str, - pub chunk: [i32; 2], - pub count: usize, -} -pub fn dispatch_entities_unload( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - chunk: [i32; 2], - count: usize, -) { - let mut event = EntitiesUnloadEvent { - dimension, - chunk, - count, - }; - for mod_hook in super::entered(mods, "entities_unload") { - mod_hook.entities_unload(host, &mut event); - } -} -/// What [`Hooks::entity_break_door`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct EntityBreakDoorEvent<'a> { - pub entity: u32, - pub kind: &'a str, - pub pos: [i32; 3], -} -pub fn dispatch_entity_break_door( - host: &mut dyn Host, - mods: &[Box], - entity: u32, - kind: &str, - pos: [i32; 3], -) { - let mut event = EntityBreakDoorEvent { entity, kind, pos }; - for mod_hook in super::entered(mods, "entity_break_door") { - mod_hook.entity_break_door(host, &mut event); - } -} -/// What [`Hooks::entity_breed`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct EntityBreedEvent { - pub parent: u32, - pub partner: u32, - pub child: Option, -} -pub fn dispatch_entity_breed( - host: &mut dyn Host, - mods: &[Box], - parent: u32, - partner: u32, - child: Option, -) { - let mut event = EntityBreedEvent { - parent, - partner, - child, - }; - for mod_hook in super::entered(mods, "entity_breed") { - mod_hook.entity_breed(host, &mut event); - } -} -/// What [`Hooks::entity_change_block`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct EntityChangeBlockEvent<'a> { - pub entity: u32, - pub kind: &'a str, - pub pos: [i32; 3], - pub from: &'a str, - pub to: &'a str, -} -pub fn dispatch_entity_change_block( - host: &mut dyn Host, - mods: &[Box], - entity: u32, - kind: &str, - pos: [i32; 3], - from: &str, - to: &str, -) { - let mut event = EntityChangeBlockEvent { - entity, - kind, - pos, - from, - to, - }; - for mod_hook in super::entered(mods, "entity_change_block") { - mod_hook.entity_change_block(host, &mut event); - } -} -/// What [`Hooks::entity_drop_item`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct EntityDropItemEvent<'a> { - pub entity: u32, - pub item: &'a str, - pub count: i32, - pub pos: [f64; 3], -} -pub fn dispatch_entity_drop_item( - host: &mut dyn Host, - mods: &[Box], - entity: u32, - item: &str, - count: i32, - pos: [f64; 3], -) { - let mut event = EntityDropItemEvent { - entity, - item, - count, - pos, - }; - for mod_hook in super::entered(mods, "entity_drop_item") { - mod_hook.entity_drop_item(host, &mut event); - } -} -/// What [`Hooks::entity_enter_block`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct EntityEnterBlockEvent<'a> { - pub entity: u32, - pub kind: &'a str, - pub pos: [i32; 3], - pub block: &'a str, -} -pub fn dispatch_entity_enter_block( - host: &mut dyn Host, - mods: &[Box], - entity: u32, - kind: &str, - pos: [i32; 3], - block: &str, -) { - let mut event = EntityEnterBlockEvent { - entity, - kind, - pos, - block, - }; - for mod_hook in super::entered(mods, "entity_enter_block") { - mod_hook.entity_enter_block(host, &mut event); - } -} -/// What [`Hooks::entity_enter_love_mode`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct EntityEnterLoveModeEvent<'a> { - pub entity: u32, - pub kind: &'a str, - pub player: Option, -} -pub fn dispatch_entity_enter_love_mode( - host: &mut dyn Host, - mods: &[Box], - entity: u32, - kind: &str, - player: Option, -) { - let mut event = EntityEnterLoveModeEvent { - entity, - kind, - player, - }; - for mod_hook in super::entered(mods, "entity_enter_love_mode") { - mod_hook.entity_enter_love_mode(host, &mut event); - } -} -/// What [`Hooks::entity_portal`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct EntityPortalEvent<'a> { - pub dimension: &'a str, - pub entity: u32, - pub pos: [i32; 3], -} -pub fn dispatch_entity_portal( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - entity: u32, - pos: [i32; 3], -) { - let mut event = EntityPortalEvent { - dimension, - entity, - pos, - }; - for mod_hook in super::entered(mods, "entity_portal") { - mod_hook.entity_portal(host, &mut event); - } -} -/// What [`Hooks::entity_pose_change`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct EntityPoseChangeEvent<'a> { - pub player: u128, - pub name: &'a str, - pub from: &'a str, - pub to: &'a str, -} -pub fn dispatch_entity_pose_change( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - from: &str, - to: &str, -) { - let mut event = EntityPoseChangeEvent { - player, - name, - from, - to, - }; - for mod_hook in super::entered(mods, "entity_pose_change") { - mod_hook.entity_pose_change(host, &mut event); - } -} -/// What [`Hooks::entity_shoot_bow`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct EntityShootBowEvent<'a> { - pub player: u128, - pub name: &'a str, - pub projectile: u32, - pub force: f32, -} -pub fn dispatch_entity_shoot_bow( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - projectile: u32, - force: f32, -) { - let mut event = EntityShootBowEvent { - player, - name, - projectile, - force, - }; - for mod_hook in super::entered(mods, "entity_shoot_bow") { - mod_hook.entity_shoot_bow(host, &mut event); - } -} -/// What [`Hooks::entity_tame`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct EntityTameEvent<'a> { - pub entity: u32, - pub kind: &'a str, - pub owner: Option, -} -pub fn dispatch_entity_tame( - host: &mut dyn Host, - mods: &[Box], - entity: u32, - kind: &str, - owner: Option, -) { - let mut event = EntityTameEvent { - entity, - kind, - owner, - }; - for mod_hook in super::entered(mods, "entity_tame") { - mod_hook.entity_tame(host, &mut event); - } -} -/// What [`Hooks::entity_teleport`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct EntityTeleportEvent<'a> { - pub dimension: &'a str, - pub entity: u32, - pub from: [f64; 3], - pub to: [f64; 3], -} -pub fn dispatch_entity_teleport( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - entity: u32, - from: [f64; 3], - to: [f64; 3], -) { - let mut event = EntityTeleportEvent { - dimension, - entity, - from, - to, - }; - for mod_hook in super::entered(mods, "entity_teleport") { - mod_hook.entity_teleport(host, &mut event); - } -} -/// What [`Hooks::entity_toggle_glide`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct EntityToggleGlideEvent<'a> { - pub player: u128, - pub name: &'a str, - pub gliding: bool, -} -pub fn dispatch_entity_toggle_glide( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - gliding: bool, -) { - let mut event = EntityToggleGlideEvent { - player, - name, - gliding, - }; - for mod_hook in super::entered(mods, "entity_toggle_glide") { - mod_hook.entity_toggle_glide(host, &mut event); - } -} -/// What [`Hooks::entity_transform`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct EntityTransformEvent<'a> { - pub entity: u32, - pub kind: &'a str, - pub transformed: u32, - pub transformed_kind: &'a str, -} -pub fn dispatch_entity_transform( - host: &mut dyn Host, - mods: &[Box], - entity: u32, - kind: &str, - transformed: u32, - transformed_kind: &str, -) { - let mut event = EntityTransformEvent { - entity, - kind, - transformed, - transformed_kind, - }; - for mod_hook in super::entered(mods, "entity_transform") { - mod_hook.entity_transform(host, &mut event); - } -} -/// What [`Hooks::entity_unleash`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct EntityUnleashEvent<'a> { - pub entity: u32, - pub kind: &'a str, -} -pub fn dispatch_entity_unleash( - host: &mut dyn Host, - mods: &[Box], - entity: u32, - kind: &str, -) { - let mut event = EntityUnleashEvent { entity, kind }; - for mod_hook in super::entered(mods, "entity_unleash") { - mod_hook.entity_unleash(host, &mut event); - } -} -/// What [`Hooks::exp_bottle`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ExpBottleEvent { - pub pos: [f64; 3], - pub experience: u32, -} -pub fn dispatch_exp_bottle( - host: &mut dyn Host, - mods: &[Box], - pos: [f64; 3], - experience: u32, -) { - let mut event = ExpBottleEvent { pos, experience }; - for mod_hook in super::entered(mods, "exp_bottle") { - mod_hook.exp_bottle(host, &mut event); - } -} -/// What [`Hooks::experience_orb_pickup_listened`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ExperienceOrbPickupListenedEvent { - pub listened: bool, -} -pub fn dispatch_experience_orb_pickup_listened( - host: &dyn Host, - mods: &[Box], - listened: &mut bool, -) { - let mut event = ExperienceOrbPickupListenedEvent { - listened: *listened, - }; - for mod_hook in super::entered(mods, "experience_orb_pickup_listened") { - mod_hook.experience_orb_pickup_listened(host, &mut event); - } - *listened = event.listened; -} -/// What [`Hooks::experience_orb_pickup`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ExperienceOrbPickupEvent<'a> { - pub player: u128, - pub dimension: &'a str, - pub orb: u32, - pub value: u32, - pub count: u32, -} -pub fn dispatch_experience_orb_pickup( - host: &mut dyn Host, - mods: &[Box], - player: u128, - dimension: &str, - orb: u32, - value: &mut u32, - count: &mut u32, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = ExperienceOrbPickupEvent { - player, - dimension, - orb, - value: *value, - count: *count, - }; - for mod_hook in super::entered(mods, "experience_orb_pickup") { - if matches!( - mod_hook.experience_orb_pickup(host, &mut event), - Flow::Cancel - ) { - *canceled = true; - break; - } - } - *value = event.value; - *count = event.count; -} -/// What [`Hooks::mending_repair`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct MendingRepairEvent<'a> { - pub player: u128, - pub stack: &'a ItemStack, - pub experience: u32, - pub repair: u32, -} -pub fn dispatch_mending_repair( - host: &mut dyn Host, - mods: &[Box], - player: u128, - stack: &ItemStack, - experience: u32, - repair: &mut u32, -) { - let mut event = MendingRepairEvent { - player, - stack, - experience, - repair: *repair, - }; - for mod_hook in super::entered(mods, "mending_repair") { - mod_hook.mending_repair(host, &mut event); - } - *repair = event.repair; -} -/// What [`Hooks::explosion_prime`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ExplosionPrimeEvent { - pub center: [f64; 3], - pub radius: f32, -} -pub fn dispatch_explosion_prime( - host: &mut dyn Host, - mods: &[Box], - center: [f64; 3], - radius: f32, -) { - let mut event = ExplosionPrimeEvent { center, radius }; - for mod_hook in super::entered(mods, "explosion_prime") { - mod_hook.explosion_prime(host, &mut event); - } -} -/// What [`Hooks::fire_odds`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct FireOddsEvent { - pub dimension: LevelDimension, - pub state: BlockStateId, -} -pub fn dispatch_fire_odds( - level: &dyn LevelHost, - mods: &[Box], - dimension: LevelDimension, - state: BlockStateId, - odds: &mut Option<(i32, i32)>, -) { - if odds.is_some() { - return; - } - let mut event = FireOddsEvent { dimension, state }; - for mod_hook in super::entered(mods, "fire_odds") { - if let Some(replacement) = mod_hook.fire_odds(level, &mut event) { - *odds = Some(replacement); - break; - } - } -} -/// What [`Hooks::smoke_source`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct SmokeSourceEvent { - pub dimension: LevelDimension, - pub state: BlockStateId, -} -pub fn dispatch_smoke_source( - level: &dyn LevelHost, - mods: &[Box], - dimension: LevelDimension, - state: BlockStateId, - smoke: &mut Option, -) { - if smoke.is_some() { - return; - } - let mut event = SmokeSourceEvent { dimension, state }; - for mod_hook in super::entered(mods, "smoke_source") { - if let Some(replacement) = mod_hook.smoke_source(level, &mut event) { - *smoke = Some(replacement); - break; - } - } -} -/// What [`Hooks::firework_explode`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct FireworkExplodeEvent { - pub entity: u32, - pub pos: [f64; 3], - pub bursts: usize, -} -pub fn dispatch_firework_explode( - host: &mut dyn Host, - mods: &[Box], - entity: u32, - pos: [f64; 3], - bursts: usize, -) { - let mut event = FireworkExplodeEvent { - entity, - pos, - bursts, - }; - for mod_hook in super::entered(mods, "firework_explode") { - mod_hook.firework_explode(host, &mut event); - } -} -/// What [`Hooks::fluid_level_change`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct FluidLevelChangeEvent<'a> { - pub dimension: LevelDimension, - pub pos: BlockPos, - pub fluid: &'a str, - pub from: i32, - pub to: i32, -} -pub fn dispatch_fluid_level_change( - level: &dyn LevelHost, - mods: &[Box], - dimension: LevelDimension, - pos: BlockPos, - fluid: &str, - from: i32, - to: i32, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = FluidLevelChangeEvent { - dimension, - pos, - fluid, - from, - to, - }; - for mod_hook in super::entered(mods, "fluid_level_change") { - if matches!(mod_hook.fluid_level_change(level, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::framerate_limit`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct FramerateLimitEvent { - pub focused: bool, - pub minimized: bool, - pub in_world: bool, - pub limit: i32, -} -pub fn dispatch_framerate_limit( - host: &dyn Host, - mods: &[Box], - focused: bool, - minimized: bool, - in_world: bool, - limit: &mut i32, -) { - let mut event = FramerateLimitEvent { - focused, - minimized, - in_world, - limit: *limit, - }; - for mod_hook in super::entered(mods, "framerate_limit") { - mod_hook.framerate_limit(host, &mut event); - } - *limit = event.limit; -} -/// What [`Hooks::furnace_burn`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct FurnaceBurnEvent<'a> { - pub pos: [i32; 3], - pub fuel: &'a str, -} -pub fn dispatch_furnace_burn(host: &dyn Host, mods: &[Box], pos: [i32; 3], fuel: &str) { - let mut event = FurnaceBurnEvent { pos, fuel }; - for mod_hook in super::entered(mods, "furnace_burn") { - mod_hook.furnace_burn(host, &mut event); - } -} -/// What [`Hooks::furnace_extract`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct FurnaceExtractEvent<'a> { - pub player: u128, - pub name: &'a str, - pub pos: [i32; 3], - pub item: &'a str, - pub count: u32, -} -pub fn dispatch_furnace_extract( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - pos: [i32; 3], - item: &str, - count: u32, -) { - let mut event = FurnaceExtractEvent { - player, - name, - pos, - item, - count, - }; - for mod_hook in super::entered(mods, "furnace_extract") { - mod_hook.furnace_extract(host, &mut event); - } -} -/// What [`Hooks::furnace_smelt`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct FurnaceSmeltEvent<'a> { - pub pos: [i32; 3], - pub item: &'a str, - pub result: &'a str, -} -pub fn dispatch_furnace_smelt( - host: &dyn Host, - mods: &[Box], - pos: [i32; 3], - item: &str, - result: &str, -) { - let mut event = FurnaceSmeltEvent { pos, item, result }; - for mod_hook in super::entered(mods, "furnace_smelt") { - mod_hook.furnace_smelt(host, &mut event); - } -} -/// What [`Hooks::furnace_start_smelt`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct FurnaceStartSmeltEvent<'a> { - pub pos: [i32; 3], - pub item: &'a str, -} -pub fn dispatch_furnace_start_smelt( - host: &dyn Host, - mods: &[Box], - pos: [i32; 3], - item: &str, -) { - let mut event = FurnaceStartSmeltEvent { pos, item }; - for mod_hook in super::entered(mods, "furnace_start_smelt") { - mod_hook.furnace_start_smelt(host, &mut event); - } -} -/// What [`Hooks::game_event`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct GameEventEvent<'a> { - pub event: &'a str, - pub pos: [f64; 3], -} -pub fn dispatch_game_event( - host: &mut dyn Host, - mods: &[Box], - event: &str, - pos: [f64; 3], -) { - let mut event_ = GameEventEvent { event, pos }; - for mod_hook in super::entered(mods, "game_event") { - mod_hook.game_event(host, &mut event_); - } -} -/// What [`Hooks::hopper_inventory_search`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct HopperInventorySearchEvent { - pub hopper: [i32; 3], - pub container: Option<[i32; 3]>, - pub entity: Option, -} -pub fn dispatch_hopper_inventory_search( - host: &dyn Host, - mods: &[Box], - hopper: [i32; 3], - container: Option<[i32; 3]>, - entity: Option, -) { - let mut event = HopperInventorySearchEvent { - hopper, - container, - entity, - }; - for mod_hook in super::entered(mods, "hopper_inventory_search") { - mod_hook.hopper_inventory_search(host, &mut event); - } -} -/// What [`Hooks::hopper_pickup_item`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct HopperPickupItemEvent<'a> { - pub dimension: LevelDimension, - pub pos: BlockPos, - pub item: &'a str, - pub count: u32, -} -pub fn dispatch_hopper_pickup_item( - level: &dyn LevelHost, - mods: &[Box], - dimension: LevelDimension, - pos: BlockPos, - item: &str, - count: u32, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = HopperPickupItemEvent { - dimension, - pos, - item, - count, - }; - for mod_hook in super::entered(mods, "hopper_pickup_item") { - if matches!(mod_hook.hopper_pickup_item(level, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::ingredient`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct IngredientEvent<'a> { - pub kind: &'a str, - pub data: &'a serde_json::Map, - pub items: &'a mut Vec, -} -pub fn dispatch_ingredient( - mods: &[Box], - kind: &str, - data: &serde_json::Map, - items: &mut Vec, -) { - let mut event = IngredientEvent { kind, data, items }; - for mod_hook in super::entered(mods, "ingredient") { - mod_hook.ingredient(&mut event); - } -} -/// What [`Hooks::inventory_block_start`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct InventoryBlockStartEvent<'a> { - pub player: u128, - pub name: &'a str, - pub pos: [i32; 3], -} -pub fn dispatch_inventory_block_start( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - pos: [i32; 3], - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = InventoryBlockStartEvent { player, name, pos }; - for mod_hook in super::entered(mods, "inventory_block_start") { - if matches!( - mod_hook.inventory_block_start(host, &mut event), - Flow::Cancel - ) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::inventory_move_item`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct InventoryMoveItemEvent<'a> { - pub source: [i32; 3], - pub destination: [i32; 3], - pub item: &'a str, -} -pub fn dispatch_inventory_move_item( - host: &dyn Host, - mods: &[Box], - source: [i32; 3], - destination: [i32; 3], - item: &str, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = InventoryMoveItemEvent { - source, - destination, - item, - }; - for mod_hook in super::entered(mods, "inventory_move_item") { - if matches!(mod_hook.inventory_move_item(host, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::item_crafted`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ItemCraftedEvent<'a> { - pub player: u128, - pub recipe: &'a str, - pub stack: &'a ItemStack, -} -pub fn dispatch_item_crafted( - host: &mut dyn Host, - mods: &[Box], - player: u128, - recipe: &str, - stack: &ItemStack, -) { - let mut event = ItemCraftedEvent { - player, - recipe, - stack, - }; - for mod_hook in super::entered(mods, "item_crafted") { - mod_hook.item_crafted(host, &mut event); - } -} -/// What [`Hooks::item_despawn`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ItemDespawnEvent<'a> { - pub entity: u32, - pub item: &'a str, - pub count: i32, - pub pos: [f64; 3], -} -pub fn dispatch_item_despawn( - host: &mut dyn Host, - mods: &[Box], - entity: u32, - item: &str, - count: i32, - pos: [f64; 3], -) { - let mut event = ItemDespawnEvent { - entity, - item, - count, - pos, - }; - for mod_hook in super::entered(mods, "item_despawn") { - mod_hook.item_despawn(host, &mut event); - } -} -/// What [`Hooks::item_energy_buffer`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ItemEnergyBufferEvent<'a> { - pub item: &'a str, -} -pub fn dispatch_item_energy_buffer( - host: &dyn Host, - mods: &[Box], - item: &str, - buffer: &mut Option, -) { - if buffer.is_some() { - return; - } - let mut event = ItemEnergyBufferEvent { item }; - for mod_hook in super::entered(mods, "item_energy_buffer") { - if let Some(replacement) = mod_hook.item_energy_buffer(host, &mut event) { - *buffer = Some(replacement); - break; - } - } -} -/// What [`Hooks::item_expire`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ItemExpireEvent<'a> { - pub entity: u32, - pub stack: &'a ItemStack, - pub extra_life: i32, -} -pub fn dispatch_item_expire( - host: &mut dyn Host, - mods: &[Box], - entity: u32, - stack: &ItemStack, - extra_life: &mut i32, -) { - let mut event = ItemExpireEvent { - entity, - stack, - extra_life: *extra_life, - }; - for mod_hook in super::entered(mods, "item_expire") { - mod_hook.item_expire(host, &mut event); - } - *extra_life = event.extra_life; -} -/// What [`Hooks::item_spawn`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ItemSpawnEvent<'a> { - pub entity: u32, - pub item: &'a str, - pub count: i32, - pub pos: [f64; 3], -} -pub fn dispatch_item_spawn( - host: &mut dyn Host, - mods: &[Box], - entity: u32, - item: &str, - count: i32, - pos: [f64; 3], -) { - let mut event = ItemSpawnEvent { - entity, - item, - count, - pos, - }; - for mod_hook in super::entered(mods, "item_spawn") { - mod_hook.item_spawn(host, &mut event); - } -} -/// What [`Hooks::leaves_decay`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct LeavesDecayEvent { - pub dimension: LevelDimension, - pub pos: BlockPos, -} -pub fn dispatch_leaves_decay( - level: &dyn LevelHost, - mods: &[Box], - dimension: LevelDimension, - pos: BlockPos, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = LeavesDecayEvent { dimension, pos }; - for mod_hook in super::entered(mods, "leaves_decay") { - if matches!(mod_hook.leaves_decay(level, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::lightning_strike`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct LightningStrikeEvent<'a> { - pub player: Option, - pub pos: [i32; 3], - pub cause: &'a str, -} -pub fn dispatch_lightning_strike( - host: &mut dyn Host, - mods: &[Box], - player: Option, - pos: [i32; 3], - cause: &str, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = LightningStrikeEvent { player, pos, cause }; - for mod_hook in super::entered(mods, "lightning_strike") { - if matches!(mod_hook.lightning_strike(host, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::menu_quick_move`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct MenuQuickMoveEvent<'a> { - pub pos: [i32; 3], - pub entity: &'a mut ModBlockEntity, - pub container_slots: usize, - pub slot: usize, - pub stack: &'a mut ItemStack, -} -pub fn dispatch_menu_quick_move( - host: &mut dyn Host, - mods: &[Box], - pos: [i32; 3], - entity: &mut ModBlockEntity, - container_slots: usize, - slot: usize, - stack: &mut ItemStack, - handled: &mut bool, -) { - if *handled { - return; - } - let mut event = MenuQuickMoveEvent { - pos, - entity, - container_slots, - slot, - stack, - }; - for mod_hook in super::entered(mods, "menu_quick_move") { - if matches!(mod_hook.menu_quick_move(host, &mut event), Flow::Cancel) { - *handled = true; - break; - } - } -} -/// What [`Hooks::moisture_change`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct MoistureChangeEvent { - pub dimension: LevelDimension, - pub pos: BlockPos, - pub from: i32, - pub to: i32, -} -pub fn dispatch_moisture_change( - level: &dyn LevelHost, - mods: &[Box], - dimension: LevelDimension, - pos: BlockPos, - from: i32, - to: i32, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = MoistureChangeEvent { - dimension, - pos, - from, - to, - }; - for mod_hook in super::entered(mods, "moisture_change") { - if matches!(mod_hook.moisture_change(level, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::anvil_craft_post`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct AnvilCraftPostEvent<'a> { - pub player: u128, - pub name: &'a str, - pub pos: [i32; 3], - pub craft: &'a horizon_client::menu::AnvilCraft, -} -pub fn dispatch_anvil_craft_post( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - pos: [i32; 3], - craft: &horizon_client::menu::AnvilCraft, -) { - let mut event = AnvilCraftPostEvent { - player, - name, - pos, - craft, - }; - for mod_hook in super::entered(mods, "anvil_craft_post") { - mod_hook.anvil_craft_post(host, &mut event); - } -} -/// What [`Hooks::anvil_craft_pre`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct AnvilCraftPreEvent<'a> { - pub player: u128, - pub name: &'a str, - pub pos: [i32; 3], - pub craft: &'a horizon_client::menu::AnvilCraft, -} -pub fn dispatch_anvil_craft_pre( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - pos: [i32; 3], - craft: &horizon_client::menu::AnvilCraft, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = AnvilCraftPreEvent { - player, - name, - pos, - craft, - }; - for mod_hook in super::entered(mods, "anvil_craft_pre") { - if matches!(mod_hook.anvil_craft_pre(host, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::armor_stand_manipulate`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ArmorStandManipulateEvent<'a> { - pub player: u128, - pub name: &'a str, - pub stand_id: i32, - pub slot: &'a str, -} -pub fn dispatch_armor_stand_manipulate( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - stand_id: i32, - slot: &str, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = ArmorStandManipulateEvent { - player, - name, - stand_id, - slot, - }; - for mod_hook in super::entered(mods, "armor_stand_manipulate") { - if matches!( - mod_hook.armor_stand_manipulate(host, &mut event), - Flow::Cancel - ) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::bucket_empty`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct BucketEmptyEvent<'a> { - pub player: u128, - pub name: &'a str, - pub pos: [i32; 3], - pub fluid: &'a str, -} -pub fn dispatch_bucket_empty( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - pos: [i32; 3], - fluid: &str, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = BucketEmptyEvent { - player, - name, - pos, - fluid, - }; - for mod_hook in super::entered(mods, "bucket_empty") { - if matches!(mod_hook.bucket_empty(host, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::container_opened`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ContainerOpenedEvent<'a> { - pub player: u128, - pub name: &'a str, - pub kind: &'a str, - pub pos: Option<[i32; 3]>, -} -pub fn dispatch_container_opened( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - kind: &str, - pos: Option<[i32; 3]>, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = ContainerOpenedEvent { - player, - name, - kind, - pos, - }; - for mod_hook in super::entered(mods, "container_opened") { - if matches!(mod_hook.container_opened(host, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::creative_slot`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct CreativeSlotEvent<'a> { - pub player: u128, - pub name: &'a str, - pub slot: i32, - pub item: &'a str, - pub count: i32, -} -pub fn dispatch_creative_slot( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - slot: i32, - item: &str, - count: i32, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = CreativeSlotEvent { - player, - name, - slot, - item, - count, - }; - for mod_hook in super::entered(mods, "creative_slot") { - if matches!(mod_hook.creative_slot(host, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::food_level_changed`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct FoodLevelChangedEvent<'a> { - pub player: u128, - pub name: &'a str, - pub from: i32, - pub to: i32, -} -pub fn dispatch_food_level_changed( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - from: i32, - to: &mut i32, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = FoodLevelChangedEvent { - player, - name, - from, - to: *to, - }; - for mod_hook in super::entered(mods, "food_level_changed") { - if matches!(mod_hook.food_level_changed(host, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } - *to = event.to; -} -/// What [`Hooks::hanging_break`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct HangingBreakEvent<'a> { - pub player: Option, - pub kind: &'a str, - pub pos: [i32; 3], -} -pub fn dispatch_hanging_break( - host: &mut dyn Host, - mods: &[Box], - player: Option, - kind: &str, - pos: [i32; 3], - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = HangingBreakEvent { player, kind, pos }; - for mod_hook in super::entered(mods, "hanging_break") { - if matches!(mod_hook.hanging_break(host, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::hanging_place`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct HangingPlaceEvent<'a> { - pub player: u128, - pub name: &'a str, - pub kind: &'a str, - pub pos: [i32; 3], -} -pub fn dispatch_hanging_place( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - kind: &str, - pos: [i32; 3], - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = HangingPlaceEvent { - player, - name, - kind, - pos, - }; - for mod_hook in super::entered(mods, "hanging_place") { - if matches!(mod_hook.hanging_place(host, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::item_damaged`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ItemDamagedEvent<'a> { - pub player: u128, - pub name: &'a str, - pub item: &'a str, - pub amount: i32, -} -pub fn dispatch_item_damaged( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - item: &str, - amount: &mut i32, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = ItemDamagedEvent { - player, - name, - item, - amount: *amount, - }; - for mod_hook in super::entered(mods, "item_damaged") { - if matches!(mod_hook.item_damaged(host, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } - *amount = event.amount; -} -/// What [`Hooks::player_changed_dimension`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerChangedDimensionEvent<'a> { - pub player: u128, - pub name: &'a str, - pub from: &'a str, - pub to: &'a str, -} -pub fn dispatch_player_changed_dimension( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - from: &str, - to: &str, -) { - let mut event = PlayerChangedDimensionEvent { - player, - name, - from, - to, - }; - for mod_hook in super::entered(mods, "player_changed_dimension") { - mod_hook.player_changed_dimension(host, &mut event); - } -} -/// What [`Hooks::player_clone`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerCloneEvent<'a> { - pub player: u128, - pub name: &'a str, - pub was_death: bool, -} -pub fn dispatch_player_clone( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - was_death: bool, -) { - let mut event = PlayerCloneEvent { - player, - name, - was_death, - }; - for mod_hook in super::entered(mods, "player_clone") { - mod_hook.player_clone(host, &mut event); - } -} -/// What [`Hooks::player_commands_sent`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerCommandsSentEvent<'a> { - pub player: u128, - pub name: &'a str, - pub names: &'a mut Vec, -} -pub fn dispatch_player_commands_sent( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - names: &mut Vec, -) { - let mut event = PlayerCommandsSentEvent { - player, - name, - names, - }; - for mod_hook in super::entered(mods, "player_commands_sent") { - mod_hook.player_commands_sent(host, &mut event); - } -} -/// What [`Hooks::player_destroy_item`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerDestroyItemEvent<'a> { - pub player: u128, - pub name: &'a str, - pub item: &'a str, - pub slot: &'a str, -} -pub fn dispatch_player_destroy_item( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - item: &str, - slot: &str, -) { - let mut event = PlayerDestroyItemEvent { - player, - name, - item, - slot, - }; - for mod_hook in super::entered(mods, "player_destroy_item") { - mod_hook.player_destroy_item(host, &mut event); - } -} -/// What [`Hooks::player_fish`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerFishEvent<'a> { - pub player: u128, - pub name: &'a str, - pub state: i32, -} -pub fn dispatch_player_fish( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - state: i32, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = PlayerFishEvent { - player, - name, - state, - }; - for mod_hook in super::entered(mods, "player_fish") { - if matches!(mod_hook.player_fish(host, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::player_flower_pot_manipulate`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerFlowerPotManipulateEvent<'a> { - pub player: u128, - pub name: &'a str, - pub pos: [i32; 3], - pub plant: &'a str, -} -pub fn dispatch_player_flower_pot_manipulate( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - pos: [i32; 3], - plant: &str, -) { - let mut event = PlayerFlowerPotManipulateEvent { - player, - name, - pos, - plant, - }; - for mod_hook in super::entered(mods, "player_flower_pot_manipulate") { - mod_hook.player_flower_pot_manipulate(host, &mut event); - } -} -/// What [`Hooks::player_game_mode_changed`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerGameModeChangedEvent<'a> { - pub player: u128, - pub name: &'a str, - pub from: i32, - pub to: i32, -} -pub fn dispatch_player_game_mode_changed( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - from: i32, - to: i32, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = PlayerGameModeChangedEvent { - player, - name, - from, - to, - }; - for mod_hook in super::entered(mods, "player_game_mode_changed") { - if matches!( - mod_hook.player_game_mode_changed(host, &mut event), - Flow::Cancel - ) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::player_insert_lectern_book`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerInsertLecternBookEvent<'a> { - pub player: u128, - pub name: &'a str, - pub pos: [i32; 3], - pub book: &'a str, -} -pub fn dispatch_player_insert_lectern_book( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - pos: [i32; 3], - book: &str, -) { - let mut event = PlayerInsertLecternBookEvent { - player, - name, - pos, - book, - }; - for mod_hook in super::entered(mods, "player_insert_lectern_book") { - mod_hook.player_insert_lectern_book(host, &mut event); - } -} -/// What [`Hooks::player_item_frame_change`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerItemFrameChangeEvent<'a> { - pub player: u128, - pub name: &'a str, - pub entity_id: u32, - pub action: &'a str, - pub item: &'a str, - pub rotation: u8, -} -pub fn dispatch_player_item_frame_change( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - entity_id: u32, - action: &str, - item: &str, - rotation: u8, -) { - let mut event = PlayerItemFrameChangeEvent { - player, - name, - entity_id, - action, - item, - rotation, - }; - for mod_hook in super::entered(mods, "player_item_frame_change") { - mod_hook.player_item_frame_change(host, &mut event); - } -} -/// What [`Hooks::player_item_mend`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerItemMendEvent<'a> { - pub player: u128, - pub name: &'a str, - pub item: &'a str, - pub repair: u32, - pub experience: u32, -} -pub fn dispatch_player_item_mend( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - item: &str, - repair: u32, - experience: u32, -) { - let mut event = PlayerItemMendEvent { - player, - name, - item, - repair, - experience, - }; - for mod_hook in super::entered(mods, "player_item_mend") { - mod_hook.player_item_mend(host, &mut event); - } -} -/// What [`Hooks::player_lectern_page_change`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerLecternPageChangeEvent<'a> { - pub player: u128, - pub name: &'a str, - pub pos: [i32; 3], - pub page: i32, -} -pub fn dispatch_player_lectern_page_change( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - pos: [i32; 3], - page: i32, -) { - let mut event = PlayerLecternPageChangeEvent { - player, - name, - pos, - page, - }; - for mod_hook in super::entered(mods, "player_lectern_page_change") { - mod_hook.player_lectern_page_change(host, &mut event); - } -} -/// What [`Hooks::player_load_from_file`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerLoadFromFileEvent<'a> { - pub player: u128, - pub name: &'a str, - pub directory: &'a std::path::Path, -} -pub fn dispatch_player_load_from_file( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - directory: &std::path::Path, -) { - let mut event = PlayerLoadFromFileEvent { - player, - name, - directory, - }; - for mod_hook in super::entered(mods, "player_load_from_file") { - mod_hook.player_load_from_file(host, &mut event); - } -} -/// What [`Hooks::player_login`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerLoginEvent<'a> { - pub player: u128, - pub name: &'a str, - pub reason: &'a mut Option, -} -pub fn dispatch_player_login( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - reason: &mut Option, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = PlayerLoginEvent { - player, - name, - reason, - }; - for mod_hook in super::entered(mods, "player_login") { - if matches!(mod_hook.player_login(host, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::player_save_to_file`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerSaveToFileEvent<'a> { - pub player: u128, - pub name: &'a str, - pub directory: &'a std::path::Path, -} -pub fn dispatch_player_save_to_file( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - directory: &std::path::Path, -) { - let mut event = PlayerSaveToFileEvent { - player, - name, - directory, - }; - for mod_hook in super::entered(mods, "player_save_to_file") { - mod_hook.player_save_to_file(host, &mut event); - } -} -/// What [`Hooks::player_sprint`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerSprintEvent<'a> { - pub player: u128, - pub name: &'a str, - pub sprinting: bool, -} -pub fn dispatch_player_sprint( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - sprinting: bool, -) { - let mut event = PlayerSprintEvent { - player, - name, - sprinting, - }; - for mod_hook in super::entered(mods, "player_sprint") { - mod_hook.player_sprint(host, &mut event); - } -} -/// What [`Hooks::player_swap_hands`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerSwapHandsEvent<'a> { - pub player: u128, - pub name: &'a str, -} -pub fn dispatch_player_swap_hands( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = PlayerSwapHandsEvent { player, name }; - for mod_hook in super::entered(mods, "player_swap_hands") { - if matches!(mod_hook.player_swap_hands(host, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::player_take_lectern_book`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerTakeLecternBookEvent<'a> { - pub player: u128, - pub name: &'a str, - pub pos: [i32; 3], - pub book: &'a str, -} -pub fn dispatch_player_take_lectern_book( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - pos: [i32; 3], - book: &str, -) { - let mut event = PlayerTakeLecternBookEvent { - player, - name, - pos, - book, - }; - for mod_hook in super::entered(mods, "player_take_lectern_book") { - mod_hook.player_take_lectern_book(host, &mut event); - } -} -/// What [`Hooks::player_teleported`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerTeleportedEvent<'a> { - pub player: u128, - pub name: &'a str, - pub from: [f64; 3], - pub to: [f64; 3], - pub dimension: &'a str, -} -pub fn dispatch_player_teleported( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - from: [f64; 3], - to: [f64; 3], - dimension: &str, -) { - let mut event = PlayerTeleportedEvent { - player, - name, - from, - to, - dimension, - }; - for mod_hook in super::entered(mods, "player_teleported") { - mod_hook.player_teleported(host, &mut event); - } -} -/// What [`Hooks::player_totem`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerTotemEvent<'a> { - pub player: u128, - pub name: &'a str, -} -pub fn dispatch_player_totem( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, -) { - let mut event = PlayerTotemEvent { player, name }; - for mod_hook in super::entered(mods, "player_totem") { - mod_hook.player_totem(host, &mut event); - } -} -/// What [`Hooks::resource_pack_status`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ResourcePackStatusEvent<'a> { - pub player: u128, - pub name: &'a str, - pub status: i32, -} -pub fn dispatch_resource_pack_status( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - status: i32, -) { - let mut event = ResourcePackStatusEvent { - player, - name, - status, - }; - for mod_hook in super::entered(mods, "resource_pack_status") { - mod_hook.resource_pack_status(host, &mut event); - } -} -/// What [`Hooks::spawn_change`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct SpawnChangeEvent { - pub pos: [i32; 3], - pub angle: [f32; 2], -} -pub fn dispatch_spawn_change( - host: &mut dyn Host, - mods: &[Box], - pos: [i32; 3], - angle: [f32; 2], -) { - let mut event = SpawnChangeEvent { pos, angle }; - for mod_hook in super::entered(mods, "spawn_change") { - mod_hook.spawn_change(host, &mut event); - } -} -/// What [`Hooks::tnt_primed`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct TntPrimedEvent { - pub player: Option, - pub pos: [i32; 3], -} -pub fn dispatch_tnt_primed( - host: &mut dyn Host, - mods: &[Box], - player: Option, - pos: [i32; 3], - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = TntPrimedEvent { player, pos }; - for mod_hook in super::entered(mods, "tnt_primed") { - if matches!(mod_hook.tnt_primed(host, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::vehicle_create`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct VehicleCreateEvent<'a> { - pub kind: &'a str, - pub pos: [f64; 3], -} -pub fn dispatch_vehicle_create( - host: &mut dyn Host, - mods: &[Box], - kind: &str, - pos: [f64; 3], -) { - let mut event = VehicleCreateEvent { kind, pos }; - for mod_hook in super::entered(mods, "vehicle_create") { - mod_hook.vehicle_create(host, &mut event); - } -} -/// What [`Hooks::vehicle_damage`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct VehicleDamageEvent<'a> { - pub kind: &'a str, - pub damage: f32, -} -pub fn dispatch_vehicle_damage( - host: &mut dyn Host, - mods: &[Box], - kind: &str, - damage: &mut f32, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = VehicleDamageEvent { - kind, - damage: *damage, - }; - for mod_hook in super::entered(mods, "vehicle_damage") { - if matches!(mod_hook.vehicle_damage(host, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } - *damage = event.damage; -} -/// What [`Hooks::vehicle_destroy`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct VehicleDestroyEvent<'a> { - pub kind: &'a str, - pub pos: [f64; 3], -} -pub fn dispatch_vehicle_destroy( - host: &mut dyn Host, - mods: &[Box], - kind: &str, - pos: [f64; 3], -) { - let mut event = VehicleDestroyEvent { kind, pos }; - for mod_hook in super::entered(mods, "vehicle_destroy") { - mod_hook.vehicle_destroy(host, &mut event); - } -} -/// What [`Hooks::vehicle_moved`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct VehicleMovedEvent<'a> { - pub player: Option, - pub kind: &'a str, - pub from: [f64; 3], - pub to: [f64; 3], -} -pub fn dispatch_vehicle_moved( - host: &mut dyn Host, - mods: &[Box], - player: Option, - kind: &str, - from: [f64; 3], - to: [f64; 3], -) { - let mut event = VehicleMovedEvent { - player, - kind, - from, - to, - }; - for mod_hook in super::entered(mods, "vehicle_moved") { - mod_hook.vehicle_moved(host, &mut event); - } -} -/// What [`Hooks::world_border_bounds_changed`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct WorldBorderBoundsChangedEvent {} -pub fn dispatch_world_border_bounds_changed(host: &mut dyn Host, mods: &[Box]) { - let mut event = WorldBorderBoundsChangedEvent {}; - for mod_hook in super::entered(mods, "world_border_bounds_changed") { - mod_hook.world_border_bounds_changed(host, &mut event); - } -} -/// What [`Hooks::world_border_center_changed`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct WorldBorderCenterChangedEvent {} -pub fn dispatch_world_border_center_changed(host: &mut dyn Host, mods: &[Box]) { - let mut event = WorldBorderCenterChangedEvent {}; - for mod_hook in super::entered(mods, "world_border_center_changed") { - mod_hook.world_border_center_changed(host, &mut event); - } -} -/// What [`Hooks::world_game_rule_change`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct WorldGameRuleChangeEvent<'a> { - pub player: u128, - pub name: &'a str, - pub rule: &'a str, - pub value: &'a str, -} -pub fn dispatch_world_game_rule_change( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - rule: &str, - value: &str, -) { - let mut event = WorldGameRuleChangeEvent { - player, - name, - rule, - value, - }; - for mod_hook in super::entered(mods, "world_game_rule_change") { - mod_hook.world_game_rule_change(host, &mut event); - } -} -/// What [`Hooks::note_play`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct NotePlayEvent<'a> { - pub dimension: LevelDimension, - pub player: Option, - pub pos: BlockPos, - pub instrument: &'a str, - pub note: i32, -} -pub fn dispatch_note_play( - level: &dyn LevelHost, - mods: &[Box], - dimension: LevelDimension, - player: Option, - pos: BlockPos, - instrument: &str, - note: i32, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = NotePlayEvent { - dimension, - player, - pos, - instrument, - note, - }; - for mod_hook in super::entered(mods, "note_play") { - if matches!(mod_hook.note_play(level, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::player_advancement_criterion_grant`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerAdvancementCriterionGrantEvent<'a> { - pub player: u128, - pub name: &'a str, - pub advancement: &'a str, - pub criterion: &'a str, -} -pub fn dispatch_player_advancement_criterion_grant( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - advancement: &str, - criterion: &str, -) { - let mut event = PlayerAdvancementCriterionGrantEvent { - player, - name, - advancement, - criterion, - }; - for mod_hook in super::entered(mods, "player_advancement_criterion_grant") { - mod_hook.player_advancement_criterion_grant(host, &mut event); - } -} -/// What [`Hooks::player_advancement_done`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerAdvancementDoneEvent<'a> { - pub player: u128, - pub name: &'a str, - pub advancement: &'a str, -} -pub fn dispatch_player_advancement_done( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - advancement: &str, -) { - let mut event = PlayerAdvancementDoneEvent { - player, - name, - advancement, - }; - for mod_hook in super::entered(mods, "player_advancement_done") { - mod_hook.player_advancement_done(host, &mut event); - } -} -/// What [`Hooks::player_advancement_progress`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerAdvancementProgressEvent<'a> { - pub player: u128, - pub name: &'a str, - pub advancement: &'a str, - pub criterion: &'a str, - pub granted: bool, -} -pub fn dispatch_player_advancement_progress( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - advancement: &str, - criterion: &str, - granted: bool, -) { - let mut event = PlayerAdvancementProgressEvent { - player, - name, - advancement, - criterion, - granted, - }; - for mod_hook in super::entered(mods, "player_advancement_progress") { - mod_hook.player_advancement_progress(host, &mut event); - } -} -/// What [`Hooks::player_arm_swing`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerArmSwingEvent<'a> { - pub player: u128, - pub name: &'a str, - pub hand: &'a str, -} -pub fn dispatch_player_arm_swing( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - hand: &str, -) { - let mut event = PlayerArmSwingEvent { player, name, hand }; - for mod_hook in super::entered(mods, "player_arm_swing") { - mod_hook.player_arm_swing(host, &mut event); - } -} -/// What [`Hooks::player_attack_cooldown_reset`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerAttackCooldownResetEvent<'a> { - pub player: u128, - pub name: &'a str, -} -pub fn dispatch_player_attack_cooldown_reset( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, -) { - let mut event = PlayerAttackCooldownResetEvent { player, name }; - for mod_hook in super::entered(mods, "player_attack_cooldown_reset") { - mod_hook.player_attack_cooldown_reset(host, &mut event); - } -} -/// What [`Hooks::player_bed_enter`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerBedEnterEvent<'a> { - pub player: u128, - pub name: &'a str, - pub pos: [i32; 3], -} -pub fn dispatch_player_bed_enter( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - pos: [i32; 3], - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = PlayerBedEnterEvent { player, name, pos }; - for mod_hook in super::entered(mods, "player_bed_enter") { - if matches!(mod_hook.player_bed_enter(host, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::player_bed_leave`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerBedLeaveEvent<'a> { - pub player: u128, - pub name: &'a str, - pub pos: [i32; 3], -} -pub fn dispatch_player_bed_leave( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - pos: [i32; 3], -) { - let mut event = PlayerBedLeaveEvent { player, name, pos }; - for mod_hook in super::entered(mods, "player_bed_leave") { - mod_hook.player_bed_leave(host, &mut event); - } -} -/// What [`Hooks::player_bucket_entity`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerBucketEntityEvent<'a> { - pub player: u128, - pub name: &'a str, - pub entity_id: i32, - pub entity: &'a str, -} -pub fn dispatch_player_bucket_entity( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - entity_id: i32, - entity: &str, -) { - let mut event = PlayerBucketEntityEvent { - player, - name, - entity_id, - entity, - }; - for mod_hook in super::entered(mods, "player_bucket_entity") { - mod_hook.player_bucket_entity(host, &mut event); - } -} -/// What [`Hooks::player_bundle_item_select`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerBundleItemSelectEvent<'a> { - pub player: u128, - pub name: &'a str, - pub slot: i32, - pub index: i32, -} -pub fn dispatch_player_bundle_item_select( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - slot: i32, - index: i32, -) { - let mut event = PlayerBundleItemSelectEvent { - player, - name, - slot, - index, - }; - for mod_hook in super::entered(mods, "player_bundle_item_select") { - mod_hook.player_bundle_item_select(host, &mut event); - } -} -/// What [`Hooks::player_change_beacon_effect`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerChangeBeaconEffectEvent<'a> { - pub player: u128, - pub name: &'a str, - pub pos: [i32; 3], - pub primary: &'a str, - pub secondary: &'a str, -} -pub fn dispatch_player_change_beacon_effect( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - pos: [i32; 3], - primary: &str, - secondary: &str, -) { - let mut event = PlayerChangeBeaconEffectEvent { - player, - name, - pos, - primary, - secondary, - }; - for mod_hook in super::entered(mods, "player_change_beacon_effect") { - mod_hook.player_change_beacon_effect(host, &mut event); - } -} -/// What [`Hooks::player_change_main_hand`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerChangeMainHandEvent<'a> { - pub player: u128, - pub name: &'a str, - pub left: bool, -} -pub fn dispatch_player_change_main_hand( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - left: bool, -) { - let mut event = PlayerChangeMainHandEvent { player, name, left }; - for mod_hook in super::entered(mods, "player_change_main_hand") { - mod_hook.player_change_main_hand(host, &mut event); - } -} -/// What [`Hooks::player_client_loaded_world`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerClientLoadedWorldEvent<'a> { - pub player: u128, - pub name: &'a str, -} -pub fn dispatch_player_client_loaded_world( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, -) { - let mut event = PlayerClientLoadedWorldEvent { player, name }; - for mod_hook in super::entered(mods, "player_client_loaded_world") { - mod_hook.player_client_loaded_world(host, &mut event); - } -} -/// What [`Hooks::player_deep_sleep`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerDeepSleepEvent<'a> { - pub player: u128, - pub name: &'a str, -} -pub fn dispatch_player_deep_sleep( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, -) { - let mut event = PlayerDeepSleepEvent { player, name }; - for mod_hook in super::entered(mods, "player_deep_sleep") { - mod_hook.player_deep_sleep(host, &mut event); - } -} -/// What [`Hooks::player_edit_book`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerEditBookEvent<'a> { - pub player: u128, - pub name: &'a str, - pub pages: i32, - pub signing: bool, -} -pub fn dispatch_player_edit_book( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - pages: i32, - signing: bool, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = PlayerEditBookEvent { - player, - name, - pages, - signing, - }; - for mod_hook in super::entered(mods, "player_edit_book") { - if matches!(mod_hook.player_edit_book(host, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::player_egg_throw`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerEggThrowEvent<'a> { - pub player: u128, - pub name: &'a str, -} -pub fn dispatch_player_egg_throw( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, -) { - let mut event = PlayerEggThrowEvent { player, name }; - for mod_hook in super::entered(mods, "player_egg_throw") { - mod_hook.player_egg_throw(host, &mut event); - } -} -/// What [`Hooks::player_elytra_boost`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerElytraBoostEvent<'a> { - pub player: u128, - pub name: &'a str, -} -pub fn dispatch_player_elytra_boost( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, -) { - let mut event = PlayerElytraBoostEvent { player, name }; - for mod_hook in super::entered(mods, "player_elytra_boost") { - mod_hook.player_elytra_boost(host, &mut event); - } -} -/// What [`Hooks::player_exp_change`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerExpChangeEvent<'a> { - pub player: u128, - pub name: &'a str, - pub amount: i32, -} -pub fn dispatch_player_exp_change( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - amount: &mut i32, -) { - let mut event = PlayerExpChangeEvent { - player, - name, - amount: *amount, - }; - for mod_hook in super::entered(mods, "player_exp_change") { - mod_hook.player_exp_change(host, &mut event); - } - *amount = event.amount; -} -/// What [`Hooks::player_exp_cooldown_change`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerExpCooldownChangeEvent<'a> { - pub player: u128, - pub name: &'a str, - pub ticks: u32, -} -pub fn dispatch_player_exp_cooldown_change( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - ticks: u32, -) { - let mut event = PlayerExpCooldownChangeEvent { - player, - name, - ticks, - }; - for mod_hook in super::entered(mods, "player_exp_cooldown_change") { - mod_hook.player_exp_cooldown_change(host, &mut event); - } -} -/// What [`Hooks::player_input`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerInputEvent<'a> { - pub player: u128, - pub name: &'a str, - pub flags: i32, -} -pub fn dispatch_player_input( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - flags: i32, -) { - let mut event = PlayerInputEvent { - player, - name, - flags, - }; - for mod_hook in super::entered(mods, "player_input") { - mod_hook.player_input(host, &mut event); - } -} -/// What [`Hooks::player_item_cooldown`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerItemCooldownEvent<'a> { - pub player: u128, - pub name: &'a str, - pub item: &'a str, - pub ticks: i32, -} -pub fn dispatch_player_item_cooldown( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - item: &str, - ticks: i32, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = PlayerItemCooldownEvent { - player, - name, - item, - ticks, - }; - for mod_hook in super::entered(mods, "player_item_cooldown") { - if matches!( - mod_hook.player_item_cooldown(host, &mut event), - Flow::Cancel - ) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::player_item_held`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerItemHeldEvent<'a> { - pub player: u128, - pub name: &'a str, - pub from_slot: i32, - pub to_slot: i32, -} -pub fn dispatch_player_item_held( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - from_slot: i32, - to_slot: i32, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = PlayerItemHeldEvent { - player, - name, - from_slot, - to_slot, - }; - for mod_hook in super::entered(mods, "player_item_held") { - if matches!(mod_hook.player_item_held(host, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::player_jump`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerJumpEvent<'a> { - pub player: u128, - pub name: &'a str, -} -pub fn dispatch_player_jump( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, -) { - let mut event = PlayerJumpEvent { player, name }; - for mod_hook in super::entered(mods, "player_jump") { - mod_hook.player_jump(host, &mut event); - } -} -/// What [`Hooks::player_kicked`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerKickedEvent<'a> { - pub player: u128, - pub name: &'a str, - pub reason: &'a str, -} -pub fn dispatch_player_kicked( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - reason: &str, -) { - let mut event = PlayerKickedEvent { - player, - name, - reason, - }; - for mod_hook in super::entered(mods, "player_kicked") { - mod_hook.player_kicked(host, &mut event); - } -} -/// What [`Hooks::player_launch_projectile`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerLaunchProjectileEvent<'a> { - pub player: u128, - pub name: &'a str, - pub item: &'a str, -} -pub fn dispatch_player_launch_projectile( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - item: &str, -) { - let mut event = PlayerLaunchProjectileEvent { player, name, item }; - for mod_hook in super::entered(mods, "player_launch_projectile") { - mod_hook.player_launch_projectile(host, &mut event); - } -} -/// What [`Hooks::player_level_change`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerLevelChangeEvent<'a> { - pub player: u128, - pub name: &'a str, - pub from: i32, - pub to: i32, -} -pub fn dispatch_player_level_change( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - from: i32, - to: i32, -) { - let mut event = PlayerLevelChangeEvent { - player, - name, - from, - to, - }; - for mod_hook in super::entered(mods, "player_level_change") { - mod_hook.player_level_change(host, &mut event); - } -} -/// What [`Hooks::player_locale_change`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerLocaleChangeEvent<'a> { - pub player: u128, - pub name: &'a str, - pub locale: &'a str, -} -pub fn dispatch_player_locale_change( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - locale: &str, -) { - let mut event = PlayerLocaleChangeEvent { - player, - name, - locale, - }; - for mod_hook in super::entered(mods, "player_locale_change") { - mod_hook.player_locale_change(host, &mut event); - } -} -/// What [`Hooks::player_loom_pattern_select`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerLoomPatternSelectEvent<'a> { - pub player: u128, - pub name: &'a str, - pub pattern: i32, -} -pub fn dispatch_player_loom_pattern_select( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - pattern: i32, -) { - let mut event = PlayerLoomPatternSelectEvent { - player, - name, - pattern, - }; - for mod_hook in super::entered(mods, "player_loom_pattern_select") { - mod_hook.player_loom_pattern_select(host, &mut event); - } -} -/// What [`Hooks::player_map_filled`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerMapFilledEvent<'a> { - pub player: u128, - pub name: &'a str, - pub map_id: i32, -} -pub fn dispatch_player_map_filled( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - map_id: i32, -) { - let mut event = PlayerMapFilledEvent { - player, - name, - map_id, - }; - for mod_hook in super::entered(mods, "player_map_filled") { - mod_hook.player_map_filled(host, &mut event); - } -} -/// What [`Hooks::player_name_entity`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerNameEntityEvent<'a> { - pub player: u128, - pub name: &'a str, - pub entity_id: u32, - pub entity_name: &'a str, -} -pub fn dispatch_player_name_entity( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - entity_id: u32, - entity_name: &str, -) { - let mut event = PlayerNameEntityEvent { - player, - name, - entity_id, - entity_name, - }; - for mod_hook in super::entered(mods, "player_name_entity") { - mod_hook.player_name_entity(host, &mut event); - } -} -/// What [`Hooks::player_open_sign`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerOpenSignEvent<'a> { - pub player: u128, - pub name: &'a str, - pub pos: [i32; 3], - pub front: bool, -} -pub fn dispatch_player_open_sign( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - pos: [i32; 3], - front: bool, -) { - let mut event = PlayerOpenSignEvent { - player, - name, - pos, - front, - }; - for mod_hook in super::entered(mods, "player_open_sign") { - mod_hook.player_open_sign(host, &mut event); - } -} -/// What [`Hooks::player_pick_item`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerPickItemEvent<'a> { - pub player: u128, - pub name: &'a str, - pub from_entity: bool, - pub pos: [i32; 3], - pub entity_id: i32, -} -pub fn dispatch_player_pick_item( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - from_entity: bool, - pos: [i32; 3], - entity_id: i32, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = PlayerPickItemEvent { - player, - name, - from_entity, - pos, - entity_id, - }; - for mod_hook in super::entered(mods, "player_pick_item") { - if matches!(mod_hook.player_pick_item(host, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::player_pickup_arrow`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerPickupArrowEvent<'a> { - pub player: u128, - pub name: &'a str, - pub entity_id: u32, - pub item: &'a str, -} -pub fn dispatch_player_pickup_arrow( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - entity_id: u32, - item: &str, -) { - let mut event = PlayerPickupArrowEvent { - player, - name, - entity_id, - item, - }; - for mod_hook in super::entered(mods, "player_pickup_arrow") { - mod_hook.player_pickup_arrow(host, &mut event); - } -} -/// What [`Hooks::player_portal`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerPortalEvent<'a> { - pub player: u128, - pub name: &'a str, - pub from_dimension: &'a str, - pub to_dimension: &'a str, - pub cause: &'a str, -} -pub fn dispatch_player_portal( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - from_dimension: &str, - to_dimension: &str, - cause: &str, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = PlayerPortalEvent { - player, - name, - from_dimension, - to_dimension, - cause, - }; - for mod_hook in super::entered(mods, "player_portal") { - if matches!(mod_hook.player_portal(host, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::player_ready_arrow`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerReadyArrowEvent<'a> { - pub player: u128, - pub name: &'a str, - pub arrow: &'a str, -} -pub fn dispatch_player_ready_arrow( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - arrow: &str, -) { - let mut event = PlayerReadyArrowEvent { - player, - name, - arrow, - }; - for mod_hook in super::entered(mods, "player_ready_arrow") { - mod_hook.player_ready_arrow(host, &mut event); - } -} -/// What [`Hooks::player_recipe_book_click`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerRecipeBookClickEvent<'a> { - pub player: u128, - pub name: &'a str, - pub recipe: &'a str, - pub all: bool, -} -pub fn dispatch_player_recipe_book_click( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - recipe: &str, - all: bool, -) { - let mut event = PlayerRecipeBookClickEvent { - player, - name, - recipe, - all, - }; - for mod_hook in super::entered(mods, "player_recipe_book_click") { - mod_hook.player_recipe_book_click(host, &mut event); - } -} -/// What [`Hooks::player_recipe_book_settings_change`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerRecipeBookSettingsChangeEvent<'a> { - pub player: u128, - pub name: &'a str, - pub book: i32, - pub open: bool, - pub filtering: bool, -} -pub fn dispatch_player_recipe_book_settings_change( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - book: i32, - open: bool, - filtering: bool, -) { - let mut event = PlayerRecipeBookSettingsChangeEvent { - player, - name, - book, - open, - filtering, - }; - for mod_hook in super::entered(mods, "player_recipe_book_settings_change") { - mod_hook.player_recipe_book_settings_change(host, &mut event); - } -} -/// What [`Hooks::player_recipe_discovered`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerRecipeDiscoveredEvent<'a> { - pub player: u128, - pub name: &'a str, - pub recipe: &'a str, -} -pub fn dispatch_player_recipe_discovered( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - recipe: &str, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = PlayerRecipeDiscoveredEvent { - player, - name, - recipe, - }; - for mod_hook in super::entered(mods, "player_recipe_discovered") { - if matches!( - mod_hook.player_recipe_discovered(host, &mut event), - Flow::Cancel - ) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::player_riptide`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerRiptideEvent<'a> { - pub player: u128, - pub name: &'a str, -} -pub fn dispatch_player_riptide( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, -) { - let mut event = PlayerRiptideEvent { player, name }; - for mod_hook in super::entered(mods, "player_riptide") { - mod_hook.player_riptide(host, &mut event); - } -} -/// What [`Hooks::player_shear_entity`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerShearEntityEvent<'a> { - pub player: u128, - pub name: &'a str, - pub entity_id: i32, - pub off_hand: bool, -} -pub fn dispatch_player_shear_entity( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - entity_id: i32, - off_hand: bool, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = PlayerShearEntityEvent { - player, - name, - entity_id, - off_hand, - }; - for mod_hook in super::entered(mods, "player_shear_entity") { - if matches!(mod_hook.player_shear_entity(host, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::player_shield_disable`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerShieldDisableEvent<'a> { - pub player: u128, - pub name: &'a str, - pub ticks: u32, -} -pub fn dispatch_player_shield_disable( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - ticks: u32, -) { - let mut event = PlayerShieldDisableEvent { - player, - name, - ticks, - }; - for mod_hook in super::entered(mods, "player_shield_disable") { - mod_hook.player_shield_disable(host, &mut event); - } -} -/// What [`Hooks::player_spawn_point_set`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerSpawnPointSetEvent<'a> { - pub player: u128, - pub name: &'a str, - pub dimension: &'a str, - pub pos: [i32; 3], - pub cause: &'a str, -} -pub fn dispatch_player_spawn_point_set( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - dimension: &str, - pos: [i32; 3], - cause: &str, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = PlayerSpawnPointSetEvent { - player, - name, - dimension, - pos, - cause, - }; - for mod_hook in super::entered(mods, "player_spawn_point_set") { - if matches!( - mod_hook.player_spawn_point_set(host, &mut event), - Flow::Cancel - ) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::player_stat_award`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerStatAwardEvent<'a> { - pub player: u128, - pub name: &'a str, - pub stat_type: &'a str, - pub stat: &'a str, - pub value: i32, -} -pub fn dispatch_player_stat_award( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - stat_type: &str, - stat: &str, - value: i32, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = PlayerStatAwardEvent { - player, - name, - stat_type, - stat, - value, - }; - for mod_hook in super::entered(mods, "player_stat_award") { - if matches!(mod_hook.player_stat_award(host, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::player_stonecutter_recipe_select`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerStonecutterRecipeSelectEvent<'a> { - pub player: u128, - pub name: &'a str, - pub recipe: i32, -} -pub fn dispatch_player_stonecutter_recipe_select( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - recipe: i32, -) { - let mut event = PlayerStonecutterRecipeSelectEvent { - player, - name, - recipe, - }; - for mod_hook in super::entered(mods, "player_stonecutter_recipe_select") { - mod_hook.player_stonecutter_recipe_select(host, &mut event); - } -} -/// What [`Hooks::player_stop_spectating_entity`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerStopSpectatingEntityEvent<'a> { - pub player: u128, - pub name: &'a str, - pub entity_id: i32, -} -pub fn dispatch_player_stop_spectating_entity( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - entity_id: i32, -) { - let mut event = PlayerStopSpectatingEntityEvent { - player, - name, - entity_id, - }; - for mod_hook in super::entered(mods, "player_stop_spectating_entity") { - mod_hook.player_stop_spectating_entity(host, &mut event); - } -} -/// What [`Hooks::player_stop_using_item`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerStopUsingItemEvent<'a> { - pub player: u128, - pub name: &'a str, -} -pub fn dispatch_player_stop_using_item( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, -) { - let mut event = PlayerStopUsingItemEvent { player, name }; - for mod_hook in super::entered(mods, "player_stop_using_item") { - mod_hook.player_stop_using_item(host, &mut event); - } -} -/// What [`Hooks::player_swap_with_equipment_slot`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerSwapWithEquipmentSlotEvent<'a> { - pub player: u128, - pub name: &'a str, - pub slot: &'a str, -} -pub fn dispatch_player_swap_with_equipment_slot( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - slot: &str, -) { - let mut event = PlayerSwapWithEquipmentSlotEvent { player, name, slot }; - for mod_hook in super::entered(mods, "player_swap_with_equipment_slot") { - mod_hook.player_swap_with_equipment_slot(host, &mut event); - } -} -/// What [`Hooks::player_teleport_end_gateway`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerTeleportEndGatewayEvent<'a> { - pub player: u128, - pub name: &'a str, - pub gateway: [i32; 3], -} -pub fn dispatch_player_teleport_end_gateway( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - gateway: [i32; 3], - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = PlayerTeleportEndGatewayEvent { - player, - name, - gateway, - }; - for mod_hook in super::entered(mods, "player_teleport_end_gateway") { - if matches!( - mod_hook.player_teleport_end_gateway(host, &mut event), - Flow::Cancel - ) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::player_toggle_entity_age_lock`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerToggleEntityAgeLockEvent<'a> { - pub player: u128, - pub name: &'a str, - pub entity_id: u32, - pub locked: bool, -} -pub fn dispatch_player_toggle_entity_age_lock( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - entity_id: u32, - locked: bool, -) { - let mut event = PlayerToggleEntityAgeLockEvent { - player, - name, - entity_id, - locked, - }; - for mod_hook in super::entered(mods, "player_toggle_entity_age_lock") { - mod_hook.player_toggle_entity_age_lock(host, &mut event); - } -} -/// What [`Hooks::player_toggle_sneak`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerToggleSneakEvent<'a> { - pub player: u128, - pub name: &'a str, - pub sneaking: bool, -} -pub fn dispatch_player_toggle_sneak( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - sneaking: bool, -) { - let mut event = PlayerToggleSneakEvent { - player, - name, - sneaking, - }; - for mod_hook in super::entered(mods, "player_toggle_sneak") { - mod_hook.player_toggle_sneak(host, &mut event); - } -} -/// What [`Hooks::player_trade`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerTradeEvent<'a> { - pub player: u128, - pub name: &'a str, - pub entity_id: u32, -} -pub fn dispatch_player_trade( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - entity_id: u32, -) { - let mut event = PlayerTradeEvent { - player, - name, - entity_id, - }; - for mod_hook in super::entered(mods, "player_trade") { - mod_hook.player_trade(host, &mut event); - } -} -/// What [`Hooks::player_unleash_entity`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerUnleashEntityEvent<'a> { - pub player: u128, - pub name: &'a str, - pub entity_id: u32, -} -pub fn dispatch_player_unleash_entity( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - entity_id: u32, -) { - let mut event = PlayerUnleashEntityEvent { - player, - name, - entity_id, - }; - for mod_hook in super::entered(mods, "player_unleash_entity") { - mod_hook.player_unleash_entity(host, &mut event); - } -} -/// What [`Hooks::player_velocity`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PlayerVelocityEvent<'a> { - pub player: u128, - pub name: &'a str, - pub velocity: [f64; 3], -} -pub fn dispatch_player_velocity( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - velocity: [f64; 3], -) { - let mut event = PlayerVelocityEvent { - player, - name, - velocity, - }; - for mod_hook in super::entered(mods, "player_velocity") { - mod_hook.player_velocity(host, &mut event); - } -} -/// What [`Hooks::portal_create`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PortalCreateEvent<'a> { - pub dimension: &'a str, - pub pos: [i32; 3], -} -pub fn dispatch_portal_create( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - pos: [i32; 3], -) { - let mut event = PortalCreateEvent { dimension, pos }; - for mod_hook in super::entered(mods, "portal_create") { - mod_hook.portal_create(host, &mut event); - } -} -/// What [`Hooks::potion_splash`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PotionSplashEvent<'a> { - pub item: &'a str, - pub pos: [f64; 3], -} -pub fn dispatch_potion_splash( - host: &mut dyn Host, - mods: &[Box], - item: &str, - pos: [f64; 3], -) { - let mut event = PotionSplashEvent { item, pos }; - for mod_hook in super::entered(mods, "potion_splash") { - mod_hook.potion_splash(host, &mut event); - } -} -/// What [`Hooks::prepare_anvil`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PrepareAnvilEvent<'a> { - pub player: u128, - pub name: &'a str, - pub pos: [i32; 3], - pub cost: i32, -} -pub fn dispatch_prepare_anvil( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - pos: [i32; 3], - cost: &mut i32, - result: &mut Option, -) { - if result.is_some() { - return; - } - let mut event = PrepareAnvilEvent { - player, - name, - pos, - cost: *cost, - }; - for mod_hook in super::entered(mods, "prepare_anvil") { - if let Some(replacement) = mod_hook.prepare_anvil(host, &mut event) { - *result = Some(replacement); - break; - } - } - *cost = event.cost; -} -/// What [`Hooks::prepare_grindstone`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PrepareGrindstoneEvent<'a> { - pub player: u128, - pub name: &'a str, - pub pos: [i32; 3], -} -pub fn dispatch_prepare_grindstone( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - pos: [i32; 3], - result: &mut Option, -) { - if result.is_some() { - return; - } - let mut event = PrepareGrindstoneEvent { player, name, pos }; - for mod_hook in super::entered(mods, "prepare_grindstone") { - if let Some(replacement) = mod_hook.prepare_grindstone(host, &mut event) { - *result = Some(replacement); - break; - } - } -} -/// What [`Hooks::prepare_hopper_transfer`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PrepareHopperTransferEvent { - pub pos: [i32; 3], -} -pub fn dispatch_prepare_hopper_transfer(host: &dyn Host, mods: &[Box], pos: [i32; 3]) { - let mut event = PrepareHopperTransferEvent { pos }; - for mod_hook in super::entered(mods, "prepare_hopper_transfer") { - mod_hook.prepare_hopper_transfer(host, &mut event); - } -} -/// What [`Hooks::prepare_item_craft`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PrepareItemCraftEvent<'a> { - pub player: u128, - pub name: &'a str, - pub pos: Option<[i32; 3]>, -} -pub fn dispatch_prepare_item_craft( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - pos: Option<[i32; 3]>, - result: &mut Option, -) { - if result.is_some() { - return; - } - let mut event = PrepareItemCraftEvent { player, name, pos }; - for mod_hook in super::entered(mods, "prepare_item_craft") { - if let Some(replacement) = mod_hook.prepare_item_craft(host, &mut event) { - *result = Some(replacement); - break; - } - } -} -/// What [`Hooks::prepare_item_enchant`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PrepareItemEnchantEvent<'a> { - pub player: u128, - pub name: &'a str, - pub pos: [i32; 3], - pub costs: &'a mut [i32; 3], -} -pub fn dispatch_prepare_item_enchant( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - pos: [i32; 3], - costs: &mut [i32; 3], -) { - let mut event = PrepareItemEnchantEvent { - player, - name, - pos, - costs, - }; - for mod_hook in super::entered(mods, "prepare_item_enchant") { - mod_hook.prepare_item_enchant(host, &mut event); - } -} -/// What [`Hooks::prepare_smithing`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct PrepareSmithingEvent<'a> { - pub player: u128, - pub name: &'a str, - pub pos: [i32; 3], -} -pub fn dispatch_prepare_smithing( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - pos: [i32; 3], - result: &mut Option, -) { - if result.is_some() { - return; - } - let mut event = PrepareSmithingEvent { player, name, pos }; - for mod_hook in super::entered(mods, "prepare_smithing") { - if let Some(replacement) = mod_hook.prepare_smithing(host, &mut event) { - *result = Some(replacement); - break; - } - } -} -/// What [`Hooks::projectile_launch`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ProjectileLaunchEvent<'a> { - pub player: Option, - pub name: &'a str, - pub entity: u32, - pub kind: &'a str, -} -pub fn dispatch_projectile_launch( - host: &mut dyn Host, - mods: &[Box], - player: Option, - name: &str, - entity: u32, - kind: &str, -) { - let mut event = ProjectileLaunchEvent { - player, - name, - entity, - kind, - }; - for mod_hook in super::entered(mods, "projectile_launch") { - mod_hook.projectile_launch(host, &mut event); - } -} -/// What [`Hooks::raid_spawn_wave`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct RaidSpawnWaveEvent { - pub raid: i32, - pub pos: [i32; 3], - pub wave: i32, - pub raiders: usize, -} -pub fn dispatch_raid_spawn_wave( - host: &mut dyn Host, - mods: &[Box], - raid: i32, - pos: [i32; 3], - wave: i32, - raiders: usize, -) { - let mut event = RaidSpawnWaveEvent { - raid, - pos, - wave, - raiders, - }; - for mod_hook in super::entered(mods, "raid_spawn_wave") { - mod_hook.raid_spawn_wave(host, &mut event); - } -} -/// What [`Hooks::raid_state_change`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct RaidStateChangeEvent<'a> { - pub raid: i32, - pub pos: [i32; 3], - pub state: &'a str, -} -pub fn dispatch_raid_state_change( - host: &mut dyn Host, - mods: &[Box], - raid: i32, - pos: [i32; 3], - state: &str, -) { - let mut event = RaidStateChangeEvent { raid, pos, state }; - for mod_hook in super::entered(mods, "raid_state_change") { - mod_hook.raid_state_change(host, &mut event); - } -} -/// What [`Hooks::sculk_bloom`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct SculkBloomEvent<'a> { - pub dimension: &'a str, - pub pos: [i32; 3], -} -pub fn dispatch_sculk_bloom( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - pos: [i32; 3], -) { - let mut event = SculkBloomEvent { dimension, pos }; - for mod_hook in super::entered(mods, "sculk_bloom") { - mod_hook.sculk_bloom(host, &mut event); - } -} -/// What [`Hooks::sleep_finished_time`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct SleepFinishedTimeEvent<'a> { - pub dimension: &'a str, - pub current: i64, - pub new_time: i64, -} -pub fn dispatch_sleep_finished_time( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - current: i64, - new_time: &mut i64, -) { - let mut event = SleepFinishedTimeEvent { - dimension, - current, - new_time: *new_time, - }; - for mod_hook in super::entered(mods, "sleep_finished_time") { - mod_hook.sleep_finished_time(host, &mut event); - } - *new_time = event.new_time; -} -/// What [`Hooks::sponge_absorb`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct SpongeAbsorbEvent { - pub dimension: LevelDimension, - pub pos: BlockPos, -} -pub fn dispatch_sponge_absorb( - level: &dyn LevelHost, - mods: &[Box], - dimension: LevelDimension, - pos: BlockPos, -) { - let mut event = SpongeAbsorbEvent { dimension, pos }; - for mod_hook in super::entered(mods, "sponge_absorb") { - mod_hook.sponge_absorb(level, &mut event); - } -} -/// What [`Hooks::tab_complete`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct TabCompleteEvent<'a> { - pub player: u128, - pub name: &'a str, - pub command: &'a str, - pub range: [usize; 2], - pub suggestions: &'a mut Vec, -} -pub fn dispatch_tab_complete( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - command: &str, - range: [usize; 2], - suggestions: &mut Vec, -) { - let mut event = TabCompleteEvent { - player, - name, - command, - range, - suggestions, - }; - for mod_hook in super::entered(mods, "tab_complete") { - mod_hook.tab_complete(host, &mut event); - } -} -/// What [`Hooks::target_hit`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct TargetHitEvent<'a> { - pub dimension: &'a str, - pub pos: [i32; 3], - pub strength: i32, -} -pub fn dispatch_target_hit( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - pos: [i32; 3], - strength: i32, -) { - let mut event = TargetHitEvent { - dimension, - pos, - strength, - }; - for mod_hook in super::entered(mods, "target_hit") { - mod_hook.target_hit(host, &mut event); - } -} -/// What [`Hooks::thunder_change`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ThunderChangeEvent<'a> { - pub dimension: &'a str, - pub thundering: bool, -} -pub fn dispatch_thunder_change( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - thundering: bool, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = ThunderChangeEvent { - dimension, - thundering, - }; - for mod_hook in super::entered(mods, "thunder_change") { - if matches!(mod_hook.thunder_change(host, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::time_skip`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct TimeSkipEvent<'a> { - pub dimension: &'a str, - pub from: i64, - pub to: i64, - pub cause: &'a str, -} -pub fn dispatch_time_skip( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - from: i64, - to: i64, - cause: &str, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = TimeSkipEvent { - dimension, - from, - to, - cause, - }; - for mod_hook in super::entered(mods, "time_skip") { - if matches!(mod_hook.time_skip(host, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::trade_select`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct TradeSelectEvent<'a> { - pub player: u128, - pub name: &'a str, - pub villager: Option, - pub index: usize, -} -pub fn dispatch_trade_select( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - villager: Option, - index: usize, -) { - let mut event = TradeSelectEvent { - player, - name, - villager, - index, - }; - for mod_hook in super::entered(mods, "trade_select") { - mod_hook.trade_select(host, &mut event); - } -} -/// What [`Hooks::trade_with_villager`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct TradeWithVillagerEvent<'a> { - pub merchant: u32, - pub kind: &'a str, - pub offer: &'a MerchantOffer, -} -pub fn dispatch_trade_with_villager( - host: &mut dyn Host, - mods: &[Box], - merchant: u32, - kind: &str, - offer: &MerchantOffer, -) { - let mut event = TradeWithVillagerEvent { - merchant, - kind, - offer, - }; - for mod_hook in super::entered(mods, "trade_with_villager") { - mod_hook.trade_with_villager(host, &mut event); - } -} -/// What [`Hooks::vault_change_state`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct VaultChangeStateEvent<'a> { - pub dimension: &'a str, - pub pos: [i32; 3], - pub state: &'a str, -} -pub fn dispatch_vault_change_state( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - pos: [i32; 3], - state: &str, -) { - let mut event = VaultChangeStateEvent { - dimension, - pos, - state, - }; - for mod_hook in super::entered(mods, "vault_change_state") { - mod_hook.vault_change_state(host, &mut event); - } -} -/// What [`Hooks::vault_loot`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct VaultLootEvent<'a> { - pub pos: [i32; 3], - pub item: &'a str, -} -pub fn dispatch_vault_loot(host: &dyn Host, mods: &[Box], pos: [i32; 3], item: &str) { - let mut event = VaultLootEvent { pos, item }; - for mod_hook in super::entered(mods, "vault_loot") { - mod_hook.vault_loot(host, &mut event); - } -} -/// What [`Hooks::weather_change`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct WeatherChangeEvent<'a> { - pub dimension: &'a str, - pub raining: bool, - pub thundering: bool, -} -pub fn dispatch_weather_change( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - raining: bool, - thundering: bool, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = WeatherChangeEvent { - dimension, - raining, - thundering, - }; - for mod_hook in super::entered(mods, "weather_change") { - if matches!(mod_hook.weather_change(host, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::whitelist_state_update`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct WhitelistStateUpdateEvent { - pub enabled: bool, -} -pub fn dispatch_whitelist_state_update( - host: &mut dyn Host, - mods: &[Box], - enabled: bool, -) { - let mut event = WhitelistStateUpdateEvent { enabled }; - for mod_hook in super::entered(mods, "whitelist_state_update") { - mod_hook.whitelist_state_update(host, &mut event); - } -} -/// What [`Hooks::world_border_bounds_change_finish`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct WorldBorderBoundsChangeFinishEvent {} -pub fn dispatch_world_border_bounds_change_finish(host: &mut dyn Host, mods: &[Box]) { - let mut event = WorldBorderBoundsChangeFinishEvent {}; - for mod_hook in super::entered(mods, "world_border_bounds_change_finish") { - mod_hook.world_border_bounds_change_finish(host, &mut event); - } -} -/// What [`Hooks::world_saved`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct WorldSavedEvent { - pub wait: bool, -} -pub fn dispatch_world_saved(host: &mut dyn Host, mods: &[Box], wait: bool) { - let mut event = WorldSavedEvent { wait }; - for mod_hook in super::entered(mods, "world_saved") { - mod_hook.world_saved(host, &mut event); - } -} -/// What [`Hooks::living_drown`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct LivingDrownEvent<'a> { - pub entity: u32, - pub kind: &'a str, - pub is_drowning: bool, - pub damage_amount: f32, - pub bubble_count: i32, -} -pub fn dispatch_living_drown( - mods: &[Box], - entity: u32, - kind: &str, - is_drowning: &mut bool, - damage_amount: &mut f32, - bubble_count: i32, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = LivingDrownEvent { - entity, - kind, - is_drowning: *is_drowning, - damage_amount: *damage_amount, - bubble_count, - }; - for mod_hook in super::entered(mods, "living_drown") { - if matches!(mod_hook.living_drown(&mut event), Flow::Cancel) { - *cancel = true; - break; - } - } - *is_drowning = event.is_drowning; - *damage_amount = event.damage_amount; -} -/// What [`Hooks::living_fall`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct LivingFallEvent<'a> { - pub entity: u32, - pub kind: &'a str, - pub distance: f64, - pub multiplier: f32, -} -pub fn dispatch_living_fall( - mods: &[Box], - entity: u32, - kind: &str, - distance: &mut f64, - multiplier: &mut f32, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = LivingFallEvent { - entity, - kind, - distance: *distance, - multiplier: *multiplier, - }; - for mod_hook in super::entered(mods, "living_fall") { - if matches!(mod_hook.living_fall(&mut event), Flow::Cancel) { - *cancel = true; - break; - } - } - *distance = event.distance; - *multiplier = event.multiplier; -} -/// What [`Hooks::living_heal`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct LivingHealEvent { - pub entity: u32, - pub amount: f32, -} -pub fn dispatch_living_heal( - mods: &[Box], - entity: u32, - amount: &mut f32, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = LivingHealEvent { - entity, - amount: *amount, - }; - for mod_hook in super::entered(mods, "living_heal") { - if matches!(mod_hook.living_heal(&mut event), Flow::Cancel) { - *cancel = true; - break; - } - } - *amount = event.amount; -} -/// What [`Hooks::living_jump`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct LivingJumpEvent<'a> { - pub entity: u32, - pub kind: &'a str, -} -pub fn dispatch_living_jump(mods: &[Box], entity: u32, kind: &str) { - let mut event = LivingJumpEvent { entity, kind }; - for mod_hook in super::entered(mods, "living_jump") { - mod_hook.living_jump(&mut event); - } -} -/// What [`Hooks::arrow_nock`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ArrowNockEvent<'a> { - pub player: u128, - pub name: &'a str, - pub bow: &'a ItemStack, - pub hand: Hand, - pub has_ammo: bool, -} -pub fn dispatch_arrow_nock( - host: &mut dyn Host, - mods: &[Box], - player: u128, - name: &str, - bow: &ItemStack, - hand: Hand, - has_ammo: bool, - cancel: &mut bool, -) { - if *cancel { - return; - } - let mut event = ArrowNockEvent { - player, - name, - bow, - hand, - has_ammo, - }; - for mod_hook in super::entered(mods, "arrow_nock") { - if matches!(mod_hook.arrow_nock(host, &mut event), Flow::Cancel) { - *cancel = true; - break; - } - } -} -/// What [`Hooks::compute_fov_modifier`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ComputeFovModifierEvent<'a> { - pub player: &'a horizon_client::camera::FovPlayer, - pub base: f32, - pub modifier: f32, -} -pub fn dispatch_compute_fov_modifier( - host: &dyn Host, - mods: &[Box], - player: &horizon_client::camera::FovPlayer, - base: f32, - modifier: &mut f32, -) { - let mut event = ComputeFovModifierEvent { - player, - base, - modifier: *modifier, - }; - for mod_hook in super::entered(mods, "compute_fov_modifier") { - mod_hook.compute_fov_modifier(host, &mut event); - } - *modifier = event.modifier; -} -/// What [`Hooks::explosion_knockback`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ExplosionKnockbackEvent<'a> { - pub dimension: &'a str, - pub center: [f64; 3], - pub blocks: &'a [[i32; 3]], - pub entity: Option, - pub knockback_velocity: &'a mut [f64; 3], -} -pub fn dispatch_explosion_knockback( - host: &mut dyn Host, - mods: &[Box], - dimension: &str, - center: [f64; 3], - blocks: &[[i32; 3]], - entity: Option, - knockback_velocity: &mut [f64; 3], -) { - let mut event = ExplosionKnockbackEvent { - dimension, - center, - blocks, - entity, - knockback_velocity, - }; - for mod_hook in super::entered(mods, "explosion_knockback") { - mod_hook.explosion_knockback(host, &mut event); - } -} -/// What [`Hooks::ingredient_test`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct IngredientTestEvent<'a> { - pub kind: &'a str, - pub data: &'a serde_json::Map, - pub stack: &'a ItemStack, - pub accept: bool, -} -pub fn dispatch_ingredient_test( - mods: &[Box], - kind: &str, - data: &serde_json::Map, - stack: &ItemStack, - accept: &mut bool, -) { - let mut event = IngredientTestEvent { - kind, - data, - stack, - accept: *accept, - }; - for mod_hook in super::entered(mods, "ingredient_test") { - mod_hook.ingredient_test(&mut event); - } - *accept = event.accept; -} -/// What [`Hooks::item_container`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ItemContainerEvent<'a> { - pub item: &'a str, -} -pub fn dispatch_item_container( - host: &dyn Host, - mods: &[Box], - item: &str, - slots: &mut Option, -) { - if slots.is_some() { - return; - } - let mut event = ItemContainerEvent { item }; - for mod_hook in super::entered(mods, "item_container") { - if let Some(replacement) = mod_hook.item_container(host, &mut event) { - *slots = Some(replacement); - break; - } - } -} -/// What [`Hooks::living_get_projectile`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct LivingGetProjectileEvent<'a> { - pub player: Option, - pub name: &'a str, - pub entity: Option, - pub kind: &'a str, - pub weapon: &'a ItemStack, - pub projectile: &'a mut Option, -} -pub fn dispatch_living_get_projectile( - host: &mut dyn Host, - mods: &[Box], - player: Option, - name: &str, - entity: Option, - kind: &str, - weapon: &ItemStack, - projectile: &mut Option, -) { - let mut event = LivingGetProjectileEvent { - player, - name, - entity, - kind, - weapon, - projectile, - }; - for mod_hook in super::entered(mods, "living_get_projectile") { - mod_hook.living_get_projectile(host, &mut event); - } -} -/// What [`Hooks::movement_input`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct MovementInputEvent { - pub forward: f32, - pub strafe: f32, - pub jump: bool, - pub sneak: bool, -} -pub fn dispatch_movement_input( - host: &dyn Host, - mods: &[Box], - forward: &mut f32, - strafe: &mut f32, - jump: &mut bool, - sneak: &mut bool, -) { - let mut event = MovementInputEvent { - forward: *forward, - strafe: *strafe, - jump: *jump, - sneak: *sneak, - }; - for mod_hook in super::entered(mods, "movement_input") { - mod_hook.movement_input(host, &mut event); - } - *forward = event.forward; - *strafe = event.strafe; - *jump = event.jump; - *sneak = event.sneak; -} -/// What [`Hooks::recipe_book_categories`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct RecipeBookCategoriesEvent<'a> { - pub categories: &'a mut Vec, -} -pub fn dispatch_recipe_book_categories( - host: &dyn Host, - mods: &[Box], - categories: &mut Vec, -) { - let mut event = RecipeBookCategoriesEvent { categories }; - for mod_hook in super::entered(mods, "recipe_book_categories") { - mod_hook.recipe_book_categories(host, &mut event); - } -} -/// What [`Hooks::render_hand_pose`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct RenderHandPoseEvent<'a> { - pub hand: horizon_client::first_person::Hand, - pub stack: Option<&'a ItemStack>, - pub partial: f32, - pub swing: f32, - pub equip: f32, - pub x: f32, - pub y: f32, - pub z: f32, - pub pitch: f32, - pub yaw: f32, - pub roll: f32, - pub scale: f32, -} -pub fn dispatch_render_hand_pose( - host: &dyn Host, - mods: &[Box], - hand: horizon_client::first_person::Hand, - stack: Option<&ItemStack>, - partial: f32, - swing: f32, - equip: f32, - x: &mut f32, - y: &mut f32, - z: &mut f32, - pitch: &mut f32, - yaw: &mut f32, - roll: &mut f32, - scale: &mut f32, -) { - let mut event = RenderHandPoseEvent { - hand, - stack, - partial, - swing, - equip, - x: *x, - y: *y, - z: *z, - pitch: *pitch, - yaw: *yaw, - roll: *roll, - scale: *scale, - }; - for mod_hook in super::entered(mods, "render_hand_pose") { - mod_hook.render_hand_pose(host, &mut event); - } - *x = event.x; - *y = event.y; - *z = event.z; - *pitch = event.pitch; - *yaw = event.yaw; - *roll = event.roll; - *scale = event.scale; -} -/// What [`Hooks::render_item_in_frame`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct RenderItemInFrameEvent<'a> { - pub entity_id: u32, - pub pos: [i32; 3], - pub stack: &'a ItemStack, -} -pub fn dispatch_render_item_in_frame( - host: &dyn Host, - mods: &[Box], - entity_id: u32, - pos: [i32; 3], - stack: &ItemStack, - canceled: &mut bool, -) { - if *canceled { - return; - } - let mut event = RenderItemInFrameEvent { - entity_id, - pos, - stack, - }; - for mod_hook in super::entered(mods, "render_item_in_frame") { - if matches!( - mod_hook.render_item_in_frame(host, &mut event), - Flow::Cancel - ) { - *canceled = true; - break; - } - } -} -/// What [`Hooks::render_name_tag`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct RenderNameTagEvent<'a> { - pub entity_id: u32, - pub kind: &'a str, - pub vanilla_visible: bool, - pub visible: bool, - pub text: &'a mut Option, -} -pub fn dispatch_render_name_tag( - host: &dyn Host, - mods: &[Box], - entity_id: u32, - kind: &str, - vanilla_visible: bool, - visible: &mut bool, - text: &mut Option, -) { - let mut event = RenderNameTagEvent { - entity_id, - kind, - vanilla_visible, - visible: *visible, - text, - }; - for mod_hook in super::entered(mods, "render_name_tag") { - mod_hook.render_name_tag(host, &mut event); - } - *visible = event.visible; -} -/// What [`Hooks::screen_closing`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ScreenClosingEvent<'a> { - pub screen: &'a NativeScreenView<'a>, -} -pub fn dispatch_screen_closing( - host: &dyn Host, - mods: &[Box], - screen: &NativeScreenView<'_>, -) { - let mut event = ScreenClosingEvent { screen }; - for mod_hook in super::entered(mods, "screen_closing") { - mod_hook.screen_closing(host, &mut event); - } -} -/// What [`Hooks::screen_init_post`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ScreenInitPostEvent<'a> { - pub screen: &'a NativeScreenView<'a>, -} -pub fn dispatch_screen_init_post( - host: &dyn Host, - mods: &[Box], - screen: &NativeScreenView<'_>, -) { - let mut event = ScreenInitPostEvent { screen }; - for mod_hook in super::entered(mods, "screen_init_post") { - mod_hook.screen_init_post(host, &mut event); - } -} -/// What [`Hooks::screen_opening`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct ScreenOpeningEvent<'a> { - pub screen: &'a NativeScreenView<'a>, -} -pub fn dispatch_screen_opening( - host: &dyn Host, - mods: &[Box], - screen: &NativeScreenView<'_>, -) { - let mut event = ScreenOpeningEvent { screen }; - for mod_hook in super::entered(mods, "screen_opening") { - mod_hook.screen_opening(host, &mut event); - } -} -/// What [`Hooks::tooltip_color`] is told. Fields may be added: the struct is `#[non_exhaustive]`. -#[non_exhaustive] -pub struct TooltipColorEvent<'a> { - pub stack: Option<&'a ItemStack>, - pub background: u32, - pub border_top: u32, - pub border_bottom: u32, -} -pub fn dispatch_tooltip_color( - host: &dyn Host, - mods: &[Box], - stack: Option<&ItemStack>, - background: &mut u32, - border_top: &mut u32, - border_bottom: &mut u32, -) { - let mut event = TooltipColorEvent { - stack, - background: *background, - border_top: *border_top, - border_bottom: *border_bottom, - }; - for mod_hook in super::entered(mods, "tooltip_color") { - mod_hook.tooltip_color(host, &mut event); - } - *background = event.background; - *border_top = event.border_top; - *border_bottom = event.border_bottom; -} diff --git a/crates/horizon-client/src/mod_api/generated_host.rs b/crates/horizon-client/src/mod_api/generated_host.rs deleted file mode 100644 index 6dcc0cd82e..0000000000 --- a/crates/horizon-client/src/mod_api/generated_host.rs +++ /dev/null @@ -1,1445 +0,0 @@ -// @generated by cargo run -p xtask -- mod-api; do not edit. -use super::*; -use std::path::PathBuf; -use std::sync::Arc; -/// Services supplied by the game to trait mods. -/// Each method's default panics with its name, so a test double implements only what its test calls; the game implements every one. -pub trait Host { - /// Draws a quad with `corners` in blocks under `draw`'s pose, in a model - /// face's order (top left, bottom left, bottom right, top right as seen - /// from its front), textured with the `uv` rectangle (u0, v0, u1, v1 in - /// texels of 16 per side) of `sprite` (`mymod:entity/screen`, - /// `mymod:block/casing`, `block/stone`) times the 0xRRGGBB `tint` - /// (0xFFFFFF for none). False for an unknown sprite. API 71. - fn draw_quad( - &self, - draw: &mut BlockEntityDraw, - sprite: &str, - corners: [[f32; 3]; 4], - uv: [f32; 4], - tint: u32, - ) -> bool { - let _ = draw; - let _ = sprite; - let _ = corners; - let _ = uv; - let _ = tint; - unimplemented!("Host::draw_quad") - } - /// Draws a box from `from` to `to` (texels of 16 per block, as a block - /// model's element) under `draw`'s pose, with the faces given, by - /// `Direction` (down, up, north, south, west, east), baked as a model - /// element's are (`FaceBakery`), times the 0xRRGGBB `tint`. A turned box - /// is drawn under a turned pose (`BlockEntityDraw::rotate_about`). False - /// when a face's sprite is unknown; that face is left out. API 71. - fn draw_cube( - &self, - draw: &mut BlockEntityDraw, - from: [f32; 3], - to: [f32; 3], - faces: [Option>; 6], - tint: u32, - ) -> bool { - let _ = draw; - let _ = from; - let _ = to; - let _ = faces; - let _ = tint; - unimplemented!("Host::draw_cube") - } - /// Draws block `block`'s model (`mymod:cog`, any block with a model) in - /// the state with `properties` (`[("axis", "x")]`, the rest default) - /// under `draw`'s pose, as vanilla's `submitBlockModel` draws a block in - /// an entity's place: a block's baked model, with its default tints. A - /// moving part that is no block is a standalone model - /// (`native_draw_model`). False for an unknown block or property. API 71. - fn draw_block( - &self, - draw: &mut BlockEntityDraw, - block: &str, - properties: &[(&str, &str)], - ) -> bool { - let _ = draw; - let _ = block; - let _ = properties; - unimplemented!("Host::draw_block") - } - /// Draws `stack` under `draw`'s pose as an item is drawn in the world - /// (`ItemModelResolver.updateForTopItem` and `ItemStackRenderState.submit`), centred on the - /// pose's origin, in its model's `display` transform: `Fixed` as a - /// campfire's or an item frame's, `Ground` as a dropped item's. `seed` - /// picks among random models (a campfire passes its slot plus its - /// position's hash). `owner_yaw` makes the block entity the item's - /// owner (`ItemOwner`), at the block's centre and turned that many - /// degrees (`getVisualRotationYInDegrees`), as a shelf is: a compass on - /// it points to its target from there. None draws it with no owner, as - /// a campfire does. False for an empty stack or one with no model. The - /// client's copy of a block entity has no item slots (`entity.items` is - /// empty there): send the stacks you draw in - /// `native_block_entity_update_tag` and read them from `entity.nbt`. - /// API 91. - fn draw_item( - &self, - draw: &mut BlockEntityDraw, - stack: &ItemStack, - display: DisplayContext, - owner_yaw: Option, - seed: i32, - ) -> bool { - let _ = draw; - let _ = stack; - let _ = display; - let _ = owner_yaw; - let _ = seed; - unimplemented!("Host::draw_item") - } - /// Access a server-owned holder's attachment map. On a client it is the - /// copy of what the server synced: `set` and `remove` change only the copy. - fn attachments(&mut self, holder: Holder) -> AttachmentResult<&mut Attachments> { - let _ = holder; - unimplemented!("Host::attachments") - } - /// Moves mob `id` to `pos` at once, as `/tp` does: it dismounts, drops its - /// path and resets its fall. False for no such mob, another dimension or a - /// position outside `/summon`'s bounds. API 37. - fn teleport_mob(&mut self, dimension: &str, id: u32, pos: [f64; 3]) -> bool { - let _ = dimension; - let _ = id; - let _ = pos; - unimplemented!("Host::teleport_mob") - } - /// Adds `velocity` to mob `id`'s: false for no such mob, another dimension - /// or a velocity that isn't finite. API 37. - fn push_mob(&mut self, dimension: &str, id: u32, velocity: [f64; 3]) -> bool { - let _ = dimension; - let _ = id; - let _ = velocity; - unimplemented!("Host::push_mob") - } - /// Sets the living mob `id`'s health, cut to its maximum: false for no such - /// mob, another dimension or a health that isn't above 0 (`native_kill_mob` - /// kills). API 37. - fn set_mob_health(&mut self, dimension: &str, id: u32, health: f32) -> bool { - let _ = dimension; - let _ = id; - let _ = health; - unimplemented!("Host::set_mob_health") - } - /// Kills mob `id` as `/kill` does: it falls and drops its loot. False for - /// no such living mob or another dimension. API 37. - fn kill_mob(&mut self, dimension: &str, id: u32) -> bool { - let _ = dimension; - let _ = id; - unimplemented!("Host::kill_mob") - } - /// Takes mob `id` out of the world with no death and no loot: false for no - /// such mob or another dimension. API 37. - fn remove_mob(&mut self, dimension: &str, id: u32) -> bool { - let _ = dimension; - let _ = id; - unimplemented!("Host::remove_mob") - } - /// Spawns an entity of `kind` (`minecraft:item_display`) at `pos` in the - /// level in place, `dimension`, from `nbt` (its compound, as `/summon - /// ` takes it; passengers included): its id. None on a - /// client's copy of the world, for another dimension, a position outside - /// `/summon`'s bounds, a chunk that isn't loaded, a kind Horizon can't - /// load, or one a mod kept out (`native_entity_join_level`). API - /// 59. - fn summon(&mut self, dimension: &str, kind: &str, pos: [f64; 3], nbt: Compound) -> Option { - let _ = dimension; - let _ = kind; - let _ = pos; - let _ = nbt; - unimplemented!("Host::summon") - } - /// Moves entity `id`, one Horizon keeps without simulating (a display, a - /// marker, an interaction), to `pos` facing `yaw` and `pitch` in degrees; - /// a display eases there over its `teleport` ticks. False for no such - /// entity, another dimension or a position outside `/summon`'s bounds. - /// Mobs move with `native_teleport_mob`. API 59. - fn move_entity( - &mut self, - dimension: &str, - id: u32, - pos: [f64; 3], - yaw: f32, - pitch: f32, - ) -> bool { - let _ = dimension; - let _ = id; - let _ = pos; - let _ = yaw; - let _ = pitch; - unimplemented!("Host::move_entity") - } - /// Removes entity `id`, one Horizon keeps without simulating, as `/kill` - /// does (it drops nothing and isn't saved). False for no such entity or - /// another dimension. Mobs go with `native_remove_mob`. API - /// 59. - fn remove_entity(&mut self, dimension: &str, id: u32) -> bool { - let _ = dimension; - let _ = id; - unimplemented!("Host::remove_entity") - } - /// The players on this server, in the order they joined. Empty on a - /// client and in a world nobody else can join. - fn server_players(&self) -> Vec { - unimplemented!("Host::server_players") - } - /// A system line in that player's chat, also inside a player's hook for - /// the acting player; false when they aren't on. - fn tell(&mut self, uuid: u128, line: Text) -> bool { - let _ = uuid; - let _ = line; - unimplemented!("Host::tell") - } - /// A line over that player's hotbar (the action bar), also inside a - /// player's hook for the acting player; false when they aren't on. API 21. - fn action_bar(&mut self, uuid: u128, line: Text) -> bool { - let _ = uuid; - let _ = line; - unimplemented!("Host::action_bar") - } - /// The lines over and under that player's list of players (Tab): - /// `ClientboundTabListPacket`, an empty text for none. False when they - /// aren't on. API 49. - fn tab_list(&mut self, uuid: u128, header: &Text, footer: &Text) -> bool { - let _ = uuid; - let _ = header; - let _ = footer; - unimplemented!("Host::tab_list") - } - /// The server's tick rate (20 unless `/tick rate` changed it) and the - /// milliseconds its ticks took on average lately, as `/tick query` - /// reports them. The ticks a second it keeps up is the rate, or - /// 1000 / milliseconds when that is lower. API 49. - fn tick_stats(&self) -> (f32, f64) { - unimplemented!("Host::tick_stats") - } - /// Whether that player counts for `players_sleeping_percentage`: an - /// ignored player who isn't in bed is left out of the players the night - /// needs asleep, as a spectator is (an away-from-keyboard mod). Kept - /// until a mod sets it back or the server stops, also if they leave and - /// come back. Only where no player's state is swapped in, as - /// `native_run_command`; false there and when they aren't on. API 49. - fn sleep_ignored(&mut self, uuid: u128, ignored: bool) -> bool { - let _ = uuid; - let _ = ignored; - unimplemented!("Host::sleep_ignored") - } - /// Shows `nickname` in place of that player's name in chat (their - /// messages, `/me`, `/msg`) and in the Tab list; None shows their name - /// again. Hovering the shown name gives the real one, which stays in the - /// log, commands, selectors and the server's events. Kept until a mod - /// sets it again or the server stops (also if they leave and come back). - /// Plain text: the mod checks it (`native_text_allowed`, its length, - /// other players' names). Only where no player's state is swapped in, - /// as `native_run_command`; false there and when they aren't on. API - /// 62. - fn set_nickname(&mut self, uuid: u128, nickname: Option<&str>) -> bool { - let _ = uuid; - let _ = nickname; - unimplemented!("Host::set_nickname") - } - /// Whether the server's chat filter (`server.toml` `[chat_filter]`) - /// lets `text` through: true without a filter, None inside a player's - /// hook (where the server is out of reach). API 62. - fn text_allowed(&self, text: &str) -> Option { - let _ = text; - unimplemented!("Host::text_allowed") - } - /// A system line in every player's chat and the server's log. - fn broadcast(&self, line: Text) { - let _ = line; - unimplemented!("Host::broadcast") - } - /// A line of the command's answer, inside `native_perform_command`: - /// sent to the player whose command it is after the call, as vanilla - /// commands' feedback is. - fn feedback(&mut self, line: Text) { - let _ = line; - unimplemented!("Host::feedback") - } - /// The player whose action or command a hook hears, as their state is - /// swapped in (`native_perform_command`, `native_right_click_block`, - /// `native_use_item_on`, `native_destroy_block`...): None outside such a - /// hook. API 21. - fn acting_player(&self) -> Option { - unimplemented!("Host::acting_player") - } - /// Runs `line` (no slash) as the server's console does: permission - /// level 4, from the overworld's spawn, with no output. A teleport to - /// another level (`execute in minecraft:the_nether run tp Alex 0 70 0`) - /// happens at the next tick's start, as the console's. Whether it ran - /// without failing. Only where no player's state is swapped in - /// (`native_tick`, `native_menu_clicked`): false inside a player's hook, - /// where the other players are out of reach. API 21. - fn run_command(&mut self, line: &str) -> bool { - let _ = line; - unimplemented!("Host::run_command") - } - /// The open world's folder (`world/`, beside its `level.dat`): a server - /// mod keeps its data in a folder of its own there, so the world's - /// backups carry it. None when no world is open. - fn world_dir(&self) -> Option { - unimplemented!("Host::world_dir") - } - /// The main hand's stack: its item, count, and whether it is plain. On a - /// client, the player's own. - fn held(&self) -> Option<(String, u32, bool)> { - unimplemented!("Host::held") - } - /// How many of plain `item` (`minecraft:diamond`) the player carries. - fn count_items(&mut self, item: &str) -> u32 { - let _ = item; - unimplemented!("Host::count_items") - } - /// Every plain item the player carries, with how many, by id. - fn carried_items(&self) -> Vec<(String, u32)> { - unimplemented!("Host::carried_items") - } - /// Takes up to `count` plain `item`, the main hand first; what it took. - fn take_items(&mut self, item: &str, count: u32) -> u32 { - let _ = item; - let _ = count; - unimplemented!("Host::take_items") - } - /// How many plain `item` still fit; 0 for an unknown item. - fn room_for(&mut self, item: &str) -> u32 { - let _ = item; - unimplemented!("Host::room_for") - } - /// Gives up to `count` plain `item` where they fit, as `Inventory.add` - /// places them; what it gave (nothing is dropped). 0 for an unknown item. - fn give_items(&mut self, item: &str, count: u32) -> u32 { - let _ = item; - let _ = count; - unimplemented!("Host::give_items") - } - /// Gives `stack` whole (its damage, name, enchantments... kept) to the - /// player `uuid`, as `Inventory.add` places it: onto equal stacks first, - /// then into empty inventory slots, never the armour or an empty off - /// hand. What did not fit comes back (nothing is dropped): all of it for - /// a player who isn't here, is dead, or whose state is swapped out by - /// another player's (inside that player's hook, only they are reached). - /// The player's screen follows as for any change to their inventory. - /// What an auto-pickup or a machine's output to a player needs, from any - /// server hook. API 98. - fn give_stack(&mut self, uuid: u128, stack: ItemStack) -> Option { - let _ = uuid; - let _ = stack; - unimplemented!("Host::give_stack") - } - /// Gives `points` of experience to the player `uuid` as an orb they touch - /// does (`ExperienceOrb.playerTouch`): Mending repairs their damaged items - /// with it first (`repairPlayerItems`), then the rest goes to their - /// experience (`Player.giveExperiencePoints`), with the level-up sound - /// every fifth level. No orb pickup sound. False, and nothing given, for - /// a player who isn't here or is dead, and for another player than the one - /// whose state is swapped in (inside that player's hook, only they are - /// reached), and while the server's player list is busy elsewhere in the - /// tick: give first, and cancel an orb only when this returned true. - /// Their bar follows as for any change to their experience. - fn give_experience(&mut self, uuid: u128, points: u32) -> bool { - let _ = uuid; - let _ = points; - unimplemented!("Host::give_experience") - } - /// Adds `levels` to the player `uuid`'s level, or takes them off when - /// negative (`Player.giveExperienceLevels`): what the anvil and the - /// enchanting table charge with, and a mod can charge or reward with. - /// Below level 0 the level and the points are 0. The progress toward the - /// next level is kept. False, and nothing changed, as for - /// `native_give_experience`. - fn give_levels(&mut self, uuid: u128, levels: i32) -> bool { - let _ = uuid; - let _ = levels; - unimplemented!("Host::give_levels") - } - /// Read a loaded, light-emitting block. No implicit chunk loading. - fn lit_block_name(&self, pos: [i32; 3]) -> Option<&str> { - let _ = pos; - unimplemented!("Host::lit_block_name") - } - /// A small local vanilla particle; caller should rate-limit emission. - fn flame_particle(&mut self, at: [f64; 3], soul: bool) { - let _ = at; - let _ = soul; - unimplemented!("Host::flame_particle") - } - /// The stack the acting player carries in `slot`, by - /// `native_inventory_tick`'s numbers: none for an empty slot or another - /// number. Change it as you like: a count of 0 empties the slot at the end - /// of `native_inventory_tick`, and the player's client gets the change. - /// API 39. - fn carried_stack(&mut self, slot: i32) -> Option<&mut ItemStack> { - let _ = slot; - unimplemented!("Host::carried_stack") - } - /// Sets a block in the current authoritative level through `Level.setBlock`. - /// Returns false for another dimension, an invalid state, or no change. - /// `flags` selects vanilla's neighbour and shape updates; Horizon sends - /// every write to players whatever the flags. A block entity block gets - /// its block entity. API 24. - fn set_block( - &mut self, - dimension: &str, - pos: [i32; 3], - state: BlockStateId, - flags: i32, - ) -> bool { - let _ = dimension; - let _ = pos; - let _ = state; - let _ = flags; - unimplemented!("Host::set_block") - } - /// For a server mod: the runs made at once, straight into the chunks of the authoritative level `dimension` - /// (the one being ticked, as for `native_set_block`), with what `fill ... strict` does for block entities (a - /// block replaced by another loses its block entity, and a new block gets the one `native_set_block` would - /// give it, a mod block's included); no neighbour is told, and the chunks changed go to the players whole. - /// Runs in chunks not loaded, and the parts of runs outside the column, are left out. The blocks changed, or - /// why the edit was left out, with nothing changed: another dimension, a client of a server, a palette entry - /// or filter that doesn't read or carries NBT (refused), a run naming a palette entry or filter past the end, - /// or more than 65536 filters. API 126. - fn edit_blocks(&mut self, dimension: &str, edit: &NativeBlockEdit) -> Result { - let _ = dimension; - let _ = edit; - unimplemented!("Host::edit_blocks") - } - /// For a server mod: each column's height by `heightmap` in the authoritative level `dimension` (the one being - /// ticked), none for a column not loaded. `Err` says why nothing was read: another dimension or a client of a - /// server, or more than 65,536 columns (`MAX_COLUMNS`). API 127. - fn surface_heights( - &self, - dimension: &str, - columns: &[(i32, i32)], - heightmap: NativeHeightmap, - ) -> Result>, String> { - let _ = dimension; - let _ = columns; - let _ = heightmap; - unimplemented!("Host::surface_heights") - } - /// For a server mod: `below` blocks under and `above` blocks from each column's height by `heightmap`, as a - /// palette and indexes, with the biome's grass, foliage and water tints (air outside the column and in columns - /// not loaded). The tints are the biome's at the column's top, not blended over the biome blend radius as the - /// game draws them: near a biome border they can differ from what players see. `Err` says why nothing was - /// read: another dimension or a client of a server, `below + above` of 0 or more than the level's height, or - /// a read of more than 1,048,576 blocks (`MAX_READ`; it runs on the server tick, so read big areas in pieces). - /// API 127. - fn read_columns( - &self, - dimension: &str, - columns: &[(i32, i32)], - heightmap: NativeHeightmap, - range: (u32, u32), - ) -> Result { - let _ = dimension; - let _ = columns; - let _ = heightmap; - let _ = range; - unimplemented!("Host::read_columns") - } - /// Asks `native_biome_colors` again for every biome and, when the colours - /// differ from those in use, rebuilds every loaded section and the far - /// terrain with them (`LevelRenderer.allChanged`, as changing Biome Blend - /// does). Call it when your colours change (a new season, a new day of a - /// slow fade) and once when the player joins, not every frame: a rebuild - /// takes the workers a few seconds. API 107. - fn refresh_biome_colors(&mut self) { - unimplemented!("Host::refresh_biome_colors") - } - /// The strongest redstone signal reaching the block at `pos` from its six - /// neighbours, 0 to 15 (`SignalGetter.getBestNeighborSignal`, what a - /// dispenser or a piston reads), in the level in place, `dimension`. 0 on - /// a client's copy of the world and for another dimension. A mod's - /// explosive, launcher or machine reads it in its block entity's tick to - /// go off or switch on. API 96. - fn neighbor_signal(&mut self, dimension: &str, pos: [i32; 3]) -> i32 { - let _ = dimension; - let _ = pos; - unimplemented!("Host::neighbor_signal") - } - /// The light at `pos` in the world in place, as `(block, sky)`, each 0 to - /// 15 (`Level.getBrightness` for `LightLayer.BLOCK` and `LightLayer.SKY`). - /// A solar panel checks for full sky light above it, a lamp or a mob farm - /// for darkness. `(0, 0)` in a chunk that isn't loaded. Like the other - /// reads here it reads the world this game holds: the level in place on - /// a server, the server's copy on a client. API 97. - fn light(&self, pos: [i32; 3]) -> (u8, u8) { - let _ = pos; - unimplemented!("Host::light") - } - /// The rain and thunder levels of the world in place, each 0.0 to 1.0 - /// (`Level.getRainLevel` and `Level.getThunderLevel`): what a solar panel - /// or a wind turbine reads. Both 0 in the Nether and the End, which have - /// no weather (`Level.canHaveWeather`). API 97. - fn weather(&self) -> (f32, f32) { - unimplemented!("Host::weather") - } - /// The world's time of day in ticks (`Level.getOverworldClockTime`), the - /// same as `native_clock`'s `day_time`; it keeps counting past 24000, so - /// take it modulo 24000 for the hour. API 97. - fn day_time(&self) -> i64 { - unimplemented!("Host::day_time") - } - /// The lowest block y and the height of the world in place - /// (`LevelHeightAccessor.getMinY` and `getHeight`): how deep a quarry or a - /// miner may dig. API 97. - fn world_height(&self) -> (i32, i32) { - unimplemented!("Host::world_height") - } - /// The most of the item `item` one slot holds (`Item.getDefaultMaxStackSize`), - /// or `None` for an unknown item: for a mod's container that fills or - /// merges stacks itself. API 97. - fn max_stack_size(&self, item: &str) -> Option { - let _ = item; - unimplemented!("Host::max_stack_size") - } - /// Item `item`'s icon drawn with model `model` (`minecraft:paper` as - /// `mymod:wheel_icon`, an `item_model` of a content mod's), as `native_item` - /// draws one; false for an unknown item. API 111. - fn item_model( - &self, - batch: &mut HudBatch, - item: &str, - model: &str, - at: [f32; 2], - size: f32, - ) -> bool { - let _ = batch; - let _ = item; - let _ = model; - let _ = at; - let _ = size; - unimplemented!("Host::item_model") - } - /// A quad of corners `points` (in order round it) in GUI pixels, `argb`: - /// a wedge, a slanted bar, what `native_fill`'s rectangles can't draw. - /// API 111. - fn quad(&self, batch: &mut HudBatch, points: [[f32; 2]; 4], argb: u32) { - let _ = batch; - let _ = points; - let _ = argb; - unimplemented!("Host::quad") - } - /// Opens a screen of mod `mod_id`'s own over the world: the screen hooks - /// fire over it as over any other, so the mod draws it in - /// `native_screen_render_post` and takes its clicks, scroll, keys and - /// typing there; the mouse is freed, and Escape closes it. Every mod's own - /// screen has the `NativeScreenView.id` `mod`, so a mod's screen hooks - /// check `native_mod_screen()` is its own before drawing. It doesn't pause - /// a single-player game (as the inventory doesn't). True when it is open: - /// false while another screen is (or another mod's), over the title - /// screen, a book, a sign editor, a dialog or a prompt, while the world - /// loads, and anywhere but the window's own client (the server thread, a - /// dedicated server). A gamepad can close it (B, Start) but not click on - /// it: its clicks don't reach the mouse hooks. API 111. - fn mod_screen_open(&mut self, mod_id: &str) -> bool { - let _ = mod_id; - unimplemented!("Host::mod_screen_open") - } - /// Closes mod `mod_id`'s own screen, if it is open. API 111. - fn mod_screen_close(&mut self, mod_id: &str) { - let _ = mod_id; - unimplemented!("Host::mod_screen_close") - } - /// The mod whose own screen is open, if one is (Escape, or another screen - /// replacing it, closes it). API 111. - fn mod_screen(&self) -> Option<&str> { - unimplemented!("Host::mod_screen") - } - /// Writes the experience orb `id`'s `value` (the points a pickup gives), `count` - /// (orbs merged into it, each worth `value`) and `age` (ticks lived; an orb - /// despawns at 6000); a `None` is left as it is. API 128. - /// False, and nothing written, for an orb not in the server level `dimension`, a - /// `value` or `count` of 0, and on a client of a server. - fn set_experience_orb( - &mut self, - dimension: &str, - id: u32, - value: Option, - count: Option, - age: Option, - ) -> bool { - let _ = dimension; - let _ = id; - let _ = value; - let _ = count; - let _ = age; - unimplemented!("Host::set_experience_orb") - } - /// The experience orbs of the server level `dimension` whose box meets - /// the box `min`..`max`, in the level's order: what a mod reads before it - /// cancels an orb in `native_entity_join_level`, to give its points with - /// `native_give_experience`. Empty for a level not in place and on a - /// client of a server. - fn experience_orbs( - &self, - dimension: &str, - min: [f64; 3], - max: [f64; 3], - ) -> Vec { - let _ = dimension; - let _ = min; - let _ = max; - unimplemented!("Host::experience_orbs") - } - /// The clock of the world this window shows, for client mods: its game - /// time and time of day as the server last sent them - /// (`ClientboundSetTimePacket`), advanced each tick between packets; none - /// on the title screen. API 92. - fn clock(&self) -> Option { - unimplemented!("Host::clock") - } - /// Throws a falling block of `state` from `pos` (its bottom centre) at - /// `velocity` (blocks a tick) in the level in place, `dimension`: it - /// falls, lands and places itself as sand does, or drops as an item - /// where it can't. The block at `pos` is left as it is: take it away first - /// with `native_set_block` to make a block fall out of the world. False, - /// with nothing thrown, on a client's copy of the world, for another - /// dimension, an air or unknown state, a chunk that isn't loaded, a value - /// that isn't finite, or with 1024 falling blocks in the level already. - /// Each axis of `velocity` is cut to 3.9. API 81. - fn spawn_falling_block( - &mut self, - dimension: &str, - state: BlockStateId, - pos: [f64; 3], - velocity: [f64; 3], - falling: NativeFalling, - ) -> bool { - let _ = dimension; - let _ = state; - let _ = pos; - let _ = velocity; - let _ = falling; - unimplemented!("Host::spawn_falling_block") - } - /// Property `key` of block state `state` (a machine's `facing`, from the - /// `state` `native_block_entity_submit` gets), if it has one: no lookup in - /// the world, so cheap each frame. API 91. - fn block_state_property(&self, state: BlockStateId, key: &str) -> Option<&str> { - let _ = state; - let _ = key; - unimplemented!("Host::block_state_property") - } - /// The first block along the view within `distance` (at most 1024), past the loaded chunks over the distant terrain. API 116. - fn raycast(&self, distance: f32) -> Option { - let _ = distance; - unimplemented!("Host::raycast") - } - /// The first block along the view within `RAYCAST_MAX`, and its distance. API 116. - fn view_hit(&self) -> Option<(f32, NativeHit)> { - unimplemented!("Host::view_hit") - } - /// Right-clicks with these items (at most 64 ids) are mod `mod_id`'s; a later call replaces the list. API 116. - fn claim_items(&mut self, mod_id: &str, ids: &[String]) { - let _ = mod_id; - let _ = ids; - unimplemented!("Host::claim_items") - } - /// Mod `mod_id`'s events since its last poll (at most 256 wait). API 116. - fn poll(&mut self, mod_id: &str) -> Vec { - let _ = mod_id; - unimplemented!("Host::poll") - } - /// Sets the stack of item entity `id` in the server level `dimension`, as - /// a hopper's `addItem` leaves what it couldn't take (`ItemEntity.setItem`); - /// an empty stack (count 0) removes the entity (`setItem(EMPTY)`, then - /// `discard`). Players see it on their next tracker update. False when the - /// level in place has no such item entity, or on a client. API 42. - fn set_item_entity(&mut self, dimension: &str, id: u32, stack: ItemStack) -> bool { - let _ = dimension; - let _ = id; - let _ = stack; - unimplemented!("Host::set_item_entity") - } - /// The block at `pos` changed its comparator output - /// (`native_analog_output`) without changing its state: the comparators - /// beside it, or behind a conductor there, read it again - /// (`Level.updateNeighbourForOutputSignal`). Acts on the level in place: - /// false (and nothing happens) when `dimension` is another, or this - /// window isn't a server. API 64. - fn update_output_neighbors(&mut self, dimension: &str, pos: [i32; 3]) -> bool { - let _ = dimension; - let _ = pos; - unimplemented!("Host::update_output_neighbors") - } - /// The block at `pos` changed the signal it gives: its neighbours, and - /// theirs (a block it powers passes the signal on), read it again, as - /// around a lever (`Level.updateNeighborsAt`). Call it when the answer - /// changes and once the block is placed. Acts on the level in place: - /// false (and nothing happens) when `dimension` is another, or this - /// window isn't a server. API 38. - fn update_signal_neighbors(&mut self, dimension: &str, pos: [i32; 3]) -> bool { - let _ = dimension; - let _ = pos; - unimplemented!("Host::update_signal_neighbors") - } - /// Sends clients the block at `pos` and its block entity's update tag - /// (`native_block_entity_update_tag`) again, as `Level.sendBlockUpdated` - /// does: with the level's other block changes, to the players who see its - /// chunk, once however often it is called before then. Changes made in a - /// block entity's tick go out at the start of the next tick, as vanilla's - /// do (`ServerLevel.tick` broadcasts chunks before it ticks block - /// entities). Call it when what clients show changed (a machine started), - /// not for every tick's progress: a menu's data slots carry that. Nothing - /// happens for an unloaded block. API 52. - fn block_entity_sync(&mut self, pos: [i32; 3]) { - let _ = pos; - unimplemented!("Host::block_entity_sync") - } - /// Plays sound event `sound` (`minecraft:entity.generic.explode`, or a - /// mod's own from its `assets//sounds.json`) at `at` for this - /// player only, `delay` ticks from now (0 to 1200, a minute): a client - /// mod's sound that waits, as thunder heard after the flash or a blast - /// heard when its shock wave arrives. `volume` past 1 carries farther, - /// as vanilla's attenuation does (16 blocks times the volume). False, with - /// nothing played, on a server or for a value out of range; an event the - /// resources don't have plays nothing, as vanilla's. API - /// 83. - fn play_local_sound( - &mut self, - sound: &str, - at: [f64; 3], - volume: f32, - pitch: f32, - delay: i32, - ) -> bool { - let _ = sound; - let _ = at; - let _ = volume; - let _ = pitch; - let _ = delay; - unimplemented!("Host::play_local_sound") - } - /// A rectangle in GUI pixels, `argb` (0x78000000 is the extras' box). - fn fill(&self, batch: &mut HudBatch, rect: [f32; 4], argb: u32) { - let _ = batch; - let _ = rect; - let _ = argb; - unimplemented!("Host::fill") - } - /// Part `src` (`[x, y, w, h]` in the sprite's own pixels) of sprite - /// `name` (vanilla's `gui/...`, or a content mod's GUI texture - /// `mymod:gui/...`), drawn stretched into `dst` (`[x, y, w, h]` in GUI - /// pixels). False when there is no such sprite. API 19. - fn sprite(&self, batch: &mut HudBatch, name: &str, src: [f32; 4], dst: [f32; 4]) -> bool { - let _ = batch; - let _ = name; - let _ = src; - let _ = dst; - unimplemented!("Host::sprite") - } - /// Item `item`'s icon (`mymod:machine`) with its top left at `at`, - /// `size` GUI pixels square (16 in a slot); false for an unknown item. - /// API 20. - fn item(&self, batch: &mut HudBatch, item: &str, at: [f32; 2], size: f32) -> bool { - let _ = batch; - let _ = item; - let _ = at; - let _ = size; - unimplemented!("Host::item") - } - /// The open menu's title (the block's name, or the one it was given), - /// as plain text. API 20. - fn screen_title(&self) -> Option { - unimplemented!("Host::screen_title") - } - /// A data file of the game's resources, content mods' included - /// (`data/mymod/machines.json`), when there is one: what a mod's content - /// says, read at runtime. Only relative paths of plain names. API 20. - fn data(&self, path: &str) -> Option> { - let _ = path; - unimplemented!("Host::data") - } - /// The block at `pos` (`mymod:cable`; air where nothing is loaded). - /// API 20. - fn block(&self, pos: [i32; 3]) -> String { - let _ = pos; - unimplemented!("Host::block") - } - /// Property `key` of the block at `pos` (a machine's `facing`), if it - /// has one. API 20. - fn block_property(&self, pos: [i32; 3], key: &str) -> Option { - let _ = pos; - let _ = key; - unimplemented!("Host::block_property") - } - /// Puts `stack` into the container at `pos` (a chest, a furnace, a mod - /// container) through its side `face`, as a hopper would, one by one, and - /// returns what did not fit: a machine's auto-output. A composter takes - /// one a call, and a client's copy of the world none. API 20. - fn insert_item( - &mut self, - pos: [i32; 3], - face: Direction, - stack: ItemStack, - ) -> Option { - let _ = pos; - let _ = face; - let _ = stack; - unimplemented!("Host::insert_item") - } - /// Where the mouse is, in GUI pixels. API 20. - fn cursor(&self) -> [f32; 2] { - unimplemented!("Host::cursor") - } - /// A line of text at `(x, y)` in GUI pixels, opaque `rgb`. - fn text(&self, batch: &mut HudBatch, text: &str, at: [f32; 2], rgb: u32, shadow: bool) { - let _ = batch; - let _ = text; - let _ = at; - let _ = rgb; - let _ = shadow; - unimplemented!("Host::text") - } - /// The folder mod `mod_id`'s post-build step wrote into when the launcher built the game - /// (`native-data//` in the build's version folder); None when the game's own path is - /// unknown. API 41. - fn built_data(&self, mod_id: &str) -> Option { - let _ = mod_id; - unimplemented!("Host::built_data") - } - /// Whether the player left mod `mod_id` on in Options > Mods. A mod - /// turned off does nothing (check this first in each hook) until the - /// launcher's next build leaves it out. - fn on(&self, mod_id: &str) -> bool { - let _ = mod_id; - unimplemented!("Host::on") - } - /// The key the player chose for a mod's key `name`, else `default` - /// (`key.keyboard.z`), as the WebAssembly mods' keys were kept - /// (`key..` in `horizon-mods.txt`). - fn mod_key(&self, mod_id: &str, name: &str, default: &str) -> String { - let _ = mod_id; - let _ = name; - let _ = default; - unimplemented!("Host::mod_key") - } - /// A mod's setting `key` (one file per key in `native_data_dir`). - fn setting(&self, mod_id: &str, key: &str) -> Option { - let _ = mod_id; - let _ = key; - unimplemented!("Host::setting") - } - /// Keeps a mod's setting `key`. - fn set_setting(&self, mod_id: &str, key: &str, value: &str) { - let _ = mod_id; - let _ = key; - let _ = value; - unimplemented!("Host::set_setting") - } - /// Sets property `key` of the block at `pos` to `value`, as a furnace's - /// tick sets its `lit` (`Level.setBlock` with `UPDATE_ALL`: neighbours - /// and clients are told); true when the block changed, false when the - /// block has no such property or value, or the property already has - /// `value` (as `Level.setBlock` for an unchanged state). The block - /// entity stays. API 18. - fn set_block_property(&mut self, pos: [i32; 3], key: &str, value: &str) -> bool { - let _ = pos; - let _ = key; - let _ = value; - unimplemented!("Host::set_block_property") - } - /// `native_set_block_property` with `Level.setBlock`'s `flags`, as a - /// brewing stand's tick sets its bottles with `flags::UPDATE_CLIENTS`. - /// Players get the write whatever the flags (`flags`); false when this - /// window isn't a server, and in each case `native_set_block_property` - /// returns false. API 33. - fn set_block_property_with( - &mut self, - pos: [i32; 3], - key: &str, - value: &str, - flags: i32, - ) -> bool { - let _ = pos; - let _ = key; - let _ = value; - let _ = flags; - unimplemented!("Host::set_block_property_with") - } - /// The game's `config/` folder beside `options.txt`, shared by every mod - /// as on Fabric and NeoForge: a mod names its own files in it - /// (`config/.json`, or a folder of its own). The same folder on the - /// window, its server thread and a LAN host; a dedicated server's is in - /// its own folder, beside `server.properties`. Made when missing. No - /// `self`, so a mixin helper calls `Game::native_config_dir()` and a trait - /// mod `Host::config_dir`. API 26. - fn config_dir(&self) -> Result { - unimplemented!("Host::config_dir") - } - /// Shows the panel beside container screens with these items (at most - /// 8192), as the game's own slots in pages, with a search box; the - /// player's clicks, searches and keys over items come back as - /// `native_panel_event`. The panel is the last mod's to show one; empty - /// items and no recipe page take yours away. - fn panel_items(&mut self, mod_id: &str, items: Vec) { - let _ = mod_id; - let _ = items; - unimplemented!("Host::panel_items") - } - /// The recipes of type `kind` (`mymod:crusher`), a type outside `minecraft`, - /// from `data//recipe/*.json` of the content mods (`.hmod`) and - /// the world's data packs, in file order: each one's id and its file as - /// JSON, for the mod to read as it likes. A data pack changes them on - /// `/reload`, so look them up again when you need them. Empty for a type - /// nobody uses and outside a world. These are the server's table on the - /// server thread, a dedicated server or a LAN host; a client, the - /// single-player window included, sees only the content mods' recipes, not - /// the world's data packs', unless a mod had the server send it the type - /// (`native_datapack_sync`). API 36. - fn recipes_of_type(&self, kind: &str) -> &[CustomRecipe] { - let _ = kind; - unimplemented!("Host::recipes_of_type") - } - /// An ingredient as a recipe's JSON names it: an item, a `#tag`, or a list - /// of them, a bare id as `minecraft:` (as vanilla reads it); `test` says - /// whether a stack fits. Items the game doesn't know are left out; None - /// when nothing is left (an unknown or empty tag, for one) and outside a - /// world. API 36. - fn recipe_ingredient(&self, json: &serde_json::Value) -> Option { - let _ = json; - unimplemented!("Host::recipe_ingredient") - } - /// A stack as a recipe's `result` names it: `id` (a bare one as - /// `minecraft:`), `count` (1 by default, up to 99) and `components`. None - /// without an `id`, for an item the game doesn't know or a count outside - /// that, and outside a world. API 36. - fn recipe_result(&self, json: &serde_json::Value) -> Option { - let _ = json; - unimplemented!("Host::recipe_result") - } - /// The `book_id`s of the recipes the player's recipe book holds now - /// (the server's book on a server); empty outside a world. - fn known_recipes(&mut self) -> Arc<[i32]> { - unimplemented!("Host::known_recipes") - } - /// Places a recipe in the open screen's grid as a click in the recipe - /// book does (`ServerboundPlaceRecipePacket`): the inventory's grid or a - /// crafting table's. `all` places as many as the items allow, as with - /// Shift. Refused while no such screen is open and for a recipe the - /// player's book doesn't hold; one missing items shows as a ghost. - fn place_recipe(&mut self, book_id: i32, all: bool) -> Result<(), String> { - let _ = book_id; - let _ = all; - unimplemented!("Host::place_recipe") - } - /// Creative mode only: gives the player `count` of `item` - /// (`minecraft:diamond`) as taking it from the creative inventory does - /// (`ServerboundSetCreativeModeSlot`): the selected slot first, then the - /// hotbar, then the inventory. The count is cut to one stack. - fn give(&mut self, item: &str, count: u32) -> Result<(), String> { - let _ = item; - let _ = count; - unimplemented!("Host::give") - } - /// For a client mod: a push added to the local player's velocity, in blocks a tick, as an - /// explosion's knockback is (the window's player moves itself, as vanilla's client does). Each component is - /// clamped to -4..4; a spectator, or a value that is not a number, is left alone. API 105. - fn push_player(&mut self, kick: [f32; 3]) { - let _ = kick; - unimplemented!("Host::push_player") - } - /// For a server mod: `player`'s creative flying speed (vanilla's 0.05 when none, at most `FLYING_MAX`), kept on - /// the server's player and sent to their client. A respawn or a game mode change keeps it, and it is saved - /// with the player (`abilities.flySpeed`, as vanilla saves it). False for a player not here or a client of a - /// server, and inside a player's hook, where the server's player list is busy: call it from a tick hook. - /// API 105. - fn set_flying_speed(&mut self, player: u128, speed: Option) -> bool { - let _ = player; - let _ = speed; - unimplemented!("Host::set_flying_speed") - } - /// Sends `data` on `channel` (`mod_id:name`, at most 30 000 bytes, at - /// most 32 a player a tick) to `player`'s client, at the tick's end, from - /// any server hook. Ok means accepted: a player who left or isn't on a - /// Horizon client doesn't get it. API 40. - fn net_send_to_player( - &mut self, - player: u128, - channel: &str, - data: &[u8], - ) -> Result<(), String> { - let _ = player; - let _ = channel; - let _ = data; - unimplemented!("Host::net_send_to_player") - } - /// The client half: sends `data` on `channel` to the server (the same - /// caps, 32 a tick). An error when the server doesn't carry mod messages - /// (a vanilla server), and until the player has joined: a server says it - /// carries them during configuration, before it can take one. API 40. - fn net_send_to_server(&mut self, channel: &str, data: &[u8]) -> Result<(), String> { - let _ = channel; - let _ = data; - unimplemented!("Host::net_send_to_server") - } - /// Drop one item as DefaultDispenseItemBehavior does. For a dispenser hook: - /// return this remainder in its answer with `CLICK`; the caller plays the - /// answer's event and smoke once. A client, another dimension or an unloaded - /// chunk keeps the input stack unchanged. API 67. - fn dispense_default( - &mut self, - dimension: &str, - pos: [i32; 3], - facing: Direction, - stack: ItemStack, - ) -> Option { - let _ = dimension; - let _ = pos; - let _ = facing; - let _ = stack; - unimplemented!("Host::dispense_default") - } - /// Opens a chest menu of `rows` rows (1 to 6) titled `title` for the - /// player, showing `items` (by slot, padded with empty ones), in place of - /// the menu they had open: its window id. - fn open_menu( - &mut self, - uuid: u128, - rows: u8, - title: Text, - items: Vec>, - ) -> Option { - let _ = uuid; - let _ = rows; - let _ = title; - let _ = items; - unimplemented!("Host::open_menu") - } - /// Replaces what the player's open native menu shows; sent with the - /// tick's container sync. - fn set_menu_items(&mut self, uuid: u128, items: Vec>) { - let _ = uuid; - let _ = items; - unimplemented!("Host::set_menu_items") - } - /// Closes the player's native menu (a `native_menu_closed` follows). - fn close_menu(&mut self, uuid: u128) { - let _ = uuid; - unimplemented!("Host::close_menu") - } - /// The player's 36 inventory slots (0..9 the hotbar), to show or offer - /// them; None for a player not here, dead, or whose state is swapped in. - fn player_stacks(&self, uuid: u128) -> Option>> { - let _ = uuid; - unimplemented!("Host::player_stacks") - } - /// Swaps two players' offers (inventory slots, each with the stack it - /// must still hold, exactly) in one step: the stacks leave their slots - /// and go to the other player where they fit whole, or nothing moves and - /// the Err says why. Both must be here and alive, neither with their - /// state swapped in. Their screens follow with the tick's sync. - fn exchange_items( - &mut self, - a: u128, - a_offer: &[(usize, ItemStack)], - b: u128, - b_offer: &[(usize, ItemStack)], - ) -> Result<(), String> { - let _ = a; - let _ = a_offer; - let _ = b; - let _ = b_offer; - unimplemented!("Host::exchange_items") - } - /// `native_fluid_insert`: up to `amount` millibuckets of `fluid` - /// (`minecraft:water`, `minecraft:lava`) into the block at `pos`, through - /// its side `face`, in the level in place: a mod block's tanks first - /// those with the same fluid, then empty ones, by the sides and tanks its - /// table gave; a cauldron by thirds of a bucket, a lava one only whole. - /// What did not fit. API 54. - fn fluid_insert(&mut self, pos: [i32; 3], face: Direction, fluid: &str, amount: i32) -> i32 { - let _ = pos; - let _ = face; - let _ = fluid; - let _ = amount; - unimplemented!("Host::fluid_insert") - } - /// Takes up to `amount` millibuckets out of the block at `pos` in the - /// level in place, `dimension`, through its side `face`: a mod block's - /// drainable tanks, one fluid only (the first that's there), or a - /// cauldron, which drains in thirds of a bucket and a lava one only - /// whole. The fluid and how much; none when the side reaches nothing. - /// API 54. - fn fluid_extract( - &mut self, - dimension: &str, - pos: [i32; 3], - face: Direction, - amount: i32, - ) -> Option<(String, i32)> { - let _ = dimension; - let _ = pos; - let _ = face; - let _ = amount; - unimplemented!("Host::fluid_extract") - } - /// Moves up to `max` millibuckets from the block at `from`, through its - /// side `from_face`, into the one at `to`, through its side `to_face`, as - /// `native_move_items` moves items: what the target won't take goes back - /// into the source, so none is lost — a cauldron's whole thirds included. - /// The fluid and how much moved; none where `native_fluid_tanks` is none - /// at `from`, where nothing drainable is, where no block holds fluids at - /// `to` or it has no room for that fluid, and within one block. The - /// source is asked for no more than the target's room. API 55. - fn move_fluids( - &mut self, - dimension: &str, - from: [i32; 3], - from_face: Direction, - to: [i32; 3], - to_face: Direction, - max: i32, - ) -> Option<(String, i32)> { - let _ = dimension; - let _ = from; - let _ = from_face; - let _ = to; - let _ = to_face; - let _ = max; - unimplemented!("Host::move_fluids") - } - /// The level in place (`minecraft:overworld`), for a hook that isn't told - /// which it runs in. API 48. - fn dimension(&self) -> &'static str { - unimplemented!("Host::dimension") - } - /// Takes up to `count` items out of slot `slot` of the container at `pos`, - /// through its side `face`; they're the mod's to place then, as a hopper's - /// are: to hand them on without losing any, use `native_move_items`. None - /// when nothing can be taken: see `native_container_slots`. API 48. - fn extract_item( - &mut self, - dimension: &str, - pos: [i32; 3], - face: Direction, - slot: usize, - count: u32, - ) -> Option { - let _ = dimension; - let _ = pos; - let _ = face; - let _ = slot; - let _ = count; - unimplemented!("Host::extract_item") - } - /// Moves up to `max` items from the container at `from`, through its side - /// `from_face`, into the one at `to`, through its side `to_face`, one by - /// one as a hopper does: an item the target refuses stays in its slot, so - /// none is lost. `item` (`minecraft:coal`) takes only that item. How many - /// moved: 0 where `native_container_slots` is none at `from`, where no - /// container is at `to`, and within one container. A composter takes one - /// a call. API 48. - fn move_items( - &mut self, - dimension: &str, - from: [i32; 3], - from_face: Direction, - to: [i32; 3], - to_face: Direction, - item: Option<&str>, - max: u32, - ) -> u32 { - let _ = dimension; - let _ = from; - let _ = from_face; - let _ = to; - let _ = to_face; - let _ = item; - let _ = max; - unimplemented!("Host::move_items") - } - /// Hides that player (a vanish) from the players below permission - /// `level`, or shows them again (None): from the next tick those players - /// lose them from the Tab list and the world (their client stops - /// tracking the player, as it does a spectator), and don't read their - /// joining or leaving; players of `level` or more still see them, and - /// a player made an operator meanwhile sees them again. By UUID: kept - /// for a player who is off (who joins hidden), until a mod sets it again - /// or the server stops. Works anywhere on a server, inside a player's - /// hook too; false on a client and in a world nobody else can join. - /// API 314. - fn hide_player(&mut self, uuid: u128, level: Option) -> bool { - let _ = uuid; - let _ = level; - unimplemented!("Host::hide_player") - } - /// Draw a stack and its slot decorations, except the count, at `at`, - /// `size` GUI pixels square. False when nothing was drawn: an unknown item - /// or an empty stack (air, or a count of 0, as `ItemStack.isEmpty`). - /// API 313. - fn item_stack(&self, batch: &mut HudBatch, stack: &ItemStack, at: [f32; 2], size: f32) -> bool { - let _ = batch; - let _ = stack; - let _ = at; - let _ = size; - unimplemented!("Host::item_stack") - } - /// A mod's own RGBA image, uploaded into the HUD atlas slot - /// [`MOD_IMAGE_SPRITE`](crate::hud::MOD_IMAGE_SPRITE) and drawn with - /// `native_sprite`: its own source rectangle crops and stretches it. `rgba` - /// is `width * height` pixels of four bytes, each side at most - /// [`MOD_IMAGE_SIZE`](crate::hud::MOD_IMAGE_SIZE); false when it is not that - /// size. The upload costs a texture patch of the image, so send one when the - /// image changed, not every frame. API 355. - fn mod_image(&mut self, width: u32, height: u32, rgba: &[u8]) -> bool { - let _ = width; - let _ = height; - let _ = rgba; - unimplemented!("Host::mod_image") - } - /// The instance (game) folder, which its players share: `saves/`, - /// `resourcepacks/`, `shaderpacks/` and `horizon-mods/` are in it, and the - /// title's screens create worlds and read packs there. A world's own - /// `datapacks/` is in that world's folder (`saves//datapacks`). - /// The folder the game runs in (`--player-dir` moves a player's options - /// and servers only, not the instance). Nothing is created. API 326. - fn instance_dir(&self) -> PathBuf { - unimplemented!("Host::instance_dir") - } - /// The most recently extracted world frame's camera, if one exists. - fn camera(&self) -> Option { - unimplemented!("Host::camera") - } - /// Collision boxes in block-local coordinates with an empty collision context. - fn block_collision(&self, pos: [i32; 3]) -> Vec<[f64; 6]> { - let _ = pos; - unimplemented!("Host::block_collision") - } - /// The entity `id` (the id events carry) as a handle for the `entity_*` - /// methods: None on a client, with no world open, or for no such entity - /// in the level in place or among the server's players. API - /// @API:entity_handles@. - fn entity(&self, id: u32) -> Option { - let _ = id; - unimplemented!("Host::entity") - } - /// The level `dimension` (`minecraft:the_nether`) as a handle: None on a - /// client, with no world open, or for a dimension the server hasn't - /// loaded. API @API:entity_handles@. - fn level(&self, dimension: &str) -> Option { - let _ = dimension; - unimplemented!("Host::level") - } - /// The entity's position (`Entity.position`): its feet, the middle of - /// its box for a painting or an item frame. API @API:entity_handles@. - fn entity_position(&self, entity: EntityRef) -> Option<[f64; 3]> { - let _ = entity; - unimplemented!("Host::entity_position") - } - /// The block the entity's position is in (`Entity.blockPosition`). API - /// @API:entity_handles@. - fn entity_block_position(&self, entity: EntityRef) -> Option<[i32; 3]> { - let _ = entity; - unimplemented!("Host::entity_block_position") - } - /// The entity's yaw and pitch in degrees (`Entity.getYRot`, `getXRot`); 0 - /// for the kinds that don't turn. API @API:entity_handles@. - fn entity_rotation(&self, entity: EntityRef) -> Option<[f32; 2]> { - let _ = entity; - unimplemented!("Host::entity_rotation") - } - /// The entity's type (`Entity.getType`): `minecraft:zombie`. API - /// @API:entity_handles@. - fn entity_kind(&self, entity: EntityRef) -> Option { - let _ = entity; - unimplemented!("Host::entity_kind") - } - /// The entity's UUID (`Entity.getUUID`), the one it is saved with; an - /// entity that has none yet gets it now, as a new vanilla entity does. - /// API @API:entity_handles@. - fn entity_uuid(&mut self, entity: EntityRef) -> Option { - let _ = entity; - unimplemented!("Host::entity_uuid") - } - /// The level the entity is in (`Entity.level`). API @API:entity_handles@. - fn entity_level(&self, entity: EntityRef) -> Option { - let _ = entity; - unimplemented!("Host::entity_level") - } - /// The entity's velocity in blocks a tick (`Entity.getDeltaMovement`). A - /// player's is the move its client last reported (`getKnownMovement`); an - /// entity Horizon doesn't simulate (a display, a painting) has none, so - /// zero. API @API:entity_motion@. - fn entity_velocity(&self, entity: EntityRef) -> Option<[f64; 3]> { - let _ = entity; - unimplemented!("Host::entity_velocity") - } - /// Sets the entity's velocity (`Entity.setDeltaMovement`); clients see it - /// as they see the entity move. False for a player (its client moves - /// it), an entity Horizon doesn't simulate, or a value that isn't finite. - /// API @API:entity_motion@. - fn entity_set_velocity(&mut self, entity: EntityRef, velocity: [f64; 3]) -> bool { - let _ = entity; - let _ = velocity; - unimplemented!("Host::entity_set_velocity") - } - /// Whether the entity stands on a block (`Entity.onGround`). API - /// @API:entity_motion@. - fn entity_on_ground(&self, entity: EntityRef) -> Option { - let _ = entity; - unimplemented!("Host::entity_on_ground") - } - /// The entity's box (`Entity.getBoundingBox`): its lowest and highest - /// corner. None for the kinds whose box Horizon doesn't keep (displays, - /// markers, End crystals, the dragon). API @API:entity_motion@. - fn entity_bounding_box(&self, entity: EntityRef) -> Option<[[f64; 3]; 2]> { - let _ = entity; - unimplemented!("Host::entity_bounding_box") - } - /// The height of the entity's eyes above its feet (`Entity.getEyeHeight`). - /// API @API:entity_motion@. - fn entity_eye_height(&self, entity: EntityRef) -> Option { - let _ = entity; - unimplemented!("Host::entity_eye_height") - } - /// Where the entity's eyes are (`Entity.getEyePosition`). API - /// @API:entity_motion@. - fn entity_eye_position(&self, entity: EntityRef) -> Option<[f64; 3]> { - let _ = entity; - unimplemented!("Host::entity_eye_position") - } - /// The unit vector the entity looks along (`Entity.getViewVector`, from - /// its pitch and yaw). API @API:entity_motion@. - fn entity_view_vector(&self, entity: EntityRef) -> Option<[f64; 3]> { - let _ = entity; - unimplemented!("Host::entity_view_vector") - } - /// The square of the distance from the entity's feet to `pos` - /// (`Entity.distanceToSqr`). API @API:entity_motion@. - fn entity_distance_to_sqr(&self, entity: EntityRef, pos: [f64; 3]) -> Option { - let _ = entity; - let _ = pos; - unimplemented!("Host::entity_distance_to_sqr") - } - /// `Capabilities.Item.ITEM`, read: the slots of one item of `stack`, - /// empty ones included, kept in its `minecraft:container`. None for an - /// item without slots, and for an empty stack. - fn item_container_slots(&self, stack: &ItemStack) -> Option>> { - let _ = stack; - unimplemented!("Host::item_container_slots") - } - /// `ResourceHandler.insert`: as much of `item` as fits into one item of - /// `stack`, topping up the slots that hold the same item and components - /// first, then the first empty ones (`SimpleContainer.addItem`'s order), - /// no slot past its limit (99, or the item's own when less). A shulker - /// box never goes in. The count that went in, and the single item it - /// becomes (the item itself when none did). The stack given is not - /// changed: shrink it by one and hand out the result, or leave the result - /// unused to simulate. None where the item has no slots. - fn item_container_insert( - &self, - stack: &ItemStack, - item: &ItemStack, - ) -> Option<(u32, ItemStack)> { - let _ = stack; - let _ = item; - unimplemented!("Host::item_container_insert") - } - /// `ResourceHandler.extract`: up to `max` out of slot `slot` of one item - /// of `stack`. What came out (none when the slot is empty or past the - /// item's slots), and the single item it becomes. The stack given is not - /// changed, as with `native_item_container_insert`. None where the item - /// has no slots. - fn item_container_extract( - &self, - stack: &ItemStack, - slot: usize, - max: u32, - ) -> Option<(Option, ItemStack)> { - let _ = stack; - let _ = slot; - let _ = max; - unimplemented!("Host::item_container_extract") - } - /// The validated category and icon of a custom recipe type, or None for - /// an undeclared/invalid type, before resources load, or outside a world. - /// Rebuilt when the recipe table changes. Does not unlock any recipes. - fn recipe_book_category(&mut self, kind: &str) -> Option { - let _ = kind; - unimplemented!("Host::recipe_book_category") - } -} -impl EntityRef { - /// The entity's position (`Entity.position`): its feet, the middle of - /// its box for a painting or an item frame. API @API:entity_handles@. - pub fn position(self, host: &dyn Host) -> Option<[f64; 3]> { - host.entity_position(self) - } - /// The block the entity's position is in (`Entity.blockPosition`). API - /// @API:entity_handles@. - pub fn block_position(self, host: &dyn Host) -> Option<[i32; 3]> { - host.entity_block_position(self) - } - /// The entity's yaw and pitch in degrees (`Entity.getYRot`, `getXRot`); 0 - /// for the kinds that don't turn. API @API:entity_handles@. - pub fn rotation(self, host: &dyn Host) -> Option<[f32; 2]> { - host.entity_rotation(self) - } - /// The entity's type (`Entity.getType`): `minecraft:zombie`. API - /// @API:entity_handles@. - pub fn kind(self, host: &dyn Host) -> Option { - host.entity_kind(self) - } - /// The entity's UUID (`Entity.getUUID`), the one it is saved with; an - /// entity that has none yet gets it now, as a new vanilla entity does. - /// API @API:entity_handles@. - pub fn uuid(self, host: &mut dyn Host) -> Option { - host.entity_uuid(self) - } - /// The level the entity is in (`Entity.level`). API @API:entity_handles@. - pub fn level(self, host: &dyn Host) -> Option { - host.entity_level(self) - } - /// The entity's velocity in blocks a tick (`Entity.getDeltaMovement`). A - /// player's is the move its client last reported (`getKnownMovement`); an - /// entity Horizon doesn't simulate (a display, a painting) has none, so - /// zero. API @API:entity_motion@. - pub fn velocity(self, host: &dyn Host) -> Option<[f64; 3]> { - host.entity_velocity(self) - } - /// Sets the entity's velocity (`Entity.setDeltaMovement`); clients see it - /// as they see the entity move. False for a player (its client moves - /// it), an entity Horizon doesn't simulate, or a value that isn't finite. - /// API @API:entity_motion@. - pub fn set_velocity(self, host: &mut dyn Host, velocity: [f64; 3]) -> bool { - host.entity_set_velocity(self, velocity) - } - /// Whether the entity stands on a block (`Entity.onGround`). API - /// @API:entity_motion@. - pub fn on_ground(self, host: &dyn Host) -> Option { - host.entity_on_ground(self) - } - /// The entity's box (`Entity.getBoundingBox`): its lowest and highest - /// corner. None for the kinds whose box Horizon doesn't keep (displays, - /// markers, End crystals, the dragon). API @API:entity_motion@. - pub fn bounding_box(self, host: &dyn Host) -> Option<[[f64; 3]; 2]> { - host.entity_bounding_box(self) - } - /// The height of the entity's eyes above its feet (`Entity.getEyeHeight`). - /// API @API:entity_motion@. - pub fn eye_height(self, host: &dyn Host) -> Option { - host.entity_eye_height(self) - } - /// Where the entity's eyes are (`Entity.getEyePosition`). API - /// @API:entity_motion@. - pub fn eye_position(self, host: &dyn Host) -> Option<[f64; 3]> { - host.entity_eye_position(self) - } - /// The unit vector the entity looks along (`Entity.getViewVector`, from - /// its pitch and yaw). API @API:entity_motion@. - pub fn view_vector(self, host: &dyn Host) -> Option<[f64; 3]> { - host.entity_view_vector(self) - } - /// The square of the distance from the entity's feet to `pos` - /// (`Entity.distanceToSqr`). API @API:entity_motion@. - pub fn distance_to_sqr(self, host: &dyn Host, pos: [f64; 3]) -> Option { - host.entity_distance_to_sqr(self, pos) - } -} diff --git a/crates/horizon-mod-api-gen/Cargo.toml b/crates/horizon-mod-api-gen/Cargo.toml new file mode 100644 index 0000000000..729ae484eb --- /dev/null +++ b/crates/horizon-mod-api-gen/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "horizon-mod-api-gen" +version = "0.1.0" +edition.workspace = true +license.workspace = true +publish = false +description = "Build-time typed native mod API generation" + +[dependencies] +syn = { version = "2", features = ["full", "visit", "visit-mut"] } +quote = "1" +serde = { version = "1", features = ["derive"] } +serde_json = "1" + +[lints] +workspace = true diff --git a/crates/horizon-mod-api-gen/src/build.rs b/crates/horizon-mod-api-gen/src/build.rs new file mode 100644 index 0000000000..adb154655c --- /dev/null +++ b/crates/horizon-mod-api-gen/src/build.rs @@ -0,0 +1,143 @@ +//! Cargo-facing generation and the launcher's trait injection boundary. +use super::{Target, catalog_hooks, free_function_source, sources}; +use quote::ToTokens; +use std::{collections::HashSet, fs, path::Path}; + +/// Generate only this crate's files, tracking every source read by the generator. +/// `root` is supplied at run time by the consuming crate's build script. +pub fn generate(root: &Path, out_dir: &Path, target: Target) -> Result<(), String> { + track(root)?; + let generated = sources(root, super::FillMode::Require)?; + let outputs = match target { + Target::Render => vec![("native_render_generated.rs", generated.render)], + Target::Client => { + println!("cargo:rerun-if-env-changed=HORIZON_MOD_API_INJECTIONS"); + let hooks = if let Some(path) = std::env::var_os("HORIZON_MOD_API_INJECTIONS") { + let path = Path::new(&path); + println!("cargo:rerun-if-changed={}", path.display()); + let injections = + fs::read_to_string(path).map_err(|e| format!("{}: {e}", path.display()))?; + inject(&generated.hooks, &injections)? + } else { + generated.hooks + }; + vec![ + ("mod_api_generated.rs", hooks), + ("mod_api_generated_host.rs", generated.host), + ("mod_api_host_generated.rs", generated.implementation), + ] + } + }; + for (name, code) in outputs { + syn::parse_file(&code).map_err(|e| format!("generated Rust: {e}"))?; + fs::write(out_dir.join(name), code).map_err(|e| e.to_string())?; + } + Ok(()) +} + +pub(super) fn track(root: &Path) -> Result<(), String> { + fn directory(path: &Path, extension: &str, recursive: bool) -> Result<(), String> { + // Track the directory as well: adding a helper source or fragment changes the inputs. + println!("cargo:rerun-if-changed={}", path.display()); + for entry in fs::read_dir(path).map_err(|e| e.to_string())? { + let path = entry.map_err(|e| e.to_string())?.path(); + if path.is_dir() && recursive { + directory(&path, extension, recursive)?; + } else if path.extension().is_some_and(|ext| ext == extension) { + println!("cargo:rerun-if-changed={}", path.display()); + } + } + Ok(()) + } + for path in ["native-hooks.json", "crates/horizon-client/src/mod_api.rs"] { + println!("cargo:rerun-if-changed={}", root.join(path).display()); + } + directory(&root.join("tools/native-hook-fragments"), "json", false)?; + directory(&root.join("crates/horizon-client/src/game"), "rs", true)?; + let catalog = serde_json::from_str( + &fs::read_to_string(root.join("native-hooks.json")).map_err(|e| e.to_string())?, + ) + .map_err(|e| e.to_string())?; + for hook in catalog_hooks(root, &catalog)? { + if let Some((krate, path)) = + free_function_source(hook["target"].as_str().ok_or("target missing")?) + { + let (module, _) = path.rsplit_once("::").ok_or("target missing module")?; + let path = root + .join("crates") + .join(krate) + .join("src") + .join(format!("{}.rs", module.replace("::", "/"))); + println!("cargo:rerun-if-changed={}", path.display()); + } + } + Ok(()) +} + +#[derive(serde::Deserialize)] +#[serde(deny_unknown_fields)] +struct Injection { + name: String, + event: String, + declaration: String, + event_declaration: String, +} + +/// Add the launcher's trait methods and event structs to the generated catalog API. +/// The JSON file is the boundary: parse declarations once, then work with typed Rust items. +pub fn inject(code: &str, injections: &str) -> Result { + let injections: Vec = + serde_json::from_str(injections).map_err(|e| format!("trait injections: {e}"))?; + if injections.is_empty() { + return Ok(code.to_owned()); + } + let mut file = syn::parse_file(code).map_err(|e| format!("generated API: {e}"))?; + let mut events: HashSet = file + .items + .iter() + .filter_map(|item| match item { + syn::Item::Struct(item) => Some(item.ident.to_string()), + syn::Item::Trait(item) => Some(item.ident.to_string()), + _ => None, + }) + .collect(); + let hooks = file + .items + .iter_mut() + .find_map(|item| match item { + syn::Item::Trait(item) if item.ident == "Hooks" => Some(item), + _ => None, + }) + .ok_or("generated API missing Hooks trait")?; + let mut methods: HashSet<_> = hooks + .items + .iter() + .filter_map(|item| match item { + syn::TraitItem::Fn(method) => Some(method.sig.ident.to_string()), + _ => None, + }) + .collect(); + let mut structs = Vec::new(); + for injection in injections { + let method: syn::TraitItemFn = syn::parse_str(&injection.declaration) + .map_err(|e| format!("trait injection {}: {e}", injection.name))?; + let event: syn::ItemStruct = syn::parse_str(&injection.event_declaration) + .map_err(|e| format!("trait injection {}: {e}", injection.event))?; + if method.sig.ident != injection.name || event.ident != injection.event { + return Err("trait injection declaration does not match its name".into()); + } + if !methods.insert(injection.name.clone()) { + return Err(format!( + "duplicate generated trait method {}", + injection.name + )); + } + if !events.insert(injection.event.clone()) { + return Err(format!("duplicate generated event {}", injection.event)); + } + hooks.items.push(syn::TraitItem::Fn(method)); + structs.push(syn::Item::Struct(event)); + } + file.items.extend(structs); + Ok(file.into_token_stream().to_string()) +} diff --git a/crates/horizon-mod-api-gen/src/injection_tests.rs b/crates/horizon-mod-api-gen/src/injection_tests.rs new file mode 100644 index 0000000000..297910096d --- /dev/null +++ b/crates/horizon-mod-api-gen/src/injection_tests.rs @@ -0,0 +1,44 @@ +use super::inject; + +fn injection(name: &str, event: &str) -> serde_json::Value { + serde_json::json!({ + "name": name, + "event": event, + "declaration": format!("fn {name}(&self) {{}}"), + "event_declaration": format!("pub struct {event};"), + }) +} + +#[test] +fn rejects_duplicate_methods_and_event_names_across_injections() { + let api = "pub trait Hooks: Send + Sync {}"; + for declarations in [ + vec![ + injection("tick", "TickEvent"), + injection("tick", "OtherEvent"), + ], + vec![ + injection("tick", "TickEvent"), + injection("other", "TickEvent"), + ], + ] { + assert!(inject(api, &serde_json::to_string(&declarations).unwrap()).is_err()); + } +} + +#[test] +fn rejects_malformed_or_misnamed_declarations_at_the_boundary() { + let api = "pub trait Hooks: Send + Sync {}"; + let mut wrong_method = injection("tick", "TickEvent"); + wrong_method["declaration"] = "fn other(&self) {}".into(); + let mut wrong_event = injection("tick", "TickEvent"); + wrong_event["event_declaration"] = "pub struct OtherEvent;".into(); + let mut invalid_rust = injection("tick", "TickEvent"); + invalid_rust["event_declaration"] = "not a struct".into(); + for declaration in [wrong_method, wrong_event, invalid_rust] { + assert!(inject(api, &serde_json::json!([declaration]).to_string()).is_err()); + } + for invalid in ["{}", "[{}]", "null", "not json"] { + assert!(inject(api, invalid).is_err()); + } +} diff --git a/crates/horizon-mod-api-gen/src/lib.rs b/crates/horizon-mod-api-gen/src/lib.rs new file mode 100644 index 0000000000..0384ebdd32 --- /dev/null +++ b/crates/horizon-mod-api-gen/src/lib.rs @@ -0,0 +1,1577 @@ +//! Generate the typed mod interface from native hook signatures and catalog contracts. +use quote::ToTokens; +use std::{collections::HashSet, fs, path::Path, process::Command}; +use syn::{FnArg, ImplItem, Item, Pat, Type, visit::Visit, visit_mut::VisitMut}; + +fn impl_functions(item: syn::ItemImpl) -> impl Iterator { + item.items.into_iter().filter_map(|item| match item { + ImplItem::Fn(method) => Some(syn::ItemFn { + attrs: method.attrs, + vis: method.vis, + sig: method.sig, + block: Box::new(method.block), + }), + _ => None, + }) +} + +/// The inherent `Game` methods of the files under `path`, each with its module (`native_hooks`, +/// `lan::guest`…) below `module`. +fn game_methods( + path: &Path, + module: &str, + methods: &mut Vec<(String, syn::ItemFn)>, +) -> Result<(), String> { + for entry in fs::read_dir(path).map_err(|e| e.to_string())? { + let path = entry.map_err(|e| e.to_string())?.path(); + let stem = path + .file_stem() + .and_then(|s| s.to_str()) + .unwrap_or_default(); + let inner = format!("{module}::{stem}"); + if path.is_dir() { + game_methods(&path, &inner, methods)?; + } else if path.extension().is_some_and(|ext| ext == "rs") { + let source = fs::read_to_string(&path).map_err(|e| e.to_string())?; + let file = syn::parse_file(&source).map_err(|e| format!("{}: {e}", path.display()))?; + for item in file.items { + if let Item::Impl(item) = item + && item.trait_.is_none() + && let Type::Path(ty) = &*item.self_ty + && ty.path.segments.len() == 1 + && ty.path.segments[0].ident == "Game" + { + methods.extend(impl_functions(item).map(|method| (inner.clone(), method))); + } + } + } + } + Ok(()) +} + +fn doc_comments( + attrs: &[syn::Attribute], + name: &str, + preserve_empty: bool, +) -> Result { + let mut docs = String::new(); + for attr in attrs.iter().filter(|attr| attr.path().is_ident("doc")) { + let syn::Meta::NameValue(value) = &attr.meta else { + return Err(format!("unsupported doc attribute in {name}")); + }; + let syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(doc), + .. + }) = &value.value + else { + return Err(format!("unsupported doc attribute in {name}")); + }; + let value = doc.value(); + for line in value.split('\n') { + if preserve_empty || !line.is_empty() { + docs.push_str(&format!("///{line}\n")); + } + } + } + Ok(docs) +} + +fn exported_types(source: &str, generated: &str) -> Result, String> { + fn use_names(tree: &syn::UseTree, names: &mut HashSet) { + match tree { + syn::UseTree::Path(path) => use_names(&path.tree, names), + syn::UseTree::Name(name) => { + names.insert(name.ident.to_string()); + } + syn::UseTree::Rename(rename) => { + names.insert(rename.rename.to_string()); + } + syn::UseTree::Group(group) => { + for item in &group.items { + use_names(item, names); + } + } + syn::UseTree::Glob(_) => {} + } + } + let mut names = HashSet::new(); + for source in [source, generated] { + let file = syn::parse_file(source).map_err(|e| e.to_string())?; + for item in file.items { + let ident = match item { + Item::Use(item) if matches!(item.vis, syn::Visibility::Public(_)) => { + use_names(&item.tree, &mut names); + None + } + Item::Struct(item) if matches!(item.vis, syn::Visibility::Public(_)) => { + Some(item.ident) + } + Item::Enum(item) if matches!(item.vis, syn::Visibility::Public(_)) => { + Some(item.ident) + } + Item::Union(item) if matches!(item.vis, syn::Visibility::Public(_)) => { + Some(item.ident) + } + Item::Type(item) if matches!(item.vis, syn::Visibility::Public(_)) => { + Some(item.ident) + } + Item::Trait(item) if matches!(item.vis, syn::Visibility::Public(_)) => { + Some(item.ident) + } + Item::Mod(item) if matches!(item.vis, syn::Visibility::Public(_)) => { + Some(item.ident) + } + _ => None, + }; + if let Some(ident) = ident { + names.insert(ident.to_string()); + } + } + } + Ok(names) +} + +fn host_method( + method: &syn::ItemFn, + name: &str, + exports: &HashSet, +) -> Result<(String, String), String> { + struct Types<'a> { + exports: &'a HashSet, + error: Option, + } + impl Types<'_> { + fn check_path(&mut self, path: &syn::Path) { + let first = path.segments.first().unwrap().ident.to_string(); + let unqualified = path.leading_colon.is_none() && path.segments.len() == 1; + let prelude = unqualified + && matches!( + first.as_str(), + "bool" + | "char" + | "str" + | "u8" + | "u16" + | "u32" + | "u64" + | "u128" + | "usize" + | "i8" + | "i16" + | "i32" + | "i64" + | "i128" + | "isize" + | "f32" + | "f64" + | "String" + | "Option" + | "Result" + | "Vec" + | "Box" + | "Arc" + | "PathBuf" + | "AsRef" + | "AsMut" + | "From" + | "Into" + | "TryFrom" + | "TryInto" + | "Iterator" + | "IntoIterator" + | "DoubleEndedIterator" + | "ExactSizeIterator" + | "Extend" + | "Fn" + | "FnMut" + | "FnOnce" + | "Sized" + | "Send" + | "Sync" + | "Unpin" + | "Drop" + | "Default" + | "Clone" + | "Copy" + | "PartialEq" + | "PartialOrd" + | "Eq" + | "Ord" + | "ToOwned" + | "ToString" + | "Future" + | "IntoFuture" + ); + if path.leading_colon.is_some() || !(prelude || self.exports.contains(&first)) { + self.error = Some(format!( + "type {} is not available in mod_api", + path.to_token_stream() + )); + } + } + } + impl<'ast> Visit<'ast> for Types<'_> { + fn visit_type_impl_trait(&mut self, _: &'ast syn::TypeImplTrait) { + self.error = Some("impl Trait is not object-safe".into()); + } + fn visit_type_path(&mut self, ty: &'ast syn::TypePath) { + if ty.qself.is_some() { + self.error = Some("qualified associated types are not supported".into()); + } else { + self.check_path(&ty.path); + } + syn::visit::visit_type_path(self, ty); + } + fn visit_trait_bound(&mut self, bound: &'ast syn::TraitBound) { + self.check_path(&bound.path); + syn::visit::visit_trait_bound(self, bound); + } + } + if method.sig.asyncness.is_some() { + return Err(format!("{name}: async methods are not object-safe")); + } + if method + .sig + .generics + .params + .iter() + .any(|param| !matches!(param, syn::GenericParam::Lifetime(_))) + { + return Err(format!("{name}: generic methods are not object-safe")); + } + let mut types = Types { + exports, + error: None, + }; + for arg in &method.sig.inputs { + if let FnArg::Typed(arg) = arg { + types.visit_type(&arg.ty); + } + } + if let syn::ReturnType::Type(_, ty) = &method.sig.output { + types.visit_type(ty); + } + if let Some(reason) = types.error { + return Err(format!("{name}: {reason}")); + } + let mut sig = method.sig.clone(); + let original = sig.ident.clone(); + sig.ident = syn::parse_str(name).map_err(|e| format!("invalid helper name {name}: {e}"))?; + let mut args = Vec::new(); + let callee = match sig.inputs.first() { + Some(FnArg::Receiver(r)) if r.reference.is_some() => "self.", + Some(FnArg::Receiver(_)) => { + return Err(format!("{name}: expected a borrowed Game receiver")); + } + _ => { + // A receiverless helper stays callable on `&self` in the trait. + sig.inputs.insert(0, syn::parse_quote!(&self)); + "Self::" + } + }; + for arg in sig.inputs.iter_mut().skip(1) { + let FnArg::Typed(arg) = arg else { + return Err(format!("{name}: unexpected receiver")); + }; + let Pat::Ident(pat) = &mut *arg.pat else { + return Err(format!("{name}: unsupported argument")); + }; + pat.mutability = None; + args.push(pat.ident.to_string()); + } + let docs = doc_comments(&method.attrs, name, true)?; + let unused = args + .iter() + .map(|arg| format!("let _ = {arg};")) + .collect::(); + Ok(( + format!( + "{docs}{} {{ {unused} unimplemented!(\"Host::{name}\") }}\n", + sig.to_token_stream() + ), + format!( + "{} {{ {callee}{original}({}) }}\n", + sig.to_token_stream(), + args.join(", ") + ), + )) +} + +/// The handle types a helper's `receiver` names (#7387), keyed as in the catalog. +const HANDLES: [(&str, &str); 2] = [("entity", "EntityRef"), ("level", "LevelRef")]; + +/// A helper's method on its handle (`receiver`): `entity_position(entity)` on +/// `Host` is also `EntityRef::position(self, host)`, which calls it. The +/// helper's first argument is the handle; the host is borrowed as the helper +/// borrows `Game`. +fn handle_method( + method: &syn::ItemFn, + name: &str, + receiver: &str, +) -> Result<(&'static str, String), String> { + let (_, handle) = HANDLES + .iter() + .find(|(key, _)| *key == receiver) + .ok_or_else(|| format!("{name}: unknown receiver {receiver}"))?; + let short = name + .strip_prefix(receiver) + .and_then(|rest| rest.strip_prefix('_')) + .ok_or_else(|| format!("{name}: a {receiver} helper's name starts with {receiver}_"))?; + let mut sig = method.sig.clone(); + sig.ident = + syn::parse_str(short).map_err(|_| format!("{name}: {short} is not a method name"))?; + let mut inputs = sig.inputs.iter(); + let host: FnArg = match inputs.next() { + Some(FnArg::Receiver(r)) if r.reference.is_some() && r.mutability.is_some() => { + syn::parse_quote!(host: &mut dyn Host) + } + Some(FnArg::Receiver(r)) if r.reference.is_some() => syn::parse_quote!(host: &dyn Host), + _ => return Err(format!("{name}: a {receiver} helper borrows Game")), + }; + let takes_handle = matches!( + inputs.next(), + Some(FnArg::Typed(arg)) if matches!(&*arg.ty, Type::Path(ty) if ty.path.is_ident(handle)) + ); + if !takes_handle { + return Err(format!( + "{name}: a {receiver} helper takes a {handle} first" + )); + } + let mut args = String::new(); + let mut rest = vec![syn::parse_quote!(self), host]; + for arg in inputs { + let mut arg = arg.clone(); + let FnArg::Typed(typed) = &mut arg else { + return Err(format!("{name}: unexpected receiver")); + }; + let Pat::Ident(pat) = &mut *typed.pat else { + return Err(format!("{name}: unsupported argument")); + }; + pat.mutability = None; + args.push_str(&format!(", {}", pat.ident)); + rest.push(arg); + } + sig.inputs = rest.into_iter().collect(); + let docs = doc_comments(&method.attrs, name, true)?; + Ok(( + handle, + format!( + "{docs}pub {} {{ host.{name}(self{args}) }}\n", + sig.to_token_stream() + ), + )) +} + +fn has_call(block: &syn::Block, expected: &str) -> bool { + struct Calls<'a> { + expected: &'a str, + found: bool, + } + impl<'ast> Visit<'ast> for Calls<'_> { + fn visit_expr_call(&mut self, call: &'ast syn::ExprCall) { + if let syn::Expr::Path(path) = &*call.func + && path + .path + .segments + .last() + .is_some_and(|s| s.ident == self.expected) + { + self.found = true; + } + syn::visit::visit_expr_call(self, call); + } + } + let mut calls = Calls { + expected, + found: false, + }; + calls.visit_block(block); + calls.found +} + +fn one_function<'a>( + items: impl Iterator, + name: &str, +) -> Result<&'a syn::ItemFn, String> { + let mut matches = items.filter(|item| item.sig.ident == name); + let first = matches + .next() + .ok_or_else(|| format!("expected one {name} signature"))?; + if matches.next().is_some() { + return Err(format!("expected one {name} signature")); + } + Ok(first) +} + +fn fresh_name(base: &str, used: &mut HashSet) -> String { + let mut name = base.to_owned(); + while !used.insert(name.clone()) { + name.push('_'); + } + name +} + +/// Turns a hook argument's type into its event field's. A `&mut` primitive scalar becomes the value +/// itself, written back after every mod has run; any other `&mut` stays a reference to the caller's +/// value. Either way one event goes through every mod, so each sees the changes before it. Every +/// elided reference lifetime becomes the event's `'a`, or `'b` inside a `&mut`, which can't shorten +/// it. The launcher keeps a copy for trait injections (`launcher/src/native_mods/inject.rs`); both +/// test the same types. +#[derive(Default)] +struct EventLifetimes { + a: bool, + b: bool, + in_mut: bool, +} + +impl EventLifetimes { + /// The field's type, and whether it is a scalar copied in and written back. + fn field(&mut self, ty: &Type) -> (Type, bool) { + let scalar = match ty { + Type::Reference(reference) if reference.mutability.is_some() => { + match &*reference.elem { + Type::Path(path) + if path.qself.is_none() + && path.path.get_ident().is_some_and(|ident| { + [ + "bool", "char", "i8", "i16", "i32", "i64", "i128", "isize", + "u8", "u16", "u32", "u64", "u128", "usize", "f32", "f64", + ] + .iter() + .any(|scalar| ident == scalar) + }) => + { + Some(&*reference.elem) + } + _ => None, + } + } + _ => None, + }; + if let Some(scalar) = scalar { + return (scalar.clone(), true); + } + let mut field = ty.clone(); + self.visit_type_mut(&mut field); + (field, false) + } +} + +impl VisitMut for EventLifetimes { + fn visit_type_reference_mut(&mut self, reference: &mut syn::TypeReference) { + if reference.lifetime.is_none() { + reference.lifetime = Some(syn::parse_quote!('_)); + } + let outer = self.in_mut; + self.visit_lifetime_mut(reference.lifetime.as_mut().unwrap()); + self.in_mut |= reference.mutability.is_some(); + self.visit_type_mut(&mut reference.elem); + self.in_mut = outer; + } + fn visit_lifetime_mut(&mut self, lifetime: &mut syn::Lifetime) { + if lifetime.ident == "_" { + *lifetime = if self.in_mut { + self.b = true; + syn::parse_quote!('b) + } else { + self.a = true; + syn::parse_quote!('a) + }; + } + } +} + +fn host_for(is_level: bool, mutable: Option, ticks: bool) -> Option<(&'static str, String)> { + mutable.map(|mutable| { + let (name, kind) = if is_level { + ( + "level", + match (mutable, ticks) { + (true, true) => "LevelWriteHost", + (false, true) => "LevelTicksHost", + (true, false) => "LevelRandomHost", + (false, false) => "LevelHost", + }, + ) + } else { + ("host", "Host") + }; + let mutable = if mutable || (is_level && ticks) { + "mut " + } else { + "" + }; + (name, format!("&{mutable}dyn {kind}")) + }) +} + +fn catalog_hooks( + root: &Path, + catalog: &serde_json::Value, +) -> Result, String> { + let mut hooks = catalog["hooks"] + .as_array() + .ok_or("catalog hooks must be an array")? + .clone(); + let mut paths = fs::read_dir(root.join("tools/native-hook-fragments")) + .map_err(|e| e.to_string())? + .map(|entry| entry.map(|entry| entry.path()).map_err(|e| e.to_string())) + .collect::, _>>()?; + paths.sort(); + for path in paths + .into_iter() + .filter(|path| path.extension().is_some_and(|ext| ext == "json")) + { + let fragment: serde_json::Value = + serde_json::from_str(&fs::read_to_string(&path).map_err(|e| e.to_string())?) + .map_err(|e| format!("{}: {e}", path.display()))?; + hooks.extend( + fragment["hooks"] + .as_array() + .ok_or("fragment hooks must be an array")? + .iter() + .cloned(), + ); + } + let mut seen = HashSet::new(); + for hook in &hooks { + let name = hook["name"].as_str().ok_or("hook name missing")?; + if !seen.insert(name.to_owned()) { + return Err(format!("duplicate hook {name}")); + } + } + Ok(hooks) +} + +/// The crate folder and module path of a hook target that is a free function +/// outside `Game`. `horizon-client-lib` builds from `horizon-client/src` +/// (`client_lib.rs`); `menu` and `breathing` are also accepted under their +/// older `horizon_client::` names. +fn free_function_source(target: &str) -> Option<(&'static str, String)> { + let prefixes = [ + ("horizon_world::", "horizon-world"), + ("horizon_client_render::", "horizon-client-render"), + ("horizon_client_lib::", "horizon-client"), + ]; + prefixes + .into_iter() + .find_map(|(prefix, krate)| target.strip_prefix(prefix).map(|p| (krate, p.to_owned()))) + .or_else(|| { + ["menu", "breathing"].into_iter().find_map(|module| { + target + .strip_prefix(&format!("horizon_client::{module}::")) + .map(|p| ("horizon-client", format!("{module}::{p}"))) + }) + }) +} + +/// Whether a hook function needs its dispatch `call` filled in: false when it +/// makes the call; true when it is a `Game` or `GameLevel` method whose body is +/// only the placeholder a new hook starts with, `let _ = (every, parameter);` +/// (`let _ = parameter;` for one). Any other body is an error, left alone. +fn needs_fill(method: &syn::ItemFn, call: &str, in_game: bool) -> Result { + if has_call(&method.block, call) { + return Ok(false); + } + let params: Vec = method + .sig + .inputs + .iter() + .filter_map(|arg| match arg { + FnArg::Typed(arg) => Some(arg.pat.to_token_stream().to_string()), + FnArg::Receiver(_) => None, + }) + .collect(); + let named = match &method.block.stmts[..] { + [ + syn::Stmt::Local(syn::Local { + pat: Pat::Wild(_), + init: + Some(syn::LocalInit { + expr, + diverge: None, + .. + }), + .. + }), + ] => match &**expr { + syn::Expr::Tuple(tuple) => Some( + tuple + .elems + .iter() + .map(|e| e.to_token_stream().to_string()) + .collect(), + ), + single @ syn::Expr::Path(_) => Some(vec![single.to_token_stream().to_string()]), + _ => None, + }, + _ => None, + }; + if in_game && named == Some(params) { + return Ok(true); + } + Err(format!( + "missing {call} call in {}: add it, or leave a Game hook's body as the let _ = (…); placeholder for cargo run -p xtask -- mod-api to fill", + method.sig.ident + )) +} + +/// A hook function whose placeholder body becomes `call`. +struct Fill { + native: String, + call: String, +} + +/// `source` with each fill's placeholder statement replaced by its call, +/// formatted as `cargo fmt` would. +fn filled_hooks(source: &str, fills: &[Fill]) -> Result, String> { + let mut source = source.to_owned(); + for Fill { native, call } in fills { + let start = source + .find(&format!("fn {native}(")) + .ok_or_else(|| format!("{native} not found"))?; + let placeholder = start + + source[start..] + .find("let _ =") + .ok_or_else(|| format!("{native} has no placeholder"))?; + let end = placeholder + + source[placeholder..] + .find(';') + .ok_or_else(|| format!("{native} has no placeholder"))? + + 1; + source.replace_range(placeholder..end, call); + } + rustfmt(&source) +} + +/// The workspace's edition, which rustfmt formats generated code for. +const EDITION: &str = "2024"; + +/// `code` as `cargo fmt` formats it. +fn rustfmt(code: &str) -> Result, String> { + let mut child = Command::new("rustfmt") + .args(["--emit", "stdout", "--edition", EDITION]) + .stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .spawn() + .map_err(|e| e.to_string())?; + use std::io::Write; + child + .stdin + .take() + .ok_or("rustfmt stdin")? + .write_all(code.as_bytes()) + .map_err(|e| e.to_string())?; + let formatted = child.wait_with_output().map_err(|e| e.to_string())?; + if !formatted.status.success() { + return Err("rustfmt failed".into()); + } + Ok(formatted.stdout) +} + +/// The crate whose generated sources are being built. +#[derive(Clone, Copy)] +pub enum Target { + Client, + Render, +} + +/// Fill new hooks' placeholders in hand-written source. Never writes generated files. +pub fn fill_hooks(root: &Path) -> Result<(), String> { + sources(root, FillMode::Fill).map(|_| ()) +} + +#[derive(Debug)] +struct Sources { + hooks: String, + host: String, + implementation: String, + render: String, +} + +/// Parse and validate the catalog and hook/helper signatures. +fn sources(root: &Path, fill: FillMode) -> Result { + let catalog: serde_json::Value = serde_json::from_str( + &fs::read_to_string(root.join("native-hooks.json")).map_err(|e| e.to_string())?, + ) + .map_err(|e| e.to_string())?; + let source = fs::read_to_string(root.join("crates/horizon-client/src/game/native_hooks.rs")) + .map_err(|e| e.to_string())?; + let file = syn::parse_file(&source).map_err(|e| e.to_string())?; + let mut methods = Vec::new(); + for item in file.items { + if let Item::Impl(item) = item { + methods.extend(impl_functions(item)); + } + } + let mut traits = String::new(); + let mut dispatch = String::new(); + let mut render_traits = String::new(); + let mut render_dispatch = String::new(); + let hooks = catalog_hooks(root, &catalog)?; + let mut fills = Vec::new(); + for hook in &hooks { + let name = hook["name"].as_str().ok_or("hook name missing")?; + let api = hook + .get("mod_api") + .ok_or_else(|| format!("hook {name} has no mod_api entry: add one"))?; + let native = format!("native_{name}"); + let target = hook["target"].as_str().ok_or("target missing")?; + let is_render = target.starts_with("horizon_client_render::"); + let external = free_function_source(target); + let in_game = external.is_none(); + let (method, call) = if let Some((krate, path)) = external { + let Some((module, function)) = path.rsplit_once("::") else { + return Err(format!("wrong target for {name}")); + }; + if function != native { + return Err(format!("wrong target for {name}")); + } + let path = root + .join("crates") + .join(krate) + .join("src") + .join(format!("{}.rs", module.replace("::", "/"))); + let source = + fs::read_to_string(&path).map_err(|e| format!("{}: {e}", path.display()))?; + let file = syn::parse_file(&source).map_err(|e| e.to_string())?; + let functions: Vec<_> = file + .items + .into_iter() + .filter_map(|item| match item { + Item::Fn(function) => Some(function), + _ => None, + }) + .collect(); + ( + one_function(functions.iter(), &native)?.clone(), + if is_render { + format!("dispatch_{name}") + } else { + name.to_owned() + }, + ) + } else { + if !target.starts_with("horizon_client::game::native_hooks::") + || !target.ends_with(&format!("::{native}")) + { + return Err(format!("wrong target for {name}")); + } + ( + one_function(methods.iter(), &native)?.clone(), + format!("dispatch_{name}"), + ) + }; + let fill = needs_fill(&method, &call, in_game)?; + let sig = &method.sig; + let mut args = Vec::new(); + let mut receiver_mut = None; + for arg in &sig.inputs { + match arg { + FnArg::Receiver(r) => { + receiver_mut = Some(r.mutability.is_some()); + } + FnArg::Typed(arg) => { + let Pat::Ident(pat) = &*arg.pat else { + return Err(format!("unsupported argument in {name}")); + }; + args.push((pat.ident.to_string(), &*arg.ty)); + } + } + } + let is_level = hook["target"] + .as_str() + .is_some_and(|target| target.contains("::GameLevel<")); + let ticks = is_level + && args.iter().any(|(name, ty)| { + name == "ticks" && ty.to_token_stream().to_string() == "& mut Ticks" + }); + if ticks { + args.retain(|(name, _)| name != "ticks"); + } + let mut used: HashSet<_> = args.iter().map(|(name, _)| name.clone()).collect(); + let mod_list = fresh_name("mods", &mut used); + let mod_item = fresh_name("mod_hook", &mut used); + let replacement = fresh_name("replacement", &mut used); + let contract = api["contract"].as_str().ok_or("contract missing")?; + let out = api.get("out").and_then(|v| v.as_str()); + if !matches!( + (contract, out), + ("cancel" | "replace" | "collect", Some(_)) | ("listen", None) + ) { + return Err(format!("invalid contract for {name}")); + } + let output = if let Some(out) = out { + let index = args + .iter() + .position(|(n, _)| n == out) + .ok_or_else(|| format!("missing output {out} in {name}"))?; + Some(args.remove(index)) + } else { + None + }; + let result = if let Some((_, ty)) = &output { + let Type::Reference(reference) = ty else { + return Err(format!("output must be a mutable reference in {name}")); + }; + if reference.mutability.is_none() { + return Err(format!("output must be a mutable reference in {name}")); + } + match contract { + "cancel" if reference.elem.to_token_stream().to_string() == "bool" => { + " -> Flow".to_owned() + } + "replace" => format!(" -> {}", reference.elem.to_token_stream()), + "collect" => String::new(), + _ => return Err(format!("unexpected output type for {name}")), + } + } else { + String::new() + }; + let host = host_for(is_level, receiver_mut, ticks) + .map(|(name, ty)| (fresh_name(name, &mut used), ty)); + let event = fresh_name("event", &mut used); + let event_type = format!( + "{}Event", + name.split('_') + .map(|word| word[..1].to_uppercase() + &word[1..]) + .collect::() + ); + let mut trait_params = vec!["&self".to_owned()]; + let mut dispatch_params = Vec::new(); + let mut call_args = Vec::new(); + if let Some((name, ty)) = &host { + trait_params.push(format!("{name}: {ty}")); + dispatch_params.push(format!("{name}: {ty}")); + call_args.push(name.clone()); + } + dispatch_params.push(if is_render { + format!("{mod_list}: &[&dyn RenderHooks]") + } else { + format!("{mod_list}: &[Box]") + }); + let mut lifetimes = EventLifetimes::default(); + let mut fields = String::new(); + let mut inits = Vec::new(); + let mut write_back = String::new(); + let collected = output.as_ref().filter(|_| contract == "collect"); + for (name, ty) in args.iter().chain(collected) { + dispatch_params.push(format!("{name}: {}", ty.to_token_stream())); + let (field, scalar) = lifetimes.field(ty); + fields.push_str(&format!("pub {name}: {},\n", field.to_token_stream())); + if scalar { + inits.push(format!("{name}: *{name}")); + write_back.push_str(&format!("*{name} = {event}.{name};")); + } else { + inits.push(name.clone()); + } + } + if let Some((name, ty)) = output.as_ref().filter(|_| contract != "collect") { + dispatch_params.push(format!("{name}: {}", ty.to_token_stream())); + } + let (generics, elided) = match (lifetimes.a, lifetimes.b) { + (_, true) => ("<'a, 'b>", "<'_, '_>"), + (true, false) => ("<'a>", "<'_>"), + (false, false) => ("", ""), + }; + trait_params.push(format!("{event}: &mut {event_type}{elided}")); + call_args.push(format!("&mut {event}")); + let unused = trait_params[1..] + .iter() + .map(|param| format!("let _ = {};", param.split(':').next().unwrap())) + .collect::(); + let default = match contract { + "cancel" => "Flow::Continue", + "replace" => "None", + _ => "", + }; + if fill { + // The dispatch's parameters in order: the host, the mods, the + // arguments and the output last. + let host = host.as_ref().map(|_| { + if ticks { + "&mut super::mod_api_host::LevelTicks { level: self, ticks }" + } else { + "self" + } + }); + let passed = host + .into_iter() + .chain(["mod_api::mods()"]) + .chain(args.iter().chain(output.as_ref()).map(|(n, _)| n.as_str())) + .collect::>() + .join(", "); + fills.push(Fill { + native: native.clone(), + call: format!("mod_api::{call}({passed});"), + }); + } + let invoke = call_args.join(", "); + let trait_params = trait_params.join(", "); + let dispatch_params = dispatch_params.join(", "); + let trait_name = if is_render { "RenderHooks" } else { "Hooks" }; + let traits = if is_render { + &mut render_traits + } else { + &mut traits + }; + let dispatch = if is_render { + &mut render_dispatch + } else { + &mut dispatch + }; + traits.push_str(&doc_comments(&method.attrs, name, false)?); + match (contract, out) { + ("cancel", Some(out)) => traits.push_str(&format!( + "///\n/// Return `Flow::Cancel` to take it: this sets `{out}`, and later mods are skipped.\n" + )), + ("replace", Some(out)) => traits.push_str(&format!( + "///\n/// Return `Some` to set `{out}`; the first mod returning `Some` wins.\n" + )), + ("collect", Some(out)) => traits.push_str(&format!( + "///\n/// Every mod gets `{event}.{out}` in order and may add, change or remove {out}.\n" + )), + _ => {} + } + traits.push_str(&format!( + "fn {name}({trait_params}){result} {{ {unused} {default} }}\n" + )); + dispatch.push_str(&format!( + "/// What [`{trait_name}::{name}`] is told. Fields may be added: the struct is `#[non_exhaustive]`.\n#[non_exhaustive]\npub struct {event_type}{generics} {{\n{fields}}}\n" + )); + let build = format!("let mut {event} = {event_type} {{ {} }};", inits.join(", ")); + // A trait mod's call is marked with the mod for a panic to name; render hooks are called bare. + let each = if is_render { + mod_list.clone() + } else { + format!("super::entered({mod_list}, \"{name}\")") + }; + let body = match contract { + "cancel" => format!( + "if *{o} {{ return; }} {build} for {mod_item} in {each} {{ if matches!({mod_item}.{name}({invoke}), Flow::Cancel) {{ *{o} = true; break; }} }} {write_back}", + o = out.unwrap() + ), + "replace" => format!( + "if {o}.is_some() {{ return; }} {build} for {mod_item} in {each} {{ if let Some({replacement}) = {mod_item}.{name}({invoke}) {{ *{o} = Some({replacement}); break; }} }} {write_back}", + o = out.unwrap() + ), + _ => format!( + "{build} for {mod_item} in {each} {{ {mod_item}.{name}({invoke}); }} {write_back}" + ), + }; + dispatch.push_str(&format!( + "pub fn dispatch_{name}({dispatch_params}) {{ {body} }}\n" + )); + } + if hooks.is_empty() { + return Err("no native hooks".into()); + } + let code = format!( + "// @generated by cargo run -p xtask -- mod-api; do not edit.\nuse super::*;\npub trait Hooks: Send + Sync {{\nfn render(&self) -> Option<&dyn RenderHooks> {{ None }}\n{traits}}}\n{dispatch}" + ); + let api_source = fs::read_to_string(root.join("crates/horizon-client/src/mod_api.rs")) + .map_err(|e| e.to_string())?; + let exports = exported_types(&api_source, &code)?; + let mut game = Vec::new(); + game_methods( + &root.join("crates/horizon-client/src/game"), + "horizon_client::game", + &mut game, + )?; + let mut host_traits = String::new(); + let mut host_impl = String::new(); + let mut names = HashSet::new(); + let mut handles = std::collections::BTreeMap::<&str, String>::new(); + let mut helpers = catalog["helpers"] + .as_array() + .ok_or("catalog helpers must be an array")? + .clone(); + let mut fragments = fs::read_dir(root.join("tools/native-hook-fragments")) + .map_err(|e| e.to_string())? + .map(|entry| entry.map(|entry| entry.path()).map_err(|e| e.to_string())) + .collect::, _>>()?; + fragments.sort(); + for path in fragments + .into_iter() + .filter(|path| path.extension().is_some_and(|ext| ext == "json")) + { + let fragment: serde_json::Value = + serde_json::from_str(&fs::read_to_string(&path).map_err(|e| e.to_string())?) + .map_err(|e| format!("{}: {e}", path.display()))?; + if let Some(pending) = fragment.get("helpers") { + helpers.extend( + pending + .as_array() + .ok_or("fragment helpers must be an array")? + .iter() + .cloned(), + ); + } + } + for helper in &helpers { + let name = helper["name"].as_str().ok_or("helper name missing")?; + if !names.insert(name) { + return Err(format!("duplicate helper {name}")); + } + let native = format!("native_{name}"); + let target = helper["target"].as_str().ok_or("helper target missing")?; + let method = one_function(game.iter().map(|(_, method)| method), &native)?; + let (module, _) = game + .iter() + .find(|(_, other)| std::ptr::eq(other, method)) + .expect("the method was found in the list"); + let expected = format!("{module}::Game<'_>::{native}"); + if target != expected { + return Err(format!( + "wrong target for helper {name}: {target}, it is {expected}" + )); + } + let (declaration, implementation) = host_method(method, name, &exports)?; + host_traits.push_str(&declaration); + host_impl.push_str(&implementation); + if let Some(receiver) = helper.get("receiver") { + let receiver = receiver + .as_str() + .ok_or("helper receiver must be a string")?; + let (handle, code) = handle_method(method, name, receiver)?; + handles.entry(handle).or_default().push_str(&code); + } + } + let handles = handles + .iter() + .map(|(handle, methods)| format!("impl {handle} {{\n{methods}}}\n")) + .collect::(); + struct StdTypes { + path_buf: bool, + arc: bool, + } + impl<'ast> Visit<'ast> for StdTypes { + fn visit_type_path(&mut self, ty: &'ast syn::TypePath) { + if ty.path.segments.len() == 1 { + self.path_buf |= ty.path.segments[0].ident == "PathBuf"; + self.arc |= ty.path.segments[0].ident == "Arc"; + } + syn::visit::visit_type_path(self, ty); + } + } + let host_trait: syn::ItemTrait = syn::parse_str(&format!("trait Host {{ {host_traits} }}")) + .map_err(|e| format!("generated Host: {e}"))?; + let mut std_types = StdTypes { + path_buf: false, + arc: false, + }; + std_types.visit_item_trait(&host_trait); + let mut imports = String::new(); + if std_types.path_buf { + imports.push_str("use std::path::PathBuf;\n"); + } + if std_types.arc { + imports.push_str("use std::sync::Arc;\n"); + } + let host = format!( + "// @generated by cargo run -p xtask -- mod-api; do not edit.\nuse super::*;\n{imports}/// Services supplied by the game to trait mods.\n/// Each method's default panics with its name, so a test double implements only what its test calls; the game implements every one.\npub trait Host {{\n{host_traits}}}\n{handles}" + ); + let implementation = format!( + "// @generated by cargo run -p xtask -- mod-api; do not edit.\nuse horizon_client::mod_api::*;\n{imports}impl Host for super::Game<'_> {{\n{host_impl}}}\n" + ); + let render_code = format!( + "// @generated by cargo run -p xtask -- mod-api; do not edit.\nuse super::*;\npub trait RenderHooks: Send + Sync {{\n{render_traits}}}\n{render_dispatch}" + ); + // A new hook's placeholder body becomes its dispatch call. + let filled = if fills.is_empty() { + None + } else if matches!(fill, FillMode::Require) { + let natives = fills + .iter() + .map(|fill| fill.native.as_str()) + .collect::>() + .join(", "); + return Err(format!( + "{natives}: the let _ = (…); placeholder needs its dispatch call; run cargo run -p xtask -- mod-api" + )); + } else if matches!(fill, FillMode::Fill) { + Some(filled_hooks(&source, &fills)?) + } else { + None + }; + for code in [&code, &host, &implementation, &render_code] { + syn::parse_file(code).map_err(|e| format!("generated Rust: {e}"))?; + } + if let Some(filled) = filled { + fs::write( + root.join("crates/horizon-client/src/game/native_hooks.rs"), + filled, + ) + .map_err(|e| e.to_string())?; + } + Ok(Sources { + hooks: code, + host, + implementation, + render: render_code, + }) +} + +#[derive(Clone, Copy)] +enum FillMode { + Fill, + Require, + Ignore, +} + +mod build; +pub use build::{generate, inject}; +#[cfg(test)] +mod injection_tests; + +/// Generate a dispatch fixture without preventing xtask from filling placeholders. +#[doc(hidden)] +pub fn generate_test_hooks(root: &Path, out_dir: &Path) -> Result<(), String> { + build::track(root)?; + let code = sources(root, FillMode::Ignore)?.hooks; + fs::write(out_dir.join("mod_api_generated.rs"), code).map_err(|e| e.to_string()) +} + +#[cfg(test)] +mod tests { + use quote::ToTokens; + use serde_json::Value; + #[test] + fn fragment_hooks_generate_and_duplicates_fail() { + let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."); + let catalog: serde_json::Value = + serde_json::from_str(&std::fs::read_to_string(root.join("native-hooks.json")).unwrap()) + .unwrap(); + let hooks = super::catalog_hooks(&root, &catalog).unwrap(); + let hook = hooks + .iter() + .find(|h| h["name"] == "recipes_received") + .unwrap(); + assert_eq!(hook["mod_api"]["contract"], "listen"); + let generated = super::sources(&root, super::FillMode::Require) + .unwrap() + .hooks; + assert!(generated.contains("fn recipes_received(")); + + let dir = root + .join("target") + .join(format!("mod-api-fragments-{}", std::process::id())); + let fragments = dir.join("tools/native-hook-fragments"); + std::fs::create_dir_all(&fragments).unwrap(); + std::fs::write(fragments.join("empty.json"), r#"{"hooks":[]}"#).unwrap(); + std::fs::write( + fragments.join("duplicate.json"), + r#"{"hooks":[{"name":"same"}]}"#, + ) + .unwrap(); + let catalog = serde_json::json!({"hooks":[{"name":"same"}]}); + assert_eq!( + super::catalog_hooks(&dir, &catalog).unwrap_err(), + "duplicate hook same" + ); + std::fs::remove_file(fragments.join("duplicate.json")).unwrap(); + assert_eq!(super::catalog_hooks(&dir, &catalog).unwrap().len(), 1); + std::fs::remove_dir_all(dir).unwrap(); + } + + #[test] + fn handle_methods_call_the_host_and_borrow_it_as_the_helper_borrows_game() { + let read: syn::ItemFn = syn::parse_quote! { + /// Where it is. + fn native_entity_position(&self, entity: EntityRef, mut scale: f64) -> Option<[f64; 3]> { todo!() } + }; + let (handle, code) = super::handle_method(&read, "entity_position", "entity").unwrap(); + assert_eq!(handle, "EntityRef"); + assert!(code.starts_with("/// Where it is.\n")); + assert!(code.contains("pub fn position (self , host : & dyn Host , scale : f64)")); + assert!(code.contains("host.entity_position(self, scale)")); + let write: syn::ItemFn = syn::parse_quote! { + fn native_level_set(&mut self, level: LevelRef) {} + }; + let (handle, code) = super::handle_method(&write, "level_set", "level").unwrap(); + assert_eq!(handle, "LevelRef"); + assert!(code.contains("host : & mut dyn Host")); + for (method, name, receiver, error) in [ + ( + syn::parse_quote! { fn native_entity_x(&self, entity: EntityRef) {} }, + "entity_x", + "chunk", + "unknown receiver", + ), + ( + syn::parse_quote! { fn native_mob_x(&self, entity: EntityRef) {} }, + "mob_x", + "entity", + "starts with entity_", + ), + ( + syn::parse_quote! { fn native_entity_type(&self, entity: EntityRef) {} }, + "entity_type", + "entity", + "not a method name", + ), + ( + syn::parse_quote! { fn native_entity_x(entity: EntityRef) {} }, + "entity_x", + "entity", + "borrows Game", + ), + ( + syn::parse_quote! { fn native_entity_x(&self, level: LevelRef) {} }, + "entity_x", + "entity", + "takes a EntityRef first", + ), + ] { + let method: syn::ItemFn = method; + assert!( + super::handle_method(&method, name, receiver) + .unwrap_err() + .contains(error), + "{name}" + ); + } + } + + #[test] + fn host_mapping_preserves_access_and_docs_and_rejects_unavailable_types() { + let exports = super::exported_types( + "pub use external::{Visible, Original as Renamed}; pub struct Defined; struct Hidden;", + "pub struct Generated;", + ) + .unwrap(); + for ty in ["Visible", "Renamed", "Defined", "Generated"] { + let method: syn::ItemFn = + syn::parse_str(&format!("fn native_test(&self, value: {ty}) {{ }}")).unwrap(); + assert!(super::host_method(&method, "test", &exports).is_ok()); + } + let foreign: syn::ItemFn = + syn::parse_quote! { fn native_foreign(&self, value: elsewhere::Visible) {} }; + assert!(super::host_method(&foreign, "foreign", &exports).is_err()); + let hidden_trait: syn::ItemFn = + syn::parse_quote! { fn native_hidden(&self, value: &dyn Hidden) {} }; + assert!(super::host_method(&hidden_trait, "hidden", &exports).is_err()); + let read: syn::ItemFn = syn::parse_quote! { + /// Read a value exactly. + /// + /// Keep the blank line too. + fn native_read(&self, pos: [i32; 3]) -> String { todo!() } + }; + let (declaration, implementation) = super::host_method(&read, "read", &exports).unwrap(); + assert!( + declaration.contains("/// Read a value exactly.\n///\n/// Keep the blank line too.") + ); + assert!(declaration.contains("& self")); + assert!(implementation.contains("self.native_read(pos)")); + let dir: syn::ItemFn = syn::parse_quote! { fn native_dir() -> PathBuf { todo!() } }; + let (declaration, implementation) = super::host_method(&dir, "dir", &exports).unwrap(); + assert!(declaration.contains("& self")); + assert!(implementation.contains("Self::native_dir()")); + let owned: syn::ItemFn = syn::parse_quote! { fn native_owned(self) {} }; + assert!(super::host_method(&owned, "owned", &exports).is_err()); + let write: syn::ItemFn = syn::parse_quote! { + fn native_write(&mut self, value: i32) { } + }; + let (declaration, _) = super::host_method(&write, "write", &exports).unwrap(); + assert!(declaration.contains("& mut self")); + let private: syn::ItemFn = syn::parse_quote! { + fn native_private(&self, value: BinOnly) { } + }; + assert!( + super::host_method(&private, "private", &exports) + .unwrap_err() + .contains("BinOnly") + ); + let generic: syn::ItemFn = syn::parse_quote! { + fn native_generic(&self, value: T) { } + }; + assert!( + super::host_method(&generic, "generic", &exports) + .unwrap_err() + .contains("object-safe") + ); + let callback: syn::ItemFn = syn::parse_quote! { + fn native_callback(&self, callback: impl FnOnce()) { } + }; + assert!( + super::host_method(&callback, "callback", &exports) + .unwrap_err() + .contains("object-safe") + ); + let asynchronous: syn::ItemFn = syn::parse_quote! { + async fn native_async(&self) { } + }; + assert!( + super::host_method(&asynchronous, "async", &exports) + .unwrap_err() + .contains("object-safe") + ); + } + + #[test] + fn lookup_rejects_missing_and_duplicate_functions() { + let first: syn::ItemFn = syn::parse_quote! { fn native_example() {} }; + let other: syn::ItemFn = syn::parse_quote! { fn unrelated() {} }; + assert!(super::one_function([&other].into_iter(), "native_example").is_err()); + assert!(super::one_function([&first, &other].into_iter(), "native_example").is_ok()); + assert!(super::one_function([&first, &first].into_iter(), "native_example").is_err()); + } + + #[test] + fn generated_locals_do_not_shadow_hook_arguments() { + let mut used = ["mods", "mods_", "mod_hook", "replacement", "host"] + .into_iter() + .map(str::to_owned) + .collect(); + assert_eq!(super::fresh_name("mods", &mut used), "mods__"); + assert_eq!(super::fresh_name("mod_hook", &mut used), "mod_hook_"); + assert_eq!(super::fresh_name("replacement", &mut used), "replacement_"); + assert_eq!(super::fresh_name("host", &mut used), "host_"); + } + + #[test] + fn level_hosts_match_receiver_mutability() { + assert_eq!( + super::host_for(true, Some(false), false), + Some(("level", "&dyn LevelHost".into())) + ); + assert_eq!( + super::host_for(true, Some(true), false), + Some(("level", "&mut dyn LevelRandomHost".into())) + ); + assert_eq!( + super::host_for(true, Some(false), true), + Some(("level", "&mut dyn LevelTicksHost".into())) + ); + assert_eq!( + super::host_for(true, Some(true), true), + Some(("level", "&mut dyn LevelWriteHost".into())) + ); + assert_eq!(super::host_for(true, None, false), None); + assert_eq!( + super::host_for(false, Some(true), false), + Some(("host", "&mut dyn Host".into())) + ); + } + + #[test] + fn only_a_game_hooks_placeholder_body_is_filled() { + let fill = |body: &str, in_game: bool| { + let method: syn::ItemFn = syn::parse_str(&format!( + "fn native_h(&mut self, player: u128, cancel: &mut bool) {{ {body} }}" + )) + .unwrap(); + super::needs_fill(&method, "dispatch_h", in_game) + }; + assert_eq!(fill("let _ = (player, cancel);", true), Ok(true)); + assert_eq!( + fill( + "horizon_client::mod_api::dispatch_h(self, mods(), player, cancel);", + true + ), + Ok(false) + ); + for (body, in_game) in [ + ("let _ = (player, cancel);", false), + ("let _ = (cancel, player);", true), + ("let _ = player;", true), + ("let _ = (player, cancel); *cancel = true;", true), + ("", true), + ] { + assert!( + fill(body, in_game) + .unwrap_err() + .starts_with("missing dispatch_h call in native_h: "), + "{body}" + ); + } + let one: syn::ItemFn = + syn::parse_quote! { fn native_one(&self, from: &str) { let _ = from; } }; + assert_eq!(super::needs_fill(&one, "dispatch_one", true), Ok(true)); + let source = "impl Game<'_> {\n fn native_other(&self, a: u32) {\n let _ = a;\n }\n fn native_h(&mut self, player: u128, name: &str, cancel: &mut bool) {\n let _ = (player, name, cancel);\n }\n}\n"; + let filled = super::filled_hooks( + source, + &[super::Fill { + native: "native_h".into(), + call: "mod_api::dispatch_h(self, mod_api::mods(), player, name, cancel);".into(), + }], + ) + .unwrap(); + assert_eq!( + String::from_utf8(filled).unwrap(), + "impl Game<'_> {\n fn native_other(&self, a: u32) {\n let _ = a;\n }\n fn native_h(&mut self, player: u128, name: &str, cancel: &mut bool) {\n mod_api::dispatch_h(self, mod_api::mods(), player, name, cancel);\n }\n}\n" + ); + } + + /// `item_damaged` taken back to how a new hook arrives (its body the + /// placeholder) in a copy of the sources: the check reports it, any other + /// body is left alone, a missing `mod_api` entry fails by name, and the + /// generator brings back the shipped file byte for byte with the generated + /// code unchanged. + #[test] + fn new_hook_placeholder_is_reported_then_filled() { + fn copy(from: &std::path::Path, to: &std::path::Path) { + if from.is_dir() { + for entry in std::fs::read_dir(from).unwrap() { + let entry = entry.unwrap(); + copy(&entry.path(), &to.join(entry.file_name())); + } + } else { + std::fs::create_dir_all(to.parent().unwrap()).unwrap(); + std::fs::copy(from, to).unwrap(); + } + } + let real = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../.."); + let root = real + .join("target") + .join(format!("mod-api-fill-{}", std::process::id())); + let catalog: Value = + serde_json::from_str(&std::fs::read_to_string(real.join("native-hooks.json")).unwrap()) + .unwrap(); + let mut inputs: Vec = [ + "native-hooks.json", + "tools/native-hook-fragments", + "crates/horizon-client/src/game", + "crates/horizon-client/src/mod_api.rs", + ] + .map(str::to_owned) + .into(); + for hook in super::catalog_hooks(&real, &catalog).unwrap() { + if let Some((krate, path)) = + super::free_function_source(hook["target"].as_str().unwrap()) + { + let (module, _) = path.rsplit_once("::").unwrap(); + inputs.push(format!( + "crates/{krate}/src/{}.rs", + module.replace("::", "/") + )); + } + } + for input in &inputs { + copy(&real.join(input), &root.join(input)); + } + let hooks = root.join("crates/horizon-client/src/game/native_hooks.rs"); + let shipped = std::fs::read_to_string(&hooks).unwrap(); + let call = "mod_api::dispatch_item_damaged(self, mod_api::mods(), player, name, item, amount, cancel);"; + assert_eq!(shipped.matches(call).count(), 1); + let unfinished = shipped.replace(call, "let _ = (player, name, item, amount);"); + std::fs::write(&hooks, &unfinished).unwrap(); + assert!( + super::fill_hooks(&root) + .unwrap_err() + .starts_with("missing dispatch_item_damaged call in native_item_damaged: ") + ); + assert_eq!(std::fs::read_to_string(&hooks).unwrap(), unfinished); + let placeholder = shipped.replace(call, "let _ = (player, name, item, amount, cancel);"); + std::fs::write(&hooks, &placeholder).unwrap(); + assert_eq!( + super::sources(&root, super::FillMode::Require).unwrap_err(), + "native_item_damaged: the let _ = (…); placeholder needs its dispatch call; run cargo run -p xtask -- mod-api" + ); + assert_eq!(std::fs::read_to_string(&hooks).unwrap(), placeholder); + // A stamped hook lives in the catalog, not in a fragment. + let catalog_path = root.join("native-hooks.json"); + let shipped_catalog = std::fs::read_to_string(&catalog_path).unwrap(); + let mut json: Value = serde_json::from_str(&shipped_catalog).unwrap(); + let hook = json["hooks"] + .as_array_mut() + .unwrap() + .iter_mut() + .find(|hook| hook["name"] == "item_damaged") + .unwrap(); + hook.as_object_mut().unwrap().remove("mod_api").unwrap(); + std::fs::write(&catalog_path, json.to_string()).unwrap(); + assert_eq!( + super::fill_hooks(&root).unwrap_err(), + "hook item_damaged has no mod_api entry: add one" + ); + std::fs::write(&catalog_path, shipped_catalog).unwrap(); + super::fill_hooks(&root).unwrap(); + // The generator writes LF; a Windows checkout (core.autocrlf) has CRLF. + let lf = |s: &str| s.replace("\r\n", "\n"); + assert_eq!(lf(&std::fs::read_to_string(&hooks).unwrap()), lf(&shipped)); + assert_eq!( + super::sources(&root, super::FillMode::Require) + .unwrap() + .hooks, + super::sources(&real, super::FillMode::Require) + .unwrap() + .hooks, + ); + super::sources(&root, super::FillMode::Require).unwrap(); + std::fs::remove_dir_all(root).unwrap(); + } + + #[test] + fn event_fields_name_their_lifetimes() { + for (ty, field, scalar, a, b) in [ + ( + "& mut WorldDrawn < '_ >", + "& 'a mut WorldDrawn < 'b >", + false, + true, + true, + ), + ("& str", "& 'a str", false, true, false), + ( + "& mut Vec < (& 'static str , u32) >", + "& 'a mut Vec < (& 'static str , u32) >", + false, + true, + false, + ), + ( + "Option < & ItemStack >", + "Option < & 'a ItemStack >", + false, + true, + false, + ), + ("u32", "u32", false, false, false), + ("& mut f32", "f32", true, false, false), + ( + "& mut Option < bool >", + "& 'a mut Option < bool >", + false, + true, + false, + ), + ] { + let mut lifetimes = super::EventLifetimes::default(); + let (field_type, is_scalar) = lifetimes.field(&syn::parse_str(ty).unwrap()); + assert_eq!(field_type.to_token_stream().to_string(), field); + assert_eq!((is_scalar, lifetimes.a, lifetimes.b), (scalar, a, b)); + } + } + + #[test] + fn library_crate_targets_resolve_to_their_source() { + for target in [ + "horizon_client_lib::menu::native_menu_slot_rules", + "horizon_client::menu::native_menu_slot_rules", + ] { + assert_eq!( + super::free_function_source(target), + Some(("horizon-client", "menu::native_menu_slot_rules".to_owned())) + ); + } + assert_eq!( + super::free_function_source("horizon_client_lib::crafting::native_ingredient"), + Some(("horizon-client", "crafting::native_ingredient".to_owned())) + ); + assert_eq!( + super::free_function_source( + "horizon_client::game::native_hooks::Game<'_>::native_zoom" + ), + None + ); + } + + #[test] + fn contracted_hook_requires_dispatch_call() { + let missing: syn::ImplItemFn = syn::parse_quote! { fn native_zoom(&self) { unrelated(); } }; + let present: syn::ImplItemFn = syn::parse_quote! { fn native_zoom(&self) { horizon_client::mod_api::dispatch_zoom(); } }; + assert!(!super::has_call(&missing.block, "dispatch_zoom")); + assert!(super::has_call(&present.block, "dispatch_zoom")); + } +} diff --git a/docs/native-mods.md b/docs/native-mods.md index d2f49f9aea..d1bde725ea 100644 --- a/docs/native-mods.md +++ b/docs/native-mods.md @@ -2979,14 +2979,18 @@ assert!(claimed); A new hook needs its fragment in `tools/native-hook-fragments/` with its `mod_api` entry (for example `{"contract": "cancel", "out": "cancel"}`), its `native_*` function with the placeholder body `let _ = (every, parameter);` (all its parameters, in order), and a run of `cargo run -p xtask -- mod-api`. -The generator adds the hook's trait method and replaces the placeholder with the `dispatch_` call. -Every hook needs a `mod_api` entry: the check fails naming any hook without one. A body that is neither the +That command only replaces the placeholder in `native_hooks.rs` with the `dispatch_` call; commit +that source change and the fragment. The `horizon-mod-api-gen` build dependency generates the hook traits, +`Host` methods and dispatch code into Cargo's `OUT_DIR` when the game and renderer build, including release +builds. These generated Rust files are not kept in the source tree or committed, and no manual regeneration +is needed after a merge. Every hook needs a `mod_api` entry: the build fails naming any hook without one. +A body that is neither the placeholder nor already calls the dispatch is an error, and the generator never changes it. Hooks that are free functions outside `Game` are wired by hand: those crates can't see the mod list, so each calls a callback the executable installs once, such as `NativeLoadHooks` in `horizon-world` and `NativeClientHooks` in the -client library (`menu_slot_rules`, `living_breathe`, `ingredient`). `cargo run -p xtask -- mod-api --check` -and the `generated_output_is_current` test report a placeholder still to fill instead of filling it. Mixin -injections go at the head of the body, before the dispatch, so they still run first. +client library (`menu_slot_rules`, `living_breathe`, `ingredient`). A build reports a placeholder still to +fill instead of changing hand-written source; run `cargo run -p xtask -- mod-api` to fill it. Mixin injections +go at the head of the body, before the dispatch, so they still run first. ### Server attachments diff --git a/launcher/Cargo.lock b/launcher/Cargo.lock index 4611916692..8ef66673a6 100644 --- a/launcher/Cargo.lock +++ b/launcher/Cargo.lock @@ -2684,6 +2684,7 @@ dependencies = [ "horizon-core", "horizon-friends", "horizon-mcp", + "horizon-mod-api-gen", "horizon-mods", "horizon-port-pack", "horizon-prepare", @@ -2727,6 +2728,16 @@ dependencies = [ "serde_json", ] +[[package]] +name = "horizon-mod-api-gen" +version = "0.1.0" +dependencies = [ + "quote", + "serde", + "serde_json", + "syn 2.0.119", +] + [[package]] name = "horizon-mods" version = "0.1.0" diff --git a/launcher/Cargo.toml b/launcher/Cargo.toml index 227b93e115..7f992dd2ee 100644 --- a/launcher/Cargo.toml +++ b/launcher/Cargo.toml @@ -8,6 +8,8 @@ description = "Downloads, prepares and starts Horizon builds" [workspace] [dependencies] +# The catalog and mod-owned trait injections share their generation rules. +horizon-mod-api-gen = { path = "../crates/horizon-mod-api-gen" } # GPUI Fast (longbridge/gpui-fast): Zed's GPUI with retained views and cached scroll layers, under # its own name. wgpu renders on Linux, not blade (#3812). gpui = { package = "gpui-fast", version = "0.1.13" } diff --git a/launcher/src/native_mods.rs b/launcher/src/native_mods.rs index b497bc6e3e..03aaa54cf8 100644 --- a/launcher/src/native_mods.rs +++ b/launcher/src/native_mods.rs @@ -1525,6 +1525,13 @@ fn cargo(rust: &Toolchain, dir: &Path, subcommand: &str) -> Command { cmd.arg("--config").arg(config); } cmd.arg("--manifest-path").arg(dir.join("Cargo.toml")); + // Only this stage's declarations apply, never a path inherited from the launcher's shell. + let injections = dir.join(inject::INPUT); + if injections.is_file() { + cmd.env(inject::ENV, injections); + } else { + cmd.env_remove(inject::ENV); + } cmd } @@ -3218,6 +3225,42 @@ mod tests { ); } + #[test] + fn staged_cargo_uses_only_its_own_injections_including_cached_output() { + let d = dir("injection-env"); + let fresh = d.join("fresh"); + let snapshot = d.join("snapshot"); + let stage = d.join("stage"); + fs::create_dir_all(fresh.join("crates/horizon-client")).unwrap(); + fs::write( + fresh.join("crates/horizon-client/Cargo.toml"), + "[package]\nname = 'horizon-client'\nversion = '0.1.0'\n[build-dependencies]\nhorizon-mod-api-gen = { path = '../horizon-mod-api-gen' }\n", + ) + .unwrap(); + let rust = Toolchain { + dir: d.join("rust"), + }; + let env = |dir: &Path| { + cargo(&rust, dir, "build") + .get_envs() + .find(|(key, _)| *key == inject::ENV) + .map(|(_, value)| value.map(std::ffi::OsStr::to_owned)) + }; + assert_eq!(env(&stage), Some(None)); + inject::stage(&fresh, &[]).unwrap(); + sync(&fresh, &snapshot).unwrap(); + sync(&snapshot, &stage).unwrap(); + assert_eq!(fs::read(stage.join(inject::INPUT)).unwrap(), b"[]"); + assert_eq!( + env(&stage), + Some(Some(stage.join(inject::INPUT).into_os_string())) + ); + fs::remove_file(fresh.join(inject::INPUT)).unwrap(); + sync(&fresh, &stage).unwrap(); + assert_eq!(env(&stage), Some(None)); + fs::remove_dir_all(d).unwrap(); + } + /// The server build (docs/server.md): compiled headless off the same source, with its own /// key, and the game build's flags unchanged. #[test] diff --git a/launcher/src/native_mods/api.rs b/launcher/src/native_mods/api.rs index b890b50d92..4e44e4653c 100644 --- a/launcher/src/native_mods/api.rs +++ b/launcher/src/native_mods/api.rs @@ -12,7 +12,7 @@ pub(super) fn valid_id(id: &str) -> bool { .all(|b| b.is_ascii_alphanumeric() || b == b'_' || b == b'-') } -fn read(path: &Path) -> Result { +pub(super) fn read(path: &Path) -> Result { let text = fs::read_to_string(path).map_err(|e| format!("{}: {e}", path.display()))?; text.parse().map_err(|e| format!("{}: {e}", path.display())) } @@ -242,6 +242,12 @@ pub(super) fn stage( ("build".into(), "../horizon-client/build.rs".into()), ])), ); + // This sibling package uses the client's build.rs, so its build dependencies apply too; + // relative paths still point to the same crates from crates/native-game. + let client = read(&stage.join("crates/horizon-client/Cargo.toml"))?; + if let Some(dependencies) = client.get("build-dependencies") { + manifest.insert("build-dependencies".into(), dependencies.clone()); + } let windows = toml::Table::from_iter([( "build-dependencies".into(), toml::Value::Table(toml::Table::from_iter([( @@ -372,8 +378,13 @@ mod tests { let dir = fixture("[mod]\nid = 'example'\nversion = '1'\n[api]\npath = 'engine'\n"); let bundle = super::super::bundle(&dir, None).unwrap(); let stage_dir = dir.join("workspace"); - fs::create_dir_all(&stage_dir).unwrap(); + fs::create_dir_all(stage_dir.join("crates/horizon-client")).unwrap(); fs::write(stage_dir.join("Cargo.toml"), "[workspace]\nmembers = []\n").unwrap(); + fs::write( + stage_dir.join("crates/horizon-client/Cargo.toml"), + "[package]\nname = 'horizon-client'\nversion = '0.1.0'\n", + ) + .unwrap(); let resolution = super::super::dependencies::Resolution { order: vec![0] }; assert!(stage(&stage_dir, &[bundle], &[vec![]], &resolution).unwrap()); let root = read(&stage_dir.join("Cargo.toml")).unwrap(); @@ -431,9 +442,15 @@ mod tests { "pub fn name_mods(ids: &'static [&'static str]) {}\n", ) .unwrap(); + fs::write(stage_dir.join("crates/horizon-client/Cargo.toml"), "[package]\nname = 'horizon-client'\nversion = '0.1.0'\n[build-dependencies]\nhorizon-mod-api-gen = { path = '../horizon-mod-api-gen' }\n").unwrap(); let bundle = super::super::bundle(&dir, None).unwrap(); stage(&stage_dir, &[bundle], &[vec![]], &resolution).unwrap(); assert!(main().contains(r#"name_mods(&["example"]);"#)); + let game = read(&stage_dir.join(GAME).join("Cargo.toml")).unwrap(); + assert_eq!( + game["build-dependencies"]["horizon-mod-api-gen"]["path"].as_str(), + Some("../horizon-mod-api-gen") + ); fs::remove_dir_all(dir).unwrap(); } diff --git a/launcher/src/native_mods/inject.rs b/launcher/src/native_mods/inject.rs index 698032dfb5..cfd28b4c47 100644 --- a/launcher/src/native_mods/inject.rs +++ b/launcher/src/native_mods/inject.rs @@ -31,7 +31,7 @@ enum Contract { /// Turns a binding's type into its event field's: a `&mut` primitive scalar becomes the value, /// written back after the call; every elided reference lifetime becomes `'a`, or `'b` inside a /// `&mut`, which can't shorten it. A copy of xtask's rule for catalog hooks -/// (`xtask/src/mod_api.rs`, `EventLifetimes`); both test the same types. +/// (`horizon-mod-api-gen/src/lib.rs`, `EventLifetimes`); both test the same types. #[derive(Default)] struct EventLifetimes { a: bool, @@ -384,51 +384,42 @@ impl Injection { } /// Add declarations to the existing generated trait, not its dispatch implementations. -pub(super) fn stage(stage: &Path, injections: &[Injection]) -> Result<(), String> { - if injections.is_empty() { - return Ok(()); - } - let path = stage.join("crates/horizon-client/src/mod_api/generated.rs"); - let source = fs::read_to_string(&path).map_err(|e| format!("{}: {e}", path.display()))?; - let file = syn::parse_file(&source).map_err(|e| format!("{}: {e}", path.display()))?; - let hooks = file - .items - .iter() - .find_map(|i| match i { - syn::Item::Trait(t) if t.ident == "Hooks" => Some(t), - _ => None, +impl Injection { + fn input(&self) -> serde_json::Value { + serde_json::json!({ + "name": self.name.to_string(), + "event": self.event.to_string(), + "declaration": self.declaration(), + "event_declaration": self.event(), }) - .ok_or("generated Hooks trait missing")?; - for injection in injections { - if hooks - .items - .iter() - .any(|i| matches!(i, syn::TraitItem::Fn(f) if f.sig.ident == injection.name)) - { - return Err(format!("trait method {} already exists", injection.name)); - } - if file - .items - .iter() - .any(|i| matches!(i, syn::Item::Struct(s) if s.ident == injection.event)) - { - return Err(format!("event {} already exists", injection.event)); - } } - let marker = "pub trait Hooks: Send + Sync {\n"; - if source.matches(marker).count() != 1 { - return Err("generated Hooks marker missing or ambiguous".into()); +} + +pub(super) const INPUT: &str = ".horizon-mod-api-injections.json"; +pub(super) const ENV: &str = "HORIZON_MOD_API_INJECTIONS"; + +/// New sources consume declarations in their build script; released sources still commit the +/// generated trait. Both paths use the same generation and collision checks, without rustfmt. +pub(super) fn stage(stage: &Path, injections: &[Injection]) -> Result<(), String> { + let client = api::read(&stage.join("crates/horizon-client/Cargo.toml"))?; + let build_time = client + .get("build-dependencies") + .and_then(toml::Value::as_table) + .is_some_and(|deps| deps.contains_key("horizon-mod-api-gen")); + let declarations = injections.iter().map(Injection::input).collect::>(); + let input = serde_json::to_string(&declarations).map_err(|e| e.to_string())?; + if build_time { + // This file travels with cached mixin output; generated Rust lives in Cargo's OUT_DIR. + let path = stage.join(INPUT); + fs::write(&path, input).map_err(|e| format!("{}: {e}", path.display())) + } else if injections.is_empty() { + Ok(()) + } else { + let path = stage.join("crates/horizon-client/src/mod_api/generated.rs"); + let source = fs::read_to_string(&path).map_err(|e| format!("{}: {e}", path.display()))?; + let output = horizon_mod_api_gen::inject(&source, &input)?; + fs::write(&path, output).map_err(|e| format!("{}: {e}", path.display())) } - let declarations = injections - .iter() - .map(Injection::declaration) - .collect::(); - let events = injections.iter().map(Injection::event).collect::(); - fs::write( - path, - source.replacen(marker, &format!("{marker}{declarations}"), 1) + &events, - ) - .map_err(|e| e.to_string()) } #[cfg(test)] @@ -455,6 +446,42 @@ mod tests { (root, bundle) } + /// Emulate a released source tree or the current build script, including its inject() call. + fn staged_hooks( + stage_dir: &Path, + injections: &[Injection], + build_time: bool, + existing: &str, + ) -> Result { + let client = stage_dir.join("crates/horizon-client"); + fs::create_dir_all(client.join("src/mod_api")).unwrap(); + let dependencies = if build_time { + "[build-dependencies]\nhorizon-mod-api-gen = { path = '../horizon-mod-api-gen' }\n" + } else { + "" + }; + fs::write( + client.join("Cargo.toml"), + format!("[package]\nname = 'horizon-client'\nversion = '0.1.0'\n{dependencies}"), + ) + .unwrap(); + let generated = client.join("src/mod_api/generated.rs"); + if !build_time { + fs::write(&generated, existing).unwrap(); + } + stage(stage_dir, injections)?; + if build_time { + assert!(!generated.exists()); + horizon_mod_api_gen::inject( + existing, + &fs::read_to_string(stage_dir.join(INPUT)).unwrap(), + ) + } else { + assert!(!stage_dir.join(INPUT).exists()); + Ok(fs::read_to_string(generated).unwrap()) + } + } + fn compile_and_run(root: &Path, program: &str) { let source = root.join("proof.rs"); let binary = root.join(format!("proof-bin{}", std::env::consts::EXE_SUFFIX)); @@ -486,18 +513,18 @@ mod tests { let manifest = fs::read_to_string(root.join(MANIFEST)).unwrap(); assert!(!manifest.contains("trait_method")); assert!(!manifest.contains("[api]")); - let stage_dir = root.join("stage/crates/horizon-client/src/mod_api"); - fs::create_dir_all(&stage_dir).unwrap(); - fs::write( - stage_dir.join("generated.rs"), - "pub trait Hooks: Send + Sync {\n}\n", - ) - .unwrap(); - stage(&root.join("stage"), &injections).unwrap(); - let output = fs::read_to_string(stage_dir.join("generated.rs")).unwrap(); - assert!(syn::parse_file(&output).is_ok()); - let program = format!( - r#" + for build_time in [false, true] { + let stage_dir = root.join(if build_time { "build-time" } else { "legacy" }); + let output = staged_hooks( + &stage_dir, + &injections, + build_time, + "pub trait Hooks: Send + Sync {\n}\n", + ) + .unwrap(); + assert!(syn::parse_file(&output).is_ok()); + let program = format!( + r#" mod mod_api {{ use std::sync::OnceLock; #[derive(PartialEq)] pub enum Flow {{ Continue, Cancel }} @@ -525,8 +552,9 @@ mod tests { assert_eq!(value, 42); }} "# - ); - compile_and_run(&root, &program); + ); + compile_and_run(&root, &program); + } fs::remove_dir_all(root).unwrap(); } @@ -535,17 +563,17 @@ mod tests { let (root, bundle) = fixture("value", "[[injection]]\nid = 'hook'\ntarget = 'horizon_client::game::tick'\nkind = 'RETURN'\ntrait_method = 'total'\nbindings = [{ name = 'total', ty = 'i32', value = '$value' }, { name = 'calls', ty = '&mut u32', value = '&mut *calls' }]\n"); let injections = prepare(&[bundle], &root).unwrap(); let helper = fs::read_to_string(root.join("horizon-trait-injection-0.rs")).unwrap(); - let stage_dir = root.join("stage/crates/horizon-client/src/mod_api"); - fs::create_dir_all(&stage_dir).unwrap(); - fs::write( - stage_dir.join("generated.rs"), - "pub trait Hooks: Send + Sync {\n}\n", - ) - .unwrap(); - stage(&root.join("stage"), &injections).unwrap(); - let output = fs::read_to_string(stage_dir.join("generated.rs")).unwrap(); - let program = format!( - r#" + for build_time in [false, true] { + let stage_dir = root.join(if build_time { "build-time" } else { "legacy" }); + let output = staged_hooks( + &stage_dir, + &injections, + build_time, + "pub trait Hooks: Send + Sync {\n}\n", + ) + .unwrap(); + let program = format!( + r#" mod mod_api {{ use std::sync::atomic::{{AtomicBool, Ordering}}; use std::sync::OnceLock; @@ -580,52 +608,45 @@ mod tests { assert_eq!(calls, 1); }} "# - ); - compile_and_run(&root, &program); + ); + compile_and_run(&root, &program); + } fs::remove_dir_all(root).unwrap(); } #[test] - fn event_fields_name_their_lifetimes() { - use quote::ToTokens; - for (ty, field, scalar, a, b) in [ - ( - "& mut WorldDrawn < '_ >", - "& 'a mut WorldDrawn < 'b >", - false, - true, - true, - ), - ("& str", "& 'a str", false, true, false), - ( - "& mut Vec < (& 'static str , u32) >", - "& 'a mut Vec < (& 'static str , u32) >", - false, - true, - false, - ), - ( - "Option < & ItemStack >", - "Option < & 'a ItemStack >", - false, - true, - false, - ), - ("u32", "u32", false, false, false), - ("& mut f32", "f32", true, false, false), - ( - "& mut Option < bool >", - "& 'a mut Option < bool >", - false, - true, - false, - ), - ] { - let mut lifetimes = EventLifetimes::default(); - let (field_type, is_scalar) = lifetimes.field(&syn::parse_str(ty).unwrap()); - assert_eq!(field_type.to_token_stream().to_string(), field); - assert_eq!((is_scalar, lifetimes.a, lifetimes.b), (scalar, a, b)); + fn staged_input_checks_conflicts_and_clears_removed_injections() { + let (root, bundle) = fixture("conflicts", "[[injection]]\nid = 'hook'\ntarget = 'horizon_client::game::tick'\nkind = 'HEAD'\ntrait_method = 'tick'\n"); + let injections = prepare(&[bundle], &root).unwrap(); + for build_time in [false, true] { + let stage_dir = root.join(if build_time { "build-time" } else { "legacy" }); + for (existing, expected) in [ + ( + "pub trait Hooks: Send + Sync { fn inject_example_tick(&self) {} }", + "duplicate generated trait method", + ), + ( + "pub trait Hooks: Send + Sync {} pub struct InjectExampleTickEvent;", + "duplicate generated event", + ), + ] { + let error = + staged_hooks(&stage_dir, &injections, build_time, existing).unwrap_err(); + assert!(error.contains(expected), "{error}"); + } + let output = staged_hooks( + &stage_dir, + &[], + build_time, + "pub trait Hooks: Send + Sync {}", + ) + .unwrap(); + assert!(!output.contains("inject_example_tick")); + if build_time { + assert_eq!(fs::read_to_string(stage_dir.join(INPUT)).unwrap(), "[]"); + } } + fs::remove_dir_all(root).unwrap(); } #[test] diff --git a/launcher/src/native_mods/inject_cache.rs b/launcher/src/native_mods/inject_cache.rs index 195192cd9e..32b0177e5a 100644 --- a/launcher/src/native_mods/inject_cache.rs +++ b/launcher/src/native_mods/inject_cache.rs @@ -34,7 +34,7 @@ pub(super) fn key( let mut hash = Sha256::new(); for part in [ // Bump when launcher generation or validation changes the snapshot's contents. - "trait-injection-v2", + "trait-injection-v3", revision, mixins, // The Cargo profile: `Performance` and `Dev` stage the same source. diff --git a/tests/test_pr_ready.py b/tests/test_pr_ready.py index d3d974a6a0..6c5f92fec3 100644 --- a/tests/test_pr_ready.py +++ b/tests/test_pr_ready.py @@ -107,14 +107,20 @@ class PrReadyTests(unittest.TestCase): " --features horizon-mods/index") self.assertEqual(pr_ready.mod_features(None), MODS) - def test_mod_api_check_tracks_hook_inputs(self): - for path in ("xtask/src/mod_api.rs", "native-hooks.json", - "crates/horizon-client/src/game/native_hooks.rs", "README.md"): + def test_mod_api_data_inputs_build_and_test_the_consuming_crates(self): + packages = {**PACKAGES, "horizon-client-render": "crates/horizon-client-render", + "horizon-mod-api-gen": "crates/horizon-mod-api-gen"} + for path in ("native-hooks.json", "tools/native-hook-fragments/player.json"): with self.subTest(path=path), mock.patch.object(pr_ready, "affected_here", return_value=[]): - steps = pr_ready.plan(args(quick=True), "origin/main", {path}, PACKAGES, LIBRARIES, DEPENDENCIES) - step = {step.name: step for step in steps}["mod API up to date"] - self.assertEqual(step.command, "cargo test --locked -p xtask mod_api") - self.assertEqual(step.skip, "native hooks untouched" if path == "README.md" else None) + steps = pr_ready.plan(args(quick=True), "origin/main", {path}, packages, + LIBRARIES | {"horizon-client-render", "horizon-mod-api-gen"}, DEPENDENCIES) + by_name = {step.name: step for step in steps} + self.assertIsNone(by_name["clippy"].skip) + self.assertIsNone(by_name["rustfmt"].skip) + tests = next(step for step in steps if step.name.startswith("tests:")) + self.assertIsNone(tests.skip) + self.assertEqual(tests.command, "cargo test --locked -p horizon-client -p horizon-client-render " + "-p horizon-mod-api-gen --tests") @unittest.skipUnless(POSIX_BASH, "needs a bash that is not WSL's") def test_the_launcher_fallback_asks_for_a_feature_the_launcher_has(self): diff --git a/tests/test_stamp_native_hooks.py b/tests/test_stamp_native_hooks.py index 30bd070800..6e202b582b 100644 --- a/tests/test_stamp_native_hooks.py +++ b/tests/test_stamp_native_hooks.py @@ -102,16 +102,35 @@ class StampTests(unittest.TestCase): self.assertEqual(hooks, [{"name": "served", "version": 1, "target": "target::served", "side": "server", "cancellable": True, "mod_api": contract}]) - def test_markers_are_stamped_in_the_mod_api_files(self): + def test_markers_are_stamped_in_hand_written_api_types(self): self.git("add", ".") self.git("commit", "-qm", "base") self.fragment("drops") - for path in stamp.MOD_API: - (self.root / path).parent.mkdir(parents=True, exist_ok=True) - (self.root / path).write_text("/// Since API @API:drops@.\n") + path = self.root / stamp.MOD_API + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("/// Since API @API:drops@.\n") self.assertFalse(stamp.stamp(False)) - for path in stamp.MOD_API: - self.assertEqual((self.root / path).read_text(), "/// Since API 8.\n") + self.assertEqual(path.read_text(), "/// Since API 8.\n") + + def test_stamping_leaves_stale_generated_copies_alone(self): + # Old checkouts may still have these files; only the generator's inputs are stamped now. + generated = ( + "crates/horizon-client/src/mod_api/generated.rs", + "crates/horizon-client/src/mod_api/generated_host.rs", + "crates/horizon-client/src/game/mod_api_host_generated.rs", + "crates/horizon-client-render/src/gfx/native_render/generated.rs", + ) + self.git("add", ".") + self.git("commit", "-qm", "base") + self.fragment("drops") + for name in generated: + path = self.root / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("/// Since API @API:drops@.\n") + self.assertFalse(stamp.stamp(False)) + self.assertIn("NATIVE_HOOKS_API: u32 = 8;", (self.root / stamp.SOURCE).read_text()) + for name in generated: + self.assertEqual((self.root / name).read_text(), "/// Since API @API:drops@.\n") def test_duplicate_fails_before_writes(self): self.git("add", ".") @@ -129,7 +148,7 @@ class StampTests(unittest.TestCase): repo = self.original fragments = [p.relative_to(repo) for p in (repo / stamp.FRAGMENTS).glob("*.json")] comments = {Path(c) for p in fragments for c in json.loads((repo / p).read_text()).get("comments", [])} - stamped = [stamp.SOURCE, stamp.DOCS, *stamp.MOD_API, *comments] + stamped = [stamp.SOURCE, stamp.DOCS, stamp.MOD_API, *comments] for path in [stamp.CATALOG, *stamped, *fragments]: if (repo / path).exists(): (self.root / path).parent.mkdir(parents=True, exist_ok=True) diff --git a/tools/pr_ready.py b/tools/pr_ready.py index 941b4c51d2..496f0aba48 100755 --- a/tools/pr_ready.py +++ b/tools/pr_ready.py @@ -75,14 +75,8 @@ in_shell() { if [ "$(uname)" = Linux ] && ! pkg-config --exists xkbcommon 2>/dev nix-shell shell.nix --run "$1"; else bash -c "$1"; fi; }""" -# What `cargo run -p xtask -- mod-api` reads: the catalog, the hook points and helpers (every file of -# the game), the mod API's own types, and the free hook points of other crates. -MOD_API_INPUTS = ( - "xtask/", "native-hooks.json", "crates/horizon-client/src/game/", - "crates/horizon-client/src/mod_api", "crates/horizon-world/src/loot.rs", - "crates/horizon-world/src/item_defaults.rs", - "crates/horizon-client-render/src/gfx/native_render", -) +# Build-script inputs outside the crates: catalog-only edits still need to build and test their consumers. +MOD_API_INPUTS = ("native-hooks.json", "tools/native-hook-fragments/") def is_doc(path): return path.endswith(".md") @@ -90,7 +84,8 @@ def is_doc(path): def workspace_rust(path): """Whether a change to path is built by the main Cargo workspace.""" - return not is_doc(path) and (path.startswith(("crates/", "xtask/", ".cargo/")) or path in ROOT_RUST) + return not is_doc(path) and (path.startswith(("crates/", "xtask/", ".cargo/", *MOD_API_INPUTS)) + or path in ROOT_RUST) def launcher_rust(path): @@ -106,6 +101,9 @@ def touched_crates(paths, packages): for path in paths: if is_doc(path): continue + if path.startswith(MOD_API_INPUTS): + crates.update({"horizon-client", "horizon-client-render", "horizon-mod-api-gen"}) + continue if "horizon-client-lib" in packages and path.startswith("crates/horizon-client/"): relative = path.removeprefix("crates/horizon-client/") gameplay = relative in {"Cargo.toml", "build.rs", "windows-segment-heap.manifest", @@ -383,9 +381,6 @@ def plan(args, base, changed, crates_of, libraries, dependencies): Step("content root tests", CONTENT_ROOT_TESTS, summary=tests_passed, skip=None if crates is None or any(p.startswith(CONTENT_ROOT) for p in changed) else "content root untouched"), - Step("mod API up to date", "cargo test --locked -p xtask mod_api", - skip=None if any(p.startswith(MOD_API_INPUTS) for p in changed) - else "native hooks untouched"), Step("tokens.css up to date", "cargo test --locked -p horizon-theme --lib tokens_css_is_up_to_date", skip=None if any(p.startswith(("crates/horizon-theme/", "xtask/")) for p in changed) else "theme untouched"), @@ -483,7 +478,7 @@ def run_windows(steps, verbose): command = shlex.split(CI_CLIPPY) # the server build's clippy runs in CI (Linux) elif step.name.startswith("tests"): command = shlex.split(step.command) - elif step.name in ("tokens.css up to date", "mod API up to date"): + elif step.name == "tokens.css up to date": command = shlex.split(step.command) elif step.name == "launcher rustfmt": command = ["cargo", "fmt", "--check"] diff --git a/tools/stamp_native_hooks.py b/tools/stamp_native_hooks.py index 2d40d12f9a..ddbac2dd01 100644 --- a/tools/stamp_native_hooks.py +++ b/tools/stamp_native_hooks.py @@ -12,15 +12,8 @@ FRAGMENTS = Path("tools/native-hook-fragments") SOURCE = Path("crates/horizon-client/src/game/native_hooks.rs") DOCS = Path("docs/native-mods.md") CATALOG = Path("native-hooks.json") -# The mod API's types and the files `cargo run -p xtask -- mod-api` generates: they copy hook and -# helper comments, markers included, so every marker is stamped in them too and `--check` holds. -MOD_API = ( - Path("crates/horizon-client/src/mod_api.rs"), - Path("crates/horizon-client/src/mod_api/generated.rs"), - Path("crates/horizon-client/src/mod_api/generated_host.rs"), - Path("crates/horizon-client/src/game/mod_api_host_generated.rs"), - Path("crates/horizon-client-render/src/gfx/native_render/generated.rs"), -) +# Hand-written API types can carry markers too; generated copies are built from stamped sources. +MOD_API = Path("crates/horizon-client/src/mod_api.rs") def stamp(check): @@ -52,8 +45,7 @@ def stamp(check): names = {hook["name"] for hook in catalog["hooks"]} helper_names = {helper["name"] for helper in catalog.get("helpers", [])} extra = {} - # The catalog takes new entries in file name order, the order `cargo run -p xtask -- mod-api` - # reads pending fragments in, so stamping leaves the generated mod API as it was. + # Match the build-time generator's fragment order so stamping preserves the generated API. for path in sorted(ordered): fragment = json.loads(path.read_text()) for hook in fragment["hooks"]: @@ -87,7 +79,7 @@ def stamp(check): docs = docs.replace(docs_marker, f"\nAPI {version} adds {doc_summary}\n" + docs_marker, 1) for filename in fragment.get("comments", []): file = Path(filename) - if file.is_absolute() or ".." in file.parts or file in (SOURCE, DOCS, CATALOG, *MOD_API): + if file.is_absolute() or ".." in file.parts or file in (SOURCE, DOCS, CATALOG, MOD_API): raise ValueError(f"invalid comment path: {filename}") text = extra.get(file, (ROOT / file).read_text()) if marker not in text: @@ -96,10 +88,9 @@ def stamp(check): # Most hook comments live in the source itself, and some hook lists in the docs. source = source.replace(marker, str(version)) docs = docs.replace(marker, str(version)) - for file in MOD_API: - if (ROOT / file).exists(): - text = extra.get(file, (ROOT / file).read_text()) - extra[file] = text.replace(marker, str(version)) + if (ROOT / MOD_API).exists(): + text = extra.get(MOD_API, (ROOT / MOD_API).read_text()) + extra[MOD_API] = text.replace(marker, str(version)) source = re.sub( r"^(pub\(crate\) const NATIVE_HOOKS_API: u32 = )\d+;$", diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 16d2c00ea1..1f6fe25ccf 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -7,10 +7,12 @@ publish = false description = "Repository tasks: cargo run -p xtask -- package BINARY OUTDIR | tokens" [dependencies] -syn = { version = "2", features = ["full", "visit", "visit-mut"] } -quote = "1" +horizon-mod-api-gen = { path = "../crates/horizon-mod-api-gen" } serde_json = "1" horizon-theme = { path = "../crates/horizon-theme" } +[build-dependencies] +horizon-mod-api-gen = { path = "../crates/horizon-mod-api-gen" } + [lints] workspace = true diff --git a/xtask/build.rs b/xtask/build.rs new file mode 100644 index 0000000000..d50c6d196e --- /dev/null +++ b/xtask/build.rs @@ -0,0 +1,6 @@ +fn main() { + let root = std::path::PathBuf::from(std::env::var_os("CARGO_MANIFEST_DIR").unwrap()).join(".."); + let out_dir = std::path::PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + horizon_mod_api_gen::generate_test_hooks(&root, &out_dir) + .expect("generating dispatch test fixture"); +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 7a5fb7276e..93b3136864 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -50,8 +50,7 @@ fn main() -> ExitCode { [task, engine, out] if task == "package-server" => { package_server(Path::new(engine), Path::new(out)) } - [task] if task == "mod-api" => mod_api::run(false), - [task, check] if task == "mod-api" && check == "--check" => mod_api::run(true), + [task] if task == "mod-api" => mod_api::run(), [task] if task == "tokens" => tokens(), [task] if task == "icons" => icons(), _ => Err(USAGE.to_string()), diff --git a/xtask/src/mod_api.rs b/xtask/src/mod_api.rs index 0340a27643..5f77fe6cc9 100644 --- a/xtask/src/mod_api.rs +++ b/xtask/src/mod_api.rs @@ -1,2006 +1,13 @@ -//! Generate the typed mod interface from native hook signatures and catalog contracts. -use quote::ToTokens; -use std::{collections::HashSet, fs, path::Path, process::Command}; -use syn::{FnArg, ImplItem, Item, Pat, Type, visit::Visit, visit_mut::VisitMut}; - -fn impl_functions(item: syn::ItemImpl) -> impl Iterator { - item.items.into_iter().filter_map(|item| match item { - ImplItem::Fn(method) => Some(syn::ItemFn { - attrs: method.attrs, - vis: method.vis, - sig: method.sig, - block: Box::new(method.block), - }), - _ => None, - }) -} - -/// The inherent `Game` methods of the files under `path`, each with its module (`native_hooks`, -/// `lan::guest`…) below `module`. -fn game_methods( - path: &Path, - module: &str, - methods: &mut Vec<(String, syn::ItemFn)>, -) -> Result<(), String> { - for entry in fs::read_dir(path).map_err(|e| e.to_string())? { - let path = entry.map_err(|e| e.to_string())?.path(); - let stem = path - .file_stem() - .and_then(|s| s.to_str()) - .unwrap_or_default(); - let inner = format!("{module}::{stem}"); - if path.is_dir() { - game_methods(&path, &inner, methods)?; - } else if path.extension().is_some_and(|ext| ext == "rs") { - let source = fs::read_to_string(&path).map_err(|e| e.to_string())?; - let file = syn::parse_file(&source).map_err(|e| format!("{}: {e}", path.display()))?; - for item in file.items { - if let Item::Impl(item) = item - && item.trait_.is_none() - && let Type::Path(ty) = &*item.self_ty - && ty.path.segments.len() == 1 - && ty.path.segments[0].ident == "Game" - { - methods.extend(impl_functions(item).map(|method| (inner.clone(), method))); - } - } - } - } - Ok(()) -} - -fn doc_comments( - attrs: &[syn::Attribute], - name: &str, - preserve_empty: bool, -) -> Result { - let mut docs = String::new(); - for attr in attrs.iter().filter(|attr| attr.path().is_ident("doc")) { - let syn::Meta::NameValue(value) = &attr.meta else { - return Err(format!("unsupported doc attribute in {name}")); - }; - let syn::Expr::Lit(syn::ExprLit { - lit: syn::Lit::Str(doc), - .. - }) = &value.value - else { - return Err(format!("unsupported doc attribute in {name}")); - }; - let value = doc.value(); - for line in value.split('\n') { - if preserve_empty || !line.is_empty() { - docs.push_str(&format!("///{line}\n")); - } - } - } - Ok(docs) -} - -fn exported_types(source: &str, generated: &str) -> Result, String> { - fn use_names(tree: &syn::UseTree, names: &mut HashSet) { - match tree { - syn::UseTree::Path(path) => use_names(&path.tree, names), - syn::UseTree::Name(name) => { - names.insert(name.ident.to_string()); - } - syn::UseTree::Rename(rename) => { - names.insert(rename.rename.to_string()); - } - syn::UseTree::Group(group) => { - for item in &group.items { - use_names(item, names); - } - } - syn::UseTree::Glob(_) => {} - } - } - let mut names = HashSet::new(); - for source in [source, generated] { - let file = syn::parse_file(source).map_err(|e| e.to_string())?; - for item in file.items { - let ident = match item { - Item::Use(item) if matches!(item.vis, syn::Visibility::Public(_)) => { - use_names(&item.tree, &mut names); - None - } - Item::Struct(item) if matches!(item.vis, syn::Visibility::Public(_)) => { - Some(item.ident) - } - Item::Enum(item) if matches!(item.vis, syn::Visibility::Public(_)) => { - Some(item.ident) - } - Item::Union(item) if matches!(item.vis, syn::Visibility::Public(_)) => { - Some(item.ident) - } - Item::Type(item) if matches!(item.vis, syn::Visibility::Public(_)) => { - Some(item.ident) - } - Item::Trait(item) if matches!(item.vis, syn::Visibility::Public(_)) => { - Some(item.ident) - } - Item::Mod(item) if matches!(item.vis, syn::Visibility::Public(_)) => { - Some(item.ident) - } - _ => None, - }; - if let Some(ident) = ident { - names.insert(ident.to_string()); - } - } - } - Ok(names) -} - -fn host_method( - method: &syn::ItemFn, - name: &str, - exports: &HashSet, -) -> Result<(String, String), String> { - struct Types<'a> { - exports: &'a HashSet, - error: Option, - } - impl Types<'_> { - fn check_path(&mut self, path: &syn::Path) { - let first = path.segments.first().unwrap().ident.to_string(); - let unqualified = path.leading_colon.is_none() && path.segments.len() == 1; - let prelude = unqualified - && matches!( - first.as_str(), - "bool" - | "char" - | "str" - | "u8" - | "u16" - | "u32" - | "u64" - | "u128" - | "usize" - | "i8" - | "i16" - | "i32" - | "i64" - | "i128" - | "isize" - | "f32" - | "f64" - | "String" - | "Option" - | "Result" - | "Vec" - | "Box" - | "Arc" - | "PathBuf" - | "AsRef" - | "AsMut" - | "From" - | "Into" - | "TryFrom" - | "TryInto" - | "Iterator" - | "IntoIterator" - | "DoubleEndedIterator" - | "ExactSizeIterator" - | "Extend" - | "Fn" - | "FnMut" - | "FnOnce" - | "Sized" - | "Send" - | "Sync" - | "Unpin" - | "Drop" - | "Default" - | "Clone" - | "Copy" - | "PartialEq" - | "PartialOrd" - | "Eq" - | "Ord" - | "ToOwned" - | "ToString" - | "Future" - | "IntoFuture" - ); - if path.leading_colon.is_some() || !(prelude || self.exports.contains(&first)) { - self.error = Some(format!( - "type {} is not available in mod_api", - path.to_token_stream() - )); - } - } - } - impl<'ast> Visit<'ast> for Types<'_> { - fn visit_type_impl_trait(&mut self, _: &'ast syn::TypeImplTrait) { - self.error = Some("impl Trait is not object-safe".into()); - } - fn visit_type_path(&mut self, ty: &'ast syn::TypePath) { - if ty.qself.is_some() { - self.error = Some("qualified associated types are not supported".into()); - } else { - self.check_path(&ty.path); - } - syn::visit::visit_type_path(self, ty); - } - fn visit_trait_bound(&mut self, bound: &'ast syn::TraitBound) { - self.check_path(&bound.path); - syn::visit::visit_trait_bound(self, bound); - } - } - if method.sig.asyncness.is_some() { - return Err(format!("{name}: async methods are not object-safe")); - } - if method - .sig - .generics - .params - .iter() - .any(|param| !matches!(param, syn::GenericParam::Lifetime(_))) - { - return Err(format!("{name}: generic methods are not object-safe")); - } - let mut types = Types { - exports, - error: None, - }; - for arg in &method.sig.inputs { - if let FnArg::Typed(arg) = arg { - types.visit_type(&arg.ty); - } - } - if let syn::ReturnType::Type(_, ty) = &method.sig.output { - types.visit_type(ty); - } - if let Some(reason) = types.error { - return Err(format!("{name}: {reason}")); - } - let mut sig = method.sig.clone(); - let original = sig.ident.clone(); - sig.ident = syn::parse_str(name).map_err(|e| format!("invalid helper name {name}: {e}"))?; - let mut args = Vec::new(); - let callee = match sig.inputs.first() { - Some(FnArg::Receiver(r)) if r.reference.is_some() => "self.", - Some(FnArg::Receiver(_)) => { - return Err(format!("{name}: expected a borrowed Game receiver")); - } - _ => { - // A receiverless helper stays callable on `&self` in the trait. - sig.inputs.insert(0, syn::parse_quote!(&self)); - "Self::" - } - }; - for arg in sig.inputs.iter_mut().skip(1) { - let FnArg::Typed(arg) = arg else { - return Err(format!("{name}: unexpected receiver")); - }; - let Pat::Ident(pat) = &mut *arg.pat else { - return Err(format!("{name}: unsupported argument")); - }; - pat.mutability = None; - args.push(pat.ident.to_string()); - } - let docs = doc_comments(&method.attrs, name, true)?; - let unused = args - .iter() - .map(|arg| format!("let _ = {arg};")) - .collect::(); - Ok(( - format!( - "{docs}{} {{ {unused} unimplemented!(\"Host::{name}\") }}\n", - sig.to_token_stream() - ), - format!( - "{} {{ {callee}{original}({}) }}\n", - sig.to_token_stream(), - args.join(", ") - ), - )) -} - -/// The handle types a helper's `receiver` names (#7387), keyed as in the catalog. -const HANDLES: [(&str, &str); 2] = [("entity", "EntityRef"), ("level", "LevelRef")]; - -/// A helper's method on its handle (`receiver`): `entity_position(entity)` on -/// `Host` is also `EntityRef::position(self, host)`, which calls it. The -/// helper's first argument is the handle; the host is borrowed as the helper -/// borrows `Game`. -fn handle_method( - method: &syn::ItemFn, - name: &str, - receiver: &str, -) -> Result<(&'static str, String), String> { - let (_, handle) = HANDLES - .iter() - .find(|(key, _)| *key == receiver) - .ok_or_else(|| format!("{name}: unknown receiver {receiver}"))?; - let short = name - .strip_prefix(receiver) - .and_then(|rest| rest.strip_prefix('_')) - .ok_or_else(|| format!("{name}: a {receiver} helper's name starts with {receiver}_"))?; - let mut sig = method.sig.clone(); - sig.ident = - syn::parse_str(short).map_err(|_| format!("{name}: {short} is not a method name"))?; - let mut inputs = sig.inputs.iter(); - let host: FnArg = match inputs.next() { - Some(FnArg::Receiver(r)) if r.reference.is_some() && r.mutability.is_some() => { - syn::parse_quote!(host: &mut dyn Host) - } - Some(FnArg::Receiver(r)) if r.reference.is_some() => syn::parse_quote!(host: &dyn Host), - _ => return Err(format!("{name}: a {receiver} helper borrows Game")), - }; - let takes_handle = matches!( - inputs.next(), - Some(FnArg::Typed(arg)) if matches!(&*arg.ty, Type::Path(ty) if ty.path.is_ident(handle)) - ); - if !takes_handle { - return Err(format!( - "{name}: a {receiver} helper takes a {handle} first" - )); - } - let mut args = String::new(); - let mut rest = vec![syn::parse_quote!(self), host]; - for arg in inputs { - let mut arg = arg.clone(); - let FnArg::Typed(typed) = &mut arg else { - return Err(format!("{name}: unexpected receiver")); - }; - let Pat::Ident(pat) = &mut *typed.pat else { - return Err(format!("{name}: unsupported argument")); - }; - pat.mutability = None; - args.push_str(&format!(", {}", pat.ident)); - rest.push(arg); - } - sig.inputs = rest.into_iter().collect(); - let docs = doc_comments(&method.attrs, name, true)?; - Ok(( - handle, - format!( - "{docs}pub {} {{ host.{name}(self{args}) }}\n", - sig.to_token_stream() - ), - )) -} - -fn has_call(block: &syn::Block, expected: &str) -> bool { - struct Calls<'a> { - expected: &'a str, - found: bool, - } - impl<'ast> Visit<'ast> for Calls<'_> { - fn visit_expr_call(&mut self, call: &'ast syn::ExprCall) { - if let syn::Expr::Path(path) = &*call.func - && path - .path - .segments - .last() - .is_some_and(|s| s.ident == self.expected) - { - self.found = true; - } - syn::visit::visit_expr_call(self, call); - } - } - let mut calls = Calls { - expected, - found: false, - }; - calls.visit_block(block); - calls.found -} - -fn one_function<'a>( - items: impl Iterator, - name: &str, -) -> Result<&'a syn::ItemFn, String> { - let mut matches = items.filter(|item| item.sig.ident == name); - let first = matches - .next() - .ok_or_else(|| format!("expected one {name} signature"))?; - if matches.next().is_some() { - return Err(format!("expected one {name} signature")); - } - Ok(first) -} - -fn fresh_name(base: &str, used: &mut HashSet) -> String { - let mut name = base.to_owned(); - while !used.insert(name.clone()) { - name.push('_'); - } - name -} - -/// Turns a hook argument's type into its event field's. A `&mut` primitive scalar becomes the value -/// itself, written back after every mod has run; any other `&mut` stays a reference to the caller's -/// value. Either way one event goes through every mod, so each sees the changes before it. Every -/// elided reference lifetime becomes the event's `'a`, or `'b` inside a `&mut`, which can't shorten -/// it. The launcher keeps a copy for trait injections (`launcher/src/native_mods/inject.rs`); both -/// test the same types. -#[derive(Default)] -struct EventLifetimes { - a: bool, - b: bool, - in_mut: bool, -} - -impl EventLifetimes { - /// The field's type, and whether it is a scalar copied in and written back. - fn field(&mut self, ty: &Type) -> (Type, bool) { - let scalar = match ty { - Type::Reference(reference) if reference.mutability.is_some() => { - match &*reference.elem { - Type::Path(path) - if path.qself.is_none() - && path.path.get_ident().is_some_and(|ident| { - [ - "bool", "char", "i8", "i16", "i32", "i64", "i128", "isize", - "u8", "u16", "u32", "u64", "u128", "usize", "f32", "f64", - ] - .iter() - .any(|scalar| ident == scalar) - }) => - { - Some(&*reference.elem) - } - _ => None, - } - } - _ => None, - }; - if let Some(scalar) = scalar { - return (scalar.clone(), true); - } - let mut field = ty.clone(); - self.visit_type_mut(&mut field); - (field, false) - } -} - -impl VisitMut for EventLifetimes { - fn visit_type_reference_mut(&mut self, reference: &mut syn::TypeReference) { - if reference.lifetime.is_none() { - reference.lifetime = Some(syn::parse_quote!('_)); - } - let outer = self.in_mut; - self.visit_lifetime_mut(reference.lifetime.as_mut().unwrap()); - self.in_mut |= reference.mutability.is_some(); - self.visit_type_mut(&mut reference.elem); - self.in_mut = outer; - } - fn visit_lifetime_mut(&mut self, lifetime: &mut syn::Lifetime) { - if lifetime.ident == "_" { - *lifetime = if self.in_mut { - self.b = true; - syn::parse_quote!('b) - } else { - self.a = true; - syn::parse_quote!('a) - }; - } - } -} - -fn host_for(is_level: bool, mutable: Option, ticks: bool) -> Option<(&'static str, String)> { - mutable.map(|mutable| { - let (name, kind) = if is_level { - ( - "level", - match (mutable, ticks) { - (true, true) => "LevelWriteHost", - (false, true) => "LevelTicksHost", - (true, false) => "LevelRandomHost", - (false, false) => "LevelHost", - }, - ) - } else { - ("host", "Host") - }; - let mutable = if mutable || (is_level && ticks) { - "mut " - } else { - "" - }; - (name, format!("&{mutable}dyn {kind}")) - }) -} - -fn catalog_hooks( - root: &Path, - catalog: &serde_json::Value, -) -> Result, String> { - let mut hooks = catalog["hooks"] - .as_array() - .ok_or("catalog hooks must be an array")? - .clone(); - let mut paths = fs::read_dir(root.join("tools/native-hook-fragments")) - .map_err(|e| e.to_string())? - .map(|entry| entry.map(|entry| entry.path()).map_err(|e| e.to_string())) - .collect::, _>>()?; - paths.sort(); - for path in paths - .into_iter() - .filter(|path| path.extension().is_some_and(|ext| ext == "json")) - { - let fragment: serde_json::Value = - serde_json::from_str(&fs::read_to_string(&path).map_err(|e| e.to_string())?) - .map_err(|e| format!("{}: {e}", path.display()))?; - hooks.extend( - fragment["hooks"] - .as_array() - .ok_or("fragment hooks must be an array")? - .iter() - .cloned(), - ); - } - let mut seen = HashSet::new(); - for hook in &hooks { - let name = hook["name"].as_str().ok_or("hook name missing")?; - if !seen.insert(name.to_owned()) { - return Err(format!("duplicate hook {name}")); - } - } - Ok(hooks) -} - -/// The crate folder and module path of a hook target that is a free function -/// outside `Game`. `horizon-client-lib` builds from `horizon-client/src` -/// (`client_lib.rs`); `menu` and `breathing` are also accepted under their -/// older `horizon_client::` names. -fn free_function_source(target: &str) -> Option<(&'static str, String)> { - let prefixes = [ - ("horizon_world::", "horizon-world"), - ("horizon_client_render::", "horizon-client-render"), - ("horizon_client_lib::", "horizon-client"), - ]; - prefixes - .into_iter() - .find_map(|(prefix, krate)| target.strip_prefix(prefix).map(|p| (krate, p.to_owned()))) - .or_else(|| { - ["menu", "breathing"].into_iter().find_map(|module| { - target - .strip_prefix(&format!("horizon_client::{module}::")) - .map(|p| ("horizon-client", format!("{module}::{p}"))) - }) - }) -} - -/// Whether a hook function needs its dispatch `call` filled in: false when it -/// makes the call; true when it is a `Game` or `GameLevel` method whose body is -/// only the placeholder a new hook starts with, `let _ = (every, parameter);` -/// (`let _ = parameter;` for one). Any other body is an error, left alone. -fn needs_fill(method: &syn::ItemFn, call: &str, in_game: bool) -> Result { - if has_call(&method.block, call) { - return Ok(false); - } - let params: Vec = method - .sig - .inputs - .iter() - .filter_map(|arg| match arg { - FnArg::Typed(arg) => Some(arg.pat.to_token_stream().to_string()), - FnArg::Receiver(_) => None, - }) - .collect(); - let named = match &method.block.stmts[..] { - [ - syn::Stmt::Local(syn::Local { - pat: Pat::Wild(_), - init: - Some(syn::LocalInit { - expr, - diverge: None, - .. - }), - .. - }), - ] => match &**expr { - syn::Expr::Tuple(tuple) => Some( - tuple - .elems - .iter() - .map(|e| e.to_token_stream().to_string()) - .collect(), - ), - single @ syn::Expr::Path(_) => Some(vec![single.to_token_stream().to_string()]), - _ => None, - }, - _ => None, - }; - if in_game && named == Some(params) { - return Ok(true); - } - Err(format!( - "missing {call} call in {}: add it, or leave a Game hook's body as the let _ = (…); placeholder for cargo run -p xtask -- mod-api to fill", - method.sig.ident - )) -} - -/// A hook function whose placeholder body becomes `call`. -struct Fill { - native: String, - call: String, -} - -/// `source` with each fill's placeholder statement replaced by its call, -/// formatted as `cargo fmt` would. -fn filled_hooks(source: &str, fills: &[Fill]) -> Result, String> { - let mut source = source.to_owned(); - for Fill { native, call } in fills { - let start = source - .find(&format!("fn {native}(")) - .ok_or_else(|| format!("{native} not found"))?; - let placeholder = start - + source[start..] - .find("let _ =") - .ok_or_else(|| format!("{native} has no placeholder"))?; - let end = placeholder - + source[placeholder..] - .find(';') - .ok_or_else(|| format!("{native} has no placeholder"))? - + 1; - source.replace_range(placeholder..end, call); - } - rustfmt(&source) -} - -/// The workspace's edition, which rustfmt formats generated code for. -const EDITION: &str = "2024"; - -/// `code` as `cargo fmt` formats it. -fn rustfmt(code: &str) -> Result, String> { - let mut child = Command::new("rustfmt") - .args(["--emit", "stdout", "--edition", EDITION]) - .stdin(std::process::Stdio::piped()) - .stdout(std::process::Stdio::piped()) - .spawn() - .map_err(|e| e.to_string())?; - use std::io::Write; - child - .stdin - .take() - .ok_or("rustfmt stdin")? - .write_all(code.as_bytes()) - .map_err(|e| e.to_string())?; - let formatted = child.wait_with_output().map_err(|e| e.to_string())?; - if !formatted.status.success() { - return Err("rustfmt failed".into()); - } - Ok(formatted.stdout) -} - -pub fn run(check: bool) -> Result<(), String> { - generate(&Path::new(env!("CARGO_MANIFEST_DIR")).join(".."), check) -} - -/// Writes the mod API generated from the sources under `root`, and fills new -/// hooks' placeholder bodies; with `check`, fails instead if either would change. -fn generate(root: &Path, check: bool) -> Result<(), String> { - let catalog: serde_json::Value = serde_json::from_str( - &fs::read_to_string(root.join("native-hooks.json")).map_err(|e| e.to_string())?, - ) - .map_err(|e| e.to_string())?; - let source = fs::read_to_string(root.join("crates/horizon-client/src/game/native_hooks.rs")) - .map_err(|e| e.to_string())?; - let file = syn::parse_file(&source).map_err(|e| e.to_string())?; - let mut methods = Vec::new(); - for item in file.items { - if let Item::Impl(item) = item { - methods.extend(impl_functions(item)); - } - } - let mut traits = String::new(); - let mut dispatch = String::new(); - let mut render_traits = String::new(); - let mut render_dispatch = String::new(); - let hooks = catalog_hooks(root, &catalog)?; - let mut fills = Vec::new(); - for hook in &hooks { - let name = hook["name"].as_str().ok_or("hook name missing")?; - let api = hook - .get("mod_api") - .ok_or_else(|| format!("hook {name} has no mod_api entry: add one"))?; - let native = format!("native_{name}"); - let target = hook["target"].as_str().ok_or("target missing")?; - let is_render = target.starts_with("horizon_client_render::"); - let external = free_function_source(target); - let in_game = external.is_none(); - let (method, call) = if let Some((krate, path)) = external { - let Some((module, function)) = path.rsplit_once("::") else { - return Err(format!("wrong target for {name}")); - }; - if function != native { - return Err(format!("wrong target for {name}")); - } - let path = root - .join("crates") - .join(krate) - .join("src") - .join(format!("{}.rs", module.replace("::", "/"))); - let source = - fs::read_to_string(&path).map_err(|e| format!("{}: {e}", path.display()))?; - let file = syn::parse_file(&source).map_err(|e| e.to_string())?; - let functions: Vec<_> = file - .items - .into_iter() - .filter_map(|item| match item { - Item::Fn(function) => Some(function), - _ => None, - }) - .collect(); - ( - one_function(functions.iter(), &native)?.clone(), - if is_render { - format!("dispatch_{name}") - } else { - name.to_owned() - }, - ) - } else { - if !target.starts_with("horizon_client::game::native_hooks::") - || !target.ends_with(&format!("::{native}")) - { - return Err(format!("wrong target for {name}")); - } - ( - one_function(methods.iter(), &native)?.clone(), - format!("dispatch_{name}"), - ) - }; - let fill = needs_fill(&method, &call, in_game)?; - let sig = &method.sig; - let mut args = Vec::new(); - let mut receiver_mut = None; - for arg in &sig.inputs { - match arg { - FnArg::Receiver(r) => { - receiver_mut = Some(r.mutability.is_some()); - } - FnArg::Typed(arg) => { - let Pat::Ident(pat) = &*arg.pat else { - return Err(format!("unsupported argument in {name}")); - }; - args.push((pat.ident.to_string(), &*arg.ty)); - } - } - } - let is_level = hook["target"] - .as_str() - .is_some_and(|target| target.contains("::GameLevel<")); - let ticks = is_level - && args.iter().any(|(name, ty)| { - name == "ticks" && ty.to_token_stream().to_string() == "& mut Ticks" - }); - if ticks { - args.retain(|(name, _)| name != "ticks"); - } - let mut used: HashSet<_> = args.iter().map(|(name, _)| name.clone()).collect(); - let mod_list = fresh_name("mods", &mut used); - let mod_item = fresh_name("mod_hook", &mut used); - let replacement = fresh_name("replacement", &mut used); - let contract = api["contract"].as_str().ok_or("contract missing")?; - let out = api.get("out").and_then(|v| v.as_str()); - if !matches!( - (contract, out), - ("cancel" | "replace" | "collect", Some(_)) | ("listen", None) - ) { - return Err(format!("invalid contract for {name}")); - } - let output = if let Some(out) = out { - let index = args - .iter() - .position(|(n, _)| n == out) - .ok_or_else(|| format!("missing output {out} in {name}"))?; - Some(args.remove(index)) - } else { - None - }; - let result = if let Some((_, ty)) = &output { - let Type::Reference(reference) = ty else { - return Err(format!("output must be a mutable reference in {name}")); - }; - if reference.mutability.is_none() { - return Err(format!("output must be a mutable reference in {name}")); - } - match contract { - "cancel" if reference.elem.to_token_stream().to_string() == "bool" => { - " -> Flow".to_owned() - } - "replace" => format!(" -> {}", reference.elem.to_token_stream()), - "collect" => String::new(), - _ => return Err(format!("unexpected output type for {name}")), - } - } else { - String::new() - }; - let host = host_for(is_level, receiver_mut, ticks) - .map(|(name, ty)| (fresh_name(name, &mut used), ty)); - let event = fresh_name("event", &mut used); - let event_type = format!( - "{}Event", - name.split('_') - .map(|word| word[..1].to_uppercase() + &word[1..]) - .collect::() - ); - let mut trait_params = vec!["&self".to_owned()]; - let mut dispatch_params = Vec::new(); - let mut call_args = Vec::new(); - if let Some((name, ty)) = &host { - trait_params.push(format!("{name}: {ty}")); - dispatch_params.push(format!("{name}: {ty}")); - call_args.push(name.clone()); - } - dispatch_params.push(if is_render { - format!("{mod_list}: &[&dyn RenderHooks]") - } else { - format!("{mod_list}: &[Box]") - }); - let mut lifetimes = EventLifetimes::default(); - let mut fields = String::new(); - let mut inits = Vec::new(); - let mut write_back = String::new(); - let collected = output.as_ref().filter(|_| contract == "collect"); - for (name, ty) in args.iter().chain(collected) { - dispatch_params.push(format!("{name}: {}", ty.to_token_stream())); - let (field, scalar) = lifetimes.field(ty); - fields.push_str(&format!("pub {name}: {},\n", field.to_token_stream())); - if scalar { - inits.push(format!("{name}: *{name}")); - write_back.push_str(&format!("*{name} = {event}.{name};")); - } else { - inits.push(name.clone()); - } - } - if let Some((name, ty)) = output.as_ref().filter(|_| contract != "collect") { - dispatch_params.push(format!("{name}: {}", ty.to_token_stream())); - } - let (generics, elided) = match (lifetimes.a, lifetimes.b) { - (_, true) => ("<'a, 'b>", "<'_, '_>"), - (true, false) => ("<'a>", "<'_>"), - (false, false) => ("", ""), - }; - trait_params.push(format!("{event}: &mut {event_type}{elided}")); - call_args.push(format!("&mut {event}")); - let unused = trait_params[1..] - .iter() - .map(|param| format!("let _ = {};", param.split(':').next().unwrap())) - .collect::(); - let default = match contract { - "cancel" => "Flow::Continue", - "replace" => "None", - _ => "", - }; - if fill { - // The dispatch's parameters in order: the host, the mods, the - // arguments and the output last. - let host = host.as_ref().map(|_| { - if ticks { - "&mut super::mod_api_host::LevelTicks { level: self, ticks }" - } else { - "self" - } - }); - let passed = host - .into_iter() - .chain(["mod_api::mods()"]) - .chain(args.iter().chain(output.as_ref()).map(|(n, _)| n.as_str())) - .collect::>() - .join(", "); - fills.push(Fill { - native: native.clone(), - call: format!("mod_api::{call}({passed});"), - }); - } - let invoke = call_args.join(", "); - let trait_params = trait_params.join(", "); - let dispatch_params = dispatch_params.join(", "); - let trait_name = if is_render { "RenderHooks" } else { "Hooks" }; - let traits = if is_render { - &mut render_traits - } else { - &mut traits - }; - let dispatch = if is_render { - &mut render_dispatch - } else { - &mut dispatch - }; - traits.push_str(&doc_comments(&method.attrs, name, false)?); - match (contract, out) { - ("cancel", Some(out)) => traits.push_str(&format!( - "///\n/// Return `Flow::Cancel` to take it: this sets `{out}`, and later mods are skipped.\n" - )), - ("replace", Some(out)) => traits.push_str(&format!( - "///\n/// Return `Some` to set `{out}`; the first mod returning `Some` wins.\n" - )), - ("collect", Some(out)) => traits.push_str(&format!( - "///\n/// Every mod gets `{event}.{out}` in order and may add, change or remove {out}.\n" - )), - _ => {} - } - traits.push_str(&format!( - "fn {name}({trait_params}){result} {{ {unused} {default} }}\n" - )); - dispatch.push_str(&format!( - "/// What [`{trait_name}::{name}`] is told. Fields may be added: the struct is `#[non_exhaustive]`.\n#[non_exhaustive]\npub struct {event_type}{generics} {{\n{fields}}}\n" - )); - let build = format!("let mut {event} = {event_type} {{ {} }};", inits.join(", ")); - // A trait mod's call is marked with the mod for a panic to name; render hooks are called bare. - let each = if is_render { - mod_list.clone() - } else { - format!("super::entered({mod_list}, \"{name}\")") - }; - let body = match contract { - "cancel" => format!( - "if *{o} {{ return; }} {build} for {mod_item} in {each} {{ if matches!({mod_item}.{name}({invoke}), Flow::Cancel) {{ *{o} = true; break; }} }} {write_back}", - o = out.unwrap() - ), - "replace" => format!( - "if {o}.is_some() {{ return; }} {build} for {mod_item} in {each} {{ if let Some({replacement}) = {mod_item}.{name}({invoke}) {{ *{o} = Some({replacement}); break; }} }} {write_back}", - o = out.unwrap() - ), - _ => format!( - "{build} for {mod_item} in {each} {{ {mod_item}.{name}({invoke}); }} {write_back}" - ), - }; - dispatch.push_str(&format!( - "pub fn dispatch_{name}({dispatch_params}) {{ {body} }}\n" - )); - } - if hooks.is_empty() { - return Err("no native hooks".into()); - } - let code = format!( - "// @generated by cargo run -p xtask -- mod-api; do not edit.\nuse super::*;\npub trait Hooks: Send + Sync {{\nfn render(&self) -> Option<&dyn RenderHooks> {{ None }}\n{traits}}}\n{dispatch}" - ); - let api_source = fs::read_to_string(root.join("crates/horizon-client/src/mod_api.rs")) - .map_err(|e| e.to_string())?; - let exports = exported_types(&api_source, &code)?; - let mut game = Vec::new(); - game_methods( - &root.join("crates/horizon-client/src/game"), - "horizon_client::game", - &mut game, - )?; - let mut host_traits = String::new(); - let mut host_impl = String::new(); - let mut names = HashSet::new(); - let mut handles = std::collections::BTreeMap::<&str, String>::new(); - let mut helpers = catalog["helpers"] - .as_array() - .ok_or("catalog helpers must be an array")? - .clone(); - let mut fragments = fs::read_dir(root.join("tools/native-hook-fragments")) - .map_err(|e| e.to_string())? - .map(|entry| entry.map(|entry| entry.path()).map_err(|e| e.to_string())) - .collect::, _>>()?; - fragments.sort(); - for path in fragments - .into_iter() - .filter(|path| path.extension().is_some_and(|ext| ext == "json")) - { - let fragment: serde_json::Value = - serde_json::from_str(&fs::read_to_string(&path).map_err(|e| e.to_string())?) - .map_err(|e| format!("{}: {e}", path.display()))?; - if let Some(pending) = fragment.get("helpers") { - helpers.extend( - pending - .as_array() - .ok_or("fragment helpers must be an array")? - .iter() - .cloned(), - ); - } - } - for helper in &helpers { - let name = helper["name"].as_str().ok_or("helper name missing")?; - if !names.insert(name) { - return Err(format!("duplicate helper {name}")); - } - let native = format!("native_{name}"); - let target = helper["target"].as_str().ok_or("helper target missing")?; - let method = one_function(game.iter().map(|(_, method)| method), &native)?; - let (module, _) = game - .iter() - .find(|(_, other)| std::ptr::eq(other, method)) - .expect("the method was found in the list"); - let expected = format!("{module}::Game<'_>::{native}"); - if target != expected { - return Err(format!( - "wrong target for helper {name}: {target}, it is {expected}" - )); - } - let (declaration, implementation) = host_method(method, name, &exports)?; - host_traits.push_str(&declaration); - host_impl.push_str(&implementation); - if let Some(receiver) = helper.get("receiver") { - let receiver = receiver - .as_str() - .ok_or("helper receiver must be a string")?; - let (handle, code) = handle_method(method, name, receiver)?; - handles.entry(handle).or_default().push_str(&code); - } - } - let handles = handles - .iter() - .map(|(handle, methods)| format!("impl {handle} {{\n{methods}}}\n")) - .collect::(); - struct StdTypes { - path_buf: bool, - arc: bool, - } - impl<'ast> Visit<'ast> for StdTypes { - fn visit_type_path(&mut self, ty: &'ast syn::TypePath) { - if ty.path.segments.len() == 1 { - self.path_buf |= ty.path.segments[0].ident == "PathBuf"; - self.arc |= ty.path.segments[0].ident == "Arc"; - } - syn::visit::visit_type_path(self, ty); - } - } - let host_trait: syn::ItemTrait = syn::parse_str(&format!("trait Host {{ {host_traits} }}")) - .map_err(|e| format!("generated Host: {e}"))?; - let mut std_types = StdTypes { - path_buf: false, - arc: false, - }; - std_types.visit_item_trait(&host_trait); - let mut imports = String::new(); - if std_types.path_buf { - imports.push_str("use std::path::PathBuf;\n"); - } - if std_types.arc { - imports.push_str("use std::sync::Arc;\n"); - } - let host = format!( - "// @generated by cargo run -p xtask -- mod-api; do not edit.\nuse super::*;\n{imports}/// Services supplied by the game to trait mods.\n/// Each method's default panics with its name, so a test double implements only what its test calls; the game implements every one.\npub trait Host {{\n{host_traits}}}\n{handles}" - ); - let implementation = format!( - "// @generated by cargo run -p xtask -- mod-api; do not edit.\nuse horizon_client::mod_api::*;\n{imports}impl Host for super::Game<'_> {{\n{host_impl}}}\n" - ); - let render_code = format!( - "// @generated by cargo run -p xtask -- mod-api; do not edit.\nuse super::*;\npub trait RenderHooks: Send + Sync {{\n{render_traits}}}\n{render_dispatch}" - ); - // A new hook's placeholder body becomes its dispatch call. - let filled = if fills.is_empty() { - None - } else if check { - let natives = fills - .iter() - .map(|fill| fill.native.as_str()) - .collect::>() - .join(", "); - return Err(format!( - "{natives}: the let _ = (…); placeholder needs its dispatch call; run cargo run -p xtask -- mod-api" - )); - } else { - Some(filled_hooks(&source, &fills)?) - }; - for (relative, code) in [ - ( - "crates/horizon-client-render/src/gfx/native_render/generated.rs", - render_code, - ), - ("crates/horizon-client/src/mod_api/generated.rs", code), - ("crates/horizon-client/src/mod_api/generated_host.rs", host), - ( - "crates/horizon-client/src/game/mod_api_host_generated.rs", - implementation, - ), - ] { - // Parse before formatting, so invalid catalog names or types fail without writing output. - syn::parse_file(&code).map_err(|e| format!("generated Rust: {e}"))?; - let formatted = rustfmt(&code)?; - let path = root.join(relative); - if check { - if fs::read(&path).map_err(|e| e.to_string())? != formatted { - return Err("mod API output is stale; run cargo run -p xtask -- mod-api".into()); - } - } else { - fs::create_dir_all(path.parent().ok_or("output parent missing")?) - .map_err(|e| e.to_string())?; - fs::write(path, formatted).map_err(|e| e.to_string())?; - } - } - if let Some(filled) = filled { - fs::write( - root.join("crates/horizon-client/src/game/native_hooks.rs"), - filled, - ) - .map_err(|e| e.to_string())?; - } - Ok(()) +//! Fill new native hooks' placeholder bodies in the hand-written source. +pub fn run() -> Result<(), String> { + let root = std::env::current_dir().map_err(|e| e.to_string())?; + let root = root + .ancestors() + .find(|path| path.join("native-hooks.json").is_file()) + .ok_or("run xtask from within the Horizon checkout")?; + horizon_mod_api_gen::fill_hooks(root) } #[cfg(test)] -mod tests { - use quote::ToTokens; - use std::sync::{Arc, Mutex}; - use std::time::Duration; - - struct ItemStack; - struct BlockEntityDraw(Vec); - #[allow(dead_code)] - mod stubs { - #[derive(Clone, Copy, Debug, PartialEq)] - pub(super) struct BlockStateId; - #[derive(Clone, Copy)] - pub(super) struct FluidState; - pub(super) struct PlacementContext; - #[derive(Clone, Copy)] - pub(super) struct BlockPos; - #[derive(Clone, Copy)] - pub(super) struct BlockId; - #[derive(Clone, Copy)] - pub(super) struct Direction; - #[derive(Clone, Copy)] - pub(super) struct LevelDimension; - pub(super) struct Ticks; - pub(super) trait LevelHost {} - pub(super) trait LevelTicksHost: LevelHost {} - pub(super) trait LevelRandomHost: LevelHost {} - pub(super) trait LevelWriteHost: LevelTicksHost + LevelRandomHost {} - pub(super) struct Compound; - pub(super) type NativeDispensed = (Option, i32); - pub(super) struct NativeLootDrops<'a>(pub &'a ()); - pub(super) struct NativeScreenView<'a>(pub &'a ()); - pub(super) struct Corners; - #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] - pub(super) struct ItemEnergyBuffer; - pub(super) struct Suggestion; - pub(super) struct HudBatch; - pub(super) struct BiomeColors; - pub(super) struct UseAnimation; - #[derive(Clone, Copy)] - pub(super) struct Hand; - pub(super) struct NativeMobRender<'a>(pub &'a ()); - pub(super) struct NativeShake; - pub(super) struct Music; - pub(super) struct MusicLayer; - pub(super) struct NativeBlockRim; - pub(super) struct NativeColoredLight; - pub(super) struct NativeMinimap; - pub(super) struct NativeMod; - pub(super) struct NativePanelEvent; - pub(super) struct NativeScreen; - pub(super) struct ModBlockEntity; - pub(super) struct ModContainer; - pub(super) struct MerchantOffer; - pub(super) struct ModFluidTanks; - pub(super) struct ModEnergyBuffer; - pub(super) struct LootStack; - pub(super) struct NativeLootContext<'a>(pub &'a ()); - pub(super) struct NativeDataMapType; - pub(super) struct RecipeBookCategory; - pub(super) struct NativeMapDecoration; - pub(super) struct NativeMapInfo<'a>(pub &'a ()); - pub(super) struct NativePlayerRender<'a>(pub &'a ()); - pub(super) struct NativeCommand; - pub(super) struct NativeLifecycleContext; - pub(super) struct NativeServerPlayer; - #[derive(Clone, Copy)] - pub(super) struct ServerTickPhase; - #[derive(Clone, Copy)] - pub(super) struct WorldPhase; - } - use serde_json::{Map, Value}; - use stubs::*; - struct Text; - #[derive(Debug, PartialEq)] - struct NativeZoom(u32); - enum Flow { - Continue, - Cancel, - } - trait Host {} - trait RenderHooks {} - struct TestHost; - impl Host for TestHost {} - mod winit { - pub mod event { - pub struct KeyEvent; - } - } - #[allow(dead_code)] - mod generated { - mod horizon_mods { - pub mod content { - pub struct Block; - pub struct Item; - } - } - mod horizon_client { - pub mod camera { - pub struct CameraEntity; - pub struct FovPlayer; - } - pub mod menu { - pub struct AnvilCraft; - } - pub mod fog { - #[derive(Clone, Copy)] - pub struct FogType; - pub struct FogData; - } - pub mod first_person { - #[derive(Clone, Copy)] - pub struct Hand; - } - pub mod hud_layout { - pub struct HealthInputs; - pub struct GuiDraw; - } - } - include!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/../crates/horizon-client/src/mod_api/generated.rs" - )); - } - use generated::*; - thread_local! { - /// The hooks each dispatch entered, a mod at a time. - static ENTERED: std::cell::RefCell> = const { std::cell::RefCell::new(Vec::new()) }; - } - fn entered<'a>( - mods: &'a [Box], - hook: &'static str, - ) -> impl Iterator> { - mods.iter().enumerate().map(move |(index, hooks)| { - ENTERED.with(|entered| entered.borrow_mut().push((index, hook))); - hooks - }) - } - struct Mod { - id: u32, - calls: Arc>>, - } - impl Hooks for Mod { - fn block_entity_submit(&self, _: &dyn Host, event: &mut BlockEntitySubmitEvent<'_>) { - assert_eq!(event.pos, [1, 2, 3]); - assert_eq!(event.block, "example:machine"); - event.draw.0.push(self.id); - } - fn living_entity_use_item_finish( - &self, - _: &mut dyn Host, - event: &mut LivingEntityUseItemFinishEvent<'_>, - ) { - *event.result = None; - } - fn mouse_button_pre(&self, _: &mut dyn Host, _: &mut MouseButtonPreEvent) -> Flow { - self.calls.lock().unwrap().push(self.id); - if self.id == 1 { - Flow::Cancel - } else { - Flow::Continue - } - } - fn menu_slot_rules(&self, event: &mut MenuSlotRulesEvent<'_>) { - event.may_place = false; - event.max_stack = self.id; - } - fn living_entity_use_item_tick( - &self, - _: &mut dyn Host, - event: &mut LivingEntityUseItemTickEvent<'_>, - ) -> Flow { - event.duration -= 1; - if self.id == 1 { - Flow::Cancel - } else { - Flow::Continue - } - } - fn loot_table_load(&self, event: &mut LootTableLoadEvent<'_>) { - event.table.as_array_mut().unwrap().push(self.id.into()); - } - fn modify_default_components(&self, event: &mut ModifyDefaultComponentsEvent<'_>) { - let value = event - .components - .entry("order") - .or_insert_with(|| serde_json::json!([])); - value.as_array_mut().unwrap().push(self.id.into()); - } - fn level_tick_pre(&self, _: &mut dyn Host, _: &mut LevelTickPreEvent<'_>) { - self.calls.lock().unwrap().push(self.id); - } - fn tooltip(&self, _: &dyn Host, event: &mut TooltipEvent<'_>) { - event.lines.push((Text, self.id)); - } - fn zoom(&self, _: &dyn Host, _: &mut ZoomEvent) -> Option { - self.calls.lock().unwrap().push(self.id); - Some(NativeZoom(self.id)) - } - fn should_apply_effect_tick_this_tick( - &self, - _: &dyn Host, - event: &mut ShouldApplyEffectTickThisTickEvent<'_>, - ) { - event.apply = !event.apply; - self.calls.lock().unwrap().push(self.id); - } - fn living_incoming_damage(&self, event: &mut LivingIncomingDamageEvent<'_>) -> Flow { - event.amount = event.amount * 2.0 + self.id as f32; - if self.id == 2 { - Flow::Cancel - } else { - Flow::Continue - } - } - fn use_item_on(&self, _: &mut dyn Host, _: &mut UseItemOnEvent<'_>) -> Flow { - self.calls.lock().unwrap().push(self.id); - if self.id == 1 { - Flow::Cancel - } else { - Flow::Continue - } - } - } - #[test] - fn block_entity_submit_dispatches_in_order_with_shared_draw() { - let calls = Arc::new(Mutex::new(Vec::new())); - let mods: Vec> = vec![ - Box::new(Mod { - id: 1, - calls: calls.clone(), - }), - Box::new(Mod { id: 2, calls }), - ]; - let mut draw = BlockEntityDraw(Vec::new()); - dispatch_block_entity_submit( - &TestHost, - &mods, - [1, 2, 3], - "example:machine", - BlockStateId, - &ModBlockEntity, - &mut draw, - ); - assert_eq!(draw.0, [1, 2]); - } - - #[test] - fn slot_rules_mutate_and_item_tick_can_cancel() { - let mods: Vec> = vec![Box::new(Mod { - id: 1, - calls: Arc::new(Mutex::new(Vec::new())), - })]; - let (mut may_place, mut max_stack) = (true, 64); - dispatch_menu_slot_rules( - &mods, - "test:menu", - 0, - &ItemStack, - &mut may_place, - &mut max_stack, - ); - assert_eq!((may_place, max_stack), (false, 1)); - let (mut duration, mut canceled) = (10, false); - dispatch_living_entity_use_item_tick( - &mut TestHost, - &mods, - &ItemStack, - None, - Hand, - &mut duration, - &mut canceled, - ); - assert_eq!((duration, canceled), (9, true)); - } - - #[test] - fn item_finish_can_clear_vanilla_result() { - let mods: Vec> = vec![Box::new(Mod { - id: 1, - calls: Arc::new(Mutex::new(Vec::new())), - })]; - let mut result = Some(ItemStack); - dispatch_living_entity_use_item_finish( - &mut TestHost, - &mods, - &ItemStack, - None, - Hand, - 0, - &mut result, - ); - assert!(result.is_none()); - } - - #[test] - fn fragment_cancel_stops_later_mods() { - let calls = Arc::new(Mutex::new(Vec::new())); - let mods: Vec> = vec![ - Box::new(Mod { - id: 1, - calls: calls.clone(), - }), - Box::new(Mod { - id: 2, - calls: calls.clone(), - }), - ]; - let mut canceled = false; - dispatch_mouse_button_pre(&mut TestHost, &mods, 0, true, 0, &mut canceled); - assert!(canceled); - assert_eq!(*calls.lock().unwrap(), [1]); - // Each call is entered under the hook's name; the cancelled one's is the last. - let entered = ENTERED.with(|entered| entered.take()); - assert_eq!(entered, [(0, "mouse_button_pre")]); - } - - #[test] - fn fragment_hooks_generate_and_duplicates_fail() { - let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join(".."); - let catalog: serde_json::Value = - serde_json::from_str(&std::fs::read_to_string(root.join("native-hooks.json")).unwrap()) - .unwrap(); - let hooks = super::catalog_hooks(&root, &catalog).unwrap(); - let hook = hooks - .iter() - .find(|h| h["name"] == "recipes_received") - .unwrap(); - assert_eq!(hook["mod_api"]["contract"], "listen"); - let generated = - std::fs::read_to_string(root.join("crates/horizon-client/src/mod_api/generated.rs")) - .unwrap(); - assert!(generated.contains("fn recipes_received(")); - - let dir = root - .join("target") - .join(format!("mod-api-fragments-{}", std::process::id())); - let fragments = dir.join("tools/native-hook-fragments"); - std::fs::create_dir_all(&fragments).unwrap(); - std::fs::write(fragments.join("empty.json"), r#"{"hooks":[]}"#).unwrap(); - std::fs::write( - fragments.join("duplicate.json"), - r#"{"hooks":[{"name":"same"}]}"#, - ) - .unwrap(); - let catalog = serde_json::json!({"hooks":[{"name":"same"}]}); - assert_eq!( - super::catalog_hooks(&dir, &catalog).unwrap_err(), - "duplicate hook same" - ); - std::fs::remove_file(fragments.join("duplicate.json")).unwrap(); - assert_eq!(super::catalog_hooks(&dir, &catalog).unwrap().len(), 1); - std::fs::remove_dir_all(dir).unwrap(); - } - - #[test] - fn handle_methods_call_the_host_and_borrow_it_as_the_helper_borrows_game() { - let read: syn::ItemFn = syn::parse_quote! { - /// Where it is. - fn native_entity_position(&self, entity: EntityRef, mut scale: f64) -> Option<[f64; 3]> { todo!() } - }; - let (handle, code) = super::handle_method(&read, "entity_position", "entity").unwrap(); - assert_eq!(handle, "EntityRef"); - assert!(code.starts_with("/// Where it is.\n")); - assert!(code.contains("pub fn position (self , host : & dyn Host , scale : f64)")); - assert!(code.contains("host.entity_position(self, scale)")); - let write: syn::ItemFn = syn::parse_quote! { - fn native_level_set(&mut self, level: LevelRef) {} - }; - let (handle, code) = super::handle_method(&write, "level_set", "level").unwrap(); - assert_eq!(handle, "LevelRef"); - assert!(code.contains("host : & mut dyn Host")); - for (method, name, receiver, error) in [ - ( - syn::parse_quote! { fn native_entity_x(&self, entity: EntityRef) {} }, - "entity_x", - "chunk", - "unknown receiver", - ), - ( - syn::parse_quote! { fn native_mob_x(&self, entity: EntityRef) {} }, - "mob_x", - "entity", - "starts with entity_", - ), - ( - syn::parse_quote! { fn native_entity_type(&self, entity: EntityRef) {} }, - "entity_type", - "entity", - "not a method name", - ), - ( - syn::parse_quote! { fn native_entity_x(entity: EntityRef) {} }, - "entity_x", - "entity", - "borrows Game", - ), - ( - syn::parse_quote! { fn native_entity_x(&self, level: LevelRef) {} }, - "entity_x", - "entity", - "takes a EntityRef first", - ), - ] { - let method: syn::ItemFn = method; - assert!( - super::handle_method(&method, name, receiver) - .unwrap_err() - .contains(error), - "{name}" - ); - } - } - - #[test] - fn host_mapping_preserves_access_and_docs_and_rejects_unavailable_types() { - let exports = super::exported_types( - "pub use external::{Visible, Original as Renamed}; pub struct Defined; struct Hidden;", - "pub struct Generated;", - ) - .unwrap(); - for ty in ["Visible", "Renamed", "Defined", "Generated"] { - let method: syn::ItemFn = - syn::parse_str(&format!("fn native_test(&self, value: {ty}) {{ }}")).unwrap(); - assert!(super::host_method(&method, "test", &exports).is_ok()); - } - let foreign: syn::ItemFn = - syn::parse_quote! { fn native_foreign(&self, value: elsewhere::Visible) {} }; - assert!(super::host_method(&foreign, "foreign", &exports).is_err()); - let hidden_trait: syn::ItemFn = - syn::parse_quote! { fn native_hidden(&self, value: &dyn Hidden) {} }; - assert!(super::host_method(&hidden_trait, "hidden", &exports).is_err()); - let read: syn::ItemFn = syn::parse_quote! { - /// Read a value exactly. - /// - /// Keep the blank line too. - fn native_read(&self, pos: [i32; 3]) -> String { todo!() } - }; - let (declaration, implementation) = super::host_method(&read, "read", &exports).unwrap(); - assert!( - declaration.contains("/// Read a value exactly.\n///\n/// Keep the blank line too.") - ); - assert!(declaration.contains("& self")); - assert!(implementation.contains("self.native_read(pos)")); - let dir: syn::ItemFn = syn::parse_quote! { fn native_dir() -> PathBuf { todo!() } }; - let (declaration, implementation) = super::host_method(&dir, "dir", &exports).unwrap(); - assert!(declaration.contains("& self")); - assert!(implementation.contains("Self::native_dir()")); - let owned: syn::ItemFn = syn::parse_quote! { fn native_owned(self) {} }; - assert!(super::host_method(&owned, "owned", &exports).is_err()); - let write: syn::ItemFn = syn::parse_quote! { - fn native_write(&mut self, value: i32) { } - }; - let (declaration, _) = super::host_method(&write, "write", &exports).unwrap(); - assert!(declaration.contains("& mut self")); - let private: syn::ItemFn = syn::parse_quote! { - fn native_private(&self, value: BinOnly) { } - }; - assert!( - super::host_method(&private, "private", &exports) - .unwrap_err() - .contains("BinOnly") - ); - let generic: syn::ItemFn = syn::parse_quote! { - fn native_generic(&self, value: T) { } - }; - assert!( - super::host_method(&generic, "generic", &exports) - .unwrap_err() - .contains("object-safe") - ); - let callback: syn::ItemFn = syn::parse_quote! { - fn native_callback(&self, callback: impl FnOnce()) { } - }; - assert!( - super::host_method(&callback, "callback", &exports) - .unwrap_err() - .contains("object-safe") - ); - let asynchronous: syn::ItemFn = syn::parse_quote! { - async fn native_async(&self) { } - }; - assert!( - super::host_method(&asynchronous, "async", &exports) - .unwrap_err() - .contains("object-safe") - ); - } - - #[test] - fn lookup_rejects_missing_and_duplicate_functions() { - let first: syn::ItemFn = syn::parse_quote! { fn native_example() {} }; - let other: syn::ItemFn = syn::parse_quote! { fn unrelated() {} }; - assert!(super::one_function([&other].into_iter(), "native_example").is_err()); - assert!(super::one_function([&first, &other].into_iter(), "native_example").is_ok()); - assert!(super::one_function([&first, &first].into_iter(), "native_example").is_err()); - } - - #[test] - fn placement_replacement_can_refuse_and_stops_later_mods() { - struct Level; - impl LevelHost for Level {} - struct Refuse(Arc>>, u32); - impl Hooks for Refuse { - fn state_for_placement( - &self, - _: &dyn LevelHost, - _: &mut StateForPlacementEvent<'_>, - ) -> Option> { - self.0.lock().unwrap().push(self.1); - Some(None) - } - } - let calls = Arc::new(Mutex::new(Vec::new())); - let mods: Vec> = vec![ - Box::new(Refuse(calls.clone(), 1)), - Box::new(Refuse(calls.clone(), 2)), - ]; - let mut answer = None; - dispatch_state_for_placement( - &Level, - &mods, - LevelDimension, - BlockStateId, - &PlacementContext, - FluidState, - &mut answer, - ); - assert_eq!(answer, Some(None)); - assert_eq!(*calls.lock().unwrap(), vec![1]); - dispatch_state_for_placement( - &Level, - &mods, - LevelDimension, - BlockStateId, - &PlacementContext, - FluidState, - &mut answer, - ); - assert_eq!(*calls.lock().unwrap(), vec![1]); - } - - #[test] - fn generated_locals_do_not_shadow_hook_arguments() { - let mut used = ["mods", "mods_", "mod_hook", "replacement", "host"] - .into_iter() - .map(str::to_owned) - .collect(); - assert_eq!(super::fresh_name("mods", &mut used), "mods__"); - assert_eq!(super::fresh_name("mod_hook", &mut used), "mod_hook_"); - assert_eq!(super::fresh_name("replacement", &mut used), "replacement_"); - assert_eq!(super::fresh_name("host", &mut used), "host_"); - } - - #[test] - fn level_hosts_match_receiver_mutability() { - assert_eq!( - super::host_for(true, Some(false), false), - Some(("level", "&dyn LevelHost".into())) - ); - assert_eq!( - super::host_for(true, Some(true), false), - Some(("level", "&mut dyn LevelRandomHost".into())) - ); - assert_eq!( - super::host_for(true, Some(false), true), - Some(("level", "&mut dyn LevelTicksHost".into())) - ); - assert_eq!( - super::host_for(true, Some(true), true), - Some(("level", "&mut dyn LevelWriteHost".into())) - ); - assert_eq!(super::host_for(true, None, false), None); - assert_eq!( - super::host_for(false, Some(true), false), - Some(("host", "&mut dyn Host".into())) - ); - } - - #[test] - fn only_a_game_hooks_placeholder_body_is_filled() { - let fill = |body: &str, in_game: bool| { - let method: syn::ItemFn = syn::parse_str(&format!( - "fn native_h(&mut self, player: u128, cancel: &mut bool) {{ {body} }}" - )) - .unwrap(); - super::needs_fill(&method, "dispatch_h", in_game) - }; - assert_eq!(fill("let _ = (player, cancel);", true), Ok(true)); - assert_eq!( - fill( - "horizon_client::mod_api::dispatch_h(self, mods(), player, cancel);", - true - ), - Ok(false) - ); - for (body, in_game) in [ - ("let _ = (player, cancel);", false), - ("let _ = (cancel, player);", true), - ("let _ = player;", true), - ("let _ = (player, cancel); *cancel = true;", true), - ("", true), - ] { - assert!( - fill(body, in_game) - .unwrap_err() - .starts_with("missing dispatch_h call in native_h: "), - "{body}" - ); - } - let one: syn::ItemFn = - syn::parse_quote! { fn native_one(&self, from: &str) { let _ = from; } }; - assert_eq!(super::needs_fill(&one, "dispatch_one", true), Ok(true)); - let source = "impl Game<'_> {\n fn native_other(&self, a: u32) {\n let _ = a;\n }\n fn native_h(&mut self, player: u128, name: &str, cancel: &mut bool) {\n let _ = (player, name, cancel);\n }\n}\n"; - let filled = super::filled_hooks( - source, - &[super::Fill { - native: "native_h".into(), - call: "mod_api::dispatch_h(self, mod_api::mods(), player, name, cancel);".into(), - }], - ) - .unwrap(); - assert_eq!( - String::from_utf8(filled).unwrap(), - "impl Game<'_> {\n fn native_other(&self, a: u32) {\n let _ = a;\n }\n fn native_h(&mut self, player: u128, name: &str, cancel: &mut bool) {\n mod_api::dispatch_h(self, mod_api::mods(), player, name, cancel);\n }\n}\n" - ); - } - - /// `item_damaged` taken back to how a new hook arrives (its body the - /// placeholder) in a copy of the sources: the check reports it, any other - /// body is left alone, a missing `mod_api` entry fails by name, and the - /// generator brings back the shipped file byte for byte with the generated - /// code unchanged. - #[test] - fn new_hook_placeholder_is_reported_then_filled() { - fn copy(from: &std::path::Path, to: &std::path::Path) { - if from.is_dir() { - for entry in std::fs::read_dir(from).unwrap() { - let entry = entry.unwrap(); - copy(&entry.path(), &to.join(entry.file_name())); - } - } else { - std::fs::create_dir_all(to.parent().unwrap()).unwrap(); - std::fs::copy(from, to).unwrap(); - } - } - let real = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join(".."); - let root = real - .join("target") - .join(format!("mod-api-fill-{}", std::process::id())); - let catalog: Value = - serde_json::from_str(&std::fs::read_to_string(real.join("native-hooks.json")).unwrap()) - .unwrap(); - let mut inputs: Vec = [ - "native-hooks.json", - "tools/native-hook-fragments", - "crates/horizon-client/src/game", - "crates/horizon-client/src/mod_api.rs", - "crates/horizon-client/src/mod_api", - "crates/horizon-client-render/src/gfx/native_render", - ] - .map(str::to_owned) - .into(); - for hook in super::catalog_hooks(&real, &catalog).unwrap() { - if let Some((krate, path)) = - super::free_function_source(hook["target"].as_str().unwrap()) - { - let (module, _) = path.rsplit_once("::").unwrap(); - inputs.push(format!( - "crates/{krate}/src/{}.rs", - module.replace("::", "/") - )); - } - } - for input in &inputs { - copy(&real.join(input), &root.join(input)); - } - let hooks = root.join("crates/horizon-client/src/game/native_hooks.rs"); - let shipped = std::fs::read_to_string(&hooks).unwrap(); - let call = "mod_api::dispatch_item_damaged(self, mod_api::mods(), player, name, item, amount, cancel);"; - assert_eq!(shipped.matches(call).count(), 1); - let unfinished = shipped.replace(call, "let _ = (player, name, item, amount);"); - std::fs::write(&hooks, &unfinished).unwrap(); - assert!( - super::generate(&root, false) - .unwrap_err() - .starts_with("missing dispatch_item_damaged call in native_item_damaged: ") - ); - assert_eq!(std::fs::read_to_string(&hooks).unwrap(), unfinished); - let placeholder = shipped.replace(call, "let _ = (player, name, item, amount, cancel);"); - std::fs::write(&hooks, &placeholder).unwrap(); - assert_eq!( - super::generate(&root, true).unwrap_err(), - "native_item_damaged: the let _ = (…); placeholder needs its dispatch call; run cargo run -p xtask -- mod-api" - ); - assert_eq!(std::fs::read_to_string(&hooks).unwrap(), placeholder); - // A stamped hook lives in the catalog, not in a fragment. - let catalog_path = root.join("native-hooks.json"); - let shipped_catalog = std::fs::read_to_string(&catalog_path).unwrap(); - let mut json: Value = serde_json::from_str(&shipped_catalog).unwrap(); - let hook = json["hooks"] - .as_array_mut() - .unwrap() - .iter_mut() - .find(|hook| hook["name"] == "item_damaged") - .unwrap(); - hook.as_object_mut().unwrap().remove("mod_api").unwrap(); - std::fs::write(&catalog_path, json.to_string()).unwrap(); - assert_eq!( - super::generate(&root, false).unwrap_err(), - "hook item_damaged has no mod_api entry: add one" - ); - std::fs::write(&catalog_path, shipped_catalog).unwrap(); - super::generate(&root, false).unwrap(); - // The generator writes LF; a Windows checkout (core.autocrlf) has CRLF. - let lf = |s: &str| s.replace("\r\n", "\n"); - assert_eq!(lf(&std::fs::read_to_string(&hooks).unwrap()), lf(&shipped)); - for generated in [ - "crates/horizon-client/src/mod_api/generated.rs", - "crates/horizon-client-render/src/gfx/native_render/generated.rs", - ] { - assert_eq!( - std::fs::read(root.join(generated)).unwrap(), - std::fs::read(real.join(generated)).unwrap() - ); - } - super::generate(&root, true).unwrap(); - std::fs::remove_dir_all(root).unwrap(); - } - - #[test] - fn event_fields_name_their_lifetimes() { - for (ty, field, scalar, a, b) in [ - ( - "& mut WorldDrawn < '_ >", - "& 'a mut WorldDrawn < 'b >", - false, - true, - true, - ), - ("& str", "& 'a str", false, true, false), - ( - "& mut Vec < (& 'static str , u32) >", - "& 'a mut Vec < (& 'static str , u32) >", - false, - true, - false, - ), - ( - "Option < & ItemStack >", - "Option < & 'a ItemStack >", - false, - true, - false, - ), - ("u32", "u32", false, false, false), - ("& mut f32", "f32", true, false, false), - ( - "& mut Option < bool >", - "& 'a mut Option < bool >", - false, - true, - false, - ), - ] { - let mut lifetimes = super::EventLifetimes::default(); - let (field_type, is_scalar) = lifetimes.field(&syn::parse_str(ty).unwrap()); - assert_eq!(field_type.to_token_stream().to_string(), field); - assert_eq!((is_scalar, lifetimes.a, lifetimes.b), (scalar, a, b)); - } - } - - #[test] - fn generated_output_is_current() { - super::run(true).unwrap(); - } - - #[test] - fn library_crate_targets_resolve_to_their_source() { - for target in [ - "horizon_client_lib::menu::native_menu_slot_rules", - "horizon_client::menu::native_menu_slot_rules", - ] { - assert_eq!( - super::free_function_source(target), - Some(("horizon-client", "menu::native_menu_slot_rules".to_owned())) - ); - } - assert_eq!( - super::free_function_source("horizon_client_lib::crafting::native_ingredient"), - Some(("horizon-client", "crafting::native_ingredient".to_owned())) - ); - assert_eq!( - super::free_function_source( - "horizon_client::game::native_hooks::Game<'_>::native_zoom" - ), - None - ); - } - - #[test] - fn contracted_hook_requires_dispatch_call() { - let missing: syn::ImplItemFn = syn::parse_quote! { fn native_zoom(&self) { unrelated(); } }; - let present: syn::ImplItemFn = syn::parse_quote! { fn native_zoom(&self) { horizon_client::mod_api::dispatch_zoom(); } }; - assert!(!super::has_call(&missing.block, "dispatch_zoom")); - assert!(super::has_call(&present.block, "dispatch_zoom")); - } - - #[test] - fn dispatch_contracts() { - let calls = Arc::new(Mutex::new(Vec::new())); - let mods: Vec> = (1..=2) - .map(|id| { - Box::new(Mod { - id, - calls: calls.clone(), - }) as Box - }) - .collect(); - let mut table = serde_json::json!([]); - dispatch_loot_table_load(&mods, "table", &mut table); - assert_eq!(table, serde_json::json!([1, 2])); - let mut components = Map::new(); - dispatch_modify_default_components(&mods, "item", &mut components); - assert_eq!(components["order"], serde_json::json!([1, 2])); - let mut host = TestHost; - dispatch_level_tick_pre(&mut host, &mods, "overworld"); - assert_eq!(*calls.lock().unwrap(), [1, 2]); - calls.lock().unwrap().clear(); - let mut zoom = None; - dispatch_zoom(&host, &mods, &mut zoom); - assert_eq!(zoom, Some(NativeZoom(1))); - assert_eq!(*calls.lock().unwrap(), [1]); - calls.lock().unwrap().clear(); - dispatch_zoom(&host, &mods, &mut zoom); - assert!(calls.lock().unwrap().is_empty()); - let mut lines = Vec::new(); - dispatch_tooltip(&host, &mods, &ItemStack, &mut lines); - assert_eq!(lines.iter().map(|line| line.1).collect::>(), [1, 2]); - let mut apply = false; - calls.lock().unwrap().clear(); - dispatch_should_apply_effect_tick_this_tick(&host, &mods, "effect", 0, 0, &mut apply); - assert!(!apply); - assert_eq!(*calls.lock().unwrap(), [1, 2]); - let mut amount = 1.0; - let mut canceled = false; - dispatch_living_incoming_damage( - &mods, - 0, - "kind", - "damage", - 0, - 0, - &mut amount, - &mut canceled, - ); - // Each mod sees the one before: (1 × 2 + 1) × 2 + 2, kept though mod 2 cancels. - assert_eq!(amount, 8.0); - assert!(canceled); - let chained: Vec> = [1, 3] - .map(|id| { - Box::new(Mod { - id, - calls: calls.clone(), - }) as Box - }) - .into(); - let mut amount = 1.0; - let mut canceled = false; - dispatch_living_incoming_damage( - &chained, - 0, - "kind", - "damage", - 0, - 0, - &mut amount, - &mut canceled, - ); - assert_eq!(amount, 9.0); - assert!(!canceled); - calls.lock().unwrap().clear(); - let mut claimed = false; - dispatch_use_item_on(&mut host, &mods, &ItemStack, [0; 3], [0; 3], &mut claimed); - assert!(claimed); - assert_eq!(*calls.lock().unwrap(), [1]); - calls.lock().unwrap().clear(); - dispatch_use_item_on(&mut host, &mods, &ItemStack, [0; 3], [0; 3], &mut claimed); - assert!(calls.lock().unwrap().is_empty()); - } -} +#[path = "mod_api_tests.rs"] +mod dispatch_tests; diff --git a/xtask/src/mod_api_tests.rs b/xtask/src/mod_api_tests.rs new file mode 100644 index 0000000000..25f6f57bc0 --- /dev/null +++ b/xtask/src/mod_api_tests.rs @@ -0,0 +1,439 @@ +mod tests { + use std::sync::{Arc, Mutex}; + use std::time::Duration; + + struct ItemStack; + struct BlockEntityDraw(Vec); + #[allow(dead_code)] + mod stubs { + #[derive(Clone, Copy, Debug, PartialEq)] + pub(super) struct BlockStateId; + #[derive(Clone, Copy)] + pub(super) struct FluidState; + pub(super) struct PlacementContext; + #[derive(Clone, Copy)] + pub(super) struct BlockPos; + #[derive(Clone, Copy)] + pub(super) struct BlockId; + #[derive(Clone, Copy)] + pub(super) struct Direction; + #[derive(Clone, Copy)] + pub(super) struct LevelDimension; + pub(super) struct Ticks; + pub(super) trait LevelHost {} + pub(super) trait LevelTicksHost: LevelHost {} + pub(super) trait LevelRandomHost: LevelHost {} + pub(super) trait LevelWriteHost: LevelTicksHost + LevelRandomHost {} + pub(super) struct Compound; + pub(super) type NativeDispensed = (Option, i32); + pub(super) struct NativeLootDrops<'a>(pub &'a ()); + pub(super) struct NativeScreenView<'a>(pub &'a ()); + pub(super) struct Corners; + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] + pub(super) struct ItemEnergyBuffer; + pub(super) struct Suggestion; + pub(super) struct HudBatch; + pub(super) struct BiomeColors; + pub(super) struct UseAnimation; + #[derive(Clone, Copy)] + pub(super) struct Hand; + pub(super) struct NativeMobRender<'a>(pub &'a ()); + pub(super) struct NativeShake; + pub(super) struct Music; + pub(super) struct MusicLayer; + pub(super) struct NativeBlockRim; + pub(super) struct NativeColoredLight; + pub(super) struct NativeMinimap; + pub(super) struct NativeMod; + pub(super) struct NativePanelEvent; + pub(super) struct NativeScreen; + pub(super) struct ModBlockEntity; + pub(super) struct ModContainer; + pub(super) struct MerchantOffer; + pub(super) struct ModFluidTanks; + pub(super) struct ModEnergyBuffer; + pub(super) struct LootStack; + pub(super) struct NativeLootContext<'a>(pub &'a ()); + pub(super) struct NativeDataMapType; + pub(super) struct RecipeBookCategory; + pub(super) struct NativeMapDecoration; + pub(super) struct NativeMapInfo<'a>(pub &'a ()); + pub(super) struct NativePlayerRender<'a>(pub &'a ()); + pub(super) struct NativeCommand; + pub(super) struct NativeLifecycleContext; + pub(super) struct NativeServerPlayer; + #[derive(Clone, Copy)] + pub(super) struct ServerTickPhase; + #[derive(Clone, Copy)] + pub(super) struct WorldPhase; + } + use serde_json::{Map, Value}; + use stubs::*; + struct Text; + #[derive(Debug, PartialEq)] + struct NativeZoom(u32); + enum Flow { + Continue, + Cancel, + } + trait Host {} + trait RenderHooks {} + struct TestHost; + impl Host for TestHost {} + mod winit { + pub mod event { + pub struct KeyEvent; + } + } + #[allow(dead_code)] + mod generated { + mod horizon_mods { + pub mod content { + pub struct Block; + pub struct Item; + } + } + mod horizon_client { + pub mod camera { + pub struct CameraEntity; + pub struct FovPlayer; + } + pub mod menu { + pub struct AnvilCraft; + } + pub mod fog { + #[derive(Clone, Copy)] + pub struct FogType; + pub struct FogData; + } + pub mod first_person { + #[derive(Clone, Copy)] + pub struct Hand; + } + pub mod hud_layout { + pub struct HealthInputs; + pub struct GuiDraw; + } + } + include!(concat!(env!("OUT_DIR"), "/mod_api_generated.rs")); + } + use generated::*; + thread_local! { + /// The hooks each dispatch entered, a mod at a time. + static ENTERED: std::cell::RefCell> = const { std::cell::RefCell::new(Vec::new()) }; + } + fn entered<'a>( + mods: &'a [Box], + hook: &'static str, + ) -> impl Iterator> { + mods.iter().enumerate().map(move |(index, hooks)| { + ENTERED.with(|entered| entered.borrow_mut().push((index, hook))); + hooks + }) + } + struct Mod { + id: u32, + calls: Arc>>, + } + impl Hooks for Mod { + fn block_entity_submit(&self, _: &dyn Host, event: &mut BlockEntitySubmitEvent<'_>) { + assert_eq!(event.pos, [1, 2, 3]); + assert_eq!(event.block, "example:machine"); + event.draw.0.push(self.id); + } + fn living_entity_use_item_finish( + &self, + _: &mut dyn Host, + event: &mut LivingEntityUseItemFinishEvent<'_>, + ) { + *event.result = None; + } + fn mouse_button_pre(&self, _: &mut dyn Host, _: &mut MouseButtonPreEvent) -> Flow { + self.calls.lock().unwrap().push(self.id); + if self.id == 1 { + Flow::Cancel + } else { + Flow::Continue + } + } + fn menu_slot_rules(&self, event: &mut MenuSlotRulesEvent<'_>) { + event.may_place = false; + event.max_stack = self.id; + } + fn living_entity_use_item_tick( + &self, + _: &mut dyn Host, + event: &mut LivingEntityUseItemTickEvent<'_>, + ) -> Flow { + event.duration -= 1; + if self.id == 1 { + Flow::Cancel + } else { + Flow::Continue + } + } + fn loot_table_load(&self, event: &mut LootTableLoadEvent<'_>) { + event.table.as_array_mut().unwrap().push(self.id.into()); + } + fn modify_default_components(&self, event: &mut ModifyDefaultComponentsEvent<'_>) { + let value = event + .components + .entry("order") + .or_insert_with(|| serde_json::json!([])); + value.as_array_mut().unwrap().push(self.id.into()); + } + fn level_tick_pre(&self, _: &mut dyn Host, _: &mut LevelTickPreEvent<'_>) { + self.calls.lock().unwrap().push(self.id); + } + fn tooltip(&self, _: &dyn Host, event: &mut TooltipEvent<'_>) { + event.lines.push((Text, self.id)); + } + fn zoom(&self, _: &dyn Host, _: &mut ZoomEvent) -> Option { + self.calls.lock().unwrap().push(self.id); + Some(NativeZoom(self.id)) + } + fn should_apply_effect_tick_this_tick( + &self, + _: &dyn Host, + event: &mut ShouldApplyEffectTickThisTickEvent<'_>, + ) { + event.apply = !event.apply; + self.calls.lock().unwrap().push(self.id); + } + fn living_incoming_damage(&self, event: &mut LivingIncomingDamageEvent<'_>) -> Flow { + event.amount = event.amount * 2.0 + self.id as f32; + if self.id == 2 { + Flow::Cancel + } else { + Flow::Continue + } + } + fn use_item_on(&self, _: &mut dyn Host, _: &mut UseItemOnEvent<'_>) -> Flow { + self.calls.lock().unwrap().push(self.id); + if self.id == 1 { + Flow::Cancel + } else { + Flow::Continue + } + } + } + #[test] + fn block_entity_submit_dispatches_in_order_with_shared_draw() { + let calls = Arc::new(Mutex::new(Vec::new())); + let mods: Vec> = vec![ + Box::new(Mod { + id: 1, + calls: calls.clone(), + }), + Box::new(Mod { id: 2, calls }), + ]; + let mut draw = BlockEntityDraw(Vec::new()); + dispatch_block_entity_submit( + &TestHost, + &mods, + [1, 2, 3], + "example:machine", + BlockStateId, + &ModBlockEntity, + &mut draw, + ); + assert_eq!(draw.0, [1, 2]); + } + + #[test] + fn slot_rules_mutate_and_item_tick_can_cancel() { + let mods: Vec> = vec![Box::new(Mod { + id: 1, + calls: Arc::new(Mutex::new(Vec::new())), + })]; + let (mut may_place, mut max_stack) = (true, 64); + dispatch_menu_slot_rules( + &mods, + "test:menu", + 0, + &ItemStack, + &mut may_place, + &mut max_stack, + ); + assert_eq!((may_place, max_stack), (false, 1)); + let (mut duration, mut canceled) = (10, false); + dispatch_living_entity_use_item_tick( + &mut TestHost, + &mods, + &ItemStack, + None, + Hand, + &mut duration, + &mut canceled, + ); + assert_eq!((duration, canceled), (9, true)); + } + + #[test] + fn item_finish_can_clear_vanilla_result() { + let mods: Vec> = vec![Box::new(Mod { + id: 1, + calls: Arc::new(Mutex::new(Vec::new())), + })]; + let mut result = Some(ItemStack); + dispatch_living_entity_use_item_finish( + &mut TestHost, + &mods, + &ItemStack, + None, + Hand, + 0, + &mut result, + ); + assert!(result.is_none()); + } + + #[test] + fn fragment_cancel_stops_later_mods() { + let calls = Arc::new(Mutex::new(Vec::new())); + let mods: Vec> = vec![ + Box::new(Mod { + id: 1, + calls: calls.clone(), + }), + Box::new(Mod { + id: 2, + calls: calls.clone(), + }), + ]; + let mut canceled = false; + dispatch_mouse_button_pre(&mut TestHost, &mods, 0, true, 0, &mut canceled); + assert!(canceled); + assert_eq!(*calls.lock().unwrap(), [1]); + // Each call is entered under the hook's name; the cancelled one's is the last. + let entered = ENTERED.with(|entered| entered.take()); + assert_eq!(entered, [(0, "mouse_button_pre")]); + } + + #[test] + fn placement_replacement_can_refuse_and_stops_later_mods() { + struct Level; + impl LevelHost for Level {} + struct Refuse(Arc>>, u32); + impl Hooks for Refuse { + fn state_for_placement( + &self, + _: &dyn LevelHost, + _: &mut StateForPlacementEvent<'_>, + ) -> Option> { + self.0.lock().unwrap().push(self.1); + Some(None) + } + } + let calls = Arc::new(Mutex::new(Vec::new())); + let mods: Vec> = vec![ + Box::new(Refuse(calls.clone(), 1)), + Box::new(Refuse(calls.clone(), 2)), + ]; + let mut answer = None; + dispatch_state_for_placement( + &Level, + &mods, + LevelDimension, + BlockStateId, + &PlacementContext, + FluidState, + &mut answer, + ); + assert_eq!(answer, Some(None)); + assert_eq!(*calls.lock().unwrap(), vec![1]); + dispatch_state_for_placement( + &Level, + &mods, + LevelDimension, + BlockStateId, + &PlacementContext, + FluidState, + &mut answer, + ); + assert_eq!(*calls.lock().unwrap(), vec![1]); + } + + #[test] + fn dispatch_contracts() { + let calls = Arc::new(Mutex::new(Vec::new())); + let mods: Vec> = (1..=2) + .map(|id| { + Box::new(Mod { + id, + calls: calls.clone(), + }) as Box + }) + .collect(); + let mut table = serde_json::json!([]); + dispatch_loot_table_load(&mods, "table", &mut table); + assert_eq!(table, serde_json::json!([1, 2])); + let mut components = Map::new(); + dispatch_modify_default_components(&mods, "item", &mut components); + assert_eq!(components["order"], serde_json::json!([1, 2])); + let mut host = TestHost; + dispatch_level_tick_pre(&mut host, &mods, "overworld"); + assert_eq!(*calls.lock().unwrap(), [1, 2]); + calls.lock().unwrap().clear(); + let mut zoom = None; + dispatch_zoom(&host, &mods, &mut zoom); + assert_eq!(zoom, Some(NativeZoom(1))); + assert_eq!(*calls.lock().unwrap(), [1]); + calls.lock().unwrap().clear(); + dispatch_zoom(&host, &mods, &mut zoom); + assert!(calls.lock().unwrap().is_empty()); + let mut lines = Vec::new(); + dispatch_tooltip(&host, &mods, &ItemStack, &mut lines); + assert_eq!(lines.iter().map(|line| line.1).collect::>(), [1, 2]); + let mut apply = false; + calls.lock().unwrap().clear(); + dispatch_should_apply_effect_tick_this_tick(&host, &mods, "effect", 0, 0, &mut apply); + assert!(!apply); + assert_eq!(*calls.lock().unwrap(), [1, 2]); + let mut amount = 1.0; + let mut canceled = false; + dispatch_living_incoming_damage( + &mods, + 0, + "kind", + "damage", + 0, + 0, + &mut amount, + &mut canceled, + ); + // Each mod sees the one before: (1 × 2 + 1) × 2 + 2, kept though mod 2 cancels. + assert_eq!(amount, 8.0); + assert!(canceled); + let chained: Vec> = [1, 3] + .map(|id| { + Box::new(Mod { + id, + calls: calls.clone(), + }) as Box + }) + .into(); + let mut amount = 1.0; + let mut canceled = false; + dispatch_living_incoming_damage( + &chained, + 0, + "kind", + "damage", + 0, + 0, + &mut amount, + &mut canceled, + ); + assert_eq!(amount, 9.0); + assert!(!canceled); + calls.lock().unwrap().clear(); + let mut claimed = false; + dispatch_use_item_on(&mut host, &mods, &ItemStack, [0; 3], [0; 3], &mut claimed); + assert!(claimed); + assert_eq!(*calls.lock().unwrap(), [1]); + calls.lock().unwrap().clear(); + dispatch_use_item_on(&mut host, &mods, &ItemStack, [0; 3], [0; 3], &mut claimed); + assert!(calls.lock().unwrap().is_empty()); + } +}