From 9da38d9c6383e2b6ebd1f2da806a9cbdfb4f2e22 Mon Sep 17 00:00:00 2001 From: ReCore Date: Mon, 23 Mar 2026 20:29:18 +1030 Subject: [PATCH 1/9] Move login chunk sending to the ECS --- src/game_systems/src/player/src/lib.rs | 5 +- .../src/player/src/new_connections.rs | 6 +- src/net/runtime/src/conn_init/login.rs | 77 ++----------------- 3 files changed, 13 insertions(+), 75 deletions(-) diff --git a/src/game_systems/src/player/src/lib.rs b/src/game_systems/src/player/src/lib.rs index f2f2e894..1fc23b84 100644 --- a/src/game_systems/src/player/src/lib.rs +++ b/src/game_systems/src/player/src/lib.rs @@ -18,7 +18,6 @@ mod send_inventory_updates; pub mod update_player_ping; pub fn register_player_systems(schedule: &mut bevy_ecs::schedule::Schedule) { - schedule.add_systems(chunk_calculator::handle); schedule.add_systems(digging_system::handle_start_digging); schedule.add_systems(digging_system::handle_finish_digging); schedule.add_systems(digging_system::handle_start_digging); @@ -31,11 +30,13 @@ pub fn register_player_systems(schedule: &mut bevy_ecs::schedule::Schedule) { // Player connection handling - chained to ensure proper event timing: // 1. accept_new_connections: Spawns entity + adds PendingPlayerJoin marker (deferred) // 2. ApplyDeferred: Flushes commands, entity now exists and is queryable - // 3. emit_player_joined: Fires PlayerJoined event (listeners can now query the entity) + // 3. chunk_calculator::handle: Starts sending chunks to the player immediately after they join + // 4. emit_player_joined: Fires PlayerJoined event (listeners can now query the entity) schedule.add_systems( ( new_connections::accept_new_connections, ApplyDeferred, + chunk_calculator::handle, emit_player_joined::emit_player_joined, ) .chain(), diff --git a/src/game_systems/src/player/src/new_connections.rs b/src/game_systems/src/player/src/new_connections.rs index 62b81d0f..c7931e07 100644 --- a/src/game_systems/src/player/src/new_connections.rs +++ b/src/game_systems/src/player/src/new_connections.rs @@ -1,4 +1,4 @@ -use bevy_ecs::prelude::{Commands, Res}; +use bevy_ecs::prelude::{Commands, MessageWriter, Res}; use std::time::Instant; use temper_components::bounds::CollisionBounds; use temper_components::player::chunk_receiver::ChunkReceiver; @@ -11,6 +11,7 @@ use temper_components::player::{ swimming::SwimmingState, }; use temper_inventories::hotbar::Hotbar; +use temper_messages::chunk_calc::ChunkCalc; use temper_net_runtime::connection::DisconnectHandle; use temper_resources::new_conn::NewConnectionRecv; use temper_state::GlobalStateResource; @@ -20,6 +21,7 @@ pub fn accept_new_connections( mut cmd: Commands, new_connections: Res, state: Res, + mut chunk_update_writer: MessageWriter, ) { if new_connections.0.is_empty() { return; @@ -106,6 +108,8 @@ pub fn accept_new_connections( ), ); + chunk_update_writer.write(ChunkCalc(entity_id)); + info!( "Player {} connected ({:?})", new_connection.player_identity.username, new_connection.player_identity.uuid diff --git a/src/net/runtime/src/conn_init/login.rs b/src/net/runtime/src/conn_init/login.rs index 106de962..8066a853 100644 --- a/src/net/runtime/src/conn_init/login.rs +++ b/src/net/runtime/src/conn_init/login.rs @@ -1,31 +1,25 @@ use crate::auth::authenticate_user; -use crate::compression::compress_packet; use crate::conn_init::VarInt; use crate::conn_init::{LoginResult, NetDecodeOpts}; use crate::connection::StreamWriter; use temper_codec::decode::NetDecode; -use temper_codec::encode::NetEncodeOpts; use temper_codec::net_types::length_prefixed_vec::LengthPrefixedVec; use temper_codec::net_types::prefixed_optional::PrefixedOptional; -use temper_config::server_config::{ServerConfig, get_global_config}; +use temper_config::server_config::{get_global_config, ServerConfig}; use temper_encryption::errors::NetEncryptionError; use temper_encryption::get_encryption_keys; use temper_encryption::read::EncryptedReader; use temper_macros::lookup_packet; -use temper_protocol::ConnState::*; use temper_protocol::incoming::packet_skeleton::PacketSkeleton; use temper_protocol::outgoing::login_success::{LoginSuccessPacket, LoginSuccessProperties}; use temper_protocol::outgoing::set_default_spawn_position::DEFAULT_SPAWN_POSITION; use temper_protocol::outgoing::{commands::CommandsPacket, registry_data::REGISTRY_PACKETS}; +use temper_protocol::ConnState::*; use temper_state::GlobalState; use rand::RngCore; use temper_components::player::offline_player_data::OfflinePlayerData; use temper_components::player::player_identity::{PlayerIdentity, PlayerProperty}; -use temper_components::player::position::Position; -use temper_core::dimension::Dimension; -use temper_core::pos::ChunkPos; -use temper_protocol::ConnState; use temper_protocol::errors::{NetAuthenticationError, NetError, PacketError}; use temper_protocol::incoming::ack_finish_configuration::AckFinishConfigurationPacket; use temper_protocol::incoming::client_information::ClientInformation; @@ -44,9 +38,9 @@ use temper_protocol::outgoing::game_event::GameEventPacket; use temper_protocol::outgoing::login_play::LoginPlayPacket; use temper_protocol::outgoing::player_abilities::PlayerAbilities; use temper_protocol::outgoing::player_info_update::PlayerInfoUpdatePacket; -use temper_protocol::outgoing::set_center_chunk::SetCenterChunk; use temper_protocol::outgoing::set_compression::SetCompressionPacket; use temper_protocol::outgoing::synchronize_player_position::SynchronizePlayerPositionPacket; +use temper_protocol::ConnState; use tokio::net::tcp::OwnedReadHalf; use tracing::{debug, error, trace}; use uuid::Uuid; @@ -420,61 +414,6 @@ fn send_player_info( Ok(()) } -/// Sends initial chunks to the player. -fn send_initial_chunks( - conn_write: &StreamWriter, - state: &GlobalState, - config: &ServerConfig, - client_view_distance: i8, - compressed: bool, - pos: Position, -) -> Result<(), NetError> { - // Send center chunk - conn_write.send_packet(SetCenterChunk::new(pos.x as i32 >> 4, pos.z as i32 >> 4))?; - - // Calculate render distance - let server_render_distance = config.chunk_render_distance as i32; - let client_view_distance = client_view_distance as i32; - let radius = server_render_distance.min(client_view_distance); - // Generate/load chunks in parallel - let mut batch = state.thread_pool.batch(); - - for rad_x in -radius..=radius { - for rad_z in -radius..=radius { - batch.execute({ - let state = state.clone(); - move || -> Result, NetError> { - let x = (pos.x as i32 >> 4) + rad_x; - let z = (pos.z as i32 >> 4) + rad_z; - let chunk = state.world.get_or_generate_chunk(ChunkPos::new(x, z), Dimension::Overworld).expect("Failed to load or generate chunk"); - let chunk_data = - temper_protocol::outgoing::chunk_and_light_data::ChunkAndLightData::from_chunk( - ChunkPos::new(x, z), - &chunk, - )?; - compress_packet(&chunk_data, compressed, &NetEncodeOpts::WithLength, 64) - } - }); - } - } - - // Send all chunks - for packet in batch.wait() { - match packet { - Ok(data) => conn_write.send_raw_packet(data)?, - Err(err) => { - error!("Failed to send chunk data: {:?}", err); - return Err(NetError::Misc(format!( - "Failed to send chunk data: {:?}", - err - ))); - } - } - } - - Ok(()) -} - /// Sends the command graph to the client. fn send_command_graph(conn_write: &StreamWriter) -> Result<(), NetError> { conn_write.send_packet(CommandsPacket::from_global_graph())?; @@ -565,18 +504,12 @@ pub(super) async fn login( }); // Phase 3: Play State Setup + + // TODO: at some point this should be moved to the ECS send_initial_play_packets(conn_write, &player_identity, &offline_data)?; sync_player_position(conn_read, conn_write, &offline_data, compressed).await?; send_player_info(conn_write, &player_identity)?; send_inventory_contents(conn_write, &offline_data)?; - send_initial_chunks( - conn_write, - &state, - config, - client_info.view_distance, - compressed, - offline_data.position.into(), - )?; send_command_graph(conn_write)?; // Login complete From 019a0be03723193cf287bc3ab5a06c92dc9c4a2a Mon Sep 17 00:00:00 2001 From: ReCore Date: Mon, 23 Mar 2026 20:48:03 +1030 Subject: [PATCH 2/9] Readme and contributing tweaks --- CODE_OF_CONDUCT.md | 7 ++- CONTRIBUTING.md | 131 ++++++++++++--------------------------------- Cargo.toml | 17 +++--- README.md | 65 +++------------------- 4 files changed, 51 insertions(+), 169 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index b71e335a..ceb585d2 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,4 +1,3 @@ - # Contributor Covenant Code of Conduct ## Our Pledge @@ -29,7 +28,7 @@ Examples of unacceptable behavior by participants include: * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a +* Other conduct that could reasonably be considered inappropriate in a professional setting ## Our Responsibilities @@ -40,8 +39,8 @@ response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, +that are not aligned to this Code of Conduct, or to temporarily or +permanently ban any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1e1d711e..90b2c417 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,122 +8,58 @@ Keep in mind that clippy, rustfmt and cargo-audit are enforced on CI, so make su 1. Make sure all tests and lints pass. PRs that don't pass CI will be rejected if your code is the cause of the failing tests/lints. -2. Make sure all needed files are also included and not using absolute paths. +2. Make sure all necessary files are also included and not using absolute paths. 3. Include a sufficient explanation of your PR. What is it adding/fixing, why does this feature need to be added/fixed, who have you discussed this with, etc. If these questions were answered in a conversation on this Discord, mention who you talked with and what consensus was reached. Unexplained PRs will rarely be accepted. 4. Check again that tests pass. -5. Check a 3rd time. -6. Check that Clippy passes with no issues. `cargo clippy --all-targets -- -Dwarnings` is used on CI. -7. Check that Rustfmt passes with no issues. `cargo fmt --all -- --check` is used on CI. -8. Check that Cargo-audit passes with no issues. `cargo audit` is used on CI. -9. Submit PR. +5. Check that Clippy passes with no issues. `cargo clippy --all-targets -- -Dwarnings` is used on CI. +6. Check that Rustfmt passes with no issues. `cargo fmt --all -- --check` is used on CI. +7. Check that Cargo-audit passes with no issues. `cargo audit` is used on CI. +8. Submit PR. -## Project specific guidelines - -Just some rules to try to keep the repo nice and organised - -### Branches - -#### `master` - -This branch is the main branch. This is where all PRs should be made to. This branch is the most up to -date and should only be merged into with completed features. - -#### `feature/feature-name` - -This branch is for developing a feature. Once the feature is complete, a PR should be -made to the master branch. This branch should be branched off of the master branch. - -#### `fix/fixed-thing` - -This branch is for fixing a bug. Once the bug is fixed, a PR should be made to the master -branch. This branch should be branched off of the master branch. - -#### `rework/refactored-thing` - -This branch is for refactoring code. Once the code is refactored, a PR should be made to the master branch. - -#### `housekeeping` - -This branch is for stuff relating to the repo itself. This could be updating the README, adding -new CI checks, etc. This branch should be branched off of the master branch. - -#### `docs` - -This branch is for updating the documentation. This branch should be branched off of the master branch. -This is used for stuff that doesn't actually modify the code, but the documentation. - -### Project Layout - -```text -+---.etc | Non-code files -+---.github | GitHub specific files -+---assets | Assets for the Readme -+---scripts | Scripts for the project, usually python or bash -+---src | Source code -| +---bin | The main binary that stitches everything together -| +---lib | The libraries that provide the business logic -| | +---adapters | Adapters and parsers for data formats -| | +---core | The core logic of the application -| | +---derive_macros | Derive macros. Split into directories for each macro -| | +---ecs | The ECS system -| | +---events | The event system -| | +---net | Networking code -| | +---plugins | Plugins interface -| | +---storage | Storage backend -| | +---utils | Utility functions -| | \---world | Code for interacting with the world -| \---tests | Unit tests -``` - -If you add a new directory, please add it to the above list along with its purpose. +> [!NOTE] +> There is a template you will be prompted to fill out when you create a PR. This is not a hard requirement, but is a +> good starting point for making sure you include all the necessary information in your PR. ### Code rules 1. Tests that only generate/dump data must be `#[ignore]`d. These tests are not useful for CI and should not be run. 2. No absolute paths. This will break the CI and make it harder to run the code on different machines. -3. Try to avoid just chaining `../` to get to the root of the project. This makes it harder to move files around and - work - out where a referenced file is. There is a `get_root_path()` function that can be used to get the root of the project - as a - PathBuf. -4. Don't be lazy and use `unwrap()`. If you are sure that a value will always be `Some`, use `expect()`. If you are not - sure, use `match` or `if let`. Please also have a more detailed `error!()` message if you are using `expect()`. -5. Avoid `.clone()`ing data. If you need to clone data, make sure that it is necessary and that the data is not too - large. - Cloning is ok however in sections of code that only need to run once and small performance hits are acceptable (eg, - loading config files, starting up the database). -6. New dependencies should be added to the workspace `Cargo.toml` file. This will make it easier to manage dependencies - and will make sure that all dependencies are of the same version. -7. If you are adding a new feature that warrants major separation, add it as a new crate and then include it in the +3. Try not to use `unwrap()`, do proper error handling. If at all possible, try to use `expect()` in place of `unwrap()` + so that if the code does panic, it will be easier to find the source of the panic. If you are sure that the code will + never panic, you can use `unwrap()`, but it is generally better to use `expect()` with a message explaining why the + code will never panic. +4. New dependencies should be added to the workspace `Cargo.toml` file. This will make it easier to manage dependencies + and will make sure that all dependencies are of the same version, preventing dependencies being compiled multiple + times due to version mismatches. +5. If you are adding a new feature that warrants major separation, add it as a new crate and then include it in the workspace `Cargo.toml` file. This will make it easier to manage the code and will make sure that the code is well separated. -8. If you are adding an extra sub-crate, you must create a new set of `thiserror` based error types for that crate. This - will make it easier to understand where an error is coming from and will make it easier to handle errors. -9. Use `cargo clippy` to check for any issues with the code. This will be checked in CI and will cause the build to fail +6. Use `cargo clippy` to check for any issues with the code. This will be checked in CI and will cause the build to fail if there are any issues. There is no excuse for *your* code to fail the lints. -10. Use `cargo fmt` to format the code. This will be checked in CI and will cause the build to fail if the code is not - formatted correctly. There is no excuse for *your* code to fail the formatting. -11. Use `#[expect(lint)]` instead of `#[allow(lint)]` if you are sure that the lint is not an issue. This will make it - easier to find and remove these lints in the future. -12. Use `#[cfg(test)]` to only include code in tests. This will make the code easier to read and understand. -13. Where applicable, add doc strings to functions and modules. This will make it easier for others to understand the +7. Use `cargo fmt` to format the code. This will be checked in CI and will cause the build to fail if the code is not + formatted correctly. There is no excuse for *your* code to fail the formatting. +8. Use `#[expect(lint)]` instead of `#[allow(lint)]` if you are sure that the lint is not an issue. This will make it + easier to find and remove these lints in the future. +9. Use `#[cfg(test)]` to only include code in tests. This will make the code easier to read and understand. +10. Where applicable, add doc strings to functions and modules. This will make it easier for others to understand the code. Check https://doc.rust-lang.org/nightly/rustdoc/how-to-write-documentation.html for more information on how to write good documentation. -14. Unsafe code is ok as long as it is well documented and the reason for the unsafe code is explained. If you are not +11. Unsafe code is ok as long as it is well-documented, and the reason for the unsafe code is explained. If you are not sure if the code is safe, ask in the Discord. -15. Limit the use of raw instructions as much as possible. This will make the code easier to read and understand. There - are some cases where raw instructions are needed, but these should be kept to a minimum. -16. You will be asked to fix your PR if folders like `.vscode` or `.idea` are included in the PR. These folders are +12. You will be asked to fix your PR if folders like `.vscode` or `.idea` are included in the PR. These folders are specific to your IDE and should not be included in the PR. -17. If you are adding a new feature, make sure to add tests for it. This will make sure that the feature works as +13. If you are adding a new feature, make sure to add tests for it. This will make sure that the feature works as expected and will help prevent regressions in the future. -18. If you are fixing a bug, make sure to add a test that reproduces the bug. This will make sure that the bug is fixed +14. If you are fixing a bug, make sure to add a test that reproduces the bug. This will make sure that the bug is fixed and will help prevent regressions in the future. -19. If your code isn't sufficiently documented, you will be asked to add documentation. -20. If your code doesn't have tests where it should, you will be asked to add tests. +15. If your code isn't sufficiently documented, you will be asked to add documentation. +16. If your code doesn't have tests where it should, you will be asked to add tests. +17. Please don't submit massive PRs with 80k changed lines, you will be asked to split these into smaller PRs. It's an + absolute nightmare to review and verify massive PRs, so please try to keep your PRs small and focused on a single + feature or bug fix. ## Notes on formatting @@ -142,8 +78,7 @@ performance hit. Automatic formatting is highly recommended as it will ensure that the code you write is correctly formatted as you go, instead of running `cargo clippy` when you are done and having 400 clippy errors to fix at once. You should still run the clippy and fmt commands before submitting a PR to make sure that the code is correctly formatted and passes the -lints, -but automatic formatting will help to catch most of these issues as you go. +lints. However, automatic formatting will help to catch most of these issues as you go. ## Code of Conduct diff --git a/Cargo.toml b/Cargo.toml index e4e4d5b0..254b725b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,13 +30,12 @@ members = [ "src/entities", "src/game_systems", "src/game_systems/src/background", - "src/game_systems/src/mobs", "src/game_systems/src/interactions", + "src/game_systems/src/mobs", "src/game_systems/src/packets", "src/game_systems/src/physics", "src/game_systems/src/player", "src/game_systems/src/world", - "src/world/block-placing", "src/inventories", "src/messages", "src/net/codec", @@ -54,6 +53,7 @@ members = [ "src/text", "src/utils", "src/world", + "src/world/block-placing", "src/world/db", "src/world/format", "src/world/gen", @@ -165,7 +165,7 @@ tokio = { version = "1.50.0", features = [ # Logging tracing = "0.1.44" -tracing-subscriber = { version = "0.3.22", features = ["env-filter"] } +tracing-subscriber = { version = "0.3.23", features = ["env-filter"] } tracing-appender = "0.2.4" tracing-tracy = { version = "0.11.4", features = ["timer-fallback", "ondemand", "fibers", "context-switch-tracing", "delayed-init"] } log = "0.4.29" @@ -239,7 +239,7 @@ yazi = "0.2.1" flate2 = "1.1.9" # Database -heed = "0.22.1-nested-rtxns-6" +heed = "0.22.1-nested-rtxns-7" # Misc deepsize = "0.2.0" @@ -252,7 +252,7 @@ num_cpus = "1.17.0" typename = "0.1.2" bevy_ecs = { version = "0.18.1", features = ["multi_threaded", "trace", "debug"], default-features = false } bevy_math = "0.18.1" -once_cell = "1.21.3" +once_cell = "1.21.4" mime_guess = "2.0.5" ## TUI/CLI @@ -261,7 +261,7 @@ ratatui-core = "0.1.0" tui-input = "0.15.0" ratatui = "0.30.0" tui-logger = { version = "0.18.1", features = ["tracing-support", "crossterm"] } -clap = { version = "4.5.60", features = ["derive", "env"] } +clap = { version = "4.6.0", features = ["derive", "env"] } indicatif = "0.18.4" colored = "3.1.1" unicode-width = "0.2.2" @@ -269,7 +269,7 @@ heck = "0.5.0" # I/O memmap2 = "0.9.10" -tempfile = "3.26.0" +tempfile = "3.27.0" walkdir = "2.5.0" include_dir = "0.7.4" @@ -283,8 +283,9 @@ phf_codegen = { version = "0.13.1" } axum = { version = "0.8.8", features = ["tokio", "ws"] } # Stats -sysinfo = { version = "0.38.3", default-features = false, features = ["system"] } +sysinfo = { version = "0.38.4", default-features = false, features = ["system"] } dir-size = "0.1.1" +# Compression is a real bottleneck that we can do little about, so compiling it with optimizations is needed in dev mode. [profile.dev.package.yazi] opt-level = 3 diff --git a/README.md b/README.md index ba2d50d9..22d45b92 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,8 @@ ## About Temper is a Minecraft server implementation written in Rust, with the goals of being extremely fast and memory -efficient, while also being easy to use and set up. With a focus on community, extensibility and performance, we hope to +efficient, while also being easy to use and set up. With a focus on community, extensibility, and performance, we hope +to create a server that can be used by everyone from casual players to large server owners. Originally a fork of the FerrumC project, Temper aims to supersede its predecessor by providing a more polished and @@ -28,7 +29,7 @@ committed to building a strong community around Temper and welcome contributions ## Key Features and goals -- **High Performance**: Temper is built with performance in mind, utilizing Rust's powerful features and optimizations +- **High Performance**: Temper is built with performance in mind, using Rust's powerful features and optimizations to offer the best possible performance. - **Memory Efficiency**: Temper is designed to use as little memory as possible, making it suitable for servers of all sizes. @@ -42,7 +43,8 @@ committed to building a strong community around Temper and welcome contributions prioritize fixing bugs and improving stability over adding new features, and we will always strive to maintain a high standard of quality in our codebase. - **Maintainable Codebase**: We want to maintain a clean and well-organized codebase that is easy to understand and - contribute to. We will follow best practices for code organization and documentation, and we will strive to make our + contribute to. We will follow the best practices for code organization and documentation, and we will strive to make + our code as readable and maintainable as possible. We will also prioritize code reviews and testing to ensure that our codebase remains healthy and maintainable over time. @@ -52,7 +54,7 @@ committed to building a strong community around Temper and welcome contributions While it is recommended to compile from source for the best performance and latest features, we understand that not everyone may be comfortable with that process. Therefore, we provide pre-compiled binaries for Windows, Linux, and macOS -on our Github Actions. +on our GitHub Actions. At a later date we hope to make this easier by providing downloads on the Releases page, but for now you can follow the instructions below to download the latest pre-compiled binary. @@ -125,61 +127,6 @@ We welcome contributions! If you'd like to contribute to Temper, please follow t The Discord server is where most of the development discussion happens, so feel free to join and ask any questions you may have or discuss your ideas with the community. -## FAQ - -### How does this project differ from: - -- **Paper/Spigot/Bukkit**: These projects are the cornerstone of the Minecraft server ecosystem, and they have been - around for a long time. However, they are all written in Java and are based on the vanilla server codebase, which is - not very performant and has a lot of technical debt. We are taking a different approach by writing the server from - scratch in Rust, which allows us to take advantage of Rust's performance and safety features to create a much more - efficient and maintainable server implementation. -- **Pumpkin**: Pumpkin's goal is to simply reimplement the vanilla server in Rust, but they are taking the approach of - just porting decompiled Java code to Rust. While easier to implement, this approach is not very maintainable and will - likely lead to a lot of technical debt and performance issues. We are taking a more holistic approach to the server - implementation, which lets the strengths of Rust shine through and allows us to make design decisions that are best - for the project rather than just porting a 17-year-old codebase. -- **FerrumC**: FerrumC is our predecessor and the project that Temper was originally forked from. While we share the - same goals of performance and efficiency, Temper aims to shoot above and beyond what FerrumC was able to achieve by - focusing on open source values, community involvement, and a more polished and user-friendly experience. We are - committed to building a strong community around Temper and welcome contributions from developers of all skill levels, - while also maintaining a high standard of quality and stability in our codebase. - -### Will we be implementing terrain generation? - -Yes! We do have a somewhat simplistic terrain generator implemented right now for demonstration purposes, but we do plan -to implement a more robust and feature-rich terrain generator in the future. Whether that will end up being 1:1 with the -vanilla generator or something custom is still up in the air, but we are open to suggestions and ideas from the -community on how to best implement this. - -### Will there be plugins? And how? - -We do very much plan to have a plugin system and as of right now we are planning to use -some kind of FFI (foreign function interface) to allow for plugins to be written in Rust. Plugins are not our top -priority -right now, and we want to make sure the API is designed well before we start implementing it to avoid breaking changes -later. -We are open to suggestions and ideas from the community on how to best implement this. - -### Will I be able to use plugins or mods from paper/spigot/bukkit/forge/fabric etc.? - -No. Even if we did implement a perfect 1:1 API match for the vanilla server, the underlying implementation is still -completely different. -Java plugins and mods rely heavily on Java features such as reflection and dynamic class loading, which simply aren't -possible in Rust. -If we made a Java translation layer, it would be extremely slow and only the most basic plugins and mods would work. If -a plugin -or mod is basic enough to work through a translation layer, it would be much better to just rewrite it in Rust for -performance -and compatibility reasons. - -### Why did we fork from FerrumC? - -We decided to carry on the grand tradition of forking a Minecraft server implementation over management disagreements. -In all seriousness, we had some differences in vision and approach to the project, and we felt that it would be best to -start fresh with a new repository and a new name. We are grateful for the work that was done on FerrumC, and we hope to -build on that foundation while also taking the project in a new direction that we are excited about. - ## License FerrumC was licensed under the MIT License, but Temper has moved to the GNU General Public License v3.0 (GPL-3.0) to From fa82f611d868e35fe5af086ce3e73c3e5df4d751 Mon Sep 17 00:00:00 2001 From: ReCore Date: Mon, 23 Mar 2026 20:52:35 +1030 Subject: [PATCH 3/9] Update workflow to not run fmt/clippy on draft prs --- .github/workflows/rust.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index b4cc2230..3965613e 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -12,9 +12,11 @@ concurrency: env: CARGO_TERM_COLOR: always + defaults: run: shell: bash + jobs: formatting_and_security: name: Formatting and Security @@ -22,21 +24,29 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v5 + - name: Install Rust stable uses: dtolnay/rust-toolchain@stable with: components: rustfmt, clippy + - uses: Swatinem/rust-cache@v2 + - name: Run cargo fmt + if: github.event_name != 'pull_request' || !github.event.pull_request.draft run: cargo fmt --all -- --check + - name: Run Clippy run: cargo clippy --all-targets -- -D warnings + - name: Install cargo-audit uses: taiki-e/install-action@v2 with: tool: cargo-audit + - name: Run Cargo Audit run: cargo audit --ignore RUSTSEC-2023-0071 + build: name: Build and Upload Artifacts if: github.ref == 'refs/heads/master' @@ -88,14 +98,18 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v5 + - name: Install Rust stable uses: dtolnay/rust-toolchain@stable with: targets: ${{ matrix.target }} + - uses: Swatinem/rust-cache@v2 + - name: Install cargo-nextest uses: taiki-e/install-action@v2 with: tool: cargo-nextest + - name: Run Tests - run: cargo nextest run --target ${{ matrix.target }} --all-targets --all-features -E "not kind(bench)" + run: cargo nextest run --target ${{ matrix.target }} --all-targets --all-features -E "not kind(bench)" \ No newline at end of file From 8d86ac21cae73cbe614246bf98f9a6656d65313b Mon Sep 17 00:00:00 2001 From: ReCore Date: Mon, 23 Mar 2026 21:01:05 +1030 Subject: [PATCH 4/9] Added buymeacoffee link, updated issue templates --- .github/FUNDING.yml | 2 +- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .github/ISSUE_TEMPLATE/feature_request.yml | 33 ++++------------------ 3 files changed, 8 insertions(+), 29 deletions(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index ad8b6b58..c19d67dd 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -10,6 +10,6 @@ liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry polar: # Replace with a single Polar username -buy_me_a_coffee: # ferrumc +buy_me_a_coffee: tempermc thanks_dev: # Replace with a single thanks.dev username custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 2c31b53a..2b6740bc 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -4,7 +4,7 @@ labels: [ bug, unconfirmed ] body: - type: markdown attributes: - value: Bug reports should only be used for reporting issues with how the server works / its features. For general questions, please use our [Discord Server](https://discord.gg/qT5J8EMjwk) instead. + value: Bug reports should only be used for reporting issues with how the server works / its features. For general questions, please use our [Discord Server](https://discord.gg/6QPZgUy4sA) instead. - type: textarea attributes: diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 21c228c3..94e6e164 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -4,44 +4,23 @@ labels: enhancement body: - type: markdown attributes: - value: This should only be used for new ideas / features. For general questions, please use our [Discord Server](https://discord.gg/qT5J8EMjwk) instead. + value: This should only be used for new ideas / features. For general questions, please use our [Discord Server](https://discord.gg/6QPZgUy4sA) instead. - type: textarea attributes: - label: Is your feature request related to a problem? - description: "A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] Link the GitHub issue if applicable." + label: Description of the feature + description: "Please provide a clear and concise description of the feature you are requesting. Include any relevant details or use cases that can help us understand the value and purpose of this feature." validations: required: true - - type: textarea - attributes: - label: "Describe the solution / feature you'd like." - description: "A clear and concise description of what feature / solution you would like to be implemented." - validations: - required: true - - - type: textarea - attributes: - label: "Alternatives you've considered." - description: "A clear and concise description of any alternative solutions / features you've considered." - validations: - required: true - - - type: textarea - attributes: - label: "Additional Context" - description: "Add any other context or screenshots about the feature request here." - validations: - required: false - - type: checkboxes attributes: - label: I have confimed that... + label: I have confirmed that... options: - label: ... such a feature does not exist already. - required: true + required: false - label: ... I ticked all the boxes without reading them required: false - label: ... such a feature request has not been submitted already. - required: true + required: false From 7e711fac734d0c986b80cd1b6546efc80553a841 Mon Sep 17 00:00:00 2001 From: ReCore Date: Mon, 23 Mar 2026 21:17:23 +1030 Subject: [PATCH 5/9] Made the block and item mappings use get_or_init() --- src/app/runtime/src/setup.rs | 12 ++++++++--- src/core/src/block_state_id.rs | 20 +++++++------------ .../interactions/src/block_interactions.rs | 12 +---------- src/world/block-placing/src/blocks/door.rs | 7 ------- src/world/block-placing/src/blocks/fence.rs | 7 +------ src/world/block-placing/src/blocks/slab.rs | 7 ------- src/world/block-placing/src/lib.rs | 7 ++++--- 7 files changed, 22 insertions(+), 50 deletions(-) diff --git a/src/app/runtime/src/setup.rs b/src/app/runtime/src/setup.rs index 20d1fdc6..285a65ca 100644 --- a/src/app/runtime/src/setup.rs +++ b/src/app/runtime/src/setup.rs @@ -4,7 +4,9 @@ use crate::errors::BinaryError; use std::time::Instant; use temper_components::player::offline_player_data::OfflinePlayerData; use temper_config::server_config::get_global_config; -use temper_core::block_state_id::{init_block_mappings, init_item_to_block_mapping}; +use temper_core::block_state_id::{ + create_block_mappings, create_item_to_block_mapping, BLOCK2ID, ID2BLOCK, ITEM_TO_BLOCK_MAPPING, +}; use temper_core::dimension::Dimension; use temper_core::pos::ChunkPos; use temper_state::GlobalState; @@ -75,6 +77,10 @@ pub fn setup_db(state: GlobalState) -> Result<(), BinaryError> { } pub fn setup_block_and_item_mapping() { - init_item_to_block_mapping(); - init_block_mappings(); + ITEM_TO_BLOCK_MAPPING + .set(create_item_to_block_mapping()) + .ok(); + let (id2block, block2id) = create_block_mappings(); + ID2BLOCK.set(id2block).ok(); + BLOCK2ID.set(block2id).ok(); } diff --git a/src/core/src/block_state_id.rs b/src/core/src/block_state_id.rs index 72f4ff3f..88731fa1 100644 --- a/src/core/src/block_state_id.rs +++ b/src/core/src/block_state_id.rs @@ -20,7 +20,7 @@ const BLOCKSFILE: &str = include_str!("../../../assets/data/blockstates.json"); pub static ID2BLOCK: OnceCell> = OnceCell::new(); pub static BLOCK2ID: OnceCell> = OnceCell::new(); -pub fn init_block_mappings() { +pub fn create_block_mappings() -> (Vec, HashMap) { let string_keys: HashMap = serde_json::from_str(BLOCKSFILE).unwrap(); if string_keys.len() != BLOCK_ENTRIES { @@ -45,8 +45,7 @@ pub fn init_block_mappings() { .enumerate() .map(|(k, v)| (v.clone(), k as i32)) .collect(); - ID2BLOCK.set(id2block).expect("Failed to set ID2BLOCK"); - BLOCK2ID.set(block2id).expect("Failed to set BLOCK2ID"); + (id2block, block2id) } /// An ID for a block, and it's state in the world. Use this over `BlockData` unless you need to @@ -67,8 +66,7 @@ impl BlockStateId { /// Given a BlockData, return a BlockStateId. Does not clone, should be quite fast. pub fn from_block_data(block_data: &BlockData) -> Self { let id = BLOCK2ID - .get() - .expect("Mappings not initialized") + .get_or_init(|| create_block_mappings().1) .get(block_data) .copied() .unwrap_or_else(|| { @@ -82,8 +80,7 @@ impl BlockStateId { /// If the ID is not found, returns None. pub fn to_block_data(&self) -> Option { ID2BLOCK - .get() - .expect("Mappings not initialized") + .get_or_init(|| create_block_mappings().0) .get(self.0 as usize) .cloned() } @@ -169,10 +166,10 @@ const ITEM_TO_BLOCK_MAPPING_FILE: &str = include_str!("../../../assets/data/item_to_block_mapping.json"); pub static ITEM_TO_BLOCK_MAPPING: OnceCell> = OnceCell::new(); -pub fn init_item_to_block_mapping() { +pub fn create_item_to_block_mapping() -> HashMap { let str_form: HashMap = serde_json::from_str(ITEM_TO_BLOCK_MAPPING_FILE) .expect("Failed to parse item_to_block_mapping.json"); - let res = str_form + str_form .into_iter() .map(|(k, v)| { ( @@ -180,8 +177,5 @@ pub fn init_item_to_block_mapping() { BlockStateId::new(u32::from_str(&v).unwrap()), ) }) - .collect(); - ITEM_TO_BLOCK_MAPPING - .set(res) - .expect("Failed to set ITEM_TO_BLOCK_MAPPING, it was already set"); + .collect() } diff --git a/src/game_systems/src/interactions/src/block_interactions.rs b/src/game_systems/src/interactions/src/block_interactions.rs index 9f47ae2b..ddaa8a62 100644 --- a/src/game_systems/src/interactions/src/block_interactions.rs +++ b/src/game_systems/src/interactions/src/block_interactions.rs @@ -161,9 +161,7 @@ pub fn is_interactive(block_state_id: BlockStateId) -> bool { mod tests { use super::*; use std::collections::BTreeMap; - use temper_core::block_state_id::{ - BlockStateId, init_block_mappings, init_item_to_block_mapping, - }; + use temper_core::block_state_id::BlockStateId; use temper_macros::block; #[test] @@ -186,8 +184,6 @@ mod tests { #[test] fn test_try_interact_opens_door() { - init_item_to_block_mapping(); - init_block_mappings(); // A closed oak door (lower half, north-facing, left hinge, unpowered) let closed_door: BlockStateId = block!("oak_door", { facing: "north", half: "lower", hinge: "left", open: false, powered: false }); @@ -205,8 +201,6 @@ mod tests { #[test] fn test_try_interact_closes_door() { - init_item_to_block_mapping(); - init_block_mappings(); // An already-open oak door let open_door: BlockStateId = block!("oak_door", { facing: "north", half: "lower", hinge: "left", open: true, powered: false }); @@ -227,8 +221,6 @@ mod tests { #[test] fn test_try_interact_not_interactive() { - init_item_to_block_mapping(); - init_block_mappings(); let stone: BlockStateId = block!("stone"); assert!(matches!( try_interact(stone), @@ -238,8 +230,6 @@ mod tests { #[test] fn test_is_interactive() { - init_item_to_block_mapping(); - init_block_mappings(); let door: BlockStateId = block!("oak_door", { facing: "north", half: "lower", hinge: "left", open: false, powered: false }); let stone: BlockStateId = block!("stone"); diff --git a/src/world/block-placing/src/blocks/door.rs b/src/world/block-placing/src/blocks/door.rs index c63169c1..1117b470 100644 --- a/src/world/block-placing/src/blocks/door.rs +++ b/src/world/block-placing/src/blocks/door.rs @@ -127,15 +127,12 @@ mod test { use super::*; use crate::BlockPlaceContext; use temper_components::player::rotation::Rotation; - use temper_core::block_state_id::{init_block_mappings, init_item_to_block_mapping}; use temper_core::dimension::Dimension; use temper_core::pos::BlockPos; use temper_macros::{block, item}; #[test] fn test_place_door() { - init_item_to_block_mapping(); - init_block_mappings(); let (state, _) = temper_state::create_test_state(); let context = BlockPlaceContext { block_clicked: Default::default(), @@ -183,8 +180,6 @@ mod test { #[test] fn test_place_door_with_block_above() { - init_item_to_block_mapping(); - init_block_mappings(); let (state, _) = temper_state::create_test_state(); // Place a block above the door position { @@ -216,8 +211,6 @@ mod test { #[test] fn test_place_door_on_invalid_face() { - init_item_to_block_mapping(); - init_block_mappings(); let (state, _) = temper_state::create_test_state(); let context = BlockPlaceContext { block_clicked: Default::default(), diff --git a/src/world/block-placing/src/blocks/fence.rs b/src/world/block-placing/src/blocks/fence.rs index c76881aa..1e4eabb7 100644 --- a/src/world/block-placing/src/blocks/fence.rs +++ b/src/world/block-placing/src/blocks/fence.rs @@ -1,5 +1,5 @@ -use crate::BlockStateId; use crate::errors::BlockPlaceError; +use crate::BlockStateId; use crate::{BlockPlaceContext, PlacableBlock, PlacedBlocks}; use bevy_math::IVec3; use std::collections::{BTreeMap, HashMap}; @@ -119,15 +119,12 @@ impl PlacableBlock for PlaceableFence { mod tests { use super::*; use crate::BlockFace; - use temper_core::block_state_id::{init_block_mappings, init_item_to_block_mapping}; use temper_core::pos::BlockPos; use temper_macros::item; use temper_state::create_test_state; #[test] fn test_place_fence() { - init_block_mappings(); - init_item_to_block_mapping(); let (state, _) = create_test_state(); let context = BlockPlaceContext { block_clicked: BlockStateId::new(0), @@ -146,8 +143,6 @@ mod tests { #[test] fn test_connects_to_neighboring_fences() { - init_block_mappings(); - init_item_to_block_mapping(); let (state, _) = create_test_state(); let base_position = BlockPos::of(0, 64, 0); // Place a fence at the base position diff --git a/src/world/block-placing/src/blocks/slab.rs b/src/world/block-placing/src/blocks/slab.rs index 6c70ac99..584198ee 100644 --- a/src/world/block-placing/src/blocks/slab.rs +++ b/src/world/block-placing/src/blocks/slab.rs @@ -165,7 +165,6 @@ mod tests { use std::collections::BTreeMap; use temper_components::player::rotation::Rotation; use temper_core::block_data::BlockData; - use temper_core::block_state_id::{init_block_mappings, init_item_to_block_mapping}; use temper_core::dimension::Dimension; use temper_core::pos::BlockPos; use temper_macros::{block, item}; @@ -184,8 +183,6 @@ mod tests { #[test] fn test_combine_slab_into_double() { - init_item_to_block_mapping(); - init_block_mappings(); let (state, _tmp) = temper_state::create_test_state(); // Place an oak bottom slab at (0,64,0) @@ -239,8 +236,6 @@ mod tests { #[test] fn test_cancel_when_target_not_air() { - init_item_to_block_mapping(); - init_block_mappings(); let (state, _tmp) = temper_state::create_test_state(); // Put a solid block (stone) at the target position @@ -276,8 +271,6 @@ mod tests { #[test] fn test_cancel_when_target_already_double() { - init_item_to_block_mapping(); - init_block_mappings(); let (state, _tmp) = temper_state::create_test_state(); // Put a double oak slab at (0,64,0) diff --git a/src/world/block-placing/src/lib.rs b/src/world/block-placing/src/lib.rs index 64b6d2c6..5598f161 100644 --- a/src/world/block-placing/src/lib.rs +++ b/src/world/block-placing/src/lib.rs @@ -6,7 +6,9 @@ use bevy_math::DVec3; use std::collections::HashMap; use temper_components::player::position::Position; use temper_components::player::rotation::Rotation; -use temper_core::block_state_id::{BlockStateId, ITEM_TO_BLOCK_MAPPING}; +use temper_core::block_state_id::{ + create_item_to_block_mapping, BlockStateId, ITEM_TO_BLOCK_MAPPING, +}; use temper_core::dimension::Dimension; use temper_core::pos::BlockPos; use temper_inventories::item::ItemID; @@ -64,8 +66,7 @@ pub fn place_item( blocks::fence::PlaceableFence::place(context, state) } else { let block_opt = ITEM_TO_BLOCK_MAPPING - .get() - .expect("Mappings file uninitialized") + .get_or_init(|| create_item_to_block_mapping()) .get(&context.item_used.0.0); if let Some(block) = block_opt { match state From 0ae620a75e518b7056c74283c540163e3629419f Mon Sep 17 00:00:00 2001 From: ReCore Date: Mon, 23 Mar 2026 21:20:05 +1030 Subject: [PATCH 6/9] forgot to add conditionals on clippy/audit --- .github/workflows/rust.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 3965613e..ef6f2be0 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -37,14 +37,17 @@ jobs: run: cargo fmt --all -- --check - name: Run Clippy + if: github.event_name != 'pull_request' || !github.event.pull_request.draft run: cargo clippy --all-targets -- -D warnings - name: Install cargo-audit + if: github.event_name != 'pull_request' || !github.event.pull_request.draft uses: taiki-e/install-action@v2 with: tool: cargo-audit - name: Run Cargo Audit + if: github.event_name != 'pull_request' || !github.event.pull_request.draft run: cargo audit --ignore RUSTSEC-2023-0071 build: From 3f0bcc3a42e1c8474ac7ce551371bf6d9fe96cec Mon Sep 17 00:00:00 2001 From: ReCore Date: Mon, 23 Mar 2026 21:22:06 +1030 Subject: [PATCH 7/9] Does this work? --- .github/workflows/rust.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index ef6f2be0..0930c0df 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -20,6 +20,7 @@ defaults: jobs: formatting_and_security: name: Formatting and Security + if: github.event_name != 'pull_request' || !github.event.pull_request.draft runs-on: ubuntu-latest steps: - name: Checkout repository @@ -33,21 +34,17 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Run cargo fmt - if: github.event_name != 'pull_request' || !github.event.pull_request.draft run: cargo fmt --all -- --check - name: Run Clippy - if: github.event_name != 'pull_request' || !github.event.pull_request.draft run: cargo clippy --all-targets -- -D warnings - name: Install cargo-audit - if: github.event_name != 'pull_request' || !github.event.pull_request.draft uses: taiki-e/install-action@v2 with: tool: cargo-audit - name: Run Cargo Audit - if: github.event_name != 'pull_request' || !github.event.pull_request.draft run: cargo audit --ignore RUSTSEC-2023-0071 build: From d3ff41aa0f353880732ae2da4f096a2d1e3f3418 Mon Sep 17 00:00:00 2001 From: ReCore Date: Mon, 23 Mar 2026 22:03:27 +1030 Subject: [PATCH 8/9] Fix benchmarks paths and remove unneeded files --- .etc/TheAIguy_.nbt | Bin 22803 -> 0 bytes .etc/benches/TheAIguy_.nbt | Bin 22803 -> 0 bytes .etc/benches/bigtest.nbt | Bin 1544 -> 0 bytes .etc/benches/registry_data.nbt | Bin 46685 -> 0 bytes .etc/blockmappings.bz2 | Bin 99326 -> 0 bytes .etc/codec.zip | Bin 5965 -> 0 bytes .etc/hello_world.nbt | Bin 33 -> 0 bytes .etc/realworld.nbt | Bin 18670 -> 0 bytes .etc/registry.nbt | Bin 49137 -> 0 bytes .etc/registry.packet | Bin 49139 -> 0 bytes .etc/registry_codec.nbt | Bin 39820 -> 0 bytes src/adapters/anvil/benches/anvil.rs | 10 +++++----- src/world/src/benches/edit_bench.rs | 4 ++-- 13 files changed, 7 insertions(+), 7 deletions(-) delete mode 100644 .etc/TheAIguy_.nbt delete mode 100644 .etc/benches/TheAIguy_.nbt delete mode 100644 .etc/benches/bigtest.nbt delete mode 100644 .etc/benches/registry_data.nbt delete mode 100644 .etc/blockmappings.bz2 delete mode 100644 .etc/codec.zip delete mode 100644 .etc/hello_world.nbt delete mode 100644 .etc/realworld.nbt delete mode 100644 .etc/registry.nbt delete mode 100644 .etc/registry.packet delete mode 100644 .etc/registry_codec.nbt diff --git a/.etc/TheAIguy_.nbt b/.etc/TheAIguy_.nbt deleted file mode 100644 index a8535eb10a9fedf341539a97d891f9924eb61301..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22803 zcmeF&QeXdMvT6qA!hmUogR6=@X9tq|NySy~B|sGyv1JnW2j z;EC+EV%yTnljm=3`uezG^z*)#(#hifn)$QR`-9^e?E#Mb`Thf=5A$^b_;tiK$g3s% zHS_92hH7FdPfX(6IEJN{%|7YYQJYe$h)5MC8FOX?ovG?d9lhKlf9}&WwKbx!IqUHO`ECJpb`Ec+D=; zSiSGgg*>b2m_+sJEQLcRTekt=(pI{EYMlUgs==GNS8HX8wP5`OGD{!V5-L#xcTs9D zf3cLM8m2UN3s)Ky6XVXyuKQahs}=)kZxxX_W6zTtd(i*72jJ);*!DZr@pH2yz(XhQOv)7!xv2gfChWsf>x8RZ-8n*4rX;;^nd+eYN;JQY@rE#MWea zJ$~0=xURym9)W=L&uYWFc0%)F8->D!{b`dN-c>X=l##22jNLY;PRr8xd}I<)Z-v|l7!8*7kelb<%7aAdJ!bJoCi16l_nQ4 z<0o&`=EyOLWnI2}`BHS}soPsuRMV%U3X0Z$dL3x(VZ5=kgiGry}-q9dVHHOMC zui?YKCe7ctXsuetmP=9-*KUJ-fKvB}fp631w#5rI6k!(6E=4jyh83ZULKZ3qXcc0W znVqEd1tp(Un-n1vB`ea)w=uVXK#+#ulf5oZ2afp$<3tWW#r%_$>?Zx`>&V#YNqUD> zQ{zB2t!luqZ$)dIk?~mMmng_I3`*JyG^y*HRh>2(uZUEWg^v`;i(D6f;L;?vFATG2 zRy$cGTPa{%FTu6#8NDsa{4ly?AwH<(BB#mau%$+#h#4*c3VRl#)p>%k*9dgT7bBE5SPI2+X+1fTT zN@70X=lP=scBLU3r6T4wZu)jY-q-{o*ThNJ?JSG7rl8Vp@K+7ul-f9 zHl09L&}uI$XtC1GyECbA6bbPGg84LTZ_ZCc8tz}X$n^-!ULo~XX5q8&2A1d`?0)XB zvc$%>mYrZ21r4rV;Dz~Y$KUQ1A8*Koy1{|({t!jQF=_XbkXd0Q`NXfAugW5nHpT%MOXY9=U&)T}=(0wa;|wwD4ke4rbfL z>-TI$q4w8^xbMEF@vWK0$>o7uLIDt3-i?eZOB3}N42Lpbmi)uq=(Z4c6c_)irkjM;`_nb2yudG1vC^vm>0rC~2a8Gb8=uA8Vk ze@Rgk=|fnV0;o8?m4jX#c73o_rs?RtIpdUqf&PR7!jO+UV=vZ-gZ>E2qgpft-rtR+ zFBqa$ZcWAJIWK^LtS9u?K&@RX&L~1S1ilF?i=P|VH_moc#^tItjm+?&wef=or438F#)8x8? zISk?2>4`1ZZ9E!$#W^t%dAY}uB8W+Cn+fdOTg@t2GfsA>B7IK0ue$!cL;8>MKy>ew zJhbtl76fV5?d7g*1r&+Gas=@e^K_F9ZBN3<3%USc5PJ_El_|YH)d-X?OMs0~UY>O} z%DE1|E$V>O0~6S`2L8ygcv#KQYh?zkqE`GWp%%kKq=vhQRl^OACq+zsI=hUqv8F=E z*H``1>qjECg->;tq3Z#Eq4=yxXev@uqa)X;5CR;m1RdQP_ciGumP4($b&PK8ix8-1 zwwZJKmH}+cy3$`7CIa-on13oVhPCf*Lxl7TVf0YxlIYlj-VEq!eH)qekAqXWs2W+m zYfR?18WjaF`lif&VmaH<1=sK)=4!O%`goHe``a$uKoqno9%2bt>!^A48i55gKPf(_j! z?e?7mFrd{zk)gSZRybYNBQeH#oVJ8$Xvo7LJ~hft&U7t80r0u(!wIAjb*Hid)WaWYTB$s7f7)B%^M$67ZAJ>p@*!R%tZsfy*&2d%?9@`6S2@iO5aUDe#tJ8M3fcd;jEFtZ4#=4Q5)Q zEn+VRW!@w+Y%OaJmDxteH$kx_fCDyT%YakBr5>EMa#c`WuYfo;q^<)>!9XkQutetG z`70ngosb?KVNl4=-Bma4kT+|^bG5|00f0jrwsv>mLAouqt~Ysv(Jm~;zjj2=G{TwA zfdHv~MuO&l<9>-X8~ZEQvUX+(1-740KMo-{<6?0rT#q?NCy%zNWG!P`ArqaEOja=(qzBl1xH~)xLXxB=4og` z^F%g5j7clpMCL`%Pdu`V9LRgR`tVeXEZF_A|Mi^&qp#IFuW$aC`vii9-0v}){z(Y_ z3i&V)eSm4}g>jA`U9{FwV%jXr>FTz(u&a^Dn8ufb=7c!h}*~cc`pSZeYIp1@3l#g?IBr;$l8l|M~GC`c) z;MPi{UeUOHi}3?5b`J%Mg4+vc5=Vr3t%H^gZ@Gc@%mdt^B(y)4mzu{;Y7ml!i$*FjVGVG*4701n3cfBPKQ}}5MfAo`qNQ`kApTaER>LvyK7ZK z+IIc!k$rSnvkvYoLSx$L>Cp?RlT{$4OrV*quIGP4@o*`@^&b-Irxv0@%hD))rB~1|SbC2ZdZnL3}lspxZE(osH zM@$l!r#<>5swMOdPvU4P1rLG*ELcUTnr~1cWkFs40j4|Ig3iuN&MA2+ai_ClhIn0d^igE*j4hyMAV{RDwiff%!LLs^}i>#qUcYdb?0hX|c zZ>f{UJs79oQ}ftvY@|08cx~A_>P2vT4U?`-x5tn&4HN{szS4ZBf(kB#NQVQUrunrC zte`21Eyjz_P8#R2L^>R0+FnH>X--7G9#W6d>UNY;mOF^r_hNrFuewiA*`KtWsBbsc4qX#1z}IsfDCuDn3yo^>J_HansX z28(GS-ic#0IoW>y6ffP6LIz*5+Yvx#Ffqh}{t}#ADv;h;m0dn&8MTE?v6WSjz*-;ulP`wg*wUrO1R!b89=_T8}{`m~|kc;b$BOV95N>F~ zTE!;$dibXZ9?-6Svn;bkC1p;Xv9A#gD$YO>4EBI|b4pSz*x#E_Rq6zgF27h%A^Npi zE(F;(>*|&O)`K^~Xev$M%hTr-X2P~oaCY3cfVMOp#WnM)P}GHxH{jD3zZ@M2K3PQoh6WyEMK4zhN8!sG4QCjnry~@ zzcn|)7?|Coh&m7|?TAYj(M)Y4H6lViO8RK(a6DuTY$h*f=qv*( z6zpObjHkgQPzyyW?5T56HE7L%kOl>y2cNe+Kt zo<_Zubq55b?sb(*pL5{X!Ch#_dyx+W)9EiFgX0rCRDs8AK=fb(qF~T#DR; z9tOk_RB1{_1RHPyJS#D}uU?ijI`CZj{+cGyMZFz^K}CU?>*Wj+kua7-wo~ZzPl{*H zT#6?~A-DUB=%-S}*_^DyFk|(roWV}o8L_PBTa$%OB{Ii2 z=Bs8Qxg9%%Gg>5GN|YBd#tw3(7~HtjF}tK2_@JfBPY3Nu)2tRotQi^<32JV)vdE7z`0ki6L$$%GevLQ^@SS2V(V26r%qn0cNaj zoj&U7Cn1yzzXDw_(!_A~eOi}&%a7ar3;0!Dus~}_xyJfFT_$VbNS7fF_UZ2v7_jSO zF{(u4gdb38kq~@q6EgGsy%c<;$~vl-mcs6#dzcw{1G1f{0tT{%yT&Mz75rzrpGl_* ziP4UjF4knwC^t~6MG;}+{G^?{uz6id7?mjfm6PzFF-e`W{z^4s1OvgY$%1NKYknPM zk2@_u1_IOW9TJXsobYTG1TkNOF-9M6bz^9{#%+EzQ5!n7gtX1X#J@3)UpyTyF(G%+ zfcFMWDfEb1q!`{D4lQ9AYto(g|3%CLvdaJLk8e^X_zDmU~bZxv8*_%7^h%kic zCFzaxj0D}=Ntx7=u@ zdEcQzx1rHWqo&D{KTKr?kUV^}DD|Uxo_;%eEU-X}jl(%#NgGnt`@DQpyXf{v!8A<= zpxmW%@%MDbz+|(`yv1nq=DUlh?U8m`@%01t%UR1oNTib&1xmfssh#Y4N2>E=n7#*kTRCb_mXzqKOERJj! z^#)j$mD3Teef7!M_G>31CZ+3rp6zLB8}Xhr4hDe3oB*Ccp&S21DKqlA)Mhx5TpB}8 z4C`UVp1mr{rhlvT#mj5(d6K7tHbx$j_+hCpWFl=*yF^(Tb+0+Y(NKA%-95Q&K`B~V zDzDZV>Jh-8+B3`?2)o3A^A#HSGqhCCK3&jcTbCvPjktzKa2Dkp5 zF(XGLbTKDH_AhTYgAWi2Xt1x!o3=^1KK4kx4mWx;e#OG(3A8G!pb!_NK|U>kebECg z-B}lM;0MWkfW3z;sOOWmhT7SDLa;erLY-jt^EJx{f%btvhfS4QkxPi^q^AXd!O-0Ky7TREAB6#+ zOn^{|c;Ev-^>nla+>Gc%POIzR>1$6a_Y@H+VE8xl4`zvBW6xHulRb?M10+5%EkcFl z$q$;T^{GOY?#`nWKB1go(~`P^TE!WtIl?&6el#MI3+a}qhQTeJp%_*#U#lGoNOG24 zFpY9mKFR0m6~3Jk@Oi?mY*RG}CtlQqL*m>g6FFlOwEhJD?VJjHD>p3w39Jp-Wfrb_@8l?XrNvZCmlQDsUbN`n zM`=B7xT3!(RPa%PpZ$X0R2&O`II%$qzkO#~3jbEAxkOJs3|N$rUt$I@mQaI1?MTqr zdf-8FYt$0Prbd@WP}RlmHs_YS!sn=u`*}DNjRTgds)u4JbWn!~6O!!0tDcQhDGE?` za^z^byp)X}QxMda7I0qiHk;Bq7?4awa*8gw7Wqq!B0@KGr=}koA*RP zHNxG45pE7@z^c}fW~e_fh;vei!=_O@Jo3C&#GR*Z>CzEy>ecnD5gkz|3j)`xD5nNO z_v<_;c3!AvSJ4XYYBmLEZRw*_zge1~g!o9hpk~H@<&i*+w~{VH074feo zg%FYvqlhgq#(a1TbL|$8^y6+1bC<7}lId@VXwBQoaWFFM4!HrXZLGD*+CvHUm>8{! z4vwh96u-Cw)^r-(QU0!Vbk!Sbbk#^kwJBY~&~)>4f}|&|*<48u3`qn9*Wqkj6gNeY zN&3of=&G1tU9qd+__}!@65SkGx`aC$+8>4a*hit?3FJaQVI<&;l8r@1)!BrqLc)@e z&wUVYtt`W%!Lg0htJYuz%*bPuQt@b%&FC{}3Vf2E8wbrMf+?t?v?w5YDe;S*@P&ON zzYSZ(yhV?0^uuWCC9wt5Je0r%4_#>PKE~--g2Du)E*JSM77}MWrA=pc2%6%Syd75l z*i>N}jyo*M%HAgPu5HyclT@d_<%rw-l~@sdkC<+?b@o5W3>CMSm0f~=L#Qa#mOxBD zT{~MjqgIL90qZ(9m$$iwi;{giX?)tq&M+&g&J&W%ZDVQ-XzZYWbzltLlWKB>sQsmX zTog(NyRMncCIT6(q}`Ze0v2FA!s)f7eOY>PaZC8XNFmA2BUOP;q7@S&=$lcn5e}`c z;-7L9&q)gIf>gL=9vi~QE!tC(FgwhV3O@K#tWh-Sd5vB-NZGltVcRdByby>g zt-wf)LKGMM9DVHk5qznNr1~aRow zKqL@30N!OgX3(a4?{P5^%LcU|17E>{eh?F9QF)&|#H_6xL~7ds$+sy3w7z!3r`;X# zvZ~?)6)o^?nN}7+b>OCMWh~R^?t`G=u>Xr%)R<{yIS|hsTcfCY^^o|l6vJf`Sd@VI zqg_r6&Y7Nwjqi00hm(l9s!Di^OD)Zqj~l9%y90Wa_ewdtG=i9-ucG`J-D8tEO9rL- zX!ip_j!)%gWh#lWMP!LTDAXbo6`g~|^0R94N(u3zVW6bZ+(iJl2hR+f(W0I@Z5-*5)9^2EHndx**ciUzA&{4rRswn-JOX7KJv^q zc`pX{DGPFyG9o_+Jg4TNboZupO~fNUaA=ebcZYfs)%XcF32X@`_&Kf;TLqh(xnkBl zqVQiB7kuVRc5`GX5Ak7R>V8uw7Y5@~hGkFd594WTcYlmrYYhF9VWKe~uzpz*lFIx; z<~haW1W8PiVX9tb#okk&!6zdf`lcbdU0=#mRB(?Yqj-a1XdJBC5R3`ZZxI4 zq}JLnwubv`i>M~qp?8-ZOqr;B2IL%!AqHF>=T+J2*IIo>kUExusFPYaX-h<0?Njyi zjA)lGw64P7hLY}s6#V#Q`o$_xYUdA7Gfyh++FG3S48qa!v`Hhmb zOfV`6w>ng)LD3k{`!*lkhK&(t2t1JXO9=IgMS@@5n;6qZp6iA?J_SBAZ1g>0K)$f3 z{;H2HHpK`ES{2PS48dj-i7FmM*29G+&kJDoZ~})TDXQO8#AB$O*7QiC`5%zhRjTr- zx0(4X96z|UBZ$UvtAvj%vCv_NiYzJXc1@t z)3BWMwXMieUt;gI_J_Fq`^I;%Gk``T?3Tdf4hp~0oW)%GYSPU27~Gk?E0OH0<9fZY z1eDlx(qS0qk9jn?q6x{%=FoO-=C7${cJ0 zkuI7-Qm@0hI^9Jg`X=Zw;P=2I2i6@i0j0_3!A!^c|BYRD*<)bzA!VPv%j5qC2UQkDr(IV7vBHBb7jcDFhN; zU|~rIFN7ZSQKdnHL4}W3n%+bdOPQ^Q_xb($ySq>r?EdCVcIJ^fxjH}M%+yn^AiGA_P>hIGwPjzMC`0A2NYFw80h!7NJ$ryNG_!*Y9sR( zQ@JDrvVq#?cj3jDn!e-P6cO8+61J(yy3MnvlwS| zg(-;Q18)3rxnSPV4Us12y0Na-zp*VV-$*A|p*St#K&CV9GXix2AOgm*)@Ze>Hs}hy zbo(H?dceA>8Z;$hs73Hl6E7p^1=p^Xzu1y$eY9MofE!t`3GgBcabPj_PHCb&)3@Nu z%S$TNcaooRD%YAr0PRtEVR`g1t-rC6+T9*Qrfe>orCTHS`P;k827QVwr74E?wC;o^xh5 z{m9-oj|bJJyftAQ)Rx*BiDVPAR3iCYZ^EMnEOr2ORJw{zPVhGCju>g7d2kvSv@sLoeo*@b^F%D)G*ew;N^== zG}M~nqzQ@{ND$i{t;C}|qjnx`)VkL^V{B-L}PV6*V=Qmfc~|H`TDn&6gl(~Dz49@XZ`@7cyB(x%U)3oQ7YRB zD3EPy6}MsIp5!$Ykr1Yxo6bUtDFa-pEGLJMWu1}yJqT%1)Qj=OQ2OrF!oOiAQvE{! z4mWu3ak1`dQqjh`U*#LTtgc~ReEgp@tweD02LW~i5nw=4UCI(Y zw;_2j$_j=>Cgq}iAzepV*kdzdgqdp|Na0<+t}b(HhI?r!TPh>>1k0WvB!Y%q%5fs% zN047rfQJtMw5$J~Q~hmq9$fQrXo5hobwPGp6Bq|-+XdF$5i2mD!}a+cW7xDrI)n); zyUeh0)tA4fVRfvUw68#5F}17$Hj{kJ!AQe|5Lja4@@^;Dd-}SHnPN6>q!WLeZFMIzi(-AT{$@7j-0?pwnHf+00wOA4msxQrI{~3Vt`T@SAYQi; zI)rsoX!R0=bq*7ORG|mq5#8Kpjh~Mby_4H1v5-m^R)z|YNDH!QT^*Hm&}^m(yph8F z)~(2O*i*7S^V8KsBQ7JgBqo7WqT9Z_q-czEm&+AOW@GY5CnpjVIhwVF8BC?p?Ot$1 z_L%TY!wlt1x$la9B>q%j@10PWsbwhGZgV$neA;bkwLzpBAu8aW86em7<9`A%%vKE$ zMC!X5L})>WtRmx{6Z(?1jA%Y@VlzF64>8YVs$!iZPEYXvYQua!8xP8UkZI>6OyJcR zmea5aJ^c2mXfl}aZh0qO8pUPyb&^@F3{86)&af5kZc2lNBq~xf7nacV5Frid^FKW2>d|is zx>Ap?@z828p~eJBhG`CW*&v$zUdqh;0^Q*&v^lZ&uvJpa`n-r<+iEB6d2mWxsS05n zvwX9gFzCVnLx~&f_f?%Ii1f7|t?O~eg7-@vZ_$^tui1I00MpDsV6OL`37l9gz)UIz z?)zqBaQC7Wp={c(V$k8pPYMW2i(M2c-#%Sq{^~h1$WpA+rMgV8rgIMH-^v0oyjF8j zoG&~u5X{04ubEjAm@+Z^4Wfh8i!DYubd!R|P>P8b|B!dL$yy!)rL@T`h>K^9m2x&& z!2*mb7*3)o(*JN&^xBZ8ym`gH0%kEXLWVeb58r_uk>I(QJLmhum-e$o)#eLH&j!E8M6j7NZzK+G(`@_0wl_bO@_>#g@4ncnpva? z2R?eU0(x!@Bdkvp!&C&-9hLx@@1laD7?6srX-gx6z>GUySE@1#VhS1fHA8cgpMZ7| zl5LfOhDTm+*jL0>%`b=IKT+4X-7mk1rnY5}yfJ6iOhctj(s~3KE0WXyj&X5kR#1PASwdUCSJe#?dPv?gu@I0bu+O5I z7BnizHb4p9cGp28!cs_gHVbH3|C4a7=c!b`UI$!|yJ-l>pi{NfZmF8)jXD{f%W1)a zlO-vr;jqd!8xz0$C-w&Np1|4sn8OZ}!4Izxxif-RWLjh(ndlgzN=5~zK&uAEL30x# zS7jXqPZ(~uTI{K;cs4G1hOAzK{wv%v&qgp)g_W)8)2N|760|y7k5)ii&r+6xAbNyY z8JV`wD{B(zI$gsElv=3SW|-5Jd(yB-+5;NaIOcecR7NGM=1h#-<=*+gpAKOJPh_Yl zLlnw|Y>~-CbrmfqFziar%L!h{>)>W}8c^Q?_)?O%6~YGVPBwJ=Bd=;!7M!k!Nda@H zrpgrBRLp48%__qwnOBbjq``f}EiQ}r|9Wjl zeuI!koxWS#E`<1!V#?1}Rb~%MJz5EhIh_r&J$qHVE5r2g zfhbOAnL%)oN?QasMA@-<$JX-@owHjAZHN|9o(gN-P#yfSPT?8kA9NB1GZQ1I6|H*N z;dyT<^4FOo!pr4?90h@^7B#gAx;q8&o0E8b+p0Pq&bXLtbE;rN$`tJo@bLzgNQ5V(;~0H>c;cNnMa2$a>wDzbFL z^8cXO_y&LaL+I!V>JcsW+f)#e`ZDS8nLtxWk1}{Z!eZKu28fsc;j+)rp6atTwWZDC zAJ>34G~F!itFyINfZOjttXG(?JXSAp#7Ar%J;byApOgG)=8W4bR(1^AzQW{45$;rr zL6{e)T~asl;20k=U8<_2dz_rC5XK@Ul0y7SvseEKvh^BgpW(rZ?3AH;pz2v~gp9~Y z+}jiug*vnFI%%R1)hVT+gdQdXO) zfCjZlIR#qQugJ#@bDm@MUu}MYz}^7=Uxq!I z3abFLz!WhNZ{D85J^OMsmyy1Prh%2*Gn~DN6EB?~L81f#{v~tTH@|=^3+iS+lHjbU29L)XD(hD zH9rJ%%TqfUg!IZJva%$_`PEcE&g^c)nPy)+j2kT5aQ)&=SJagQ7$()N%nA*|YOJ+7 zpr(NhLxb730q^PRUcOA4S=`JX>&IILMV^Ux*d)2the2lH+MtD384s6NrHOJ!G~*$w>A-4Vdxn98 zL>nTfDw!0%SeL`U#$bGaIsF+px%ed)F$JMk0Gu%p0>aKF1$iKy8dLH(o@2a2J@v*A z1qPTQK(|=c$YuZIC2ZeC3Nm;{myyr;Z_Op3#~2;Wd$VzpQ>9A;-uA^ZKW>WHD9Z2% z4Fs#;jeNcs%vP6h?k9JFfGJXPyre1cd@Jby+8urLkm|PRxabz$2mBjYrAvW?mdbfB z$s?9k{TY0&@gBTrWp%bU%bPP>(?Ke)ED3a|ex05hVYVTU`kd&5slanv0ZW~)uANM) zVU+yBKClB_XHng}I_ySfUe%1n_D>gXB_7$N)blcdyHi@Ie4lfce zq57l4b*xS)KT3U*^EWy;1}AaI(7!>dV7ddwF{Ud+7xJAHJ|MzrM!ptsR^Jk9pK3kP zg|xwwMt2Yr7#rs<>vutSbwJo504|x=WpahPwb0eaTZ`rGSc=lE`WH+r!Mg2U zW{S@c`U-8U&G&FdiAfquhqb8Cn-a_B6U>W5YWES+Po|8rK3aoi#_Unb&*$->5uULk zi8|O)mLO?ffWVcbp2_;J;Y0F8Hq&Q$hZZ!O1 zY#_0jnb@4~y~?;&5DPh$aM7HuYu5@oG$=O=m@YVwx!Dzas$r43o&; z@N0Obh3a`|x20bFt1!VMn_x-|aobSF-dXJfr*GU5s;?rDD`WIa1Lei&pZ_=9PzoXi&|0NLkK3H?hO)Xx~5!#Xgm61;9# zbe+omNX{1wnd+?#lsMALcx+R$oXyn&^YX5W{m6lGDHz$ZtUJkj+#IAj68OrA3^H=v zGqPA%+WwSMCF z1vPF~{}i&H=D6i2ORVNdQ~v)((VZmVgIv0WKY*@5zH=+Ok?N4S_sXoF(u~vey@xgRDK>r&NC4O~(wbB{-uORAdK8!TC5V2oUz(y@uFzm3j zQycRNQ&-&T5k<30YEC3WnWS&@lD6;0&l|Pga(u~W)AlNhA6GJyEfwvg9rdt*R1o5T zC??k$^d0PxLy3h2LQ0fddZ@y@cVDJk&v2cK2urwub2fWO(& z_JpUj1-bHNPQIaCXlpcog1YTr>e8|Bb0WQeyis!2bhvT-#mUoZ|JOW&!Gkwy3(-=S zR$$Kbo7D4vkx&H9v<1e$o~JJ3%=tPpQiend%<=CbChwsu%PCsbP~UknT^E)}CQ{lI z8rsP-TIvu(JWVavq|zzIE6GKp5Vq=oRXh-#{VAkUe1gcdo(uIP{1GMGp7ZsFC&k$ju>EX4hJbf7f^QF^yr%u^0%sIMyp-O z-e|k0p*ib934AA>@waiap*Sh!zc;2H4kzPm#8d2;KxiK`RLe7s$)&ok4b9G{ ziX$I4_(D6eKANlC(s~@v%9AB%?zEm%0nj{{BfAEu70eiSl5F2p z+Qg?=J{KzQT7Dnec*4^ROk237FrOMBaL68TNRb)H4LusSV_PJ}I3>gaKUd6wYD3ZJ zmWT6uJ2ny#qZ^){Xl3ZokL})J$wC$xpU%XhmQ5^@736cY>|Uz5QG_2Ckw-egFEE<5 zgkm$`Uyzq@l(Yf$8~+Hax*}jhu5Fh?HMq8d#y90(E9*2Sx#h0@w}1Y(fBv_B{7X7`v8!7gck19=e|B%VsLka0ua<=6=9b=SEtYScnSFt$ zcJG6!qPEkdo#!(*aAg>Kb!f^?ySl8rJLkXi-e1-&ndgFmL507Ex&8Uzi<=%T>9Ma@ zc-d_os>biuxyFl?Sr@JE`gwZnC2-DKG;ABuYOQ9L(fzrDK}2e&7y2o8h-hL*AxR>1@*M4t*em`^j6O1cP znp(sc?sIv9fG^KmdQd*W!uvC6q-gwpO?Y#Etczxdfd=~fLb47Xk+IXqRX!>|Km9Xjm?k#l?w#OB| z6SCcI#kdgGe#linYbM+7gd>A*bE$V^9(mP}M|YKX=X=`0i01Axt(mc&&*|GSzIZs` z@G7e1b2F-EieZU?fxBdghuj_C=X})C%^udCr15om)6G*!M@{+_n%ZAwk`}O~?6K|Y z#;{(A9et#RfqzzN=-ip1K!I;Ct@-Kb^~Hg~`O3+?b#L#zrrk-Q=B^6U5%_5f;?~6z z$M%4DRfBMu_sb%6(9OQcrhred$!@aERj}faWJ*LE zk=URdLrWb)o1t5F#!n(X*h6}x>SQvqE9u^}=iZ%q4d|Ug`qfi?<@YCCLX$jLhf-|i z1@8!n!;_<$7^-q1@aXs)B$4Zw)6Qo_emJ3m3k!ZaN|;Z&WRstg`Yu{!)@*A>s_`_n zlbW`Dhk=1__kv47O{YGUS)^j|qWHRE|GlPxa(8aJw35O)&wOp~_g6rwr4;qhU6vtN z^F6AUE%vA@_SeO{(!r=qc=;aW`z0g$`R;u`!}}fX)%qIs`|RKKEy=?@$yh*aGS6cb zxP2gO70ey*|Mu)WheDCgeHPh4!13hPgz)os@6GB>ZU*-zN=(5K8` zFW1Hv` zsUF|kQ=FEb_eZ^^8)y6L^BLM-zxVqWv`VbruHKG%AHT<)7qnJ99*;UtKgZt@JXSn! z&&SKptk>Qyx6dT9S2n(P#p^-38@r$TZLi9|(mm?ksau`*5{`5ZRxOWNe)sEz<7lmR z-H-1^oNmv*S+~3@pDJzNZ=Tn#oO}mrM%Ux3CW!Ve7q_&*E`KmfLU!Kfa#mky-i86xil%9$KIF$V}b=UPg-_AYG z6!ge+ZC@E}xgSxgTuD=>jR+}vhDsbq_a-J{D5rmq_1Gu& z_5~+PRp&_=EAO28OOo06rf*<+t~3l09+!+$P&37!1$ zFSn4tgr-YC1DZwv9xB85FsC~+>duRM4JT8d!#lb2w9M{OIPZ>Z(>*hG8_`PAP%(4Z$o99QfG&tB1EaG%25lk3Bp!0xl2Q^ zOFTMMi6*sp>;Of(u7UXuzZuGCR|vu*9-VG}!%!Xrp!J>;S7$J}XXOe{2P#_=jY_9i zwslO`SWj`rVYrHH^enYUG}LvjwnM&2kTN$K5s<{Hc*h?CeF_&geB=BKveat*Z-L%< z2HZCgqjJ3&Pn3ssx5L{f<>UYQ47jrkp1#`zrtiCm>F*&d4@@5iVF_@8AOPFPO;`q) zE(XE^@Hn{w;9d3S?w+@g>;s^80sPGvZ%@g`U+xu-xAV~z1$lS&%WLT>LXNC`4`wqa?zj`{(9`+s={xRBCIC8lJGa$3;S!pR6DcI!G? zQZa~th2j^%)DZx57J9Hgb9+qIhe@O)Y9`?~AD;c!*I>Rmq}-91=6<{jNWV6+uY}w? zX5A{-pX1y+F|Pr%_h>1w%pqz&pzE#aPJz5 zZr+OQLcrKZ1JDf{eETK-%CfZ5h7NLb{~gDw(Z5rW`-Yq=>ju^`ULU;!M7&yh8W+D4 zJwLVfaXOb&TgqD>6a7E6*#ENqf93plLh4)3qy3jk?6cRg>X&NtUn%&ds(tc1HvUzU zefB$c{#Bd#*IEB7|2pqqHQIljVVoL>?cIXg)*hGb9S+-DESC2uY|kOsvB>{(yXz2i z+tv>~4a=_TMq}huBU>C^a%k=Je(zo1(}M(7w`&IQHbAbaH@m=473yigCHM$)LV8wA zJI6;nv}4799urn`qFWP{EVB4e-5LST)0Xo!g&p%YA|TE3e|2!?@lfq=9Pih1r<>xI zWm3jeZmBF&)?zYOkv2)mZY+bbW#0xvQn?W>S+a`|LfNv-NS4WN3?pStW2_^LWyXy8 zopb*9eqPUcKkx5z&g;xS^E!WgY2;M*zBMO!s+v8#&2jO5!X?j^#LolwTc?`4)F1uP zP}b^jed^7v=s9y~z4|*-b>G&I-k&MT(1!0hhY7O-6u z*ltY#%vHcF0?a~SYZeGL08j@2CIGO309fe@Nh80w`C)snq$_2fjOaBzkCLA0@3-_@s<67}{5eQ_ zn6$aiYI8uSo<&5>))P*DhvUXz*nU4`i1sjX7HCbueaG|w(8PXHw#@)9tbuMY(B%Q$ z7XT=4%vsRl0Np`g3JsXj2ZC(?i~?W{04M;Ew{lA@JRjBByd3j+l=;-oeuNg7QvBbQ z6j{HlY$Mx^MUKB)wc<91?FyzZlx!EMhLY_J)ljmXpc+ax9;%^aJ3=*-YzL@@l5G#w zP_l7Q4JF$Ss-a}tLN%0Z8>ohoZT&1op|;#IL9xo_1AmMBf>~UqR;5Ht6YDTfIJ36o z(L(fd@|?6)GpIOs7u{2Eyk#$Wabu6ggLiWhuco@m$32fM%})-BSC&LPh@_MW-yJ*9 zpm9lcd>Mc`WB^`c1F(k#!eSNxed_?YX9|R#AoK^fzpVmbOAiQTKzMa=e$r@YVyL<3 zDhcRR`@)j&uG;Y|p3 zgHQ>C?I8RUf?Xg~1Yr#{(;J-G3BvOrEP!T0umgl=K$r~8Tum2I_!?iPHO^|A$}V&0 zD77bQ#YB6YKGHNE8y%wADY9&uHp=;C>*dYFMKU@v;}8;O1GM~2b3qsnLWfQeo(Ewu z2tPt51m!_k0YWNdc7diM2;_FG5O#v_q7Muwio1O@Ye9W53O~Y(~OJwqOP}Qji)d5gVg;ZZqeFv(yAax#8jiITK zN(a?CNc98N51<+asf(cM2u*EhS=m>Vtd7!u)q_Zx3FCTl{G}M0haLM8zq}_-x$(cC zQ+s}{0B1M2MZ5p?^s`2M*b&D3rx+JT6^+RfY25sAEO73}vDcqeq#Rr=R*voYrN3{_ z#F@A?*_(m*zJ&=?ay*Yud;|*+_Nuhb-#PN zCMGlUfQ19rG_XX0WkPP&lpUeE>H6rOlgN`@L5tlR6zBQsd4@Y-_t$3pO0GlPV2(uj zV*BXyf!f!2+L-FTkk)ajA#1xRqU#qy}YoG)C6aTWBXyj+DPD!S}mL%kL^dT zKMAdav#HpAN{}{kc^@$s&XyzP7;HSz8WQ6AqGpvzIeoyzun;!{H7i8QLHS2W%&xSJ z6;NbPWUwuATPcQU*PP~)mu?ZSc_y-+Po8Z%)NE*e{7IgX+Frdm*Zr~g>3>P(7XgAk zAjAT~K0x>`2?)x7-~b4nfDjJ|2tZH-1l@zcgrk6P0T8SKfeZ))Kv+5|HBgt|9Ny;` zHyy|m@I(a81ZU5{D_!jYxbzVMH@}%2hr!mpp&1FLvIVb11v?WV>^Q;C35A{MCjO)c z#sd8IW4wEZ{d@9vC)*jFiRaXu(J$XVucqUmKh4qklc|C5mFuD?jqynTon2s)t!^e> zc~+&`iU01Qw`y_ji9X zhF;JsX_w%^Ct0@V?~!`SdfzuvDcRvKf3;gZSvdawFyCqfQJ%j3pnLFMV##>i2!fp@S`Ec-sz%*yA`tjGD-g_J+PJ+ zo;&ozXO~LJqp=NWkzV9fG2I0Hlvj3U2!qBN z()+(l=g`vDX4t;zz{?ia#;^pBR+D7rh_}iZ*0?zI4fZR2#!%^DdZUy=++cvpuaMm5 z=PK4Y#)*^zfsIn)x@xx)m9((Z_`GLY^c(3_$-|3kg3$=y+){?|J38l8nq1NP15HJO^{=jSVAN3H#Ne**nPC|jWA~( zx89PiXl=IR>#*J%5oeMj-kn#nX@ap1^>pRT^ZQZ+E{vTRxh{1~@Yd|iG^I;KTA_S2 zI52=wzhLTaV{5#jAhml?;_+m!Zi72hMz65+x|W{Yp^I^Wor<@j#nD44226ob{;N z?vAAwrd;&fyJ+`+bEhbA9(2(LueEtA)!(=)N2Hg2qzGpECtugM_~E-kF}4Q?Pg_+cBzyI`6DMnl-!_8B0f+%ULBgG@kSnaU3zuc$NQU zdflk>@uI}&tbKi%{UFERM^*q_pJ0n%hq+?B@ zUB3=n^Y!Mnh-q&b-J6H`)k(37r@5BOmo;xS{x}#Up+`_n71e23soJ#5WhUUK-4}h- zQO9rmHFq_$;M8i={)5q%PRSsyeU>UeUOLxy0gr(j57=uUuh;huqlFC_`5uPePK|K5 z%aJ!sXQOEGw$V0Mug6v`hFRimpLFHTMZ}DlP1v(aT2%)WB>za{_ULMu9DUtW5)S-m zSanp)5C38o&&R+pE9TjFZcD@~)>0&)pjxAJTev&ySc0Ex6vn!}Qbvs^aqM)F zgJKD>^T6Q};g9F!mMlA_*95C#yCu747~Y2d;b!wDL>;wUVYfA-7^m)jVWiw)1ns#V7H0p!L)Ab{ zAd7G5KZymmvWbPg!0(_apQ!TG#|2$kmqf#{nbVu|nF$a|%X!y9j^cQwS;y*2T zy@hL8#@(-daQcL0q!&8fz&E$9-t=;+*iW098bq11T*P=4wGat|u~LsR_Oy@A?rdiZ7#(~;7luzW=nPv%Hiq1$vX48sHG~uAuB*gp z8M@T`iN|m!77MMk101D^DmxSbyHA?GL)iwNwAD}}+mavr>_o)1011v|-vf`f!=x&UqMF^s zdfO^AfnW!eXdMvT6qA!hmUogR6=@X9tq|NySy~B|sGyv1JnW2j z;EC+EV%yTnljm=3`uezG^z*)#(#hifn)$QR`-9^e?E#Mb`Thf=5A$^b_;tiK$g3s% zHS_92hH7FdPfX(6IEJN{%|7YYQJYe$h)5MC8FOX?ovG?d9lhKlf9}&WwKbx!IqUHO`ECJpb`Ec+D=; zSiSGgg*>b2m_+sJEQLcRTekt=(pI{EYMlUgs==GNS8HX8wP5`OGD{!V5-L#xcTs9D zf3cLM8m2UN3s)Ky6XVXyuKQahs}=)kZxxX_W6zTtd(i*72jJ);*!DZr@pH2yz(XhQOv)7!xv2gfChWsf>x8RZ-8n*4rX;;^nd+eYN;JQY@rE#MWea zJ$~0=xURym9)W=L&uYWFc0%)F8->D!{b`dN-c>X=l##22jNLY;PRr8xd}I<)Z-v|l7!8*7kelb<%7aAdJ!bJoCi16l_nQ4 z<0o&`=EyOLWnI2}`BHS}soPsuRMV%U3X0Z$dL3x(VZ5=kgiGry}-q9dVHHOMC zui?YKCe7ctXsuetmP=9-*KUJ-fKvB}fp631w#5rI6k!(6E=4jyh83ZULKZ3qXcc0W znVqEd1tp(Un-n1vB`ea)w=uVXK#+#ulf5oZ2afp$<3tWW#r%_$>?Zx`>&V#YNqUD> zQ{zB2t!luqZ$)dIk?~mMmng_I3`*JyG^y*HRh>2(uZUEWg^v`;i(D6f;L;?vFATG2 zRy$cGTPa{%FTu6#8NDsa{4ly?AwH<(BB#mau%$+#h#4*c3VRl#)p>%k*9dgT7bBE5SPI2+X+1fTT zN@70X=lP=scBLU3r6T4wZu)jY-q-{o*ThNJ?JSG7rl8Vp@K+7ul-f9 zHl09L&}uI$XtC1GyECbA6bbPGg84LTZ_ZCc8tz}X$n^-!ULo~XX5q8&2A1d`?0)XB zvc$%>mYrZ21r4rV;Dz~Y$KUQ1A8*Koy1{|({t!jQF=_XbkXd0Q`NXfAugW5nHpT%MOXY9=U&)T}=(0wa;|wwD4ke4rbfL z>-TI$q4w8^xbMEF@vWK0$>o7uLIDt3-i?eZOB3}N42Lpbmi)uq=(Z4c6c_)irkjM;`_nb2yudG1vC^vm>0rC~2a8Gb8=uA8Vk ze@Rgk=|fnV0;o8?m4jX#c73o_rs?RtIpdUqf&PR7!jO+UV=vZ-gZ>E2qgpft-rtR+ zFBqa$ZcWAJIWK^LtS9u?K&@RX&L~1S1ilF?i=P|VH_moc#^tItjm+?&wef=or438F#)8x8? zISk?2>4`1ZZ9E!$#W^t%dAY}uB8W+Cn+fdOTg@t2GfsA>B7IK0ue$!cL;8>MKy>ew zJhbtl76fV5?d7g*1r&+Gas=@e^K_F9ZBN3<3%USc5PJ_El_|YH)d-X?OMs0~UY>O} z%DE1|E$V>O0~6S`2L8ygcv#KQYh?zkqE`GWp%%kKq=vhQRl^OACq+zsI=hUqv8F=E z*H``1>qjECg->;tq3Z#Eq4=yxXev@uqa)X;5CR;m1RdQP_ciGumP4($b&PK8ix8-1 zwwZJKmH}+cy3$`7CIa-on13oVhPCf*Lxl7TVf0YxlIYlj-VEq!eH)qekAqXWs2W+m zYfR?18WjaF`lif&VmaH<1=sK)=4!O%`goHe``a$uKoqno9%2bt>!^A48i55gKPf(_j! z?e?7mFrd{zk)gSZRybYNBQeH#oVJ8$Xvo7LJ~hft&U7t80r0u(!wIAjb*Hid)WaWYTB$s7f7)B%^M$67ZAJ>p@*!R%tZsfy*&2d%?9@`6S2@iO5aUDe#tJ8M3fcd;jEFtZ4#=4Q5)Q zEn+VRW!@w+Y%OaJmDxteH$kx_fCDyT%YakBr5>EMa#c`WuYfo;q^<)>!9XkQutetG z`70ngosb?KVNl4=-Bma4kT+|^bG5|00f0jrwsv>mLAouqt~Ysv(Jm~;zjj2=G{TwA zfdHv~MuO&l<9>-X8~ZEQvUX+(1-740KMo-{<6?0rT#q?NCy%zNWG!P`ArqaEOja=(qzBl1xH~)xLXxB=4og` z^F%g5j7clpMCL`%Pdu`V9LRgR`tVeXEZF_A|Mi^&qp#IFuW$aC`vii9-0v}){z(Y_ z3i&V)eSm4}g>jA`U9{FwV%jXr>FTz(u&a^Dn8ufb=7c!h}*~cc`pSZeYIp1@3l#g?IBr;$l8l|M~GC`c) z;MPi{UeUOHi}3?5b`J%Mg4+vc5=Vr3t%H^gZ@Gc@%mdt^B(y)4mzu{;Y7ml!i$*FjVGVG*4701n3cfBPKQ}}5MfAo`qNQ`kApTaER>LvyK7ZK z+IIc!k$rSnvkvYoLSx$L>Cp?RlT{$4OrV*quIGP4@o*`@^&b-Irxv0@%hD))rB~1|SbC2ZdZnL3}lspxZE(osH zM@$l!r#<>5swMOdPvU4P1rLG*ELcUTnr~1cWkFs40j4|Ig3iuN&MA2+ai_ClhIn0d^igE*j4hyMAV{RDwiff%!LLs^}i>#qUcYdb?0hX|c zZ>f{UJs79oQ}ftvY@|08cx~A_>P2vT4U?`-x5tn&4HN{szS4ZBf(kB#NQVQUrunrC zte`21Eyjz_P8#R2L^>R0+FnH>X--7G9#W6d>UNY;mOF^r_hNrFuewiA*`KtWsBbsc4qX#1z}IsfDCuDn3yo^>J_HansX z28(GS-ic#0IoW>y6ffP6LIz*5+Yvx#Ffqh}{t}#ADv;h;m0dn&8MTE?v6WSjz*-;ulP`wg*wUrO1R!b89=_T8}{`m~|kc;b$BOV95N>F~ zTE!;$dibXZ9?-6Svn;bkC1p;Xv9A#gD$YO>4EBI|b4pSz*x#E_Rq6zgF27h%A^Npi zE(F;(>*|&O)`K^~Xev$M%hTr-X2P~oaCY3cfVMOp#WnM)P}GHxH{jD3zZ@M2K3PQoh6WyEMK4zhN8!sG4QCjnry~@ zzcn|)7?|Coh&m7|?TAYj(M)Y4H6lViO8RK(a6DuTY$h*f=qv*( z6zpObjHkgQPzyyW?5T56HE7L%kOl>y2cNe+Kt zo<_Zubq55b?sb(*pL5{X!Ch#_dyx+W)9EiFgX0rCRDs8AK=fb(qF~T#DR; z9tOk_RB1{_1RHPyJS#D}uU?ijI`CZj{+cGyMZFz^K}CU?>*Wj+kua7-wo~ZzPl{*H zT#6?~A-DUB=%-S}*_^DyFk|(roWV}o8L_PBTa$%OB{Ii2 z=Bs8Qxg9%%Gg>5GN|YBd#tw3(7~HtjF}tK2_@JfBPY3Nu)2tRotQi^<32JV)vdE7z`0ki6L$$%GevLQ^@SS2V(V26r%qn0cNaj zoj&U7Cn1yzzXDw_(!_A~eOi}&%a7ar3;0!Dus~}_xyJfFT_$VbNS7fF_UZ2v7_jSO zF{(u4gdb38kq~@q6EgGsy%c<;$~vl-mcs6#dzcw{1G1f{0tT{%yT&Mz75rzrpGl_* ziP4UjF4knwC^t~6MG;}+{G^?{uz6id7?mjfm6PzFF-e`W{z^4s1OvgY$%1NKYknPM zk2@_u1_IOW9TJXsobYTG1TkNOF-9M6bz^9{#%+EzQ5!n7gtX1X#J@3)UpyTyF(G%+ zfcFMWDfEb1q!`{D4lQ9AYto(g|3%CLvdaJLk8e^X_zDmU~bZxv8*_%7^h%kic zCFzaxj0D}=Ntx7=u@ zdEcQzx1rHWqo&D{KTKr?kUV^}DD|Uxo_;%eEU-X}jl(%#NgGnt`@DQpyXf{v!8A<= zpxmW%@%MDbz+|(`yv1nq=DUlh?U8m`@%01t%UR1oNTib&1xmfssh#Y4N2>E=n7#*kTRCb_mXzqKOERJj! z^#)j$mD3Teef7!M_G>31CZ+3rp6zLB8}Xhr4hDe3oB*Ccp&S21DKqlA)Mhx5TpB}8 z4C`UVp1mr{rhlvT#mj5(d6K7tHbx$j_+hCpWFl=*yF^(Tb+0+Y(NKA%-95Q&K`B~V zDzDZV>Jh-8+B3`?2)o3A^A#HSGqhCCK3&jcTbCvPjktzKa2Dkp5 zF(XGLbTKDH_AhTYgAWi2Xt1x!o3=^1KK4kx4mWx;e#OG(3A8G!pb!_NK|U>kebECg z-B}lM;0MWkfW3z;sOOWmhT7SDLa;erLY-jt^EJx{f%btvhfS4QkxPi^q^AXd!O-0Ky7TREAB6#+ zOn^{|c;Ev-^>nla+>Gc%POIzR>1$6a_Y@H+VE8xl4`zvBW6xHulRb?M10+5%EkcFl z$q$;T^{GOY?#`nWKB1go(~`P^TE!WtIl?&6el#MI3+a}qhQTeJp%_*#U#lGoNOG24 zFpY9mKFR0m6~3Jk@Oi?mY*RG}CtlQqL*m>g6FFlOwEhJD?VJjHD>p3w39Jp-Wfrb_@8l?XrNvZCmlQDsUbN`n zM`=B7xT3!(RPa%PpZ$X0R2&O`II%$qzkO#~3jbEAxkOJs3|N$rUt$I@mQaI1?MTqr zdf-8FYt$0Prbd@WP}RlmHs_YS!sn=u`*}DNjRTgds)u4JbWn!~6O!!0tDcQhDGE?` za^z^byp)X}QxMda7I0qiHk;Bq7?4awa*8gw7Wqq!B0@KGr=}koA*RP zHNxG45pE7@z^c}fW~e_fh;vei!=_O@Jo3C&#GR*Z>CzEy>ecnD5gkz|3j)`xD5nNO z_v<_;c3!AvSJ4XYYBmLEZRw*_zge1~g!o9hpk~H@<&i*+w~{VH074feo zg%FYvqlhgq#(a1TbL|$8^y6+1bC<7}lId@VXwBQoaWFFM4!HrXZLGD*+CvHUm>8{! z4vwh96u-Cw)^r-(QU0!Vbk!Sbbk#^kwJBY~&~)>4f}|&|*<48u3`qn9*Wqkj6gNeY zN&3of=&G1tU9qd+__}!@65SkGx`aC$+8>4a*hit?3FJaQVI<&;l8r@1)!BrqLc)@e z&wUVYtt`W%!Lg0htJYuz%*bPuQt@b%&FC{}3Vf2E8wbrMf+?t?v?w5YDe;S*@P&ON zzYSZ(yhV?0^uuWCC9wt5Je0r%4_#>PKE~--g2Du)E*JSM77}MWrA=pc2%6%Syd75l z*i>N}jyo*M%HAgPu5HyclT@d_<%rw-l~@sdkC<+?b@o5W3>CMSm0f~=L#Qa#mOxBD zT{~MjqgIL90qZ(9m$$iwi;{giX?)tq&M+&g&J&W%ZDVQ-XzZYWbzltLlWKB>sQsmX zTog(NyRMncCIT6(q}`Ze0v2FA!s)f7eOY>PaZC8XNFmA2BUOP;q7@S&=$lcn5e}`c z;-7L9&q)gIf>gL=9vi~QE!tC(FgwhV3O@K#tWh-Sd5vB-NZGltVcRdByby>g zt-wf)LKGMM9DVHk5qznNr1~aRow zKqL@30N!OgX3(a4?{P5^%LcU|17E>{eh?F9QF)&|#H_6xL~7ds$+sy3w7z!3r`;X# zvZ~?)6)o^?nN}7+b>OCMWh~R^?t`G=u>Xr%)R<{yIS|hsTcfCY^^o|l6vJf`Sd@VI zqg_r6&Y7Nwjqi00hm(l9s!Di^OD)Zqj~l9%y90Wa_ewdtG=i9-ucG`J-D8tEO9rL- zX!ip_j!)%gWh#lWMP!LTDAXbo6`g~|^0R94N(u3zVW6bZ+(iJl2hR+f(W0I@Z5-*5)9^2EHndx**ciUzA&{4rRswn-JOX7KJv^q zc`pX{DGPFyG9o_+Jg4TNboZupO~fNUaA=ebcZYfs)%XcF32X@`_&Kf;TLqh(xnkBl zqVQiB7kuVRc5`GX5Ak7R>V8uw7Y5@~hGkFd594WTcYlmrYYhF9VWKe~uzpz*lFIx; z<~haW1W8PiVX9tb#okk&!6zdf`lcbdU0=#mRB(?Yqj-a1XdJBC5R3`ZZxI4 zq}JLnwubv`i>M~qp?8-ZOqr;B2IL%!AqHF>=T+J2*IIo>kUExusFPYaX-h<0?Njyi zjA)lGw64P7hLY}s6#V#Q`o$_xYUdA7Gfyh++FG3S48qa!v`Hhmb zOfV`6w>ng)LD3k{`!*lkhK&(t2t1JXO9=IgMS@@5n;6qZp6iA?J_SBAZ1g>0K)$f3 z{;H2HHpK`ES{2PS48dj-i7FmM*29G+&kJDoZ~})TDXQO8#AB$O*7QiC`5%zhRjTr- zx0(4X96z|UBZ$UvtAvj%vCv_NiYzJXc1@t z)3BWMwXMieUt;gI_J_Fq`^I;%Gk``T?3Tdf4hp~0oW)%GYSPU27~Gk?E0OH0<9fZY z1eDlx(qS0qk9jn?q6x{%=FoO-=C7${cJ0 zkuI7-Qm@0hI^9Jg`X=Zw;P=2I2i6@i0j0_3!A!^c|BYRD*<)bzA!VPv%j5qC2UQkDr(IV7vBHBb7jcDFhN; zU|~rIFN7ZSQKdnHL4}W3n%+bdOPQ^Q_xb($ySq>r?EdCVcIJ^fxjH}M%+yn^AiGA_P>hIGwPjzMC`0A2NYFw80h!7NJ$ryNG_!*Y9sR( zQ@JDrvVq#?cj3jDn!e-P6cO8+61J(yy3MnvlwS| zg(-;Q18)3rxnSPV4Us12y0Na-zp*VV-$*A|p*St#K&CV9GXix2AOgm*)@Ze>Hs}hy zbo(H?dceA>8Z;$hs73Hl6E7p^1=p^Xzu1y$eY9MofE!t`3GgBcabPj_PHCb&)3@Nu z%S$TNcaooRD%YAr0PRtEVR`g1t-rC6+T9*Qrfe>orCTHS`P;k827QVwr74E?wC;o^xh5 z{m9-oj|bJJyftAQ)Rx*BiDVPAR3iCYZ^EMnEOr2ORJw{zPVhGCju>g7d2kvSv@sLoeo*@b^F%D)G*ew;N^== zG}M~nqzQ@{ND$i{t;C}|qjnx`)VkL^V{B-L}PV6*V=Qmfc~|H`TDn&6gl(~Dz49@XZ`@7cyB(x%U)3oQ7YRB zD3EPy6}MsIp5!$Ykr1Yxo6bUtDFa-pEGLJMWu1}yJqT%1)Qj=OQ2OrF!oOiAQvE{! z4mWu3ak1`dQqjh`U*#LTtgc~ReEgp@tweD02LW~i5nw=4UCI(Y zw;_2j$_j=>Cgq}iAzepV*kdzdgqdp|Na0<+t}b(HhI?r!TPh>>1k0WvB!Y%q%5fs% zN047rfQJtMw5$J~Q~hmq9$fQrXo5hobwPGp6Bq|-+XdF$5i2mD!}a+cW7xDrI)n); zyUeh0)tA4fVRfvUw68#5F}17$Hj{kJ!AQe|5Lja4@@^;Dd-}SHnPN6>q!WLeZFMIzi(-AT{$@7j-0?pwnHf+00wOA4msxQrI{~3Vt`T@SAYQi; zI)rsoX!R0=bq*7ORG|mq5#8Kpjh~Mby_4H1v5-m^R)z|YNDH!QT^*Hm&}^m(yph8F z)~(2O*i*7S^V8KsBQ7JgBqo7WqT9Z_q-czEm&+AOW@GY5CnpjVIhwVF8BC?p?Ot$1 z_L%TY!wlt1x$la9B>q%j@10PWsbwhGZgV$neA;bkwLzpBAu8aW86em7<9`A%%vKE$ zMC!X5L})>WtRmx{6Z(?1jA%Y@VlzF64>8YVs$!iZPEYXvYQua!8xP8UkZI>6OyJcR zmea5aJ^c2mXfl}aZh0qO8pUPyb&^@F3{86)&af5kZc2lNBq~xf7nacV5Frid^FKW2>d|is zx>Ap?@z828p~eJBhG`CW*&v$zUdqh;0^Q*&v^lZ&uvJpa`n-r<+iEB6d2mWxsS05n zvwX9gFzCVnLx~&f_f?%Ii1f7|t?O~eg7-@vZ_$^tui1I00MpDsV6OL`37l9gz)UIz z?)zqBaQC7Wp={c(V$k8pPYMW2i(M2c-#%Sq{^~h1$WpA+rMgV8rgIMH-^v0oyjF8j zoG&~u5X{04ubEjAm@+Z^4Wfh8i!DYubd!R|P>P8b|B!dL$yy!)rL@T`h>K^9m2x&& z!2*mb7*3)o(*JN&^xBZ8ym`gH0%kEXLWVeb58r_uk>I(QJLmhum-e$o)#eLH&j!E8M6j7NZzK+G(`@_0wl_bO@_>#g@4ncnpva? z2R?eU0(x!@Bdkvp!&C&-9hLx@@1laD7?6srX-gx6z>GUySE@1#VhS1fHA8cgpMZ7| zl5LfOhDTm+*jL0>%`b=IKT+4X-7mk1rnY5}yfJ6iOhctj(s~3KE0WXyj&X5kR#1PASwdUCSJe#?dPv?gu@I0bu+O5I z7BnizHb4p9cGp28!cs_gHVbH3|C4a7=c!b`UI$!|yJ-l>pi{NfZmF8)jXD{f%W1)a zlO-vr;jqd!8xz0$C-w&Np1|4sn8OZ}!4Izxxif-RWLjh(ndlgzN=5~zK&uAEL30x# zS7jXqPZ(~uTI{K;cs4G1hOAzK{wv%v&qgp)g_W)8)2N|760|y7k5)ii&r+6xAbNyY z8JV`wD{B(zI$gsElv=3SW|-5Jd(yB-+5;NaIOcecR7NGM=1h#-<=*+gpAKOJPh_Yl zLlnw|Y>~-CbrmfqFziar%L!h{>)>W}8c^Q?_)?O%6~YGVPBwJ=Bd=;!7M!k!Nda@H zrpgrBRLp48%__qwnOBbjq``f}EiQ}r|9Wjl zeuI!koxWS#E`<1!V#?1}Rb~%MJz5EhIh_r&J$qHVE5r2g zfhbOAnL%)oN?QasMA@-<$JX-@owHjAZHN|9o(gN-P#yfSPT?8kA9NB1GZQ1I6|H*N z;dyT<^4FOo!pr4?90h@^7B#gAx;q8&o0E8b+p0Pq&bXLtbE;rN$`tJo@bLzgNQ5V(;~0H>c;cNnMa2$a>wDzbFL z^8cXO_y&LaL+I!V>JcsW+f)#e`ZDS8nLtxWk1}{Z!eZKu28fsc;j+)rp6atTwWZDC zAJ>34G~F!itFyINfZOjttXG(?JXSAp#7Ar%J;byApOgG)=8W4bR(1^AzQW{45$;rr zL6{e)T~asl;20k=U8<_2dz_rC5XK@Ul0y7SvseEKvh^BgpW(rZ?3AH;pz2v~gp9~Y z+}jiug*vnFI%%R1)hVT+gdQdXO) zfCjZlIR#qQugJ#@bDm@MUu}MYz}^7=Uxq!I z3abFLz!WhNZ{D85J^OMsmyy1Prh%2*Gn~DN6EB?~L81f#{v~tTH@|=^3+iS+lHjbU29L)XD(hD zH9rJ%%TqfUg!IZJva%$_`PEcE&g^c)nPy)+j2kT5aQ)&=SJagQ7$()N%nA*|YOJ+7 zpr(NhLxb730q^PRUcOA4S=`JX>&IILMV^Ux*d)2the2lH+MtD384s6NrHOJ!G~*$w>A-4Vdxn98 zL>nTfDw!0%SeL`U#$bGaIsF+px%ed)F$JMk0Gu%p0>aKF1$iKy8dLH(o@2a2J@v*A z1qPTQK(|=c$YuZIC2ZeC3Nm;{myyr;Z_Op3#~2;Wd$VzpQ>9A;-uA^ZKW>WHD9Z2% z4Fs#;jeNcs%vP6h?k9JFfGJXPyre1cd@Jby+8urLkm|PRxabz$2mBjYrAvW?mdbfB z$s?9k{TY0&@gBTrWp%bU%bPP>(?Ke)ED3a|ex05hVYVTU`kd&5slanv0ZW~)uANM) zVU+yBKClB_XHng}I_ySfUe%1n_D>gXB_7$N)blcdyHi@Ie4lfce zq57l4b*xS)KT3U*^EWy;1}AaI(7!>dV7ddwF{Ud+7xJAHJ|MzrM!ptsR^Jk9pK3kP zg|xwwMt2Yr7#rs<>vutSbwJo504|x=WpahPwb0eaTZ`rGSc=lE`WH+r!Mg2U zW{S@c`U-8U&G&FdiAfquhqb8Cn-a_B6U>W5YWES+Po|8rK3aoi#_Unb&*$->5uULk zi8|O)mLO?ffWVcbp2_;J;Y0F8Hq&Q$hZZ!O1 zY#_0jnb@4~y~?;&5DPh$aM7HuYu5@oG$=O=m@YVwx!Dzas$r43o&; z@N0Obh3a`|x20bFt1!VMn_x-|aobSF-dXJfr*GU5s;?rDD`WIa1Lei&pZ_=9PzoXi&|0NLkK3H?hO)Xx~5!#Xgm61;9# zbe+omNX{1wnd+?#lsMALcx+R$oXyn&^YX5W{m6lGDHz$ZtUJkj+#IAj68OrA3^H=v zGqPA%+WwSMCF z1vPF~{}i&H=D6i2ORVNdQ~v)((VZmVgIv0WKY*@5zH=+Ok?N4S_sXoF(u~vey@xgRDK>r&NC4O~(wbB{-uORAdK8!TC5V2oUz(y@uFzm3j zQycRNQ&-&T5k<30YEC3WnWS&@lD6;0&l|Pga(u~W)AlNhA6GJyEfwvg9rdt*R1o5T zC??k$^d0PxLy3h2LQ0fddZ@y@cVDJk&v2cK2urwub2fWO(& z_JpUj1-bHNPQIaCXlpcog1YTr>e8|Bb0WQeyis!2bhvT-#mUoZ|JOW&!Gkwy3(-=S zR$$Kbo7D4vkx&H9v<1e$o~JJ3%=tPpQiend%<=CbChwsu%PCsbP~UknT^E)}CQ{lI z8rsP-TIvu(JWVavq|zzIE6GKp5Vq=oRXh-#{VAkUe1gcdo(uIP{1GMGp7ZsFC&k$ju>EX4hJbf7f^QF^yr%u^0%sIMyp-O z-e|k0p*ib934AA>@waiap*Sh!zc;2H4kzPm#8d2;KxiK`RLe7s$)&ok4b9G{ ziX$I4_(D6eKANlC(s~@v%9AB%?zEm%0nj{{BfAEu70eiSl5F2p z+Qg?=J{KzQT7Dnec*4^ROk237FrOMBaL68TNRb)H4LusSV_PJ}I3>gaKUd6wYD3ZJ zmWT6uJ2ny#qZ^){Xl3ZokL})J$wC$xpU%XhmQ5^@736cY>|Uz5QG_2Ckw-egFEE<5 zgkm$`Uyzq@l(Yf$8~+Hax*}jhu5Fh?HMq8d#y90(E9*2Sx#h0@w}1Y(fBv_B{7X7`v8!7gck19=e|B%VsLka0ua<=6=9b=SEtYScnSFt$ zcJG6!qPEkdo#!(*aAg>Kb!f^?ySl8rJLkXi-e1-&ndgFmL507Ex&8Uzi<=%T>9Ma@ zc-d_os>biuxyFl?Sr@JE`gwZnC2-DKG;ABuYOQ9L(fzrDK}2e&7y2o8h-hL*AxR>1@*M4t*em`^j6O1cP znp(sc?sIv9fG^KmdQd*W!uvC6q-gwpO?Y#Etczxdfd=~fLb47Xk+IXqRX!>|Km9Xjm?k#l?w#OB| z6SCcI#kdgGe#linYbM+7gd>A*bE$V^9(mP}M|YKX=X=`0i01Axt(mc&&*|GSzIZs` z@G7e1b2F-EieZU?fxBdghuj_C=X})C%^udCr15om)6G*!M@{+_n%ZAwk`}O~?6K|Y z#;{(A9et#RfqzzN=-ip1K!I;Ct@-Kb^~Hg~`O3+?b#L#zrrk-Q=B^6U5%_5f;?~6z z$M%4DRfBMu_sb%6(9OQcrhred$!@aERj}faWJ*LE zk=URdLrWb)o1t5F#!n(X*h6}x>SQvqE9u^}=iZ%q4d|Ug`qfi?<@YCCLX$jLhf-|i z1@8!n!;_<$7^-q1@aXs)B$4Zw)6Qo_emJ3m3k!ZaN|;Z&WRstg`Yu{!)@*A>s_`_n zlbW`Dhk=1__kv47O{YGUS)^j|qWHRE|GlPxa(8aJw35O)&wOp~_g6rwr4;qhU6vtN z^F6AUE%vA@_SeO{(!r=qc=;aW`z0g$`R;u`!}}fX)%qIs`|RKKEy=?@$yh*aGS6cb zxP2gO70ey*|Mu)WheDCgeHPh4!13hPgz)os@6GB>ZU*-zN=(5K8` zFW1Hv` zsUF|kQ=FEb_eZ^^8)y6L^BLM-zxVqWv`VbruHKG%AHT<)7qnJ99*;UtKgZt@JXSn! z&&SKptk>Qyx6dT9S2n(P#p^-38@r$TZLi9|(mm?ksau`*5{`5ZRxOWNe)sEz<7lmR z-H-1^oNmv*S+~3@pDJzNZ=Tn#oO}mrM%Ux3CW!Ve7q_&*E`KmfLU!Kfa#mky-i86xil%9$KIF$V}b=UPg-_AYG z6!ge+ZC@E}xgSxgTuD=>jR+}vhDsbq_a-J{D5rmq_1Gu& z_5~+PRp&_=EAO28OOo06rf*<+t~3l09+!+$P&37!1$ zFSn4tgr-YC1DZwv9xB85FsC~+>duRM4JT8d!#lb2w9M{OIPZ>Z(>*hG8_`PAP%(4Z$o99QfG&tB1EaG%25lk3Bp!0xl2Q^ zOFTMMi6*sp>;Of(u7UXuzZuGCR|vu*9-VG}!%!Xrp!J>;S7$J}XXOe{2P#_=jY_9i zwslO`SWj`rVYrHH^enYUG}LvjwnM&2kTN$K5s<{Hc*h?CeF_&geB=BKveat*Z-L%< z2HZCgqjJ3&Pn3ssx5L{f<>UYQ47jrkp1#`zrtiCm>F*&d4@@5iVF_@8AOPFPO;`q) zE(XE^@Hn{w;9d3S?w+@g>;s^80sPGvZ%@g`U+xu-xAV~z1$lS&%WLT>LXNC`4`wqa?zj`{(9`+s={xRBCIC8lJGa$3;S!pR6DcI!G? zQZa~th2j^%)DZx57J9Hgb9+qIhe@O)Y9`?~AD;c!*I>Rmq}-91=6<{jNWV6+uY}w? zX5A{-pX1y+F|Pr%_h>1w%pqz&pzE#aPJz5 zZr+OQLcrKZ1JDf{eETK-%CfZ5h7NLb{~gDw(Z5rW`-Yq=>ju^`ULU;!M7&yh8W+D4 zJwLVfaXOb&TgqD>6a7E6*#ENqf93plLh4)3qy3jk?6cRg>X&NtUn%&ds(tc1HvUzU zefB$c{#Bd#*IEB7|2pqqHQIljVVoL>?cIXg)*hGb9S+-DESC2uY|kOsvB>{(yXz2i z+tv>~4a=_TMq}huBU>C^a%k=Je(zo1(}M(7w`&IQHbAbaH@m=473yigCHM$)LV8wA zJI6;nv}4799urn`qFWP{EVB4e-5LST)0Xo!g&p%YA|TE3e|2!?@lfq=9Pih1r<>xI zWm3jeZmBF&)?zYOkv2)mZY+bbW#0xvQn?W>S+a`|LfNv-NS4WN3?pStW2_^LWyXy8 zopb*9eqPUcKkx5z&g;xS^E!WgY2;M*zBMO!s+v8#&2jO5!X?j^#LolwTc?`4)F1uP zP}b^jed^7v=s9y~z4|*-b>G&I-k&MT(1!0hhY7O-6u z*ltY#%vHcF0?a~SYZeGL08j@2CIGO309fe@Nh80w`C)snq$_2fjOaBzkCLA0@3-_@s<67}{5eQ_ zn6$aiYI8uSo<&5>))P*DhvUXz*nU4`i1sjX7HCbueaG|w(8PXHw#@)9tbuMY(B%Q$ z7XT=4%vsRl0Np`g3JsXj2ZC(?i~?W{04M;Ew{lA@JRjBByd3j+l=;-oeuNg7QvBbQ z6j{HlY$Mx^MUKB)wc<91?FyzZlx!EMhLY_J)ljmXpc+ax9;%^aJ3=*-YzL@@l5G#w zP_l7Q4JF$Ss-a}tLN%0Z8>ohoZT&1op|;#IL9xo_1AmMBf>~UqR;5Ht6YDTfIJ36o z(L(fd@|?6)GpIOs7u{2Eyk#$Wabu6ggLiWhuco@m$32fM%})-BSC&LPh@_MW-yJ*9 zpm9lcd>Mc`WB^`c1F(k#!eSNxed_?YX9|R#AoK^fzpVmbOAiQTKzMa=e$r@YVyL<3 zDhcRR`@)j&uG;Y|p3 zgHQ>C?I8RUf?Xg~1Yr#{(;J-G3BvOrEP!T0umgl=K$r~8Tum2I_!?iPHO^|A$}V&0 zD77bQ#YB6YKGHNE8y%wADY9&uHp=;C>*dYFMKU@v;}8;O1GM~2b3qsnLWfQeo(Ewu z2tPt51m!_k0YWNdc7diM2;_FG5O#v_q7Muwio1O@Ye9W53O~Y(~OJwqOP}Qji)d5gVg;ZZqeFv(yAax#8jiITK zN(a?CNc98N51<+asf(cM2u*EhS=m>Vtd7!u)q_Zx3FCTl{G}M0haLM8zq}_-x$(cC zQ+s}{0B1M2MZ5p?^s`2M*b&D3rx+JT6^+RfY25sAEO73}vDcqeq#Rr=R*voYrN3{_ z#F@A?*_(m*zJ&=?ay*Yud;|*+_Nuhb-#PN zCMGlUfQ19rG_XX0WkPP&lpUeE>H6rOlgN`@L5tlR6zBQsd4@Y-_t$3pO0GlPV2(uj zV*BXyf!f!2+L-FTkk)ajA#1xRqU#qy}YoG)C6aTWBXyj+DPD!S}mL%kL^dT zKMAdav#HpAN{}{kc^@$s&XyzP7;HSz8WQ6AqGpvzIeoyzun;!{H7i8QLHS2W%&xSJ z6;NbPWUwuATPcQU*PP~)mu?ZSc_y-+Po8Z%)NE*e{7IgX+Frdm*Zr~g>3>P(7XgAk zAjAT~K0x>`2?)x7-~b4nfDjJ|2tZH-1l@zcgrk6P0T8SKfeZ))Kv+5|HBgt|9Ny;` zHyy|m@I(a81ZU5{D_!jYxbzVMH@}%2hr!mpp&1FLvIVb11v?WV>^Q;C35A{MCjO)c z#sd8IW4wEZ{d@9vC)*jFiRaXu(J$XVucqUmKh4qklc|C5mFuD?jqynTon2s)t!^e> zc~+&`iU01Qw`y_ji9X zhF;JsX_w%^Ct0@V?~!`SdfzuvDcRvKf3;gZSvdawFyCqfQJ%j3pnLFMV##>i2!fp@S`Ec-sz%*yA`tjGD-g_J+PJ+ zo;&ozXO~LJqp=NWkzV9fG2I0Hlvj3U2!qBN z()+(l=g`vDX4t;zz{?ia#;^pBR+D7rh_}iZ*0?zI4fZR2#!%^DdZUy=++cvpuaMm5 z=PK4Y#)*^zfsIn)x@xx)m9((Z_`GLY^c(3_$-|3kg3$=y+){?|J38l8nq1NP15HJO^{=jSVAN3H#Ne**nPC|jWA~( zx89PiXl=IR>#*J%5oeMj-kn#nX@ap1^>pRT^ZQZ+E{vTRxh{1~@Yd|iG^I;KTA_S2 zI52=wzhLTaV{5#jAhml?;_+m!Zi72hMz65+x|W{Yp^I^Wor<@j#nD44226ob{;N z?vAAwrd;&fyJ+`+bEhbA9(2(LueEtA)!(=)N2Hg2qzGpECtugM_~E-kF}4Q?Pg_+cBzyI`6DMnl-!_8B0f+%ULBgG@kSnaU3zuc$NQU zdflk>@uI}&tbKi%{UFERM^*q_pJ0n%hq+?B@ zUB3=n^Y!Mnh-q&b-J6H`)k(37r@5BOmo;xS{x}#Up+`_n71e23soJ#5WhUUK-4}h- zQO9rmHFq_$;M8i={)5q%PRSsyeU>UeUOLxy0gr(j57=uUuh;huqlFC_`5uPePK|K5 z%aJ!sXQOEGw$V0Mug6v`hFRimpLFHTMZ}DlP1v(aT2%)WB>za{_ULMu9DUtW5)S-m zSanp)5C38o&&R+pE9TjFZcD@~)>0&)pjxAJTev&ySc0Ex6vn!}Qbvs^aqM)F zgJKD>^T6Q};g9F!mMlA_*95C#yCu747~Y2d;b!wDL>;wUVYfA-7^m)jVWiw)1ns#V7H0p!L)Ab{ zAd7G5KZymmvWbPg!0(_apQ!TG#|2$kmqf#{nbVu|nF$a|%X!y9j^cQwS;y*2T zy@hL8#@(-daQcL0q!&8fz&E$9-t=;+*iW098bq11T*P=4wGat|u~LsR_Oy@A?rdiZ7#(~;7luzW=nPv%Hiq1$vX48sHG~uAuB*gp z8M@T`iN|m!77MMk101D^DmxSbyHA?GL)iwNwAD}}+mavr>_o)1011v|-vf`f!=x&UqMF^s zdfO^AfnW!G*e@df_D&i}gqPGPqU&jnM^6kt^t>)wZ)M~uTF8m` zCNE;PAYxHONsx(S6EGkO}$eh+sF5}WUA2=$?xstlL&DF$}r6d+kS5GqW ztMiLMs;K8;>i+glt+i4~HVPZ7Ynk#@`XZkS>kwPtXLpucO68!zY}D5-%Tk)pABE+4 fjCrr-_v-(?k1%j;d!U>C53~F8>+Sc^O$&Yl_JmkZ diff --git a/.etc/benches/registry_data.nbt b/.etc/benches/registry_data.nbt deleted file mode 100644 index d26ff7c219da03a5310e7a1888b6fc56808bed45..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46685 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%0AES@>}&nAzqpDV*gC-o_@3LoOsD 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

>aACDL^B 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 diff --git a/.etc/hello_world.nbt b/.etc/hello_world.nbt deleted file mode 100644 index f3f5e21aacfc8ce832a459c6c6d827127109cb5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33 ocmd;L;Lb?R$;nqJ&o9bJ;b36NOUzAW;B-pNOUx@u%uQqf0G#LvsQ>@~ diff --git a/.etc/realworld.nbt b/.etc/realworld.nbt deleted file mode 100644 index 8b4472ebb73faefb16013cb46231572975aed6ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18670 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 by8m6hEn^MCyT->)1ypahe`23vcbxwR^J1TA diff --git a/.etc/registry.packet b/.etc/registry.packet deleted file mode 100644 index 0fac34c4c9c1e087a91a0adc202c54ee666655ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49139 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 diff --git a/.etc/registry_codec.nbt b/.etc/registry_codec.nbt deleted file mode 100644 index 32927e6eeb32be548fd94c06a5c3d1c26ba9fab9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39820 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 diff --git a/src/adapters/anvil/benches/anvil.rs b/src/adapters/anvil/benches/anvil.rs index 2b181f27..4f916f90 100644 --- a/src/adapters/anvil/benches/anvil.rs +++ b/src/adapters/anvil/benches/anvil.rs @@ -11,7 +11,7 @@ fn criterion_benchmark(c: &mut Criterion) { read_all_group.bench_function("temper Rayon", |b| { b.iter(|| { - let file_path = PathBuf::from("../../../../.etc/r.0.0.mca"); + let file_path = PathBuf::from("../../../.etc/r.0.0.mca"); let loaded_file = load_anvil_file(file_path).unwrap(); let locations = loaded_file.get_locations(); locations.chunks(96).par_bridge().for_each(|chunk| { @@ -24,7 +24,7 @@ fn criterion_benchmark(c: &mut Criterion) { read_all_group.bench_function("temper", |b| { b.iter(|| { - let file_path = PathBuf::from("../../../../.etc/r.0.0.mca"); + let file_path = PathBuf::from("../../../.etc/r.0.0.mca"); let loaded_file = load_anvil_file(file_path).unwrap(); let locations = loaded_file.get_locations(); locations.iter().for_each(|location| { @@ -39,7 +39,7 @@ fn criterion_benchmark(c: &mut Criterion) { read_all_group.bench_function("FastAnvil", |b| { b.iter(|| { - let file = File::open("../../../../.etc/r.0.0.mca").unwrap(); + let file = File::open("../../../.etc/r.0.0.mca").unwrap(); let mut region = Region::from_stream(file).unwrap(); region.iter().for_each(|chunk| { black_box(chunk.unwrap().data); @@ -53,7 +53,7 @@ fn criterion_benchmark(c: &mut Criterion) { read_one_group.bench_function("temper", |b| { b.iter(|| { - let file_path = PathBuf::from("../../../../.etc/r.0.0.mca"); + let file_path = PathBuf::from("../../../.etc/r.0.0.mca"); let loaded_file = load_anvil_file(file_path).unwrap(); black_box(loaded_file.get_chunk(0, 0).expect("bad chunk")); }); @@ -61,7 +61,7 @@ fn criterion_benchmark(c: &mut Criterion) { read_one_group.bench_function("FastAnvil", |b| { b.iter(|| { - let file = File::open("../../../../.etc/r.0.0.mca").unwrap(); + let file = File::open("../../../.etc/r.0.0.mca").unwrap(); let mut region = Region::from_stream(file).unwrap(); black_box(region.read_chunk(0, 0).unwrap()); }); diff --git a/src/world/src/benches/edit_bench.rs b/src/world/src/benches/edit_bench.rs index fbb5acbd..00393b26 100644 --- a/src/world/src/benches/edit_bench.rs +++ b/src/world/src/benches/edit_bench.rs @@ -12,8 +12,8 @@ fn get_rand_in_range(min: i32, max: i32) -> i32 { #[expect(clippy::unit_arg)] pub(crate) fn bench_edits(c: &mut Criterion) { - let chunk_data = std::fs::read("../../../.etc/raw_chunk.dat").unwrap(); - let chunk: Chunk = bitcode::decode(&chunk_data) + let chunk_data = include_bytes!("../../../../.etc/raw_chunk.dat"); + let chunk: Chunk = bitcode::decode(chunk_data) .expect("If this fails, go run the dump_chunk test at src/lib/world/src/mod"); let mut read_group = c.benchmark_group("edit_read"); From c9c17fcc285b186f9748f89b9e80684bf06b9529 Mon Sep 17 00:00:00 2001 From: ReCore Date: Fri, 27 Mar 2026 16:48:08 +1030 Subject: [PATCH 9/9] unify playeridentity and entityidentity into just identity --- src/commands/src/arg/entities/any_entity.rs | 12 +- src/commands/src/arg/entities/any_player.rs | 19 +-- src/commands/src/arg/entities/entity_uuid.rs | 20 +-- src/commands/src/arg/entities/mod.rs | 137 ++++++++++++------ src/commands/src/arg/entities/player.rs | 22 ++- .../src/arg/entities/random_player.rs | 12 +- src/components/src/entity_identity.rs | 30 ++-- src/components/src/player/mod.rs | 3 +- src/components/src/player/pending_events.rs | 4 +- src/components/src/player/player_bundle.rs | 10 +- src/components/src/player/player_identity.rs | 27 ---- src/components/src/player/player_marker.rs | 4 + .../src/player/player_properties.rs | 13 ++ src/default_commands/src/echo.rs | 12 +- src/default_commands/src/fly.rs | 13 +- src/default_commands/src/kill.rs | 15 +- src/default_commands/src/nested.rs | 14 +- src/default_commands/src/say.rs | 10 +- src/default_commands/src/tp.rs | 6 +- src/entities/src/bundles/mod.rs | 6 +- .../src/background/src/connection_killer.rs | 32 ++-- .../src/background/src/send_entity_updates.rs | 9 +- .../src/background/src/world_sync.rs | 4 +- src/game_systems/src/mobs/src/pig.rs | 8 +- .../src/packets/src/chat_message.rs | 10 +- src/game_systems/src/packets/src/command.rs | 9 +- .../src/packets/src/pick_item_from_block.rs | 8 +- .../src/packets/src/player_command.rs | 12 +- .../src/packets/src/player_input.rs | 14 +- .../src/packets/src/player_loaded.rs | 10 +- src/game_systems/src/packets/src/swing_arm.rs | 8 +- .../src/player/src/emit_player_joined.rs | 7 +- .../src/player/src/entity_spawn.rs | 4 +- .../src/player/src/gamemode_change.rs | 19 ++- .../src/player/src/movement_broadcast.rs | 6 +- .../src/player/src/new_connections.rs | 24 ++- .../src/player/src/player_despawn.rs | 7 +- .../src/player/src/player_join_message.rs | 16 +- .../src/player/src/player_leave_message.rs | 19 +-- .../src/player/src/player_spawn.rs | 31 ++-- .../src/player/src/player_swimming.rs | 8 +- src/game_systems/src/player/src/player_tp.rs | 8 +- .../src/player/src/update_player_ping.rs | 4 +- .../src/shutdown/src/send_shutdown_packet.rs | 14 +- src/messages/src/player_join.rs | 4 +- src/messages/src/player_leave.rs | 4 +- .../src/outgoing/entity_position_sync.rs | 8 +- .../src/outgoing/player_info_update.rs | 31 ++-- .../protocol/src/outgoing/remove_entities.rs | 8 +- src/net/protocol/src/outgoing/spawn_entity.rs | 37 +---- .../src/outgoing/update_entity_position.rs | 12 +- .../update_entity_position_and_rotation.rs | 8 +- .../src/outgoing/update_entity_rotation.rs | 8 +- src/net/runtime/src/auth.rs | 2 +- src/net/runtime/src/conn_init/login.rs | 42 ++++-- src/net/runtime/src/conn_init/mod.rs | 6 +- src/net/runtime/src/conn_init/status.rs | 1 + src/net/runtime/src/connection.rs | 15 +- 58 files changed, 464 insertions(+), 402 deletions(-) delete mode 100644 src/components/src/player/player_identity.rs create mode 100644 src/components/src/player/player_marker.rs create mode 100644 src/components/src/player/player_properties.rs diff --git a/src/commands/src/arg/entities/any_entity.rs b/src/commands/src/arg/entities/any_entity.rs index 89e0c61a..e39f441c 100644 --- a/src/commands/src/arg/entities/any_entity.rs +++ b/src/commands/src/arg/entities/any_entity.rs @@ -1,15 +1,9 @@ use bevy_ecs::prelude::Entity; -use temper_components::entity_identity::EntityIdentity; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; +use temper_components::player::player_marker::PlayerMarker; pub(crate) fn resolve_any_entity<'a>( - iter: impl Iterator< - Item = ( - Entity, - Option<&'a EntityIdentity>, - Option<&'a PlayerIdentity>, - ), - >, + iter: impl Iterator)>, ) -> Vec { iter.map(|(entity, _, _)| entity).collect() } diff --git a/src/commands/src/arg/entities/any_player.rs b/src/commands/src/arg/entities/any_player.rs index d9775527..c61117b1 100644 --- a/src/commands/src/arg/entities/any_player.rs +++ b/src/commands/src/arg/entities/any_player.rs @@ -1,21 +1,16 @@ use bevy_ecs::entity::Entity; -use temper_components::entity_identity::EntityIdentity; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; +use temper_components::player::player_marker::PlayerMarker; pub(crate) fn resolve_any_player<'a>( - iter: impl Iterator< - Item = ( - Entity, - Option<&'a EntityIdentity>, - Option<&'a PlayerIdentity>, - ), - >, + iter: impl Iterator)>, ) -> Vec { let mut players = Vec::new(); - for (entity, _, player_id) in iter { - if player_id.is_some() { - players.push(entity); + for (entity, _, player_marker) in iter { + if player_marker.is_none() { + continue; } + players.push(entity); } players } diff --git a/src/commands/src/arg/entities/entity_uuid.rs b/src/commands/src/arg/entities/entity_uuid.rs index e551c2f1..c5a3e459 100644 --- a/src/commands/src/arg/entities/entity_uuid.rs +++ b/src/commands/src/arg/entities/entity_uuid.rs @@ -1,23 +1,15 @@ use bevy_ecs::entity::Entity; -use temper_components::entity_identity::EntityIdentity; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; +use temper_components::player::player_marker::PlayerMarker; use uuid::Uuid; pub(crate) fn resolve_uuid<'a>( uuid: Uuid, - iter: impl Iterator< - Item = ( - Entity, - Option<&'a EntityIdentity>, - Option<&'a PlayerIdentity>, - ), - >, + iter: impl Iterator)>, ) -> Option { - for (entity, entity_id_opt, player_id_opt) in iter { - match (player_id_opt, entity_id_opt) { - (Some(player_id), _) if player_id.uuid == uuid => return Some(entity), - (_, Some(entity_id)) if entity_id.uuid == uuid => return Some(entity), - _ => {} + for (entity, entity_id_opt, _) in iter { + if entity_id_opt.uuid == uuid { + return Some(entity); } } None diff --git a/src/commands/src/arg/entities/mod.rs b/src/commands/src/arg/entities/mod.rs index a59d1ab5..d337fb91 100644 --- a/src/commands/src/arg/entities/mod.rs +++ b/src/commands/src/arg/entities/mod.rs @@ -7,10 +7,10 @@ mod random_player; use crate::arg::primitive::PrimitiveArgument; use crate::arg::{CommandArgument, ParserResult}; use crate::{CommandContext, Suggestion}; -use ::uuid::Uuid; use bevy_ecs::prelude::Entity; -use temper_components::entity_identity::EntityIdentity; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; +use temper_components::player::player_marker::PlayerMarker; +use ::uuid::Uuid; /// Represents an entity argument in a command. /// It can be a player name, UUID, or special selectors like @e, @p, @r, @a. @@ -20,10 +20,10 @@ use temper_components::player::player_identity::PlayerIdentity; /// ```ignore /// # use temper_commands::arg::entities::EntityArgument; /// # use temper_core::identity::entity_identity::EntityIdentity; -/// # use temper_core::identity::player_identity::PlayerIdentity; +/// # use temper_core::identity::player_identity::Identity; /// # use bevy_ecs::prelude::World; /// -/// fn my_command(query: Query<(Entity, Option<&EntityIdentity>, Option<&PlayerIdentity>)>) { +/// fn my_command(query: Query<(Entity, Option<&EntityIdentity>, Option<&Identity>)>) { /// let arg = EntityArgument::PlayerName("Steve".to_string()); /// let result = arg.resolve(query.iter()); /// assert_eq!(result, vec![entity]); @@ -106,13 +106,7 @@ impl CommandArgument for EntityArgument { impl EntityArgument { pub fn resolve<'a>( &self, - iter: impl Iterator< - Item = ( - Entity, - Option<&'a EntityIdentity>, - Option<&'a PlayerIdentity>, - ), - >, + iter: impl Iterator)>, ) -> Vec { match self { EntityArgument::PlayerName(name) => player::resolve_player_name(name.clone(), iter) @@ -140,8 +134,7 @@ mod tests { use crate::{Command, CommandInput, Sender}; use bevy_ecs::prelude::World; use std::sync::Arc; - use temper_components::entity_identity::EntityIdentity; - use temper_components::player::player_identity::PlayerIdentity; + use temper_components::entity_identity::Identity; use temper_state::create_test_state; #[test] @@ -245,23 +238,19 @@ mod tests { let mut world = World::new(); let entity = world .spawn(( - EntityIdentity { - entity_id: 0, - uuid: Uuid::new_v4(), - }, - PlayerIdentity { - username: "Steve".to_string(), + Identity { + name: Some("Steve".to_string()), uuid: Default::default(), - short_uuid: 0, - properties: vec![], + entity_id: 0, }, + PlayerMarker, )) .id(); let arg = EntityArgument::PlayerName("Steve".to_string()); let result = arg.resolve( world - .query::<(Entity, Option<&EntityIdentity>, Option<&PlayerIdentity>)>() + .query::<(Entity, &Identity, Option<&PlayerMarker>)>() .iter(&world), ); assert_eq!(result, vec![entity]); @@ -272,15 +261,16 @@ mod tests { let mut world = World::new(); let test_uuid = Uuid::new_v4(); let entity = world - .spawn((EntityIdentity { + .spawn((Identity { entity_id: 0, uuid: test_uuid, + name: None, },)) .id(); let arg = EntityArgument::Uuid(test_uuid); let result = arg.resolve( world - .query::<(Entity, Option<&EntityIdentity>, Option<&PlayerIdentity>)>() + .query::<(Entity, &Identity, Option<&PlayerMarker>)>() .iter(&world), ); assert_eq!(result, vec![entity]); @@ -290,21 +280,23 @@ mod tests { fn test_resolves_any_entity() { let mut world = World::new(); let entity1 = world - .spawn(EntityIdentity { + .spawn(Identity { entity_id: 0, uuid: Uuid::new_v4(), + name: None, }) .id(); let entity2 = world - .spawn(EntityIdentity { + .spawn(Identity { entity_id: 1, uuid: Uuid::new_v4(), + name: None, }) .id(); let arg = EntityArgument::AnyEntity; let result = arg.resolve( world - .query::<(Entity, Option<&EntityIdentity>, Option<&PlayerIdentity>)>() + .query::<(Entity, &Identity, Option<&PlayerMarker>)>() .iter(&world), ); assert_eq!(result.len(), 2); @@ -317,42 +309,35 @@ mod tests { let mut world = World::new(); let entity1 = world .spawn(( - EntityIdentity { - entity_id: 0, - uuid: Uuid::new_v4(), - }, - PlayerIdentity { - username: "Steve".to_string(), + Identity { + name: Some("Steve".to_string()), uuid: Uuid::new_v4(), - short_uuid: 0, - properties: vec![], + entity_id: 0, }, + PlayerMarker, )) .id(); let entity2 = world .spawn(( - EntityIdentity { + Identity { + name: Some("Alex".to_string()), entity_id: 1, uuid: Uuid::new_v4(), }, - PlayerIdentity { - username: "Alex".to_string(), - uuid: Uuid::new_v4(), - short_uuid: 1, - properties: vec![], - }, + PlayerMarker, )) .id(); let non_player_entity = world - .spawn(EntityIdentity { + .spawn((Identity { entity_id: 2, uuid: Uuid::new_v4(), - }) + name: None, + },)) .id(); let arg = EntityArgument::AnyPlayer; let result = arg.resolve( world - .query::<(Entity, Option<&EntityIdentity>, Option<&PlayerIdentity>)>() + .query::<(Entity, &Identity, Option<&PlayerMarker>)>() .iter(&world), ); assert_eq!(result.len(), 2); @@ -360,4 +345,64 @@ mod tests { assert!(result.contains(&entity2)); assert!(!result.contains(&non_player_entity)); } + + #[test] + fn resolves_random_player() { + let mut world = World::new(); + let entity1 = world + .spawn(( + Identity { + name: Some("Steve".to_string()), + uuid: Uuid::new_v4(), + entity_id: 0, + }, + PlayerMarker, + )) + .id(); + let entity2 = world + .spawn(( + Identity { + name: Some("Alex".to_string()), + entity_id: 1, + uuid: Uuid::new_v4(), + }, + PlayerMarker, + )) + .id(); + let non_player_entity = world + .spawn((Identity { + entity_id: 2, + uuid: Uuid::new_v4(), + name: None, + },)) + .id(); + let arg = EntityArgument::RandomPlayer; + let result = arg.resolve( + world + .query::<(Entity, &Identity, Option<&PlayerMarker>)>() + .iter(&world), + ); + assert_eq!(result.len(), 1); + assert!(result.contains(&entity1) || result.contains(&entity2)); + assert!(!result.contains(&non_player_entity)); + + // Run the test 500 times to ensure randomness + // Technically this could actually result in 500 identical results, but the odds of that are astronomically low (about 1 in 3.27e150) + let mut results = vec![]; + for _ in 0..500 { + let result = arg.resolve( + world + .query::<(Entity, &Identity, Option<&PlayerMarker>)>() + .iter(&world), + ); + assert_eq!(result.len(), 1); + results.push(result[0]); + } + let unique_results: std::collections::HashSet<_> = results.into_iter().collect(); + assert_eq!( + unique_results.len(), + 2, + "Random player selection is not random enough" + ); + } } diff --git a/src/commands/src/arg/entities/player.rs b/src/commands/src/arg/entities/player.rs index 016ccb33..da3be1f8 100644 --- a/src/commands/src/arg/entities/player.rs +++ b/src/commands/src/arg/entities/player.rs @@ -1,20 +1,18 @@ use bevy_ecs::prelude::Entity; -use temper_components::entity_identity::EntityIdentity; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; +use temper_components::player::player_marker::PlayerMarker; pub(crate) fn resolve_player_name<'a>( name: String, - iter: impl Iterator< - Item = ( - Entity, - Option<&'a EntityIdentity>, - Option<&'a PlayerIdentity>, - ), - >, + iter: impl Iterator)>, ) -> Option { - for (entity, _, player_id) in iter { - if let Some(identity) = player_id - && identity.username == name + for (entity, id, player_marker) in iter { + if player_marker.is_some() + && id + .name + .as_ref() + .map(|n| n.eq_ignore_ascii_case(&name)) + .unwrap_or(false) { return Some(entity); } diff --git a/src/commands/src/arg/entities/random_player.rs b/src/commands/src/arg/entities/random_player.rs index 7ea89a80..09898a5f 100644 --- a/src/commands/src/arg/entities/random_player.rs +++ b/src/commands/src/arg/entities/random_player.rs @@ -1,16 +1,10 @@ use bevy_ecs::entity::Entity; use rand::prelude::IteratorRandom; -use temper_components::entity_identity::EntityIdentity; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; +use temper_components::player::player_marker::PlayerMarker; pub(crate) fn resolve_random_player<'a>( - iter: impl Iterator< - Item = ( - Entity, - Option<&'a EntityIdentity>, - Option<&'a PlayerIdentity>, - ), - >, + iter: impl Iterator)>, ) -> Option { let mut rng = rand::thread_rng(); iter.filter_map(|(entity, _, player_id)| { diff --git a/src/components/src/entity_identity.rs b/src/components/src/entity_identity.rs index fe899884..ab84c942 100644 --- a/src/components/src/entity_identity.rs +++ b/src/components/src/entity_identity.rs @@ -8,53 +8,59 @@ use std::sync::atomic::{AtomicI32, Ordering}; /// starting from a high number to avoid collisions with player short_uuid. static ENTITY_ID_COUNTER: AtomicI32 = AtomicI32::new(1_000_000); -/// Identity component for non-player entities. -/// -/// Similar to PlayerIdentity but for physics and other entities. -/// Contains a unique network ID and UUID for the entity. +/// Identity component for entities in the game world, including players and non-player entities (mobs, items, etc.). /// /// # Examples /// /// ```ignore -/// use temper_core::identity::EntityIdentity; +/// use temper_core::identity::Identity; /// -/// let pig_identity = EntityIdentity::new(); +/// let pig_identity = Identity::new(Some("Pig".to_string())); /// assert!(pig_identity.entity_id >= 1_000_000); /// ``` #[derive(Debug, Component, Clone)] -pub struct EntityIdentity { +pub struct Identity { /// Network entity ID used in packets. /// Must be unique across all entities in the server. + /// For players, this is generally the first 4 bytes of the player's UUID, unless multiple + /// players have the same UUID (eg. offline mode) in which case it will be random. pub entity_id: i32, /// Unique identifier for this entity. /// Generated randomly for each spawned entity. + /// For players, this is the full UUID from Mojang's authentication system. pub uuid: uuid::Uuid, + + /// Optional name for the entity + /// For players, this is the username. For other entities, it can be None or a custom name. + pub name: Option, } -impl EntityIdentity { +impl Identity { /// Creates a new entity identity with a unique ID and UUID. /// /// The entity_id is generated from an atomic counter to ensure uniqueness. /// The UUID is randomly generated. - pub fn new() -> Self { + pub fn new(name: Option) -> Self { Self { entity_id: ENTITY_ID_COUNTER.fetch_add(1, Ordering::SeqCst), uuid: uuid::Uuid::new_v4(), + name, } } /// Creates an entity identity with a specific UUID (for loading from disk). - pub fn with_uuid(uuid: uuid::Uuid) -> Self { + pub fn with_uuid(uuid: uuid::Uuid, name: Option) -> Self { Self { entity_id: ENTITY_ID_COUNTER.fetch_add(1, Ordering::SeqCst), uuid, + name, } } } -impl Default for EntityIdentity { +impl Default for Identity { fn default() -> Self { - Self::new() + Self::new(None) } } diff --git a/src/components/src/player/mod.rs b/src/components/src/player/mod.rs index eb0dacbf..e525c41f 100644 --- a/src/components/src/player/mod.rs +++ b/src/components/src/player/mod.rs @@ -10,7 +10,8 @@ pub mod keepalive; pub mod offline_player_data; pub mod pending_events; pub mod player_bundle; -pub mod player_identity; +pub mod player_marker; +pub mod player_properties; pub mod position; pub mod rotation; pub mod sneak; diff --git a/src/components/src/player/pending_events.rs b/src/components/src/player/pending_events.rs index 13b12430..a15b5297 100644 --- a/src/components/src/player/pending_events.rs +++ b/src/components/src/player/pending_events.rs @@ -8,7 +8,7 @@ //! This ensures events about entities only fire after the entity is fully queryable. //! See `docs/_internal/deferred-commands-event-timing.md` for details. -use crate::player::player_identity::PlayerIdentity; +use crate::entity_identity::Identity; use bevy_ecs::prelude::Component; /// Marker component indicating a player entity was just created and needs @@ -16,4 +16,4 @@ use bevy_ecs::prelude::Component; /// /// Added by `accept_new_connections`, removed by `emit_player_joined`. #[derive(Component)] -pub struct PendingPlayerJoin(pub PlayerIdentity); +pub struct PendingPlayerJoin(pub Identity); diff --git a/src/components/src/player/player_bundle.rs b/src/components/src/player/player_bundle.rs index fbff6ea9..9098ca60 100644 --- a/src/components/src/player/player_bundle.rs +++ b/src/components/src/player/player_bundle.rs @@ -1,7 +1,9 @@ use crate::bounds::CollisionBounds; +use crate::entity_identity::Identity; use crate::player::chunk_receiver::ChunkReceiver; use crate::player::grounded::OnGround; -use crate::player::player_identity::PlayerIdentity; +use crate::player::player_marker::PlayerMarker; +use crate::player::player_properties::PlayerProperties; use crate::player::position::Position; use crate::player::rotation::Rotation; use crate::{ @@ -21,11 +23,12 @@ use temper_inventories::{hotbar::Hotbar, inventory::Inventory}; #[derive(Bundle, Default)] pub struct PlayerBundle { // Identity - pub identity: PlayerIdentity, + pub identity: Identity, // Core State pub abilities: PlayerAbilities, pub gamemode: GameModeComponent, + pub player_properties: PlayerProperties, // Position/World pub position: Position, @@ -48,4 +51,7 @@ pub struct PlayerBundle { // Movement State pub swimming: SwimmingState, pub sneak: SneakState, + + // Player Marker + pub player_marker: PlayerMarker, } diff --git a/src/components/src/player/player_identity.rs b/src/components/src/player/player_identity.rs deleted file mode 100644 index 6ce1a08a..00000000 --- a/src/components/src/player/player_identity.rs +++ /dev/null @@ -1,27 +0,0 @@ -use bevy_ecs::prelude::Component; - -#[derive(Debug, Component, Default, Clone)] -pub struct PlayerIdentity { - pub username: String, - pub uuid: uuid::Uuid, - pub short_uuid: i32, - pub properties: Vec, -} - -impl PlayerIdentity { - pub fn new(username: String, uuid: u128, properties: Vec) -> Self { - Self { - username, - uuid: uuid::Uuid::from_u128(uuid), - short_uuid: uuid as i32, - properties, - } - } -} - -#[derive(Debug, Default, Clone)] -pub struct PlayerProperty { - pub name: String, - pub value: String, - pub signature: Option, -} diff --git a/src/components/src/player/player_marker.rs b/src/components/src/player/player_marker.rs new file mode 100644 index 00000000..26002ddb --- /dev/null +++ b/src/components/src/player/player_marker.rs @@ -0,0 +1,4 @@ +use bevy_ecs::prelude::Component; + +#[derive(Debug, Component, Default)] +pub struct PlayerMarker; diff --git a/src/components/src/player/player_properties.rs b/src/components/src/player/player_properties.rs new file mode 100644 index 00000000..82221d2f --- /dev/null +++ b/src/components/src/player/player_properties.rs @@ -0,0 +1,13 @@ +use bevy_ecs::prelude::Component; + +#[derive(Debug, Default, Clone)] +pub struct PlayerProperty { + pub name: String, + pub value: String, + pub signature: Option, +} + +#[derive(Debug, Component, Default, Clone)] +pub struct PlayerProperties { + pub properties: Vec, +} diff --git a/src/default_commands/src/echo.rs b/src/default_commands/src/echo.rs index e79e7e5f..520781a7 100644 --- a/src/default_commands/src/echo.rs +++ b/src/default_commands/src/echo.rs @@ -1,21 +1,19 @@ use bevy_ecs::prelude::*; use temper_commands::{arg::primitive::string::GreedyString, Sender}; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; use temper_macros::command; use temper_text::{TextComponent, TextComponentBuilder}; #[command("echo")] -fn test_command( - #[arg] message: GreedyString, - #[sender] sender: Sender, - query: Query<&PlayerIdentity>, -) { +fn test_command(#[arg] message: GreedyString, #[sender] sender: Sender, query: Query<&Identity>) { let username = match sender { Sender::Server => "Server".to_string(), Sender::Player(entity) => query .get(entity) .expect("sender does not exist") - .username + .name + .as_ref() + .expect("No Player Name") .clone(), }; diff --git a/src/default_commands/src/fly.rs b/src/default_commands/src/fly.rs index 70cd6c2d..33f174d7 100644 --- a/src/default_commands/src/fly.rs +++ b/src/default_commands/src/fly.rs @@ -1,7 +1,7 @@ use bevy_ecs::prelude::*; use temper_commands::Sender; +use temper_components::entity_identity::Identity; use temper_components::player::abilities::PlayerAbilities; -use temper_components::player::player_identity::PlayerIdentity; use temper_macros::command; use temper_net_runtime::connection::StreamWriter; use temper_protocol::outgoing::player_abilities::PlayerAbilities as OutgoingPlayerAbilities; @@ -12,7 +12,7 @@ use tracing::{error, info}; #[command("fly")] fn fly_command( #[sender] sender: Sender, - mut player_query: Query<(Entity, &PlayerIdentity, &mut PlayerAbilities, &StreamWriter)>, + mut player_query: Query<(Entity, &Identity, &mut PlayerAbilities, &StreamWriter)>, ) { // 1. Ensure the sender is a player let player_entity = match sender { @@ -58,7 +58,8 @@ fn fly_command( if let Err(e) = writer.send_packet_ref(&sync_packet) { error!( "Failed to send abilities sync packet to {}: {:?}", - identity.username, e + identity.name.as_ref().expect("No Player Name"), + e ); } @@ -69,5 +70,9 @@ fn fly_command( ); // 6. Log the action - info!("Toggled flying for {}: {}", identity.username, status); + info!( + "Toggled flying for {}: {}", + identity.name.as_ref().expect("No Player Name"), + status + ); } diff --git a/src/default_commands/src/kill.rs b/src/default_commands/src/kill.rs index 5be4ab3d..37386d80 100644 --- a/src/default_commands/src/kill.rs +++ b/src/default_commands/src/kill.rs @@ -1,10 +1,11 @@ #![expect(clippy::type_complexity)] + use bevy_ecs::prelude::{Commands, Entity, Query}; use temper_codec::net_types::length_prefixed_vec::LengthPrefixedVec; use temper_commands::arg::entities::EntityArgument; use temper_commands::Sender; -use temper_components::entity_identity::EntityIdentity; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; +use temper_components::player::player_marker::PlayerMarker; use temper_macros::command; use temper_net_runtime::connection::StreamWriter; use temper_protocol::outgoing::remove_entities::RemoveEntitiesPacket; @@ -16,7 +17,7 @@ fn kill_command( #[sender] sender: Sender, #[arg] entity_argument: EntityArgument, args: ( - Query<(Entity, Option<&EntityIdentity>, Option<&PlayerIdentity>)>, + Query<(Entity, &Identity, Option<&PlayerMarker>)>, Commands, Query<&StreamWriter>, ), @@ -38,12 +39,12 @@ fn kill_command( overlay: false, }; for entity in selected_entities { - if let Ok((ent, entity_id_opt, player_id_opt)) = query.get(entity) { - if let Some(entity_id) = entity_id_opt { - removed_entities.push(entity_id.entity_id.into()); + if let Ok((ent, identity, player_marker)) = query.get(entity) { + if player_marker.is_none() { + removed_entities.push(identity.entity_id.into()); cmd.entity(ent).despawn(); removed_count += 1; - } else if let Some(_player_id) = player_id_opt { + } else { // Don't remove players, just send them a killed message if let Ok(conn) = conn_query.get(ent) { if let Err(err) = conn.send_packet_ref(&killed_message) { diff --git a/src/default_commands/src/nested.rs b/src/default_commands/src/nested.rs index 7a67d368..9483a265 100644 --- a/src/default_commands/src/nested.rs +++ b/src/default_commands/src/nested.rs @@ -1,17 +1,19 @@ use bevy_ecs::prelude::*; use temper_commands::Sender; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; use temper_macros::command; use temper_text::TextComponent; #[command("nested")] -fn nested_command(#[sender] sender: Sender, query: Query<&PlayerIdentity>) { +fn nested_command(#[sender] sender: Sender, query: Query<&Identity>) { let username = match sender { Sender::Server => "Server".to_string(), Sender::Player(entity) => query .get(entity) .expect("sender does not exist") - .username + .name + .as_ref() + .expect("No Player Name") .clone(), }; @@ -22,13 +24,15 @@ fn nested_command(#[sender] sender: Sender, query: Query<&PlayerIdentity>) { } #[command("nested nested")] -fn nested_nested_command(#[sender] sender: Sender, query: Query<&PlayerIdentity>) { +fn nested_nested_command(#[sender] sender: Sender, query: Query<&Identity>) { let username = match sender { Sender::Server => "Server".to_string(), Sender::Player(entity) => query .get(entity) .expect("sender does not exist") - .username + .name + .as_ref() + .expect("No Player Name") .clone(), }; diff --git a/src/default_commands/src/say.rs b/src/default_commands/src/say.rs index 401dc5fa..5c0b5b3d 100644 --- a/src/default_commands/src/say.rs +++ b/src/default_commands/src/say.rs @@ -1,7 +1,7 @@ use bevy_ecs::prelude::Query; use temper_commands::arg::primitive::string::GreedyString; use temper_commands::Sender; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; use temper_core::mq; use temper_macros::command; use temper_net_runtime::connection::StreamWriter; @@ -10,13 +10,17 @@ use temper_net_runtime::connection::StreamWriter; fn say_command( #[sender] sender: Sender, #[arg] message: GreedyString, - query: Query<(&StreamWriter, &PlayerIdentity)>, + query: Query<(&StreamWriter, &Identity)>, ) { let full_message = match sender { Sender::Server => format!(" {}", message.as_str()), Sender::Player(entity) => { let player_identity = query.get(entity).expect("sender does not exist").1; - format!("<{}> {}", player_identity.username, message.as_str()) + format!( + "<{}> {}", + player_identity.name.as_ref().expect("No Player Name"), + message.as_str() + ) } }; diff --git a/src/default_commands/src/tp.rs b/src/default_commands/src/tp.rs index 86038a20..40a06749 100644 --- a/src/default_commands/src/tp.rs +++ b/src/default_commands/src/tp.rs @@ -5,8 +5,8 @@ use temper_commands::arg::entities::EntityArgument; use temper_commands::arg::position::CommandPosition; use temper_commands::Sender; use temper_commands::Sender::Player; -use temper_components::entity_identity::EntityIdentity; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; +use temper_components::player::player_marker::PlayerMarker; use temper_components::player::position::Position; use temper_components::player::rotation::Rotation; use temper_macros::command; @@ -52,7 +52,7 @@ fn tp_to_command( args: ( Query<(&Rotation, &Position)>, MessageWriter, - Query<(Entity, Option<&EntityIdentity>, Option<&PlayerIdentity>)>, + Query<(Entity, &Identity, Option<&PlayerMarker>)>, ), ) { let (query, mut tp_player_msg, resolve_q) = args; diff --git a/src/entities/src/bundles/mod.rs b/src/entities/src/bundles/mod.rs index d9c548c0..02138b3f 100644 --- a/src/entities/src/bundles/mod.rs +++ b/src/entities/src/bundles/mod.rs @@ -30,7 +30,7 @@ pub use passive::*; macro_rules! define_entity_bundle { ($bundle_name:ident, $vanilla_type:ident) => { use bevy_ecs::prelude::Bundle; - use temper_components::entity_identity::EntityIdentity; + use temper_components::entity_identity::Identity; use temper_components::player::{ grounded::OnGround, position::Position, rotation::Rotation, velocity::Velocity, @@ -43,7 +43,7 @@ macro_rules! define_entity_bundle { #[derive(Bundle)] pub struct $bundle_name { - pub identity: EntityIdentity, + pub identity: Identity, pub metadata: EntityMetadata, pub combat: CombatProperties, pub spawn: SpawnProperties, @@ -61,7 +61,7 @@ macro_rules! define_entity_bundle { let spawn = SpawnProperties::from_metadata(&metadata); Self { - identity: EntityIdentity::new(), + identity: Identity::new(None), metadata, combat, spawn, diff --git a/src/game_systems/src/background/src/connection_killer.rs b/src/game_systems/src/background/src/connection_killer.rs index ee12b23d..636fd5f5 100644 --- a/src/game_systems/src/background/src/connection_killer.rs +++ b/src/game_systems/src/background/src/connection_killer.rs @@ -1,6 +1,6 @@ use bevy_ecs::prelude::{Commands, Entity, MessageWriter, Query, Res}; +use temper_components::entity_identity::Identity; use temper_components::player::offline_player_data::OfflinePlayerData; -use temper_components::player::player_identity::PlayerIdentity; use temper_components::player::position::Position; use temper_components::player::rotation::Rotation; use temper_components::{ @@ -22,7 +22,7 @@ use tracing::{debug, info, trace, warn}; type PlayerCacheQuery<'a> = ( Entity, &'a StreamWriter, - &'a PlayerIdentity, + &'a Identity, &'a PlayerAbilities, &'a GameModeComponent, &'a Position, @@ -36,11 +36,11 @@ type PlayerCacheQuery<'a> = ( ); // This query is a "fallback" for half-connected players -type PlayerIdentityQuery<'a> = &'a PlayerIdentity; +type IdentityQuery<'a> = &'a Identity; pub fn connection_killer( full_player_query: Query, - identity_query: Query, + identity_query: Query, mut cmd: Commands, state: Res, mut leave_events: MessageWriter, @@ -69,20 +69,18 @@ pub fn connection_killer( effects, )) = full_player_query.get(disconnecting_entity) { + let username = player_identity.name.as_ref().expect("No Player Name"); // --- SUCCESS: This is a fully-joined player --- info!( "Player {} ({}) disconnected: {}.", - player_identity.username, + username, player_identity.uuid, reason.as_deref().unwrap_or("No reason") ); // Send disconnect packet if conn.running.load(std::sync::atomic::Ordering::Relaxed) { - trace!( - "Sending disconnect packet to player {}", - player_identity.username - ); + trace!("Sending disconnect packet to player {}", username); if let Err(e) = conn.send_packet_ref(&temper_protocol::outgoing::disconnect::DisconnectPacket { reason: TextComponent::from(reason.as_deref().unwrap_or("Disconnected")) @@ -91,13 +89,13 @@ pub fn connection_killer( { warn!( "Failed to send disconnect packet to player {}: {:?}", - player_identity.username, e + username, e ); } } else { trace!( "Connection for player {} is not running, skipping disconnect packet", - player_identity.username + username ); } @@ -119,15 +117,9 @@ pub fn connection_killer( .world .save_player_data(player_identity.uuid, &data_to_cache) { - warn!( - "Failed to save player data for {}: {:?}", - player_identity.username, err - ); + warn!("Failed to save player data for {}: {:?}", username, err); } else { - debug!( - "Successfully saved player data for {}", - player_identity.username - ); + debug!("Successfully saved player data for {}", username); } // --- 3. Fire PlayerLeaveEvent --- @@ -143,7 +135,7 @@ pub fn connection_killer( if let Ok(player_identity) = identity_query.get(disconnecting_entity) { warn!( "-> (Half-player had identity: {})", - player_identity.username + player_identity.name.as_ref().expect("No Player Name") ); leave_events.write(PlayerLeft(player_identity.clone())); } else { diff --git a/src/game_systems/src/background/src/send_entity_updates.rs b/src/game_systems/src/background/src/send_entity_updates.rs index 10ddb277..c2209ae9 100644 --- a/src/game_systems/src/background/src/send_entity_updates.rs +++ b/src/game_systems/src/background/src/send_entity_updates.rs @@ -1,9 +1,8 @@ #![expect(clippy::type_complexity)] use bevy_ecs::prelude::{MessageReader, Query}; use temper_codec::net_types::angle::NetAngle; -use temper_components::entity_identity::EntityIdentity; +use temper_components::entity_identity::Identity; use temper_components::player::grounded::OnGround; -use temper_components::player::player_identity::PlayerIdentity; use temper_components::player::position::Position; use temper_components::player::rotation::Rotation; use temper_components::player::velocity::Velocity; @@ -20,8 +19,8 @@ pub fn handle( &Velocity, &Rotation, &mut LastSyncedPosition, - Option<&EntityIdentity>, - Option<&PlayerIdentity>, + Option<&Identity>, + Option<&Identity>, &OnGround, )>, mut conn_query: Query<&StreamWriter>, @@ -38,7 +37,7 @@ pub fn handle( let id = if let Some(entity_id) = entity_id_opt { entity_id.entity_id } else if let Some(player_id) = player_id_opt { - player_id.short_uuid + player_id.entity_id } else { warn!( "Tried to send entity update for entity without identity: {:?}", diff --git a/src/game_systems/src/background/src/world_sync.rs b/src/game_systems/src/background/src/world_sync.rs index a1310e51..95e45f9e 100644 --- a/src/game_systems/src/background/src/world_sync.rs +++ b/src/game_systems/src/background/src/world_sync.rs @@ -1,6 +1,7 @@ #![expect(clippy::type_complexity)] use bevy_ecs::prelude::{Query, Res, ResMut}; use temper_components::active_effects::ActiveEffects; +use temper_components::entity_identity::Identity; use temper_components::health::Health; use temper_components::player::abilities::PlayerAbilities; use temper_components::player::experience::Experience; @@ -8,7 +9,6 @@ use temper_components::player::gamemode::GameModeComponent; use temper_components::player::gameplay_state::ender_chest::EnderChest; use temper_components::player::hunger::Hunger; use temper_components::player::offline_player_data::OfflinePlayerData; -use temper_components::player::player_identity::PlayerIdentity; use temper_components::player::position::Position; use temper_components::player::rotation::Rotation; use temper_inventories::inventory::Inventory; @@ -17,7 +17,7 @@ use temper_state::GlobalStateResource; pub fn sync_world( player_query: Query<( - &PlayerIdentity, + &Identity, &PlayerAbilities, &GameModeComponent, &Position, diff --git a/src/game_systems/src/mobs/src/pig.rs b/src/game_systems/src/mobs/src/pig.rs index bf273356..9cccc4cc 100644 --- a/src/game_systems/src/mobs/src/pig.rs +++ b/src/game_systems/src/mobs/src/pig.rs @@ -1,11 +1,7 @@ use bevy_ecs::prelude::{Query, With}; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; use temper_components::player::position::Position; use temper_entities::markers::entity_types::Pig; #[expect(unused_variables)] -pub fn tick_pig( - query: Query<&Position, With>, - players: Query<&Position, With>, -) { -} +pub fn tick_pig(query: Query<&Position, With>, players: Query<&Position, With>) {} diff --git a/src/game_systems/src/packets/src/chat_message.rs b/src/game_systems/src/packets/src/chat_message.rs index c0622b9c..aefc93aa 100644 --- a/src/game_systems/src/packets/src/chat_message.rs +++ b/src/game_systems/src/packets/src/chat_message.rs @@ -1,16 +1,20 @@ use bevy_ecs::prelude::*; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; use temper_core::mq; use temper_protocol::ChatMessagePacketReceiver; use temper_text::TextComponent; -pub fn handle(receiver: Res, query: Query<&PlayerIdentity>) { +pub fn handle(receiver: Res, query: Query<&Identity>) { for (message, sender) in receiver.0.try_iter() { let Ok(identity) = query.get(sender) else { continue; }; - let message = format!("<{}> {}", identity.username, message.message); + let message = format!( + "<{}> {}", + identity.name.as_ref().expect("No Player Name"), + message.message + ); mq::broadcast(TextComponent::from(message), false); } } diff --git a/src/game_systems/src/packets/src/command.rs b/src/game_systems/src/packets/src/command.rs index eb6fa269..6abac5e2 100644 --- a/src/game_systems/src/packets/src/command.rs +++ b/src/game_systems/src/packets/src/command.rs @@ -1,10 +1,10 @@ use bevy_ecs::prelude::*; use temper_commands::{ - Sender, messages::{CommandDispatched, ResolvedCommandDispatched}, resolve, + Sender, }; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; use temper_core::mq; use temper_protocol::ChatCommandPacketReceiver; use temper_state::GlobalStateResource; @@ -15,7 +15,7 @@ pub fn handle( mut dispatch_msgs: MessageWriter, mut resolved_dispatch_msgs: MessageWriter, state: Res, - query: Query<&PlayerIdentity>, + query: Query<&Identity>, ) { for (event, entity) in receiver.0.try_iter() { let sender = Sender::Player(entity); @@ -36,7 +36,8 @@ pub fn handle( }; info!( "Player {} executed command: /{}", - player_id.username, event.command + player_id.name.as_ref().expect("No Player Name"), + event.command ); resolved_dispatch_msgs.write(ResolvedCommandDispatched { command, diff --git a/src/game_systems/src/packets/src/pick_item_from_block.rs b/src/game_systems/src/packets/src/pick_item_from_block.rs index 1b791a7b..a84c8f62 100644 --- a/src/game_systems/src/packets/src/pick_item_from_block.rs +++ b/src/game_systems/src/packets/src/pick_item_from_block.rs @@ -1,7 +1,7 @@ use bevy_ecs::prelude::{Entity, Query, Res}; use temper_codec::net_types::var_int::VarInt; +use temper_components::entity_identity::Identity; use temper_components::player::abilities::PlayerAbilities; -use temper_components::player::player_identity::PlayerIdentity; use temper_inventories::item::ItemID; use temper_inventories::slot::InventorySlot; use temper_inventories::{hotbar::Hotbar, inventory::Inventory}; @@ -20,7 +20,7 @@ pub fn handle( state: Res, mut player_inv_query: Query<( Entity, - &PlayerIdentity, + &Identity, &PlayerAbilities, &mut Inventory, &mut Hotbar, @@ -42,7 +42,9 @@ pub fn handle( debug!( "Player {} requested pick block at {:?} (Include Data: {})", - identity.username, packet.location, packet.include_data, + identity.name.as_ref().expect("No Player Name"), + packet.location, + packet.include_data, ); // 2. Get block from world diff --git a/src/game_systems/src/packets/src/player_command.rs b/src/game_systems/src/packets/src/player_command.rs index ec3508ff..f40adff0 100644 --- a/src/game_systems/src/packets/src/player_command.rs +++ b/src/game_systems/src/packets/src/player_command.rs @@ -1,11 +1,11 @@ use bevy_ecs::prelude::{Entity, Query, Res}; use temper_codec::net_types::var_int::VarInt; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; use temper_net_runtime::broadcast::broadcast_packet_except; use temper_net_runtime::connection::StreamWriter; -use temper_protocol::PlayerCommandPacketReceiver; use temper_protocol::incoming::player_command::PlayerCommandAction; use temper_protocol::outgoing::entity_metadata::{EntityMetadata, EntityMetadataPacket}; +use temper_protocol::PlayerCommandPacketReceiver; use tracing::trace; /// Handles PlayerCommand packets (sprinting, leave bed, etc.) @@ -13,7 +13,7 @@ use tracing::trace; pub fn handle( receiver: Res, conn_query: Query<(Entity, &StreamWriter)>, - identity_query: Query<&PlayerIdentity>, + identity_query: Query<&Identity>, ) { for (event, eid) in receiver.0.try_iter() { // Get the sender's identity to use the correct entity ID @@ -21,11 +21,13 @@ pub fn handle( continue; }; - let entity_id = VarInt::new(identity.short_uuid); + let entity_id = VarInt::new(identity.entity_id); trace!( "PlayerCommand: {:?} from {} (entity_id={})", - event.action, identity.username, identity.short_uuid + event.action, + identity.name.as_ref().expect("No Player Name"), + identity.entity_id ); match event.action { diff --git a/src/game_systems/src/packets/src/player_input.rs b/src/game_systems/src/packets/src/player_input.rs index daaa1a3b..5e4841cd 100644 --- a/src/game_systems/src/packets/src/player_input.rs +++ b/src/game_systems/src/packets/src/player_input.rs @@ -5,12 +5,12 @@ use bevy_ecs::prelude::{Entity, Query, Res}; use temper_codec::net_types::var_int::VarInt; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; use temper_components::player::sneak::SneakState; use temper_net_runtime::broadcast::broadcast_packet_except; use temper_net_runtime::connection::StreamWriter; -use temper_protocol::PlayerInputReceiver; use temper_protocol::outgoing::entity_metadata::{EntityMetadata, EntityMetadataPacket}; +use temper_protocol::PlayerInputReceiver; use tracing::{debug, warn}; /// PlayerInput flags (1.21.x protocol) @@ -21,7 +21,7 @@ const FLAG_SNEAK: u8 = 0x20; pub fn handle( receiver: Res, conn_query: Query<(Entity, &StreamWriter)>, - identity_query: Query<&PlayerIdentity>, + identity_query: Query<&Identity>, mut sneak_query: Query<&mut SneakState>, ) { for (event, eid) in receiver.0.try_iter() { @@ -33,7 +33,7 @@ pub fn handle( let Ok(mut sneak_state) = sneak_query.get_mut(eid) else { warn!( "SneakState component missing for player {} - this shouldn't happen", - identity.username + identity.name.as_ref().expect("No Player Name") ); continue; }; @@ -46,11 +46,13 @@ pub fn handle( } sneak_state.is_sneaking = is_sneaking; - let entity_id = VarInt::new(identity.short_uuid); + let entity_id = VarInt::new(identity.entity_id); debug!( "PlayerInput: sneak={} from {} (entity_id={})", - is_sneaking, identity.username, identity.short_uuid + is_sneaking, + identity.name.as_ref().expect("No Player Name"), + identity.entity_id ); let packet = if is_sneaking { diff --git a/src/game_systems/src/packets/src/player_loaded.rs b/src/game_systems/src/packets/src/player_loaded.rs index 6c7853c3..58664833 100644 --- a/src/game_systems/src/packets/src/player_loaded.rs +++ b/src/game_systems/src/packets/src/player_loaded.rs @@ -1,20 +1,20 @@ use bevy_ecs::prelude::{Entity, Query, Res}; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; use temper_components::player::position::Position; use temper_core::block_state_id::BlockStateId; use temper_core::dimension::Dimension; use temper_core::pos::BlockPos; use temper_macros::match_block; use temper_net_runtime::connection::StreamWriter; -use temper_protocol::PlayerLoadedReceiver; use temper_protocol::outgoing::synchronize_player_position::SynchronizePlayerPositionPacket; +use temper_protocol::PlayerLoadedReceiver; use temper_state::GlobalStateResource; use tracing::warn; pub fn handle( ev: Res, state: Res, - query: Query<(Entity, &Position, &StreamWriter, &PlayerIdentity)>, + query: Query<(Entity, &Position, &StreamWriter, &Identity)>, ) { for (_, player) in ev.0.try_iter() { let Ok((entity, player_pos, conn, identity)) = query.get(player) else { @@ -49,7 +49,7 @@ pub fn handle( if match_block!("air", head_block) || match_block!("cave_air", head_block) { tracing::info!( "Player {} loaded at position: ({}, {}, {})", - identity.username, + identity.name.as_ref().expect("No Player Name"), player_pos.x, player_pos.y, player_pos.z @@ -57,7 +57,7 @@ pub fn handle( } else { tracing::info!( "Player {} loaded at position: ({}, {}, {}) with head block: {:?}", - identity.username, + identity.name.as_ref().expect("No Player Name"), player_pos.x, player_pos.y, player_pos.z, diff --git a/src/game_systems/src/packets/src/swing_arm.rs b/src/game_systems/src/packets/src/swing_arm.rs index 5e4b81a9..1a420178 100644 --- a/src/game_systems/src/packets/src/swing_arm.rs +++ b/src/game_systems/src/packets/src/swing_arm.rs @@ -1,15 +1,15 @@ use bevy_ecs::prelude::{Entity, Query, Res}; use temper_codec::net_types::var_int::VarInt; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; use temper_net_runtime::connection::StreamWriter; -use temper_protocol::SwingArmPacketReceiver; use temper_protocol::outgoing::entity_animation::EntityAnimationPacket; +use temper_protocol::SwingArmPacketReceiver; use temper_state::GlobalStateResource; use tracing::error; pub fn handle( receiver: Res, - query: Query<&PlayerIdentity>, + query: Query<&Identity>, conn_query: Query<(Entity, &StreamWriter)>, state: Res, ) { @@ -19,7 +19,7 @@ pub fn handle( error!("Game ID not found for entity: {:?}", eid); continue; }; - let packet = EntityAnimationPacket::new(VarInt::new(game_id.short_uuid), animation); + let packet = EntityAnimationPacket::new(VarInt::new(game_id.entity_id), animation); for (entity, conn) in conn_query.iter() { if entity == eid { continue; // Skip sending to the player who triggered the event diff --git a/src/game_systems/src/player/src/emit_player_joined.rs b/src/game_systems/src/player/src/emit_player_joined.rs index 9f048de5..a55d9e61 100644 --- a/src/game_systems/src/player/src/emit_player_joined.rs +++ b/src/game_systems/src/player/src/emit_player_joined.rs @@ -24,7 +24,8 @@ pub fn emit_player_joined( for (entity, pending) in query.iter() { trace!( "Emitting PlayerJoined event for {} ({:?})", - pending.0.username, entity + pending.0.name.clone().unwrap_or("Unknown".to_string()), + entity ); events.write(PlayerJoined { @@ -32,8 +33,8 @@ pub fn emit_player_joined( entity, }); - // Remove marker so we don't fire again. - // This removal is deferred but that's fine - Added only fires once per addition. + // Remove the marker so we don't fire again. + // This removal is deferred, but that's fine - Added only fires once per addition. commands.entity(entity).remove::(); } } diff --git a/src/game_systems/src/player/src/entity_spawn.rs b/src/game_systems/src/player/src/entity_spawn.rs index a2783a5f..7bbe712b 100644 --- a/src/game_systems/src/player/src/entity_spawn.rs +++ b/src/game_systems/src/player/src/entity_spawn.rs @@ -1,5 +1,5 @@ use bevy_ecs::prelude::*; -use temper_components::entity_identity::EntityIdentity; +use temper_components::entity_identity::Identity; use temper_components::player::position::Position; use temper_components::player::rotation::Rotation; use temper_entities::bundles::*; @@ -73,7 +73,7 @@ fn broadcast_entity_spawn(world: &mut World, entity: Entity) { }; let protocol_id = metadata.protocol_id(); - let identity = match world.get::(entity) { + let identity = match world.get::(entity) { Some(i) => i, None => { error!("Failed to get entity identity for {:?}", entity); diff --git a/src/game_systems/src/player/src/gamemode_change.rs b/src/game_systems/src/player/src/gamemode_change.rs index 704b65ed..6554848e 100644 --- a/src/game_systems/src/player/src/gamemode_change.rs +++ b/src/game_systems/src/player/src/gamemode_change.rs @@ -1,7 +1,7 @@ use bevy_ecs::prelude::*; +use temper_components::entity_identity::Identity; use temper_components::player::abilities::PlayerAbilities; use temper_components::player::gamemode::GameModeComponent; -use temper_components::player::player_identity::PlayerIdentity; use temper_messages::PlayerGameModeChanged; use temper_net_runtime::connection::StreamWriter; use temper_protocol::outgoing::game_event::GameEventPacket; @@ -14,7 +14,7 @@ use tracing::{error, info}; pub fn handle( mut events: MessageReader, mut player_query: Query<( - &PlayerIdentity, + &Identity, &mut PlayerAbilities, &mut GameModeComponent, &StreamWriter, @@ -44,7 +44,8 @@ pub fn handle( if let Err(e) = writer.send_packet_ref(&gamemode_packet) { error!( "Failed to send gamemode change packet to {}: {:?}", - identity.username, e + identity.name.as_ref().expect("No Player Name"), + e ); } @@ -53,7 +54,8 @@ pub fn handle( if let Err(e) = writer.send_packet_ref(&abilities_packet) { error!( "Failed to send abilities sync packet to {}: {:?}", - identity.username, e + identity.name.as_ref().expect("No Player Name"), + e ); } @@ -76,10 +78,15 @@ pub fn handle( if let Err(e) = writer.send_packet_ref(&chat_packet) { error!( "Failed to send gamemode confirmation message to {}: {:?}", - identity.username, e + identity.name.as_ref().expect("No Player Name"), + e ); } - info!("Set gamemode for {} to {:?}", identity.username, new_mode); + info!( + "Set gamemode for {} to {:?}", + identity.name.as_ref().expect("No Player Name"), + new_mode + ); } } diff --git a/src/game_systems/src/player/src/movement_broadcast.rs b/src/game_systems/src/player/src/movement_broadcast.rs index 57d30070..f6094ef8 100644 --- a/src/game_systems/src/player/src/movement_broadcast.rs +++ b/src/game_systems/src/player/src/movement_broadcast.rs @@ -14,7 +14,7 @@ use bevy_ecs::prelude::{Entity, MessageReader, Query}; use temper_codec::net_types::angle::NetAngle; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; use temper_components::player::position::Position; use temper_components::player::rotation::Rotation; use temper_macros::NetEncode; @@ -52,7 +52,7 @@ const MAX_DELTA: i16 = (7.5 * 4096f32) as i16; /// broadcast logic, ensuring consistent handling across all movement packet types. pub fn handle_player_move( mut movement_msgs: MessageReader, - query: Query<(&Position, &Rotation, &PlayerIdentity)>, + query: Query<(&Position, &Rotation, &Identity)>, broadcast_query: Query<(Entity, &StreamWriter)>, ) { for movement in movement_msgs.read() { @@ -118,7 +118,7 @@ pub fn handle_player_move( // Build head rotation packet if we have rotation let head_rot_packet = if has_rotation { Some(SetHeadRotationPacket::new( - identity.short_uuid, + identity.entity_id, NetAngle::from_degrees(rot.yaw as f64), )) } else { diff --git a/src/game_systems/src/player/src/new_connections.rs b/src/game_systems/src/player/src/new_connections.rs index c7931e07..1559fa16 100644 --- a/src/game_systems/src/player/src/new_connections.rs +++ b/src/game_systems/src/player/src/new_connections.rs @@ -4,6 +4,7 @@ use temper_components::bounds::CollisionBounds; use temper_components::player::chunk_receiver::ChunkReceiver; use temper_components::player::grounded::OnGround; use temper_components::player::keepalive::KeepAliveTracker; +use temper_components::player::player_marker::PlayerMarker; use temper_components::player::teleport_tracker::TeleportTracker; use temper_components::player::{ gamemode::GameModeComponent, offline_player_data::OfflinePlayerData, @@ -39,7 +40,12 @@ pub fn accept_new_connections( Err(err) => { error!( "Error loading player data for {}: {:?}", - new_connection.player_identity.username, err + new_connection + .player_identity + .name + .as_ref() + .expect("No Player Name"), + err ); None } @@ -49,6 +55,7 @@ pub fn accept_new_connections( let player_bundle = PlayerBundle { identity: new_connection.player_identity.clone(), abilities: player_data.abilities, + player_properties: new_connection.player_properties, gamemode: GameModeComponent(player_data.gamemode), position: player_data.position.into(), rotation: player_data.rotation, @@ -71,6 +78,7 @@ pub fn accept_new_connections( z_offset_start: -0.3, z_offset_end: 0.3, }, + player_marker: PlayerMarker, }; // --- 3. Spawn the PlayerBundle, then .insert() the network components --- @@ -104,7 +112,12 @@ pub fn accept_new_connections( entity_id, ( new_connection.player_identity.uuid.as_u128(), - new_connection.player_identity.username.clone(), + new_connection + .player_identity + .name + .as_ref() + .expect("No Player Name") + .clone(), ), ); @@ -112,7 +125,12 @@ pub fn accept_new_connections( info!( "Player {} connected ({:?})", - new_connection.player_identity.username, new_connection.player_identity.uuid + new_connection + .player_identity + .name + .as_ref() + .expect("No Player Name"), + new_connection.player_identity.uuid ); if let Err(err) = return_sender.send(entity_id) { diff --git a/src/game_systems/src/player/src/player_despawn.rs b/src/game_systems/src/player/src/player_despawn.rs index 3619cb34..c43979cd 100644 --- a/src/game_systems/src/player/src/player_despawn.rs +++ b/src/game_systems/src/player/src/player_despawn.rs @@ -5,7 +5,7 @@ //! 2. Broadcast PlayerInfoRemovePacket to remove from tab list use bevy_ecs::prelude::{Entity, MessageReader, Query, Res}; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; use temper_messages::player_leave::PlayerLeft; use temper_net_runtime::connection::StreamWriter; use temper_protocol::outgoing::player_info_remove::PlayerInfoRemovePacket; @@ -16,7 +16,7 @@ use tracing::{error, trace}; /// Listens for `PlayerLeft` events and broadcasts despawn packets to remaining players. pub fn handle( mut events: MessageReader, - player_query: Query<(Entity, &PlayerIdentity, &StreamWriter)>, + player_query: Query<(Entity, &Identity, &StreamWriter)>, state: Res, ) { for event in events.read() { @@ -58,7 +58,8 @@ pub fn handle( trace!( "Player {} left: notified {} remaining players", - left_player.username, notified_count + left_player.name.as_ref().expect("No Player Name"), + notified_count ); } } diff --git a/src/game_systems/src/player/src/player_join_message.rs b/src/game_systems/src/player/src/player_join_message.rs index 6daa6940..6bae0a8d 100644 --- a/src/game_systems/src/player/src/player_join_message.rs +++ b/src/game_systems/src/player/src/player_join_message.rs @@ -1,5 +1,5 @@ use bevy_ecs::prelude::{Entity, MessageReader, Query}; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; use temper_core::mq; use temper_text::{Color, NamedColor, TextComponent}; @@ -9,16 +9,15 @@ use tracing::trace; /// Listens for `PlayerJoinEvent` and broadcasts the "join" message /// to all other connected players via the Message Queue. -pub fn handle( - mut events: MessageReader, - player_query: Query<(Entity, &PlayerIdentity)>, -) { +pub fn handle(mut events: MessageReader, player_query: Query<(Entity, &Identity)>) { for event in events.read() { let player_who_joined = &event.identity; // Build the "Player joined the game" message - let mut message = - TextComponent::from(format!("{} joined the game", player_who_joined.username)); + let mut message = TextComponent::from(format!( + "{} joined the game", + player_who_joined.name.as_ref().expect("No Player Name") + )); message.color = Some(Color::Named(NamedColor::Yellow)); // Broadcast to all players on the server @@ -27,7 +26,8 @@ pub fn handle( trace!( "Notified {} that {} joined", - receiver_identity.username, player_who_joined.username + receiver_identity.name.as_ref().expect("No Player Name"), + player_who_joined.name.as_ref().expect("No Player Name") ); } } diff --git a/src/game_systems/src/player/src/player_leave_message.rs b/src/game_systems/src/player/src/player_leave_message.rs index f37a250a..54fad5a3 100644 --- a/src/game_systems/src/player/src/player_leave_message.rs +++ b/src/game_systems/src/player/src/player_leave_message.rs @@ -1,24 +1,24 @@ use bevy_ecs::prelude::{Entity, MessageReader, Query}; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; use temper_core::mq; use temper_messages::player_leave::PlayerLeft; use temper_text::{Color, NamedColor, TextComponent}; -use tracing::trace; // We only need trace, mq will handle errors +use tracing::trace; +// We only need trace, mq will handle errors /// Listens for `PlayerLeaveEvent` and broadcasts the "left" message /// to all other connected players via the Message Queue. -pub fn handle( - mut events: MessageReader, - player_query: Query<(Entity, &PlayerIdentity)>, -) { +pub fn handle(mut events: MessageReader, player_query: Query<(Entity, &Identity)>) { // 1. Loop through each "player left" event for event in events.read() { let player_who_left = &event.0; // 2. Build the "Player left the game" message - let mut message = - TextComponent::from(format!("{} left the game", player_who_left.username)); + let mut message = TextComponent::from(format!( + "{} left the game", + player_who_left.name.as_ref().expect("No Player Name") + )); message.color = Some(Color::Named(NamedColor::Yellow)); // 3. Loop through all players on the server @@ -33,7 +33,8 @@ pub fn handle( trace!( "Notified {} that {} left", - receiver_identity.username, player_who_left.username + receiver_identity.name.as_ref().expect("No Player Name"), + player_who_left.name.as_ref().expect("No Player Name") ); } } diff --git a/src/game_systems/src/player/src/player_spawn.rs b/src/game_systems/src/player/src/player_spawn.rs index eb42ce03..7fbf5b90 100644 --- a/src/game_systems/src/player/src/player_spawn.rs +++ b/src/game_systems/src/player/src/player_spawn.rs @@ -5,7 +5,8 @@ //! 2. Broadcast the new player's info + spawn packets to existing players use bevy_ecs::prelude::{Entity, MessageReader, Query, Res}; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; +use temper_components::player::player_properties::PlayerProperties; use temper_components::player::position::Position; use temper_components::player::rotation::Rotation; use temper_macros::get_registry_entry; @@ -22,7 +23,14 @@ const PLAYER_TYPE_ID: i32 = /// Listens for `PlayerJoined` events and handles spawning players for each other. pub fn handle( mut events: MessageReader, - player_query: Query<(Entity, &PlayerIdentity, &Position, &Rotation, &StreamWriter)>, + player_query: Query<( + Entity, + &Identity, + &Position, + &Rotation, + &StreamWriter, + &PlayerProperties, + )>, state: Res, ) { for event in events.read() { @@ -30,7 +38,9 @@ pub fn handle( let new_player_identity = &event.identity; // Get the new player's connection and components - let Ok((_, _, new_pos, new_rot, new_conn)) = player_query.get(new_player_entity) else { + let Ok((_, _, new_pos, new_rot, new_conn, player_properties)) = + player_query.get(new_player_entity) + else { error!( "Failed to get new player components for spawn broadcast: {:?}", new_player_entity @@ -40,9 +50,9 @@ pub fn handle( // Create packets for the new player once (to broadcast to existing players) let new_player_info_packet = - PlayerInfoUpdatePacket::new_player_join_packet(new_player_identity); + PlayerInfoUpdatePacket::new_player_join_packet(new_player_identity, player_properties); let new_player_spawn_packet = SpawnEntityPacket::new( - new_player_identity.short_uuid, + new_player_identity.entity_id, new_player_identity.uuid.as_u128(), PLAYER_TYPE_ID, new_pos, @@ -52,7 +62,7 @@ pub fn handle( let mut spawned_for_new_player = 0; let mut spawned_for_existing = 0; - for (entity, identity, pos, rot, conn) in player_query.iter() { + for (entity, identity, pos, rot, conn, player_properties) in player_query.iter() { // Skip self if entity == new_player_entity { continue; @@ -65,7 +75,8 @@ pub fn handle( // 1. Send existing player's info to the new player // PlayerInfoUpdate MUST come before SpawnEntity (protocol requirement) - let existing_player_info = PlayerInfoUpdatePacket::new_player_join_packet(identity); + let existing_player_info = + PlayerInfoUpdatePacket::new_player_join_packet(identity, player_properties); if let Err(e) = new_conn.send_packet_ref(&existing_player_info) { error!("Failed to send existing player info to new player: {:?}", e); continue; @@ -73,7 +84,7 @@ pub fn handle( // 2. Send existing player's spawn packet to the new player let existing_player_spawn = SpawnEntityPacket::new( - identity.short_uuid, + identity.entity_id, identity.uuid.as_u128(), PLAYER_TYPE_ID, pos, @@ -107,7 +118,9 @@ pub fn handle( trace!( "Player {} joined: sent {} existing players, spawned for {} existing players", - new_player_identity.username, spawned_for_new_player, spawned_for_existing + new_player_identity.name.as_ref().expect("No Player Name"), + spawned_for_new_player, + spawned_for_existing ); } } diff --git a/src/game_systems/src/player/src/player_swimming.rs b/src/game_systems/src/player/src/player_swimming.rs index d4b82757..454f9dc9 100644 --- a/src/game_systems/src/player/src/player_swimming.rs +++ b/src/game_systems/src/player/src/player_swimming.rs @@ -2,7 +2,7 @@ use bevy_ecs::prelude::*; use bevy_math::DVec3; use std::collections::HashSet; use temper_codec::net_types::var_int::VarInt; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; use temper_components::player::position::Position; use temper_components::player::swimming::SwimmingState; use temper_core::block_state_id::BlockStateId; @@ -43,7 +43,7 @@ fn is_player_in_water(state: &temper_state::GlobalState, pos: &Position) -> bool /// System that detects when players enter/exit water and updates their swimming state /// Also broadcasts the swimming pose to all connected clients pub fn detect_player_swimming( - mut swimmers: Query<(&PlayerIdentity, Ref, &mut SwimmingState)>, + mut swimmers: Query<(&Identity, Ref, &mut SwimmingState)>, all_connections: Query<(Entity, &StreamWriter)>, state: Res, mut world_change: MessageReader, @@ -63,7 +63,7 @@ pub fn detect_player_swimming( if in_water && !swimming_state.is_swimming { swimming_state.is_swimming = true; - let entity_id = VarInt::new(identity.short_uuid); + let entity_id = VarInt::new(identity.entity_id); let packet = EntityMetadataPacket::new( entity_id, [ @@ -76,7 +76,7 @@ pub fn detect_player_swimming( } else if !in_water && swimming_state.is_swimming { swimming_state.is_swimming = false; - let entity_id = VarInt::new(identity.short_uuid); + let entity_id = VarInt::new(identity.entity_id); let packet = EntityMetadataPacket::new( entity_id, [ diff --git a/src/game_systems/src/player/src/player_tp.rs b/src/game_systems/src/player/src/player_tp.rs index c344ff81..7f7eb21d 100644 --- a/src/game_systems/src/player/src/player_tp.rs +++ b/src/game_systems/src/player/src/player_tp.rs @@ -1,5 +1,5 @@ use bevy_ecs::prelude::{Entity, MessageReader, MessageWriter, Query}; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; use temper_components::player::position::Position; use temper_components::player::teleport_tracker::TeleportTracker; use temper_messages::chunk_calc::ChunkCalc; @@ -12,7 +12,7 @@ use tracing::error; pub fn teleport_player( mut query: Query<(Entity, &StreamWriter, &mut Position, &mut TeleportTracker)>, - id_query: Query<&PlayerIdentity>, + id_query: Query<&Identity>, mut message_reader: MessageReader, mut chunk_calc_msg: MessageWriter, mut player_update_msg: MessageWriter, @@ -23,7 +23,7 @@ pub fn teleport_player( Ok(id) => id, Err(err) => { error!( - "Failed to get PlayerIdentity for entity {:?}: {}", + "Failed to get Identity for entity {:?}: {}", message_entity, err ); continue; @@ -56,7 +56,7 @@ pub fn teleport_player( // Otherwise send teleport entity packet. This ideally should be handled by the send // entity updates system, but it seems to be a bit buggy if let Err(err) = conn.send_packet(TeleportEntityPacket { - entity_id: id.short_uuid.into(), + entity_id: id.entity_id.into(), x: message.x, y: message.y, z: message.z, diff --git a/src/game_systems/src/player/src/update_player_ping.rs b/src/game_systems/src/player/src/update_player_ping.rs index a7eb8416..d275752a 100644 --- a/src/game_systems/src/player/src/update_player_ping.rs +++ b/src/game_systems/src/player/src/update_player_ping.rs @@ -1,10 +1,10 @@ use bevy_ecs::prelude::Query; +use temper_components::entity_identity::Identity; use temper_components::player::keepalive::KeepAliveTracker; -use temper_components::player::player_identity::PlayerIdentity; use temper_net_runtime::connection::StreamWriter; use temper_protocol::outgoing::player_info_update::PlayerInfoUpdatePacket; -pub fn handle(query: Query<(&PlayerIdentity, &KeepAliveTracker)>, conns: Query<&StreamWriter>) { +pub fn handle(query: Query<(&Identity, &KeepAliveTracker)>, conns: Query<&StreamWriter>) { let packet = PlayerInfoUpdatePacket::update_players_ping( query .iter() diff --git a/src/game_systems/src/shutdown/src/send_shutdown_packet.rs b/src/game_systems/src/shutdown/src/send_shutdown_packet.rs index da249dc0..d9b9205c 100644 --- a/src/game_systems/src/shutdown/src/send_shutdown_packet.rs +++ b/src/game_systems/src/shutdown/src/send_shutdown_packet.rs @@ -1,13 +1,10 @@ use bevy_ecs::prelude::{Entity, Query, Res}; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; use temper_net_runtime::connection::StreamWriter; use temper_state::GlobalStateResource; use temper_text::TextComponent; -pub fn handle( - query: Query<(Entity, &StreamWriter, &PlayerIdentity)>, - state: Res, -) { +pub fn handle(query: Query<(Entity, &StreamWriter, &Identity)>, state: Res) { let packet = temper_protocol::outgoing::disconnect::DisconnectPacket { reason: TextComponent::from("Server is shutting down").into(), }; @@ -17,11 +14,14 @@ pub fn handle( if let Err(e) = conn.send_packet_ref(&packet) { tracing::error!( "Failed to send shutdown packet to player {}: {}", - identity.username, + identity.name.as_ref().expect("No Player Name"), e ); } else { - tracing::info!("Shutdown packet sent to player {}", identity.username); + tracing::info!( + "Shutdown packet sent to player {}", + identity.name.as_ref().expect("No Player Name") + ); } } } diff --git a/src/messages/src/player_join.rs b/src/messages/src/player_join.rs index 7eb27b00..056a0ce6 100644 --- a/src/messages/src/player_join.rs +++ b/src/messages/src/player_join.rs @@ -1,5 +1,5 @@ use bevy_ecs::prelude::*; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; /// Fired by the `new_connection` system when a player joins /// @@ -8,6 +8,6 @@ use temper_components::player::player_identity::PlayerIdentity; /// and `player_spawn` to broadcast spawn packets #[derive(Message, Clone)] pub struct PlayerJoined { - pub identity: PlayerIdentity, + pub identity: Identity, pub entity: Entity, } diff --git a/src/messages/src/player_leave.rs b/src/messages/src/player_leave.rs index 574aa293..467092c5 100644 --- a/src/messages/src/player_leave.rs +++ b/src/messages/src/player_leave.rs @@ -1,6 +1,6 @@ use bevy_ecs::prelude::*; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; #[derive(Message, Clone)] #[allow(unused)] -pub struct PlayerLeft(pub PlayerIdentity); +pub struct PlayerLeft(pub Identity); diff --git a/src/net/protocol/src/outgoing/entity_position_sync.rs b/src/net/protocol/src/outgoing/entity_position_sync.rs index 3cc5473e..5fa55fc7 100644 --- a/src/net/protocol/src/outgoing/entity_position_sync.rs +++ b/src/net/protocol/src/outgoing/entity_position_sync.rs @@ -1,8 +1,8 @@ use temper_codec::net_types::var_int::VarInt; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; use temper_components::player::position::Position; use temper_components::player::rotation::Rotation; -use temper_macros::{NetEncode, packet}; +use temper_macros::{packet, NetEncode}; #[derive(NetEncode, Clone)] #[packet(packet_id = "entity_position_sync", state = "play")] @@ -21,14 +21,14 @@ pub struct TeleportEntityPacket { impl TeleportEntityPacket { pub fn new( - entity_id: &PlayerIdentity, + entity_id: &Identity, position: &Position, angle: &Rotation, on_ground: bool, ) -> Self { // Todo: Add velocity parameters if needed Self { - entity_id: VarInt::new(entity_id.short_uuid), + entity_id: VarInt::new(entity_id.entity_id), x: position.x, y: position.y, z: position.z, diff --git a/src/net/protocol/src/outgoing/player_info_update.rs b/src/net/protocol/src/outgoing/player_info_update.rs index 63083cbb..f9427b1a 100644 --- a/src/net/protocol/src/outgoing/player_info_update.rs +++ b/src/net/protocol/src/outgoing/player_info_update.rs @@ -3,8 +3,9 @@ use bevy_ecs::prelude::{Component, Entity, Query}; use temper_codec::net_types::length_prefixed_vec::LengthPrefixedVec; use temper_codec::net_types::prefixed_optional::PrefixedOptional; use temper_codec::net_types::var_int::VarInt; -use temper_components::player::player_identity::PlayerIdentity; -use temper_macros::{NetEncode, packet}; +use temper_components::entity_identity::Identity; +use temper_components::player::player_properties::PlayerProperties; +use temper_macros::{packet, NetEncode}; use tracing::debug; #[derive(NetEncode)] @@ -27,28 +28,34 @@ impl PlayerInfoUpdatePacket { } /// The packet to be sent to all already connected players when a new player joins the server - pub fn new_player_join_packet(identity: &PlayerIdentity) -> Self { - Self::with_players(vec![PlayerWithActions::add_player(identity)]) + pub fn new_player_join_packet( + identity: &Identity, + player_properties: &PlayerProperties, + ) -> Self { + Self::with_players(vec![PlayerWithActions::add_player( + identity, + player_properties, + )]) } /// The packet to be sent to a new player when they join the server, /// To let them know about all the players that are already connected pub fn existing_player_info_packet( new_player_id: Entity, - query: Query<(Entity, &PlayerIdentity)>, + query: Query<(Entity, &Identity, &PlayerProperties)>, ) -> Self { - let players: Vec<&PlayerIdentity> = { - let players = query.iter().collect::>(); + let players: Vec<(&Identity, &PlayerProperties)> = { + let players = query.iter().collect::>(); players .iter() .filter(|&player| player.0 == new_player_id) - .map(|player| player.1) + .map(|player| (player.1, player.2)) .collect() }; let players = players .into_iter() - .map(PlayerWithActions::add_player) + .map(|(identity, properties)| PlayerWithActions::add_player(identity, properties)) .collect::>(); debug!("Sending PlayerInfoUpdatePacket with {:?} players", players); @@ -88,14 +95,14 @@ impl PlayerWithActions { mask } - pub fn add_player(identity: &PlayerIdentity) -> Self { + pub fn add_player(identity: &Identity, player_properties: &PlayerProperties) -> Self { Self { uuid: identity.uuid.as_u128(), actions: vec![ PlayerAction::AddPlayer { - name: identity.username.clone(), + name: identity.name.as_ref().expect("No Player Name").clone(), properties: LengthPrefixedVec::new( - identity + player_properties .properties .iter() .map(|property| NetPlayerProperty { diff --git a/src/net/protocol/src/outgoing/remove_entities.rs b/src/net/protocol/src/outgoing/remove_entities.rs index f6d46f23..1c2dbf1f 100644 --- a/src/net/protocol/src/outgoing/remove_entities.rs +++ b/src/net/protocol/src/outgoing/remove_entities.rs @@ -1,7 +1,7 @@ use temper_codec::net_types::length_prefixed_vec::LengthPrefixedVec; use temper_codec::net_types::var_int::VarInt; -use temper_components::player::player_identity::PlayerIdentity; -use temper_macros::{NetEncode, packet}; +use temper_components::entity_identity::Identity; +use temper_macros::{packet, NetEncode}; #[derive(NetEncode)] #[packet(packet_id = "remove_entities", state = "play")] @@ -12,11 +12,11 @@ pub struct RemoveEntitiesPacket { impl RemoveEntitiesPacket { pub fn from_entities(entity_ids: T) -> Self where - T: IntoIterator, + T: IntoIterator, { let entity_ids: Vec = entity_ids .into_iter() - .map(|entity| VarInt::new(entity.short_uuid)) + .map(|entity| VarInt::new(entity.entity_id)) .collect(); Self { entity_ids: LengthPrefixedVec::new(entity_ids), diff --git a/src/net/protocol/src/outgoing/spawn_entity.rs b/src/net/protocol/src/outgoing/spawn_entity.rs index b407cd94..a51a1467 100644 --- a/src/net/protocol/src/outgoing/spawn_entity.rs +++ b/src/net/protocol/src/outgoing/spawn_entity.rs @@ -2,11 +2,10 @@ use crate::errors::NetError; use bevy_ecs::prelude::{Entity, Query}; use temper_codec::net_types::angle::NetAngle; use temper_codec::net_types::var_int::VarInt; -use temper_components::entity_identity::EntityIdentity; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; use temper_components::player::position::Position; use temper_components::player::rotation::Rotation; -use temper_macros::{NetEncode, get_registry_entry, packet}; +use temper_macros::{packet, NetEncode}; #[derive(NetEncode)] #[packet(packet_id = "add_entity", state = "play")] @@ -26,8 +25,6 @@ pub struct SpawnEntityPacket { velocity_z: i16, } -const PLAYER_ID: u64 = get_registry_entry!("minecraft:entity_type.entries.minecraft:player"); - impl SpawnEntityPacket { /// Creates a spawn entity packet from direct component values. /// @@ -60,34 +57,6 @@ impl SpawnEntityPacket { } } - pub fn player( - entity_id: Entity, - query: Query<(&PlayerIdentity, &Position, &Rotation)>, - ) -> Result { - let (player_identity, position, rotation) = query - .get(entity_id) - .map_err(|e| NetError::ECSError(e.into()))?; - - let (x, y, z) = position.xyz(); - let (yaw, pitch) = rotation.yaw_pitch(); - - Ok(Self { - entity_id: VarInt::new(player_identity.short_uuid), - entity_uuid: player_identity.uuid.as_u128(), - r#type: VarInt::new(PLAYER_ID as i32), - x, - y, - z, - pitch: NetAngle::from_degrees(pitch as f64), - yaw: NetAngle::from_degrees(yaw as f64), - head_yaw: NetAngle::from_degrees(yaw as f64), - data: VarInt::new(0), - velocity_x: 0, - velocity_y: 0, - velocity_z: 0, - }) - } - /// Creates a spawn entity packet for any entity (mob, projectile, etc.). /// /// # Arguments @@ -98,7 +67,7 @@ impl SpawnEntityPacket { pub fn entity( entity: Entity, entity_type_id: u16, - query: Query<(&EntityIdentity, &Position, &Rotation)>, + query: Query<(&Identity, &Position, &Rotation)>, ) -> Result { let (identity, position, rotation) = query .get(entity) diff --git a/src/net/protocol/src/outgoing/update_entity_position.rs b/src/net/protocol/src/outgoing/update_entity_position.rs index d832ac74..c6f52bdd 100644 --- a/src/net/protocol/src/outgoing/update_entity_position.rs +++ b/src/net/protocol/src/outgoing/update_entity_position.rs @@ -1,6 +1,6 @@ use temper_codec::net_types::var_int::VarInt; -use temper_components::player::player_identity::PlayerIdentity; -use temper_macros::{NetEncode, packet}; +use temper_components::entity_identity::Identity; +use temper_macros::{packet, NetEncode}; #[derive(NetEncode, Clone)] #[packet(packet_id = "move_entity_pos", state = "play")] @@ -13,13 +13,9 @@ pub struct UpdateEntityPositionPacket { } impl UpdateEntityPositionPacket { - pub fn new( - entity_id: &PlayerIdentity, - delta_positions: (i16, i16, i16), - on_ground: bool, - ) -> Self { + pub fn new(entity_id: &Identity, delta_positions: (i16, i16, i16), on_ground: bool) -> Self { Self { - entity_id: VarInt::new(entity_id.short_uuid), + entity_id: VarInt::new(entity_id.entity_id), delta_x: delta_positions.0, delta_y: delta_positions.1, delta_z: delta_positions.2, diff --git a/src/net/protocol/src/outgoing/update_entity_position_and_rotation.rs b/src/net/protocol/src/outgoing/update_entity_position_and_rotation.rs index 7be54927..f2849c9b 100644 --- a/src/net/protocol/src/outgoing/update_entity_position_and_rotation.rs +++ b/src/net/protocol/src/outgoing/update_entity_position_and_rotation.rs @@ -1,8 +1,8 @@ use temper_codec::net_types::angle::NetAngle; use temper_codec::net_types::var_int::VarInt; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; use temper_components::player::rotation::Rotation; -use temper_macros::{NetEncode, packet}; +use temper_macros::{packet, NetEncode}; #[derive(NetEncode, Clone)] #[packet(packet_id = "move_entity_pos_rot", state = "play")] @@ -18,13 +18,13 @@ pub struct UpdateEntityPositionAndRotationPacket { impl UpdateEntityPositionAndRotationPacket { pub fn new( - entity_id: &PlayerIdentity, + entity_id: &Identity, delta_positions: (i16, i16, i16), new_rot: &Rotation, on_ground: bool, ) -> Self { Self { - entity_id: VarInt::new(entity_id.short_uuid), + entity_id: VarInt::new(entity_id.entity_id), delta_x: delta_positions.0, delta_y: delta_positions.1, delta_z: delta_positions.2, diff --git a/src/net/protocol/src/outgoing/update_entity_rotation.rs b/src/net/protocol/src/outgoing/update_entity_rotation.rs index 7403fc83..00524362 100644 --- a/src/net/protocol/src/outgoing/update_entity_rotation.rs +++ b/src/net/protocol/src/outgoing/update_entity_rotation.rs @@ -1,8 +1,8 @@ use temper_codec::net_types::angle::NetAngle; use temper_codec::net_types::var_int::VarInt; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::entity_identity::Identity; use temper_components::player::rotation::Rotation; -use temper_macros::{NetEncode, packet}; +use temper_macros::{packet, NetEncode}; #[derive(NetEncode, Clone)] #[packet(packet_id = "move_entity_rot", state = "play")] @@ -13,9 +13,9 @@ pub struct UpdateEntityRotationPacket { pub on_ground: bool, } impl UpdateEntityRotationPacket { - pub fn new(entity_id: &PlayerIdentity, new_rot: &Rotation, on_ground: bool) -> Self { + pub fn new(entity_id: &Identity, new_rot: &Rotation, on_ground: bool) -> Self { Self { - entity_id: VarInt::new(entity_id.short_uuid), + entity_id: VarInt::new(entity_id.entity_id), yaw: NetAngle::from_degrees(new_rot.yaw as f64), pitch: NetAngle::from_degrees(new_rot.pitch as f64), on_ground, diff --git a/src/net/runtime/src/auth.rs b/src/net/runtime/src/auth.rs index 9302878c..8c1cadb7 100644 --- a/src/net/runtime/src/auth.rs +++ b/src/net/runtime/src/auth.rs @@ -2,7 +2,7 @@ use base64::Engine; use serde_derive::Deserialize; use std::str::FromStr; use std::sync::Arc; -use temper_components::player::player_identity::PlayerProperty; +use temper_components::player::player_properties::PlayerProperty; use temper_encryption::minecraft_hex_digest; use temper_protocol::errors::NetAuthenticationError; use uuid::Uuid; diff --git a/src/net/runtime/src/conn_init/login.rs b/src/net/runtime/src/conn_init/login.rs index 8066a853..cef9f52e 100644 --- a/src/net/runtime/src/conn_init/login.rs +++ b/src/net/runtime/src/conn_init/login.rs @@ -18,8 +18,9 @@ use temper_protocol::ConnState::*; use temper_state::GlobalState; use rand::RngCore; +use temper_components::entity_identity::Identity; use temper_components::player::offline_player_data::OfflinePlayerData; -use temper_components::player::player_identity::{PlayerIdentity, PlayerProperty}; +use temper_components::player::player_properties::{PlayerProperties, PlayerProperty}; use temper_protocol::errors::{NetAuthenticationError, NetError, PacketError}; use temper_protocol::incoming::ack_finish_configuration::AckFinishConfigurationPacket; use temper_protocol::incoming::client_information::ClientInformation; @@ -194,7 +195,7 @@ async fn send_login_success( login_start: &LoginStartPacket, player_properties: &[PlayerProperty], compressed: bool, -) -> Result { +) -> Result<(Identity, PlayerProperties), NetError> { // Send Login Success let login_success = LoginSuccessPacket { uuid: login_start.uuid, @@ -212,12 +213,11 @@ async fn send_login_success( }; conn_write.send_packet(login_success)?; - // Build PlayerIdentity - let player_identity = PlayerIdentity { + // Build Identity + let player_identity = Identity { uuid: Uuid::from_u128(login_start.uuid), - username: login_start.username.clone(), - short_uuid: login_start.uuid as i32, - properties: player_properties.to_vec(), + name: Some(login_start.username.clone()), + entity_id: login_start.uuid as i32, }; // Wait for Login Acknowledged @@ -235,7 +235,12 @@ async fn send_login_success( let _login_acknowledged = LoginAcknowledgedPacket::decode(&mut skel.data, &NetDecodeOpts::None)?; - Ok(player_identity) + Ok(( + player_identity, + PlayerProperties { + properties: player_properties.to_vec(), + }, + )) } // ================================================================================================= @@ -341,14 +346,14 @@ async fn finish_configuration( /// Sends initial play state packets (login_play, abilities, op level). fn send_initial_play_packets( conn_write: &StreamWriter, - player_identity: &PlayerIdentity, + player_identity: &Identity, offline_data: &OfflinePlayerData, ) -> Result<(), NetError> { // Send login_play let game_mode = offline_data.gamemode; conn_write.send_packet(LoginPlayPacket::new( - player_identity.short_uuid, + player_identity.entity_id, game_mode as u8, ))?; @@ -359,7 +364,7 @@ fn send_initial_play_packets( // Send OP level (TODO: use actual player OP level) conn_write.send_packet(EntityStatus { - entity_id: player_identity.short_uuid, + entity_id: player_identity.entity_id, status: 28, // OP level 4 })?; @@ -405,10 +410,12 @@ async fn sync_player_position( /// Sends player info and game event packets. fn send_player_info( conn_write: &StreamWriter, - player_identity: &PlayerIdentity, + player_identity: &Identity, + player_properties: &PlayerProperties, ) -> Result<(), NetError> { conn_write.send_packet(PlayerInfoUpdatePacket::new_player_join_packet( player_identity, + player_properties, ))?; conn_write.send_packet(GameEventPacket::new(13, 0.0))?; Ok(()) @@ -474,7 +481,7 @@ pub(super) async fn login( let player_properties = setup_encryption_and_auth(conn_read, conn_write, config, &login_start, compressed).await?; - let player_identity = send_login_success( + let (player_identity, player_properties) = send_login_success( conn_read, conn_write, &login_start, @@ -494,7 +501,11 @@ pub(super) async fn login( .unwrap_or_else(|err| { error!( "Error loading player data for {}: {:?}", - player_identity.username, err + player_identity + .name + .clone() + .unwrap_or("UnknownPlayerName".to_string()), + err ); None }) @@ -508,7 +519,7 @@ pub(super) async fn login( // TODO: at some point this should be moved to the ECS send_initial_play_packets(conn_write, &player_identity, &offline_data)?; sync_player_position(conn_read, conn_write, &offline_data, compressed).await?; - send_player_info(conn_write, &player_identity)?; + send_player_info(conn_write, &player_identity, &player_properties)?; send_inventory_contents(conn_write, &offline_data)?; send_command_graph(conn_write)?; @@ -519,6 +530,7 @@ pub(super) async fn login( player_identity: Some(player_identity), compression: compressed, client_information_component: Some(client_info.into()), + player_properties: Some(player_properties), }, )) } diff --git a/src/net/runtime/src/conn_init/mod.rs b/src/net/runtime/src/conn_init/mod.rs index 7951fb6e..fdd864c3 100644 --- a/src/net/runtime/src/conn_init/mod.rs +++ b/src/net/runtime/src/conn_init/mod.rs @@ -7,8 +7,9 @@ use crate::connection::StreamWriter; use std::sync::atomic::Ordering; use temper_codec::decode::{NetDecode, NetDecodeOpts}; use temper_codec::net_types::var_int::VarInt; +use temper_components::entity_identity::Identity; use temper_components::player::client_information::ClientInformationComponent; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::player::player_properties::PlayerProperties; use temper_encryption::read::EncryptedReader; use temper_macros::lookup_packet; use temper_protocol::errors::NetError; @@ -26,7 +27,8 @@ use tracing::{error, trace}; /// - `player_identity`: Populated when login is successful and a player is identified. /// - `compression`: Indicates whether network compression should be enabled for this connection. pub(crate) struct LoginResult { - pub player_identity: Option, + pub player_identity: Option, + pub player_properties: Option, pub compression: bool, pub client_information_component: Option, } diff --git a/src/net/runtime/src/conn_init/status.rs b/src/net/runtime/src/conn_init/status.rs index a5959b42..1f563e08 100644 --- a/src/net/runtime/src/conn_init/status.rs +++ b/src/net/runtime/src/conn_init/status.rs @@ -103,6 +103,7 @@ pub(super) async fn status( player_identity: None, compression: false, client_information_component: None, + player_properties: None, }, )) } diff --git a/src/net/runtime/src/connection.rs b/src/net/runtime/src/connection.rs index ead51bf7..7d72790f 100644 --- a/src/net/runtime/src/connection.rs +++ b/src/net/runtime/src/connection.rs @@ -8,25 +8,26 @@ use std::time::Duration; use temper_codec::encode::NetEncode; use temper_codec::encode::NetEncodeOpts; use temper_codec::net_types::NetTypesError; +use temper_components::entity_identity::Identity; use temper_components::player::client_information::ClientInformationComponent; -use temper_components::player::player_identity::PlayerIdentity; +use temper_components::player::player_properties::PlayerProperties; use temper_encryption::read::EncryptedReader; use temper_encryption::write::EncryptedWriter; -use temper_protocol::ConnState::Play; use temper_protocol::errors::CompressionError::GenericCompressionError; use temper_protocol::errors::NetError::HandshakeTimeout; use temper_protocol::errors::PacketError::InvalidPacket; use temper_protocol::errors::{NetError, PacketError}; use temper_protocol::incoming::packet_skeleton::PacketSkeleton; -use temper_protocol::{PacketSender, handle_packet}; +use temper_protocol::ConnState::Play; +use temper_protocol::{handle_packet, PacketSender}; use temper_state::ServerState; use tokio::io::AsyncWriteExt; -use tokio::net::TcpStream; use tokio::net::tcp::OwnedWriteHalf; +use tokio::net::TcpStream; use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; use tokio::sync::oneshot; use tokio::time::timeout; -use tracing::{Instrument, debug, debug_span, error, trace, warn}; +use tracing::{debug, debug_span, error, trace, warn, Instrument}; /// The maximum time allowed for a client to complete its initial handshake. /// Connections exceeding this duration will be dropped to avoid resource hogging. @@ -232,8 +233,9 @@ impl StreamWriter { /// needs to be registered with the game world. pub struct NewConnection { pub stream: StreamWriter, - pub player_identity: PlayerIdentity, + pub player_identity: Identity, pub client_information_component: ClientInformationComponent, + pub player_properties: PlayerProperties, pub entity_return: oneshot::Sender, pub disconnect_handle: oneshot::Sender<()>, } @@ -344,6 +346,7 @@ pub async fn handle_connection( .send(NewConnection { stream, player_identity: login_result.player_identity.unwrap_or_default(), + player_properties: login_result.player_properties.unwrap_or_default(), entity_return, disconnect_handle: disconnect_return, client_information_component: login_result