mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 14:26:04 +00:00
commit
f414aa4d37
35 changed files with 6089 additions and 81 deletions
51
Cargo.lock
generated
51
Cargo.lock
generated
|
@ -45,9 +45,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.53"
|
||||
version = "0.1.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600"
|
||||
checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -78,6 +78,13 @@ dependencies = [
|
|||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "azalea-block"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"block-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "azalea-brigadier"
|
||||
version = "0.1.0"
|
||||
|
@ -176,6 +183,7 @@ dependencies = [
|
|||
name = "azalea-world"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"azalea-block",
|
||||
"azalea-core",
|
||||
"azalea-nbt",
|
||||
"azalea-protocol",
|
||||
|
@ -196,6 +204,15 @@ dependencies = [
|
|||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bot"
|
||||
version = "0.1.0"
|
||||
|
@ -220,9 +237,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.9.1"
|
||||
version = "3.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
|
||||
checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
|
@ -458,13 +475,11 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.23"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39522e96686d38f4bc984b9198e3a0613264abaebaff2c5c918bfa6b6da09af"
|
||||
checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crc32fast",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
|
@ -729,9 +744,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.5.1"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082"
|
||||
checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
|
||||
dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
@ -1104,9 +1119,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
|||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.9"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd"
|
||||
checksum = "a41d061efea015927ac527063765e73601444cdc344ba855bc7bd44578b25e1c"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
|
@ -1207,9 +1222,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.95"
|
||||
version = "1.0.96"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942"
|
||||
checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1272,9 +1287,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
|||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.18.2"
|
||||
version = "1.19.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4903bf0427cf68dddd5aa6a93220756f8be0c34fcfa9f5e6191e103e15a31395"
|
||||
checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"libc",
|
||||
|
@ -1290,9 +1305,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "1.7.0"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
|
||||
checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
|
@ -11,6 +11,7 @@ members = [
|
|||
"azalea-crypto",
|
||||
"azalea-world",
|
||||
"azalea-language",
|
||||
"azalea-block",
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Azalea
|
||||
|
||||
A Rust crate for creating Minecraft bots.
|
||||
A collection of Rust crates for creating Minecraft bots and other utilities.
|
||||
|
||||
<p align="center">
|
||||
<img src="https://cdn.matdoes.dev/images/flowering_azalea.webp" alt="Azalea" height="200">
|
||||
|
|
11
azalea-block/Cargo.toml
Normal file
11
azalea-block/Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
edition = "2021"
|
||||
name = "azalea-block"
|
||||
version = "0.1.0"
|
||||
|
||||
[lib]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
block-macros = {path = "./block-macros"}
|
8
azalea-block/README.md
Normal file
8
azalea-block/README.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Azalea Block
|
||||
|
||||
Representation of Minecraft block states.
|
||||
|
||||
There's two main things here, the `BlockState` enum and the `Block` trait.
|
||||
`BlockState` is a simple enum with every possible block state as variant, and `Block` is a heavier trait which lets you access information about a block more easily.
|
||||
|
||||
Every block is a struct that implements `Block`. You can freely convert between `BlockState` and `Block` with .into().
|
14
azalea-block/block-macros/Cargo.toml
Normal file
14
azalea-block/block-macros/Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
edition = "2021"
|
||||
name = "block-macros"
|
||||
version = "0.1.0"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0.39"
|
||||
quote = "1.0.18"
|
||||
syn = "1.0.95"
|
496
azalea-block/block-macros/src/lib.rs
Normal file
496
azalea-block/block-macros/src/lib.rs
Normal file
|
@ -0,0 +1,496 @@
|
|||
mod utils;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use std::collections::HashMap;
|
||||
use syn::{
|
||||
self, braced,
|
||||
parse::{Parse, ParseStream, Result},
|
||||
parse_macro_input,
|
||||
punctuated::Punctuated,
|
||||
Expr, Ident, LitStr, Token,
|
||||
};
|
||||
use utils::{combinations_of, to_pascal_case};
|
||||
|
||||
struct PropertyDefinition {
|
||||
name: LitStr,
|
||||
struct_name: Ident,
|
||||
variants: Punctuated<Ident, Token![,]>,
|
||||
}
|
||||
struct PropertyDefinitions {
|
||||
properties: Vec<PropertyDefinition>,
|
||||
}
|
||||
|
||||
struct PropertyAndDefault {
|
||||
struct_name: Ident,
|
||||
default: Ident,
|
||||
}
|
||||
struct PropertyWithNameAndDefault {
|
||||
name: String,
|
||||
struct_name: Ident,
|
||||
default: Ident,
|
||||
}
|
||||
struct BlockDefinition {
|
||||
name: Ident,
|
||||
behavior: Expr,
|
||||
properties_and_defaults: Vec<PropertyAndDefault>,
|
||||
}
|
||||
impl PropertyAndDefault {
|
||||
fn into_property_with_name_and_default(&self, name: String) -> PropertyWithNameAndDefault {
|
||||
PropertyWithNameAndDefault {
|
||||
name,
|
||||
struct_name: self.struct_name.clone(),
|
||||
default: self.default.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
struct BlockDefinitions {
|
||||
blocks: Vec<BlockDefinition>,
|
||||
}
|
||||
struct MakeBlockStates {
|
||||
property_definitions: PropertyDefinitions,
|
||||
block_definitions: BlockDefinitions,
|
||||
}
|
||||
|
||||
impl Parse for PropertyDefinition {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
// "face" => Face {
|
||||
// Floor,
|
||||
// Wall,
|
||||
// Ceiling
|
||||
// },
|
||||
|
||||
// if you're wondering, the reason it's in quotes is because `type` is
|
||||
// a keyword in rust so if we don't put it in quotes it results in a
|
||||
// syntax error
|
||||
let name = input.parse()?;
|
||||
input.parse::<Token![=>]>()?;
|
||||
let struct_name = input.parse()?;
|
||||
|
||||
let content;
|
||||
braced!(content in input);
|
||||
let variants = content.parse_terminated(Ident::parse)?;
|
||||
|
||||
input.parse::<Token![,]>()?;
|
||||
Ok(PropertyDefinition {
|
||||
name,
|
||||
struct_name,
|
||||
variants,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for PropertyDefinitions {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let mut property_definitions = Vec::new();
|
||||
while !input.is_empty() {
|
||||
property_definitions.push(input.parse()?);
|
||||
}
|
||||
|
||||
Ok(PropertyDefinitions {
|
||||
properties: property_definitions,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for BlockDefinition {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
// acacia_button => BlockBehavior::default(), {
|
||||
// Facing=North,
|
||||
// Powered=False,
|
||||
// Face=Wall,
|
||||
// },
|
||||
let name = input.parse()?;
|
||||
input.parse::<Token![=>]>()?;
|
||||
let behavior = input.parse()?;
|
||||
|
||||
input.parse::<Token![,]>()?;
|
||||
let content;
|
||||
braced!(content in input);
|
||||
|
||||
let mut properties_and_defaults = Vec::new();
|
||||
|
||||
loop {
|
||||
let property = match content.parse() {
|
||||
Ok(property) => property,
|
||||
Err(_) => break,
|
||||
};
|
||||
content.parse::<Token![=]>()?;
|
||||
let property_default = content.parse()?;
|
||||
properties_and_defaults.push(PropertyAndDefault {
|
||||
struct_name: property,
|
||||
default: property_default,
|
||||
});
|
||||
if content.parse::<Token![,]>().is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
input.parse::<Token![,]>()?;
|
||||
Ok(BlockDefinition {
|
||||
name,
|
||||
behavior,
|
||||
properties_and_defaults,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for BlockDefinitions {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let mut blocks = Vec::new();
|
||||
while !input.is_empty() {
|
||||
blocks.push(input.parse()?);
|
||||
}
|
||||
|
||||
Ok(BlockDefinitions { blocks })
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for MakeBlockStates {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
// Properties => { ... } Blocks => { ... }
|
||||
let properties_ident = input.parse::<Ident>()?;
|
||||
assert_eq!(properties_ident.to_string(), "Properties");
|
||||
input.parse::<Token![=>]>()?;
|
||||
let content;
|
||||
braced!(content in input);
|
||||
let properties = content.parse()?;
|
||||
|
||||
input.parse::<Token![,]>()?;
|
||||
|
||||
let blocks_ident = input.parse::<Ident>()?;
|
||||
assert_eq!(blocks_ident.to_string(), "Blocks");
|
||||
input.parse::<Token![=>]>()?;
|
||||
let content;
|
||||
braced!(content in input);
|
||||
let blocks = content.parse()?;
|
||||
|
||||
Ok(MakeBlockStates {
|
||||
property_definitions: properties,
|
||||
block_definitions: blocks,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn make_block_states(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as MakeBlockStates);
|
||||
|
||||
let mut property_enums = quote! {};
|
||||
let mut properties_map = HashMap::new();
|
||||
let mut property_struct_names_to_names = HashMap::new();
|
||||
|
||||
let mut state_id: usize = 0;
|
||||
|
||||
for property in &input.property_definitions.properties {
|
||||
let mut property_enum_variants = quote! {};
|
||||
let mut property_from_number_variants = quote! {};
|
||||
let mut property_enum_variant_names = Vec::new();
|
||||
|
||||
let property_struct_name = &property.struct_name;
|
||||
|
||||
property_struct_names_to_names.insert(
|
||||
property_struct_name.to_string(),
|
||||
property.name.clone().value(),
|
||||
);
|
||||
|
||||
for i in 0..property.variants.len() {
|
||||
let variant = &property.variants[i];
|
||||
|
||||
let i_lit = syn::Lit::Int(syn::LitInt::new(
|
||||
&i.to_string(),
|
||||
proc_macro2::Span::call_site(),
|
||||
));
|
||||
|
||||
property_enum_variants.extend(quote! {
|
||||
#variant = #i_lit,
|
||||
});
|
||||
|
||||
// i_lit is used here instead of i because otherwise it says 0size
|
||||
// in the expansion and that looks uglier
|
||||
property_from_number_variants.extend(quote! {
|
||||
#i_lit => #property_struct_name::#variant,
|
||||
});
|
||||
|
||||
property_enum_variant_names.push(variant.to_string());
|
||||
}
|
||||
|
||||
property_enums.extend(quote! {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum #property_struct_name {
|
||||
#property_enum_variants
|
||||
}
|
||||
|
||||
impl From<usize> for #property_struct_name {
|
||||
fn from(value: usize) -> Self {
|
||||
match value {
|
||||
#property_from_number_variants
|
||||
_ => panic!("Invalid property value: {}", value),
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
properties_map.insert(
|
||||
property_struct_name.to_string(),
|
||||
property_enum_variant_names,
|
||||
);
|
||||
}
|
||||
|
||||
let mut block_state_enum_variants = quote! {};
|
||||
let mut block_structs = quote! {};
|
||||
let mut from_state_to_block_match = quote! {};
|
||||
for block in &input.block_definitions.blocks {
|
||||
let block_property_names = &block
|
||||
.properties_and_defaults
|
||||
.iter()
|
||||
.map(|p| p.struct_name.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
let mut block_properties_vec = Vec::new();
|
||||
for property_name in block_property_names {
|
||||
let property_variants = properties_map
|
||||
.get(property_name)
|
||||
.expect(format!("Property '{}' not found", property_name).as_str())
|
||||
.clone();
|
||||
block_properties_vec.push(property_variants);
|
||||
}
|
||||
|
||||
let mut properties_with_name: Vec<PropertyWithNameAndDefault> =
|
||||
Vec::with_capacity(block.properties_and_defaults.len());
|
||||
for property in &block.properties_and_defaults {
|
||||
let index: Option<usize> = if block
|
||||
.properties_and_defaults
|
||||
.iter()
|
||||
.filter(|p| p.struct_name == property.struct_name)
|
||||
.count()
|
||||
> 1
|
||||
{
|
||||
Some(
|
||||
properties_with_name
|
||||
.iter()
|
||||
.filter(|p| p.struct_name == property.struct_name)
|
||||
.count(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut property_name = property_struct_names_to_names
|
||||
.get(&property.struct_name.to_string())
|
||||
.expect(format!("Property '{}' is bad", property.struct_name).as_str())
|
||||
.clone();
|
||||
if let Some(index) = index {
|
||||
property_name.push_str(&format!("_{}", &index.to_string()));
|
||||
}
|
||||
properties_with_name
|
||||
.push(property.into_property_with_name_and_default(property_name.clone()));
|
||||
}
|
||||
|
||||
// pub face: properties::Face,
|
||||
// pub facing: properties::Facing,
|
||||
// pub powered: properties::Powered,
|
||||
// or
|
||||
// pub has_bottle_0: HasBottle,
|
||||
// pub has_bottle_1: HasBottle,
|
||||
// pub has_bottle_2: HasBottle,
|
||||
let mut block_struct_fields = quote! {};
|
||||
for PropertyWithNameAndDefault {
|
||||
struct_name, name, ..
|
||||
} in &properties_with_name
|
||||
{
|
||||
// let property_name_snake =
|
||||
// Ident::new(&property.to_string(), proc_macro2::Span::call_site());
|
||||
let name_ident = Ident::new(&name, proc_macro2::Span::call_site());
|
||||
block_struct_fields.extend(quote! {
|
||||
pub #name_ident: #struct_name,
|
||||
})
|
||||
}
|
||||
|
||||
let block_name_pascal_case = Ident::new(
|
||||
&to_pascal_case(&block.name.to_string()),
|
||||
proc_macro2::Span::call_site(),
|
||||
);
|
||||
let block_struct_name = Ident::new(
|
||||
&format!("{}Block", block_name_pascal_case),
|
||||
proc_macro2::Span::call_site(),
|
||||
);
|
||||
|
||||
let mut from_block_to_state_match_inner = quote! {};
|
||||
|
||||
let first_state_id = state_id;
|
||||
|
||||
// if there's no properties, then the block is just a single state
|
||||
if block_properties_vec.len() == 0 {
|
||||
block_state_enum_variants.extend(quote! {
|
||||
#block_name_pascal_case,
|
||||
});
|
||||
state_id += 1;
|
||||
}
|
||||
for combination in combinations_of(&block_properties_vec) {
|
||||
state_id += 1;
|
||||
let variant_name = Ident::new(
|
||||
&format!(
|
||||
"{}_{}",
|
||||
block_name_pascal_case,
|
||||
combination
|
||||
.iter()
|
||||
.map(|v| v.to_string())
|
||||
.collect::<Vec<String>>()
|
||||
.join("")
|
||||
),
|
||||
proc_macro2::Span::call_site(),
|
||||
);
|
||||
block_state_enum_variants.extend(quote! {
|
||||
#variant_name,
|
||||
});
|
||||
|
||||
// face: properties::Face::Floor,
|
||||
// facing: properties::Facing::North,
|
||||
// powered: properties::Powered::True,
|
||||
let mut from_block_to_state_combination_match_inner = quote! {};
|
||||
for i in 0..properties_with_name.len() {
|
||||
let property = &properties_with_name[i];
|
||||
let property_name = &property.name;
|
||||
let property_name_ident = Ident::new(property_name, proc_macro2::Span::call_site());
|
||||
let property_struct_name_ident = &property.struct_name;
|
||||
let variant =
|
||||
Ident::new(&combination[i].to_string(), proc_macro2::Span::call_site());
|
||||
|
||||
from_block_to_state_combination_match_inner.extend(quote! {
|
||||
#property_name_ident: #property_struct_name_ident::#variant,
|
||||
});
|
||||
}
|
||||
|
||||
from_block_to_state_match_inner.extend(quote! {
|
||||
#block_struct_name {
|
||||
#from_block_to_state_combination_match_inner
|
||||
} => BlockState::#variant_name,
|
||||
});
|
||||
}
|
||||
|
||||
// 7035..=7058 => {
|
||||
// let b = b - 7035;
|
||||
// &AcaciaButtonBlock {
|
||||
// powered: Powered::from((b / 1) % 2),
|
||||
// facing: Facing::from((b / 2) % 4),
|
||||
// face: Face::from((b / 8) % 3),
|
||||
// }
|
||||
// }
|
||||
let mut from_state_to_block_inner = quote! {};
|
||||
let mut division = 1usize;
|
||||
for i in (0..properties_with_name.len()).rev() {
|
||||
let PropertyWithNameAndDefault {
|
||||
struct_name: property_struct_name_ident,
|
||||
name: property_name,
|
||||
..
|
||||
} = &properties_with_name[i];
|
||||
|
||||
let property_variants = &block_properties_vec[i];
|
||||
let property_variants_count = property_variants.len();
|
||||
let property_name_ident = Ident::new(property_name, proc_macro2::Span::call_site());
|
||||
from_state_to_block_inner.extend(quote! {
|
||||
#property_name_ident: #property_struct_name_ident::from((b / #division) % #property_variants_count),
|
||||
});
|
||||
|
||||
division *= property_variants_count;
|
||||
}
|
||||
|
||||
let last_state_id = state_id - 1;
|
||||
from_state_to_block_match.extend(quote! {
|
||||
#first_state_id..=#last_state_id => {
|
||||
let b = b - #first_state_id;
|
||||
Box::new(#block_struct_name {
|
||||
#from_state_to_block_inner
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
let mut block_default_fields = quote! {};
|
||||
for PropertyWithNameAndDefault {
|
||||
struct_name: struct_name_ident,
|
||||
name,
|
||||
default: property_default,
|
||||
} in properties_with_name
|
||||
{
|
||||
let name_ident = Ident::new(&name, proc_macro2::Span::call_site());
|
||||
block_default_fields.extend(quote! {
|
||||
#name_ident: #struct_name_ident::#property_default,
|
||||
})
|
||||
}
|
||||
|
||||
let block_behavior = &block.behavior;
|
||||
let block_id = block.name.to_string();
|
||||
|
||||
let from_block_to_state_match = if block.properties_and_defaults.len() > 0 {
|
||||
quote! {
|
||||
match b {
|
||||
#from_block_to_state_match_inner
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quote! { BlockState::#block_name_pascal_case }
|
||||
};
|
||||
|
||||
let block_struct = quote! {
|
||||
#[derive(Debug)]
|
||||
pub struct #block_struct_name {
|
||||
#block_struct_fields
|
||||
}
|
||||
|
||||
impl Block for #block_struct_name {
|
||||
fn behavior(&self) -> BlockBehavior {
|
||||
#block_behavior
|
||||
}
|
||||
fn id(&self) -> &'static str {
|
||||
#block_id
|
||||
}
|
||||
}
|
||||
|
||||
impl From<#block_struct_name> for BlockState {
|
||||
fn from(b: #block_struct_name) -> Self {
|
||||
#from_block_to_state_match
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for #block_struct_name {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
#block_default_fields
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
block_structs.extend(block_struct);
|
||||
}
|
||||
|
||||
let last_state_id = (state_id - 1) as u32;
|
||||
quote! {
|
||||
#property_enums
|
||||
|
||||
#[repr(u32)]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
||||
pub enum BlockState {
|
||||
#block_state_enum_variants
|
||||
}
|
||||
|
||||
#block_structs
|
||||
|
||||
impl From<BlockState> for Box<dyn Block> {
|
||||
fn from(b: BlockState) -> Self {
|
||||
let b = b as usize;
|
||||
match b {
|
||||
#from_state_to_block_match
|
||||
_ => panic!("Invalid block state: {}", b),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BlockState {
|
||||
/// Returns the highest possible state
|
||||
#[inline]
|
||||
pub fn max_state() -> u32 {
|
||||
#last_state_id
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.into()
|
||||
}
|
42
azalea-block/block-macros/src/utils.rs
Normal file
42
azalea-block/block-macros/src/utils.rs
Normal file
|
@ -0,0 +1,42 @@
|
|||
pub fn combinations_of<T: Clone>(items: &[Vec<T>]) -> Vec<Vec<T>> {
|
||||
let mut combinations = Vec::new();
|
||||
if items.len() == 0 {
|
||||
return combinations;
|
||||
};
|
||||
if items.len() == 1 {
|
||||
for item in &items[0] {
|
||||
combinations.push(vec![item.clone()]);
|
||||
}
|
||||
return combinations;
|
||||
};
|
||||
|
||||
for i in 0..items[0].len() {
|
||||
let item = &items[0][i];
|
||||
for other_combinations in combinations_of(&items[1..]) {
|
||||
let mut combination = Vec::new();
|
||||
combination.push(item.clone());
|
||||
combination.extend(other_combinations);
|
||||
combinations.push(combination);
|
||||
}
|
||||
}
|
||||
|
||||
combinations
|
||||
}
|
||||
|
||||
pub fn to_pascal_case(s: &str) -> String {
|
||||
let mut result = String::new();
|
||||
let mut prev_was_underscore = true; // set to true by default so the first character is capitalized
|
||||
for c in s.chars() {
|
||||
if c == '_' {
|
||||
prev_was_underscore = true;
|
||||
} else {
|
||||
if prev_was_underscore {
|
||||
result.push(c.to_ascii_uppercase());
|
||||
prev_was_underscore = false;
|
||||
} else {
|
||||
result.push(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
12
azalea-block/src/behavior.rs
Normal file
12
azalea-block/src/behavior.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
#[derive(Default)]
|
||||
pub struct BlockBehavior {
|
||||
pub has_collision: bool,
|
||||
}
|
||||
|
||||
impl BlockBehavior {
|
||||
#[inline]
|
||||
pub fn no_collision(mut self) -> Self {
|
||||
self.has_collision = false;
|
||||
self
|
||||
}
|
||||
}
|
4954
azalea-block/src/blocks.rs
Normal file
4954
azalea-block/src/blocks.rs
Normal file
File diff suppressed because it is too large
Load diff
47
azalea-block/src/lib.rs
Normal file
47
azalea-block/src/lib.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
mod behavior;
|
||||
mod blocks;
|
||||
|
||||
pub use behavior::BlockBehavior;
|
||||
pub use blocks::*;
|
||||
|
||||
use std::mem;
|
||||
|
||||
impl BlockState {
|
||||
/// Transmutes a u32 to a block state. UB if the value is not a valid block
|
||||
/// state.
|
||||
#[inline]
|
||||
pub unsafe fn from_u32_unsafe(state_id: u32) -> Self {
|
||||
mem::transmute::<u32, BlockState>(state_id)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_valid_state(state_id: u32) -> bool {
|
||||
state_id <= Self::max_state()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u32> for BlockState {
|
||||
type Error = ();
|
||||
|
||||
/// Safely converts a state id to a block state.
|
||||
fn try_from(state_id: u32) -> Result<Self, Self::Error> {
|
||||
if Self::is_valid_state(state_id) {
|
||||
Ok(unsafe { Self::from_u32_unsafe(state_id) })
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_from_u32() {
|
||||
assert_eq!(BlockState::try_from(0).unwrap(), BlockState::Air);
|
||||
|
||||
assert!(BlockState::try_from(BlockState::max_state()).is_ok());
|
||||
assert!(BlockState::try_from(BlockState::max_state() + 1).is_err());
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
use crate::Player;
|
||||
use azalea_core::{resource_location::ResourceLocation, ChunkPos};
|
||||
use azalea_core::{resource_location::ResourceLocation, ChunkPos, EntityPos};
|
||||
use azalea_protocol::{
|
||||
connect::{GameConnection, HandshakeConnection},
|
||||
packets::{
|
||||
|
@ -351,6 +351,11 @@ impl Client {
|
|||
}
|
||||
GamePacket::ClientboundAddEntityPacket(p) => {
|
||||
println!("Got add entity packet {:?}", p);
|
||||
let pos = EntityPos {
|
||||
x: p.x,
|
||||
y: p.y,
|
||||
z: p.z,
|
||||
};
|
||||
}
|
||||
GamePacket::ClientboundSetEntityDataPacket(p) => {
|
||||
// println!("Got set entity data packet {:?}", p);
|
||||
|
@ -447,6 +452,9 @@ impl Client {
|
|||
GamePacket::ClientboundServerDataPacket(p) => {
|
||||
println!("Got server data packet {:?}", p);
|
||||
}
|
||||
GamePacket::ClientboundSetEquipmentPacket(p) => {
|
||||
println!("Got set equipment packet {:?}", p);
|
||||
}
|
||||
_ => panic!("Unexpected packet {:?}", packet),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
#[derive(Default)]
|
||||
use azalea_core::EntityPos;
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Entity {
|
||||
/// The incremental numerical id of the entity.
|
||||
pub id: u32,
|
||||
pub pos: EntityPos,
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::Entity;
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Player {
|
||||
/// The entity attached to the player. There's some useful fields here.
|
||||
pub entity: Entity,
|
||||
|
|
|
@ -11,9 +11,7 @@ mod slot;
|
|||
pub use slot::{Slot, SlotData};
|
||||
|
||||
mod position;
|
||||
pub use position::{
|
||||
BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos, ChunkSectionPos, GlobalPos,
|
||||
};
|
||||
pub use position::*;
|
||||
|
||||
mod direction;
|
||||
pub use direction::Direction;
|
||||
|
|
|
@ -147,6 +147,23 @@ pub struct GlobalPos {
|
|||
pub dimension: ResourceLocation,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct EntityPos {
|
||||
pub x: f64,
|
||||
pub y: f64,
|
||||
pub z: f64,
|
||||
}
|
||||
|
||||
impl From<&EntityPos> for BlockPos {
|
||||
fn from(pos: &EntityPos) -> Self {
|
||||
BlockPos {
|
||||
x: pos.x as i32,
|
||||
y: pos.y as i32,
|
||||
z: pos.z as i32,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -3,6 +3,7 @@ use uuid::Uuid;
|
|||
|
||||
#[derive(Clone, Debug, McBuf, GamePacket)]
|
||||
pub struct ClientboundAddEntityPacket {
|
||||
/// The id of the entity.
|
||||
#[var]
|
||||
pub id: u32,
|
||||
pub uuid: Uuid,
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
use azalea_core::Slot;
|
||||
use packet_macros::{GamePacket, McBuf};
|
||||
|
||||
use crate::mc_buf::{McBufReadable, McBufWritable};
|
||||
|
||||
#[derive(Clone, Debug, McBuf, GamePacket)]
|
||||
pub struct ClientboundSetEquipmentPacket {
|
||||
#[var]
|
||||
pub entity: i32,
|
||||
pub slots: EquipmentSlots,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct EquipmentSlots {
|
||||
pub slots: Vec<(EquipmentSlot, Slot)>,
|
||||
}
|
||||
|
||||
impl McBufReadable for EquipmentSlots {
|
||||
fn read_into(buf: &mut impl std::io::Read) -> Result<Self, String> {
|
||||
let mut slots = vec![];
|
||||
|
||||
loop {
|
||||
let equipment_byte = u8::read_into(buf)?;
|
||||
let equipment_slot = EquipmentSlot::from_byte(equipment_byte & 127)
|
||||
.ok_or_else(|| format!("Invalid equipment slot byte {}", equipment_byte))?;
|
||||
let item = Slot::read_into(buf)?;
|
||||
slots.push((equipment_slot, item));
|
||||
if equipment_byte & 128 == 0 {
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
Ok(EquipmentSlots { slots })
|
||||
}
|
||||
}
|
||||
impl McBufWritable for EquipmentSlots {
|
||||
fn write_into(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
|
||||
for i in 0..self.slots.len() {
|
||||
let (equipment_slot, item) = &self.slots[i];
|
||||
let mut equipment_byte = *equipment_slot as u8;
|
||||
if i != self.slots.len() - 1 {
|
||||
equipment_byte |= 128;
|
||||
}
|
||||
equipment_byte.write_into(buf)?;
|
||||
item.write_into(buf)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Copy, McBuf)]
|
||||
pub enum EquipmentSlot {
|
||||
MainHand = 0,
|
||||
OffHand = 1,
|
||||
Feet = 2,
|
||||
Legs = 3,
|
||||
Chest = 4,
|
||||
Head = 5,
|
||||
}
|
||||
|
||||
impl EquipmentSlot {
|
||||
pub fn from_byte(byte: u8) -> Option<Self> {
|
||||
match byte {
|
||||
0 => Some(EquipmentSlot::MainHand),
|
||||
1 => Some(EquipmentSlot::OffHand),
|
||||
2 => Some(EquipmentSlot::Feet),
|
||||
3 => Some(EquipmentSlot::Legs),
|
||||
4 => Some(EquipmentSlot::Chest),
|
||||
5 => Some(EquipmentSlot::Head),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ use packet_macros::{GamePacket, McBuf};
|
|||
#[derive(Clone, Debug, McBuf, GamePacket)]
|
||||
pub struct ClientboundSoundPacket {
|
||||
#[var]
|
||||
/// TODO: use the sound registry instead of just being a u32
|
||||
// TODO: use the sound registry instead of just being a u32
|
||||
pub sound: u32,
|
||||
pub source: SoundSource,
|
||||
pub x: i32,
|
||||
|
@ -11,6 +11,8 @@ pub struct ClientboundSoundPacket {
|
|||
pub z: i32,
|
||||
pub volume: f32,
|
||||
pub pitch: f32,
|
||||
/// Seed used to pick sound varient.
|
||||
pub seed: u64,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Copy, McBuf)]
|
||||
|
|
|
@ -37,6 +37,7 @@ pub mod clientbound_set_default_spawn_position_packet;
|
|||
pub mod clientbound_set_display_chat_preview_packet;
|
||||
pub mod clientbound_set_entity_data_packet;
|
||||
pub mod clientbound_set_entity_link_packet;
|
||||
pub mod clientbound_set_equipment_packet;
|
||||
pub mod clientbound_set_experience_packet;
|
||||
pub mod clientbound_set_health_packet;
|
||||
pub mod clientbound_set_time_packet;
|
||||
|
@ -104,6 +105,7 @@ declare_state_packets!(
|
|||
0x4b: clientbound_set_display_chat_preview_packet::ClientboundSetDisplayChatPreviewPacket,
|
||||
0x4d: clientbound_set_entity_data_packet::ClientboundSetEntityDataPacket,
|
||||
0x4f: clientbound_entity_velocity_packet::ClientboundEntityVelocityPacket,
|
||||
0x50: clientbound_set_equipment_packet::ClientboundSetEquipmentPacket,
|
||||
0x51: clientbound_set_experience_packet::ClientboundSetExperiencePacket,
|
||||
0x52: clientbound_set_health_packet::ClientboundSetHealthPacket,
|
||||
0x59: clientbound_set_time_packet::ClientboundSetTimePacket,
|
||||
|
|
|
@ -9,3 +9,4 @@ version = "0.1.0"
|
|||
azalea-core = {path = "../azalea-core"}
|
||||
azalea-nbt = {path = "../azalea-nbt"}
|
||||
azalea-protocol = {path = "../azalea-protocol"}
|
||||
azalea-block = {path = "../azalea-block"}
|
||||
|
|
|
@ -77,8 +77,8 @@ pub struct BitStorage {
|
|||
mask: u64,
|
||||
size: usize,
|
||||
values_per_long: u8,
|
||||
divide_mul: i32,
|
||||
divide_add: i32,
|
||||
divide_mul: u64,
|
||||
divide_add: u64,
|
||||
divide_shift: i32,
|
||||
}
|
||||
|
||||
|
@ -138,16 +138,16 @@ impl BitStorage {
|
|||
mask,
|
||||
size,
|
||||
values_per_long: values_per_long as u8,
|
||||
divide_mul,
|
||||
divide_add,
|
||||
divide_mul: divide_mul as u32 as u64,
|
||||
divide_add: divide_add as u32 as u64,
|
||||
divide_shift,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn cell_index(&self, index: u64) -> usize {
|
||||
// as unsigned wrap
|
||||
let first = self.divide_mul as u32 as u64;
|
||||
let second = self.divide_add as u64;
|
||||
let first = self.divide_mul;
|
||||
let second = self.divide_add;
|
||||
|
||||
(((index * first) + second) >> 32 >> self.divide_shift)
|
||||
.try_into()
|
||||
|
@ -188,6 +188,12 @@ impl BitStorage {
|
|||
let bit_index = (index - cell_index * self.values_per_long as usize) * self.bits;
|
||||
*cell = *cell & !(self.mask << bit_index) | (value & self.mask) << bit_index;
|
||||
}
|
||||
|
||||
/// The number of entries.
|
||||
#[inline]
|
||||
pub fn size(&self) -> usize {
|
||||
self.size
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -4,6 +4,7 @@ mod bit_storage;
|
|||
mod palette;
|
||||
|
||||
use crate::palette::PalettedContainerType;
|
||||
use azalea_block::BlockState;
|
||||
use azalea_core::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos};
|
||||
use azalea_protocol::mc_buf::{McBufReadable, McBufWritable};
|
||||
pub use bit_storage::BitStorage;
|
||||
|
@ -57,7 +58,7 @@ impl World {
|
|||
self.storage.view_center = *pos;
|
||||
}
|
||||
|
||||
pub fn get_block_state(&self, pos: &BlockPos) -> Option<u32> {
|
||||
pub fn get_block_state(&self, pos: &BlockPos) -> Option<BlockState> {
|
||||
self.storage.get_block_state(pos, self.min_y)
|
||||
}
|
||||
}
|
||||
|
@ -122,7 +123,7 @@ impl ChunkStorage {
|
|||
&& (chunk_pos.z - self.view_center.z).unsigned_abs() <= self.chunk_radius
|
||||
}
|
||||
|
||||
pub fn get_block_state(&self, pos: &BlockPos, min_y: i32) -> Option<u32> {
|
||||
pub fn get_block_state(&self, pos: &BlockPos, min_y: i32) -> Option<BlockState> {
|
||||
let chunk_pos = ChunkPos::from(pos);
|
||||
println!("chunk_pos {:?} block_pos {:?}", chunk_pos, pos);
|
||||
let chunk = &self[&chunk_pos];
|
||||
|
@ -175,7 +176,7 @@ impl Chunk {
|
|||
(y.div_floor(16) - min_section_index) as u32
|
||||
}
|
||||
|
||||
pub fn get(&self, pos: &ChunkBlockPos, min_y: i32) -> u32 {
|
||||
pub fn get(&self, pos: &ChunkBlockPos, min_y: i32) -> BlockState {
|
||||
let section_index = self.section_index(pos.y, min_y);
|
||||
// TODO: make sure the section exists
|
||||
let section = &self.sections[section_index as usize];
|
||||
|
@ -204,12 +205,25 @@ pub struct Section {
|
|||
impl McBufReadable for Section {
|
||||
fn read_into(buf: &mut impl Read) -> Result<Self, String> {
|
||||
let block_count = u16::read_into(buf)?;
|
||||
|
||||
// this is commented out because the vanilla server is wrong
|
||||
// assert!(
|
||||
// block_count <= 16 * 16 * 16,
|
||||
// "A section has more blocks than what should be possible. This is a bug!"
|
||||
// );
|
||||
|
||||
let states = PalettedContainer::read_with_type(buf, &PalettedContainerType::BlockStates)?;
|
||||
|
||||
for i in 0..states.storage.size() {
|
||||
if !BlockState::is_valid_state(states.storage.get(i) as u32) {
|
||||
return Err(format!(
|
||||
"Invalid block state {} (index {}) found in section.",
|
||||
states.storage.get(i),
|
||||
i
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let biomes = PalettedContainer::read_with_type(buf, &PalettedContainerType::Biomes)?;
|
||||
Ok(Section {
|
||||
block_count,
|
||||
|
@ -229,9 +243,11 @@ impl McBufWritable for Section {
|
|||
}
|
||||
|
||||
impl Section {
|
||||
// TODO: return a BlockState instead of a u32
|
||||
fn get(&self, pos: ChunkSectionBlockPos) -> u32 {
|
||||
fn get(&self, pos: ChunkSectionBlockPos) -> BlockState {
|
||||
// TODO: use the unsafe method and do the check earlier
|
||||
self.states
|
||||
.get(pos.x as usize, pos.y as usize, pos.z as usize)
|
||||
.try_into()
|
||||
.expect("Invalid block state.")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ async fn main() {
|
|||
println!("Hello, world!");
|
||||
|
||||
// let address = "95.111.249.143:10000";
|
||||
let address = "localhost:65519";
|
||||
let address = "localhost:51028";
|
||||
// let response = azalea_client::ping::ping_server(&address.try_into().unwrap())
|
||||
// .await
|
||||
// .unwrap();
|
||||
|
@ -21,6 +21,10 @@ async fn main() {
|
|||
// TODO: have a "loaded" or "ready" event that fires when all chunks are loaded
|
||||
Event::Login => {}
|
||||
Event::Chat(p) => {
|
||||
let state = client.state.lock().await;
|
||||
let world = state.world.as_ref().unwrap();
|
||||
// println!("{:?}", state.player.entity);
|
||||
// world.get_block_state(state.player.entity.pos);
|
||||
// println!("{}", p.message.to_ansi(None));
|
||||
// if p.message.to_ansi(None) == "<py5> ok" {
|
||||
// let state = client.state.lock().await;
|
||||
|
|
28
codegen/genblocks.py
Normal file
28
codegen/genblocks.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
import lib.code.version
|
||||
import lib.code.packet
|
||||
import lib.code.blocks
|
||||
import lib.code.utils
|
||||
import lib.download
|
||||
import lib.extract
|
||||
import lib.utils
|
||||
import sys
|
||||
import os
|
||||
|
||||
version_id = lib.code.version.get_version_id()
|
||||
|
||||
lib.download.get_burger()
|
||||
lib.download.get_client_jar(version_id)
|
||||
|
||||
print('Generating data with burger')
|
||||
os.system(
|
||||
f'cd {lib.utils.get_dir_location("downloads/Burger")} && python munch.py ../client-{version_id}.jar --output ../burger-{version_id}.json --toppings blockstates'
|
||||
)
|
||||
print('Ok')
|
||||
|
||||
mappings = lib.download.get_mappings_for_version(version_id)
|
||||
block_states_burger = lib.extract.get_block_states_burger(version_id)
|
||||
ordered_blocks = lib.extract.get_ordered_blocks_burger(version_id)
|
||||
block_states_report = lib.extract.get_block_states_report(version_id)
|
||||
|
||||
lib.code.blocks.generate_blocks(
|
||||
block_states_burger, block_states_report, ordered_blocks, mappings)
|
185
codegen/lib/code/blocks.py
Normal file
185
codegen/lib/code/blocks.py
Normal file
|
@ -0,0 +1,185 @@
|
|||
from typing import Optional
|
||||
from lib.utils import to_snake_case, upper_first_letter, get_dir_location, to_camel_case
|
||||
from ..mappings import Mappings
|
||||
import re
|
||||
|
||||
BLOCKS_RS_DIR = get_dir_location('../azalea-block/src/blocks.rs')
|
||||
|
||||
# Terminology:
|
||||
# - Property: A property of a block, like "direction"
|
||||
# - Variant: A potential state of a property, like "up"
|
||||
# - State: A possible state of a block, a combination of variants
|
||||
# - Block: Has properties and states.
|
||||
|
||||
|
||||
def generate_blocks(blocks_burger: dict, blocks_report: dict, ordered_blocks: list[str], mappings: Mappings):
|
||||
with open(BLOCKS_RS_DIR, 'r') as f:
|
||||
existing_code = f.read().splitlines()
|
||||
|
||||
new_make_block_states_macro_code = []
|
||||
new_make_block_states_macro_code.append('make_block_states! {')
|
||||
|
||||
def get_property_struct_name(property: Optional[dict], block_data_burger: dict, property_variants: list[str]) -> str:
|
||||
# these are hardcoded because otherwise they cause conflicts
|
||||
# some names inspired by https://github.com/feather-rs/feather/blob/main/feather/blocks/src/generated/table.rs
|
||||
if property_variants == ['north', 'east', 'south', 'west', 'up', 'down']:
|
||||
return 'FacingCubic'
|
||||
if property_variants == ['north', 'south', 'west', 'east']:
|
||||
return 'FacingCardinal'
|
||||
if property_variants == ['top', 'bottom']:
|
||||
return 'TopBottom'
|
||||
if property_variants == ['north_south', 'east_west', 'ascending_east', 'ascending_west', 'ascending_north', 'ascending_south']:
|
||||
return 'RailShape'
|
||||
if property_variants == ['straight', 'inner_left', 'inner_right', 'outer_left', 'outer_right']:
|
||||
return 'StairShape'
|
||||
if property_variants == ['normal', 'sticky']:
|
||||
return 'PistonType'
|
||||
if property_variants == ['x', 'z']:
|
||||
return 'AxisXZ'
|
||||
if property_variants == ['single', 'left', 'right']:
|
||||
return 'ChestType'
|
||||
if property_variants == ['compare', 'subtract']:
|
||||
return 'ComparatorType'
|
||||
|
||||
if property is None:
|
||||
return ''.join(map(to_camel_case, property_variants))
|
||||
|
||||
property_name = None
|
||||
for class_name in [block_data_burger['class']] + block_data_burger['super']:
|
||||
property_name = mappings.get_field(
|
||||
class_name, property['field_name'])
|
||||
if property_name:
|
||||
break
|
||||
assert property_name
|
||||
property_name = to_camel_case(property_name.lower())
|
||||
if property['type'] == 'int':
|
||||
property_name = to_camel_case(
|
||||
block_data_burger['text_id']) + property_name
|
||||
|
||||
# if property_variants == ['none', 'low', 'tall']:
|
||||
|
||||
if property_variants == ['up', 'side', 'none']:
|
||||
property_name = 'Wire' + to_camel_case(property_name)
|
||||
|
||||
return property_name
|
||||
|
||||
# Find properties
|
||||
properties = {}
|
||||
|
||||
# This dict looks like { 'FloweringAzaleaLeavesDistance': 'distance' }
|
||||
property_struct_names_to_names = {}
|
||||
for block_id in ordered_blocks:
|
||||
block_data_burger = blocks_burger[block_id]
|
||||
block_data_report = blocks_report[f'minecraft:{block_id}']
|
||||
|
||||
block_properties = {}
|
||||
for property_name in list(block_data_report.get('properties', {}).keys()):
|
||||
property_burger = None
|
||||
for property in block_data_burger.get('states', []):
|
||||
if property['name'] == property_name:
|
||||
property_burger = property
|
||||
break
|
||||
|
||||
property_variants = block_data_report['properties'][property_name]
|
||||
|
||||
if property_burger is None:
|
||||
print(
|
||||
'Warning: The reports have states for a block, but Burger doesn\'t!', block_data_burger)
|
||||
|
||||
property_struct_name = get_property_struct_name(
|
||||
property_burger, block_data_burger, property_variants)
|
||||
|
||||
if property_struct_name in properties:
|
||||
if not properties[property_struct_name] == property_variants:
|
||||
raise Exception(
|
||||
'There are multiple enums with the same name! '
|
||||
f'Name: {property_struct_name}, variants: {property_variants}/{properties[property_struct_name]}. '
|
||||
'This can be fixed by hardcoding a name in the get_property_struct_name function.'
|
||||
)
|
||||
|
||||
block_properties[property_struct_name] = property_variants
|
||||
|
||||
# if the name ends with _<number>, remove that part
|
||||
ending = property_name.split('_')[-1]
|
||||
if ending.isdigit():
|
||||
property_name = property_name[:-(len(ending) + 1)]
|
||||
|
||||
# `type` is a reserved keyword, so we use kind instead ¯\_(ツ)_/¯
|
||||
if property_name == 'type':
|
||||
property_name = 'kind'
|
||||
property_struct_names_to_names[property_struct_name] = property_name
|
||||
|
||||
properties.update(block_properties)
|
||||
|
||||
# Property codegen
|
||||
new_make_block_states_macro_code.append(' Properties => {')
|
||||
for property_struct_name, property_variants in properties.items():
|
||||
# "face" => Face {
|
||||
# Floor,
|
||||
# Wall,
|
||||
# Ceiling,
|
||||
# },
|
||||
property_name = property_struct_names_to_names[property_struct_name]
|
||||
new_make_block_states_macro_code.append(
|
||||
f' "{property_name}" => {property_struct_name} {{')
|
||||
|
||||
for variant in property_variants:
|
||||
new_make_block_states_macro_code.append(
|
||||
f' {to_camel_case(variant)},')
|
||||
|
||||
new_make_block_states_macro_code.append(
|
||||
f' }},')
|
||||
new_make_block_states_macro_code.append(' },')
|
||||
|
||||
# Block codegen
|
||||
new_make_block_states_macro_code.append(' Blocks => {')
|
||||
for block_id in ordered_blocks:
|
||||
block_data_burger = blocks_burger[block_id]
|
||||
block_data_report = blocks_report['minecraft:' + block_id]
|
||||
|
||||
block_properties = block_data_burger.get('states', [])
|
||||
block_properties_burger = block_data_burger.get('states', [])
|
||||
|
||||
default_property_variants: dict[str, str] = {}
|
||||
for state in block_data_report['states']:
|
||||
if state.get('default'):
|
||||
default_property_variants = state.get('properties', {})
|
||||
|
||||
# TODO: use burger to generate the blockbehavior
|
||||
new_make_block_states_macro_code.append(
|
||||
f' {block_id} => BlockBehavior::default(), {{')
|
||||
for property_name in list(block_data_report.get('properties', {}).keys()):
|
||||
property_burger = None
|
||||
for property in block_data_burger.get('states', []):
|
||||
if property['name'] == property_name:
|
||||
property_burger = property
|
||||
break
|
||||
|
||||
property_default = default_property_variants.get(property_name)
|
||||
property_variants = block_data_report['properties'][property_name]
|
||||
|
||||
property_struct_name = get_property_struct_name(
|
||||
property_burger, block_data_burger, property_variants)
|
||||
assert property_default is not None
|
||||
new_make_block_states_macro_code.append(
|
||||
f' {property_struct_name}={to_camel_case(property_default)},')
|
||||
new_make_block_states_macro_code.append(' },')
|
||||
new_make_block_states_macro_code.append(' }')
|
||||
new_make_block_states_macro_code.append('}')
|
||||
|
||||
new_code = []
|
||||
in_macro = False
|
||||
for line in existing_code:
|
||||
if line == 'make_block_states! {':
|
||||
in_macro = True
|
||||
elif line == '}':
|
||||
if in_macro:
|
||||
in_macro = False
|
||||
new_code.extend(new_make_block_states_macro_code)
|
||||
continue
|
||||
if in_macro:
|
||||
continue
|
||||
new_code.append(line)
|
||||
|
||||
with open(BLOCKS_RS_DIR, 'w') as f:
|
||||
f.write('\n'.join(new_code))
|
|
@ -1,6 +1,6 @@
|
|||
from .utils import burger_type_to_rust_type, write_packet_file
|
||||
from ..utils import padded_hex, to_snake_case, to_camel_case
|
||||
from ..mappings import Mappings
|
||||
from lib.code.utils import burger_type_to_rust_type, write_packet_file
|
||||
from lib.utils import padded_hex, to_snake_case, to_camel_case, get_dir_location
|
||||
from lib.mappings import Mappings
|
||||
import os
|
||||
|
||||
|
||||
|
@ -74,7 +74,8 @@ def generate_packet(burger_packets, mappings: Mappings, target_packet_id, target
|
|||
'\n'.join(generated_packet_code))
|
||||
print()
|
||||
|
||||
mod_rs_dir = f'../azalea-protocol/src/packets/{state}/mod.rs'
|
||||
mod_rs_dir = get_dir_location(
|
||||
f'../azalea-protocol/src/packets/{state}/mod.rs')
|
||||
with open(mod_rs_dir, 'r') as f:
|
||||
mod_rs = f.read().splitlines()
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
|
||||
from lib.utils import get_dir_location
|
||||
import os
|
||||
|
||||
# utilities specifically for codegen
|
||||
|
@ -67,9 +68,9 @@ def burger_type_to_rust_type(burger_type):
|
|||
|
||||
|
||||
def write_packet_file(state, packet_name_snake_case, code):
|
||||
with open(f'../azalea-protocol/src/packets/{state}/{packet_name_snake_case}.rs', 'w') as f:
|
||||
with open(get_dir_location(f'../azalea-protocol/src/packets/{state}/{packet_name_snake_case}.rs'), 'w') as f:
|
||||
f.write(code)
|
||||
|
||||
|
||||
def fmt():
|
||||
os.system('cd .. && cargo fmt')
|
||||
os.system(f'cd {get_dir_location("..")} && cargo fmt')
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
from lib.utils import get_dir_location
|
||||
import re
|
||||
import os
|
||||
|
||||
README_DIR = os.path.join(os.path.dirname(__file__), '../../../README.md')
|
||||
README_DIR = get_dir_location('../README.md')
|
||||
VERSION_REGEX = r'\*Currently supported Minecraft version: `(.*)`.\*'
|
||||
|
||||
|
||||
|
|
|
@ -1,39 +1,40 @@
|
|||
from lib.utils import get_dir_location
|
||||
from .mappings import Mappings
|
||||
import requests
|
||||
import json
|
||||
import os
|
||||
|
||||
# make sure the downloads directory exists
|
||||
if not os.path.exists('downloads'):
|
||||
os.mkdir('downloads')
|
||||
if not os.path.exists(get_dir_location('downloads')):
|
||||
os.mkdir(get_dir_location('downloads'))
|
||||
|
||||
|
||||
def get_burger():
|
||||
if not os.path.exists('downloads/Burger'):
|
||||
if not os.path.exists(get_dir_location('downloads/Burger')):
|
||||
print('\033[92mDownloading Burger...\033[m')
|
||||
os.system(
|
||||
'cd downloads && git clone https://github.com/pokechu22/Burger && cd Burger && git pull')
|
||||
f'cd {get_dir_location("downloads")} && git clone https://github.com/pokechu22/Burger && cd Burger && git pull')
|
||||
|
||||
print('\033[92mInstalling dependencies...\033[m')
|
||||
os.system('cd downloads/Burger && pip install six jawa')
|
||||
|
||||
|
||||
def get_version_manifest():
|
||||
if not os.path.exists(f'downloads/version_manifest.json'):
|
||||
if not os.path.exists(get_dir_location(f'downloads/version_manifest.json')):
|
||||
print(
|
||||
f'\033[92mDownloading version manifest...\033[m')
|
||||
version_manifest_data = requests.get(
|
||||
'https://launchermeta.mojang.com/mc/game/version_manifest.json').json()
|
||||
with open(f'downloads/version_manifest.json', 'w') as f:
|
||||
with open(get_dir_location(f'downloads/version_manifest.json'), 'w') as f:
|
||||
json.dump(version_manifest_data, f)
|
||||
else:
|
||||
with open(f'downloads/version_manifest.json', 'r') as f:
|
||||
with open(get_dir_location(f'downloads/version_manifest.json'), 'r') as f:
|
||||
version_manifest_data = json.load(f)
|
||||
return version_manifest_data
|
||||
|
||||
|
||||
def get_version_data(version_id: str):
|
||||
if not os.path.exists(f'downloads/{version_id}.json'):
|
||||
if not os.path.exists(get_dir_location(f'downloads/{version_id}.json')):
|
||||
version_manifest_data = get_version_manifest()
|
||||
|
||||
print(
|
||||
|
@ -45,46 +46,43 @@ def get_version_data(version_id: str):
|
|||
raise ValueError(
|
||||
f'No version with id {version_id} found. Maybe delete downloads/version_manifest.json and try again?')
|
||||
package_data = requests.get(package_url).json()
|
||||
with open(f'downloads/{version_id}.json', 'w') as f:
|
||||
with open(get_dir_location(f'downloads/{version_id}.json'), 'w') as f:
|
||||
json.dump(package_data, f)
|
||||
else:
|
||||
with open(f'downloads/{version_id}.json', 'r') as f:
|
||||
with open(get_dir_location(f'downloads/{version_id}.json'), 'r') as f:
|
||||
package_data = json.load(f)
|
||||
return package_data
|
||||
|
||||
|
||||
def get_client_jar(version_id: str):
|
||||
if not os.path.exists(f'downloads/client-{version_id}.jar'):
|
||||
if not os.path.exists(get_dir_location(f'downloads/client-{version_id}.jar')):
|
||||
package_data = get_version_data(version_id)
|
||||
print('\033[92mDownloading client jar...\033[m')
|
||||
client_jar_url = package_data['downloads']['client']['url']
|
||||
with open(f'downloads/client-{version_id}.jar', 'wb') as f:
|
||||
with open(get_dir_location(f'downloads/client-{version_id}.jar'), 'wb') as f:
|
||||
f.write(requests.get(client_jar_url).content)
|
||||
|
||||
|
||||
def get_burger_data_for_version(version_id: str):
|
||||
if not os.path.exists(f'downloads/burger-{version_id}.json'):
|
||||
get_burger()
|
||||
get_client_jar(version_id)
|
||||
|
||||
os.system(
|
||||
f'cd downloads/Burger && python munch.py ../client-{version_id}.jar --output ../burger-{version_id}.json'
|
||||
)
|
||||
with open(f'downloads/burger-{version_id}.json', 'r') as f:
|
||||
return json.load(f)
|
||||
def get_server_jar(version_id: str):
|
||||
if not os.path.exists(get_dir_location(f'downloads/server-{version_id}.jar')):
|
||||
package_data = get_version_data(version_id)
|
||||
print('\033[92mDownloading server jar...\033[m')
|
||||
server_jar_url = package_data['downloads']['server']['url']
|
||||
with open(get_dir_location(f'downloads/server-{version_id}.jar'), 'wb') as f:
|
||||
f.write(requests.get(server_jar_url).content)
|
||||
|
||||
|
||||
def get_mappings_for_version(version_id: str):
|
||||
if not os.path.exists(f'downloads/mappings-{version_id}.txt'):
|
||||
if not os.path.exists(get_dir_location(f'downloads/mappings-{version_id}.txt')):
|
||||
package_data = get_version_data(version_id)
|
||||
|
||||
client_mappings_url = package_data['downloads']['client_mappings']['url']
|
||||
|
||||
mappings_text = requests.get(client_mappings_url).text
|
||||
|
||||
with open(f'downloads/mappings-{version_id}.txt', 'w') as f:
|
||||
with open(get_dir_location(f'downloads/mappings-{version_id}.txt'), 'w') as f:
|
||||
f.write(mappings_text)
|
||||
else:
|
||||
with open(f'downloads/mappings-{version_id}.txt', 'r') as f:
|
||||
with open(get_dir_location(f'downloads/mappings-{version_id}.txt'), 'r') as f:
|
||||
mappings_text = f.read()
|
||||
return Mappings.parse(mappings_text)
|
||||
|
|
44
codegen/lib/extract.py
Normal file
44
codegen/lib/extract.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
# Extracting data from the Minecraft jars
|
||||
|
||||
from lib.download import get_server_jar, get_burger, get_client_jar
|
||||
from lib.utils import get_dir_location
|
||||
import json
|
||||
import os
|
||||
|
||||
|
||||
def generate_data_from_server_jar(version_id: str):
|
||||
if os.path.exists(get_dir_location(f'downloads/generated-{version_id}')):
|
||||
return
|
||||
|
||||
get_server_jar(version_id)
|
||||
os.system(
|
||||
f'cd {get_dir_location(f"downloads")} && java -DbundlerMainClass=net.minecraft.data.Main -jar {get_dir_location(f"downloads/server-{version_id}.jar")} --all --output \"{get_dir_location(f"downloads/generated-{version_id}")}\"'
|
||||
)
|
||||
|
||||
|
||||
def get_block_states_report(version_id: str):
|
||||
generate_data_from_server_jar(version_id)
|
||||
with open(get_dir_location(f'downloads/generated-{version_id}/reports/blocks.json'), 'r') as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def get_block_states_burger(version_id: str):
|
||||
burger_data = get_burger_data_for_version(version_id)
|
||||
return burger_data[0]['blocks']['block']
|
||||
|
||||
|
||||
def get_ordered_blocks_burger(version_id: str):
|
||||
burger_data = get_burger_data_for_version(version_id)
|
||||
return burger_data[0]['blocks']['ordered_blocks']
|
||||
|
||||
|
||||
def get_burger_data_for_version(version_id: str):
|
||||
if not os.path.exists(get_dir_location(f'downloads/burger-{version_id}.json')):
|
||||
get_burger()
|
||||
get_client_jar(version_id)
|
||||
|
||||
os.system(
|
||||
f'cd {get_dir_location("downloads/Burger")} && python munch.py ../client-{version_id}.jar --output ../burger-{version_id}.json'
|
||||
)
|
||||
with open(get_dir_location(f'downloads/burger-{version_id}.json'), 'r') as f:
|
||||
return json.load(f)
|
|
@ -1,4 +1,5 @@
|
|||
import re
|
||||
import os
|
||||
|
||||
# utilities that could be used for things other than codegen
|
||||
|
||||
|
@ -10,8 +11,15 @@ def to_snake_case(name: str):
|
|||
|
||||
def to_camel_case(name: str):
|
||||
s = re.sub('_([a-z])', lambda m: m.group(1).upper(), name)
|
||||
return s[0].upper() + s[1:]
|
||||
s = upper_first_letter(s)
|
||||
# if the first character is a number, we need to add an underscore
|
||||
# maybe we could convert it to the number name (like 2 would become "two")?
|
||||
if s[0].isdigit():
|
||||
s = f'_{s}'
|
||||
return s
|
||||
|
||||
def upper_first_letter(name: str):
|
||||
return name[0].upper() + name[1:]
|
||||
|
||||
def padded_hex(n: int):
|
||||
return f'0x{n:02x}'
|
||||
|
@ -44,3 +52,7 @@ def group_packets(packets: list[PacketIdentifier]):
|
|||
packet_groups[key] = []
|
||||
packet_groups[key].append(packet.packet_id)
|
||||
return packet_groups
|
||||
|
||||
|
||||
def get_dir_location(name: str):
|
||||
return os.path.join(os.path.dirname(__file__), '..', name)
|
||||
|
|
|
@ -4,17 +4,18 @@ import lib.code.utils
|
|||
import lib.code.version
|
||||
import lib.code.packet
|
||||
import lib.download
|
||||
import lib.extract
|
||||
import sys
|
||||
import os
|
||||
|
||||
old_version_id = lib.code.version.get_version_id()
|
||||
old_mappings = lib.download.get_mappings_for_version(old_version_id)
|
||||
old_burger_data = lib.download.get_burger_data_for_version(old_version_id)
|
||||
old_burger_data = lib.extract.get_burger_data_for_version(old_version_id)
|
||||
old_packet_list = list(old_burger_data[0]['packets']['packet'].values())
|
||||
|
||||
new_version_id = sys.argv[1]
|
||||
new_mappings = lib.download.get_mappings_for_version(new_version_id)
|
||||
new_burger_data = lib.download.get_burger_data_for_version(new_version_id)
|
||||
new_burger_data = lib.extract.get_burger_data_for_version(new_version_id)
|
||||
new_packet_list = list(new_burger_data[0]['packets']['packet'].values())
|
||||
|
||||
|
||||
|
|
|
@ -1,17 +1,22 @@
|
|||
from lib import download, code # type: ignore
|
||||
import lib.code.version
|
||||
import lib.code.packet
|
||||
import lib.code.utils
|
||||
import lib.download
|
||||
import lib.extract
|
||||
import sys
|
||||
import os
|
||||
|
||||
mappings = download.get_mappings_for_version('1.18.2')
|
||||
burger_data = download.get_burger_data_for_version('1.18.2')
|
||||
version_id = lib.code.version.get_version_id()
|
||||
|
||||
mappings = lib.download.get_mappings_for_version(version_id)
|
||||
burger_data = lib.extract.get_burger_data_for_version(version_id)
|
||||
|
||||
burger_packets_data = burger_data[0]['packets']['packet']
|
||||
packet_id, direction, state = int(sys.argv[1]), sys.argv[2], sys.argv[3]
|
||||
print(
|
||||
f'Generating code for packet id: {packet_id} with direction {direction} and state {state}')
|
||||
code.packetcodegen.generate_packet(burger_packets_data, mappings,
|
||||
lib.code.packet.generate_packet(burger_packets_data, mappings,
|
||||
packet_id, direction, state)
|
||||
|
||||
code.fmt()
|
||||
lib.code.utils.fmt()
|
||||
|
||||
print('Done!')
|
||||
|
|
|
@ -14,7 +14,7 @@ loop {
|
|||
pathfinder::Goals::NearXZ(5, azalea::BlockXZ(0, 0))
|
||||
).await;
|
||||
let chest = bot.open_chest(&bot.world.find_one_block(|b| b.id == "minecraft:chest")).await.unwrap();
|
||||
bot.take_amount(&chest, 3, |i| i.id == "#minecraft:planks").await;
|
||||
bot.take_amount(&chest, 5, |i| i.id == "#minecraft:planks").await;
|
||||
// when rust adds async drop this won't be necessary
|
||||
chest.close().await;
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue