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" version = "0.1.0"
dependencies = [ dependencies = [
"postcard", "postcard",
"serde",
"serde_json",
"tracing", "tracing",
"tracing-serde-structured", "tracing-serde-structured",
"wit-bindgen 0.57.1", "wit-bindgen 0.57.1",

View File

@ -393,7 +393,7 @@ impl ToTokens for BlockPropertyStruct {
fn to_state_id(&self, block: &Block) -> u16 { fn to_state_id(&self, block: &Block) -> u16 {
if !Self::handles_block_id(block.id) { 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() 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 { fn from_state_id(state_id: u16, block: &Block) -> Self {
debug_assert!( debug_assert!(
Self::handles_block_id(block.id), 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; let min_id = block.states[0].id;
@ -420,7 +420,7 @@ impl ToTokens for BlockPropertyStruct {
fn default(block: &Block) -> Self { fn default(block: &Block) -> Self {
if !Self::handles_block_id(block.id) { 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) 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 { fn from_props(props: &[(&str, &str)], block: &Block) -> Self {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
if !matches!(block.id, #(#block_ids)|*) { 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); let mut block_props = Self::default(block);
for (key, value) in props { for (key, value) in props {

View File

@ -13,9 +13,6 @@ uuid.workspace = true
toml.workspace = true toml.workspace = true
[features]
# Adds helper to change the config at runtime
test_helper = []
[lints] [lints]
workspace = true 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) { if !["water", "lava"].contains(&fluid.name) {
panic!( panic!(
"{} is not a valid fluid for {}", "{} is not a valid fluid for {}",
&fluid.name, "WaterLikeFluidProperties" fluid.name, "WaterLikeFluidProperties"
); );
} }
let prop_index = self.to_index(); let prop_index = self.to_index();
@ -921,7 +921,7 @@ impl FluidProperties for WaterLikeFluidProperties {
if !["water", "lava"].contains(&fluid.name) { if !["water", "lava"].contains(&fluid.name) {
panic!( panic!(
"{} is not a valid fluid for {}", "{} is not a valid fluid for {}",
&fluid.name, "WaterLikeFluidProperties" fluid.name, "WaterLikeFluidProperties"
); );
} }
for (idx, state) in fluid.states.iter().enumerate() { for (idx, state) in fluid.states.iter().enumerate() {
@ -935,7 +935,7 @@ impl FluidProperties for WaterLikeFluidProperties {
if !["water", "lava"].contains(&fluid.name) { if !["water", "lava"].contains(&fluid.name) {
panic!( panic!(
"{} is not a valid fluid for {}", "{} is not a valid fluid for {}",
&fluid.name, "WaterLikeFluidProperties" fluid.name, "WaterLikeFluidProperties"
); );
} }
Self::from_state_id(fluid.default_state_index, fluid) Self::from_state_id(fluid.default_state_index, fluid)
@ -947,7 +947,7 @@ impl FluidProperties for WaterLikeFluidProperties {
if !["water", "lava"].contains(&fluid.name) { if !["water", "lava"].contains(&fluid.name) {
panic!( panic!(
"{} is not a valid fluid for {}", "{} is not a valid fluid for {}",
&fluid.name, "WaterLikeFluidProperties" fluid.name, "WaterLikeFluidProperties"
); );
} }
let mut fluid_props = Self::default(fluid); let mut fluid_props = Self::default(fluid);
@ -995,7 +995,7 @@ impl FluidProperties for FlowingWaterLikeFluidProperties {
if !["flowing_water", "flowing_lava"].contains(&fluid.name) { if !["flowing_water", "flowing_lava"].contains(&fluid.name) {
panic!( panic!(
"{} is not a valid fluid for {}", "{} is not a valid fluid for {}",
&fluid.name, "FlowingWaterLikeFluidProperties" fluid.name, "FlowingWaterLikeFluidProperties"
); );
} }
let prop_index = self.to_index(); let prop_index = self.to_index();
@ -1009,7 +1009,7 @@ impl FluidProperties for FlowingWaterLikeFluidProperties {
if !["flowing_water", "flowing_lava"].contains(&fluid.name) { if !["flowing_water", "flowing_lava"].contains(&fluid.name) {
panic!( panic!(
"{} is not a valid fluid for {}", "{} is not a valid fluid for {}",
&fluid.name, "FlowingWaterLikeFluidProperties" fluid.name, "FlowingWaterLikeFluidProperties"
); );
} }
for (idx, state) in fluid.states.iter().enumerate() { for (idx, state) in fluid.states.iter().enumerate() {
@ -1023,7 +1023,7 @@ impl FluidProperties for FlowingWaterLikeFluidProperties {
if !["flowing_water", "flowing_lava"].contains(&fluid.name) { if !["flowing_water", "flowing_lava"].contains(&fluid.name) {
panic!( panic!(
"{} is not a valid fluid for {}", "{} is not a valid fluid for {}",
&fluid.name, "FlowingWaterLikeFluidProperties" fluid.name, "FlowingWaterLikeFluidProperties"
); );
} }
Self::from_state_id(fluid.default_state_index, fluid) 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) { if !["flowing_water", "flowing_lava"].contains(&fluid.name) {
panic!( panic!(
"{} is not a valid fluid for {}", "{} is not a valid fluid for {}",
&fluid.name, "FlowingWaterLikeFluidProperties" fluid.name, "FlowingWaterLikeFluidProperties"
); );
} }
let mut fluid_props = Self::default(fluid); let mut fluid_props = Self::default(fluid);

View File

@ -10,6 +10,8 @@ wit-bindgen = { workspace = true }
tracing = { workspace = true } tracing = { workspace = true }
postcard = { workspace = true } postcard = { workspace = true }
tracing-serde-structured = { workspace = true } tracing-serde-structured = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
[package.metadata.component] [package.metadata.component]
target = { path = "../pumpkin-plugin-wit" } 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 changed_main_hand;
pub mod egg_throw; pub mod egg_throw;
pub mod exp_change; 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 commands;
pub mod events; pub mod events;
pub mod forms;
/// Constants for plugin permissions. /// Constants for plugin permissions.
/// ///
/// Use these in your `PluginMetadata` to request access to specific host features. /// Use these in your `PluginMetadata` to request access to specific host features.

View File

@ -258,6 +258,13 @@ interface event {
can-build: bool, can-build: bool,
cancelled: bool cancelled: bool
} }
record bedrock-form-response-event-data {
player: player,
form-id: u32,
response-data: option<string>,
}
record server-command-event-data { record server-command-event-data {
command: string, command: string,
cancelled: bool cancelled: bool
@ -321,6 +328,7 @@ interface event {
block-can-build-event, block-can-build-event,
block-grow-event, block-grow-event,
block-place-event, block-place-event,
bedrock-form-response-event,
server-command-event, server-command-event,
spawn-change-event, spawn-change-event,
server-broadcast-event, server-broadcast-event,
@ -358,6 +366,7 @@ interface event {
block-can-build-event(block-can-build-event-data), block-can-build-event(block-can-build-event-data),
block-grow-event(block-grow-event-data), block-grow-event(block-grow-event-data),
block-place-event(block-place-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), server-command-event(server-command-event-data),
spawn-change-event(spawn-change-event-data), spawn-change-event(spawn-change-event-data),
server-broadcast-event(server-broadcast-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; import entity;
/// Provides boss bar creation and management. /// Provides boss bar creation and management.
import boss-bar; import boss-bar;
/// Provides Bedrock custom forms.
import forms;
// --- Plugin-provided exports --- // --- Plugin-provided exports ---

View File

@ -9,6 +9,7 @@ pub mod handshake;
pub mod inventory_content; pub mod inventory_content;
pub mod level_chunk; pub mod level_chunk;
pub mod level_event; pub mod level_event;
pub mod modal_form_request;
pub mod move_player; pub mod move_player;
pub mod network_chunk_publisher_update; pub mod network_chunk_publisher_update;
pub mod network_settings; pub mod network_settings;
@ -38,6 +39,7 @@ pub use handshake::*;
pub use inventory_content::*; pub use inventory_content::*;
pub use level_chunk::*; pub use level_chunk::*;
pub use level_event::*; pub use level_event::*;
pub use modal_form_request::*;
pub use move_player::*; pub use move_player::*;
pub use network_chunk_publisher_update::*; pub use network_chunk_publisher_update::*;
pub use network_settings::*; 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 interaction;
pub mod loading_screen; pub mod loading_screen;
pub mod login; pub mod login;
pub mod modal_form_response;
pub mod player_action; pub mod player_action;
pub mod player_auth_input; pub mod player_auth_input;
pub mod raknet; 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) => { IdOrStateDeserializer::Id(id) => {
debug_assert!(*id == 0); debug_assert_eq!(*id, 0);
// Get the data // Get the data
let value = T::deserialize(deserializer)?; let value = T::deserialize(deserializer)?;
*self = IdOrStateDeserializer::Value(value); *self = IdOrStateDeserializer::Value(value);

View File

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

View File

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

View File

@ -79,8 +79,6 @@ harness = false
name = "noise_router" name = "noise_router"
harness = false harness = false
[features]
tokio_taskdump = []
# [lints] # [lints]
# workspace = true # 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; fn biome(&self, x: i32, y: i32, z: i32, noise: &mut MultiNoiseSampler<'_>) -> &'static Biome;
} }
#[derive(Clone, Copy)]
pub struct MultiNoiseBiomeSupplier { pub struct MultiNoiseBiomeSupplier {
source: &'static BiomeTree, source: &'static BiomeTree,
} }

View File

@ -5,7 +5,7 @@ pub const fn to_long(float: f32) -> i64 {
(float * 10000f32) as i64 (float * 10000f32) as i64
} }
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct NoiseValuePoint { pub struct NoiseValuePoint {
pub temperature: i64, pub temperature: i64,
pub humidity: 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()); let mut chunks = Vec::with_capacity(self.chunk_map.len());
for (pos, holder) in &self.chunk_map {
if let Some(chunk) = &holder.chunk { for (pos, holder) in self.chunk_map.iter_mut() {
match chunk { if let Some(chunk) = holder.chunk.take() {
Chunk::Level(sync_chunk) => { let should_save = match &chunk {
if sync_chunk.is_dirty() { Chunk::Level(sync_chunk) => sync_chunk.is_dirty(),
chunks.push((*pos, Chunk::Level(sync_chunk.clone()))); Chunk::Proto(_) => save_proto_chunk,
} };
}
Chunk::Proto(proto) => { if should_save {
if save_proto_chunk { chunks.push((*pos, chunk));
chunks.push((*pos, Chunk::Proto(proto.clone()))); } else {
} holder.chunk = Some(chunk);
}
} }
} }
} }
if chunks.is_empty() { if chunks.is_empty() {
return; return;
} }
info!( info!(
"Saving {} chunks (collected from {} holders)...", "Saving {} chunks (collected from {} holders)...",
chunks.len(), chunks.len(),
self.chunk_map.len() self.chunk_map.len()
); );
let mut data = self.io_lock.0.lock().unwrap(); 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; *data.entry(*pos).or_insert(0) += 1;
} }
drop(data); drop(data);
if let Err(e) = self.io_write.send(chunks) { if let Err(e) = self.io_write.send(chunks) {
error!( error!("Failed to send chunks to io write thread: {:?}", e);
"Failed to send chunks to io write thread during unload (may have shut down): {:?}",
e
);
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,7 +7,6 @@ use crate::generation::noise::router::{
density_function::NoiseFunctionComponentRange, density_function::NoiseFunctionComponentRange,
}; };
#[derive(Clone)]
pub struct FindTopSurface { pub struct FindTopSurface {
density_index: usize, density_index: usize,
upper_bound_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. /// 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 struct ProtoChunk {
pub x: i32, pub x: i32,
pub z: i32, pub z: i32,
@ -162,10 +161,10 @@ pub struct ProtoChunk {
/// HEIGHTMAPS /// HEIGHTMAPS
/// ///
/// Top block that is not air /// Top block that is not air
pub flat_surface_height_map: Box<[i16]>, pub flat_surface_height_map: [i16; CHUNK_AREA],
flat_ocean_floor_height_map: Box<[i16]>, flat_ocean_floor_height_map: [i16; CHUNK_AREA],
pub flat_motion_blocking_height_map: Box<[i16]>, pub flat_motion_blocking_height_map: [i16; CHUNK_AREA],
pub flat_motion_blocking_no_leaves_height_map: Box<[i16]>, pub flat_motion_blocking_no_leaves_height_map: [i16; CHUNK_AREA],
structure_starts: FxHashMap<StructureKeys, StructureInstance>, structure_starts: FxHashMap<StructureKeys, StructureInstance>,
// Height of the chunk for indexing // Height of the chunk for indexing
@ -214,7 +213,7 @@ impl ProtoChunk {
let height = dimension.logical_height as u16; let height = dimension.logical_height as u16;
let section_count = (height as usize) / 16; 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 { Self {
x, x,
z, z,
@ -228,9 +227,9 @@ impl ProtoChunk {
* biome_coords::from_block(height as i32) as usize * biome_coords::from_block(height as i32) as usize
] ]
.into_boxed_slice(), .into_boxed_slice(),
flat_surface_height_map: default_heightmap.clone(), flat_surface_height_map: default_heightmap,
flat_ocean_floor_height_map: default_heightmap.clone(), flat_ocean_floor_height_map: default_heightmap,
flat_motion_blocking_height_map: default_heightmap.clone(), flat_motion_blocking_height_map: default_heightmap,
flat_motion_blocking_no_leaves_height_map: default_heightmap, flat_motion_blocking_no_leaves_height_map: default_heightmap,
structure_starts: FxHashMap::default(), structure_starts: FxHashMap::default(),
height, height,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -20,7 +20,6 @@ use crate::{
}; };
/// Straight exterior bridge segment (5 × 10 × 19). /// Straight exterior bridge segment (5 × 10 × 19).
#[derive(Clone)]
pub struct BridgePiece { pub struct BridgePiece {
pub piece: NetherFortressPiece, pub piece: NetherFortressPiece,
} }
@ -148,8 +147,4 @@ impl StructurePieceBase for BridgePiece {
chunk, &bb, false, 4, 1, 17, 4, 4, 17, fence_west, fence_west, 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). /// 4-way exterior bridge crossing (19 × 10 × 19).
/// Also serves as the generation start piece. /// Also serves as the generation start piece.
#[derive(Clone)]
pub struct BridgeCrossingPiece { pub struct BridgeCrossingPiece {
pub piece: NetherFortressPiece, 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). /// 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 /// Generated randomly on each side and used as a fallback when no valid piece
/// can be placed. /// can be placed.
#[derive(Clone)]
pub struct BridgeEndPiece { pub struct BridgeEndPiece {
pub piece: NetherFortressPiece, pub piece: NetherFortressPiece,
/// Seed used so the crumbling geometry is deterministic per world seed. /// 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). /// Exterior platform with a Blaze mob-spawner (7 × 8 × 9).
#[derive(Clone)]
pub struct BridgePlatformPiece { pub struct BridgePlatformPiece {
pub piece: NetherFortressPiece, pub piece: NetherFortressPiece,
pub has_blaze_spawner: bool, 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). /// Small 3-way crossing for the exterior bridge network (7 × 9 × 7).
#[derive(Clone)]
pub struct BridgeSmallCrossingPiece { pub struct BridgeSmallCrossingPiece {
pub piece: NetherFortressPiece, 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). /// Stairway that transitions from bridge level to interior level (7 × 11 × 7).
#[derive(Clone)]
pub struct BridgeStairsPiece { pub struct BridgeStairsPiece {
pub piece: NetherFortressPiece, 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). /// Interior corridor piece with a balcony overlook (9 × 7 × 9).
#[derive(Clone)]
pub struct CorridorBalconyPiece { pub struct CorridorBalconyPiece {
pub piece: NetherFortressPiece, 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). /// 4-way interior corridor crossing (5 × 7 × 5).
#[derive(Clone)]
pub struct CorridorCrossingPiece { pub struct CorridorCrossingPiece {
pub piece: NetherFortressPiece, 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). /// Large entrance tower connecting the bridge network to the interior (13 × 14 × 13).
#[derive(Clone)]
pub struct CorridorExitPiece { pub struct CorridorExitPiece {
pub piece: NetherFortressPiece, pub piece: NetherFortressPiece,
} }
@ -229,8 +228,4 @@ impl StructurePieceBase for CorridorExitPiece {
// chunk.schedule_fluid_tick(&lava_pos, 0); // 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). /// Interior corridor that turns left (NW), optionally containing a chest (5 × 7 × 5).
#[derive(Clone)]
pub struct CorridorLeftTurnPiece { pub struct CorridorLeftTurnPiece {
pub piece: NetherFortressPiece, pub piece: NetherFortressPiece,
pub contains_chest: bool, 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). /// Large interior room with nether wart crops (13 × 14 × 13).
#[derive(Clone)]
pub struct CorridorNetherWartsRoomPiece { pub struct CorridorNetherWartsRoomPiece {
pub piece: NetherFortressPiece, 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). /// Interior corridor that turns right (SE), optionally containing a chest (5 × 7 × 5).
#[derive(Clone)]
pub struct CorridorRightTurnPiece { pub struct CorridorRightTurnPiece {
pub piece: NetherFortressPiece, pub piece: NetherFortressPiece,
pub contains_chest: bool, 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). /// Descending interior corridor staircase (5 × 14 × 10).
#[derive(Clone)]
pub struct CorridorStairsPiece { pub struct CorridorStairsPiece {
pub piece: NetherFortressPiece, 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_z = section_coords::section_to_block(context.chunk_z) + 2;
let start_piece = BridgeCrossingPiece::new_start(&mut random, start_x, start_z); 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< let mut pieces_to_process: Vec<
Box<dyn crate::generation::structure::structures::StructurePieceBase>, 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 bridge_pieces = get_bridge_piece_weights();
let mut corridor_pieces = get_corridor_piece_weights(); let mut corridor_pieces = get_corridor_piece_weights();
collector.add_piece(start_box);
start_piece.fill_openings( start_piece.fill_openings(
&start_piece.piece.piece, &base_piece,
&mut random, &mut random,
&mut bridge_pieces, &mut bridge_pieces,
&mut corridor_pieces, &mut corridor_pieces,
@ -67,17 +65,22 @@ impl StructureGenerator for NetherFortressGenerator {
&mut pieces_to_process, &mut pieces_to_process,
); );
collector.add_piece(Box::new(start_piece));
while !pieces_to_process.is_empty() { while !pieces_to_process.is_empty() {
let idx = random.next_bounded_i32(pieces_to_process.len() as i32) as usize; let idx = random.next_bounded_i32(pieces_to_process.len() as i32) as usize;
let piece = pieces_to_process.remove(idx); let piece = pieces_to_process.remove(idx);
piece.fill_openings_nether( piece.fill_openings_nether(
&start_piece.piece.piece, &base_piece,
&mut random, &mut random,
&mut bridge_pieces, &mut bridge_pieces,
&mut corridor_pieces, &mut corridor_pieces,
&mut collector, &mut collector,
&mut pieces_to_process, &mut pieces_to_process,
); );
collector.add_piece(piece);
} }
Some(StructurePosition { Some(StructurePosition {
@ -619,7 +622,6 @@ impl NetherFortressPiece {
orientation, orientation,
self.piece.chain_length + 1, self.piece.chain_length + 1,
) { ) {
collector.add_piece(p.clone_box());
pieces_to_process.push(p); pieces_to_process.push(p);
} }
} }

View File

@ -17,7 +17,6 @@ use crate::{
/// Straight interior corridor segment (5 × 7 × 5). /// Straight interior corridor segment (5 × 7 × 5).
/// This is also the fallback piece returned by the generation logic. /// This is also the fallback piece returned by the generation logic.
#[derive(Clone)]
pub struct SmallCorridorPiece { pub struct SmallCorridorPiece {
pub piece: NetherFortressPiece, 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 { struct NetherFossilPiece {
shiftable_structure_piece: ShiftableStructurePiece, shiftable_structure_piece: ShiftableStructurePiece,
template: Arc<StructureTemplate>, template: Arc<StructureTemplate>,
@ -156,10 +155,6 @@ impl NetherFossilPiece {
} }
impl StructurePieceBase for NetherFossilPiece { impl StructurePieceBase for NetherFossilPiece {
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new(self.clone())
}
fn place( fn place(
&mut self, &mut self,
chunk: &mut ProtoChunk, chunk: &mut ProtoChunk,

View File

@ -19,7 +19,6 @@ use crate::{
}, },
}; };
#[derive(Clone)]
pub struct ChestCorridorPiece { pub struct ChestCorridorPiece {
pub piece: StrongholdPiece, pub piece: StrongholdPiece,
pub chest_generated: bool, 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 { pub struct CorridorPiece {
piece: StrongholdPiece, piece: StrongholdPiece,
left_exit: bool, 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); 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 struct SmallCorridorPiece {
pub piece: StrongholdPiece, pub piece: StrongholdPiece,
pub length: i32, pub length: i32,
@ -238,10 +232,6 @@ impl SmallCorridorPiece {
} }
impl StructurePieceBase for 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 { fn get_structure_piece(&self) -> &crate::generation::structure::structures::StructurePiece {
&self.piece.piece &self.piece.piece
} }

View File

@ -22,7 +22,6 @@ use crate::{
}, },
}; };
#[derive(Clone)]
pub struct FiveWayCrossingPiece { pub struct FiveWayCrossingPiece {
pub piece: StrongholdPiece, pub piece: StrongholdPiece,
pub lower_left_exists: bool, pub lower_left_exists: bool,
@ -313,8 +312,4 @@ impl StructurePieceBase for FiveWayCrossingPiece {
// let torch = Block::WALL_TORCH.default_state.with("facing", "south"); // let torch = Block::WALL_TORCH.default_state.with("facing", "south");
// inner.add_block(chunk, &torch, 6, 5, 6, &box_limit); // 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 struct LeftTurnPiece {
pub piece: StrongholdPiece, 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); 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 struct LibraryPiece {
pub piece: StrongholdPiece, pub piece: StrongholdPiece,
pub tall: bool, pub tall: bool,
@ -237,8 +236,4 @@ impl StructurePieceBase for LibraryPiece {
// inner.add_chest(chunk, &box_limit, random, 12, 8, 1, "stronghold_library"); // 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 last_piece_type: Option<StrongholdPieceType> = None;
let mut has_portal_room = false; 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_x = section_coords::section_to_block(context.chunk_x) + 2;
let start_z = section_coords::section_to_block(context.chunk_z) + 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_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' // In Vanilla, pieces_to_process is 'start.pieces'
let mut pieces_to_process: Vec<Box<dyn StructurePieceBase>> = Vec::new(); let mut pieces_to_process: Vec<Box<dyn StructurePieceBase>> = Vec::new();
collector.add_piece(start_box);
// Initial Fill // Initial Fill
start_piece.fill_openings( start_piece.fill_openings(
&start_piece.piece.piece, &base_piece,
&mut random, &mut random,
&mut weights, &mut weights,
&mut last_piece_type, &mut last_piece_type,
@ -100,12 +92,15 @@ impl StructureGenerator for StrongholdGenerator {
&mut pieces_to_process, &mut pieces_to_process,
); );
collector.add_piece(Box::new(start_piece));
// Growth Loop // Growth Loop
while !pieces_to_process.is_empty() { while !pieces_to_process.is_empty() {
let idx = random.next_bounded_i32(pieces_to_process.len() as i32) as usize; let idx = random.next_bounded_i32(pieces_to_process.len() as i32) as usize;
let piece = pieces_to_process.remove(idx); let piece = pieces_to_process.remove(idx);
piece.fill_openings( piece.fill_openings(
&start_piece.piece.piece, &base_piece,
&mut random, &mut random,
&mut weights, &mut weights,
&mut last_piece_type, &mut last_piece_type,
@ -113,6 +108,9 @@ impl StructureGenerator for StrongholdGenerator {
&mut collector, &mut collector,
&mut pieces_to_process, &mut pieces_to_process,
); );
// Move into collector AFTER processing its children
collector.add_piece(piece);
} }
// Shift height // Shift height
@ -252,7 +250,6 @@ fn get_initial_weights() -> Vec<PieceWeight> {
] ]
} }
#[derive(Clone)]
pub struct StrongholdPiece { pub struct StrongholdPiece {
pub piece: StructurePiece, pub piece: StructurePiece,
pub entry_door: EntranceType, pub entry_door: EntranceType,
@ -617,7 +614,6 @@ impl StrongholdPiece {
}; };
if let Some(p) = next_piece { if let Some(p) = next_piece {
collector.add_piece(p.clone_box());
return Some(p); return Some(p);
} }

View File

@ -26,7 +26,6 @@ use crate::{
}, },
}; };
#[derive(Clone)]
pub struct PortalRoomPiece { pub struct PortalRoomPiece {
pub piece: StrongholdPiece, pub piece: StrongholdPiece,
pub spawner_placed: bool, 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 { pub struct PrisonHallPiece {
piece: StrongholdPiece, 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_bottom, 4, 1, 8, &box_limit);
// inner.add_block(chunk, &door_top, 4, 2, 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 struct RightTurnPiece {
pub piece: StrongholdPiece, 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); 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 portal_room_exists: bool,
pub pieces: Vec<Box<dyn StructurePieceBase>>, 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 { impl SpiralStaircasePiece {
/// Matches Start(Random random, int i, int j) /// Matches Start(Random random, int i, int j)
@ -102,10 +88,6 @@ impl SpiralStaircasePiece {
} }
impl StructurePieceBase for SpiralStaircasePiece { impl StructurePieceBase for SpiralStaircasePiece {
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new((*self).clone())
}
fn get_structure_piece(&self) -> &StructurePiece { fn get_structure_piece(&self) -> &StructurePiece {
&self.piece.piece &self.piece.piece
} }

View File

@ -22,7 +22,6 @@ use crate::{
}, },
}; };
#[derive(Clone)]
pub struct SquareRoomPiece { pub struct SquareRoomPiece {
piece: StrongholdPiece, piece: StrongholdPiece,
room_type: u32, 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 struct StairsPiece {
pub piece: StrongholdPiece, 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 { pub struct SwampHutPiece {
shiftable_structure_piece: ShiftableStructurePiece, shiftable_structure_piece: ShiftableStructurePiece,
} }
impl StructurePieceBase for SwampHutPiece { impl StructurePieceBase for SwampHutPiece {
fn clone_box(&self) -> Box<dyn StructurePieceBase> {
Box::new(self.clone())
}
fn place( fn place(
&mut self, &mut self,
chunk: &mut ProtoChunk, chunk: &mut ProtoChunk,

View File

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

View File

@ -61,7 +61,7 @@ pub type SyncEntityChunk = Arc<ChunkEntityData>;
pub struct Level { pub struct Level {
pub seed: Seed, pub seed: Seed,
pub world_portal: ArcSwap<Option<Arc<dyn WorldPortalExt>>>, pub world_portal: ArcSwap<Option<Arc<dyn WorldPortalExt>>>,
pub level_folder: LevelFolder, pub level_folder: Arc<LevelFolder>,
pub lighting_config: LightingEngineConfig, pub lighting_config: LightingEngineConfig,
/// Counts the number of ticks that have been scheduled for this world /// Counts the number of ticks that have been scheduled for this world
@ -117,26 +117,12 @@ pub struct RandomTickSample {
pub tick_fluid: bool, pub tick_fluid: bool,
} }
#[derive(Clone)]
pub struct LevelFolder { pub struct LevelFolder {
pub root_folder: PathBuf, pub root_folder: PathBuf,
pub region_folder: PathBuf, pub region_folder: PathBuf,
pub entities_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 { impl Level {
#[must_use] #[must_use]
pub fn from_root_folder( 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(&region_folder).expect("Failed to create Region folder");
std::fs::create_dir_all(&entities_folder).expect("Failed to create Entities 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, root_folder,
region_folder, region_folder,
entities_folder, entities_folder,
}; });
let seed = Seed(seed as u64); let seed = Seed(seed as u64);
let world_gen = get_world_gen(seed, dimension).into(); 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 { pub fn is_chunk_loaded(&self, coordinates: &Vector2<i32>) -> bool {
self.loaded_chunks.contains_key(coordinates) self.loaded_chunks.contains_key(coordinates)
} }
@ -784,6 +764,16 @@ impl Level {
self.loaded_chunks.get(coordinates).map(|x| f(x.value())) 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 { pub fn get_rough_biome(&self, position: &BlockPos) -> &'static Biome {
let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position(); let (chunk_coordinate, relative) = position.chunk_and_chunk_relative_position();
let id = self.read_chunk_sync(&chunk_coordinate, |chunk| { 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) 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> { pub fn get_entity_chunk_sync(&self, pos: &Vector2<i32>) -> Option<SyncEntityChunk> {
self.loaded_entity_chunks self.loaded_entity_chunks
.get(pos) .get(pos)

View File

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

View File

@ -155,7 +155,7 @@ impl CrashReport {
eprintln!( eprintln!(
"{}\n{}", "{}\n{}",
RED.console_color("Backtrace:"), 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) { 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.fall_flying.store(fall_flying, Relaxed);
self.set_flag(Flag::FallFlying, fall_flying).await; 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); let new_state_id = block.from_properties(&props).to_state_id(block);
world world
.set_block_state(&location, new_state_id, BlockFlags::empty()) .set_block_state(&location, new_state_id, BlockFlags::NOTIFY_LISTENERS)
.await; .await;
// Consume one item.
item.decrement(1);
world.sync_world_event(WorldEvent::EndPortalFrameFill, location, 0); world.sync_world_event(WorldEvent::EndPortalFrameFill, location, 0);
// Try to complete the portal. // Try to complete the portal.
EndPortal::get_new_portal(&world, location).await; 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> { fn can_be_lit(block: &Block, state_id: u16) -> Option<u16> {
let mut props = match &block.properties(state_id) { let mut props = {
Some(props) => props.to_props(), let props = &block.properties(state_id)?;
None => return None, props.to_props()
}; };
if let Some((_, value)) = props.iter_mut().find(|(k, _)| *k == "extinguished") { 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_split_number: AtomicU16,
output_sequenced_index: AtomicU32, output_sequenced_index: AtomicU32,
output_ordered_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. /// An notifier that is triggered when this client is closed.
close_token: CancellationToken, close_token: CancellationToken,
last_seen: Arc<AtomicCell<std::time::Instant>>, last_seen: Arc<AtomicCell<std::time::Instant>>,
@ -133,6 +135,7 @@ impl BedrockClient {
output_split_number: AtomicU16::new(0), output_split_number: AtomicU16::new(0),
output_sequenced_index: AtomicU32::new(0), output_sequenced_index: AtomicU32::new(0),
output_ordered_index: AtomicU32::new(0), output_ordered_index: AtomicU32::new(0),
next_form_id: AtomicU32::new(0),
compounds: Arc::new(Mutex::new(HashMap::new())), compounds: Arc::new(Mutex::new(HashMap::new())),
close_token: CancellationToken::new(), close_token: CancellationToken::new(),
last_seen: Arc::new(AtomicCell::new(std::time::Instant::now())), last_seen: Arc::new(AtomicCell::new(std::time::Instant::now())),
@ -789,6 +792,16 @@ impl BedrockClient {
SAnimate::PACKET_ID => { SAnimate::PACKET_ID => {
self.handle_animate(player, server, &SAnimate::read(reader)?); 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 => { SLoadingScreen::PACKET_ID => {
// Ignore for now // 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 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!( info!(
"LAN broadcast running on {}", "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 changed_main_hand;
pub mod egg_throw; pub mod egg_throw;
pub mod exp_change; pub mod exp_change;

View File

@ -11,8 +11,8 @@ use crate::plugin::{
}, },
player::to_wit_item_stack, player::to_wit_item_stack,
pumpkin::plugin::event::{ pumpkin::plugin::event::{
Event, InteractAction as WasmInteractAction, InventoryClickEventData, BedrockFormResponseEventData, Event, InteractAction as WasmInteractAction,
InventoryCloseEventData, PlayerChangeWorldEventData, InventoryClickEventData, InventoryCloseEventData, PlayerChangeWorldEventData,
PlayerChangedMainHandEventData, PlayerChatEventData, PlayerCommandSendEventData, PlayerChangedMainHandEventData, PlayerChatEventData, PlayerCommandSendEventData,
PlayerCustomPayloadEventData, PlayerEggThrowEventData, PlayerExpChangeEventData, PlayerCustomPayloadEventData, PlayerEggThrowEventData, PlayerExpChangeEventData,
PlayerFishEventData, PlayerFishState as WasmPlayerFishState, PlayerFishEventData, PlayerFishState as WasmPlayerFishState,
@ -26,6 +26,7 @@ use crate::plugin::{
}, },
}, },
player::{ player::{
bedrock_form_response::BedrockFormResponseEvent,
changed_main_hand::PlayerChangedMainHandEvent, changed_main_hand::PlayerChangedMainHandEvent,
egg_throw::PlayerEggThrowEvent, egg_throw::PlayerEggThrowEvent,
exp_change::PlayerExpChangeEvent, 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 context;
pub mod entity; pub mod entity;
pub mod events; pub mod events;
pub mod forms;
pub mod generated_packets; pub mod generated_packets;
pub mod gui; pub mod gui;
pub mod i18n; pub mod i18n;

View File

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

View File

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