Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/assets/build/downloading.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,21 @@ pub fn generate() {
// Gotta use file locking cos nexttest running multiple builds at doesn't play nice
let _lock = lock_generation(assets_dir.join(".generate.lock"));

if !generated_assets_exist(&assets_dir) {
let version_changed = if assets_dir.join("version").exists()
&& std::fs::read_to_string(assets_dir.join("version"))
.expect("Failed to read version file")
== crate::SERVER_VERSION
{
info!("Server version unchanged, skipping asset generation");
false
} else {
info!("Server version changed, regenerating assets");
std::fs::remove_dir_all(&assets_dir).expect("Failed to remove old generated assets");
std::fs::create_dir(&assets_dir).expect("Failed to create generated assets directory");
true
};

if !generated_assets_exist(&assets_dir) || version_changed {
let server_jar_path = assets_dir.join("server.jar");
if !server_jar_path.exists() {
download_jar(server_jar_path);
Expand Down Expand Up @@ -57,6 +71,8 @@ pub fn generate() {
include_str!("notice.txt"),
)
.expect("Could not write notice file");
std::fs::write(assets_dir.join("version"), crate::SERVER_VERSION)
.expect("Could not write version file");
} else {
println!("cargo:error=Setup failed");
}
Expand Down
5 changes: 5 additions & 0 deletions src/assets/build/generate_source.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::item_to_block_mapping::write_item_to_block_mapping;
use crate::registry_packets::write_registry_packets;
use crate::tag_packets::write_tag_packets;
use std::collections::BTreeMap;
use std::env;
use std::fs;
Expand All @@ -12,6 +13,7 @@ pub fn generate_source(assets_path: PathBuf) {
let blockstates_path = write_blockstates(&out_dir, &reports_dir);
let item_to_block_mapping_path = write_item_to_block_mapping(&out_dir, &reports_dir);
let registry_packets_path = write_registry_packets(&out_dir, &data_dir);
let tag_packets_path = write_tag_packets(&out_dir, &assets_path, &data_dir);

let mut content = String::new();
content.push_str("pub const BLOCKSTATES: &str = include_str!(");
Expand All @@ -26,6 +28,9 @@ pub fn generate_source(assets_path: PathBuf) {
content.push_str("pub const REGISTRY_PACKETS: &str = include_str!(");
content.push_str(&format!("{:?}", registry_packets_path.to_string_lossy()));
content.push_str(");\n\n");
content.push_str("pub const TAG_PACKETS: &str = include_str!(");
content.push_str(&format!("{:?}", tag_packets_path.to_string_lossy()));
content.push_str(");\n\n");
content.push_str("pub mod reports {\n");
write_dir(&mut content, &reports_dir, 1);
content.push_str("}\n");
Expand Down
3 changes: 3 additions & 0 deletions src/assets/build/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ mod downloading;
mod generate_source;
mod item_to_block_mapping;
mod registry_packets;
mod tag_packets;

use semver::Version;
use std::path::PathBuf;

const MIN_JAVA_VERSION: Version = Version::new(21, 0, 0);

const SERVER_VERSION: &str = "1.21.8";

const NO_JAVA_MESSAGE: &str = "No java install detected. If you have installed one, please add to your path. If you haven't,\
the Adoptium JDK is recommended and you can get it here: https://adoptium.net/temurin/releases?version=25. If you go with another\
JDK you'll need to make sure it supports at least version 21, but higher is better.";
Expand Down
6 changes: 6 additions & 0 deletions src/assets/build/registry_packets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ const REGISTRY_PACKET_IDS: &[&str] = &[
"minecraft:dimension_type",
"minecraft:damage_type",
"minecraft:banner_pattern",
"minecraft:enchantment",
"minecraft:jukebox_song",
"minecraft:instrument",
"minecraft:test_environment",
"minecraft:test_instance",
"minecraft:dialog",
];

pub fn write_registry_packets(out_dir: &Path, data_dir: &Path) -> PathBuf {
Expand Down
187 changes: 187 additions & 0 deletions src/assets/build/tag_packets.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
use serde_json::Value;
use std::collections::BTreeMap;
use std::fs;
use std::path::{Path, PathBuf};

type RegistryIds = BTreeMap<String, i32>;
type TagPackets = BTreeMap<String, BTreeMap<String, Vec<i32>>>;

pub fn write_tag_packets(out_dir: &Path, assets_dir: &Path, data_dir: &Path) -> PathBuf {
let extracted_dir = assets_dir
.parent()
.expect("Assets directory should have a parent")
.join("extracted");
let tags_path = extracted_dir.join("tags.json");

println!("cargo:rerun-if-changed={}", tags_path.display());

let tags: BTreeMap<String, BTreeMap<String, Vec<String>>> =
serde_json::from_str(&fs::read_to_string(&tags_path).expect("Failed to read tags.json"))
.expect("Failed to parse tags.json");

let report_registry_ids = read_report_registry_ids(assets_dir);
let extracted_registry_ids = read_extracted_registry_ids(&extracted_dir);

let mut tag_packets = TagPackets::new();
for (tag_registry, tag_set) in tags {
let registry_id = format!("minecraft:{tag_registry}");
let registry_ids = report_registry_ids
.get(&registry_id)
.cloned()
.or_else(|| extracted_registry_ids.get(&registry_id).cloned())
.unwrap_or_else(|| read_data_registry_ids(data_dir, &registry_id));

let mut packet_tags = BTreeMap::new();
for (tag_name, values) in tag_set {
let values = values
.iter()
.map(|value| {
let value = namespaced(value);
*registry_ids.get(&value).unwrap_or_else(|| {
panic!("No protocol id for tag value {value} in {registry_id}")
})
})
.collect();

packet_tags.insert(tag_name, values);
}

tag_packets.insert(registry_id, packet_tags);
}

let path = out_dir.join("tag_packets.json");
let content = serde_json::to_string(&tag_packets).expect("Failed to serialize tag packets");
fs::write(&path, content).expect("Failed to write tag packets");
path
}

fn read_report_registry_ids(assets_dir: &Path) -> BTreeMap<String, RegistryIds> {
let path = assets_dir
.join("generated")
.join("reports")
.join("registries.json");
let registries: Value =
serde_json::from_str(&fs::read_to_string(path).expect("Failed to read registries report"))
.expect("Failed to parse registries report");

let mut ids = BTreeMap::new();
let Some(registries) = registries.as_object() else {
return ids;
};

for (registry_id, registry) in registries {
let Some(entries) = registry.get("entries").and_then(Value::as_object) else {
continue;
};

let mut registry_ids = RegistryIds::new();
for (entry_id, entry) in entries {
let protocol_id = entry
.get("protocol_id")
.and_then(Value::as_i64)
.expect("Registry report entry missing protocol_id");
registry_ids.insert(namespaced(entry_id), protocol_id as i32);
}

ids.insert(registry_id.clone(), registry_ids);
}

ids
}

fn read_extracted_registry_ids(extracted_dir: &Path) -> BTreeMap<String, RegistryIds> {
let mut ids = BTreeMap::new();

read_extracted_ids(
&mut ids,
"minecraft:enchantment",
&extracted_dir.join("enchantments.json"),
);
read_extracted_ids(
&mut ids,
"minecraft:entity_type",
&extracted_dir.join("entities.json"),
);

ids
}

fn read_extracted_ids(ids: &mut BTreeMap<String, RegistryIds>, registry_id: &str, path: &Path) {
if !path.exists() {
return;
}

let entries: Value =
serde_json::from_str(&fs::read_to_string(path).expect("Failed to read extracted registry"))
.expect("Failed to parse extracted registry");
let Some(entries) = entries.as_object() else {
return;
};

let registry_ids = ids.entry(registry_id.to_string()).or_default();
for (entry_id, entry) in entries {
let Some(id) = entry.get("id").and_then(Value::as_i64) else {
continue;
};
registry_ids
.entry(namespaced(entry_id))
.or_insert(id as i32);
}
}

fn read_data_registry_ids(data_dir: &Path, registry_id: &str) -> RegistryIds {
let registry_dir = data_dir.join(registry_id.replacen(':', "/", 1));
assert!(
registry_dir.exists(),
"Generated registry data missing for {registry_id}"
);

read_registry_entries(&registry_dir, &registry_dir)
.into_keys()
.enumerate()
.map(|(id, entry)| (entry, id as i32))
.collect()
}

fn read_registry_entries(dir: &Path, root: &Path) -> BTreeMap<String, ()> {
let mut entries = fs::read_dir(dir)
.expect("Failed to read registry directory")
.collect::<Result<Vec<_>, _>>()
.expect("Failed to read registry directory entry");

entries.sort_by_key(|entry| entry.path());

let mut registry_entries = BTreeMap::new();
for entry in entries {
let path = entry.path();
if path.is_dir() {
registry_entries.extend(read_registry_entries(&path, root));
continue;
}

if path.extension().and_then(|extension| extension.to_str()) != Some("json") {
continue;
}

let id = path
.strip_prefix(root)
.expect("Registry entry should be inside registry root")
.with_extension("")
.components()
.map(|component| component.as_os_str().to_string_lossy())
.collect::<Vec<_>>()
.join("/");

registry_entries.insert(namespaced(&id), ());
}

registry_entries
}

fn namespaced(value: &str) -> String {
if value.contains(':') {
value.to_string()
} else {
format!("minecraft:{value}")
}
}
6 changes: 6 additions & 0 deletions src/base/macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ mod net;
mod profiling;
mod registries_packets;
mod static_loading;
mod update_tags;

#[proc_macro_attribute]
pub fn profile(attr: TokenStream, item: TokenStream) -> TokenStream {
Expand Down Expand Up @@ -76,6 +77,11 @@ pub fn build_registry_packets(input: TokenStream) -> TokenStream {
registries_packets::build_mapping(input)
}

#[proc_macro]
pub fn build_update_tags(input: TokenStream) -> TokenStream {
update_tags::build_mapping(input)
}

/// A macro to lookup block state IDs at compile time.
///
/// Feed in the block name as a string literal, and an optional set of properties as a map.
Expand Down
18 changes: 18 additions & 0 deletions src/base/macros/src/update_tags.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use quote::quote;
use std::collections::BTreeMap;

pub(crate) fn build_mapping(_: proc_macro::TokenStream) -> proc_macro::TokenStream {
let tag_packets: BTreeMap<String, BTreeMap<String, Vec<i32>>> =
serde_json::from_str(temper_assets::generated::TAG_PACKETS).unwrap();

let tag_packets = tag_packets
.into_iter()
.map(|(registry_id, tags)| (registry_id, tags.into_iter().collect::<Vec<_>>()))
.collect::<Vec<_>>();
let raw_packets_data = bitcode::encode(&tag_packets);

quote! {
vec![#(#raw_packets_data),*]
}
.into()
}
1 change: 1 addition & 0 deletions src/net/protocol/src/outgoing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ pub mod unload_chunk;
pub mod hurt_animation;
pub mod respawn;
pub mod set_health;
pub mod update_tags;
pub mod update_time;

pub mod synchronise_vehicle_position;
Expand Down
14 changes: 13 additions & 1 deletion src/net/protocol/src/outgoing/registry_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,24 @@ pub struct RegistryEntry {

#[cfg(test)]
mod tests {
use crate::outgoing::registry_data::RegistryEntry;
use crate::outgoing::registry_data::{REGISTRY_PACKETS, RegistryEntry};
use indexmap::IndexMap;
use serde_json::Value;
use std::io::Write;
use temper_codec::net_types::prefixed_optional::PrefixedOptional;

#[test]
fn includes_tag_dependent_synced_registries() {
let registry_ids = REGISTRY_PACKETS
.iter()
.map(|packet| packet.registry_id.as_str())
.collect::<Vec<_>>();

assert!(registry_ids.contains(&"minecraft:enchantment"));
assert!(registry_ids.contains(&"minecraft:instrument"));
assert!(registry_ids.contains(&"minecraft:dialog"));
}

#[test]
#[ignore]
fn generate_nbt() {
Expand Down
Loading
Loading