1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 14:26:04 +00:00

Merge pull request #9 from mat-1/azalea-block

azalea-block
This commit is contained in:
mat 2022-06-17 23:10:54 +00:00 committed by GitHub
commit f414aa4d37
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 6089 additions and 81 deletions

51
Cargo.lock generated
View file

@ -45,9 +45,9 @@ dependencies = [
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.53" version = "0.1.56"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed6aa3524a2dfcf9fe180c51eae2b58738348d819517ceadf95789c51fff7600" checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -78,6 +78,13 @@ dependencies = [
"uuid", "uuid",
] ]
[[package]]
name = "azalea-block"
version = "0.1.0"
dependencies = [
"block-macros",
]
[[package]] [[package]]
name = "azalea-brigadier" name = "azalea-brigadier"
version = "0.1.0" version = "0.1.0"
@ -176,6 +183,7 @@ dependencies = [
name = "azalea-world" name = "azalea-world"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"azalea-block",
"azalea-core", "azalea-core",
"azalea-nbt", "azalea-nbt",
"azalea-protocol", "azalea-protocol",
@ -196,6 +204,15 @@ dependencies = [
"generic-array", "generic-array",
] ]
[[package]]
name = "block-macros"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "bot" name = "bot"
version = "0.1.0" version = "0.1.0"
@ -220,9 +237,9 @@ dependencies = [
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.9.1" version = "3.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
@ -458,13 +475,11 @@ dependencies = [
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.0.23" version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39522e96686d38f4bc984b9198e3a0613264abaebaff2c5c918bfa6b6da09af" checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
dependencies = [ dependencies = [
"cfg-if",
"crc32fast", "crc32fast",
"libc",
"miniz_oxide", "miniz_oxide",
] ]
@ -729,9 +744,9 @@ dependencies = [
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.5.1" version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
dependencies = [ dependencies = [
"adler", "adler",
] ]
@ -1104,9 +1119,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]] [[package]]
name = "semver" name = "semver"
version = "1.0.9" version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd" checksum = "a41d061efea015927ac527063765e73601444cdc344ba855bc7bd44578b25e1c"
[[package]] [[package]]
name = "serde" name = "serde"
@ -1207,9 +1222,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.95" version = "1.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1272,9 +1287,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.18.2" version = "1.19.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4903bf0427cf68dddd5aa6a93220756f8be0c34fcfa9f5e6191e103e15a31395" checksum = "c51a52ed6686dd62c320f9b89299e9dfb46f730c7a48e635c19f21d116cb1439"
dependencies = [ dependencies = [
"bytes", "bytes",
"libc", "libc",
@ -1290,9 +1305,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-macros" name = "tokio-macros"
version = "1.7.0" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" checksum = "9724f9a975fb987ef7a3cd9be0350edcbe130698af5b8f7a631e23d42d052484"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View file

@ -11,6 +11,7 @@ members = [
"azalea-crypto", "azalea-crypto",
"azalea-world", "azalea-world",
"azalea-language", "azalea-language",
"azalea-block",
] ]
[profile.release] [profile.release]

View file

@ -1,6 +1,6 @@
# Azalea # Azalea
A Rust crate for creating Minecraft bots. A collection of Rust crates for creating Minecraft bots and other utilities.
<p align="center"> <p align="center">
<img src="https://cdn.matdoes.dev/images/flowering_azalea.webp" alt="Azalea" height="200"> <img src="https://cdn.matdoes.dev/images/flowering_azalea.webp" alt="Azalea" height="200">

11
azalea-block/Cargo.toml Normal file
View 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
View 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().

View 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"

View 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()
}

View 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
}

View 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

File diff suppressed because it is too large Load diff

47
azalea-block/src/lib.rs Normal file
View 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());
}
}

View file

