feat: add bedrock forms
This commit is contained in:
parent
f383c88c76
commit
5aafff35e8
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -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",
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
@ -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);
|
||||
|
||||
@ -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" }
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
pub mod bedrock_form_response;
|
||||
pub mod changed_main_hand;
|
||||
pub mod egg_throw;
|
||||
pub mod exp_change;
|
||||
|
||||
218
pumpkin-plugin-api/src/forms.rs
Normal file
218
pumpkin-plugin-api/src/forms.rs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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.
|
||||
|
||||
@ -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),
|
||||
|
||||
54
pumpkin-plugin-wit/v0.1/forms.wit
Normal file
54
pumpkin-plugin-wit/v0.1/forms.wit
Normal 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;
|
||||
}
|
||||
@ -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 ---
|
||||
|
||||
|
||||
@ -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::*;
|
||||
|
||||
@ -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,
|
||||
}
|
||||
@ -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;
|
||||
|
||||
18
pumpkin-protocol/src/bedrock/server/modal_form_response.rs
Normal file
18
pumpkin-protocol/src/bedrock/server/modal_form_response.rs
Normal 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 })
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -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]>,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -79,8 +79,6 @@ harness = false
|
||||
name = "noise_router"
|
||||
harness = false
|
||||
|
||||
[features]
|
||||
tokio_taskdump = []
|
||||
|
||||
# [lints]
|
||||
# workspace = true
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
#[derive(Clone)]
|
||||
pub struct CarvingMask {
|
||||
min_y: i32,
|
||||
mask: Vec<u64>,
|
||||
|
||||
@ -12,7 +12,6 @@ pub struct SpringFeatureFeature {
|
||||
pub valid_blocks: BlockWrapper,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum BlockWrapper {
|
||||
Single(String),
|
||||
Multi(Vec<String>),
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -472,7 +472,6 @@ impl FlatCache {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Cache2D {
|
||||
pub(crate) input_index: usize,
|
||||
last_sample_column: u64,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -43,7 +43,6 @@ pub trait StaticIndependentChunkNoiseFunctionComponentImpl: NoiseFunctionCompone
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Wrapper {
|
||||
pub input_index: usize,
|
||||
pub wrapper_type: WrapperType,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -7,7 +7,6 @@ use crate::generation::noise::router::{
|
||||
density_function::NoiseFunctionComponentRange,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FindTopSurface {
|
||||
density_index: usize,
|
||||
upper_bound_index: usize,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -10,7 +10,6 @@ use crate::{
|
||||
|
||||
pub const DEFAULT_H_POS: i32 = -1;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ShiftableStructurePiece {
|
||||
pub piece: StructurePiece,
|
||||
h_pos: i32,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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(®ion_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)
|
||||
|
||||
@ -115,7 +115,6 @@ tempfile.workspace = true
|
||||
|
||||
[features]
|
||||
console-subscriber = ["dep:console-subscriber"]
|
||||
tokio_taskdump = ["pumpkin-world/tokio_taskdump"]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@ -155,7 +155,7 @@ impl CrashReport {
|
||||
eprintln!(
|
||||
"{}\n{}",
|
||||
RED.console_color("Backtrace:"),
|
||||
&self.captured_backtrace
|
||||
self.captured_backtrace
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -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") {
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 {}",
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
pub mod bedrock_form_response;
|
||||
pub mod changed_main_hand;
|
||||
pub mod egg_throw;
|
||||
pub mod exp_change;
|
||||
|
||||
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
129
pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/forms.rs
Normal file
129
pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/forms.rs
Normal 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
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user