From 95893b7463ab4807b401414f3fbef77b45e29de2 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Sat, 16 May 2026 21:22:08 +0200 Subject: [PATCH] feat(plugin-api): add get biome --- pumpkin-codegen/src/wit/biome.rs | 37 ++++++++++ pumpkin-codegen/src/wit/mod.rs | 2 + pumpkin-plugin-wit/v0.1/biomes.wit | 71 +++++++++++++++++++ pumpkin-plugin-wit/v0.1/world.wit | 7 ++ .../loader/wasm/wasm_host/wit/v0_1/entity.rs | 37 ++++------ .../loader/wasm/wasm_host/wit/v0_1/mod.rs | 1 + .../loader/wasm/wasm_host/wit/v0_1/world.rs | 63 ++++++++++++++++ pumpkin/src/world/mod.rs | 17 +++++ 8 files changed, 212 insertions(+), 23 deletions(-) create mode 100644 pumpkin-codegen/src/wit/biome.rs create mode 100644 pumpkin-plugin-wit/v0.1/biomes.wit diff --git a/pumpkin-codegen/src/wit/biome.rs b/pumpkin-codegen/src/wit/biome.rs new file mode 100644 index 000000000..77ec2e96b --- /dev/null +++ b/pumpkin-codegen/src/wit/biome.rs @@ -0,0 +1,37 @@ +use semver::Version; +use std::collections::BTreeMap; +use std::fs; +use wit_encoder::{Enum, Interface, Package, PackageName, TypeDef, TypeDefKind}; + +pub fn build() -> String { + let json: BTreeMap = + serde_json::from_str(&fs::read_to_string("../assets/biome.json").unwrap()) + .expect("Failed to parse biome.json"); + + let mut package = Package::new(PackageName::new( + "pumpkin", + "plugin", + Some(Version::new(0, 1, 0)), + )); + let mut interface = Interface::new("biomes"); + + let mut biome_enum = Enum::empty(); + let mut biome_vec = json.keys().collect::>(); + biome_vec.sort(); + + for raw_name in biome_vec { + let name = raw_name + .strip_prefix("minecraft:") + .unwrap_or(raw_name) + .replace('_', "-"); + biome_enum.case(name); + } + + interface.type_def(TypeDef::new( + "biome", + TypeDefKind::Enum(biome_enum), + )); + package.interface(interface); + + package.to_string() +} diff --git a/pumpkin-codegen/src/wit/mod.rs b/pumpkin-codegen/src/wit/mod.rs index ed4b488ae..46aa46d84 100644 --- a/pumpkin-codegen/src/wit/mod.rs +++ b/pumpkin-codegen/src/wit/mod.rs @@ -1,4 +1,5 @@ pub mod bedrock_packet; +pub mod biome; pub mod data_component; pub mod enchantment; pub mod entity_type; @@ -30,6 +31,7 @@ pub fn main() { (bedrock_packet::build, "bedrock-packets.wit"), (data_component::build, "data-components.wit"), (enchantment::build, "enchantments.wit"), + (biome::build, "biomes.wit"), ]; for (build_fn, file) in build_functions { diff --git a/pumpkin-plugin-wit/v0.1/biomes.wit b/pumpkin-plugin-wit/v0.1/biomes.wit new file mode 100644 index 000000000..fb972c9fc --- /dev/null +++ b/pumpkin-plugin-wit/v0.1/biomes.wit @@ -0,0 +1,71 @@ +package pumpkin:plugin@0.1.0; + +interface biomes { + enum biome { + badlands, + bamboo-jungle, + basalt-deltas, + beach, + birch-forest, + cherry-grove, + cold-ocean, + crimson-forest, + dark-forest, + deep-cold-ocean, + deep-dark, + deep-frozen-ocean, + deep-lukewarm-ocean, + deep-ocean, + desert, + dripstone-caves, + end-barrens, + end-highlands, + end-midlands, + eroded-badlands, + flower-forest, + forest, + frozen-ocean, + frozen-peaks, + frozen-river, + grove, + ice-spikes, + jagged-peaks, + jungle, + lukewarm-ocean, + lush-caves, + mangrove-swamp, + meadow, + mushroom-fields, + nether-wastes, + ocean, + old-growth-birch-forest, + old-growth-pine-taiga, + old-growth-spruce-taiga, + pale-garden, + plains, + river, + savanna, + savanna-plateau, + small-end-islands, + snowy-beach, + snowy-plains, + snowy-slopes, + snowy-taiga, + soul-sand-valley, + sparse-jungle, + stony-peaks, + stony-shore, + sunflower-plains, + swamp, + taiga, + the-end, + the-void, + warm-ocean, + warped-forest, + windswept-forest, + windswept-gravelly-hills, + windswept-hills, + windswept-savanna, + wooded-badlands, + } +} diff --git a/pumpkin-plugin-wit/v0.1/world.wit b/pumpkin-plugin-wit/v0.1/world.wit index 256904df6..c9e52d0cd 100644 --- a/pumpkin-plugin-wit/v0.1/world.wit +++ b/pumpkin-plugin-wit/v0.1/world.wit @@ -5,6 +5,7 @@ interface %world { use particles.{particle}; use sounds.{sound}; use entity-types.{entity-type}; + use biomes.{biome}; use uuid.{uuid}; /// Defines how a block reacts when pushed by a piston. @@ -258,6 +259,12 @@ interface %world { /// Sets the block light level at the specified position. set-block-light: func(pos: block-pos, level: u8); + /// Returns the biome at the specified position. + get-biome: func(pos: block-pos) -> biome; + + /// Spawns an entity of the specified type at the given position. + spawn-entity: func(entity-type: entity-type, pos: position) -> entity; + /// Returns a list of all entities in this world. get-entities: func() -> list; } diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/entity.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/entity.rs index 1d90452dd..b3f2d4d05 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/entity.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/entity.rs @@ -72,32 +72,23 @@ impl HostEntity for PluginHostState { entity: Resource, ) -> wasmtime::Result { let entity = entity_from_resource(self, &entity)?; - let name = entity.get_entity().entity_type.resource_name.to_string(); - // Standard WIT enum mapping: kebab-case - let _wit_name = name.replace('_', "-"); - // We need to find the variant in the generated enum. - // Since we don't have a direct mapping function yet, we use debug format or similar if available, - // but for now let's just use the fact that it's an enum. - // Actually, the easiest way is to use the generated `from_name` or similar if we added it, - // but here we are returning it TO the guest. + let original_name = entity.get_entity().entity_type.resource_name; - // Wait, I need to CONSTRUCT the EntityType enum. - // I can't easily do it by string unless I have a mapping. - // But wit-bindgen generates the enum. + let mut names: Vec = serde_json::from_str::< + std::collections::BTreeMap, + >(&std::fs::read_to_string("assets/entities.json")?)? + .keys() + .cloned() + .collect(); + names.sort(); - // Let's assume there is a way to construct it. - // Actually, I can use the fact that it's a simple enum. - // I'll use a hack for now if I don't have a better way, but wait! - // I can just use `serde_json` to parse the string into the enum if it implements Deserialize! - // Or just a match. + let index = names + .iter() + .position(|n| n == original_name) + .ok_or_else(|| wasmtime::Error::msg(format!("Unknown entity type: {original_name}")))?; - // Better: let's see if pumpkin_data has a way. - // For now, I'll just return a placeholder or implement a basic match for the most common ones - // OR better: use the generated code. - - Err(wasmtime::Error::msg( - "get_type implementation pending full enum mapping", - )) + // Safety: The WIT enum is generated from the sorted keys of assets/entities.json. + Ok(unsafe { std::mem::transmute::(index as u8) }) } async fn get_position(&mut self, entity: Resource) -> wasmtime::Result { diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/mod.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/mod.rs index 03b3ce17e..cf7f25107 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/mod.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/mod.rs @@ -41,6 +41,7 @@ impl pumpkin::plugin::java_packets::Host for PluginHostState {} impl pumpkin::plugin::bedrock_packets::Host for PluginHostState {} impl pumpkin::plugin::data_components::Host for PluginHostState {} impl pumpkin::plugin::enchantments::Host for PluginHostState {} +impl pumpkin::plugin::biomes::Host for PluginHostState {} pub fn add_to_linker(linker: &mut Linker) -> wasmtime::Result<()> { Plugin::add_to_linker::<_, HasSelf<_>>(linker, |state: &mut PluginHostState| state)?; diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/world.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/world.rs index 98e4e558d..d059dd35e 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/world.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/world.rs @@ -403,6 +403,69 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState { Ok(()) } + async fn get_biome( + &mut self, + world: Resource, + pos: WitBlockPos, + ) -> wasmtime::Result { + let world_ref = self.get_world_res(&world)?; + let internal_pos = BlockPos::new(pos.x, pos.y, pos.z); + let biome = world_ref.provider.get_biome(&internal_pos); + + let mut names: Vec = serde_json::from_str::< + std::collections::BTreeMap, + >(&std::fs::read_to_string("assets/biome.json")?)? + .keys() + .cloned() + .collect(); + names.sort(); + + let index = names + .iter() + .position(|n| n.strip_prefix("minecraft:").unwrap_or(n) == biome.registry_id) + .ok_or_else(|| wasmtime::Error::msg(format!("Unknown biome: {}", biome.registry_id)))?; + + // Safety: The WIT enum is generated from the sorted keys of assets/biome.json. + Ok(unsafe { std::mem::transmute::(index as u8) }) + } + + async fn spawn_entity( + &mut self, + world: Resource, + entity_type: pumpkin::plugin::entity_types::EntityType, + pos: pumpkin::plugin::common::Position, + ) -> wasmtime::Result> { + let world_ref = self.get_world_res(&world)?; + let world_provider = world_ref.provider.clone(); + + let mut names: Vec = serde_json::from_str::< + std::collections::BTreeMap, + >(&std::fs::read_to_string("assets/entities.json")?)? + .keys() + .cloned() + .collect(); + names.sort(); + + let type_name = names.get(entity_type as usize).ok_or_else(|| { + wasmtime::Error::msg(format!("Invalid entity type index: {}", entity_type as u8)) + })?; + + let internal_type = pumpkin_data::entity::EntityType::from_name(type_name) + .ok_or_else(|| wasmtime::Error::msg(format!("Invalid entity type: {type_name}")))?; + + let internal_pos = pumpkin_util::math::vector3::Vector3::new(pos.0, pos.1, pos.2); + let entity = crate::entity::r#type::from_type( + internal_type, + internal_pos, + &world_provider, + uuid::Uuid::new_v4(), + ); + + world_provider.spawn_entity(entity.clone()).await; + + self.add_entity(entity) + } + async fn get_entities( &mut self, world: Resource, diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index b32ba6727..0e42dc896 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -3444,6 +3444,23 @@ impl World { .set_sky_light_level(&self.level, position, light_level); } + pub fn get_biome(&self, position: &BlockPos) -> &'static Biome { + let chunk_pos = position.chunk_position(); + if let Some(chunk) = self.level.loaded_chunks.get(&chunk_pos) { + let id = chunk + .section + .get_rough_biome_absolute_y( + (position.0.x & 15) as usize, + position.0.y, + (position.0.z & 15) as usize, + ) + .unwrap_or(0); + Biome::from_id(id).unwrap_or(&Biome::PLAINS) + } else { + &Biome::PLAINS + } + } + pub fn schedule_block_tick( &self, block: &Block,