@ -1,5 +1,5 @@
use crate::Player; use crate::Player;
use azalea_core::{resource_location::ResourceLocation, ChunkPos}; use azalea_core::{resource_location::ResourceLocation, ChunkPos, EntityPos};
use azalea_protocol::{ use azalea_protocol::{
connect::{GameConnection, HandshakeConnection}, connect::{GameConnection, HandshakeConnection},
packets::{ packets::{
@ -351,6 +351,11 @@ impl Client {
} }
GamePacket::ClientboundAddEntityPacket(p) => { GamePacket::ClientboundAddEntityPacket(p) => {
println!("Got add entity packet {:?}", p); println!("Got add entity packet {:?}", p);
let pos = EntityPos {
x: p.x,
y: p.y,
z: p.z,
};
} }
GamePacket::ClientboundSetEntityDataPacket(p) => { GamePacket::ClientboundSetEntityDataPacket(p) => {
// println!("Got set entity data packet {:?}", p); // println!("Got set entity data packet {:?}", p);
@ -447,6 +452,9 @@ impl Client {
GamePacket::ClientboundServerDataPacket(p) => { GamePacket::ClientboundServerDataPacket(p) => {
println!("Got server data packet {:?}", p); println!("Got server data packet {:?}", p);
} }
GamePacket::ClientboundSetEquipmentPacket(p) => {
println!("Got set equipment packet {:?}", p);
}
_ => panic!("Unexpected packet {:?}", packet), _ => panic!("Unexpected packet {:?}", packet),
} }
} }

View file

@ -1,5 +1,8 @@
#[derive(Default)] use azalea_core::EntityPos;
#[derive(Default, Debug)]
pub struct Entity { pub struct Entity {
/// The incremental numerical id of the entity. /// The incremental numerical id of the entity.
pub id: u32, pub id: u32,
pub pos: EntityPos,
} }

View file

@ -1,6 +1,6 @@
use crate::Entity; use crate::Entity;
#[derive(Default)] #[derive(Default, Debug)]
pub struct Player { pub struct Player {
/// The entity attached to the player. There's some useful fields here. /// The entity attached to the player. There's some useful fields here.
pub entity: Entity, pub entity: Entity,

View file

@ -11,9 +11,7 @@ mod slot;
pub use slot::{Slot, SlotData}; pub use slot::{Slot, SlotData};
mod position; mod position;
pub use position::{ pub use position::*;
BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos, ChunkSectionPos, GlobalPos,
};
mod direction; mod direction;
pub use direction::Direction; pub use direction::Direction;

View file

@ -147,6 +147,23 @@ pub struct GlobalPos {
pub dimension: ResourceLocation, 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View file

@ -3,6 +3,7 @@ use uuid::Uuid;
#[derive(Clone, Debug, McBuf, GamePacket)] #[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundAddEntityPacket { pub struct ClientboundAddEntityPacket {
/// The id of the entity.
#[var] #[var]
pub id: u32, pub id: u32,
pub uuid: Uuid, pub uuid: Uuid,

View file

@ -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,
}
}
}

View file

@ -3,7 +3,7 @@ use packet_macros::{GamePacket, McBuf};
#[derive(Clone, Debug, McBuf, GamePacket)] #[derive(Clone, Debug, McBuf, GamePacket)]
pub struct ClientboundSoundPacket { pub struct ClientboundSoundPacket {
#[var] #[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 sound: u32,
pub source: SoundSource, pub source: SoundSource,
pub x: i32, pub x: i32,
@ -11,6 +11,8 @@ pub struct ClientboundSoundPacket {
pub z: i32, pub z: i32,
pub volume: f32, pub volume: f32,
pub pitch: f32, pub pitch: f32,
/// Seed used to pick sound varient.
pub seed: u64,
} }
#[derive(Clone, Debug, Copy, McBuf)] #[derive(Clone, Debug, Copy, McBuf)]

View file

@ -37,6 +37,7 @@ pub mod clientbound_set_default_spawn_position_packet;
pub mod clientbound_set_display_chat_preview_packet; pub mod clientbound_set_display_chat_preview_packet;
pub mod clientbound_set_entity_data_packet; pub mod clientbound_set_entity_data_packet;
pub mod clientbound_set_entity_link_packet; pub mod clientbound_set_entity_link_packet;
pub mod clientbound_set_equipment_packet;
pub mod clientbound_set_experience_packet; pub mod clientbound_set_experience_packet;
pub mod clientbound_set_health_packet; pub mod clientbound_set_health_packet;
pub mod clientbound_set_time_packet; pub mod clientbound_set_time_packet;
@ -104,6 +105,7 @@ declare_state_packets!(
0x4b: clientbound_set_display_chat_preview_packet::ClientboundSetDisplayChatPreviewPacket, 0x4b: clientbound_set_display_chat_preview_packet::ClientboundSetDisplayChatPreviewPacket,
0x4d: clientbound_set_entity_data_packet::ClientboundSetEntityDataPacket, 0x4d: clientbound_set_entity_data_packet::ClientboundSetEntityDataPacket,
0x4f: clientbound_entity_velocity_packet::ClientboundEntityVelocityPacket, 0x4f: clientbound_entity_velocity_packet::ClientboundEntityVelocityPacket,
0x50: clientbound_set_equipment_packet::ClientboundSetEquipmentPacket,
0x51: clientbound_set_experience_packet::ClientboundSetExperiencePacket, 0x51: clientbound_set_experience_packet::ClientboundSetExperiencePacket,
0x52: clientbound_set_health_packet::ClientboundSetHealthPacket, 0x52: clientbound_set_health_packet::ClientboundSetHealthPacket,
0x59: clientbound_set_time_packet::ClientboundSetTimePacket, 0x59: clientbound_set_time_packet::ClientboundSetTimePacket,

View file

@ -9,3 +9,4 @@ version = "0.1.0"
azalea-core = {path = "../azalea-core"} azalea-core = {path = "../azalea-core"}
azalea-nbt = {path = "../azalea-nbt"} azalea-nbt = {path = "../azalea-nbt"}
azalea-protocol = {path = "../azalea-protocol"} azalea-protocol = {path = "../azalea-protocol"}
azalea-block = {path = "../azalea-block"}

View file

@ -77,8 +77,8 @@ pub struct BitStorage {
mask: u64, mask: u64,
size: usize, size: usize,
values_per_long: u8, values_per_long: u8,
divide_mul: i32, divide_mul: u64,
divide_add: i32, divide_add: u64,
divide_shift: i32, divide_shift: i32,
} }
@ -138,16 +138,16 @@ impl BitStorage {
mask, mask,
size, size,
values_per_long: values_per_long as u8, values_per_long: values_per_long as u8,
divide_mul, divide_mul: divide_mul as u32 as u64,
divide_add, divide_add: divide_add as u32 as u64,
divide_shift, divide_shift,
}) })
} }
pub fn cell_index(&self, index: u64) -> usize { pub fn cell_index(&self, index: u64) -> usize {
// as unsigned wrap // as unsigned wrap
let first = self.divide_mul as u32 as u64; let first = self.divide_mul;
let second = self.divide_add as u64; let second = self.divide_add;
(((index * first) + second) >> 32 >> self.divide_shift) (((index * first) + second) >> 32 >> self.divide_shift)
.try_into() .try_into()
@ -188,6 +188,12 @@ impl BitStorage {
let bit_index = (index - cell_index * self.values_per_long as usize) * self.bits; 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; *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)] #[cfg(test)]

View file

@ -4,6 +4,7 @@ mod bit_storage;
mod palette; mod palette;
use crate::palette::PalettedContainerType; use crate::palette::PalettedContainerType;
use azalea_block::BlockState;
use azalea_core::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos}; use azalea_core::{BlockPos, ChunkBlockPos, ChunkPos, ChunkSectionBlockPos};
use azalea_protocol::mc_buf::{McBufReadable, McBufWritable}; use azalea_protocol::mc_buf::{McBufReadable, McBufWritable};
pub use bit_storage::BitStorage; pub use bit_storage::BitStorage;
@ -57,7 +58,7 @@ impl World {
self.storage.view_center = *pos; 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) 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 && (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); let chunk_pos = ChunkPos::from(pos);
println!("chunk_pos {:?} block_pos {:?}", chunk_pos, pos); println!("chunk_pos {:?} block_pos {:?}", chunk_pos, pos);
let chunk = &self[&chunk_pos]; let chunk = &self[&chunk_pos];
@ -175,7 +176,7 @@ impl Chunk {
(y.div_floor(16) - min_section_index) as u32 (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); let section_index = self.section_index(pos.y, min_y);
// TODO: make sure the section exists // TODO: make sure the section exists
let section = &self.sections[section_index as usize]; let section = &self.sections[section_index as usize];
@ -204,12 +205,25 @@ pub struct Section {
impl McBufReadable for Section { impl McBufReadable for Section {
fn read_into(buf: &mut impl Read) -> Result<Self, String> { fn read_into(buf: &mut impl Read) -> Result<Self, String> {
let block_count = u16::read_into(buf)?; let block_count = u16::read_into(buf)?;
// this is commented out because the vanilla server is wrong // this is commented out because the vanilla server is wrong
// assert!( // assert!(
// block_count <= 16 * 16 * 16, // block_count <= 16 * 16 * 16,
// "A section has more blocks than what should be possible. This is a bug!" // "A section has more blocks than what should be possible. This is a bug!"
// ); // );
let states = PalettedContainer::read_with_type(buf, &PalettedContainerType::BlockStates)?; 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)?; let biomes = PalettedContainer::read_with_type(buf, &PalettedContainerType::Biomes)?;
Ok(Section { Ok(Section {
block_count, block_count,
@ -229,9 +243,11 @@ impl McBufWritable for Section {
} }
impl Section { impl Section {
// TODO: return a BlockState instead of a u32 fn get(&self, pos: ChunkSectionBlockPos) -> BlockState {
fn get(&self, pos: ChunkSectionBlockPos) -> u32 { // TODO: use the unsafe method and do the check earlier
self.states self.states
.get(pos.x as usize, pos.y as usize, pos.z as usize) .get(pos.x as usize, pos.y as usize, pos.z as usize)
.try_into()
.expect("Invalid block state.")
} }
} }

View file

@ -6,7 +6,7 @@ async fn main() {
println!("Hello, world!"); println!("Hello, world!");
// let address = "95.111.249.143:10000"; // 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()) // let response = azalea_client::ping::ping_server(&address.try_into().unwrap())
// .await // .await
// .unwrap(); // .unwrap();
@ -21,6 +21,10 @@ async fn main() {
// TODO: have a "loaded" or "ready" event that fires when all chunks are loaded // TODO: have a "loaded" or "ready" event that fires when all chunks are loaded
Event::Login => {} Event::Login => {}
Event::Chat(p) => { 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)); // println!("{}", p.message.to_ansi(None));
// if p.message.to_ansi(None) == "<py5> ok" { // if p.message.to_ansi(None) == "<py5> ok" {
// let state = client.state.lock().await; // let state = client.state.lock().await;

28
codegen/genblocks.py Normal file
View 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
View 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))

View file

@ -1,6 +1,6 @@
from .utils import burger_type_to_rust_type, write_packet_file from lib.code.utils import burger_type_to_rust_type, write_packet_file
from ..utils import padded_hex, to_snake_case, to_camel_case from lib.utils import padded_hex, to_snake_case, to_camel_case, get_dir_location
from ..mappings import Mappings from lib.mappings import Mappings
import os import os
@ -74,7 +74,8 @@ def generate_packet(burger_packets, mappings: Mappings, target_packet_id, target
'\n'.join(generated_packet_code)) '\n'.join(generated_packet_code))
print() 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: with open(mod_rs_dir, 'r') as f:
mod_rs = f.read().splitlines() mod_rs = f.read().splitlines()

View file

@ -1,4 +1,5 @@
from lib.utils import get_dir_location
import os import os
# utilities specifically for codegen # 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): 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) f.write(code)
def fmt(): def fmt():
os.system('cd .. && cargo fmt') os.system(f'cd {get_dir_location("..")} && cargo fmt')

View file

@ -1,7 +1,8 @@
from lib.utils import get_dir_location
import re import re
import os 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: `(.*)`.\*' VERSION_REGEX = r'\*Currently supported Minecraft version: `(.*)`.\*'

View file

@ -1,39 +1,40 @@
from lib.utils import get_dir_location
from .mappings import Mappings from .mappings import Mappings
import requests import requests
import json import json
import os import os
# make sure the downloads directory exists # make sure the downloads directory exists
if not os.path.exists('downloads'): if not os.path.exists(get_dir_location('downloads')):
os.mkdir('downloads') os.mkdir(get_dir_location('downloads'))
def get_burger(): 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') print('\033[92mDownloading Burger...\033[m')
os.system( 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') print('\033[92mInstalling dependencies...\033[m')
os.system('cd downloads/Burger && pip install six jawa') os.system('cd downloads/Burger && pip install six jawa')
def get_version_manifest(): 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( print(
f'\033[92mDownloading version manifest...\033[m') f'\033[92mDownloading version manifest...\033[m')
version_manifest_data = requests.get( version_manifest_data = requests.get(
'https://launchermeta.mojang.com/mc/game/version_manifest.json').json() '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) json.dump(version_manifest_data, f)
else: 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) version_manifest_data = json.load(f)
return version_manifest_data return version_manifest_data
def get_version_data(version_id: str): 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() version_manifest_data = get_version_manifest()
print( print(
@ -45,46 +46,43 @@ def get_version_data(version_id: str):
raise ValueError( raise ValueError(
f'No version with id {version_id} found. Maybe delete downloads/version_manifest.json and try again?') f'No version with id {version_id} found. Maybe delete downloads/version_manifest.json and try again?')
package_data = requests.get(package_url).json() 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) json.dump(package_data, f)
else: 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) package_data = json.load(f)
return package_data return package_data
def get_client_jar(version_id: str): 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) package_data = get_version_data(version_id)
print('\033[92mDownloading client jar...\033[m') print('\033[92mDownloading client jar...\033[m')
client_jar_url = package_data['downloads']['client']['url'] 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) f.write(requests.get(client_jar_url).content)
def get_burger_data_for_version(version_id: str): def get_server_jar(version_id: str):
if not os.path.exists(f'downloads/burger-{version_id}.json'): if not os.path.exists(get_dir_location(f'downloads/server-{version_id}.jar')):
get_burger() package_data = get_version_data(version_id)
get_client_jar(version_id) print('\033[92mDownloading server jar...\033[m')
server_jar_url = package_data['downloads']['server']['url']
os.system( with open(get_dir_location(f'downloads/server-{version_id}.jar'), 'wb') as f:
f'cd downloads/Burger && python munch.py ../client-{version_id}.jar --output ../burger-{version_id}.json' f.write(requests.get(server_jar_url).content)
)
with open(f'downloads/burger-{version_id}.json', 'r') as f:
return json.load(f)
def get_mappings_for_version(version_id: str): 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) package_data = get_version_data(version_id)
client_mappings_url = package_data['downloads']['client_mappings']['url'] client_mappings_url = package_data['downloads']['client_mappings']['url']
mappings_text = requests.get(client_mappings_url).text 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) f.write(mappings_text)
else: 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() mappings_text = f.read()
return Mappings.parse(mappings_text) return Mappings.parse(mappings_text)

44
codegen/lib/extract.py Normal file
View 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)

View file

@ -1,4 +1,5 @@
import re import re
import os
# utilities that could be used for things other than codegen # 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): def to_camel_case(name: str):
s = re.sub('_([a-z])', lambda m: m.group(1).upper(), name) 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): def padded_hex(n: int):
return f'0x{n:02x}' return f'0x{n:02x}'
@ -44,3 +52,7 @@ def group_packets(packets: list[PacketIdentifier]):
packet_groups[key] = [] packet_groups[key] = []
packet_groups[key].append(packet.packet_id) packet_groups[key].append(packet.packet_id)
return packet_groups return packet_groups
def get_dir_location(name: str):
return os.path.join(os.path.dirname(__file__), '..', name)

View file

@ -4,17 +4,18 @@ import lib.code.utils
import lib.code.version import lib.code.version
import lib.code.packet import lib.code.packet
import lib.download import lib.download
import lib.extract
import sys import sys
import os import os
old_version_id = lib.code.version.get_version_id() old_version_id = lib.code.version.get_version_id()
old_mappings = lib.download.get_mappings_for_version(old_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()) old_packet_list = list(old_burger_data[0]['packets']['packet'].values())
new_version_id = sys.argv[1] new_version_id = sys.argv[1]
new_mappings = lib.download.get_mappings_for_version(new_version_id) 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()) new_packet_list = list(new_burger_data[0]['packets']['packet'].values())

View file

@ -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 sys
import os
mappings = download.get_mappings_for_version('1.18.2') version_id = lib.code.version.get_version_id()
burger_data = download.get_burger_data_for_version('1.18.2')
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'] burger_packets_data = burger_data[0]['packets']['packet']
packet_id, direction, state = int(sys.argv[1]), sys.argv[2], sys.argv[3] packet_id, direction, state = int(sys.argv[1]), sys.argv[2], sys.argv[3]
print( print(
f'Generating code for packet id: {packet_id} with direction {direction} and state {state}') 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) packet_id, direction, state)
code.fmt() lib.code.utils.fmt()
print('Done!') print('Done!')

View file

@ -14,7 +14,7 @@ loop {
pathfinder::Goals::NearXZ(5, azalea::BlockXZ(0, 0)) pathfinder::Goals::NearXZ(5, azalea::BlockXZ(0, 0))
).await; ).await;
let chest = bot.open_chest(&bot.world.find_one_block(|b| b.id == "minecraft:chest")).await.unwrap(); 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 // when rust adds async drop this won't be necessary
chest.close().await; chest.close().await;