feat(plugin-api): add get biome

This commit is contained in:
Alexander Medvedev 2026-05-16 21:22:08 +02:00
parent e2ff17d6f0
commit 95893b7463
8 changed files with 212 additions and 23 deletions

View File

@ -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<String, serde_json::Value> =
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::<Vec<_>>();
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()
}

View File

@ -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 {

View File

@ -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,
}
}

View File

@ -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<entity>;
}

View File

@ -72,32 +72,23 @@ impl HostEntity for PluginHostState {
entity: Resource<Entity>,
) -> wasmtime::Result<entity_types::EntityType> {
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<String> = serde_json::from_str::<
std::collections::BTreeMap<String, serde_json::Value>,
>(&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::<u8, entity_types::EntityType>(index as u8) })
}
async fn get_position(&mut self, entity: Resource<Entity>) -> wasmtime::Result<Position> {

View File

@ -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<PluginHostState>) -> wasmtime::Result<()> {
Plugin::add_to_linker::<_, HasSelf<_>>(linker, |state: &mut PluginHostState| state)?;

View File

@ -403,6 +403,69 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState {
Ok(())
}
async fn get_biome(
&mut self,
world: Resource<World>,
pos: WitBlockPos,
) -> wasmtime::Result<pumpkin::plugin::biomes::Biome> {
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<String> = serde_json::from_str::<
std::collections::BTreeMap<String, serde_json::Value>,
>(&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::<u8, pumpkin::plugin::biomes::Biome>(index as u8) })
}
async fn spawn_entity(
&mut self,
world: Resource<World>,
entity_type: pumpkin::plugin::entity_types::EntityType,
pos: pumpkin::plugin::common::Position,
) -> wasmtime::Result<Resource<pumpkin::plugin::world::Entity>> {
let world_ref = self.get_world_res(&world)?;
let world_provider = world_ref.provider.clone();
let mut names: Vec<String> = serde_json::from_str::<
std::collections::BTreeMap<String, serde_json::Value>,
>(&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<World>,

View File

@ -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,