├── .gitignore
├── .rustfmt.toml
├── Cargo.toml
├── LICENSE
├── README.md
├── assets
├── blocks.json
├── dimension.snbt
├── dimension_codec.snbt
├── items.json
├── mappings.json
├── protocol.json
└── types.json
├── chat
├── Cargo.toml
└── src
│ ├── builder.rs
│ ├── color.rs
│ ├── component.rs
│ └── lib.rs
├── datapack
├── Cargo.toml
├── src
│ ├── data
│ │ ├── advancement
│ │ │ ├── conditions.rs
│ │ │ ├── display.rs
│ │ │ └── mod.rs
│ │ ├── datatypes.rs
│ │ ├── functions.rs
│ │ ├── item_modifiers.rs
│ │ ├── loot_tables.rs
│ │ ├── mod.rs
│ │ ├── predicate.rs
│ │ ├── recipe
│ │ │ ├── cooking.rs
│ │ │ ├── ingredient.rs
│ │ │ ├── mod.rs
│ │ │ ├── recipe.rs
│ │ │ ├── shaped.rs
│ │ │ ├── shapeless.rs
│ │ │ ├── smithing.rs
│ │ │ └── stonecutting.rs
│ │ ├── structure.rs
│ │ ├── tags.rs
│ │ └── world_gen
│ │ │ ├── biome.rs
│ │ │ ├── carvers.rs
│ │ │ ├── density_function.rs
│ │ │ ├── dimension.rs
│ │ │ ├── dimension_type.rs
│ │ │ ├── features.rs
│ │ │ ├── jigsaw_pool.rs
│ │ │ ├── mod.rs
│ │ │ ├── noise.rs
│ │ │ ├── noise_settings.rs
│ │ │ ├── processors.rs
│ │ │ ├── structure_features.rs
│ │ │ ├── structure_set.rs
│ │ │ └── surface_builders.rs
│ ├── datapack.rs
│ └── lib.rs
└── tests
│ └── default_datapack.rs
├── macros
├── Cargo.toml
└── src
│ └── lib.rs
├── macros_impl
├── Cargo.toml
└── src
│ ├── lib.rs
│ └── packet
│ ├── gen.rs
│ ├── mod.rs
│ └── parse.rs
├── network
├── Cargo.toml
├── build.rs
└── src
│ ├── bitmask.rs
│ ├── lib.rs
│ ├── netutil.rs
│ └── packet_data.rs
├── qdat
├── Cargo.toml
├── build.rs
├── buildscript
│ ├── blockstate.rs
│ ├── item_info.rs
│ ├── items.rs
│ └── mod.rs
└── src
│ ├── block
│ ├── behavior
│ │ └── mod.rs
│ ├── mod.rs
│ └── states.rs
│ ├── item
│ ├── item.rs
│ ├── item_info.rs
│ └── mod.rs
│ ├── lib.rs
│ ├── uln.rs
│ └── world
│ ├── lighting.rs
│ ├── location.rs
│ └── mod.rs
├── quartz
├── Cargo.toml
├── build.rs
├── format.sh
└── src
│ ├── base
│ ├── assets.rs
│ ├── config.rs
│ ├── diagnostic.rs
│ ├── exec.rs
│ ├── mod.rs
│ ├── registries.rs
│ ├── server.rs
│ └── static_registry.rs
│ ├── block
│ ├── entities
│ │ └── furnace_entity.rs
│ ├── entity.rs
│ ├── init.rs
│ ├── mod.rs
│ └── state.rs
│ ├── command
│ ├── context.rs
│ ├── executor.rs
│ └── mod.rs
│ ├── entities
│ ├── mod.rs
│ └── player.rs
│ ├── item
│ ├── init.rs
│ ├── inventory.rs
│ ├── item_stack.rs
│ └── mod.rs
│ ├── lib.rs
│ ├── main.rs
│ ├── network
│ ├── connection.rs
│ ├── handler.rs
│ ├── mod.rs
│ └── packet.rs
│ ├── scheduler
│ ├── mod.rs
│ └── task.rs
│ └── world
│ ├── chunk
│ ├── chunk.rs
│ ├── error.rs
│ ├── gen
│ │ ├── density_function.rs
│ │ └── mod.rs
│ ├── palette.rs
│ ├── provider.rs
│ ├── section.rs
│ └── states.rs
│ ├── mod.rs
│ └── world.rs
├── rust-toolchain.toml
└── util
├── Cargo.toml
└── src
├── hash.rs
├── lib.rs
├── logging.rs
├── map.rs
├── math.rs
├── single_access.rs
└── variant.rs
/.gitignore:
--------------------------------------------------------------------------------
1 | # Decompiled, mapped Minecraft Java source
2 | source/
3 |
4 | # Server generated files
5 | run/
6 | logs/
7 | plugins/
8 | config.json
9 |
10 | # Pre-build artifacts
11 | node_modules/
12 | parser.js
13 |
14 | # Cargo and rustc artifacts
15 | target/
16 | Cargo.lock
17 | **/*.rs.bk
18 |
19 | # IDE files
20 | .vscode/
--------------------------------------------------------------------------------
/.rustfmt.toml:
--------------------------------------------------------------------------------
1 | edition = "2021"
2 | tab_spaces = 4
3 | use_field_init_shorthand = true
4 | use_try_shorthand = true
5 | max_width = 100
6 | use_small_heuristics = "Default"
7 | fn_args_layout = "Tall"
8 |
9 | unstable_features = true
10 |
11 | blank_lines_upper_bound = 2
12 | brace_style = "SameLineWhere"
13 | condense_wildcard_suffixes = true
14 | control_brace_style = "AlwaysSameLine"
15 | format_strings = true
16 | imports_layout = "HorizontalVertical"
17 | match_arm_blocks = false
18 | imports_granularity = "Crate"
19 | overflow_delimited_expr = true
20 | reorder_impl_items = true
21 | space_after_colon = true
22 | spaces_around_ranges = true
23 | trailing_semicolon = true
24 | where_single_line = true
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = [
3 | "chat",
4 | "quartz",
5 | "macros",
6 | "macros_impl",
7 | "util",
8 | "qdat",
9 | "network",
10 | "datapack"
11 | ]
12 |
13 | [profile.release]
14 | lto = "fat"
15 | debug = true
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Cassy343, maddymakesgames
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Quartz
2 | A reimplementation of a minecraft server in rust.
3 | Currently working on supporting 1.18.2
4 |
5 | ## Mission Statement
6 |
7 | Be better than feather.
8 |
9 | ## Current Features
10 | - [x] Logging
11 | - [x] Console commands
12 | - [x] Server List ping
13 | - [x] Initial Join Game flow
14 | - [x] Chunk loading
15 |
16 | ## Planned Features
17 | We plan on trying to be as 1:1 to vanilla as we can with the exception of any current bugs in vanilla
18 | We also plan on having our own plugin system to allow extending the functionality of Quartz
19 |
20 | ## Related Repos
21 | [Quartz Proxy](https://github.com/Rusty-Quartz/quartz_proxy), used to test reading and writing of packets and to log data the vanilla server sends
22 | [Quartz Commands](https://github.com/Rusty-Quartz/quartz_commands), the command system used to handle console and in-game commands
23 | [Quartz NBT](https://github.com/Rusty-Quartz/quartz_nbt), our nbt and snbt crate
24 | [Quartz Data Generators](https://github.com/Rusty-Quartz/data-generator), a mod for Minecraft that exports JSON files to be used in our buildscripts
25 |
26 | ### Credits
27 | Packet info and minecraft datatypes from the [minecraft protocol wiki](https://wiki.vg/)
28 | Info about the format of datapacks taken from [Minsode's datapack generator](https://misode.github.io/)
29 | Other info taken from minecraft source deobfuscated using [parchment mappings](https://parchmentmc.org/), [quilt mappings](https://github.com/QuiltMC/quilt-mappings), and Mojang's own mappings
30 |
--------------------------------------------------------------------------------
/assets/dimension.snbt:
--------------------------------------------------------------------------------
1 | {
2 | min_y: 0b,
3 | natural: 1b,
4 | has_raids: 1b,
5 | respawn_anchor_works: 0b,
6 | has_skylight: 1b,
7 | has_ceiling: 0b,
8 | logical_height: 256s,
9 | height: 256s,
10 | effects: "minecraft:overworld",
11 | bed_works: 1b,
12 | infiniburn: "minecraft:infiniburn_overworld",
13 | piglin_safe: 0b,
14 | ambient_light: 0.0f,
15 | coordinate_scale: 1.0f,
16 | ultrawarm: 0b
17 | }
--------------------------------------------------------------------------------
/assets/mappings.json:
--------------------------------------------------------------------------------
1 | {
2 | "types": {
3 | "varint": "i32",
4 | "varlong": "i64",
5 | "string": "String",
6 | "unlocalized_name": "qdat::UnlocalizedName",
7 | "uuid": "uuid::Uuid",
8 | "nbt_tag": "quartz_nbt::NbtCompound",
9 | "box_nbt_tag": "Box",
10 | "position": "qdat::world::location::BlockPosition",
11 | "slot": "quartz_net::packet_data::Slot",
12 | "chat": "Box",
13 | "tab_complete_match": "quartz_net::packet_data::TabCompleteMatch",
14 | "statistic": "quartz_net::packet_data::Statistic",
15 | "block_lights": "qdat::world::lighting::LightBuffer",
16 | "map_icon": "quartz_net::packet_data::MapIcon",
17 | "villager_trade": "quartz_net::packet_data::VillagerTrade",
18 | "entity_metadata_wrapper": "quartz_net::packet_data::EntityMetadataWrapper",
19 | "inventory_slot": "quartz_net::packet_data::InventorySlot",
20 | "equipment_slot": "quartz_net::packet_data::EquipmentSlot",
21 | "advancement_map_element": "quartz_net::packet_data::AdvancementMapElement",
22 | "advancement_progress_map_element": "quartz_net::packet_data::AdvancementProgressMapElement",
23 | "advancement": "quartz_net::packet_data::Advancement",
24 | "advancement_requirements": "quartz_net::packet_data::AdvancementRequirements",
25 | "advancement_progress": "quartz_net::packet_data::AdvancementProgress",
26 | "advancement_progress_criteria": "quartz_net::packet_data::AdvancementProgressCriteria",
27 | "criteria_progress": "quartz_net::packet_data::CriteriaProgress",
28 | "advancement_display": "quartz_net::packet_data::AdvancementDisplay",
29 | "entity_property": "quartz_net::packet_data::EntityProperty",
30 | "attribute_modifier": "quartz_net::packet_data::AttributeModifier",
31 | "recipe": "quartz_net::packet_data::Recipe",
32 | "entity_metadata": "quartz_net::packet_data::EntityMetadata",
33 | "particle": "quartz_net::packet_data::ParticleData",
34 | "wrapped_player_info_action": "quartz_net::packet_data::WrappedPlayerInfoAction",
35 | "player_info_action": "quartz_net::packet_data::PlayerInfoAction",
36 | "tag_array": "quartz_net::packet_data::TagArray",
37 | "section_data": "quartz_net::packet_data::SectionData",
38 | "jigsaw_update_data": "Box",
39 | "mask": "quartz_net::BitMask",
40 | "gamemode": "qdat::Gamemode",
41 | "facing": "qdat::world::location::BlockFace"
42 | },
43 | "primitives": [
44 | "bool",
45 | "i8",
46 | "u8",
47 | "i16",
48 | "u16",
49 | "i32",
50 | "i64",
51 | "f32",
52 | "f64",
53 | "u128",
54 | "usize",
55 | "varint",
56 | "varlong",
57 | "uuid"
58 | ],
59 | "variable_repr": [
60 | "varint",
61 | "varlong"
62 | ]
63 | }
--------------------------------------------------------------------------------
/chat/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "quartz_chat"
3 | version = "0.1.0"
4 | authors = ["Ian Smith "]
5 | edition = "2021"
6 |
7 | [dependencies]
8 | bitflags = "1.3.2"
9 | serde = { version = "1.0", features = ["derive"] }
10 | serde_json = "1.0"
11 | serde_with = "1.9"
12 | quartz_nbt = "0.2.5"
13 |
14 | [target.'cfg(unix)'.dependencies]
15 | termion = "1.5"
--------------------------------------------------------------------------------
/chat/src/builder.rs:
--------------------------------------------------------------------------------
1 | use crate::{color::Color, component::*};
2 |
3 | /// Utility struct for building text components. Components can have children, and those children
4 | /// can have children, etc., however this utility only allows for a base component with a list of
5 | /// standalone child components. A new child with different formatting can be appended via the `add`
6 | /// method, and the same methods for adjusting color and formatting can be used to modify that child
7 | /// component.
8 | pub struct ComponentBuilder {
9 | component: Component,
10 | current_empty: bool,
11 | }
12 |
13 | impl ComponentBuilder {
14 | /// Creates a builder whose base component has no color and an empty text field.
15 | pub const fn empty() -> Self {
16 | ComponentBuilder {
17 | component: Component::empty(),
18 | current_empty: false,
19 | }
20 | }
21 |
22 | /// Creates a builder whose base component has no text and the color white.
23 | pub fn new() -> Self {
24 | ComponentBuilder {
25 | component: Component::colored(String::new(), Color::White),
26 | current_empty: false,
27 | }
28 | }
29 |
30 | /// Retrieves the current component being built.
31 | fn current(&mut self) -> &mut Component {
32 | self.current_empty = false;
33 |
34 | if self.component.has_children() {
35 | // These unwraps are checked above
36 | self.component.extra.as_mut().unwrap().last_mut().unwrap()
37 | } else {
38 | &mut self.component
39 | }
40 | }
41 |
42 | /// Finish the current component and prepare a new component which can have a different color, formatting, etc.
43 | #[allow(clippy::should_implement_trait)]
44 | pub fn add(mut self, component_type: ComponentType) -> Self {
45 | self.current().component_type = component_type;
46 | self.add_empty()
47 | }
48 |
49 | /// Finish the current component, setting its text field to the given value, and prepare a new component
50 | /// which can have a different color, formatting, etc.
51 | pub fn add_text(self, text: T) -> Self {
52 | self.add(ComponentType::text(text))
53 | }
54 |
55 | /// Finish the current component, setting its text field to an empty string, and prepare a new component
56 | /// which can have a different color, formatting, etc.
57 | pub fn add_empty(mut self) -> Self {
58 | if !self.current_empty {
59 | self.component.add_child(Component::empty());
60 | self.current_empty = true;
61 | }
62 |
63 | self
64 | }
65 |
66 | /// Consumes this builder and returns a finished text component.
67 | pub fn build(mut self) -> Component {
68 | if self.current_empty {
69 | let _ = self.component.extra.as_mut().unwrap().pop();
70 | }
71 |
72 | self.component
73 | }
74 |
75 | /// Consumes this builder and returns a vec of the base component's children, excluding the base
76 | /// component itself.
77 | pub fn build_children(mut self) -> Vec {
78 | if self.current_empty {
79 | let _ = self.component.extra.as_mut().unwrap().pop();
80 | }
81 |
82 | match self.component.extra {
83 | Some(children) => children,
84 | None => Vec::new(),
85 | }
86 | }
87 |
88 | /// Set the color of the current component to the given color.
89 | pub fn color>(mut self, color: C) -> Self {
90 | self.current().color = Some(color.into());
91 | self
92 | }
93 |
94 | /// Set the color of the current component to a custom color.
95 | pub fn custom_color(mut self, red: u8, green: u8, blue: u8) -> Self {
96 | self.current().color = Some(Color::Custom(red, green, blue));
97 | self
98 | }
99 |
100 | /// Set the format of the current component according to the given flags.
101 | pub fn format(mut self, format: Format) -> Self {
102 | self.current().format = format;
103 | self
104 | }
105 |
106 | /// Set the font of this component.
107 | pub fn font(mut self, font: Font) -> Self {
108 | self.current().font = Some(font);
109 | self
110 | }
111 |
112 | /// Set the insertion text of this component (the text inserted into a player's chat when
113 | /// they shift-click this component).
114 | pub fn insertion(mut self, insertion: String) -> Self {
115 | self.current().insertion = Some(insertion);
116 | self
117 | }
118 |
119 | /// Set the click event of this component.
120 | pub fn click_event(mut self, event: ClickEvent) -> Self {
121 | self.current().click_event = Some(Box::new(event));
122 | self
123 | }
124 |
125 | /// Set the hover event of this component.
126 | pub fn hover_event(mut self, event: HoverEvent) -> Self {
127 | self.current().hover_event = Some(Box::new(event));
128 | self
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/chat/src/color.rs:
--------------------------------------------------------------------------------
1 | #[cfg(unix)]
2 | use std::fmt::{self, Formatter};
3 |
4 | use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
5 |
6 | #[cfg(unix)]
7 | use termion::{color, style};
8 |
9 | /// Highest level definition of a chat color which can either be predefined or custom as of minecraft
10 | /// 1.16.
11 | #[derive(Debug, Clone, Copy, PartialEq, Eq)]
12 | #[doc(hidden)]
13 | pub enum Color {
14 | Black,
15 | DarkBlue,
16 | DarkGreen,
17 | DarkAqua,
18 | DarkRed,
19 | DarkPurple,
20 | Gold,
21 | Gray,
22 | DarkGray,
23 | Blue,
24 | Green,
25 | Aqua,
26 | Red,
27 | LightPurple,
28 | Yellow,
29 | White,
30 | Reset,
31 | /// A custom RBG color.
32 | Custom(u8, u8, u8),
33 | }
34 |
35 | impl Serialize for Color {
36 | fn serialize(&self, serializer: S) -> Result
37 | where S: Serializer {
38 | let string = match self {
39 | Color::Black => "black",
40 | Color::DarkBlue => "dark_blue",
41 | Color::DarkGreen => "dark_green",
42 | Color::DarkAqua => "dark_aqua",
43 | Color::DarkRed => "dark_red",
44 | Color::DarkPurple => "dark_purple",
45 | Color::Gold => "gold",
46 | Color::Gray => "gray",
47 | Color::DarkGray => "dark_gray",
48 | Color::Blue => "blue",
49 | Color::Green => "green",
50 | Color::Aqua => "aqua",
51 | Color::Red => "red",
52 | Color::LightPurple => "light_purple",
53 | Color::Yellow => "yellow",
54 | Color::White => "white",
55 | Color::Reset => "reset",
56 | &Color::Custom(r, g, b) => {
57 | return serializer.serialize_str(&format!(
58 | "#{:06X}",
59 | (r as u32) << 16 | (g as u32) << 8 | (b as u32)
60 | ));
61 | }
62 | };
63 |
64 | serializer.serialize_str(string)
65 | }
66 | }
67 |
68 | impl<'de> Deserialize<'de> for Color {
69 | fn deserialize(deserializer: D) -> Result
70 | where D: Deserializer<'de> {
71 | let string: &'de str = Deserialize::deserialize(deserializer)?;
72 |
73 | match string {
74 | "black" => Ok(Color::Black),
75 | "dark_blue" => Ok(Color::DarkBlue),
76 | "dark_green" => Ok(Color::DarkGreen),
77 | "dark_aqua" => Ok(Color::DarkAqua),
78 | "dark_red" => Ok(Color::DarkRed),
79 | "dark_purple" => Ok(Color::DarkPurple),
80 | "gold" => Ok(Color::Gold),
81 | "gray" => Ok(Color::Gray),
82 | "dark_gray" => Ok(Color::DarkGray),
83 | "blue" => Ok(Color::Blue),
84 | "green" => Ok(Color::Green),
85 | "aqua" => Ok(Color::Aqua),
86 | "red" => Ok(Color::Red),
87 | "light_purple" => Ok(Color::LightPurple),
88 | "yellow" => Ok(Color::Yellow),
89 | "white" => Ok(Color::White),
90 | "reset" => Ok(Color::Reset),
91 | _ => {
92 | if string.is_empty() {
93 | return Err(de::Error::custom(
94 | "Expected hex color, found an empty string.",
95 | ));
96 | }
97 |
98 | if string.len() != 7 {
99 | return Err(de::Error::custom(
100 | "Expected hex color in the form of '#RRGGBB'",
101 | ));
102 | }
103 |
104 | if let Ok(rgb) = u32::from_str_radix(&string[1 ..], 16) {
105 | Ok(Color::Custom(
106 | (rgb >> 16) as u8,
107 | (rgb >> 8) as u8,
108 | rgb as u8,
109 | ))
110 | } else {
111 | Err(de::Error::custom(
112 | "Invalid hex color, expected 6 hexadecimal digits (0-F).",
113 | ))
114 | }
115 | }
116 | }
117 | }
118 | }
119 |
120 | impl Color {
121 | /// Applies the color to the terminal (unix only).
122 | #[cfg(unix)]
123 | pub fn apply(&self, f: &mut Formatter) -> fmt::Result {
124 | match self {
125 | Color::Black => write!(f, "{}", color::Fg(color::Black)),
126 | Color::DarkBlue => write!(f, "{}", color::Fg(color::Blue)),
127 | Color::DarkGreen => write!(f, "{}", color::Fg(color::Green)),
128 | Color::DarkAqua => write!(f, "{}", color::Fg(color::Cyan)),
129 | Color::DarkRed => write!(f, "{}", color::Fg(color::Red)),
130 | Color::DarkPurple => write!(f, "{}", color::Fg(color::Magenta)),
131 | Color::Gold => write!(f, "{}", color::Fg(color::Yellow)),
132 | Color::Gray => write!(f, "{}", color::Fg(color::White)),
133 | Color::DarkGray => write!(f, "{}", color::Fg(color::LightBlack)),
134 | Color::Blue => write!(f, "{}", color::Fg(color::LightBlue)),
135 | Color::Green => write!(f, "{}", color::Fg(color::LightGreen)),
136 | Color::Aqua => write!(f, "{}", color::Fg(color::LightCyan)),
137 | Color::Red => write!(f, "{}", color::Fg(color::LightRed)),
138 | Color::LightPurple => write!(f, "{}", color::Fg(color::LightMagenta)),
139 | Color::Yellow => write!(f, "{}", color::Fg(color::LightYellow)),
140 | Color::White => write!(f, "{}", color::Fg(color::LightWhite)),
141 | Color::Reset => write!(f, "{}{}", color::Fg(color::Reset), style::Reset),
142 | // Dividing by 43 maps the color to the correct ANSI range of [0,5]
143 | &Color::Custom(r, g, b) => write!(
144 | f,
145 | "{}",
146 | color::Fg(color::AnsiValue::rgb(r / 43, g / 43, b / 43))
147 | ),
148 | }
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/chat/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![warn(missing_docs)]
2 |
3 | //! Provides support for minecraft chat components.
4 |
5 | /// Contains component builders for in-code component creation.
6 | mod builder;
7 | /// Defines and handles the application of chat colors.
8 | pub mod color;
9 | /// Defines chat components and their variants.
10 | pub mod component;
11 |
12 | pub use builder::ComponentBuilder;
13 | pub use component::Component;
14 |
--------------------------------------------------------------------------------
/datapack/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "quartz_datapack"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 | qdat = { path = "../qdat" }
10 | quartz_chat = { path = "../chat" }
11 |
12 | quartz_nbt = { version = "0.2.4", features = ["serde"]}
13 |
14 | serde = {version = "1", features = ["derive"]}
15 | serde_json = "1"
--------------------------------------------------------------------------------
/datapack/src/data/advancement/display.rs:
--------------------------------------------------------------------------------
1 | use qdat::UnlocalizedName;
2 | use quartz_chat::Component;
3 | use serde::{Deserialize, Serialize};
4 |
5 | /// The display element of an [Advancement]
6 | #[derive(Serialize, Deserialize)]
7 | pub struct AdvancementDisplay {
8 | pub icon: Option,
9 | // TODO: this is a text component
10 | pub title: AdvancementDisplayText,
11 | pub frame: Option,
12 | pub background: Option,
13 | // Same as title
14 | pub description: AdvancementDisplayText,
15 | pub show_toast: bool,
16 | pub announce_to_chat: bool,
17 | pub hidden: bool,
18 | }
19 |
20 | /// The icon of an [AdvancementDisplay]
21 | #[derive(Serialize, Deserialize)]
22 | pub struct AdvancementIcon {
23 | pub item: UnlocalizedName,
24 | // This is actual snbt supposidly? idk I'll deal with it later
25 | // TODO: make this actual nbt
26 | pub nbt: Option,
27 | }
28 |
29 | #[derive(Serialize, Deserialize)]
30 | pub enum AdvancementFrame {
31 | #[serde(rename = "task")]
32 | Task,
33 | #[serde(rename = "challenge")]
34 | Challenge,
35 | #[serde(rename = "goal")]
36 | Goal,
37 | }
38 |
39 | #[derive(Serialize, Deserialize)]
40 | #[serde(untagged)]
41 | pub enum AdvancementDisplayText {
42 | String(String),
43 | TextComponent(Component),
44 | TextComponentList(Vec),
45 | // WHY THE FUCK IS THIS VALID
46 | Number(f32),
47 | Boolean(bool),
48 | }
49 |
--------------------------------------------------------------------------------
/datapack/src/data/advancement/mod.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashMap;
2 |
3 | use qdat::UnlocalizedName;
4 |
5 | use serde::{Deserialize, Serialize};
6 |
7 | pub mod conditions;
8 | pub mod display;
9 |
10 | use crate::data::advancement::conditions::AdvancementConditions;
11 |
12 | use self::display::AdvancementDisplay;
13 |
14 | /// An advancement
15 | #[derive(Serialize, Deserialize)]
16 | pub struct Advancement {
17 | pub display: Option,
18 | pub parent: Option,
19 | pub criteria: HashMap,
20 | pub requirements: Option,
21 | pub rewards: Option,
22 | }
23 |
24 | #[derive(Serialize, Deserialize)]
25 | #[serde(untagged)]
26 | pub enum AdvancementRequirements {
27 | /// A list of the required criteria
28 | List(Vec),
29 | /// A list of lists of criteria
30 | ///
31 | /// All of the lists only have to have one of their criteria met
32 | ///
33 | /// Basically ANDing of OR groups
34 | LogicalList(Vec>),
35 | }
36 |
37 | /// The rewards of an [Advancement]
38 | #[derive(Serialize, Deserialize)]
39 | pub struct AdvancementRewards {
40 | pub recipes: Option>,
41 | pub loot: Option>,
42 | pub experience: Option,
43 | pub function: Option,
44 | }
45 |
--------------------------------------------------------------------------------
/datapack/src/data/functions.rs:
--------------------------------------------------------------------------------
1 | use std::io::{Read, Write};
2 |
3 | /// A list of commands loaded from a mcfunction file
4 | ///
5 | /// This is just the contents of the file but with comments stripped
6 | pub type Function = Vec;
7 |
8 | /// Reads the lines of a string from `reader` and returns a Function after stripping the comments
9 | pub fn read_function(mut reader: T) -> std::io::Result {
10 | let mut buf = String::new();
11 | reader.read_to_string(&mut buf)?;
12 |
13 | Ok(buf
14 | .lines()
15 | .filter_map(|l| {
16 | // Filter out all comment lines
17 | if !l.starts_with('#') {
18 | Some(l.to_owned())
19 | } else {
20 | None
21 | }
22 | })
23 | .collect())
24 | }
25 |
26 | pub fn write_function(function: &Function, mut writer: T) -> std::io::Result<()> {
27 | write!(writer, "{}", function.join("\n"))
28 | }
29 |
--------------------------------------------------------------------------------
/datapack/src/data/item_modifiers.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashMap;
2 |
3 | use qdat::UnlocalizedName;
4 | use serde::{Deserialize, Serialize};
5 |
6 | use super::{
7 | datatypes::AmountOrRange,
8 | loot_tables::NumberProvider,
9 | noise_settings::BlockState,
10 | predicate::Predicate,
11 | };
12 |
13 | #[derive(Serialize, Deserialize)]
14 | pub struct ItemModifier {
15 | #[serde(flatten)]
16 | modifier: ItemModifierType,
17 | conditions: Vec,
18 | }
19 |
20 | #[derive(Serialize, Deserialize)]
21 | #[serde(rename_all = "snake_case")]
22 | pub enum ItemModifierType {
23 | ApplyBonus {
24 | enchantment: UnlocalizedName,
25 | formula: String,
26 | parameters: ApplyBonusParameters,
27 | },
28 | CopyName {
29 | source: String,
30 | },
31 | CopyNbt {
32 | source: NbtSource,
33 | ops: Vec,
34 | },
35 | CopyState(BlockState),
36 | EnchantRandomly {
37 | enchantments: Vec,
38 | },
39 | EnchantWithLevels {
40 | treasure: bool,
41 | levels: NumberProvider,
42 | },
43 | ExplorationMap {
44 | destination: String,
45 | decoration: String,
46 | zoom: Option,
47 | search_results: Option,
48 | skip_existing_chunks: Option,
49 | },
50 | ExplosionDelay,
51 | FurnaceSmelt,
52 | FillPlayerHead {
53 | entity: String,
54 | },
55 | LimitCount {
56 | limit: AmountOrRange>,
57 | },
58 | LootingEnchant {
59 | count: NumberProvider,
60 | limit: i32,
61 | },
62 | SetAttributes {
63 | modifiers: Vec,
64 | },
65 | SetBannerPatern {
66 | patterns: Vec,
67 | #[serde(default = "Default::default")]
68 | append: bool,
69 | },
70 | SetContents {
71 | // NOTE: no clue what datatype this is lol
72 | entries: Vec,
73 | },
74 | SetCount {
75 | count: NumberProvider,
76 | #[serde(default = "Default::default")]
77 | add: bool,
78 | },
79 | SetDamage {
80 | damage: NumberProvider,
81 | #[serde(default = "Default::default")]
82 | add: bool,
83 | },
84 | SetEnchantments {
85 | enchantments: HashMap>,
86 | #[serde(default = "Default::default")]
87 | add: bool,
88 | },
89 | SetLootTable {
90 | name: UnlocalizedName,
91 | #[serde(default = "Default::default")]
92 | seed: i32,
93 | },
94 | SetLore {
95 | // TODO: this is a json text component
96 | lore: Vec,
97 | entry: String,
98 | replace: bool,
99 | },
100 | SetName {
101 | // TODO: this is a json text component
102 | name: String,
103 | entity: String,
104 | },
105 | SetNbt {
106 | tag: String,
107 | },
108 | SetStewEffect {
109 | effects: Vec,
110 | },
111 | }
112 |
113 | #[derive(Serialize, Deserialize)]
114 | pub struct ApplyBonusParameters {
115 | extra: i32,
116 | probability: f32,
117 | #[serde(rename = "bonusMultiplier")]
118 | bonus_multiplier: f32,
119 | }
120 |
121 | #[derive(Serialize, Deserialize)]
122 | #[serde(rename_all = "snake_case")]
123 | #[serde(tag = "type")]
124 | pub enum NbtSource {
125 | Context { target: String },
126 | Storage { source: UnlocalizedName },
127 | }
128 |
129 | #[derive(Serialize, Deserialize)]
130 | pub struct NbtOpData {
131 | source: String,
132 | target: String,
133 | op: NbtOp,
134 | }
135 |
136 | #[derive(Serialize, Deserialize)]
137 | #[serde(rename_all = "snake_case")]
138 | pub enum NbtOp {
139 | Replace,
140 | Append,
141 | Merge,
142 | }
143 |
144 | #[derive(Serialize, Deserialize)]
145 | pub struct AttributeModifier {
146 | name: String,
147 | atrribute: String,
148 | operation: AttributeModifierOperation,
149 | amount: NumberProvider,
150 | id: Option,
151 | slot: AttributeModifierSlot,
152 | }
153 |
154 | #[derive(Serialize, Deserialize)]
155 | #[serde(rename_all = "snake_case")]
156 | pub enum AttributeModifierOperation {
157 | Addition,
158 | MultiplyBase,
159 | MultiplyTotal,
160 | }
161 |
162 | #[derive(Serialize, Deserialize)]
163 | #[serde(untagged)]
164 | pub enum AttributeModifierSlots {
165 | Singleton(AttributeModifierSlot),
166 | List(Vec),
167 | }
168 |
169 | #[derive(Serialize, Deserialize)]
170 | #[serde(rename_all = "lowercase")]
171 | pub enum AttributeModifierSlot {
172 | MainHand,
173 | OffHand,
174 | Feet,
175 | Legs,
176 | Chest,
177 | Head,
178 | }
179 |
180 | #[derive(Serialize, Deserialize)]
181 | pub struct BannerPattern {
182 | pattern: String,
183 | color: BannerColor,
184 | }
185 |
186 | #[derive(Serialize, Deserialize)]
187 | pub enum BannerColor {
188 | White,
189 | Orange,
190 | Magenta,
191 | LightBlue,
192 | Yellow,
193 | Lime,
194 | Pink,
195 | Gray,
196 | LightGray,
197 | Cyan,
198 | Purple,
199 | Blue,
200 | Brown,
201 | Green,
202 | Red,
203 | Black,
204 | }
205 |
206 | #[derive(Serialize, Deserialize)]
207 | pub struct StewEffect {
208 | r#type: UnlocalizedName,
209 | duration: NumberProvider,
210 | }
211 |
--------------------------------------------------------------------------------
/datapack/src/data/loot_tables.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashMap;
2 |
3 | use qdat::UnlocalizedName;
4 | use serde::{Deserialize, Serialize};
5 |
6 | use crate::data::{
7 | datatypes::{AmountOrRange, Enchantment, Entity, EntityType, PredicateLocation},
8 | predicate::Predicate,
9 | };
10 |
11 | #[derive(Serialize, Deserialize)]
12 | pub struct LootTable {
13 | pub r#type: LootTableType,
14 | #[serde(default)]
15 | pub functions: Vec,
16 | #[serde(default)]
17 | pub pools: Vec,
18 | }
19 |
20 | #[derive(Serialize, Deserialize)]
21 | pub enum LootTableType {
22 | #[serde(rename = "minecraft:empty")]
23 | Empty,
24 | #[serde(rename = "minecraft:entity")]
25 | Entity,
26 | #[serde(rename = "minecraft:block")]
27 | Block,
28 | #[serde(rename = "minecraft:chest")]
29 | Chest,
30 | #[serde(rename = "minecraft:fishing")]
31 | Fishing,
32 | #[serde(rename = "minecraft:gift")]
33 | Gift,
34 | #[serde(rename = "minecraft:advancement_reward")]
35 | AdvancementReward,
36 | #[serde(rename = "minecraft:barter")]
37 | Barter,
38 | #[serde(rename = "minecraft:command")]
39 | Command,
40 | #[serde(rename = "minecraft:selector")]
41 | Selector,
42 | #[serde(rename = "minecraft:advancement_entity")]
43 | AdvancementEntity,
44 | #[serde(rename = "minecraft:generic")]
45 | Generic,
46 | }
47 |
48 | #[derive(Serialize, Deserialize)]
49 | pub struct LootTableFunction {
50 | pub function: UnlocalizedName,
51 | #[serde(default)]
52 | pub conditions: Vec,
53 | }
54 |
55 | #[derive(Serialize, Deserialize)]
56 | pub struct LootTablePool {
57 | #[serde(default)]
58 | pub conditions: Vec,
59 | #[serde(default)]
60 | pub functions: Vec,
61 | pub rolls: NumberProvider,
62 | pub bonus_rolls: NumberProvider,
63 | #[serde(default)]
64 | pub entries: Vec,
65 | }
66 |
67 | #[derive(Serialize, Deserialize)]
68 | pub struct LootTableEntry {
69 | #[serde(default)]
70 | pub conditions: Vec,
71 | #[serde(default)]
72 | pub functions: Vec,
73 | #[serde(flatten)]
74 | pub r#type: LootTableEntryType,
75 | }
76 |
77 | #[derive(Serialize, Deserialize)]
78 | #[serde(tag = "condition")]
79 | pub enum LootTableCondition {
80 | #[serde(rename = "minecraft:alternative")]
81 | Alternative { terms: Vec },
82 | #[serde(rename = "minecraft:block_state_property")]
83 | BlockStateProperty {
84 | block: UnlocalizedName,
85 | properties: HashMap,
86 | },
87 | #[serde(rename = "minecraft:entity_properties")]
88 | EntityProperties {
89 | // This has to be "this"
90 | entity: String,
91 | predicate: Box,
92 | },
93 | #[serde(rename = "minecraft:entity_scores")]
94 | EntityScores {
95 | // Has to be "this"
96 | entity: String,
97 | scores: HashMap>,
98 | },
99 | #[serde(rename = "minecraft:inverted")]
100 | Inverted { term: Predicate },
101 | #[serde(rename = "minecraft:location_check")]
102 | LocationCheck {
103 | #[serde(rename = "offsetX")]
104 | x_offset: Option,
105 | #[serde(rename = "offsetY")]
106 | y_offset: Option,
107 | #[serde(rename = "offsetZ")]
108 | z_offset: Option,
109 | predicate: Option>,
110 | },
111 | #[serde(rename = "minecraft:match_tool")]
112 | MatchTool {
113 | #[serde(default)]
114 | items: Vec,
115 | tag: Option,
116 | count: Option>,
117 | durability: Option>,
118 | potion: Option,
119 | nbt: Option,
120 | enchantments: Option,
121 | },
122 | #[serde(rename = "minecraft:random_chance")]
123 | RandomChance { chance: f32 },
124 | #[serde(rename = "minecraft:reference")]
125 | Reference { name: UnlocalizedName },
126 | #[serde(rename = "minecraft:survives_explosion")]
127 | SurvivesExplosion,
128 | #[serde(rename = "minecraft:table_bonus")]
129 | TableBonus {
130 | enchantment: UnlocalizedName,
131 | #[serde(default)]
132 | chances: Vec,
133 | },
134 | #[serde(rename = "minecraft:time_check")]
135 | TimeCheck {
136 | value: Option>,
137 | period: Option,
138 | },
139 | #[serde(rename = "minecraft:value_check")]
140 | ValueCheck {
141 | value: Option>,
142 | range: Option>,
143 | },
144 | #[serde(rename = "minecraft:weather_check")]
145 | WeatherCheck {
146 | raining: Option,
147 | thundering: Option,
148 | },
149 | }
150 |
151 | #[derive(Serialize, Deserialize)]
152 | #[serde(tag = "type")]
153 | pub enum LootTableEntryType {
154 | #[serde(rename = "minecraft:item")]
155 | Item { name: UnlocalizedName },
156 | #[serde(rename = "minecraft:tag")]
157 | Tag {
158 | name: UnlocalizedName,
159 | #[serde(default)]
160 | expand: bool,
161 | },
162 | #[serde(rename = "minecraft:loot_table")]
163 | LootTable { name: UnlocalizedName },
164 | #[serde(rename = "minecraft:group")]
165 | Group { childern: Vec> },
166 | #[serde(rename = "minecraft:alternatives")]
167 | Alternatives { children: Vec> },
168 | #[serde(rename = "minecraft:sequence")]
169 | Sequence { children: Vec> },
170 | #[serde(rename = "minecraft:dynamic")]
171 | Dynamic { name: UnlocalizedName },
172 | #[serde(rename = "minecraft:empty")]
173 | Empty,
174 | }
175 |
176 | #[derive(Serialize, Deserialize)]
177 | #[serde(untagged)]
178 | pub enum NumberProvider {
179 | Singleton(T),
180 | Object(NumberProviderInternal),
181 | }
182 |
183 | #[derive(Serialize, Deserialize)]
184 | #[serde(tag = "type")]
185 | pub enum NumberProviderInternal {
186 | #[serde(rename = "minecraft:constant")]
187 | Constant { value: T },
188 | #[serde(rename = "minecraft:uniform")]
189 | Uniform {
190 | max: Box>,
191 | min: Box>,
192 | },
193 | #[serde(rename = "minecraft:binomial")]
194 | Binomial {
195 | n: Box>,
196 | p: Box>,
197 | },
198 | #[serde(rename = "minecraft:score")]
199 | Score {
200 | target: ScoreboardNumProviderTarget,
201 | score: String,
202 | scale: Option,
203 | },
204 | }
205 | #[derive(Serialize, Deserialize)]
206 | #[serde(untagged)]
207 | pub enum ScoreboardNumProviderTarget {
208 | Constant(EntityType),
209 | Variable(ScoreboardNameProvider),
210 | }
211 |
212 | #[derive(Serialize, Deserialize)]
213 | #[serde(tag = "type")]
214 | #[serde(rename_all = "snake_case")]
215 | pub enum ScoreboardNameProvider {
216 | Fixed { name: String },
217 | Context { target: EntityType },
218 | }
219 |
--------------------------------------------------------------------------------
/datapack/src/data/mod.rs:
--------------------------------------------------------------------------------
1 | //! Stores the definitions of all the formats in the `data` folder of datapacks
2 | pub mod advancement;
3 | pub mod functions;
4 | pub mod loot_tables;
5 | pub mod predicate;
6 | pub mod recipe;
7 | pub mod structure;
8 | pub mod tags;
9 | mod world_gen;
10 | pub use world_gen::*;
11 | pub mod datatypes;
12 | pub mod item_modifiers;
13 |
--------------------------------------------------------------------------------
/datapack/src/data/predicate.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashMap;
2 |
3 | use crate::data::datatypes::*;
4 | use qdat::UnlocalizedName;
5 | use serde::{Deserialize, Serialize};
6 |
7 | use super::loot_tables::NumberProvider;
8 |
9 | #[derive(Serialize, Deserialize)]
10 | #[serde(tag = "condition")]
11 | pub enum Predicate {
12 | #[serde(rename = "minecraft:alternative")]
13 | Alternative { terms: Vec },
14 | #[serde(rename = "minecraft:block_state_property")]
15 | BlockStateProperty {
16 | block: UnlocalizedName,
17 | properties: Option>,
18 | },
19 | #[serde(rename = "minecraft:damage_source_properties")]
20 | DamageSourceProperties { predicate: Box },
21 | #[serde(rename = "minecraft:entity_properties")]
22 | EntityPropreties {
23 | entity: EntityType,
24 | predicate: Box,
25 | },
26 | #[serde(rename = "minecraft:entity_scores")]
27 | EntityScores {
28 | entity: EntityType,
29 | scores: HashMap>,
30 | },
31 | #[serde(rename = "minecraft:inverted")]
32 | Inverted { term: Box },
33 | #[serde(rename = "minecraft:killed_by_player")]
34 | KilledByPlayer {
35 | #[serde(default)]
36 | inverse: bool,
37 | },
38 | #[serde(rename = "minecraft:location_check")]
39 | LocationCheck {
40 | #[serde(rename = "offsetX")]
41 | offset_x: Option,
42 | #[serde(rename = "offsetY")]
43 | offset_y: Option,
44 | #[serde(rename = "offsetZ")]
45 | offset_z: Option,
46 | predicate: Box,
47 | },
48 | #[serde(rename = "minecraft:match_tool")]
49 | MatchTool { predicate: Item },
50 | #[serde(rename = "minecraft:random_chance")]
51 | RandomChance { chance: f32 },
52 | #[serde(rename = "minecraft:random_chance_with_looting")]
53 | RandomChanceWithLooting {
54 | chance: f32,
55 | looting_multiplier: f32,
56 | },
57 | #[serde(rename = "minecraft:reference")]
58 | Reference { name: UnlocalizedName },
59 | #[serde(rename = "minecraft:survives_explosion")]
60 | SurvivesExplosion,
61 | #[serde(rename = "minecraft:table_bonus")]
62 | TableBonus {
63 | enchantment: UnlocalizedName,
64 | chances: Vec,
65 | },
66 | #[serde(rename = "minecraft:time_check")]
67 | TimeCheck {
68 | value: NumberProvider,
69 | period: i32,
70 | },
71 | #[serde(rename = "minecraft:weather_check")]
72 | WeatherCheck { raining: bool, thundering: bool },
73 | #[serde(rename = "minecraft:value_check")]
74 | ValueCheck {
75 | value: NumberProvider,
76 | range: AmountOrRange>,
77 | },
78 | }
79 |
--------------------------------------------------------------------------------
/datapack/src/data/recipe/cooking.rs:
--------------------------------------------------------------------------------
1 | use std::{fmt::Debug, marker::PhantomData};
2 |
3 | use qdat::UnlocalizedName;
4 | use serde::{Deserialize, Serialize};
5 |
6 | use crate::data::recipe::ingredient::Ingredient;
7 |
8 | /// The generic format of a cooking recipe
9 | ///
10 | /// The only thing that changes based on the cooking type is the amount of time it takes to cook
11 | /// Custom cooking recipes can be implemented by implementing [CookingRecipeType] on a struct
12 | #[derive(Serialize, Deserialize, PartialEq, Debug)]
13 | pub struct CookingRecipe {
14 | #[serde(skip_serializing_if = "Option::is_none")]
15 | pub group: Option,
16 | #[serde(rename = "ingredient")]
17 | pub input: Ingredient,
18 | pub result: UnlocalizedName,
19 | pub experience: f64,
20 | #[serde(default = "T::cook_time")]
21 | #[serde(rename = "cookingtime")]
22 | pub cook_time: u64,
23 | #[serde(skip)]
24 | pub _phatom: PhantomData,
25 | }
26 |
27 | /// Represents the different type of cooking recipes by changing the cooking time
28 | pub trait CookingRecipeType: Debug + PartialEq {
29 | /// Defines the default cooking time for this recipe type
30 | ///
31 | /// This is the only thing that differs between the cooking recipe types
32 | // NOTE: if they add const trait fns then change this to be const
33 | fn cook_time() -> u64;
34 | }
35 |
36 | #[macro_export]
37 | macro_rules! smelting_type {
38 | ($($name: ident, $time: literal),*) => {
39 | $(
40 | #[doc = "To be used as the type parameter for [CookingRecipeType](quartz_datapack::data::recipe::cooking::CookingRecipeType)"]
41 | #[derive(Debug, Eq, PartialEq)]
42 | pub struct $name;
43 | impl CookingRecipeType for $name {
44 | fn cook_time() -> u64 {
45 | $time
46 | }
47 | }
48 | )*
49 | };
50 | }
51 |
52 | smelting_type! {
53 | SmeltingRecipe, 200,
54 | BlastingRecipe, 100,
55 | SmokingRecipe, 100,
56 | // All vanilla campfire recipies have a cook time of 600 but the default is 100 for some reason
57 | CampfireRecipe, 100
58 | }
59 |
60 | #[test]
61 | fn cooking_ser_test() {
62 | use crate::data::recipe::{ingredient::Ingredient, VanillaRecipeType};
63 |
64 | let recipe = VanillaRecipeType::SmeltingRecipe(CookingRecipe {
65 | group: Some("copper_ingot".to_owned()),
66 | input: Ingredient::Item(UnlocalizedName::minecraft("raw_copper")),
67 | result: UnlocalizedName::minecraft("copper_ingot"),
68 | cook_time: 200,
69 | experience: 0.7,
70 | _phatom: PhantomData,
71 | });
72 |
73 | let serialized = serde_json::to_string(&recipe).unwrap();
74 |
75 | assert_eq!(
76 | &serialized,
77 | r#"{"type":"minecraft:smelting","group":"copper_ingot","ingredient":{"item":"minecraft:raw_copper"},"result":"minecraft:copper_ingot","experience":0.7,"cookingtime":200}"#
78 | )
79 | }
80 |
81 | #[test]
82 | fn cooking_de_test() {
83 | use crate::data::recipe::{ingredient::Ingredient, VanillaRecipeType};
84 |
85 | let input = r#"{
86 | "type": "minecraft:smelting",
87 | "group": "copper_ingot",
88 | "ingredient": {"item": "minecraft:raw_copper"},
89 | "result": "minecraft:copper_ingot",
90 | "experience": 0.7
91 | }"#;
92 |
93 | let recipe: VanillaRecipeType = serde_json::from_str(input).unwrap();
94 |
95 | assert_eq!(
96 | recipe,
97 | VanillaRecipeType::SmeltingRecipe(CookingRecipe {
98 | group: Some("copper_ingot".to_owned()),
99 | input: Ingredient::Item(UnlocalizedName::minecraft("raw_copper")),
100 | result: UnlocalizedName::minecraft("copper_ingot"),
101 | cook_time: 200,
102 | experience: 0.7,
103 | _phatom: PhantomData,
104 | })
105 | )
106 | }
107 |
--------------------------------------------------------------------------------
/datapack/src/data/recipe/ingredient.rs:
--------------------------------------------------------------------------------
1 | use qdat::UnlocalizedName;
2 | use serde::{
3 | de::Visitor,
4 | ser::{SerializeMap, SerializeSeq},
5 | Deserialize,
6 | Serialize,
7 | };
8 |
9 | /// Represents an ingredient in a recipe
10 | #[derive(PartialOrd, Ord, PartialEq, Eq, Clone, Debug)]
11 | pub enum Ingredient {
12 | /// A single item
13 | Item(UnlocalizedName),
14 | /// A tag to use as the item provider
15 | Tag(UnlocalizedName),
16 | /// A list of ingredients to use as an ingredient provider
17 | /// # Note
18 | ///Do not have nested Ingredient::Lists
19 | List(Box<[Ingredient]>),
20 | }
21 |
22 | impl Ingredient {
23 | pub fn is_empty(&self) -> bool {
24 | match self {
25 | Ingredient::Item(i) => i == "minecraft:air",
26 | // As long as the UnlocalizedName is valid we need to resolve the tag
27 | Ingredient::Tag(_) => false,
28 | // the iter.any call can be expensive if the array is large
29 | // in practice lists should p much not be used cause tags exist sooooooo
30 | Ingredient::List(l) => l.len() == 0 || l.iter().any(|i| i.is_empty()),
31 | }
32 | }
33 | }
34 |
35 | #[derive(Serialize, Deserialize)]
36 | struct ItemJSON {
37 | pub item: String,
38 | }
39 | #[derive(Serialize, Deserialize)]
40 | struct TagJSON {
41 | pub tag: String,
42 | }
43 | impl Serialize for Ingredient {
44 | fn serialize(&self, serializer: S) -> Result
45 | where S: serde::Serializer {
46 | match self {
47 | Ingredient::Item(i) => {
48 | let mut struc = serializer.serialize_map(Some(1))?;
49 | struc.serialize_entry("item", &i.to_string())?;
50 | struc.end()
51 | }
52 | Ingredient::Tag(t) => {
53 | let mut struc = serializer.serialize_map(Some(1))?;
54 | struc.serialize_entry("tag", &t.to_string())?;
55 | struc.end()
56 | }
57 | Ingredient::List(l) => {
58 | let mut arr = serializer.serialize_seq(Some(l.len()))?;
59 | for ingr in l.iter() {
60 | match ingr {
61 | Ingredient::Item(i) => {
62 | arr.serialize_element(&ItemJSON {
63 | item: i.to_string(),
64 | })?;
65 | }
66 | Ingredient::Tag(t) => {
67 | arr.serialize_element(&TagJSON { tag: t.to_string() })?;
68 | }
69 | Ingredient::List(_) =>
70 | return Err(serde::ser::Error::custom(
71 | "You can't have a list of ingredients inside another list of \
72 | ingredients",
73 | )),
74 | }
75 | }
76 | arr.end()
77 | }
78 | }
79 | }
80 | }
81 |
82 | impl<'de> Deserialize<'de> for Ingredient {
83 | fn deserialize(deserializer: D) -> Result
84 | where D: serde::Deserializer<'de> {
85 | deserializer.deserialize_any(IngredientVisitor)
86 | }
87 | }
88 |
89 | struct IngredientVisitor;
90 |
91 | impl<'de> Visitor<'de> for IngredientVisitor {
92 | type Value = Ingredient;
93 |
94 | fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
95 | formatter.write_str("An ingredient")
96 | }
97 |
98 | fn visit_map(self, mut map: A) -> Result
99 | where A: serde::de::MapAccess<'de> {
100 | let mut ingr = None;
101 | while let Some((key, value)) = map.next_entry::<&str, &str>()? {
102 | let uln = UnlocalizedName::from_str(value);
103 | if let Err(e) = uln {
104 | return Err(serde::de::Error::custom(format!(
105 | "Invalid Identifier provided: {e}"
106 | )));
107 | }
108 | // This error message kinda sucks
109 | if ingr.is_some() {
110 | return Err(serde::de::Error::custom(
111 | "Multiple values specified in ingredient",
112 | ));
113 | }
114 | match key {
115 | "item" => ingr = Some(Ingredient::Item(uln.unwrap())),
116 | "tag" => ingr = Some(Ingredient::Tag(uln.unwrap())),
117 | _ => return Err(serde::de::Error::unknown_field(key, &["item", "tag"])),
118 | }
119 | }
120 |
121 |
122 | ingr.ok_or_else(|| serde::de::Error::missing_field("item or tag"))
123 | }
124 |
125 | fn visit_seq(self, mut seq: A) -> Result
126 | where A: serde::de::SeqAccess<'de> {
127 | let mut ingrs = Vec::new();
128 |
129 | while let Some(ingr) = seq.next_element::()? {
130 | match ingr {
131 | Ingredient::List(_) =>
132 | return Err(serde::de::Error::custom(
133 | "Cannot have an ingredient list inside a list",
134 | )),
135 | _ => {
136 | ingrs.push(ingr);
137 | }
138 | }
139 | }
140 |
141 | Ok(Ingredient::List(ingrs.into_boxed_slice()))
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/datapack/src/data/recipe/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod cooking;
2 | pub mod ingredient;
3 | #[allow(clippy::module_inception)]
4 | mod recipe;
5 | pub mod shaped;
6 | pub mod shapeless;
7 | pub mod smithing;
8 | pub mod stonecutting;
9 |
10 | pub use recipe::*;
11 |
--------------------------------------------------------------------------------
/datapack/src/data/recipe/recipe.rs:
--------------------------------------------------------------------------------
1 | use qdat::UnlocalizedName;
2 | use serde::{Deserialize, Serialize};
3 |
4 | use crate::data::recipe::{
5 | cooking::*,
6 | shaped::ShapedCraftingRecipe,
7 | shapeless::ShapelessCraftingRecipe,
8 | smithing::SmithingRecipe,
9 | stonecutting::StonecuttingRecipe,
10 | };
11 |
12 |
13 | /// The output type of most recipe types
14 | #[derive(Deserialize, Serialize, PartialEq, Eq, Debug)]
15 | pub struct RecipeOutput {
16 | pub item: UnlocalizedName,
17 | #[serde(default = "default_count")]
18 | pub count: u8,
19 | }
20 |
21 | const fn default_count() -> u8 {
22 | 1
23 | }
24 |
25 | /// The vanilla recipe types, returns Other if the type is unknown
26 | /// # Note
27 | /// Singleton variants are not defined in datapacks and are left to servers / clients to implement properly
28 | /// Recipes in datapacks with these types only either enable or disable that special recipe type
29 | #[derive(Serialize, Deserialize, PartialEq, Debug)]
30 | #[serde(tag = "type")]
31 | pub enum VanillaRecipeType {
32 | #[serde(rename = "minecraft:crafting_shaped")]
33 | ShapedRecipe(ShapedCraftingRecipe),
34 | #[serde(rename = "minecraft:crafting_shapeless")]
35 | ShapelessRecipe(ShapelessCraftingRecipe),
36 | #[serde(rename = "minecraft:smelting")]
37 | SmeltingRecipe(CookingRecipe),
38 | #[serde(rename = "minecraft:blasting")]
39 | BlastingRecipe(CookingRecipe),
40 | #[serde(rename = "minecraft:smoking")]
41 | SmokingRecipe(CookingRecipe),
42 | #[serde(rename = "minecraft:campfire_cooking")]
43 | CampfireRecipe(CookingRecipe),
44 | #[serde(rename = "minecraft:smithing")]
45 | SmithingRecipe(SmithingRecipe),
46 | #[serde(rename = "minecraft:stonecutting")]
47 | StonecuttingRecipe(StonecuttingRecipe),
48 |
49 | // Special recipe types
50 | #[serde(rename = "minecraft:armordye")]
51 | ArmorDye,
52 | #[serde(rename = "minecraft:bannerduplicate")]
53 | BannerDuplicate,
54 | #[serde(rename = "minecraft:bookcloning")]
55 | BookClone,
56 | #[serde(rename = "minecraft:firework_rocket")]
57 | FireworkRocket,
58 | #[serde(rename = "minecraft:firework_star")]
59 | FireworkStar,
60 | #[serde(rename = "minecraft:firework_star_fade")]
61 | FireworkStarFade,
62 | #[serde(rename = "minecraft:mapcloning")]
63 | MapClone,
64 | #[serde(rename = "minecraft:mapextending")]
65 | MapExtend,
66 | #[serde(rename = "minecraft:repairitem")]
67 | RepairItem,
68 | #[serde(rename = "minecraft:shielddecoration")]
69 | ShieldDecorate,
70 | #[serde(rename = "minecraft:shulkerboxcoloring")]
71 | ShulkerBoxColoring,
72 | #[serde(rename = "minecraft:tippedarrow")]
73 | TippedArrow,
74 | #[serde(rename = "minecraft:suspiciousstew")]
75 | SuspiciousStew,
76 |
77 | // If we get unknown types
78 | #[serde(other)]
79 | Unknown,
80 | }
81 |
--------------------------------------------------------------------------------
/datapack/src/data/recipe/shapeless.rs:
--------------------------------------------------------------------------------
1 | use std::collections::BTreeMap;
2 |
3 | use serde::{de::Visitor, ser::SerializeSeq, Deserialize, Serialize};
4 |
5 | use crate::data::recipe::{ingredient::Ingredient, RecipeOutput};
6 |
7 | /// A shapeless crafting recipe
8 | #[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
9 | pub struct ShapelessCraftingRecipe {
10 | #[serde(skip_serializing_if = "Option::is_none")]
11 | pub group: Option,
12 | #[serde(rename = "ingredients")]
13 | pub inputs: ShapelessIngredients,
14 | pub result: RecipeOutput,
15 | }
16 |
17 | /// The ingredients list of a shapeless recipe
18 | #[derive(Debug, PartialEq, Eq)]
19 | pub struct ShapelessIngredients(Box<[(Ingredient, u8)]>);
20 |
21 | impl Serialize for ShapelessIngredients {
22 | fn serialize(&self, serializer: S) -> Result
23 | where S: serde::Serializer {
24 | let mut seq = serializer.serialize_seq(Some(self.0.len()))?;
25 |
26 | for (ingr, count) in self.0.iter() {
27 | for _ in 0 .. *count {
28 | seq.serialize_element(ingr)?;
29 | }
30 | }
31 |
32 | seq.end()
33 | }
34 | }
35 |
36 | impl<'de> Deserialize<'de> for ShapelessIngredients {
37 | fn deserialize(deserializer: D) -> Result
38 | where D: serde::Deserializer<'de> {
39 | deserializer.deserialize_seq(ShapelessIngredientsVisitor)
40 | }
41 | }
42 |
43 | struct ShapelessIngredientsVisitor;
44 |
45 | impl<'de> Visitor<'de> for ShapelessIngredientsVisitor {
46 | type Value = ShapelessIngredients;
47 |
48 | fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
49 | formatter.write_str("List of ingredients")
50 | }
51 |
52 | fn visit_seq(self, mut seq: A) -> Result
53 | where A: serde::de::SeqAccess<'de> {
54 | let mut map = BTreeMap::new();
55 |
56 | while let Some(i) = seq.next_element::()? {
57 | // Clippy's warning here just is not valid for this use case I think
58 | #[allow(clippy::map_entry)]
59 | if map.contains_key(&i) {
60 | *map.get_mut(&i).unwrap() += 1;
61 | } else {
62 | map.insert(i, 1);
63 | }
64 | }
65 |
66 | Ok(ShapelessIngredients(
67 | map.into_iter().collect::>().into_boxed_slice(),
68 | ))
69 | }
70 | }
71 |
72 | #[test]
73 | fn shapeless_ser_test() {
74 | use crate::data::recipe::{ingredient::Ingredient, RecipeOutput, VanillaRecipeType};
75 | use qdat::UnlocalizedName;
76 |
77 | let recipe = VanillaRecipeType::ShapelessRecipe(ShapelessCraftingRecipe {
78 | inputs: ShapelessIngredients(Box::new([
79 | (Ingredient::Item(UnlocalizedName::minecraft("sand")), 4),
80 | (Ingredient::Item(UnlocalizedName::minecraft("gravel")), 4),
81 | (Ingredient::Item(UnlocalizedName::minecraft("red_dye")), 1),
82 | ])),
83 | result: RecipeOutput {
84 | item: UnlocalizedName::minecraft("red_concrete_powder"),
85 | count: 8,
86 | },
87 | group: Some("concrete_powder".to_owned()),
88 | });
89 |
90 | let serialized = serde_json::to_string(&recipe).unwrap();
91 |
92 | assert_eq!(
93 | r#"{"type":"minecraft:crafting_shapeless","group":"concrete_powder","ingredients":[{"item":"minecraft:sand"},{"item":"minecraft:sand"},{"item":"minecraft:sand"},{"item":"minecraft:sand"},{"item":"minecraft:gravel"},{"item":"minecraft:gravel"},{"item":"minecraft:gravel"},{"item":"minecraft:gravel"},{"item":"minecraft:red_dye"}],"result":{"item":"minecraft:red_concrete_powder","count":8}}"#,
94 | &serialized
95 | )
96 | }
97 |
98 | #[test]
99 | fn shapeless_de_test() {
100 | use crate::data::recipe::{ingredient::Ingredient, RecipeOutput, VanillaRecipeType};
101 | use qdat::UnlocalizedName;
102 |
103 | let input = r#"{
104 | "type": "minecraft:crafting_shapeless",
105 | "group": "concrete_powder",
106 | "ingredients": [
107 | {"item": "minecraft:red_dye"},
108 | {"item": "minecraft:sand"},
109 | {"item": "minecraft:sand"},
110 | {"item": "minecraft:sand"},
111 | {"item": "minecraft:sand"},
112 | {"item": "minecraft:gravel"},
113 | {"item": "minecraft:gravel"},
114 | {"item": "minecraft:gravel"},
115 | {"item": "minecraft:gravel"}
116 | ],
117 | "result": {
118 | "item": "minecraft:red_concrete_powder",
119 | "count": 8
120 | }
121 | }"#;
122 |
123 | let recipe: VanillaRecipeType = serde_json::from_str(input).unwrap();
124 |
125 | assert_eq!(
126 | recipe,
127 | VanillaRecipeType::ShapelessRecipe(ShapelessCraftingRecipe {
128 | inputs: ShapelessIngredients(Box::new([
129 | (Ingredient::Item(UnlocalizedName::minecraft("gravel")), 4),
130 | (Ingredient::Item(UnlocalizedName::minecraft("red_dye")), 1),
131 | (Ingredient::Item(UnlocalizedName::minecraft("sand")), 4),
132 | ])),
133 | result: RecipeOutput {
134 | item: UnlocalizedName::minecraft("red_concrete_powder"),
135 | count: 8,
136 | },
137 | group: Some("concrete_powder".to_owned()),
138 | })
139 | )
140 | }
141 |
--------------------------------------------------------------------------------
/datapack/src/data/recipe/smithing.rs:
--------------------------------------------------------------------------------
1 | use qdat::UnlocalizedName;
2 | use serde::{Deserialize, Serialize};
3 |
4 | use crate::data::recipe::ingredient::Ingredient;
5 |
6 | /// A smithing recipe
7 | #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
8 | pub struct SmithingRecipe {
9 | #[serde(skip_serializing_if = "Option::is_none")]
10 | pub group: Option,
11 | /// The base of the smithing recipe
12 | /// # Note
13 | /// This ingredient cannot be a list
14 | pub base: Ingredient,
15 | pub addition: Ingredient,
16 | /// In most cases, the nbt of enchantments will be carried over to the result
17 | pub result: SmithingOutput,
18 | }
19 |
20 | #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
21 | // mojang I hate you please stop making things very close but not the same
22 | pub struct SmithingOutput {
23 | pub item: UnlocalizedName,
24 | }
25 |
26 | #[test]
27 | fn smithing_ser_test() {
28 | use crate::data::recipe::{ingredient::Ingredient, VanillaRecipeType};
29 |
30 | let recipe = VanillaRecipeType::SmithingRecipe(SmithingRecipe {
31 | group: None,
32 | base: Ingredient::Item(UnlocalizedName::minecraft("diamond_pickaxe")),
33 | addition: Ingredient::Item(UnlocalizedName::minecraft("netherite_ingot")),
34 | result: SmithingOutput {
35 | item: UnlocalizedName::minecraft("netherite_pickaxe"),
36 | },
37 | });
38 |
39 | let serialized = serde_json::to_string(&recipe).unwrap();
40 |
41 | assert_eq!(
42 | &serialized,
43 | r#"{"type":"minecraft:smithing","base":{"item":"minecraft:diamond_pickaxe"},"addition":{"item":"minecraft:netherite_ingot"},"result":{"item":"minecraft:netherite_pickaxe"}}"#
44 | )
45 | }
46 |
47 | #[test]
48 | fn smithing_de_test() {
49 | use crate::data::recipe::{ingredient::Ingredient, VanillaRecipeType};
50 |
51 | let input = r#"{
52 | "type": "minecraft:smithing",
53 | "base": {
54 | "item": "minecraft:diamond_pickaxe"
55 | },
56 | "addition": {
57 | "item": "minecraft:netherite_ingot"
58 | },
59 | "result": {
60 | "item": "minecraft:netherite_pickaxe"
61 | }
62 | }"#;
63 |
64 | let recipe: VanillaRecipeType = serde_json::from_str(input).unwrap();
65 |
66 | assert_eq!(
67 | recipe,
68 | VanillaRecipeType::SmithingRecipe(SmithingRecipe {
69 | group: None,
70 | base: Ingredient::Item(UnlocalizedName::minecraft("diamond_pickaxe")),
71 | addition: Ingredient::Item(UnlocalizedName::minecraft("netherite_ingot")),
72 | result: SmithingOutput {
73 | item: UnlocalizedName::minecraft("netherite_pickaxe")
74 | }
75 | })
76 | )
77 | }
78 |
--------------------------------------------------------------------------------
/datapack/src/data/recipe/stonecutting.rs:
--------------------------------------------------------------------------------
1 | use qdat::UnlocalizedName;
2 | use serde::{de::Visitor, ser::SerializeMap, Deserialize, Serialize};
3 |
4 | use crate::data::recipe::{ingredient::Ingredient, recipe::RecipeOutput};
5 |
6 | /// A stone cutting recipe
7 | #[derive(Debug, Eq, PartialEq)]
8 | pub struct StonecuttingRecipe {
9 | pub group: Option,
10 | pub input: Ingredient,
11 | pub result: RecipeOutput,
12 | }
13 |
14 | impl Serialize for StonecuttingRecipe {
15 | fn serialize(&self, serializer: S) -> Result
16 | where S: serde::Serializer {
17 | let mut map = serializer.serialize_map(Some(4))?;
18 |
19 | if self.group.is_some() {
20 | map.serialize_entry("group", &self.group)?;
21 | }
22 |
23 | map.serialize_entry("ingredient", &self.input)?;
24 | map.serialize_entry("result", &self.result.item)?;
25 | map.serialize_entry("count", &self.result.count)?;
26 |
27 | map.end()
28 | }
29 | }
30 |
31 | impl<'de> Deserialize<'de> for StonecuttingRecipe {
32 | fn deserialize(deserializer: D) -> Result
33 | where D: serde::Deserializer<'de> {
34 | deserializer.deserialize_map(StonecuttingRecipeVisitor)
35 | }
36 | }
37 |
38 |
39 | struct StonecuttingRecipeVisitor;
40 |
41 | impl<'de> Visitor<'de> for StonecuttingRecipeVisitor {
42 | type Value = StonecuttingRecipe;
43 |
44 | fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
45 | formatter.write_str("a stonecutting recipe")
46 | }
47 |
48 | fn visit_map(self, mut map: A) -> Result
49 | where A: serde::de::MapAccess<'de> {
50 | map_visitor!(
51 | map,
52 | (group, "group", String),
53 | (ingredient, "ingredient", Ingredient),
54 | (result, "result", UnlocalizedName),
55 | (count, "count", u8)
56 | );
57 |
58 | missing_field_error!(ingredient, "ingredient", result, "result", count, "count");
59 |
60 | Ok(StonecuttingRecipe {
61 | group,
62 | input: ingredient,
63 | result: RecipeOutput {
64 | item: result,
65 | count,
66 | },
67 | })
68 | }
69 | }
70 |
71 | #[test]
72 | fn stonecutting_ser_test() {
73 | use crate::data::recipe::{ingredient::Ingredient, VanillaRecipeType};
74 |
75 | let recipe = VanillaRecipeType::StonecuttingRecipe(StonecuttingRecipe {
76 | group: None,
77 | input: Ingredient::Item(UnlocalizedName::minecraft("stone")),
78 | result: RecipeOutput {
79 | item: UnlocalizedName::minecraft("stone_brick_slab"),
80 | count: 2,
81 | },
82 | });
83 |
84 | let serialized = serde_json::to_string(&recipe).unwrap();
85 |
86 | assert_eq!(
87 | &serialized,
88 | r#"{"type":"minecraft:stonecutting","ingredient":{"item":"minecraft:stone"},"result":"minecraft:stone_brick_slab","count":2}"#
89 | )
90 | }
91 |
92 | #[test]
93 | fn stonecutting_de_test() {
94 | use crate::data::recipe::{ingredient::Ingredient, VanillaRecipeType};
95 |
96 | let input = r#"{
97 | "type": "minecraft:stonecutting",
98 | "ingredient": {
99 | "item": "minecraft:stone"
100 | },
101 | "result": "minecraft:stone_brick_slab",
102 | "count": 2
103 | }"#;
104 |
105 | let recipe: VanillaRecipeType = serde_json::from_str(input).unwrap();
106 |
107 | assert_eq!(
108 | recipe,
109 | VanillaRecipeType::StonecuttingRecipe(StonecuttingRecipe {
110 | group: None,
111 | input: Ingredient::Item(UnlocalizedName::minecraft("stone")),
112 | result: RecipeOutput {
113 | item: UnlocalizedName::minecraft("stone_brick_slab"),
114 | count: 2
115 | }
116 | })
117 | );
118 | }
119 |
--------------------------------------------------------------------------------
/datapack/src/data/structure.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashMap;
2 |
3 | use qdat::UnlocalizedName;
4 | use quartz_nbt::NbtCompound;
5 | use serde::{Deserialize, Serialize};
6 |
7 | /// A structure
8 | ///
9 | /// This is stored in NBT files in contrast to the rest of datapacks that are in json
10 | #[derive(Serialize, Deserialize)]
11 | pub struct Structure {
12 | #[serde(rename = "DataVersion")]
13 | pub data_version: i32,
14 | pub size: Vec,
15 | pub palette: Option>,
16 | pub blocks: Vec,
17 | pub entities: Vec,
18 | }
19 |
20 | /// An entry in the palette of a structure
21 | #[derive(Serialize, Deserialize)]
22 | pub struct StructurePaletteEntry {
23 | #[serde(rename = "Name")]
24 | pub name: UnlocalizedName,
25 | #[serde(rename = "Properties")]
26 | #[serde(default)]
27 | pub properties: HashMap,
28 | }
29 |
30 | /// A block of a structure
31 | #[derive(Serialize, Deserialize)]
32 | pub struct StructureBlock {
33 | pub state: i32,
34 | pub pos: Vec,
35 | pub nbt: Option,
36 | }
37 |
38 | /// An entity in a structure
39 | #[derive(Serialize, Deserialize)]
40 | pub struct StructureEntity {
41 | pub pos: Vec,
42 | #[serde(rename = "blockPos")]
43 | pub block_pos: Vec,
44 | pub nbt: NbtCompound,
45 | }
46 |
--------------------------------------------------------------------------------
/datapack/src/data/world_gen/biome.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashMap;
2 |
3 | use qdat::UnlocalizedName;
4 | use serde::{Deserialize, Serialize};
5 |
6 | use crate::data::tags::IdsOrTag;
7 |
8 | #[derive(Serialize, Deserialize)]
9 | pub struct Biome {
10 | pub category: BiomeCategory,
11 | pub precipitation: Precipitation,
12 | pub temperature: f32,
13 | pub tempearture_modifier: Option,
14 | pub downfall: f32,
15 | pub creature_spawn_probability: Option,
16 | pub effects: BiomeEffects,
17 | pub carvers: BiomeCarvers,
18 | pub features: Vec,
19 | pub spawners: HashMap>,
20 | // This is literally never used by vanilla
21 | pub spawn_costs: HashMap,
22 | }
23 |
24 | #[derive(Serialize, Deserialize)]
25 | #[serde(rename_all = "snake_case")]
26 | pub enum Precipitation {
27 | None,
28 | Rain,
29 | Snow,
30 | }
31 |
32 | #[derive(Serialize, Deserialize)]
33 | #[serde(rename_all = "snake_case")]
34 | pub enum BiomeCategory {
35 | None,
36 | Taiga,
37 | ExtremeHills,
38 | Jungle,
39 | Mesa,
40 | Plains,
41 | Savanna,
42 | Icy,
43 | TheEnd,
44 | Beach,
45 | Forest,
46 | Ocean,
47 | Desert,
48 | River,
49 | Swamp,
50 | Mushroom,
51 | Nether,
52 | Underground,
53 | Mountain,
54 | }
55 |
56 | #[derive(Serialize, Deserialize)]
57 | pub enum TemperatureModifier {
58 | None,
59 | Frozen,
60 | }
61 |
62 | #[derive(Serialize, Deserialize)]
63 | pub struct BiomeEffects {
64 | pub fog_color: Option,
65 | pub foliage_color: Option,
66 | pub grass_color: Option,
67 | pub sky_color: Option,
68 | pub water_color: Option,
69 | pub particle: Option,
70 | pub additions_sound: Option,
71 | pub ambient_sound: Option,
72 | pub mood_sound: Option,
73 | pub music: Option,
74 | }
75 |
76 | #[derive(Serialize, Deserialize)]
77 | #[serde(rename_all = "snake_case")]
78 | pub enum GrassColorModifier {
79 | None,
80 | DarkForest,
81 | Swamp,
82 | }
83 |
84 | #[derive(Serialize, Deserialize)]
85 | pub struct BiomeParticle {
86 | pub probability: f32,
87 | pub options: BiomeParticleOptions,
88 | }
89 |
90 | #[derive(Serialize, Deserialize)]
91 | pub struct BiomeParticleOptions {
92 | r#type: UnlocalizedName,
93 | }
94 | // Idk if this is still valid
95 | // #[derive(Serialize, Deserialize)]
96 | // #[serde(tag = "type")]
97 | // pub enum BiomeParticleOptions {
98 | // #[serde(rename = "minecraft:block")]
99 | // Block {
100 | // #[serde(rename = "Name")]
101 | // name: UnlocalizedName,
102 | // #[serde(rename = "Properties")]
103 | // properties: HashMap,
104 | // },
105 | // #[serde(rename = "minecraft:falling_dust")]
106 | // FallingDust {
107 | // #[serde(rename = "Name")]
108 | // name: UnlocalizedName,
109 | // #[serde(rename = "Properties")]
110 | // properties: HashMap,
111 | // },
112 | // #[serde(rename = "minecraft:dust")]
113 | // Dust { r: f32, g: f32, b: f32, scale: f32 },
114 | // #[serde(rename = "minecraft:item")]
115 | // Item {
116 | // id: UnlocalizedName,
117 | // #[serde(rename = "Count")]
118 | // count: i32,
119 | // // TO DO: this is snbt
120 | // tag: String,
121 | // },
122 | // }
123 |
124 | #[derive(Serialize, Deserialize)]
125 | pub struct MoodSound {
126 | pub sound: String,
127 | pub tick_delay: i32,
128 | pub block_search_extent: i32,
129 | pub offset: f64,
130 | }
131 |
132 | #[derive(Serialize, Deserialize)]
133 | pub struct AdditionalSound {
134 | pub sound: String,
135 | pub tick_chance: f64,
136 | }
137 |
138 | #[derive(Serialize, Deserialize)]
139 | pub struct BiomeMusic {
140 | pub sound: String,
141 | pub min_delay: i32,
142 | pub max_delay: i32,
143 | pub replace_current_music: bool,
144 | }
145 |
146 | #[derive(Serialize, Deserialize)]
147 | pub struct MobSpawners {
148 | pub r#type: UnlocalizedName,
149 | pub weight: i32,
150 | #[serde(rename = "minCount")]
151 | pub min_count: i32,
152 | #[serde(rename = "maxCount")]
153 | pub max_count: i32,
154 | }
155 |
156 | #[derive(Serialize, Deserialize)]
157 | pub struct SpawnCosts {
158 | pub energy_budget: f64,
159 | pub charge: f64,
160 | }
161 |
162 | #[derive(Serialize, Deserialize, PartialEq, Eq, Hash)]
163 | #[serde(rename_all = "snake_case")]
164 | pub enum MobCategory {
165 | Monster,
166 | Creature,
167 | Ambient,
168 | WaterCreature,
169 | UndergroundWaterCreature,
170 | WaterAmbient,
171 | Axolotls,
172 | Misc,
173 | }
174 |
175 | #[derive(Serialize, Deserialize)]
176 | pub struct BiomeCarvers {
177 | pub air: Option,
178 | pub liquid: Option,
179 | }
180 |
181 | #[derive(Serialize, Deserialize)]
182 | #[serde(untagged)]
183 | pub enum BiomeCarver {
184 | Singleton(UnlocalizedName),
185 | List(#[serde(default)] Vec),
186 | }
187 |
--------------------------------------------------------------------------------
/datapack/src/data/world_gen/carvers.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashMap;
2 |
3 | use serde::{Deserialize, Serialize};
4 | use serde_json::Value;
5 |
6 | #[derive(Serialize, Deserialize)]
7 | pub struct CarverSettings {
8 | pub probability: f32,
9 | pub y: HeightProvider,
10 | #[serde(rename = "yScale")]
11 | pub y_scale: FloatProvider,
12 | pub default_settings: Option>,
13 | pub lava_level: VerticalAnchor,
14 | #[serde(default = "Default::default")]
15 | pub aquifers_enabled: bool,
16 | }
17 |
18 | #[derive(Serialize, Deserialize)]
19 | #[serde(tag = "type", content = "config")]
20 | pub enum Carver {
21 | #[serde(rename = "minecraft:cave")]
22 | Cave {
23 | #[serde(flatten)]
24 | settings: CarverSettings,
25 | horizontal_radius_multiplier: FloatProvider,
26 | vertical_radius_multiplier: FloatProvider,
27 | floor_level: FloatProvider,
28 | },
29 | #[serde(rename = "minecraft:nether_cave")]
30 | NetherCave {
31 | #[serde(flatten)]
32 | settings: CarverSettings,
33 | horizontal_radius_multiplier: FloatProvider,
34 | vertical_radius_multiplier: FloatProvider,
35 | floor_level: FloatProvider,
36 | },
37 | #[serde(rename = "minecraft:underwater_cave")]
38 | UnderwaterCave {
39 | #[serde(flatten)]
40 | settings: CarverSettings,
41 | horizontal_radius_multiplier: FloatProvider,
42 | vertical_radius_multiplier: FloatProvider,
43 | floor_level: FloatProvider,
44 | },
45 | #[serde(rename = "minecraft:canyon")]
46 | Canyon {
47 | #[serde(flatten)]
48 | settings: CarverSettings,
49 | vertical_rotation: FloatProvider,
50 | shape: CanyonShape,
51 | },
52 | #[serde(rename = "minecraft:underwater_canyon")]
53 | UnderwaterCanyon {
54 | #[serde(flatten)]
55 | settings: CarverSettings,
56 | vertical_rotation: FloatProvider,
57 | shape: CanyonShape,
58 | },
59 | }
60 |
61 | #[derive(Serialize, Deserialize)]
62 | pub struct CanyonShape {
63 | pub thickness: FloatProvider,
64 | pub width_smoothness: i32,
65 | pub horizontal_radius_factor: FloatProvider,
66 | pub vertical_radius_default_factor: FloatProvider,
67 | pub vertical_radius_center_factor: FloatProvider,
68 | }
69 |
70 | #[derive(Serialize, Deserialize)]
71 | #[serde(untagged)]
72 | pub enum FloatProvider {
73 | Constant(f32),
74 | Provider(FloatProviderInternal),
75 | }
76 |
77 | #[derive(Serialize, Deserialize)]
78 | #[serde(tag = "type", content = "value")]
79 | pub enum FloatProviderInternal {
80 | #[serde(rename = "minecraft:constant")]
81 | Constant(f32),
82 | #[serde(rename = "minecraft:uniform")]
83 | Uniform {
84 | min_inclusive: f32,
85 | max_exclusive: f32,
86 | },
87 | #[serde(rename = "minecraft:clamped_normal")]
88 | ClampedNormal {
89 | mean: f32,
90 | deviation: f32,
91 | min: f32,
92 | max: f32,
93 | },
94 | #[serde(rename = "minecraft:trapezoid")]
95 | Trapezoid { min: f32, max: f32, plateau: f32 },
96 | }
97 |
98 | #[derive(Serialize, Deserialize)]
99 | #[serde(untagged)]
100 | pub enum HeightProvider {
101 | Constant {
102 | value: VerticalAnchor,
103 | },
104 | Uniform {
105 | min_inclusive: VerticalAnchor,
106 | max_inclusive: VerticalAnchor,
107 | },
108 | BiasedToBottom {
109 | min_inclusive: VerticalAnchor,
110 | max_inclusive: VerticalAnchor,
111 | /// # Note
112 | /// If present, has to be at least 1
113 | inner: Option,
114 | },
115 | VeryBiasedToBottom {
116 | min_inclusive: VerticalAnchor,
117 | max_inclusive: VerticalAnchor,
118 | /// # Note
119 | /// If present, has to be at least 1
120 | inner: Option,
121 | },
122 | Trapezoid {
123 | min_inclusive: VerticalAnchor,
124 | max_inclusive: VerticalAnchor,
125 | plateau: Option,
126 | },
127 | }
128 |
129 | #[derive(Serialize, Deserialize)]
130 | #[serde(untagged)]
131 | pub enum VerticalAnchor {
132 | Absolute { absolute: i32 },
133 | AboveBottom { above_bottom: i32 },
134 | BelowTop { below_top: i32 },
135 | }
136 |
--------------------------------------------------------------------------------
/datapack/src/data/world_gen/density_function.rs:
--------------------------------------------------------------------------------
1 | use qdat::UnlocalizedName;
2 | use serde::{Deserialize, Serialize};
3 |
4 | #[derive(Serialize, Deserialize)]
5 | #[serde(untagged)]
6 | pub enum DensityFunctionProvider {
7 | Constant(f64),
8 | Inline(Box),
9 | Reference(UnlocalizedName),
10 | }
11 |
12 |
13 | #[derive(Serialize, Deserialize)]
14 | #[serde(tag = "type")]
15 | pub enum DensityFunction {
16 | #[serde(rename = "minecraft:abs")]
17 | Abs { argument: DensityFunctionProvider },
18 | #[serde(rename = "minecraft:add")]
19 | Add {
20 | argument1: DensityFunctionProvider,
21 | argument2: DensityFunctionProvider,
22 | },
23 | #[serde(rename = "minecraft:beardifier")]
24 | Beardifier,
25 | #[serde(rename = "minecraft:blend_alpha")]
26 | BlendAlpha,
27 | #[serde(rename = "minecraft:blend_density")]
28 | BendDensity { argument: DensityFunctionProvider },
29 | #[serde(rename = "minecraft:blend_offset")]
30 | BlendOffset,
31 | #[serde(rename = "minecraft:cache_2d")]
32 | Cache2d { argument: DensityFunctionProvider },
33 | #[serde(rename = "minecraft:cache_all_in_cell")]
34 | CacheAllInCell { argument: DensityFunctionProvider },
35 | #[serde(rename = "minecraft:cache_once")]
36 | CacheOnce { argument: DensityFunctionProvider },
37 | #[serde(rename = "minecraft:clamp")]
38 | Clamp {
39 | input: DensityFunctionProvider,
40 | min: f64,
41 | max: f64,
42 | },
43 | #[serde(rename = "minecraft:constant")]
44 | Constant { argument: f64 },
45 | #[serde(rename = "minecraft:cube")]
46 | Cube { argument: DensityFunctionProvider },
47 | #[serde(rename = "minecraft:end_islands")]
48 | EndIslands,
49 | #[serde(rename = "minecraft:flat_cache")]
50 | FlatCache { argument: DensityFunctionProvider },
51 | #[serde(rename = "minecraft:half_negative")]
52 | HalfNegative { argument: DensityFunctionProvider },
53 | #[serde(rename = "minecraft:interpolated")]
54 | Interpolated { argument: DensityFunctionProvider },
55 | #[serde(rename = "minecraft:max")]
56 | Max {
57 | argument1: DensityFunctionProvider,
58 | argument2: DensityFunctionProvider,
59 | },
60 | #[serde(rename = "minecraft:min")]
61 | Min {
62 | argument1: DensityFunctionProvider,
63 | argument2: DensityFunctionProvider,
64 | },
65 | #[serde(rename = "minecraft:mul")]
66 | Mul {
67 | argument1: DensityFunctionProvider,
68 | argument2: DensityFunctionProvider,
69 | },
70 | #[serde(rename = "minecraft:noise")]
71 | Noise {
72 | noise: UnlocalizedName,
73 | xz_scale: f64,
74 | y_scale: f64,
75 | },
76 | #[serde(rename = "minecraft:old_blended_noise")]
77 | OldBlendedNoise,
78 | /*{
79 | xz_scale: f64,
80 | y_scale: f64,
81 | xz_factor: f64,
82 | y_factor: f64,
83 | smear_scale_multiplier: f64,
84 | } */
85 | #[serde(rename = "minecraft:quarter_negative")]
86 | QuarterNegative { argument: DensityFunctionProvider },
87 | #[serde(rename = "minecraft:range_choice")]
88 | RangeChoice {
89 | input: DensityFunctionProvider,
90 | min_inclusive: f64,
91 | max_exclusive: f64,
92 | when_in_range: DensityFunctionProvider,
93 | when_out_of_range: DensityFunctionProvider,
94 | },
95 | #[serde(rename = "minecraft:shift")]
96 | Shift { argument: UnlocalizedName },
97 | #[serde(rename = "minecraft:shift_a")]
98 | ShiftA { argument: UnlocalizedName },
99 | #[serde(rename = "minecraft:shift_b")]
100 | ShiftB { argument: UnlocalizedName },
101 | #[serde(rename = "minecraft:shifted_noise")]
102 | ShiftedNoise {
103 | noise: UnlocalizedName,
104 | xz_scale: f64,
105 | y_scale: f64,
106 | shift_x: DensityFunctionProvider,
107 | shift_y: DensityFunctionProvider,
108 | shift_z: DensityFunctionProvider,
109 | },
110 | #[serde(rename = "minecraft:slide")]
111 | Slide { argument: DensityFunctionProvider },
112 | #[serde(rename = "minecraft:spline")]
113 | Spline {
114 | spline: SplineValue,
115 | min_value: f64,
116 | max_value: f64,
117 | },
118 | #[serde(rename = "minecraft:square")]
119 | Square { argument: DensityFunctionProvider },
120 | #[serde(rename = "minecraft:squeeze")]
121 | Squeeze { argument: DensityFunctionProvider },
122 | #[serde(rename = "minecraft:terrain_shaper_spline")]
123 | TerrainShaperSpline {
124 | spline: TerrainShaperSplineType,
125 | min_value: f64,
126 | max_value: f64,
127 | continentalness: DensityFunctionProvider,
128 | erosion: DensityFunctionProvider,
129 | weirdness: DensityFunctionProvider,
130 | },
131 | #[serde(rename = "minecraft:weird_scaled_sampler")]
132 | WeirdScaledSampler {
133 | rarity_value_mapper: String,
134 | noise: UnlocalizedName,
135 | input: DensityFunctionProvider,
136 | },
137 | #[serde(rename = "minecraft:y_clamped_gradient")]
138 | YClampedGradient {
139 | from_y: f64,
140 | to_y: f64,
141 | from_value: f64,
142 | to_value: f64,
143 | },
144 | }
145 |
146 |
147 | #[derive(Serialize, Deserialize)]
148 | pub struct Spline {
149 | coordinate: DensityFunctionProvider,
150 | points: Vec,
151 | }
152 |
153 | #[derive(Serialize, Deserialize)]
154 | pub struct SplinePoint {
155 | location: f64,
156 | value: SplineValue,
157 | derivative: f64,
158 | }
159 |
160 | #[derive(Serialize, Deserialize)]
161 | pub enum SplineValue {
162 | Constant(f64),
163 | Spline(Spline),
164 | }
165 |
166 | #[derive(Serialize, Deserialize)]
167 | #[serde(rename_all = "snake_case")]
168 | pub enum TerrainShaperSplineType {
169 | Offset,
170 | Factor,
171 | Jaggedness,
172 | }
173 |
174 | #[derive(Serialize, Deserialize)]
175 | #[serde(rename_all = "snake_case")]
176 | pub enum RarityValueType {
177 | Type1,
178 | Type2,
179 | }
180 |
--------------------------------------------------------------------------------
/datapack/src/data/world_gen/dimension.rs:
--------------------------------------------------------------------------------
1 | use qdat::UnlocalizedName;
2 | use serde::{Deserialize, Serialize};
3 |
4 | use crate::data::{structure_set::StructureSetProvider, tags::IdsOrTag};
5 |
6 | use super::noise_settings::NoiseSettings;
7 |
8 | #[derive(Serialize, Deserialize)]
9 | pub struct Dimension {
10 | pub r#type: UnlocalizedName,
11 | pub generator: DimensionGenerator,
12 | }
13 |
14 | #[derive(Serialize, Deserialize)]
15 | #[serde(tag = "type")]
16 | pub enum DimensionGenerator {
17 | #[serde(rename = "minecraft:noise")]
18 | Noise {
19 | seed: i32,
20 | settings: DimensionNoiseSettings,
21 | biome_source: BiomeSourceType,
22 | },
23 | #[serde(rename = "minecraft:flat")]
24 | Flat { settings: SuperflatSettings },
25 | #[serde(rename = "minecraft:debug")]
26 | Debug,
27 | }
28 |
29 | #[derive(Serialize, Deserialize)]
30 | #[serde(untagged)]
31 | pub enum DimensionNoiseSettings {
32 | Preset(String),
33 | Settings(Box),
34 | }
35 |
36 | #[derive(Serialize, Deserialize)]
37 | pub struct BiomeSource {
38 | // NOTE: this might not be an uln
39 | pub biomes: Vec,
40 | pub r#type: UnlocalizedName,
41 | }
42 |
43 | #[derive(Serialize, Deserialize)]
44 | #[serde(tag = "type")]
45 | pub enum BiomeSourceType {
46 | #[serde(rename = "minecraft:vanilla_layered")]
47 | VanillaLayer {
48 | large_biomes: bool,
49 | legacy_biome_init_layer: bool,
50 | },
51 | #[serde(rename = "minecraft:multi_noise")]
52 | MultiNoise { biomes: Vec },
53 | #[serde(rename = "minecraft:the_end")]
54 | TheEnd,
55 | #[serde(rename = "minecraft:fixed")]
56 | Fixed { biome: String },
57 | #[serde(rename = "minecraft:checkerboard")]
58 | CheckerBoard { biomes: IdsOrTag, scale: i32 },
59 | }
60 |
61 | #[derive(Serialize, Deserialize)]
62 | pub struct BiomeSourceBiome {
63 | // Wiki says this can be repeated
64 | // WHAT THE FUCK DOES THAT MEAN
65 | pub biome: UnlocalizedName,
66 | pub parameters: DimensionBiomeParameters,
67 | }
68 |
69 | #[derive(Serialize, Deserialize)]
70 | pub struct DimensionBiomeParameters {
71 | pub erosion: AmountOrRangeArray,
72 | pub depth: AmountOrRangeArray,
73 | pub weirdness: AmountOrRangeArray,
74 | pub offset: AmountOrRangeArray,
75 | pub temperature: AmountOrRangeArray,
76 | pub humidity: AmountOrRangeArray,
77 | pub continentalness: AmountOrRangeArray,
78 | }
79 |
80 | #[derive(Serialize, Deserialize)]
81 | #[serde(untagged)]
82 | pub enum AmountOrRangeArray {
83 | Singleton(f32),
84 | Array([f32; 2]),
85 | }
86 |
87 | #[derive(Serialize, Deserialize)]
88 | pub struct BiomeNoise {
89 | #[serde(rename = "firstOctive")]
90 | pub first_octive: i32,
91 | pub amplitudes: Vec,
92 | }
93 |
94 | #[derive(Serialize, Deserialize)]
95 | pub struct SuperflatSettings {
96 | pub layers: Vec,
97 | pub biome: String,
98 | #[serde(default = "Default::default")]
99 | pub lakes: bool,
100 | #[serde(default = "Default::default")]
101 | pub features: bool,
102 | #[serde(default = "Default::default")]
103 | pub structure_overrides: Vec,
104 | }
105 |
106 |
107 | #[derive(Serialize, Deserialize)]
108 | pub struct SuperflatLayer {
109 | pub height: i32,
110 | pub block: UnlocalizedName,
111 | }
112 |
113 | #[derive(Serialize, Deserialize)]
114 | pub struct StrongholdSettings {
115 | pub distance: i32,
116 | pub count: i32,
117 | pub spread: i32,
118 | }
119 |
120 | #[derive(Serialize, Deserialize)]
121 | #[serde(tag = "type")]
122 | pub enum StructureSettings {
123 | #[serde(rename = "minecraft:random_spread")]
124 | RandomSpread {
125 | spacing: i32,
126 | separation: i32,
127 | salt: i32,
128 | },
129 | #[serde(rename = "minecraft:concentric_rings")]
130 | ConcentricRings {
131 | distance: i32,
132 | spread: i32,
133 | count: i32,
134 | },
135 | }
136 |
--------------------------------------------------------------------------------
/datapack/src/data/world_gen/dimension_type.rs:
--------------------------------------------------------------------------------
1 | use serde::{Deserialize, Serialize};
2 |
3 | use crate::data::tags::TagProvider;
4 |
5 | #[derive(Serialize, Deserialize)]
6 | pub struct DimensionType {
7 | pub ultrawarm: bool,
8 | pub natural: bool,
9 | pub coordinate_scale: f32,
10 | pub has_skylight: bool,
11 | pub has_ceiling: bool,
12 | pub ambient_light: f32,
13 | pub fixed_time: Option,
14 | pub piglin_safe: bool,
15 | pub bed_works: bool,
16 | pub respawn_anchor_works: bool,
17 | pub has_raids: bool,
18 | pub logical_height: i32,
19 | pub min_y: i32,
20 | pub height: i32,
21 | pub infiniburn: TagProvider,
22 | #[serde(default = "Default::default")]
23 | pub effects: DimensionEffects,
24 | }
25 |
26 | #[derive(Serialize, Deserialize)]
27 | pub enum DimensionEffects {
28 | #[serde(rename = "minecraft:overworld")]
29 | Overworld,
30 | #[serde(rename = "minecraft:the_nether")]
31 | TheNether,
32 | #[serde(rename = "minecraft:the_end")]
33 | TheEnd,
34 | }
35 |
36 | impl Default for DimensionEffects {
37 | fn default() -> Self {
38 | Self::Overworld
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/datapack/src/data/world_gen/jigsaw_pool.rs:
--------------------------------------------------------------------------------
1 | use qdat::UnlocalizedName;
2 | use serde::{Deserialize, Serialize};
3 |
4 | use crate::data::tags::IdsOrTag;
5 |
6 | use super::processors::Processor;
7 |
8 | #[derive(Serialize, Deserialize)]
9 | pub struct JigsawPool {
10 | pub name: UnlocalizedName,
11 | pub fallback: UnlocalizedName,
12 | pub elements: Vec,
13 | }
14 |
15 |
16 | #[derive(Serialize, Deserialize)]
17 | #[serde(rename_all = "snake_case")]
18 | pub enum JigsawProjection {
19 | Rigid,
20 | TerrainMatching,
21 | }
22 |
23 | #[derive(Serialize, Deserialize)]
24 | pub struct WeightedJigsawElement {
25 | weight: u32,
26 | element: JigsawElement,
27 | }
28 |
29 | #[derive(Serialize, Deserialize)]
30 | #[serde(tag = "element_type")]
31 | pub enum JigsawElement {
32 | #[serde(rename = "minecraft:empty_pool_element")]
33 | EmptyPoolElement,
34 | #[serde(rename = "minecraft:list_pool_element")]
35 | ListPoolElement {
36 | projection: JigsawProjection,
37 | elements: Vec,
38 | },
39 | #[serde(rename = "minecraft:feature_pool_element")]
40 | FeaturePoolElement {
41 | projection: JigsawProjection,
42 | feature: IdsOrTag,
43 | },
44 | #[serde(rename = "minecraft:legacy_single_pool_element")]
45 | LegacySinglePoolElement {
46 | location: UnlocalizedName,
47 | projection: JigsawProjection,
48 | processors: JigsawProcessor,
49 | },
50 | #[serde(rename = "minecraft:single_pool_element")]
51 | SinglePoolElement {
52 | location: UnlocalizedName,
53 | processors: JigsawProcessor,
54 | projection: JigsawProjection,
55 | },
56 | }
57 |
58 | #[derive(Serialize, Deserialize)]
59 | #[serde(untagged)]
60 | pub enum JigsawProcessor {
61 | Singleton(Processor),
62 | Uln(UnlocalizedName),
63 | List(Vec),
64 | }
65 |
--------------------------------------------------------------------------------
/datapack/src/data/world_gen/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod biome;
2 | pub mod carvers;
3 | pub mod density_function;
4 | pub mod dimension;
5 | pub mod dimension_type;
6 | pub mod features;
7 | pub mod jigsaw_pool;
8 | pub mod noise;
9 | pub mod noise_settings;
10 | pub mod processors;
11 | pub mod structure_features;
12 | pub mod structure_set;
13 | pub mod surface_builders;
14 |
--------------------------------------------------------------------------------
/datapack/src/data/world_gen/noise.rs:
--------------------------------------------------------------------------------
1 | use serde::{Deserialize, Serialize};
2 |
3 | #[derive(Serialize, Deserialize)]
4 | pub struct Noise {
5 | #[serde(rename = "firstOctave")]
6 | pub first_octave: i32,
7 | pub amplitudes: Vec,
8 | }
9 |
--------------------------------------------------------------------------------
/datapack/src/data/world_gen/noise_settings.rs:
--------------------------------------------------------------------------------
1 | use std::collections::HashMap;
2 |
3 | use qdat::UnlocalizedName;
4 | use serde::{Deserialize, Serialize};
5 |
6 | use crate::data::world_gen::density_function::DensityFunctionProvider;
7 |
8 | use super::features::SurfaceType;
9 |
10 | #[derive(Serialize, Deserialize)]
11 | pub struct NoiseSettings {
12 | pub sea_level: i32,
13 | pub disable_mob_generation: bool,
14 | pub ore_veins_enabled: bool,
15 | pub aquifers_enabled: bool,
16 | pub legacy_random_source: bool,
17 | pub default_block: BlockState,
18 | pub default_fluid: BlockState,
19 | pub noise: NoiseOptions,
20 | pub surface_rule: SurfaceRule,
21 | }
22 |
23 | #[derive(Serialize, Deserialize)]
24 | #[serde(tag = "type")]
25 | pub enum SurfaceRule {
26 | #[serde(rename = "minecraft:condition")]
27 | Condition {
28 | if_true: Box,
29 | then_run: Box,
30 | },
31 | #[serde(rename = "minecraft:block")]
32 | Block { result_state: BlockState },
33 | #[serde(rename = "minecraft:vertical_gradient")]
34 | VerticalGradient {
35 | random_name: String,
36 | true_at_and_below: HeightConditionProvider,
37 | false_at_and_above: HeightConditionProvider,
38 | },
39 | #[serde(rename = "minecraft:above_preliminary_surface")]
40 | AbovePreliminarySurface,
41 | #[serde(rename = "minecraft:sequence")]
42 | Sequnce { sequence: Vec },
43 | #[serde(rename = "minecraft:stone_depth")]
44 | StoneDepth {
45 | offset: i32,
46 | add_surface_depth: bool,
47 | secondary_depth_range: i32,
48 | surface_type: SurfaceType,
49 | },
50 | #[serde(rename = "minecraft:water")]
51 | Water {
52 | offset: i32,
53 | surface_depth_multiplier: i32,
54 | add_stone_depth: bool,
55 | },
56 | #[serde(rename = "minecraft:biome")]
57 | Biome { biome_is: Vec },
58 | #[serde(rename = "minecraft:y_above")]
59 | YAbove {
60 | anchor: HeightConditionProvider,
61 | surface_depth_multiplier: i32,
62 | add_stone_depth: bool,
63 | },
64 | #[serde(rename = "minecraft:not")]
65 | Not { invert: Box },
66 | #[serde(rename = "minecraft:noise_threshold")]
67 | NoiseThreshold {
68 | noise: NoiseType,
69 | min_threshold: f64,
70 | max_threshold: f64,
71 | },
72 | #[serde(rename = "minecraft:steep")]
73 | Steep,
74 | #[serde(rename = "minecraft:hole")]
75 | Hole,
76 | #[serde(rename = "minecraft:bandlands")]
77 | Bandlands,
78 | #[serde(rename = "minecraft:temperature")]
79 | Temperature,
80 | }
81 |
82 | #[derive(Serialize, Deserialize)]
83 | pub enum NoiseType {
84 | #[serde(rename = "minecraft:surface")]
85 | Surface,
86 | #[serde(rename = "minecraft:surface_swamp")]
87 | SurfaceSwamp,
88 | #[serde(rename = "minecraft:packed_ice")]
89 | PackedIce,
90 | #[serde(rename = "minecraft:ice")]
91 | Ice,
92 | #[serde(rename = "minecraft:powder_snow")]
93 | PowderSnow,
94 | #[serde(rename = "minecraft:calcite")]
95 | Calcite,
96 | #[serde(rename = "minecraft:gravel")]
97 | Gravel,
98 | #[serde(rename = "minecraft:patch")]
99 | Patch,
100 | #[serde(rename = "minecraft:nether_state_selector")]
101 | NetherStateSelector,
102 | #[serde(rename = "minecraft:netherrack")]
103 | Netherrack,
104 | #[serde(rename = "minecraft:nether_wart")]
105 | NetherWart,
106 | #[serde(rename = "minecraft:soul_sand_layer")]
107 | SoulSandLayer,
108 | #[serde(rename = "minecraft:gravel_layer")]
109 | GravelLayer,
110 | }
111 |
112 |
113 | #[derive(Serialize, Deserialize)]
114 | #[serde(untagged)]
115 | pub enum HeightConditionProvider {
116 | AboveBottom { above_bottom: i32 },
117 | Absolute { absolute: i32 },
118 | BelowTop { below_top: i32 },
119 | }
120 |
121 | #[derive(Serialize, Deserialize)]
122 | pub struct NoiseOptions {
123 | pub min_y: i32,
124 | pub height: i32,
125 | pub size_horizontal: i32,
126 | pub size_vertical: i32,
127 | #[serde(default = "Default::default")]
128 | pub island_noise_override: bool,
129 | #[serde(default = "Default::default")]
130 | pub amplified: bool,
131 | #[serde(default = "Default::default")]
132 | pub large_biomes: bool,
133 | pub sampling: NoiseSampling,
134 | pub top_slide: NoiseCurve,
135 | pub bottom_slide: NoiseCurve,
136 | pub terrain_shaper: TerrainShaper,
137 | }
138 |
139 | #[derive(Serialize, Deserialize)]
140 | pub struct TerrainShaper {
141 | pub offset: TerrainShaperValue,
142 | pub factor: TerrainShaperValue,
143 | pub jaggedness: TerrainShaperValue,
144 | }
145 |
146 | #[derive(Serialize, Deserialize)]
147 | #[serde(untagged)]
148 | pub enum TerrainShaperValue {
149 | Spline {
150 | coordinate: TerrainSpineCoordinate,
151 | points: Vec,
152 | },
153 | Constant(f32),
154 | }
155 |
156 | #[derive(Serialize, Deserialize)]
157 | pub struct TerrainSplinePoint {
158 | pub location: f32,
159 | pub value: TerrainShaperValue,
160 | pub derivative: f32,
161 | }
162 |
163 | #[derive(Serialize, Deserialize)]
164 | #[serde(rename_all = "snake_case")]
165 | pub enum TerrainSpineCoordinate {
166 | Continents,
167 | Erosion,
168 | Ridges,
169 | Weirdness,
170 | }
171 |
172 | #[derive(Serialize, Deserialize)]
173 | pub struct NoiseSampling {
174 | pub xz_scale: f64,
175 | pub xz_factor: f64,
176 | pub y_scale: f64,
177 | pub y_factor: f64,
178 | }
179 |
180 | #[derive(Serialize, Deserialize)]
181 | pub struct NoiseCurve {
182 | pub target: f32,
183 | pub size: i32,
184 | pub offset: i32,
185 | }
186 |
187 |
188 | #[derive(Serialize, Deserialize)]
189 | pub struct BlockState {
190 | #[serde(rename = "Name")]
191 | pub name: UnlocalizedName,
192 | #[serde(rename = "Properties")]
193 | #[serde(default = "Default::default")]
194 | pub properties: HashMap,
195 | }
196 |
197 | #[derive(Serialize, Deserialize)]
198 | pub struct NoiseRouter {
199 | barrier: DensityFunctionProvider,
200 | fluid_level_floodedness: DensityFunctionProvider,
201 | fluid_level_spread: DensityFunctionProvider,
202 | lava: DensityFunctionProvider,
203 | temperature: DensityFunctionProvider,
204 | vegetation: DensityFunctionProvider,
205 | continents: DensityFunctionProvider,
206 | erosion: DensityFunctionProvider,
207 | depth: DensityFunctionProvider,
208 | ridges: DensityFunctionProvider,
209 | initial_density_without_jaggedness: DensityFunctionProvider,
210 | final_density: DensityFunctionProvider,
211 | vein_toggle: DensityFunctionProvider,
212 | vein_ridged: DensityFunctionProvider,
213 | vein_gap: DensityFunctionProvider,
214 | }
215 |
--------------------------------------------------------------------------------
/datapack/src/data/world_gen/processors.rs:
--------------------------------------------------------------------------------
1 | use qdat::UnlocalizedName;
2 | use serde::{Deserialize, Serialize};
3 |
4 | use crate::data::tags::TagProvider;
5 |
6 | use super::{features::HeightMaps, noise_settings::BlockState};
7 |
8 | #[derive(Serialize, Deserialize)]
9 | pub struct ProcessorList {
10 | processors: Vec,
11 | }
12 |
13 | #[derive(Serialize, Deserialize)]
14 | #[serde(tag = "processor_type")]
15 | pub enum Processor {
16 | #[serde(rename = "minecraft:rule")]
17 | Rule { rules: Vec },
18 | #[serde(rename = "minecraft:block_age")]
19 | BlockAge { mossiness: f32 },
20 | #[serde(rename = "minecraft:block_ignore")]
21 | BlockIgnore { blocks: Vec },
22 | #[serde(rename = "minecraft:gravity")]
23 | Gravity { heightmap: HeightMaps, offset: i32 },
24 | #[serde(rename = "minecraft:block_rot")]
25 | BlockRot { integrity: f32 },
26 | #[serde(rename = "minecraft:blackstone_replace")]
27 | BlackstoneReplace,
28 | #[serde(rename = "minecraft:jigsaw_replacement")]
29 | JigsawReplacement,
30 | #[serde(rename = "minecraft:lava_submerged_block")]
31 | LavaSubmergedBlock,
32 | #[serde(rename = "minecraft:nop")]
33 | Nop,
34 | #[serde(rename = "minecraft:protected_blocks")]
35 | ProtectedBlocks { value: TagProvider },
36 | }
37 |
38 | #[derive(Serialize, Deserialize)]
39 | pub struct ProcessorRule {
40 | pub position_predicate: Option,
41 | pub input_predicate: ProcessorPredicate,
42 | pub location_predicate: ProcessorPredicate,
43 | pub output_state: BlockState,
44 | /// Never used in vanilla
45 | // TODO: this is nbt
46 | pub output_nbt: Option,
47 | }
48 |
49 | #[derive(Serialize, Deserialize)]
50 | #[serde(tag = "predicate_type")]
51 | pub enum ProcessorPredicate {
52 | #[serde(rename = "minecraft:always_true")]
53 | AlwaysTrue,
54 | #[serde(rename = "minecraft:axis_aligned_linear_pos")]
55 | AxisAlignedLinearPos {
56 | axis: Axis,
57 | min_chance: f32,
58 | max_chance: f32,
59 | min_dist: i32,
60 | max_dist: i32,
61 | },
62 | #[serde(rename = "minecraft:block_match")]
63 | BlockMatch { block: UnlocalizedName },
64 | #[serde(rename = "minecraft:blockstate_match")]
65 | BlockstateMatch { block_state: BlockState },
66 | #[serde(rename = "minecraft:random_block_match")]
67 | RandomBlockMatch {
68 | block: UnlocalizedName,
69 | probability: f32,
70 | },
71 | #[serde(rename = "minecraft:tag_match")]
72 | TagMatch { tag: UnlocalizedName },
73 | }
74 |
75 | #[derive(Serialize, Deserialize)]
76 | #[serde(tag = "predicate_type")]
77 | pub enum PositionPredicate {
78 | #[serde(rename = "minecraft:always_true")]
79 | AlwaysTrue,
80 | #[serde(rename = "minecraft:axis_aligned_linear_pos")]
81 | AxisAlignedLinearPos {
82 | axis: Axis,
83 | min_chance: f32,
84 | max_chance: f32,
85 | min_dist: i32,
86 | max_dist: i32,
87 | },
88 | #[serde(rename = "minecraft:linear_pos")]
89 | LinearPos {
90 | min_chance: f32,
91 | max_chance: f32,
92 | min_dist: i32,
93 | max_dist: i32,
94 | },
95 | }
96 |
97 | #[derive(Serialize, Deserialize)]
98 | pub enum Axis {
99 | #[serde(rename = "x")]
100 | X,
101 | #[serde(rename = "y")]
102 | Y,
103 | #[serde(rename = "z")]
104 | Z,
105 | }
106 |
--------------------------------------------------------------------------------
/datapack/src/data/world_gen/structure_features.rs:
--------------------------------------------------------------------------------
1 | use qdat::UnlocalizedName;
2 | use serde::{Deserialize, Serialize};
3 |
4 | #[derive(Serialize, Deserialize)]
5 | #[serde(tag = "type", content = "config")]
6 | pub enum StructureFeatures {
7 | #[serde(rename = "minecraft:bastion_remnant")]
8 | BastionRemnant {
9 | start_pool: UnlocalizedName,
10 | size: i32,
11 | },
12 | #[serde(rename = "minecraft:buried_treasure")]
13 | BuriedTreasure { probability: f32 },
14 | #[serde(rename = "minecraft:desert_pyramid")]
15 | DesertPyramid {},
16 | #[serde(rename = "minecraft:endcity")]
17 | Endcity {},
18 | #[serde(rename = "minecraft:fortress")]
19 | Fortress {},
20 | #[serde(rename = "minecraft:igloo")]
21 | Igloo {},
22 | #[serde(rename = "minecraft:jungle_pyramid")]
23 | JunglePyramid {},
24 | #[serde(rename = "minecraft:mansion")]
25 | Mansion {},
26 | #[serde(rename = "minecraft:mineshaft")]
27 | Mineshaft {
28 | r#type: MineshaftType,
29 | probability: f32,
30 | },
31 | #[serde(rename = "minecraft:monument")]
32 | Monument {},
33 | #[serde(rename = "minecraft:nether_fossil")]
34 | NetherFossil {},
35 | #[serde(rename = "minecraft:ocean_ruin")]
36 | OceanRuin {
37 | biome_temp: BiomeTemperature,
38 | large_probability: f32,
39 | cluster_probability: f32,
40 | },
41 | #[serde(rename = "minecraft:pillager_outpost")]
42 | PillagerOutpost {
43 | start_pool: UnlocalizedName,
44 | size: i32,
45 | },
46 | #[serde(rename = "minecraft:ruined_portal")]
47 | RuinedPortal { portal_type: RuinedPortalType },
48 | #[serde(rename = "minecraft:shipwreck")]
49 | Shipwreck {
50 | #[serde(default = "Default::default")]
51 | is_beached: bool,
52 | },
53 | #[serde(rename = "minecraft:stronghold")]
54 | Stronghold {},
55 | #[serde(rename = "minecraft:swamp_hut")]
56 | SwampHut {},
57 | #[serde(rename = "minecraft:village")]
58 | Village {
59 | start_pool: UnlocalizedName,
60 | size: i32,
61 | },
62 | }
63 |
64 | #[derive(Serialize, Deserialize)]
65 | #[serde(rename_all = "snake_case")]
66 | pub enum MineshaftType {
67 | Normal,
68 | Mesa,
69 | }
70 |
71 | #[derive(Serialize, Deserialize)]
72 | #[serde(rename_all = "snake_case")]
73 | pub enum BiomeTemperature {
74 | Warm,
75 | Cold,
76 | }
77 |
78 | #[derive(Serialize, Deserialize)]
79 | #[serde(rename_all = "snake_case")]
80 | pub enum RuinedPortalType {
81 | Standard,
82 | Desert,
83 | Jungle,
84 | Swamp,
85 | Mountain,
86 | Ocean,
87 | Nether,
88 | }
89 |
--------------------------------------------------------------------------------
/datapack/src/data/world_gen/structure_set.rs:
--------------------------------------------------------------------------------
1 | use qdat::UnlocalizedName;
2 | use serde::{Deserialize, Serialize};
3 |
4 | use crate::data::tags::IdsOrTag;
5 |
6 | #[derive(Serialize, Deserialize)]
7 | #[serde(untagged)]
8 | pub enum StructureSetProvider {
9 | Reference(UnlocalizedName),
10 | Inline(StructureSet),
11 | }
12 |
13 | #[derive(Serialize, Deserialize)]
14 | pub struct StructureSet {
15 | pub structures: Vec,
16 | pub placement: StructurePlacementModifier,
17 | }
18 |
19 | #[derive(Serialize, Deserialize)]
20 | pub struct WeightedStructure {
21 | structure: IdsOrTag,
22 | weight: i32,
23 | }
24 |
25 | #[derive(Serialize, Deserialize)]
26 | #[serde(tag = "type")]
27 | pub enum StructurePlacementModifier {
28 | #[serde(rename = "minecraft:random_spread")]
29 | RandomSpread {
30 | spread_type: Option,
31 | spacing: u16,
32 | separation: u16,
33 | salt: u64,
34 | locate_offset: Option<[i8; 3]>,
35 | },
36 | #[serde(rename = "minecraft:concentric_rings")]
37 | ConcentricRings {
38 | distance: u16,
39 | spread: u16,
40 | count: u16,
41 | },
42 | }
43 |
44 | #[derive(Serialize, Deserialize)]
45 | #[serde(rename_all = "snake_case")]
46 | pub enum SpreadType {
47 | Linear,
48 | Triangular,
49 | }
50 |
--------------------------------------------------------------------------------
/datapack/src/data/world_gen/surface_builders.rs:
--------------------------------------------------------------------------------
1 | use qdat::UnlocalizedName;
2 | use serde::{Deserialize, Serialize};
3 |
4 | use super::noise_settings::BlockState;
5 |
6 | #[derive(Serialize, Deserialize)]
7 | pub struct SurfaceBuilder {
8 | pub r#type: UnlocalizedName,
9 | pub config: SurfaceBuilderConfig,
10 | }
11 |
12 | #[derive(Serialize, Deserialize)]
13 | pub struct SurfaceBuilderConfig {
14 | pub top_material: BlockState,
15 | pub under_material: BlockState,
16 | pub underwater_material: BlockState,
17 | }
18 |
--------------------------------------------------------------------------------
/datapack/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! quartz_datapack is a crate to read and write minecraft datapacks
2 | //!
3 | //! Note: To avoid the amount of single-use datatypes some types have notes ensuring the usage is valid
4 | //! If these notes are not followed then the datapack will not be valid by vanilla's standards
5 | //! To help with this, manual Serialize and Deserialize implementations will enforce these rules
6 | //! Though, where custom implementations were not already necessary for the datatype these were not added
7 |
8 | /// Unwraps the fields and returns a missing_field error if they are `None`
9 | macro_rules! missing_field_error {
10 | ($($field: ident, $field_str: literal),*) => {
11 | $(let $field = match $field {
12 | Some(f) => f,
13 | None => return Err(serde::de::Error::missing_field($field_str)),
14 | };)*
15 | };
16 | }
17 |
18 | /// Generates a map visitor to deserialize a map with the given fields
19 | macro_rules! map_visitor {
20 | ($map: ident, $(($field: ident, $field_str: literal, $field_type: ty)),*) => {
21 | $(let mut $field = None;)*
22 |
23 | while let Some(__key__) = $map.next_key()? {
24 | match __key__ {
25 | $($field_str => {
26 | if $field.is_some() {
27 | return Err(serde::de::Error::duplicate_field($field_str))
28 | }
29 |
30 | $field = Some($map.next_value::<$field_type>()?);
31 | },)*
32 | _ => return Err(serde::de::Error::unknown_field(__key__, &[$($field_str),*]))
33 | }
34 | }
35 | };
36 | }
37 |
38 | mod datapack;
39 | pub use datapack::*;
40 | pub mod data;
41 |
--------------------------------------------------------------------------------
/datapack/tests/default_datapack.rs:
--------------------------------------------------------------------------------
1 | use quartz_datapack::{DataPack, VersionFilter};
2 |
3 | #[ignore]
4 | #[test]
5 | fn default_datapack_test() {
6 | let datapack_path = "../run/datapacks/";
7 |
8 | // We use a none filter because we don't want the filter to trigger
9 | let output = DataPack::read_datapacks(&datapack_path, VersionFilter::None);
10 |
11 | assert!(output.is_ok());
12 |
13 | let output = output.unwrap();
14 |
15 | for pack in output {
16 | let pack = pack.unwrap();
17 | let mut path = "../run/out/datapacks/".to_owned();
18 | path.push_str(pack.name());
19 | pack.write_datapack(&path).unwrap();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/macros/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "quartz_macros"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [lib]
7 | proc-macro = true
8 |
9 | [dependencies]
10 | quartz_macros_impl = { path = '../macros_impl' }
11 | syn = { version = "1.0", features = ["full"] }
12 |
--------------------------------------------------------------------------------
/macros/src/lib.rs:
--------------------------------------------------------------------------------
1 | extern crate proc_macro;
2 |
3 | use quartz_macros_impl::*;
4 | use syn::{parse_macro_input, DeriveInput};
5 |
6 | #[proc_macro_derive(WriteToPacket, attributes(packet_serde))]
7 | pub fn derive_write_to_packet(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
8 | packet::derive_write_to_packet_impl(parse_macro_input!(item as DeriveInput)).into()
9 | }
10 |
11 | #[proc_macro_derive(ReadFromPacket, attributes(packet_serde))]
12 | pub fn derive_read_from_packet(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
13 | packet::derive_read_from_packet_impl(parse_macro_input!(item as DeriveInput)).into()
14 | }
15 |
--------------------------------------------------------------------------------
/macros_impl/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "quartz_macros_impl"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | proc-macro2 = "1.0"
8 | proc-macro-crate = "1.0"
9 | quote = "1.0"
10 | syn = { version = "1.0", features = ["full"] }
11 |
--------------------------------------------------------------------------------
/macros_impl/src/lib.rs:
--------------------------------------------------------------------------------
1 | pub mod packet;
2 |
3 | use proc_macro2::{Span, TokenStream};
4 | use proc_macro_crate::{crate_name, FoundCrate};
5 | use quote::quote;
6 | use syn::{Error, GenericArgument, Ident, PathArguments, Result, Type};
7 |
8 | pub fn quartz() -> TokenStream {
9 | match crate_name("quartz") {
10 | Ok(FoundCrate::Itself) => quote! { crate },
11 | Ok(FoundCrate::Name(name)) => {
12 | let name = Ident::new(&name, Span::call_site());
13 | quote! { ::#name }
14 | }
15 | Err(e) => Error::new(Span::call_site(), format!("{e}")).to_compile_error(),
16 | }
17 | }
18 |
19 | pub fn quartz_net() -> TokenStream {
20 | match crate_name("quartz_net") {
21 | Ok(FoundCrate::Itself) => quote! { crate },
22 | Ok(FoundCrate::Name(name)) => {
23 | let name = Ident::new(&name, Span::call_site());
24 | quote! { ::#name }
25 | }
26 | Err(e) => Error::new(Span::call_site(), format!("{e}")).to_compile_error(),
27 | }
28 | }
29 |
30 | pub fn is_boxed_slice(ty: &Type) -> bool {
31 | match ty {
32 | Type::Path(path) =>
33 | path.qself.is_none()
34 | && path.path.leading_colon.is_none()
35 | && !path.path.segments.is_empty()
36 | && path.path.segments.last().unwrap().ident == "Box"
37 | && matches!(extract_type_from_container(ty), Ok(Type::Slice(_))),
38 | _ => false,
39 | }
40 | }
41 |
42 | pub fn is_option(ty: &Type) -> bool {
43 | match ty {
44 | Type::Path(path) =>
45 | path.qself.is_none()
46 | && path.path.leading_colon.is_none()
47 | && !path.path.segments.is_empty()
48 | && path.path.segments.last().unwrap().ident == "Option",
49 | _ => false,
50 | }
51 | }
52 |
53 | pub fn extract_type_from_container(ty: &Type) -> Result {
54 | match ty {
55 | Type::Slice(slice) => Ok(slice.elem.as_ref().clone()),
56 | Type::Path(path) => {
57 | let type_params = &path.path.segments.last().unwrap().arguments;
58 |
59 | let generic_arg = match type_params {
60 | PathArguments::AngleBracketed(params) => params.args.first().unwrap(),
61 | tokens => return Err(Error::new_spanned(tokens, "Expected type parameter")),
62 | };
63 |
64 | match generic_arg {
65 | GenericArgument::Type(ty) => Ok(ty.clone()),
66 | arg => Err(Error::new_spanned(arg, "Expected type parameter")),
67 | }
68 | }
69 | ty => Err(Error::new_spanned(ty, "Expected path type")),
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/macros_impl/src/packet/mod.rs:
--------------------------------------------------------------------------------
1 | mod gen;
2 | mod parse;
3 |
4 | pub use gen::*;
5 | pub use parse::*;
6 |
7 | use proc_macro2::TokenStream;
8 | use syn::{Data, DeriveInput};
9 |
10 | pub fn derive_write_to_packet_impl(input: DeriveInput) -> TokenStream {
11 | match &input.data {
12 | Data::Enum(data) => {
13 | let variants = match parse_enum(data, Side::Write) {
14 | Ok(variants) => variants,
15 | Err(e) => return e.to_compile_error(),
16 | };
17 | gen_enum_serializer_impl(input, &variants)
18 | }
19 | _ => {
20 | let fields = match parse_fields(&input, Side::Write) {
21 | Ok(fields) => fields,
22 | Err(e) => return e.to_compile_error(),
23 | };
24 | gen_struct_serializer_impl(input, &fields)
25 | }
26 | }
27 | }
28 |
29 | pub fn derive_read_from_packet_impl(input: DeriveInput) -> TokenStream {
30 | let fields = match parse_fields(&input, Side::Read) {
31 | Ok(fields) => fields,
32 | Err(e) => return e.to_compile_error(),
33 | };
34 | gen_struct_deserializer_impl(input, &fields)
35 | }
36 |
37 | #[derive(Clone, Copy, PartialEq, Eq)]
38 | pub(crate) enum Side {
39 | Read,
40 | Write,
41 | }
42 |
--------------------------------------------------------------------------------
/network/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "quartz_net"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 | qdat = { path = "../qdat" }
10 | quartz_chat = { path = "../chat" }
11 | quartz_macros = { path = "../macros" }
12 | quartz_nbt = "0.2.5"
13 |
14 | uuid = { version = "1", features = ["serde", "v4"] }
15 | openssl = "0.10"
16 | byteorder = "1.4"
17 | serde = "1"
18 | serde_json = "1"
19 | log = "0.4"
20 |
21 | [build-dependencies]
22 | quartz_macros_impl = { path = '../macros_impl' }
23 | serde = { version = "1.0", features = ["derive"] }
24 | serde_json = { version = "1.0", features = ["preserve_order"]}
25 | indexmap = { version = "1.7", features = ["serde"] }
26 | proc-macro2 = "1.0.24"
27 | quote = "1.0.8"
28 | once_cell = "1.8.0"
29 | syn = { version = "1.0.55", features = ["full"] }
--------------------------------------------------------------------------------
/network/src/bitmask.rs:
--------------------------------------------------------------------------------
1 | use crate::{PacketBuffer, PacketSerdeError, ReadFromPacket, WriteToPacket};
2 | use log::warn;
3 |
4 | #[derive(Clone, Copy, Debug)]
5 | pub struct BitMask(u128);
6 |
7 | impl BitMask {
8 | pub fn new() -> Self {
9 | Self(0)
10 | }
11 |
12 | pub fn into_raw(self) -> u128 {
13 | self.0
14 | }
15 |
16 | pub fn set(&mut self, index: usize) {
17 | self.0 |= 1u128 << index;
18 | }
19 |
20 | pub fn as_empty(&self) -> Self {
21 | Self(!self.0 | 1)
22 | }
23 | }
24 |
25 | impl Default for BitMask {
26 | fn default() -> Self {
27 | Self::new()
28 | }
29 | }
30 |
31 | impl ReadFromPacket for BitMask {
32 | fn read_from(buffer: &mut PacketBuffer) -> Result {
33 | let len = buffer.read_varying::()? as usize;
34 |
35 | if len > 2 {
36 | warn!("Encountered bit mask containing more than 128 bits");
37 | }
38 |
39 | let mut mask = 0;
40 | for _ in 0 .. len.min(2) {
41 | mask <<= 64;
42 | mask |= buffer.read::()? as u128;
43 | }
44 |
45 | Ok(Self(mask))
46 | }
47 | }
48 |
49 | impl WriteToPacket for BitMask {
50 | fn write_to(&self, buffer: &mut PacketBuffer) {
51 | let lo = (self.0 & u64::MAX as u128) as u64;
52 | let hi = (self.0 >> 64) as u64;
53 |
54 | if hi == 0 {
55 | buffer.write_varying(&1i32);
56 | buffer.write(&lo);
57 | } else {
58 | buffer.write_varying(&2i32);
59 | buffer.write(&lo);
60 | buffer.write(&hi);
61 | };
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/network/src/lib.rs:
--------------------------------------------------------------------------------
1 | mod bitmask;
2 | mod netutil;
3 | pub mod packet_data;
4 |
5 | pub use bitmask::*;
6 | pub use netutil::*;
7 |
8 | /// All possible states of a client's connection to the server.
9 | #[derive(Clone, Copy, PartialEq, Eq, Debug)]
10 | pub enum ConnectionState {
11 | /// The handshake state of the connection in which the client selects the next state to enter:
12 | /// either the `Status` state or `Login` state.
13 | Handshake,
14 | /// The client is requesting a server status ping.
15 | Status,
16 | /// The client is logging into the server.
17 | Login,
18 | /// The client has successfully logged into the server and is playing the game.
19 | Play,
20 | /// The client has disconnected.
21 | Disconnected,
22 | }
23 |
24 | /// The numeric protocol version the server uses.
25 | pub const PROTOCOL_VERSION: i32 = 755;
26 | /// The ID for the legacy ping packet.
27 | pub const LEGACY_PING_PACKET_ID: i32 = 0xFE;
28 |
29 | mod build {
30 | #![allow(clippy::redundant_pattern, clippy::match_single_binding)]
31 | use super::*;
32 | include!(concat!(env!("OUT_DIR"), "/packet_def_output.rs"));
33 | }
34 |
35 | pub use build::*;
36 |
--------------------------------------------------------------------------------
/qdat/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "qdat"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7 |
8 | [dependencies]
9 | serde = {version = "1", features = ["derive"]}
10 | phf = { version = "0.11", features = ["macros"] }
11 | tinyvec = "1.3"
12 | serde_json = "1"
13 | flashmap = "0.1"
14 |
15 | [build-dependencies]
16 | serde = { version = "1.0", features = ["derive"] }
17 | serde_json = { version = "1.0", features = ["preserve_order"]}
18 | indexmap = { version = "1.7", features = ["serde"] }
19 | proc-macro2 = "1.0.24"
20 | quote = "1.0.8"
21 | syn = { version = "1.0.55", features = ["full"] }
22 | prettyplease = "0.1"
--------------------------------------------------------------------------------
/qdat/build.rs:
--------------------------------------------------------------------------------
1 | #![allow(clippy::ptr_arg)]
2 | mod buildscript;
3 |
4 | fn main() {
5 | buildscript::gen_blockstates();
6 | buildscript::gen_items();
7 | println!("cargo:rerun-if-changed=build.rs");
8 | }
9 |
--------------------------------------------------------------------------------
/qdat/buildscript/items.rs:
--------------------------------------------------------------------------------
1 | use std::{env, path::Path};
2 |
3 | use indexmap::IndexMap;
4 |
5 | use proc_macro2::TokenStream;
6 | use serde::Deserialize;
7 |
8 | use quote::{format_ident, quote};
9 |
10 | use crate::buildscript::item_info::ItemInfo;
11 |
12 | pub fn gen_items() {
13 | let out_dir = env::var_os("OUT_DIR").unwrap();
14 | let dest_path = Path::new(&out_dir).join("items_output.rs");
15 |
16 | let data = serde_json::from_str::>(include_str!(
17 | "../../assets/items.json"
18 | ))
19 | .expect("Error parsing items.json");
20 |
21 | let item_defs = gen_const_item_structs(&data);
22 | let usize_fn = gen_item_from_usize(&data);
23 | let uln_fn = gen_item_from_id(&data);
24 |
25 | std::fs::write(
26 | &dest_path,
27 | quote! {
28 | use phf::phf_map;
29 | #item_defs
30 |
31 | #usize_fn
32 | #uln_fn
33 | }
34 | .to_string(),
35 | )
36 | .unwrap();
37 | super::format_in_place(dest_path.as_os_str());
38 |
39 | println!("cargo:rerun-if-changed=../assets/items.json");
40 | println!("cargo:rerun-if-changed=buildscript/items.rs");
41 | println!("cargo:rerun-if-changed=buildscript/item_info.rs");
42 | }
43 |
44 | /// Generates a const variable for each vanilla item
45 | fn gen_const_item_structs(data: &IndexMap) -> TokenStream {
46 | let mut streams = Vec::new();
47 |
48 | for (i, (name, item)) in data.iter().enumerate() {
49 | let ident = format_ident!("{}_ITEM", name.to_uppercase());
50 | let stack_size = item.stack_size;
51 | let rarity = item.rarity;
52 | let num_id = i as u16;
53 |
54 | streams.push(if let Some(info) = &item.info {
55 | quote! {
56 | const #ident: Item = Item {
57 | id: #name,
58 | num_id: #num_id,
59 | stack_size: #stack_size,
60 | rarity: #rarity,
61 | item_info: Some(#info)
62 | };
63 | }
64 | } else {
65 | quote! {
66 | const #ident: Item = Item {
67 | id: #name,
68 | num_id: #num_id,
69 | stack_size: #stack_size,
70 | rarity: #rarity,
71 | item_info: None
72 | };
73 | }
74 | });
75 | }
76 |
77 | streams
78 | .into_iter()
79 | .reduce(|mut out, stream| {
80 | out.extend(stream);
81 | out
82 | })
83 | .unwrap()
84 | }
85 |
86 | /// Generates a phf map to lookup from the i32 used in the network protocol to an item instance
87 | fn gen_item_from_usize(data: &IndexMap) -> TokenStream {
88 | let mut branches = Vec::new();
89 |
90 | for (id, (name, _)) in data.iter().enumerate() {
91 | let name = format_ident!("{}_ITEM", name.to_uppercase());
92 | let id = id as i32;
93 | branches.push(quote! {
94 | #id => #name
95 | })
96 | }
97 |
98 | quote! {
99 | pub static ITEM_LOOKUP_BY_NUMERIC_ID: phf::Map = phf_map!{
100 | #(#branches),*
101 | };
102 | }
103 | }
104 |
105 | /// Generates a phf map to lookup from an identifier to an Item instance
106 | ///
107 | /// # Note
108 | /// Is explicitly not a ULN, it is just the identifier part
109 | fn gen_item_from_id(data: &IndexMap) -> TokenStream {
110 | let mut branches = Vec::new();
111 |
112 | for (name, _) in data.iter() {
113 | let const_name = format_ident!("{}_ITEM", name.to_uppercase());
114 | branches.push(quote! {
115 | #name => #const_name
116 | })
117 | }
118 |
119 | quote! {
120 | pub static ITEM_LOOKUP_BY_NAME: phf::Map<&'static str, Item> = phf_map!{
121 | #(#branches),*
122 | };
123 | }
124 | }
125 |
126 | #[derive(Deserialize)]
127 | struct RawItemData {
128 | pub stack_size: u8,
129 | pub rarity: u8,
130 | pub info: Option,
131 | }
132 |
--------------------------------------------------------------------------------
/qdat/buildscript/mod.rs:
--------------------------------------------------------------------------------
1 | mod blockstate;
2 | pub use blockstate::gen_blockstates;
3 | mod item_info;
4 | mod items;
5 | pub use items::gen_items;
6 |
7 | use std::{ffi::OsStr, process::Command};
8 |
9 | pub(crate) fn format_in_place(file: &OsStr) {
10 | Command::new("rustfmt")
11 | .arg(file)
12 | .output()
13 | .unwrap_or_else(|_| panic!("Failed to format file: {file:?}"));
14 | }
15 |
16 | pub(crate) fn format_ast(code: String) -> syn::Result {
17 | let file = syn::parse_file(&code)?;
18 | Ok(prettyplease::unparse(&file))
19 | }
20 |
--------------------------------------------------------------------------------
/qdat/src/block/behavior/mod.rs:
--------------------------------------------------------------------------------
1 | // use crate::world::location::BlockPosition;
2 |
3 | // pub trait BlockBehavior {
4 | // // TO DO: add world argument
5 | // fn on_break(_position: BlockPosition, _state: &'static BlockState) {}
6 | // }
7 |
8 | pub struct BlockBehaviorSMT {
9 | // on_break: fn(position: BlockPosition, state: &'static BlockState),
10 | }
11 |
12 | impl BlockBehaviorSMT {
13 | // Allow new_without_default because we plan on yeeting this (or rewriting it) later anyway
14 | #[allow(clippy::new_without_default)]
15 | pub fn new() -> Self {
16 | BlockBehaviorSMT {
17 | // on_break: T::on_break,
18 | }
19 | }
20 | }
21 |
22 | impl Clone for BlockBehaviorSMT {
23 | fn clone(&self) -> Self {
24 | BlockBehaviorSMT {
25 | // on_break: self.on_break,
26 | }
27 | }
28 | }
29 |
30 | pub struct DefaultBehavior;
31 |
32 | // impl BlockBehavior for DefaultBehavior {}
33 |
--------------------------------------------------------------------------------
/qdat/src/block/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod behavior;
2 | #[allow(missing_docs, nonstandard_style, dead_code)]
3 | pub mod states;
4 |
5 | use crate::UnlocalizedName;
6 | pub use behavior::BlockBehaviorSMT;
7 | use std::fmt::{self, Debug, Display, Formatter};
8 | use tinyvec::ArrayVec;
9 |
10 | pub type StateID = u16;
11 |
12 | /// A specific block type, not to be confused with a block state which specifies variants of a type. This
13 | /// is used as a data handle for block states.
14 | pub struct Block {
15 | /// The namespaced key identifying this block.
16 | pub name: UnlocalizedName,
17 | /// All block state properties and their valid values.
18 | pub properties: ArrayVec<[(String, Vec); 16]>,
19 | /// The ID for the base state of this block.
20 | pub base_state: StateID,
21 | /// The ID for the default state of this block.
22 | pub default_state: StateID,
23 | /// The static method table defining the behavior of a block.
24 | pub behavior: BlockBehaviorSMT,
25 | }
26 |
27 | impl Display for Block {
28 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
29 | Display::fmt(&self.name, f)
30 | }
31 | }
32 |
33 | impl Debug for Block {
34 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
35 | Display::fmt(self, f)
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/qdat/src/block/states.rs:
--------------------------------------------------------------------------------
1 | use crate::block::StateID;
2 |
3 | mod build {
4 | #![allow(clippy::should_implement_trait)]
5 | use super::*;
6 | include!(concat!(env!("OUT_DIR"), "/blockstate_output.rs"));
7 | }
8 | pub use build::*;
9 |
10 | pub const AIR: StateID = BlockStateData::Air.id();
11 | pub const VOID_AIR: StateID = BlockStateData::VoidAir.id();
12 | pub const CAVE_AIR: StateID = BlockStateData::CaveAir.id();
13 |
14 | #[inline]
15 | pub const fn is_air(state: StateID) -> bool {
16 | state == AIR || state == VOID_AIR || state == CAVE_AIR
17 | }
18 |
19 | pub struct BlockStateMetadata {
20 | pub default_state_data: BlockStateData,
21 | pub internal_block_id: usize,
22 | }
23 |
24 | impl BlockStateMetadata {
25 | const fn new(default_state_data: BlockStateData, internal_block_id: usize) -> Self {
26 | BlockStateMetadata {
27 | default_state_data,
28 | internal_block_id,
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/qdat/src/item/item.rs:
--------------------------------------------------------------------------------
1 | use crate::item::*;
2 |
3 | include!(concat!(env!("OUT_DIR"), "/items_output.rs"));
4 |
5 | /// Represents a minecraft item
6 | #[derive(Debug)]
7 | pub struct Item {
8 | /// The item id
9 | pub id: &'static str,
10 | pub num_id: u16,
11 | /// The max size a stack can be
12 | pub stack_size: u8,
13 | /// The rarity of the item
14 | pub rarity: u8,
15 | /// Holds extra info about the item
16 | pub item_info: Option,
17 | }
18 |
--------------------------------------------------------------------------------
/qdat/src/item/item_info.rs:
--------------------------------------------------------------------------------
1 | use serde::Deserialize;
2 |
3 | #[derive(Debug, Deserialize)]
4 | #[serde(untagged)]
5 | /// Represents all possible extra data we need to store about items
6 | pub enum ItemInfo {
7 | /// The info needed for food items
8 | FoodInfo {
9 | /// The amount of hunger the item restores
10 | hunger: u32,
11 | /// The amount of saturation given
12 | saturation: f32,
13 | /// Weather the item is a meat item
14 | meat: bool,
15 | /// If the player can eat it while they are full
16 | eat_when_full: bool,
17 | /// If the player eats the food item faster than normal
18 | snack: bool,
19 | // TODO: Add status effects
20 | // status_effects: Vec
21 | },
22 |
23 | /// The info needed for tools
24 | ToolInfo {
25 | /// The type of the tool
26 | tool_type: ToolType,
27 | /// The level of the tool
28 | level: ToolLevel,
29 | /// The damage done by the tool
30 | attack_damage: f32, // TODO: implements enchantments
31 | // possible_enchantments: Vec
32 | },
33 |
34 | /// The info needed for armor items
35 | ArmorInfo {
36 | /// The type of the armor
37 | armor_type: ArmorType,
38 | // level: ArmorLevel,
39 | /// The protection given by the armor piece
40 | protection: u32,
41 | /// The amount of toughness given
42 | toughness: f32,
43 | /// The max durability of the item
44 | max_durability: u32,
45 | // TODO: implements enchantments
46 | // possible_enchantments: Vec
47 | },
48 |
49 | /// Miscellaneous tools
50 | UsableInfo {
51 | /// Which tool type the item is
52 | usable_type: UsableType,
53 | /// The durability of the item
54 | max_durability: u32,
55 | },
56 |
57 | /// Ranged weapon info
58 | RangedWeaponInfo {
59 | /// Which ranged weapon it is
60 | weapon_type: RangedWeapon,
61 | /// The max charge time
62 | max_charge_time: u32,
63 | /// The max durability
64 | max_durability: u32,
65 | },
66 | }
67 |
68 | impl ItemInfo {
69 | /// Gets the max durability of an item
70 | pub const fn max_durability(&self) -> u32 {
71 | match self {
72 | ItemInfo::FoodInfo { .. } => 0,
73 | ItemInfo::ArmorInfo { max_durability, .. } => *max_durability,
74 | ItemInfo::UsableInfo { max_durability, .. } => *max_durability,
75 | ItemInfo::RangedWeaponInfo { max_durability, .. } => *max_durability,
76 | ItemInfo::ToolInfo { level, .. } => level.max_durability(),
77 | }
78 | }
79 | }
80 |
81 | /// The different types of tools
82 | #[derive(Debug, Deserialize)]
83 | #[serde(rename_all = "snake_case")]
84 | pub enum ToolType {
85 | Sword,
86 | Pickaxe,
87 | Shovel,
88 | Axe,
89 | Hoe,
90 | }
91 |
92 | /// The possible levels for tools
93 | #[derive(Debug, Deserialize)]
94 | #[serde(rename_all = "snake_case")]
95 | pub enum ToolLevel {
96 | Wood,
97 | Stone,
98 | Iron,
99 | Gold,
100 | Diamond,
101 | Netherite,
102 | }
103 |
104 | impl ToolLevel {
105 | /// The max durability of the tool based on its level
106 | pub const fn max_durability(&self) -> u32 {
107 | match self {
108 | ToolLevel::Wood => 59,
109 | ToolLevel::Gold => 32,
110 | ToolLevel::Stone => 131,
111 | ToolLevel::Iron => 250,
112 | ToolLevel::Diamond => 1561,
113 | ToolLevel::Netherite => 2031,
114 | }
115 | }
116 | }
117 |
118 | /// The possible armor pieces
119 | #[derive(Debug, Deserialize)]
120 | #[serde(rename_all = "snake_case")]
121 | pub enum ArmorType {
122 | Helmet,
123 | Chestplate,
124 | Leggings,
125 | Boots,
126 | }
127 |
128 | /// The possible usable items
129 | #[derive(Debug, Deserialize)]
130 | #[serde(rename_all = "snake_case")]
131 | pub enum UsableType {
132 | Shears,
133 | FishingRod,
134 | FlintAndSteel,
135 | Shield,
136 | CarrotStick,
137 | FungusStick,
138 | }
139 |
140 | /// The possible ranged weapons
141 | #[derive(Debug, Deserialize)]
142 | #[serde(rename_all = "snake_case")]
143 | pub enum RangedWeapon {
144 | Bow,
145 | Crossbow,
146 | Trident,
147 | }
148 |
--------------------------------------------------------------------------------
/qdat/src/item/mod.rs:
--------------------------------------------------------------------------------
1 | // We allow module name to be the same because we don't expose the item module
2 | #[allow(clippy::module_inception)]
3 | mod item;
4 | #[allow(missing_docs)]
5 | mod item_info;
6 |
7 | pub use item::*;
8 | pub use item_info::*;
9 |
10 | use crate::block::states::BlockStateData;
11 | pub fn item_to_block(item: &'static Item) -> Option {
12 | crate::block::states::BLOCK_LOOKUP_BY_NAME
13 | .get(item.id)
14 | .map(|bsm| bsm.default_state_data)
15 | }
16 |
--------------------------------------------------------------------------------
/qdat/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![feature(new_uninit)]
2 |
3 | pub mod block;
4 | pub mod item;
5 | pub mod world;
6 |
7 | pub mod uln;
8 |
9 | pub use uln::*;
10 |
11 | use serde::{Deserialize, Serialize};
12 |
13 | #[derive(Debug, Serialize, Deserialize, Clone, Copy)]
14 | #[serde(rename = "snake_case")]
15 | pub enum Gamemode {
16 | /// None is only valid when sending the JoinGame packet previous gamemode packet
17 | None,
18 | Survival,
19 | Creative,
20 | Adventure,
21 | Specator,
22 | }
23 |
--------------------------------------------------------------------------------
/qdat/src/world/lighting.rs:
--------------------------------------------------------------------------------
1 | use std::{
2 | error::Error,
3 | fmt::{self, Display, Formatter},
4 | ptr,
5 | };
6 |
7 | pub const LIGHTING_LENGTH: usize = 2048;
8 | type RawLightBuffer = [u8; LIGHTING_LENGTH];
9 |
10 | pub struct Lighting {
11 | pub block: Option,
12 | pub sky: Option,
13 | }
14 |
15 | impl Lighting {
16 | pub const fn new() -> Self {
17 | Lighting {
18 | block: None,
19 | sky: None,
20 | }
21 | }
22 |
23 | #[inline]
24 | pub fn init_block(&mut self, source: &[u8]) -> Result<(), LightingInitError> {
25 | Self::init(source, &mut self.block)
26 | }
27 |
28 | #[inline]
29 | pub fn init_sky(&mut self, source: &[u8]) -> Result<(), LightingInitError> {
30 | Self::init(source, &mut self.sky)
31 | }
32 |
33 | #[inline]
34 | pub fn has_block_light(&self) -> bool {
35 | self.block.is_some()
36 | }
37 |
38 | #[inline]
39 | pub fn block_light(&self) -> Option<&LightBuffer> {
40 | self.block.as_ref()
41 | }
42 |
43 | #[inline]
44 | pub fn block_light_mut(&mut self) -> Option<&mut LightBuffer> {
45 | self.block.as_mut()
46 | }
47 |
48 | #[inline]
49 | pub fn has_sky_light(&self) -> bool {
50 | self.sky.is_some()
51 | }
52 |
53 | #[inline]
54 | pub fn sky_light(&self) -> Option<&LightBuffer> {
55 | self.sky.as_ref()
56 | }
57 |
58 | #[inline]
59 | pub fn sky_light_mut(&mut self) -> Option<&mut LightBuffer> {
60 | self.sky.as_mut()
61 | }
62 |
63 | fn init(source: &[u8], buffer: &mut Option) -> Result<(), LightingInitError> {
64 | if buffer.is_some() {
65 | return Err(LightingInitError::AlreadyInitialized);
66 | }
67 |
68 | *buffer = Some(LightBuffer::new(source)?);
69 | Ok(())
70 | }
71 | }
72 |
73 | #[derive(Debug, Clone)]
74 | #[repr(transparent)]
75 | pub struct LightBuffer {
76 | pub data: Box,
77 | }
78 |
79 | impl LightBuffer {
80 | pub fn new(source: &[u8]) -> Result {
81 | if source.len() != LIGHTING_LENGTH {
82 | return Err(LightingInitError::InvalidLength(source.len()));
83 | }
84 |
85 | let src = source.as_ptr();
86 | let mut buf = Box::::new_uninit();
87 | let dst = buf.as_mut_ptr() as *mut u8;
88 |
89 | // Safety:
90 | // - `src` is valid for `LIGHTING_LENGTH` u8s because of the length check at the beginning
91 | // of this function
92 | // - `dst` is valid for `LIGHTING_LENGTH` u8s because it points to a `LightBuffer` which
93 | // is a [u8; LIGHTING_LENGTH]
94 | // - `src` is aligned because it came from a valid slice, and `dst` is aligned because it
95 | // came from a Box
96 | // - `src` and `dst` do not overlap because `dst` was newly allocated
97 | unsafe {
98 | ptr::copy_nonoverlapping(src, dst, LIGHTING_LENGTH);
99 | }
100 |
101 | Ok(LightBuffer {
102 | // Safety: we properly initialized the array above
103 | data: unsafe { buf.assume_init() },
104 | })
105 | }
106 | }
107 |
108 | #[derive(Clone, Copy, Debug)]
109 | pub enum LightingInitError {
110 | InvalidLength(usize),
111 | AlreadyInitialized,
112 | }
113 |
114 | impl Display for LightingInitError {
115 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
116 | match self {
117 | Self::InvalidLength(len) => write!(
118 | f,
119 | "expected light buffer of length {LIGHTING_LENGTH} but found length of {len}"
120 | ),
121 | Self::AlreadyInitialized => write!(f, "light buffer already initialized"),
122 | }
123 | }
124 | }
125 |
126 | impl Error for LightingInitError {}
127 |
--------------------------------------------------------------------------------
/qdat/src/world/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod lighting;
2 | pub mod location;
3 |
--------------------------------------------------------------------------------
/quartz/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "quartz"
3 | version = "0.1.0"
4 | authors = ["maddymakesgames", "Cassy343"]
5 | edition = "2021"
6 |
7 | [[bin]]
8 | name = "quartz_launcher"
9 | path = "src/main.rs"
10 |
11 | [lib]
12 | name = "quartz"
13 | path = "src/lib.rs"
14 |
15 | [dependencies]
16 | # Internal dependencies
17 | quartz_chat = { path = "../chat" }
18 | quartz_datapack = { path = "../datapack" }
19 | quartz_util = { path = "../util" }
20 | quartz_macros = { path = "../macros" }
21 | quartz_net = { path = "../network" }
22 | qdat = { path = "../qdat" }
23 |
24 | # External dependencies
25 | byteorder = "1.4.3"
26 | dashmap = "5.1.0"
27 | enum_dispatch = "0.3.7"
28 | flate2 = "1.0.20"
29 | flashmap = "0.1"
30 | futures-util = "0.3.16"
31 | hecs = {git = "https://github.com/Rusty-Quartz/checs", features = ["macros"]}
32 | hex = "0.4.3"
33 | linefeed = "0.6.0"
34 | log = "0.4.14"
35 | once_cell = "1.8.0"
36 | openssl = "0.10.34"
37 | parking_lot = { version = "0.12.0", features = ["nightly"] }
38 | quartz_commands = "0.1.0"
39 | quartz_nbt = { version = "0.2.5", features = ["serde"] }
40 | rand = "0.8.4"
41 | regex = "1.5"
42 | serde = { version = "1.0", features = ["derive"] }
43 | serde_json = "1.0"
44 | static_assertions = "1.1.0"
45 | tinyvec = "1.2.0"
46 | ureq = { version = "2.1.1", features = ["json"] }
47 | uuid = { version = "1", features = ["serde", "v4", "v3"] }
48 | noise = "0.7.0"
49 |
50 | [dependencies.tokio]
51 | version = "1.9.0"
52 | features = [
53 | "io-util",
54 | "fs",
55 | "net",
56 | "parking_lot",
57 | "rt",
58 | "rt-multi-thread",
59 | "sync",
60 | "time"
61 | ]
62 |
63 | [build-dependencies]
64 | quartz_macros_impl = { path = '../macros_impl' }
65 | serde = { version = "1.0", features = ["derive"] }
66 | serde_json = { version = "1.0", features = ["preserve_order"]}
67 | indexmap = { version = "1.7", features = ["serde"] }
68 | proc-macro2 = "1.0.24"
69 | quote = "1.0.8"
70 | once_cell = "1.8.0"
71 | syn = { version = "1.0.55", features = ["full"] }
72 |
73 | [target.'cfg(unix)'.dependencies]
74 | termion = "2.0"
--------------------------------------------------------------------------------
/quartz/format.sh:
--------------------------------------------------------------------------------
1 | # `cargo fmt` ignores some directories in here for some reason, so this file just invokes `rustfmt`
2 | # with the appropriate config on all rust source files manually
3 |
4 | find -name "*.rs" -exec rustfmt --config-path ../ {} \;
5 |
--------------------------------------------------------------------------------
/quartz/src/base/assets.rs:
--------------------------------------------------------------------------------
1 | /// JSON data detailing block and state information.
2 | pub const BLOCK_INFO: &str = include_str!("../../../assets/blocks.json");
3 |
4 | /// Item data detailing stack size, rarity, etc.
5 | pub const ITEM_INFO: &str = include_str!("../../../assets/items.json");
6 |
--------------------------------------------------------------------------------
/quartz/src/base/config.rs:
--------------------------------------------------------------------------------
1 | use log::*;
2 | use qdat::Gamemode;
3 | use quartz_chat::Component;
4 | use serde::{Deserialize, Serialize};
5 | use std::{
6 | fs::{File, OpenOptions},
7 | io::{self, prelude::*, Read, SeekFrom, Write},
8 | path::Path,
9 | };
10 |
11 | /// The main server configuration.
12 | #[derive(Serialize, Deserialize)]
13 | pub struct Config {
14 | /// The maximum number of players the server will allow, defaults to 50.
15 | pub max_players: u16,
16 | /// The server IP, defaults to "0.0.0.0"
17 | pub server_ip: String,
18 | /// The server port, defaults to 25565.
19 | pub port: u16,
20 | /// The server's message of the day, written using CFMT format (see `chat::cfmt::parse_cfmt`).
21 | pub motd: Component,
22 | /// Whether to run the server in online or offline mode
23 | /// Offline mode skips the login state of the connection flow
24 | pub online_mode: bool,
25 | /// The default gamemode for a player who joins the server
26 | pub default_gamemode: Gamemode,
27 | }
28 |
29 | // Instantiate a config with default values
30 | impl Default for Config {
31 | fn default() -> Self {
32 | Config {
33 | max_players: 50,
34 | server_ip: "0.0.0.0".to_owned(),
35 | port: 25565,
36 | motd: Component::text("A Minecraft Server".to_owned()),
37 | online_mode: true,
38 | default_gamemode: Gamemode::Survival,
39 | }
40 | }
41 | }
42 |
43 | /// Attempts to parse the server configuration at the given path. The config should be in JSON format.
44 | pub fn load_config(path: &Path) -> io::Result {
45 | let std_path = Path::new(path);
46 |
47 | if std_path.exists() {
48 | // Try to open the file
49 | let mut file = OpenOptions::new().read(true).write(true).open(std_path)?;
50 |
51 | // Read the file to a string
52 | let mut json = String::new();
53 | file.read_to_string(&mut json)?;
54 |
55 | // Parse the json
56 | let config: Config = match serde_json::from_str(&json) {
57 | Ok(cfg) => cfg,
58 | Err(e) => {
59 | error!("Invalid config JSON: {}", e);
60 | return use_default(&mut file);
61 | }
62 | };
63 |
64 | Ok(config)
65 | } else {
66 | info!("Config file not found, creating file");
67 | use_default(&mut File::create(std_path)?)
68 | }
69 | }
70 |
71 | fn use_default(file: &mut File) -> io::Result {
72 | info!("Using default configurations");
73 |
74 | let default = Config::default();
75 |
76 | // Go to the beginning of the file
77 | file.seek(SeekFrom::Start(0))?;
78 |
79 | // Write the default JSON
80 | let json = serde_json::to_string_pretty(&default).unwrap();
81 | let bytes = json.as_bytes();
82 | file.write_all(bytes)?;
83 |
84 | // Reset the file length
85 | file.set_len(bytes.len() as u64)?;
86 |
87 | Ok(default)
88 | }
89 |
--------------------------------------------------------------------------------
/quartz/src/base/diagnostic.rs:
--------------------------------------------------------------------------------
1 | pub struct Diagnostics {
2 | pub(crate) microseconds_per_tick: f64,
3 | }
4 |
5 | impl Diagnostics {
6 | pub const fn new() -> Self {
7 | Diagnostics {
8 | microseconds_per_tick: 0.0,
9 | }
10 | }
11 |
12 | /// Returns a buffered milliseconds per tick (MSPT) measurement. This reading is buffer for 100
13 | /// tick cycles.
14 | #[inline]
15 | pub fn mspt(&self) -> f64 {
16 | self.microseconds_per_tick / 1000.0
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/quartz/src/base/exec.rs:
--------------------------------------------------------------------------------
1 | use linefeed::{DefaultTerminal, Interface};
2 | use log::{error, info};
3 | use once_cell::sync::OnceCell;
4 | use parking_lot::{Mutex, RwLock};
5 | use std::{
6 | fmt::Display,
7 | sync::{
8 | atomic::{AtomicBool, Ordering},
9 | Arc,
10 | },
11 | time::{Duration, Instant},
12 | };
13 | use tokio::{runtime::Builder, task::LocalSet};
14 |
15 | use crate::{CommandExecutor, Config, Diagnostics, QuartzServer};
16 |
17 | /// The state variable that controls whether or not the server and its various sub-processes are running.
18 | /// If set to false then the server will gracefully stop.
19 | pub(crate) static RUNNING: AtomicBool = AtomicBool::new(false);
20 | static CONFIG: OnceCell> = OnceCell::new();
21 | static RAW_CONSOLE: OnceCell>> = OnceCell::new();
22 | pub static DIAGNOSTICS: Mutex = Mutex::new(Diagnostics::new());
23 | static COMMAND_EXECUTOR: OnceCell = OnceCell::new();
24 |
25 | /// Returns whether or not the server is running.
26 | #[inline]
27 | pub fn is_running() -> bool {
28 | RUNNING.load(Ordering::Acquire)
29 | }
30 |
31 | pub fn config() -> &'static RwLock {
32 | CONFIG.get().expect("Config not initialized yet")
33 | }
34 |
35 | pub fn raw_console() -> &'static Interface {
36 | &**RAW_CONSOLE.get().expect("Raw console not initialized")
37 | }
38 |
39 | pub unsafe fn raw_console_unchecked() -> &'static Interface {
40 | &**RAW_CONSOLE.get_unchecked()
41 | }
42 |
43 | pub fn display_to_console(message: &T) {
44 | match raw_console().lock_writer_erase() {
45 | Ok(mut writer) =>
46 | if let Err(e) = writeln!(writer, "{message}") {
47 | error!("Failed to send message to console: {}", e);
48 | },
49 | Err(e) => error!("Failed to lock console interface: {}", e),
50 | }
51 | }
52 |
53 | pub fn command_executor() -> &'static CommandExecutor {
54 | COMMAND_EXECUTOR
55 | .get()
56 | .expect("Command executor not initialized")
57 | }
58 |
59 | pub fn run(config: Config, raw_console: Arc>) {
60 | CONFIG
61 | .set(RwLock::new(config))
62 | .ok()
63 | .expect("Config initialized before server was run.");
64 | RAW_CONSOLE
65 | .set(raw_console)
66 | .ok()
67 | .expect("Raw console initialized before server was run.");
68 |
69 | let rt = Builder::new_multi_thread()
70 | .enable_all()
71 | .thread_name("main-tick-thread")
72 | .build()
73 | .expect("Failed to build main-tick runtime");
74 | let rt = Arc::new(rt);
75 |
76 | let mut server = QuartzServer::new(Arc::clone(&rt));
77 | server.init();
78 | COMMAND_EXECUTOR
79 | .set(CommandExecutor::new())
80 | .ok()
81 | .expect("Command executor initialized unexpectedly");
82 |
83 | info!("Started server thread");
84 |
85 | let local_set = LocalSet::new();
86 | local_set.block_on(&*rt, async move {
87 | let mut clock = ServerClock::new();
88 |
89 | while RUNNING.load(Ordering::Acquire) {
90 | if let Some(mut guard) = DIAGNOSTICS.try_lock() {
91 | guard.microseconds_per_tick = clock.micros_ema;
92 | }
93 |
94 | clock.start();
95 | server.tick().await;
96 | clock.finish_tick().await;
97 | }
98 | });
99 |
100 | drop(local_set);
101 |
102 | let rt = Arc::try_unwrap(rt);
103 | match rt {
104 | Ok(rt) => rt.shutdown_timeout(Duration::from_secs(5)),
105 | Err(_) => error!("Failed to reclaim ownership of runtime"),
106 | }
107 | }
108 |
109 | const FULL_TICK_LENGTH: u64 = 50;
110 | const FULL_TICK: Duration = Duration::from_millis(FULL_TICK_LENGTH);
111 |
112 | /// Keeps track of the time each tick takes and regulates the server ticks per second (TPS).
113 | pub struct ServerClock {
114 | micros_ema: f64,
115 | time: Instant,
116 | }
117 |
118 | impl ServerClock {
119 | /// Creates a new clock with the given tick length in milliseconds.
120 | pub fn new() -> Self {
121 | ServerClock {
122 | micros_ema: 0.0,
123 | time: Instant::now(),
124 | }
125 | }
126 |
127 | /// Called at the start of a server tick.
128 | pub(crate) fn start(&mut self) {
129 | self.time = Instant::now();
130 | }
131 |
132 | /// The tick code has finished executing, so record the time and sleep if extra time remains.
133 | async fn finish_tick(&mut self) -> f64 {
134 | let elapsed = self.time.elapsed();
135 | let micros = elapsed.as_micros() as f64;
136 | self.micros_ema = (99.0 * self.micros_ema + micros) / 100.0;
137 |
138 | if elapsed < FULL_TICK {
139 | tokio::time::sleep(FULL_TICK - elapsed).await;
140 | }
141 |
142 | micros
143 | }
144 |
145 | /// Converts a milliseconds pet tick value to ticks per second.
146 | #[inline]
147 | pub fn as_tps(mspt: f64) -> f64 {
148 | if mspt < FULL_TICK_LENGTH as f64 {
149 | 1000.0 / FULL_TICK_LENGTH as f64
150 | } else {
151 | 1000.0 / mspt
152 | }
153 | }
154 |
155 | /// The maximum tps the server will tick at.
156 | #[inline]
157 | pub fn max_tps() -> f64 {
158 | 1000.0 / FULL_TICK_LENGTH as f64
159 | }
160 | }
161 |
162 | impl Default for ServerClock {
163 | fn default() -> Self {
164 | Self::new()
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/quartz/src/base/mod.rs:
--------------------------------------------------------------------------------
1 | // TODO: we might want to change the name of these to something else
2 | // depends on what we end up doing with StaticRegistry
3 | pub mod registries;
4 | pub(crate) mod static_registry;
5 | // TODO: move behind a compiler flag or something
6 | pub use super::static_registry::*;
7 |
8 | /// Contains quartz assets data.
9 | pub mod assets;
10 | /// Defines the server config.
11 | pub mod config;
12 | pub mod diagnostic;
13 | pub mod exec;
14 | /// Main server module.
15 | pub mod server;
16 |
17 | pub use config::Config;
18 | pub use diagnostic::Diagnostics;
19 | pub use exec::*;
20 | pub use server::QuartzServer;
21 |
--------------------------------------------------------------------------------
/quartz/src/base/static_registry.rs:
--------------------------------------------------------------------------------
1 | use crate::{
2 | block::{entity::StaticBlockEntity, StaticBlockState, *},
3 | command::StaticCommandExecutor,
4 | };
5 | use log::info;
6 | use once_cell::sync::OnceCell;
7 | use qdat::{
8 | block::{states::BLOCK_LOOKUP_BY_NAME, Block},
9 | UlnStr,
10 | };
11 |
12 | static GLOBAL_STATIC_REGISTRY: OnceCell = OnceCell::new();
13 |
14 | pub type BlockState = StaticBlockState;
15 | pub type StateID = qdat::block::StateID;
16 | pub type BlockEntity = StaticBlockEntity;
17 | pub type Registry = StaticRegistry;
18 | pub type CommandExecutor = StaticCommandExecutor;
19 |
20 | pub const fn null_state_id() -> StateID {
21 | 0u16
22 | }
23 |
24 | pub struct StaticRegistry {
25 | blocks: &'static [Block],
26 | global_palette: Box<[StaticBlockState]>,
27 | }
28 |
29 | impl StaticRegistry {
30 | pub(crate) fn init() -> Result<(), ()> {
31 | info!("Initializing static registry");
32 |
33 | info!("Initializing blocks");
34 | let mut raw = load_raw_block_data();
35 | attach_behavior(&mut raw);
36 | let blocks = make_block_list(&raw).leak();
37 | let global_palette = make_static_global_palette(&raw, blocks).into_boxed_slice();
38 |
39 | GLOBAL_STATIC_REGISTRY
40 | .set(StaticRegistry {
41 | blocks,
42 | global_palette,
43 | })
44 | .map_err(|_| ())
45 | }
46 |
47 | #[inline]
48 | pub fn get() -> &'static Self {
49 | #[cfg(debug_assertions)]
50 | {
51 | GLOBAL_STATIC_REGISTRY
52 | .get()
53 | .expect("Global static registry not initialized")
54 | }
55 |
56 | #[cfg(not(debug_assertions))]
57 | {
58 | unsafe { GLOBAL_STATIC_REGISTRY.get_unchecked() }
59 | }
60 | }
61 |
62 | pub fn default_state(block_name: &UlnStr) -> Option {
63 | if block_name.namespace() != "minecraft" {
64 | return None;
65 | }
66 |
67 | BLOCK_LOOKUP_BY_NAME
68 | .get(block_name.identifier())
69 | .map(|meta| StaticBlockState {
70 | // Safety: internal block IDs are guaranteed to be consistent and in-bounds
71 | handle: unsafe { Self::get().blocks.get_unchecked(meta.internal_block_id) },
72 | data: meta.default_state_data,
73 | })
74 | }
75 |
76 | pub fn state_for_id(id: StateID) -> Option<&'static BlockState> {
77 | Self::get().global_palette.get(id as usize)
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/quartz/src/block/entities/furnace_entity.rs:
--------------------------------------------------------------------------------
1 | use crate::{
2 | block::entity::BlockEntity,
3 | item::{get_item, Inventory, ItemStack},
4 | };
5 | use qdat::{world::location::BlockPosition, UlnStr};
6 |
7 | use quartz_nbt::NbtCompound;
8 |
9 | // While this is somewhat accurate to how the Furnace BE will be implemented the tick method is no where near finished and some key fields are missing
10 | // Currently this is mostly for testing BEs
11 |
12 | pub struct FurnaceBlockEntity {
13 | #[allow(dead_code)]
14 | pos: BlockPosition,
15 | custom_name: String,
16 | lock: bool,
17 | items: Inventory,
18 | burn_time: i32,
19 | cook_time: i32,
20 | cook_time_total: i32,
21 | active: bool,
22 | }
23 |
24 | impl FurnaceBlockEntity {
25 | pub fn new(pos: BlockPosition, name: Option) -> Self {
26 | FurnaceBlockEntity {
27 | pos,
28 | custom_name: match name {
29 | Some(name) => name,
30 | _ => "Furnace".to_owned(),
31 | },
32 | lock: false,
33 | items: Inventory::new(3),
34 | burn_time: 0,
35 | cook_time: 0,
36 | cook_time_total: 0,
37 | active: false,
38 | }
39 | }
40 | }
41 |
42 | impl BlockEntity for FurnaceBlockEntity {
43 | fn from_nbt(&mut self, nbt: &NbtCompound) {
44 | self.burn_time = nbt.get("BurnTime").unwrap_or(0);
45 | self.cook_time = nbt.get("CookTime").unwrap_or(0);
46 | self.cook_time_total = nbt.get("CookTimeTotal").unwrap_or(0);
47 | self.items.from_tag(nbt);
48 |
49 | if nbt.contains_key("CustomName") {
50 | self.custom_name = nbt.get("CustomName").unwrap_or("Furnace").to_owned();
51 | }
52 |
53 | if nbt.contains_key("Lock") {
54 | self.lock = nbt.get("Lock").unwrap_or(false);
55 | }
56 | }
57 |
58 | fn write_nbt(&self, nbt: &mut NbtCompound) {
59 | nbt.insert("BurnTime".to_owned(), self.burn_time);
60 | nbt.insert("CookTime".to_owned(), self.cook_time);
61 | nbt.insert("CookTimeTotal".to_owned(), self.cook_time_total);
62 | self.items.write_tag(nbt);
63 | nbt.insert("CustomName".to_owned(), self.custom_name.clone());
64 | nbt.insert("Lock".to_owned(), self.lock);
65 | }
66 |
67 | fn tick(&mut self) {
68 | // Currently just testing of inventories
69 | if self.active {
70 | self.cook_time += 1;
71 | if self.cook_time > self.cook_time_total {
72 | self.items.insert(
73 | 2,
74 | ItemStack::new(get_item(UlnStr::minecraft("stone")).unwrap()).into(),
75 | );
76 | }
77 | } else if self.items.get(2).is_empty() {
78 | self.items.insert(
79 | 2,
80 | ItemStack::new(get_item(UlnStr::minecraft("stone")).unwrap()).into(),
81 | );
82 | } else {
83 | self.items.increment(2);
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/quartz/src/block/entity.rs:
--------------------------------------------------------------------------------
1 | use crate::block::entities::FurnaceBlockEntity;
2 | use enum_dispatch::enum_dispatch;
3 | use quartz_nbt::NbtCompound;
4 |
5 | // All block entities must impl this
6 | /// Trait for block entities
7 | #[enum_dispatch]
8 | pub trait BlockEntity {
9 | /// Writes the entity info to a compound tag
10 | fn write_nbt(&self, nbt: &mut NbtCompound);
11 | /// Reads info from a compound tag
12 | #[allow(clippy::wrong_self_convention)]
13 | fn from_nbt(&mut self, nbt: &NbtCompound);
14 | /// Ticks the block entity
15 | fn tick(&mut self);
16 | }
17 |
18 | #[enum_dispatch(BlockEntity)]
19 | pub enum StaticBlockEntity {
20 | FurnaceBlockEntity,
21 | }
22 |
--------------------------------------------------------------------------------
/quartz/src/block/init.rs:
--------------------------------------------------------------------------------
1 | use qdat::{
2 | block::{states::BLOCK_LOOKUP_BY_NAME, Block, BlockBehaviorSMT, StateID},
3 | UnlocalizedName,
4 | };
5 | use serde::{Deserialize, Serialize};
6 | use serde_json;
7 | use std::collections::{BTreeMap, HashMap};
8 | use tinyvec::ArrayVec;
9 |
10 | use crate::{
11 | assets,
12 | block::{BlockStateImpl, StateBuilder, StaticBlockState, StaticStateBuilder},
13 | };
14 |
15 | pub(crate) fn load_raw_block_data() -> HashMap {
16 | serde_json::from_str::>(assets::BLOCK_INFO)
17 | .expect("assets/blocks.json is corrupted.")
18 | }
19 |
20 | pub(crate) fn attach_behavior(raw: &mut HashMap) {
21 | macro_rules! attach {
22 | ($behavior:ty, $( $block_name:literal ),+) => {
23 | $(
24 | raw.get_mut(concat!("minecraft:", $block_name))
25 | .expect("Invalid block name during behavior attachment")
26 | .behavior = Some(BlockBehaviorSMT::new());
27 | )+
28 | };
29 | }
30 |
31 | attach!(DefaultBehavior, "air", "stone");
32 | }
33 |
34 | pub(crate) fn make_block_list(raw: &HashMap) -> Vec {
35 | let mut block_list = Vec::new();
36 |
37 | let mut raw = raw.iter().collect::>();
38 | raw.sort_by_key(|(_, info)| info.interm_id);
39 |
40 | for (name, block_info) in raw {
41 | let uln = UnlocalizedName::from_str(name)
42 | .expect("Invalid block name encountered during registration.");
43 |
44 | // This should never happen if the data integrity is not compromised
45 | if block_info.states.is_empty() {
46 | panic!("Invalid block encountered: {name}, no states found.");
47 | }
48 |
49 | block_list.push(Block {
50 | name: uln,
51 | properties: block_info
52 | .properties
53 | .clone()
54 | .into_iter()
55 | .collect::>(),
56 | base_state: block_info.states[0].id,
57 | default_state: block_info.default,
58 | behavior: block_info
59 | .behavior
60 | .clone()
61 | .unwrap_or_else(BlockBehaviorSMT::new),
62 | });
63 | }
64 |
65 | block_list
66 | }
67 |
68 | // TODO: put behind feature flag if necessary
69 | pub(crate) fn make_static_global_palette(
70 | raw: &HashMap,
71 | blocks: &'static [Block],
72 | ) -> Vec {
73 | let mut global_palette = Vec::new();
74 |
75 | for block in raw.values() {
76 | let handle: &'static Block = &blocks[block.interm_id];
77 |
78 | for state_info in block.states.iter() {
79 | let default_state = StaticBlockState {
80 | handle,
81 | data: BLOCK_LOOKUP_BY_NAME
82 | .get(handle.name.identifier())
83 | .unwrap()
84 | .default_state_data,
85 | };
86 | let mut state_builder = StaticStateBuilder::new(default_state.clone());
87 |
88 | for (key, value) in state_info.properties.iter() {
89 | state_builder
90 | .add_property(key.as_str(), value.as_str())
91 | .unwrap();
92 | }
93 |
94 | let state = state_builder.build();
95 |
96 | // Make sure the computed ID matches the ID in the generated data
97 | assert_eq!(
98 | state_info.id,
99 | state.id(),
100 | "Computed ID for {} does not match stored ID.\nState is {:?}",
101 | state.handle.name,
102 | state.data
103 | );
104 |
105 | global_palette.push(state);
106 | }
107 | }
108 |
109 | global_palette.sort_by_key(|state| state.id());
110 | global_palette
111 | }
112 |
113 | #[derive(Serialize, Deserialize)]
114 | pub(crate) struct RawBlockInfo {
115 | // Use a BTreeMap for ordering so that we can compute state IDs
116 | #[serde(default = "BTreeMap::new")]
117 | properties: BTreeMap>,
118 | default: StateID,
119 | interm_id: usize,
120 | states: Vec>,
121 | #[serde(skip_serializing, skip_deserializing, default = "Option::default")]
122 | behavior: Option,
123 | }
124 |
125 | #[derive(Serialize, Deserialize)]
126 | struct RawStateInfo {
127 | id: T,
128 | #[serde(default = "BTreeMap::new")]
129 | properties: BTreeMap,
130 | }
131 |
--------------------------------------------------------------------------------
/quartz/src/block/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod entity;
2 | mod init;
3 | pub mod state;
4 | pub(crate) use init::*;
5 | pub use state::*;
6 |
7 | #[allow(missing_docs)]
8 | pub mod entities {
9 | pub mod furnace_entity;
10 | pub use furnace_entity::FurnaceBlockEntity;
11 | }
12 |
--------------------------------------------------------------------------------
/quartz/src/command/context.rs:
--------------------------------------------------------------------------------
1 | use crate::{display_to_console, network::AsyncWriteHandle, CommandExecutor, QuartzServer};
2 | use quartz_chat::component::Component;
3 | use quartz_net::ClientBoundPacket;
4 | use uuid::Uuid;
5 |
6 | /// The context in which a command is executed. This has no use outside the lifecycle of a command.
7 | pub struct CommandContext<'ctx> {
8 | /// A shared reference to the server.
9 | pub server: &'ctx mut QuartzServer,
10 | /// A shared reference to the executor that created this context.
11 | pub executor: &'ctx CommandExecutor,
12 | /// The sender of the command.
13 | pub sender: CommandSender,
14 | }
15 |
16 | // Shortcut functions for getting argument values
17 | impl<'ctx> CommandContext<'ctx> {
18 | /// Creates a new command context with the given parameters.
19 | pub fn new(
20 | server: &'ctx mut QuartzServer,
21 | executor: &'ctx CommandExecutor,
22 | sender: CommandSender,
23 | ) -> Self {
24 | CommandContext {
25 | server,
26 | executor,
27 | sender,
28 | }
29 | }
30 | }
31 |
32 | /// A command sender, can be command block, player, or the console.
33 | pub enum CommandSender {
34 | /// The console sender type.
35 | Console,
36 | Client(AsyncWriteHandle),
37 | }
38 |
39 | impl CommandSender {
40 | /// Sends a message to the sender.
41 | pub fn send_message(&self, message: Component) {
42 | match self {
43 | CommandSender::Console => display_to_console(&message),
44 | CommandSender::Client(handle) => handle.send_packet(ClientBoundPacket::ChatMessage {
45 | sender: Uuid::from_u128(0),
46 | position: 1,
47 | json_data: Box::new(message),
48 | }),
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/quartz/src/command/executor.rs:
--------------------------------------------------------------------------------
1 | use quartz_commands::CommandModule;
2 |
3 | use crate::command::CommandContext;
4 |
5 | pub struct StaticCommandExecutor;
6 |
7 | impl StaticCommandExecutor {
8 | pub fn new() -> Self {
9 | StaticCommandExecutor
10 | }
11 | }
12 |
13 | impl Default for StaticCommandExecutor {
14 | fn default() -> Self {
15 | Self::new()
16 | }
17 | }
18 |
19 | impl<'ctx> CommandModule> for StaticCommandExecutor {
20 | fn dispatch(
21 | &self,
22 | command: &str,
23 | context: CommandContext<'ctx>,
24 | ) -> Result<(), quartz_commands::Error> {
25 | (cmds::NativeCommandSet).dispatch(command, context)
26 | }
27 |
28 | fn get_suggestions(&self, command: &str, context: &CommandContext<'ctx>) -> Vec