feat: add bedrock forms

This commit is contained in:
Alexander Medvedev 2026-05-15 18:24:28 +02:00
parent f383c88c76
commit 5aafff35e8
86 changed files with 1226 additions and 898 deletions

2
Cargo.lock generated
View File

@ -2965,6 +2965,8 @@ name = "pumpkin-plugin-api"
version = "0.1.0"
dependencies = [
"postcard",
"serde",
"serde_json",
"tracing",
"tracing-serde-structured",
"wit-bindgen 0.57.1",

View File

@ -393,7 +393,7 @@ impl ToTokens for BlockPropertyStruct {
fn to_state_id(&self, block: &Block) -> u16 {
if !Self::handles_block_id(block.id) {
panic!("{} is not a valid block for {}", &block.name, #struct_name);
panic!("{} is not a valid block for {}", block.name, #struct_name);
}
block.states[0].id + self.to_index()
}
@ -401,7 +401,7 @@ impl ToTokens for BlockPropertyStruct {
fn from_state_id(state_id: u16, block: &Block) -> Self {
debug_assert!(
Self::handles_block_id(block.id),
"{} is not a valid block for {}", &block.name, #struct_name
"{} is not a valid block for {}", block.name, #struct_name
);
let min_id = block.states[0].id;
@ -420,7 +420,7 @@ impl ToTokens for BlockPropertyStruct {
fn default(block: &Block) -> Self {
if !Self::handles_block_id(block.id) {
panic!("{} is not a valid block for {}", &block.name, #struct_name);
panic!("{} is not a valid block for {}", block.name, #struct_name);
}
Self::from_state_id(block.default_state.id, block)
}
@ -433,7 +433,7 @@ impl ToTokens for BlockPropertyStruct {
fn from_props(props: &[(&str, &str)], block: &Block) -> Self {
#[cfg(debug_assertions)]
if !matches!(block.id, #(#block_ids)|*) {
panic!("{} is not a valid block for {}", &block.name, #struct_name);
panic!("{} is not a valid block for {}", block.name, #struct_name);
}
let mut block_props = Self::default(block);
for (key, value) in props {

View File

@ -13,9 +13,6 @@ uuid.workspace = true
toml.workspace = true
[features]
# Adds helper to change the config at runtime
test_helper = []
[lints]
workspace = true

File diff suppressed because it is too large Load Diff

View File

@ -907,7 +907,7 @@ impl FluidProperties for WaterLikeFluidProperties {
if !["water", "lava"].contains(&fluid.name) {
panic!(
"{} is not a valid fluid for {}",
&fluid.name, "WaterLikeFluidProperties"
fluid.name, "WaterLikeFluidProperties"
);
}
let prop_index = self.to_index();
@ -921,7 +921,7 @@ impl FluidProperties for WaterLikeFluidProperties {
if !["water", "lava"].contains(&fluid.name) {
panic!(
"{} is not a valid fluid for {}",
&fluid.name, "WaterLikeFluidProperties"
fluid.name, "WaterLikeFluidProperties"
);
}
for (idx, state) in fluid.states.iter().enumerate() {
@ -935,7 +935,7 @@ impl FluidProperties for WaterLikeFluidProperties {
if !["water", "lava"].contains(&fluid.name) {
panic!(
"{} is not a valid fluid for {}",
&fluid.name, "WaterLikeFluidProperties"
fluid.name, "WaterLikeFluidProperties"
);
}
Self::from_state_id(fluid.default_state_index, fluid)
@ -947,7 +947,7 @@ impl FluidProperties for WaterLikeFluidProperties {
if !["water", "lava"].contains(&fluid.name) {
panic!(
"{} is not a valid fluid for {}",
&fluid.name, "WaterLikeFluidProperties"
fluid.name, "WaterLikeFluidProperties"
);
}
let mut fluid_props = Self::default(fluid);
@ -995,7 +995,7 @@ impl FluidProperties for FlowingWaterLikeFluidProperties {
if !["flowing_water", "flowing_lava"].contains(&fluid.name) {
panic!(
"{} is not a valid fluid for {}",
&fluid.name, "FlowingWaterLikeFluidProperties"
fluid.name, "FlowingWaterLikeFluidProperties"
);
}
let prop_index = self.to_index();
@ -1009,7 +1009,7 @@ impl FluidProperties for FlowingWaterLikeFluidProperties {
if !["flowing_water", "flowing_lava"].contains(&fluid.name) {
panic!(
"{} is not a valid fluid for {}",
&fluid.name, "FlowingWaterLikeFluidProperties"
fluid.name, "FlowingWaterLikeFluidProperties"
);
}
for (idx, state) in fluid.states.iter().enumerate() {
@ -1023,7 +1023,7 @@ impl FluidProperties for FlowingWaterLikeFluidProperties {
if !["flowing_water", "flowing_lava"].contains(&fluid.name) {
panic!(
"{} is not a valid fluid for {}",
&fluid.name, "FlowingWaterLikeFluidProperties"
fluid.name, "FlowingWaterLikeFluidProperties"
);
}
Self::from_state_id(fluid.default_state_index, fluid)
@ -1038,7 +1038,7 @@ impl FluidProperties for FlowingWaterLikeFluidProperties {
if !["flowing_water", "flowing_lava"].contains(&fluid.name) {
panic!(
"{} is not a valid fluid for {}",
&fluid.name, "FlowingWaterLikeFluidProperties"
fluid.name, "FlowingWaterLikeFluidProperties"
);
}
let mut fluid_props = Self::default(fluid);

View File

@ -10,6 +10,8 @@ wit-bindgen = { workspace = true }
tracing = { workspace = true }
postcard = { workspace = true }
tracing-serde-structured = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
[package.metadata.component]
target = { path = "../pumpkin-plugin-wit" }

View File

@ -0,0 +1,21 @@
use crate::wit::pumpkin::plugin::event::{BedrockFormResponseEventData, Event, EventType};
use super::super::FromIntoEvent;
/// An event that occurs when a player responds to a Bedrock custom form.
pub struct BedrockFormResponseEvent;
impl FromIntoEvent for BedrockFormResponseEvent {
const EVENT_TYPE: EventType = EventType::BedrockFormResponseEvent;
type Data = BedrockFormResponseEventData;
fn data_from_event(event: Event) -> Self::Data {
match event {
Event::BedrockFormResponseEvent(data) => data,
_ => panic!("unexpected event"),
}
}
fn data_into_event(data: Self::Data) -> Event {
Event::BedrockFormResponseEvent(data)
}
}

View File

@ -1,3 +1,4 @@
pub mod bedrock_form_response;
pub mod changed_main_hand;
pub mod egg_throw;
pub mod exp_change;

View File

@ -0,0 +1,218 @@
use crate::player::Player;
use crate::text::TextComponent;
use crate::wit::pumpkin::plugin::forms::{
self as wit_forms, CustomForm, CustomFormElement, Form, FormImage, ImageType, ModalForm,
SimpleForm, SimpleFormButton,
};
pub struct SimpleFormBuilder {
title: TextComponent,
content: TextComponent,
buttons: Vec<SimpleFormButton>,
}
impl SimpleFormBuilder {
pub fn new(title: impl Into<TextComponent>, content: impl Into<TextComponent>) -> Self {
Self {
title: title.into(),
content: content.into(),
buttons: Vec::new(),
}
}
pub fn button(mut self, text: impl Into<TextComponent>, image: Option<FormImage>) -> Self {
self.buttons.push(SimpleFormButton {
text: text.into(),
image,
});
self
}
pub fn build(self) -> Form {
Form::Simple(SimpleForm {
title: self.title,
content: self.content,
buttons: self.buttons,
})
}
}
pub struct ModalFormBuilder {
title: TextComponent,
content: TextComponent,
button1: TextComponent,
button2: TextComponent,
}
impl ModalFormBuilder {
pub fn new(title: impl Into<TextComponent>, content: impl Into<TextComponent>) -> Self {
Self {
title: title.into(),
content: content.into(),
button1: TextComponent::text("Yes"),
button2: TextComponent::text("No"),
}
}
pub fn button1(mut self, text: impl Into<TextComponent>) -> Self {
self.button1 = text.into();
self
}
pub fn button2(mut self, text: impl Into<TextComponent>) -> Self {
self.button2 = text.into();
self
}
pub fn build(self) -> Form {
Form::Modal(ModalForm {
title: self.title,
content: self.content,
button1: self.button1,
button2: self.button2,
})
}
}
pub struct CustomFormBuilder {
title: TextComponent,
elements: Vec<CustomFormElement>,
}
impl CustomFormBuilder {
pub fn new(title: impl Into<TextComponent>) -> Self {
Self {
title: title.into(),
elements: Vec::new(),
}
}
pub fn label(mut self, text: impl Into<TextComponent>) -> Self {
self.elements.push(CustomFormElement::Label(text.into()));
self
}
pub fn toggle(mut self, text: impl Into<TextComponent>, default: bool) -> Self {
self.elements
.push(CustomFormElement::Toggle((text.into(), default)));
self
}
pub fn slider(
mut self,
text: impl Into<TextComponent>,
min: f32,
max: f32,
step: f32,
default: f32,
) -> Self {
self.elements.push(CustomFormElement::Slider((
text.into(),
min,
max,
step,
default,
)));
self
}
pub fn step_slider(
mut self,
text: impl Into<TextComponent>,
steps: Vec<String>,
default: u32,
) -> Self {
self.elements
.push(CustomFormElement::StepSlider((text.into(), steps, default)));
self
}
pub fn dropdown(
mut self,
text: impl Into<TextComponent>,
options: Vec<String>,
default: u32,
) -> Self {
self.elements
.push(CustomFormElement::Dropdown((text.into(), options, default)));
self
}
pub fn input(
mut self,
text: impl Into<TextComponent>,
placeholder: impl Into<String>,
default: impl Into<String>,
) -> Self {
self.elements.push(CustomFormElement::Input((
text.into(),
placeholder.into(),
default.into(),
)));
self
}
pub fn build(self) -> Form {
Form::Custom(CustomForm {
title: self.title,
elements: self.elements,
})
}
}
pub fn send_form(player: Player, form: Form) -> u32 {
wit_forms::send_form(player, form)
}
pub fn url_image(url: impl Into<String>) -> FormImage {
FormImage {
type_: ImageType::Url,
data: url.into(),
}
}
pub fn path_image(path: impl Into<String>) -> FormImage {
FormImage {
type_: ImageType::Path,
data: path.into(),
}
}
pub enum FormResponse {
Simple(u32),
Modal(bool),
Custom(Vec<serde_json::Value>),
Closed,
}
impl FormResponse {
pub fn parse(data: Option<String>) -> Self {
match data {
None => Self::Closed,
Some(s) => {
if let Ok(val) = serde_json::from_str::<serde_json::Value>(&s) {
if val.is_u64() {
Self::Simple(val.as_u64().unwrap() as u32)
} else if val.is_boolean() {
Self::Modal(val.as_bool().unwrap())
} else if val.is_array() {
Self::Custom(val.as_array().unwrap().clone())
} else {
Self::Closed // Or some error state
}
} else {
// Fallback for some clients that might send raw strings for simple/modal
if s == "true" {
Self::Modal(true)
} else if s == "false" {
Self::Modal(false)
} else if let Ok(idx) = s.parse::<u32>() {
Self::Simple(idx)
} else {
Self::Closed
}
}
}
}
}
}

View File

@ -35,6 +35,7 @@ use crate::{
pub mod commands;
pub mod events;
pub mod forms;
/// Constants for plugin permissions.
///
/// Use these in your `PluginMetadata` to request access to specific host features.

View File

@ -258,6 +258,13 @@ interface event {
can-build: bool,
cancelled: bool
}
record bedrock-form-response-event-data {
player: player,
form-id: u32,
response-data: option<string>,
}
record server-command-event-data {
command: string,
cancelled: bool
@ -321,6 +328,7 @@ interface event {
block-can-build-event,
block-grow-event,
block-place-event,
bedrock-form-response-event,
server-command-event,
spawn-change-event,
server-broadcast-event,
@ -358,6 +366,7 @@ interface event {
block-can-build-event(block-can-build-event-data),
block-grow-event(block-grow-event-data),
block-place-event(block-place-event-data),
bedrock-form-response-event(bedrock-form-response-event-data),
server-command-event(server-command-event-data),
spawn-change-event(spawn-change-event-data),
server-broadcast-event(server-broadcast-event-data),

View File

@ -0,0 +1,54 @@
interface forms {
use player.{player};
use text.{text-component};
enum image-type {
url,
path,
}
record form-image {
%type: image-type,
data: string,
}
record simple-form-button {
text: text-component,
image: option<form-image>,
}
record simple-form {
title: text-component,
content: text-component,
buttons: list<simple-form-button>,
}
record modal-form {
title: text-component,
content: text-component,
button1: text-component,
button2: text-component,
}
variant custom-form-element {
label(text-component),
toggle(tuple<text-component, bool>),
slider(tuple<text-component, f32, f32, f32, f32>), // text, min, max, step, default
step-slider(tuple<text-component, list<string>, u32>), // text, steps, default
dropdown(tuple<text-component, list<string>, u32>), // text, options, default
input(tuple<text-component, string, string>), // text, placeholder, default
}
record custom-form {
title: text-component,
elements: list<custom-form-element>,
}
variant form {
simple(simple-form),
modal(modal-form),
custom(custom-form),
}
send-form: func(player: player, form: form) -> u32;
}

View File

@ -35,6 +35,8 @@ world plugin {
import entity;
/// Provides boss bar creation and management.
import boss-bar;
/// Provides Bedrock custom forms.
import forms;
// --- Plugin-provided exports ---

View File

@ -9,6 +9,7 @@ pub mod handshake;
pub mod inventory_content;
pub mod level_chunk;
pub mod level_event;
pub mod modal_form_request;
pub mod move_player;
pub mod network_chunk_publisher_update;
pub mod network_settings;
@ -38,6 +39,7 @@ pub use handshake::*;
pub use inventory_content::*;
pub use level_chunk::*;
pub use level_event::*;
pub use modal_form_request::*;
pub use move_player::*;
pub use network_chunk_publisher_update::*;
pub use network_settings::*;

View File

@ -0,0 +1,9 @@
use crate::{codec::var_int::VarInt, serial::PacketWrite};
use pumpkin_macros::packet;
#[packet(100)]
#[derive(PacketWrite)]
pub struct CModalFormRequest {
pub form_id: VarInt,
pub form_data: String,
}

View File

@ -5,6 +5,7 @@ pub mod container_close;
pub mod interaction;
pub mod loading_screen;
pub mod login;
pub mod modal_form_response;
pub mod player_action;
pub mod player_auth_input;
pub mod raknet;

View File

@ -0,0 +1,18 @@
use crate::codec::var_int::VarInt;
use crate::serial::PacketRead;
use pumpkin_macros::packet;
use std::io::{Error, Read};
#[packet(101)]
pub struct SModalFormResponse {
pub form_id: VarInt,
pub form_data: Option<String>,
}
impl PacketRead for SModalFormResponse {
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
let form_id = VarInt::read(reader)?;
let form_data = Option::<String>::read(reader)?;
Ok(Self { form_id, form_data })
}
}

View File

@ -110,7 +110,7 @@ impl<'de, T: Deserialize<'de>> Visitor<'de> for IdOrVisitor<T> {
})?);
}
IdOrStateDeserializer::Id(id) => {
debug_assert!(*id == 0);
debug_assert_eq!(*id, 0);
// Get the data
let value = T::deserialize(deserializer)?;
*self = IdOrStateDeserializer::Value(value);

View File

@ -226,7 +226,6 @@ impl PerlinNoiseSampler {
}
/// Data for a single octave in an octave Perlin noise sampler.
#[derive(Clone)]
pub struct SamplerData {
/// The Perlin noise sampler for this octave.
pub sampler: PerlinNoiseSampler,
@ -243,7 +242,6 @@ pub struct SamplerData {
/// Octave noise, also known as fractal noise, combines multiple octaves of Perlin noise
/// with different frequencies and amplitudes to create more complex, natural-looking patterns.
/// Each octave adds finer detail to the overall noise.
#[derive(Clone)]
pub struct OctavePerlinNoiseSampler {
/// The list of samplers for each octave, with their associated parameters.
pub samplers: Box<[SamplerData]>,

View File

@ -216,7 +216,6 @@ impl RandomImpl for Xoroshiro {
/// This struct allows for deterministic creation of multiple independent
/// random generators from a single seed, which is essential for
/// parallelizable world generation.
#[derive(Clone)]
pub struct XoroshiroSplitter {
/// Lower 64 bits of the splitter state.
lo: u64,

View File

@ -79,8 +79,6 @@ harness = false
name = "noise_router"
harness = false
[features]
tokio_taskdump = []
# [lints]
# workspace = true

View File

@ -19,7 +19,6 @@ pub trait BiomeSupplier {
fn biome(&self, x: i32, y: i32, z: i32, noise: &mut MultiNoiseSampler<'_>) -> &'static Biome;
}
#[derive(Clone, Copy)]
pub struct MultiNoiseBiomeSupplier {
source: &'static BiomeTree,
}

View File

@ -5,7 +5,7 @@ pub const fn to_long(float: f32) -> i64 {
(float * 10000f32) as i64
}
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct NoiseValuePoint {
pub temperature: i64,
pub humidity: i64,

View File

@ -586,42 +586,42 @@ impl GenerationSchedule {
}
}
fn save_all_chunk(&self, save_proto_chunk: bool) {
fn save_all_chunk(&mut self, save_proto_chunk: bool) {
let mut chunks = Vec::with_capacity(self.chunk_map.len());
for (pos, holder) in &self.chunk_map {
if let Some(chunk) = &holder.chunk {
match chunk {
Chunk::Level(sync_chunk) => {
if sync_chunk.is_dirty() {
chunks.push((*pos, Chunk::Level(sync_chunk.clone())));
}
}
Chunk::Proto(proto) => {
if save_proto_chunk {
chunks.push((*pos, Chunk::Proto(proto.clone())));
}
}
for (pos, holder) in self.chunk_map.iter_mut() {
if let Some(chunk) = holder.chunk.take() {
let should_save = match &chunk {
Chunk::Level(sync_chunk) => sync_chunk.is_dirty(),
Chunk::Proto(_) => save_proto_chunk,
};
if should_save {
chunks.push((*pos, chunk));
} else {
holder.chunk = Some(chunk);
}
}
}
if chunks.is_empty() {
return;
}
info!(
"Saving {} chunks (collected from {} holders)...",
chunks.len(),
self.chunk_map.len()
);
let mut data = self.io_lock.0.lock().unwrap();
for (pos, _chunk) in &chunks {
for (pos, _) in &chunks {
*data.entry(*pos).or_insert(0) += 1;
}
drop(data);
if let Err(e) = self.io_write.send(chunks) {
error!(
"Failed to send chunks to io write thread during unload (may have shut down): {:?}",
e
);
error!("Failed to send chunks to io write thread: {:?}", e);
}
}

View File

@ -1,4 +1,3 @@
#[derive(Clone)]
pub struct CarvingMask {
min_y: i32,
mask: Vec<u64>,

View File

@ -12,7 +12,6 @@ pub struct SpringFeatureFeature {
pub valid_blocks: BlockWrapper,
}
#[derive(Clone)]
pub enum BlockWrapper {
Single(String),
Multi(Vec<String>),

View File

@ -1,7 +1,6 @@
use pumpkin_data::chunk::DoublePerlinNoiseParameters;
use pumpkin_util::{noise::perlin::OctavePerlinNoiseSampler, random::RandomImpl};
#[derive(Clone)]
pub struct DoublePerlinNoiseSampler {
first_sampler: OctavePerlinNoiseSampler,
second_sampler: OctavePerlinNoiseSampler,

View File

@ -472,7 +472,6 @@ impl FlatCache {
}
}
#[derive(Clone)]
pub struct Cache2D {
pub(crate) input_index: usize,
last_sample_column: u64,

View File

@ -10,7 +10,6 @@ use super::{
IndexToNoisePos, NoiseFunctionComponentRange, StaticIndependentChunkNoiseFunctionComponentImpl,
};
#[derive(Clone)]
pub struct Constant {
value: f64,
}
@ -43,7 +42,6 @@ impl StaticIndependentChunkNoiseFunctionComponentImpl for Constant {
}
}
#[derive(Clone)]
pub struct Linear {
pub(crate) input_index: usize,
min_value: f64,
@ -95,7 +93,6 @@ impl Linear {
}
}
#[derive(Clone)]
pub struct Binary {
pub(crate) input1_index: usize,
pub(crate) input2_index: usize,
@ -275,7 +272,6 @@ impl Binary {
}
}
#[derive(Clone)]
pub struct Unary {
pub(crate) input_index: usize,
min_value: f64,
@ -345,7 +341,6 @@ impl Unary {
}
}
#[derive(Clone)]
pub struct Clamp {
input_index: usize,
data: &'static ClampData,

View File

@ -21,7 +21,6 @@ use super::{
IndexToNoisePos, NoiseFunctionComponentRange, StaticIndependentChunkNoiseFunctionComponentImpl,
};
#[derive(Clone)]
pub struct EndIsland {
sampler: Arc<SimplexNoiseSampler>,
}
@ -170,7 +169,6 @@ impl NoiseFunctionComponentRange for WeirdScaled {
}
}
#[derive(Clone)]
pub struct ClampedYGradient {
data: &'static ClampedYGradientData,
}
@ -205,7 +203,6 @@ impl StaticIndependentChunkNoiseFunctionComponentImpl for ClampedYGradient {
}
}
#[derive(Clone)]
pub struct RangeChoice {
input_index: usize,
pub(crate) when_in_index: usize,

View File

@ -43,7 +43,6 @@ pub trait StaticIndependentChunkNoiseFunctionComponentImpl: NoiseFunctionCompone
}
}
#[derive(Clone)]
pub struct Wrapper {
pub input_index: usize,
pub wrapper_type: WrapperType,

View File

@ -8,7 +8,6 @@ use crate::generation::noise::router::{
use super::NoiseFunctionComponentRange;
#[derive(Clone)]
pub enum SplineValue {
Spline(Spline),
Fixed(f32),
@ -37,7 +36,6 @@ impl SplineValue {
}
}
#[derive(Clone)]
pub struct SplinePoint {
pub location: f32,
pub value: SplineValue,
@ -67,7 +65,6 @@ pub enum Range {
Below,
}
#[derive(Clone)]
pub struct Spline {
pub input_index: usize,
pub points: Box<[SplinePoint]>,
@ -201,7 +198,6 @@ impl Spline {
}
}
#[derive(Clone)]
pub struct SplineFunction {
spline: Spline,
min_value: f64,

View File

@ -7,7 +7,6 @@ use crate::generation::noise::router::{
density_function::NoiseFunctionComponentRange,
};
#[derive(Clone)]
pub struct FindTopSurface {
density_index: usize,
upper_bound_index: usize,

View File

@ -150,7 +150,6 @@ impl FluidLevelSamplerImpl for StandardChunkFluidLevelSampler {
///
/// 12. full: Generation is done and a chunk can now be loaded. The proto-chunk is now converted to a level chunk and all block updates deferred in the above steps are executed.
///
#[derive(Clone)]
pub struct ProtoChunk {
pub x: i32,
pub z: i32,
@ -162,10 +161,10 @@ pub struct ProtoChunk {
/// HEIGHTMAPS
///
/// Top block that is not air
pub flat_surface_height_map: Box<[i16]>,
flat_ocean_floor_height_map: Box<[i16]>,
pub flat_motion_blocking_height_map: Box<[i16]>,
pub flat_motion_blocking_no_leaves_height_map: Box<[i16]>,
pub flat_surface_height_map: [i16; CHUNK_AREA],
flat_ocean_floor_height_map: [i16; CHUNK_AREA],
pub flat_motion_blocking_height_map: [i16; CHUNK_AREA],
pub flat_motion_blocking_no_leaves_height_map: [i16; CHUNK_AREA],
structure_starts: FxHashMap<StructureKeys, StructureInstance>,
// Height of the chunk for indexing
@ -214,7 +213,7 @@ impl ProtoChunk {
let height = dimension.logical_height as u16;
let section_count = (height as usize) / 16;
let default_heightmap = vec![i16::MIN; CHUNK_AREA].into_boxed_slice();
let default_heightmap = [i16::MIN; CHUNK_AREA];
Self {
x,
z,
@ -228,9 +227,9 @@ impl ProtoChunk {
* biome_coords::from_block(height as i32) as usize
]
.into_boxed_slice(),
flat_surface_height_map: default_heightmap.clone(),
flat_ocean_floor_height_map: default_heightmap.clone(),
flat_motion_blocking_height_map: default_heightmap.clone(),
flat_surface_height_map: default_heightmap,
flat_ocean_floor_height_map: default_heightmap,
flat_motion_blocking_height_map: default_heightmap,
flat_motion_blocking_no_leaves_height_map: default_heightmap,
structure_starts: FxHashMap::default(),
height,

View File

@ -10,7 +10,6 @@ use crate::{
pub const DEFAULT_H_POS: i32 = -1;
#[derive(Clone)]
pub struct ShiftableStructurePiece {
pub piece: StructurePiece,
h_pos: i32,

View File

@ -46,16 +46,11 @@ impl StructureGenerator for BuriedTreasureGenerator {
}
}
#[derive(Clone)]
pub struct BuriedTreasurePiece {
piece: StructurePiece,
}
impl StructurePieceBase for BuriedTreasurePiece {
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new((*self).clone())
}
fn place(
&mut self,
chunk: &mut ProtoChunk,

View File

@ -62,7 +62,6 @@ impl StructureGenerator for DesertPyramidGenerator {
}
}
#[derive(Clone)]
pub struct DesertPyramidPiece {
piece: StructurePiece,
height_adjusted: bool,
@ -446,10 +445,6 @@ impl DesertPyramidPiece {
}
impl StructurePieceBase for DesertPyramidPiece {
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new(self.clone())
}
fn get_structure_piece(&self) -> &StructurePiece {
&self.piece
}

View File

@ -129,7 +129,6 @@ impl StructureGenerator for IglooGenerator {
}
/// Single igloo structure piece containing dome and optional basement.
#[derive(Clone)]
pub struct IglooPiece {
shiftable_structure_piece: ShiftableStructurePiece,
top_template: Arc<StructureTemplate>,
@ -141,10 +140,6 @@ pub struct IglooPiece {
}
impl StructurePieceBase for IglooPiece {
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new(self.clone())
}
fn place(
&mut self,
chunk: &mut ProtoChunk,

View File

@ -68,7 +68,6 @@ impl StructureGenerator for JungleTempleGenerator {
})
}
}
#[derive(Clone)]
pub struct JungleTemplePiece {
// Composition: Rust uses a base struct to mimic the ScatteredFeaturePiece inheritance
piece: StructurePiece,
@ -79,10 +78,6 @@ pub struct JungleTemplePiece {
placed_trap_2: bool,
}
impl StructurePieceBase for JungleTemplePiece {
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new(self.clone())
}
fn get_structure_piece(&self) -> &StructurePiece {
&self.piece
}

View File

@ -47,8 +47,6 @@ pub trait StructurePieceBase: Send + Sync {
self.get_structure_piece_mut().translate(x, y, z);
}
fn clone_box(&self) -> Box<dyn StructurePieceBase>;
/// Places the blocks for this piece into the chunk.
fn place(
&mut self,
@ -449,10 +447,6 @@ impl StructurePiece {
}
impl StructurePieceBase for StructurePiece {
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new(self.clone())
}
fn place(
&mut self,
_chunk: &mut ProtoChunk,
@ -623,7 +617,6 @@ pub fn create_chunk_random(seed: i64, chunk_x: i32, chunk_z: i32) -> RandomGener
RandomGenerator::Xoroshiro(Xoroshiro::from_seed(carver_seed))
}
#[derive(Clone)]
pub enum StructureInstance {
/// This chunk is the "owner" of the structure.
Start(StructurePosition),

View File

@ -20,7 +20,6 @@ use crate::{
};
/// Straight exterior bridge segment (5 × 10 × 19).
#[derive(Clone)]
pub struct BridgePiece {
pub piece: NetherFortressPiece,
}
@ -148,8 +147,4 @@ impl StructurePieceBase for BridgePiece {
chunk, &bb, false, 4, 1, 17, 4, 4, 17, fence_west, fence_west,
);
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new(self.clone())
}
}

View File

@ -18,7 +18,6 @@ use crate::{
/// 4-way exterior bridge crossing (19 × 10 × 19).
/// Also serves as the generation start piece.
#[derive(Clone)]
pub struct BridgeCrossingPiece {
pub piece: NetherFortressPiece,
}
@ -182,8 +181,4 @@ impl StructurePieceBase for BridgeCrossingPiece {
}
}
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new(self.clone())
}
}

View File

@ -19,7 +19,6 @@ use crate::{
/// Dead-end / terminator piece for the exterior bridge (5 × 10 × 8).
/// Generated randomly on each side and used as a fallback when no valid piece
/// can be placed.
#[derive(Clone)]
pub struct BridgeEndPiece {
pub piece: NetherFortressPiece,
/// Seed used so the crumbling geometry is deterministic per world seed.
@ -113,8 +112,4 @@ impl StructurePieceBase for BridgeEndPiece {
}
}
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new(self.clone())
}
}

View File

@ -17,7 +17,6 @@ use crate::{
};
/// Exterior platform with a Blaze mob-spawner (7 × 8 × 9).
#[derive(Clone)]
pub struct BridgePlatformPiece {
pub piece: NetherFortressPiece,
pub has_blaze_spawner: bool,
@ -163,8 +162,4 @@ impl StructurePieceBase for BridgePlatformPiece {
}
}
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new(self.clone())
}
}

View File

@ -16,7 +16,6 @@ use crate::{
};
/// Small 3-way crossing for the exterior bridge network (7 × 9 × 7).
#[derive(Clone)]
pub struct BridgeSmallCrossingPiece {
pub piece: NetherFortressPiece,
}
@ -156,8 +155,4 @@ impl StructurePieceBase for BridgeSmallCrossingPiece {
}
}
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new(self.clone())
}
}

View File

@ -16,7 +16,6 @@ use crate::{
};
/// Stairway that transitions from bridge level to interior level (7 × 11 × 7).
#[derive(Clone)]
pub struct BridgeStairsPiece {
pub piece: NetherFortressPiece,
}
@ -142,8 +141,4 @@ impl StructurePieceBase for BridgeStairsPiece {
}
}
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new(self.clone())
}
}

View File

@ -20,7 +20,6 @@ use crate::{
};
/// Interior corridor piece with a balcony overlook (9 × 7 × 9).
#[derive(Clone)]
pub struct CorridorBalconyPiece {
pub piece: NetherFortressPiece,
}
@ -164,8 +163,4 @@ impl StructurePieceBase for CorridorBalconyPiece {
}
}
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new(self.clone())
}
}

View File

@ -13,7 +13,6 @@ use crate::{
};
/// 4-way interior corridor crossing (5 × 7 × 5).
#[derive(Clone)]
pub struct CorridorCrossingPiece {
pub piece: NetherFortressPiece,
}
@ -130,8 +129,4 @@ impl StructurePieceBase for CorridorCrossingPiece {
}
}
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new(self.clone())
}
}

View File

@ -20,7 +20,6 @@ use crate::{
};
/// Large entrance tower connecting the bridge network to the interior (13 × 14 × 13).
#[derive(Clone)]
pub struct CorridorExitPiece {
pub piece: NetherFortressPiece,
}
@ -229,8 +228,4 @@ impl StructurePieceBase for CorridorExitPiece {
// chunk.schedule_fluid_tick(&lava_pos, 0);
// }
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new(self.clone())
}
}

View File

@ -20,7 +20,6 @@ use crate::{
};
/// Interior corridor that turns left (NW), optionally containing a chest (5 × 7 × 5).
#[derive(Clone)]
pub struct CorridorLeftTurnPiece {
pub piece: NetherFortressPiece,
pub contains_chest: bool,
@ -154,8 +153,4 @@ impl StructurePieceBase for CorridorLeftTurnPiece {
}
}
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new(self.clone())
}
}

View File

@ -18,7 +18,6 @@ use crate::{
};
/// Large interior room with nether wart crops (13 × 14 × 13).
#[derive(Clone)]
pub struct CorridorNetherWartsRoomPiece {
pub piece: NetherFortressPiece,
}
@ -274,8 +273,4 @@ impl StructurePieceBase for CorridorNetherWartsRoomPiece {
}
}
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new(self.clone())
}
}

View File

@ -20,7 +20,6 @@ use crate::{
};
/// Interior corridor that turns right (SE), optionally containing a chest (5 × 7 × 5).
#[derive(Clone)]
pub struct CorridorRightTurnPiece {
pub piece: NetherFortressPiece,
pub contains_chest: bool,
@ -154,8 +153,4 @@ impl StructurePieceBase for CorridorRightTurnPiece {
}
}
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new(self.clone())
}
}

View File

@ -18,7 +18,6 @@ use crate::{
};
/// Descending interior corridor staircase (5 × 14 × 10).
#[derive(Clone)]
pub struct CorridorStairsPiece {
pub piece: NetherFortressPiece,
}
@ -145,8 +144,4 @@ impl StructurePieceBase for CorridorStairsPiece {
}
}
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new(self.clone())
}
}

View File

@ -46,8 +46,8 @@ impl StructureGenerator for NetherFortressGenerator {
let start_z = section_coords::section_to_block(context.chunk_z) + 2;
let start_piece = BridgeCrossingPiece::new_start(&mut random, start_x, start_z);
let start_box: Box<dyn crate::generation::structure::structures::StructurePieceBase> =
Box::new(start_piece.clone());
let base_piece = start_piece.piece.piece.clone();
let mut pieces_to_process: Vec<
Box<dyn crate::generation::structure::structures::StructurePieceBase>,
@ -56,10 +56,8 @@ impl StructureGenerator for NetherFortressGenerator {
let mut bridge_pieces = get_bridge_piece_weights();
let mut corridor_pieces = get_corridor_piece_weights();
collector.add_piece(start_box);
start_piece.fill_openings(
&start_piece.piece.piece,
&base_piece,
&mut random,
&mut bridge_pieces,
&mut corridor_pieces,
@ -67,17 +65,22 @@ impl StructureGenerator for NetherFortressGenerator {
&mut pieces_to_process,
);
collector.add_piece(Box::new(start_piece));
while !pieces_to_process.is_empty() {
let idx = random.next_bounded_i32(pieces_to_process.len() as i32) as usize;
let piece = pieces_to_process.remove(idx);
piece.fill_openings_nether(
&start_piece.piece.piece,
&base_piece,
&mut random,
&mut bridge_pieces,
&mut corridor_pieces,
&mut collector,
&mut pieces_to_process,
);
collector.add_piece(piece);
}
Some(StructurePosition {
@ -619,7 +622,6 @@ impl NetherFortressPiece {
orientation,
self.piece.chain_length + 1,
) {
collector.add_piece(p.clone_box());
pieces_to_process.push(p);
}
}

View File

@ -17,7 +17,6 @@ use crate::{
/// Straight interior corridor segment (5 × 7 × 5).
/// This is also the fallback piece returned by the generation logic.
#[derive(Clone)]
pub struct SmallCorridorPiece {
pub piece: NetherFortressPiece,
}
@ -122,8 +121,4 @@ impl StructurePieceBase for SmallCorridorPiece {
}
}
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new(self.clone())
}
}

View File

@ -106,7 +106,6 @@ impl StructureGenerator for NetherFossilGenerator {
}
}
#[derive(Clone)]
struct NetherFossilPiece {
shiftable_structure_piece: ShiftableStructurePiece,
template: Arc<StructureTemplate>,
@ -156,10 +155,6 @@ impl NetherFossilPiece {
}
impl StructurePieceBase for NetherFossilPiece {
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new(self.clone())
}
fn place(
&mut self,
chunk: &mut ProtoChunk,

View File

@ -19,7 +19,6 @@ use crate::{
},
};
#[derive(Clone)]
pub struct ChestCorridorPiece {
pub piece: StrongholdPiece,
pub chest_generated: bool,
@ -159,8 +158,4 @@ impl StructurePieceBase for ChestCorridorPiece {
// }
// }
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new((*self).clone())
}
}

View File

@ -19,7 +19,6 @@ use crate::{
},
};
#[derive(Clone)]
pub struct CorridorPiece {
piece: StrongholdPiece,
left_exit: bool,
@ -164,13 +163,8 @@ impl StructurePieceBase for CorridorPiece {
inner.fill_with_outline(chunk, &box_limit, false, 4, 1, 2, 4, 3, 4, air, air);
}
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new((*self).clone())
}
}
#[derive(Clone)]
pub struct SmallCorridorPiece {
pub piece: StrongholdPiece,
pub length: i32,
@ -238,10 +232,6 @@ impl SmallCorridorPiece {
}
impl StructurePieceBase for SmallCorridorPiece {
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new((*self).clone())
}
fn get_structure_piece(&self) -> &crate::generation::structure::structures::StructurePiece {
&self.piece.piece
}

View File

@ -22,7 +22,6 @@ use crate::{
},
};
#[derive(Clone)]
pub struct FiveWayCrossingPiece {
pub piece: StrongholdPiece,
pub lower_left_exists: bool,
@ -313,8 +312,4 @@ impl StructurePieceBase for FiveWayCrossingPiece {
// let torch = Block::WALL_TORCH.default_state.with("facing", "south");
// inner.add_block(chunk, &torch, 6, 5, 6, &box_limit);
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new((*self).clone())
}
}

View File

@ -19,7 +19,6 @@ use crate::{
},
};
#[derive(Clone)]
pub struct LeftTurnPiece {
pub piece: StrongholdPiece,
}
@ -138,8 +137,4 @@ impl StructurePieceBase for LeftTurnPiece {
inner.fill_with_outline(chunk, &box_limit, false, 4, 1, 1, 4, 3, 3, air, air);
}
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new((*self).clone())
}
}

View File

@ -19,7 +19,6 @@ use crate::{
},
};
#[derive(Clone)]
pub struct LibraryPiece {
pub piece: StrongholdPiece,
pub tall: bool,
@ -237,8 +236,4 @@ impl StructurePieceBase for LibraryPiece {
// inner.add_chest(chunk, &box_limit, random, 12, 8, 1, "stronghold_library");
// }
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new((*self).clone())
}
}

View File

@ -71,27 +71,19 @@ impl StructureGenerator for StrongholdGenerator {
let mut last_piece_type: Option<StrongholdPieceType> = None;
let mut has_portal_room = false;
// let attempt_seed = get_carver_seed(
// &mut context.random,
// context.seed as u64,
// context.chunk_x,
// context.chunk_z,
// );
let start_x = section_coords::section_to_block(context.chunk_x) + 2;
let start_z = section_coords::section_to_block(context.chunk_z) + 2;
let start_piece = SpiralStaircasePiece::new_start(&mut random, start_x, start_z);
let start_box: Box<dyn StructurePieceBase> = Box::new(start_piece.clone());
let base_piece = start_piece.piece.piece.clone();
// In Vanilla, pieces_to_process is 'start.pieces'
let mut pieces_to_process: Vec<Box<dyn StructurePieceBase>> = Vec::new();
collector.add_piece(start_box);
// Initial Fill
start_piece.fill_openings(
&start_piece.piece.piece,
&base_piece,
&mut random,
&mut weights,
&mut last_piece_type,
@ -100,12 +92,15 @@ impl StructureGenerator for StrongholdGenerator {
&mut pieces_to_process,
);
collector.add_piece(Box::new(start_piece));
// Growth Loop
while !pieces_to_process.is_empty() {
let idx = random.next_bounded_i32(pieces_to_process.len() as i32) as usize;
let piece = pieces_to_process.remove(idx);
piece.fill_openings(
&start_piece.piece.piece,
&base_piece,
&mut random,
&mut weights,
&mut last_piece_type,
@ -113,6 +108,9 @@ impl StructureGenerator for StrongholdGenerator {
&mut collector,
&mut pieces_to_process,
);
// Move into collector AFTER processing its children
collector.add_piece(piece);
}
// Shift height
@ -252,7 +250,6 @@ fn get_initial_weights() -> Vec<PieceWeight> {
]
}
#[derive(Clone)]
pub struct StrongholdPiece {
pub piece: StructurePiece,
pub entry_door: EntranceType,
@ -617,7 +614,6 @@ impl StrongholdPiece {
};
if let Some(p) = next_piece {
collector.add_piece(p.clone_box());
return Some(p);
}

View File

@ -26,7 +26,6 @@ use crate::{
},
};
#[derive(Clone)]
pub struct PortalRoomPiece {
pub piece: StrongholdPiece,
pub spawner_placed: bool,
@ -372,8 +371,4 @@ impl StructurePieceBase for PortalRoomPiece {
}
}
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new((*self).clone())
}
}

View File

@ -22,7 +22,6 @@ use crate::{
},
};
#[derive(Clone)]
pub struct PrisonHallPiece {
piece: StrongholdPiece,
}
@ -222,8 +221,4 @@ impl StructurePieceBase for PrisonHallPiece {
// inner.add_block(chunk, &door_bottom, 4, 1, 8, &box_limit);
// inner.add_block(chunk, &door_top, 4, 2, 8, &box_limit);
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new((*self).clone())
}
}

View File

@ -19,7 +19,6 @@ use crate::{
},
};
#[derive(Clone)]
pub struct RightTurnPiece {
pub piece: StrongholdPiece,
}
@ -143,8 +142,4 @@ impl StructurePieceBase for RightTurnPiece {
inner.fill_with_outline(chunk, &box_limit, false, 0, 1, 1, 0, 3, 3, air, air);
}
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new((*self).clone())
}
}

View File

@ -28,20 +28,6 @@ pub struct SpiralStaircasePiece {
pub portal_room_exists: bool,
pub pieces: Vec<Box<dyn StructurePieceBase>>,
}
impl Clone for SpiralStaircasePiece {
fn clone(&self) -> Self {
Self {
// StrongholdPiece and other types must derive Clone
piece: self.piece.clone(),
orientation: self.orientation,
is_structure_start: self.is_structure_start,
last_piece_data_idx: self.last_piece_data_idx,
portal_room_exists: self.portal_room_exists,
// Use the clone_box helper we added to the trait!
pieces: self.pieces.iter().map(|p| p.clone_box()).collect(),
}
}
}
impl SpiralStaircasePiece {
/// Matches Start(Random random, int i, int j)
@ -102,10 +88,6 @@ impl SpiralStaircasePiece {
}
impl StructurePieceBase for SpiralStaircasePiece {
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new((*self).clone())
}
fn get_structure_piece(&self) -> &StructurePiece {
&self.piece.piece
}

View File

@ -22,7 +22,6 @@ use crate::{
},
};
#[derive(Clone)]
pub struct SquareRoomPiece {
piece: StrongholdPiece,
room_type: u32,
@ -273,8 +272,4 @@ impl StructurePieceBase for SquareRoomPiece {
_ => {}
}
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new((*self).clone())
}
}

View File

@ -22,7 +22,6 @@ use crate::{
},
};
#[derive(Clone)]
pub struct StairsPiece {
pub piece: StrongholdPiece,
}
@ -144,8 +143,4 @@ impl StructurePieceBase for StairsPiece {
}
}
}
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new((*self).clone())
}
}

View File

@ -55,16 +55,11 @@ impl StructureGenerator for SwampHutGenerator {
}
}
#[derive(Clone)]
pub struct SwampHutPiece {
shiftable_structure_piece: ShiftableStructurePiece,
}
impl StructurePieceBase for SwampHutPiece {
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new(self.clone())
}
fn place(
&mut self,
chunk: &mut ProtoChunk,

View File

@ -28,7 +28,6 @@ use super::{BlockMirror, BlockRotation, BlockStateResolver, StructureTemplate};
/// - Applying rotation and mirror transformations
/// - Placing blocks at world coordinates
/// - Skipping structure void blocks
#[derive(Clone)]
pub struct TemplatePiece {
/// The underlying structure piece metadata.
pub piece: StructurePiece,
@ -159,10 +158,6 @@ impl TemplatePiece {
}
impl StructurePieceBase for TemplatePiece {
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new(self.clone())
}
fn place(
&mut self,
chunk: &mut ProtoChunk,

View File

@ -61,7 +61,7 @@ pub type SyncEntityChunk = Arc<ChunkEntityData>;
pub struct Level {
pub seed: Seed,
pub world_portal: ArcSwap<Option<Arc<dyn WorldPortalExt>>>,
pub level_folder: LevelFolder,
pub level_folder: Arc<LevelFolder>,
pub lighting_config: LightingEngineConfig,
/// Counts the number of ticks that have been scheduled for this world
@ -117,26 +117,12 @@ pub struct RandomTickSample {
pub tick_fluid: bool,
}
#[derive(Clone)]
pub struct LevelFolder {
pub root_folder: PathBuf,
pub region_folder: PathBuf,
pub entities_folder: PathBuf,
}
#[ignore]
#[cfg(feature = "tokio_taskdump")]
pub async fn dump() {
// let handle = Handle::current();
// if let Ok(dump) = timeout(Duration::from_secs(100), handle.dump()).await {
// for (i, task) in dump.tasks().iter().enumerate() {
// let trace = task.trace();
// log::error!("TASK {i}:");
// log::error!("{trace}\n");
// }
// }
}
impl Level {
#[must_use]
pub fn from_root_folder(
@ -152,11 +138,11 @@ impl Level {
std::fs::create_dir_all(&region_folder).expect("Failed to create Region folder");
std::fs::create_dir_all(&entities_folder).expect("Failed to create Entities folder");
let level_folder = LevelFolder {
let level_folder = Arc::new(LevelFolder {
root_folder,
region_folder,
entities_folder,
};
});
let seed = Seed(seed as u64);
let world_gen = get_world_gen(seed, dimension).into();
@ -766,12 +752,6 @@ impl Level {
}
}
pub fn try_get_chunk(&self, coordinates: &Vector2<i32>) -> Option<Arc<ChunkData>> {
self.loaded_chunks
.get(coordinates)
.map(|x| x.value().clone())
}
pub fn is_chunk_loaded(&self, coordinates: &Vector2<i32>) -> bool {
self.loaded_chunks.contains_key(coordinates)
}
@ -784,6 +764,16 @@ impl Level {
self.loaded_chunks.get(coordinates).map(|x| f(x.value()))
}
pub fn read_entity_chunk_sync<R, F: Fn(&SyncEntityChunk) -> R>(
&self,
coordinates: &Vector2<i32>,
f: F,
) -> Option<R> {
self.loaded_entity_chunks
.get(coordinates)
.map(|x| f(x.value()))
}
pub fn get_rough_biome(&self, position: &BlockPos) -> &'static Biome {
let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position();
let id = self.read_chunk_sync(&chunk_coordinate, |chunk| {
@ -796,16 +786,6 @@ impl Level {
Biome::from_id(id.flatten().unwrap_or(0)).unwrap_or(&Biome::THE_VOID)
}
pub fn read_entity_chunk_sync<R, F: Fn(&SyncEntityChunk) -> R>(
&self,
coordinates: &Vector2<i32>,
f: F,
) -> Option<R> {
self.loaded_entity_chunks
.get(coordinates)
.map(|x| f(x.value()))
}
pub fn get_entity_chunk_sync(&self, pos: &Vector2<i32>) -> Option<SyncEntityChunk> {
self.loaded_entity_chunks
.get(pos)

View File

@ -115,7 +115,6 @@ tempfile.workspace = true
[features]
console-subscriber = ["dep:console-subscriber"]
tokio_taskdump = ["pumpkin-world/tokio_taskdump"]
[lints]
workspace = true

View File

@ -155,7 +155,7 @@ impl CrashReport {
eprintln!(
"{}\n{}",
RED.console_color("Backtrace:"),
&self.captured_backtrace
self.captured_backtrace
);
}
}

View File

@ -2165,7 +2165,7 @@ impl Entity {
}
pub async fn set_fall_flying(&self, fall_flying: bool) {
assert!(self.fall_flying.load(Relaxed) != fall_flying);
assert_ne!(self.fall_flying.load(Relaxed), fall_flying);
self.fall_flying.store(fall_flying, Relaxed);
self.set_flag(Flag::FallFlying, fall_flying).await;
}

View File

@ -62,16 +62,14 @@ impl ItemBehaviour for EnderEyeItem {
let new_state_id = block.from_properties(&props).to_state_id(block);
world
.set_block_state(&location, new_state_id, BlockFlags::empty())
.set_block_state(&location, new_state_id, BlockFlags::NOTIFY_LISTENERS)
.await;
// Consume one item.
item.decrement(1);
world.sync_world_event(WorldEvent::EndPortalFrameFill, location, 0);
// Try to complete the portal.
EndPortal::get_new_portal(&world, location).await;
// Consume one item.
item.decrement(1);
})
}

View File

@ -47,9 +47,9 @@ impl Ignition {
}
fn can_be_lit(block: &Block, state_id: u16) -> Option<u16> {
let mut props = match &block.properties(state_id) {
Some(props) => props.to_props(),
None => return None,
let mut props = {
let props = &block.properties(state_id)?;
props.to_props()
};
if let Some((_, value)) = props.iter_mut().find(|(k, _)| *k == "extinguished") {

View File

@ -94,6 +94,8 @@ pub struct BedrockClient {
output_split_number: AtomicU16,
output_sequenced_index: AtomicU32,
output_ordered_index: AtomicU32,
/// The next form ID to use for custom forms.
pub next_form_id: AtomicU32,
/// An notifier that is triggered when this client is closed.
close_token: CancellationToken,
last_seen: Arc<AtomicCell<std::time::Instant>>,
@ -133,6 +135,7 @@ impl BedrockClient {
output_split_number: AtomicU16::new(0),
output_sequenced_index: AtomicU32::new(0),
output_ordered_index: AtomicU32::new(0),
next_form_id: AtomicU32::new(0),
compounds: Arc::new(Mutex::new(HashMap::new())),
close_token: CancellationToken::new(),
last_seen: Arc::new(AtomicCell::new(std::time::Instant::now())),
@ -789,6 +792,16 @@ impl BedrockClient {
SAnimate::PACKET_ID => {
self.handle_animate(player, server, &SAnimate::read(reader)?);
}
pumpkin_protocol::bedrock::server::modal_form_response::SModalFormResponse::PACKET_ID => {
self.handle_modal_form_response(
player,
server,
pumpkin_protocol::bedrock::server::modal_form_response::SModalFormResponse::read(
reader,
)?,
)
.await;
}
SLoadingScreen::PACKET_ID => {
// Ignore for now
}

View File

@ -411,4 +411,18 @@ impl BedrockClient {
}
}}
}
pub async fn handle_modal_form_response(
&self,
player: &Arc<Player>,
server: &Server,
packet: pumpkin_protocol::bedrock::server::modal_form_response::SModalFormResponse,
) {
let event = crate::plugin::api::events::player::bedrock_form_response::BedrockFormResponseEvent::new(
player.clone(),
packet.form_id.0 as u32,
packet.form_data,
);
let _ = server.plugin_manager.fire(event).await;
}
}

View File

@ -60,7 +60,7 @@ impl LANBroadcast {
let mut interval = time::interval(Duration::from_millis(1500));
let advertisement = format!("[MOTD]{}[/MOTD][AD]{}[/AD]", &self.motd, bound_addr.port());
let advertisement = format!("[MOTD]{}[/MOTD][AD]{}[/AD]", self.motd, bound_addr.port());
info!(
"LAN broadcast running on {}",

View File

@ -0,0 +1,37 @@
use pumpkin_macros::Event;
use std::sync::Arc;
use crate::entity::player::Player;
use super::PlayerEvent;
/// An event that occurs when a player responds to a Bedrock custom form.
#[derive(Event, Clone)]
pub struct BedrockFormResponseEvent {
/// The player who responded to the form.
pub player: Arc<Player>,
/// The ID of the form.
pub form_id: u32,
/// The response data as a JSON string. None if the form was closed.
pub response_data: Option<String>,
}
impl BedrockFormResponseEvent {
/// Creates a new instance of `BedrockFormResponseEvent`.
#[must_use]
pub const fn new(player: Arc<Player>, form_id: u32, response_data: Option<String>) -> Self {
Self {
player,
form_id,
response_data,
}
}
}
impl PlayerEvent for BedrockFormResponseEvent {
fn get_player(&self) -> &Arc<Player> {
&self.player
}
}

View File

@ -1,3 +1,4 @@
pub mod bedrock_form_response;
pub mod changed_main_hand;
pub mod egg_throw;
pub mod exp_change;

View File

@ -11,8 +11,8 @@ use crate::plugin::{
},
player::to_wit_item_stack,
pumpkin::plugin::event::{
Event, InteractAction as WasmInteractAction, InventoryClickEventData,
InventoryCloseEventData, PlayerChangeWorldEventData,
BedrockFormResponseEventData, Event, InteractAction as WasmInteractAction,
InventoryClickEventData, InventoryCloseEventData, PlayerChangeWorldEventData,
PlayerChangedMainHandEventData, PlayerChatEventData, PlayerCommandSendEventData,
PlayerCustomPayloadEventData, PlayerEggThrowEventData, PlayerExpChangeEventData,
PlayerFishEventData, PlayerFishState as WasmPlayerFishState,
@ -26,6 +26,7 @@ use crate::plugin::{
},
},
player::{
bedrock_form_response::BedrockFormResponseEvent,
changed_main_hand::PlayerChangedMainHandEvent,
egg_throw::PlayerEggThrowEvent,
exp_change::PlayerExpChangeEvent,
@ -733,3 +734,28 @@ impl ToFromWasmEvent for PlayerToggleSprintEvent {
}
}
}
impl ToFromWasmEvent for BedrockFormResponseEvent {
fn to_wasm_event(&self, state: &mut PluginHostState) -> Event {
let player = state
.add_player(self.player.clone())
.expect("failed to add player resource");
Event::BedrockFormResponseEvent(BedrockFormResponseEventData {
player,
form_id: self.form_id,
response_data: self.response_data.clone(),
})
}
fn from_wasm_event(event: Event, state: &mut PluginHostState) -> Self {
match event {
Event::BedrockFormResponseEvent(data) => Self {
player: consume_player(state, &data.player),
form_id: data.form_id,
response_data: data.response_data,
},
_ => panic!("unexpected event type"),
}
}
}

View File

@ -0,0 +1,129 @@
use crate::net::ClientPlatform;
use crate::plugin::loader::wasm::wasm_host::state::PluginHostState;
use crate::plugin::loader::wasm::wasm_host::wit::v0_1::player::player_from_resource;
use crate::plugin::loader::wasm::wasm_host::wit::v0_1::player::text_component_from_resource;
use crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::forms::{
CustomForm, CustomFormElement, Form, Host, ImageType, ModalForm, SimpleForm,
};
use pumpkin_protocol::bedrock::client::modal_form_request::CModalFormRequest;
use pumpkin_util::translation::Locale;
use serde_json::{Value, json};
use std::str::FromStr;
use std::sync::atomic::Ordering;
use wasmtime::component::Resource;
impl Host for PluginHostState {
async fn send_form(
&mut self,
player_res: Resource<
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::player::Player,
>,
form: Form,
) -> wasmtime::Result<u32> {
let player = player_from_resource(self, &player_res)?;
if let ClientPlatform::Bedrock(client) = &player.client {
let form_id = client.next_form_id.fetch_add(1, Ordering::Relaxed);
let locale_str = player.config.load().locale.clone();
let locale = Locale::from_str(&locale_str).unwrap_or(Locale::EnUs);
let form_json = match form {
Form::Simple(simple) => self.serialize_simple_form(simple, locale),
Form::Modal(modal) => self.serialize_modal_form(&modal, locale),
Form::Custom(custom) => self.serialize_custom_form(custom, locale),
};
client
.send_game_packet(&CModalFormRequest {
form_id: pumpkin_protocol::codec::var_int::VarInt(form_id as i32),
form_data: form_json.to_string(),
})
.await;
Ok(form_id)
} else {
Ok(0)
}
}
}
impl PluginHostState {
fn translate_res(
&self,
res: &Resource<
crate::plugin::loader::wasm::wasm_host::wit::v0_1::pumpkin::plugin::text::TextComponent,
>,
locale: Locale,
) -> String {
let component = text_component_from_resource(self, res);
component.0.get_text(locale)
}
fn serialize_simple_form(&self, form: SimpleForm, locale: Locale) -> Value {
let buttons: Vec<Value> = form
.buttons
.into_iter()
.map(|b| {
let mut obj = json!({ "text": self.translate_res(&b.text, locale) });
if let Some(image) = b.image {
obj.as_object_mut().unwrap().insert(
"image".to_string(),
json!({
"type": match image.type_ {
ImageType::Url => "url",
ImageType::Path => "path",
},
"data": image.data
}),
);
}
obj
})
.collect();
json!({
"type": "form",
"title": self.translate_res(&form.title, locale),
"content": self.translate_res(&form.content, locale),
"buttons": buttons
})
}
fn serialize_modal_form(&self, form: &ModalForm, locale: Locale) -> Value {
json!({
"type": "modal",
"title": self.translate_res(&form.title, locale),
"content": self.translate_res(&form.content, locale),
"button1": self.translate_res(&form.button1, locale),
"button2": self.translate_res(&form.button2, locale)
})
}
fn serialize_custom_form(&self, form: CustomForm, locale: Locale) -> Value {
let elements: Vec<Value> = form.elements.into_iter().map(|e| {
match e {
CustomFormElement::Label(text) => json!({ "type": "label", "text": self.translate_res(&text, locale) }),
CustomFormElement::Toggle((text, default)) => json!({ "type": "toggle", "text": self.translate_res(&text, locale), "default": default }),
CustomFormElement::Slider((text, min, max, step, default)) => json!({
"type": "slider", "text": self.translate_res(&text, locale), "min": min, "max": max, "step": step, "default": default
}),
CustomFormElement::StepSlider((text, steps, default)) => json!({
"type": "step_slider", "text": self.translate_res(&text, locale), "steps": steps, "default": default
}),
CustomFormElement::Dropdown((text, options, default)) => json!({
"type": "dropdown", "text": self.translate_res(&text, locale), "options": options, "default": default
}),
CustomFormElement::Input((text, placeholder, default)) => json!({
"type": "input", "text": self.translate_res(&text, locale), "placeholder": placeholder, "default": default
}),
}
}).collect();
json!({
"type": "custom_form",
"title": self.translate_res(&form.title, locale),
"content": elements
})
}
}

View File

@ -13,6 +13,7 @@ pub mod common;
pub mod context;
pub mod entity;
pub mod events;
pub mod forms;
pub mod generated_packets;
pub mod gui;
pub mod i18n;

View File

@ -424,13 +424,11 @@ impl World {
}
pub async fn flush_synced_block_events(self: &Arc<Self>) {
let events;
// THIS IS IMPORTANT
// it prevents deadlocks and also removes the need to wait for a lock when adding a new synced block
{
let events = {
let mut queue = self.synced_block_event_queue.lock().await;
events = queue.clone();
queue.clear();
std::mem::take(&mut *queue)
};
for event in events {
@ -1599,6 +1597,8 @@ impl World {
(position, yaw, pitch)
} else {
let spawn_position = Vector2::new(level_info.spawn_x, level_info.spawn_z);
let chunk_pos = Vector2::new(level_info.spawn_x >> 4, level_info.spawn_z >> 4);
self.level.get_or_fetch_chunk(chunk_pos, |_| ()).await;
let pos_y = self.get_top_block(spawn_position) + 1; // +1 to spawn on top of the block
let position = Vector3::new(
@ -1937,6 +1937,8 @@ impl World {
} else {
let info = &self.level_info.load();
let spawn_position = Vector2::new(info.spawn_x, info.spawn_z);
let chunk_pos = Vector2::new(info.spawn_x >> 4, info.spawn_z >> 4);
self.level.get_or_fetch_chunk(chunk_pos, |_| ()).await;
let pos_y = self.get_top_block(spawn_position) + 1; // +1 to spawn on top of the block
let position = Vector3::new(
@ -2428,6 +2430,8 @@ impl World {
// FIXME: This spawn position calculation is incorrect. Should use vanilla's
// proper spawn position calculation (see #1381). The y-level calculation
// needs to account for spawn radius and find a safe spawn position.
let chunk_pos = Vector2::new(spawn_x >> 4, spawn_z >> 4);
self.level.get_or_fetch_chunk(chunk_pos, |_| ()).await;
let top = self.get_top_block(Vector2::new(spawn_x, spawn_z));
(
@ -2496,6 +2500,8 @@ impl World {
);
// FIXME: This spawn position calculation is incorrect. Should use vanilla's
// proper spawn position calculation (see #1381).
let chunk_pos = Vector2::new(spawn_x >> 4, spawn_z >> 4);
self.level.get_or_fetch_chunk(chunk_pos, |_| ()).await;
let top = self.get_top_block(Vector2::new(spawn_x, spawn_z));
let fallback_pos = Vector3::new(
f64::from(spawn_x) + 0.5,

View File

@ -94,7 +94,7 @@ impl EndPortal {
.set_block_state(
&pos.offset(Vector3::new(x, 0, z)),
Block::END_PORTAL.default_state.id,
BlockFlags::empty(),
BlockFlags::NOTIFY_LISTENERS,
)
.await;
}