mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 06:16:04 +00:00
share registries in swarms and fix some bugs
This commit is contained in:
parent
000abfa136
commit
f0b58c7e74
21 changed files with 634 additions and 556 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -332,6 +332,7 @@ dependencies = [
|
|||
"nohash-hasher",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
|
@ -525,6 +526,8 @@ dependencies = [
|
|||
"nohash-hasher",
|
||||
"once_cell",
|
||||
"parking_lot",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"uuid",
|
||||
|
|
|
@ -173,9 +173,8 @@ impl McBufReadable for UnsizedByteArray {
|
|||
impl<T: McBufReadable + Send> McBufReadable for Vec<T> {
|
||||
default fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
||||
let length = u32::var_read_from(buf)? as usize;
|
||||
// we don't set the capacity here so we can't get exploited into
|
||||
// allocating a bunch
|
||||
let mut contents = Vec::new();
|
||||
// we limit the capacity to not get exploited into allocating a bunch
|
||||
let mut contents = Vec::with_capacity(usize::min(length, 65536));
|
||||
for _ in 0..length {
|
||||
contents.push(T::read_from(buf)?);
|
||||
}
|
||||
|
@ -186,7 +185,7 @@ impl<T: McBufReadable + Send> McBufReadable for Vec<T> {
|
|||
impl<K: McBufReadable + Send + Eq + Hash, V: McBufReadable + Send> McBufReadable for HashMap<K, V> {
|
||||
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
||||
let length = i32::var_read_from(buf)? as usize;
|
||||
let mut contents = HashMap::new();
|
||||
let mut contents = HashMap::with_capacity(usize::min(length, 65536));
|
||||
for _ in 0..length {
|
||||
contents.insert(K::read_from(buf)?, V::read_from(buf)?);
|
||||
}
|
||||
|
@ -199,7 +198,7 @@ impl<K: McBufReadable + Send + Eq + Hash, V: McBufVarReadable + Send> McBufVarRe
|
|||
{
|
||||
fn var_read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
||||
let length = i32::var_read_from(buf)? as usize;
|
||||
let mut contents = HashMap::new();
|
||||
let mut contents = HashMap::with_capacity(usize::min(length, 65536));
|
||||
for _ in 0..length {
|
||||
contents.insert(K::read_from(buf)?, V::var_read_from(buf)?);
|
||||
}
|
||||
|
@ -253,7 +252,7 @@ impl McBufVarReadable for u16 {
|
|||
impl<T: McBufVarReadable> McBufVarReadable for Vec<T> {
|
||||
fn var_read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
||||
let length = i32::var_read_from(buf)? as usize;
|
||||
let mut contents = Vec::new();
|
||||
let mut contents = Vec::with_capacity(usize::min(length, 65536));
|
||||
for _ in 0..length {
|
||||
contents.push(T::var_read_from(buf)?);
|
||||
}
|
||||
|
|
|
@ -79,30 +79,22 @@ fn handle_receive_chunk_events(
|
|||
|
||||
let local_player = query.get_mut(event.entity).unwrap();
|
||||
|
||||
// OPTIMIZATION: if we already know about the chunk from the
|
||||
// shared world (and not ourselves), then we don't need to
|
||||
// parse it again. This is only used when we have a shared
|
||||
// world, since we check that the chunk isn't currently owned
|
||||
// by this client.
|
||||
let shared_chunk = local_player.instance.read().chunks.get(&pos);
|
||||
let this_client_has_chunk = local_player
|
||||
.partial_instance
|
||||
.read()
|
||||
.chunks
|
||||
.limited_get(&pos)
|
||||
.is_some();
|
||||
let mut instance = local_player.instance.write();
|
||||
let mut partial_instance = local_player.partial_instance.write();
|
||||
|
||||
let mut world = local_player.instance.write();
|
||||
let mut partial_world = local_player.partial_instance.write();
|
||||
// OPTIMIZATION: if we already know about the chunk from the shared world (and
|
||||
// not ourselves), then we don't need to parse it again. This is only used when
|
||||
// we have a shared world, since we check that the chunk isn't currently owned
|
||||
// by this client.
|
||||
let shared_chunk = instance.chunks.get(&pos);
|
||||
let this_client_has_chunk = partial_instance.chunks.limited_get(&pos).is_some();
|
||||
|
||||
if !this_client_has_chunk {
|
||||
if let Some(shared_chunk) = shared_chunk {
|
||||
trace!("Skipping parsing chunk {pos:?} because we already know about it");
|
||||
partial_world.chunks.set_with_shared_reference(
|
||||
&pos,
|
||||
Some(shared_chunk.clone()),
|
||||
&mut world.chunks,
|
||||
);
|
||||
partial_instance
|
||||
.chunks
|
||||
.limited_set(&pos, Some(shared_chunk));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -112,11 +104,11 @@ fn handle_receive_chunk_events(
|
|||
let empty_nbt_compound = NbtCompound::default();
|
||||
let heightmaps = heightmaps.unwrap_or(&empty_nbt_compound);
|
||||
|
||||
if let Err(e) = partial_world.chunks.replace_with_packet_data(
|
||||
if let Err(e) = partial_instance.chunks.replace_with_packet_data(
|
||||
&pos,
|
||||
&mut Cursor::new(&event.packet.chunk_data.data),
|
||||
heightmaps,
|
||||
&mut world.chunks,
|
||||
&mut instance.chunks,
|
||||
) {
|
||||
error!("Couldn't set chunk data: {e}");
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ use crate::{
|
|||
raw_connection::RawConnection,
|
||||
respawn::RespawnPlugin,
|
||||
task_pool::TaskPoolPlugin,
|
||||
Account, PlayerInfo, ReceivedRegistries,
|
||||
Account, PlayerInfo,
|
||||
};
|
||||
|
||||
use azalea_auth::{game_profile::GameProfile, sessionserver::ClientSessionServerError};
|
||||
|
@ -99,8 +99,6 @@ pub struct Client {
|
|||
pub profile: GameProfile,
|
||||
/// The entity for this client in the ECS.
|
||||
pub entity: Entity,
|
||||
/// The world that this client is in.
|
||||
pub world: Arc<RwLock<PartialInstance>>,
|
||||
|
||||
/// The entity component system. You probably don't need to access this
|
||||
/// directly. Note that if you're using a shared world (i.e. a swarm), this
|
||||
|
@ -147,7 +145,6 @@ impl Client {
|
|||
profile,
|
||||
// default our id to 0, it'll be set later
|
||||
entity,
|
||||
world: Arc::new(RwLock::new(PartialInstance::default())),
|
||||
|
||||
ecs,
|
||||
|
||||
|
@ -268,7 +265,6 @@ impl Client {
|
|||
read_conn,
|
||||
write_conn,
|
||||
),
|
||||
received_registries: ReceivedRegistries::default(),
|
||||
local_player_events: LocalPlayerEvents(tx),
|
||||
game_profile: GameProfileComponent(game_profile),
|
||||
client_information: crate::ClientInformation::default(),
|
||||
|
@ -592,7 +588,6 @@ impl Client {
|
|||
#[derive(Bundle)]
|
||||
pub struct LocalPlayerBundle {
|
||||
pub raw_connection: RawConnection,
|
||||
pub received_registries: ReceivedRegistries,
|
||||
pub local_player_events: LocalPlayerEvents,
|
||||
pub game_profile: GameProfileComponent,
|
||||
pub client_information: ClientInformation,
|
||||
|
@ -604,7 +599,7 @@ pub struct LocalPlayerBundle {
|
|||
/// use [`LocalEntity`].
|
||||
#[derive(Bundle)]
|
||||
pub struct JoinedClientBundle {
|
||||
pub instance_holder: InstanceHolder,
|
||||
// note that InstanceHolder isn't here because it's set slightly before we fully join the world
|
||||
pub physics_state: PhysicsState,
|
||||
pub inventory: InventoryComponent,
|
||||
pub tab_list: TabList,
|
||||
|
@ -679,11 +674,12 @@ async fn run_schedule_loop(
|
|||
mut run_schedule_receiver: mpsc::UnboundedReceiver<()>,
|
||||
) {
|
||||
loop {
|
||||
// whenever we get an event from run_schedule_receiver, run the schedule
|
||||
run_schedule_receiver.recv().await;
|
||||
// get rid of any queued events
|
||||
while let Ok(()) = run_schedule_receiver.try_recv() {}
|
||||
|
||||
// whenever we get an event from run_schedule_receiver, run the schedule
|
||||
run_schedule_receiver.recv().await;
|
||||
|
||||
let mut ecs = ecs.lock();
|
||||
ecs.run_schedule(outer_schedule_label);
|
||||
ecs.clear_trackers();
|
||||
|
|
|
@ -163,7 +163,7 @@ fn packet_listener(query: Query<&LocalPlayerEvents>, mut events: EventReader<Pac
|
|||
for event in events.read() {
|
||||
let local_player_events = query
|
||||
.get(event.entity)
|
||||
.expect("Non-local entities shouldn't be able to receive add player events");
|
||||
.expect("Non-local entities shouldn't be able to receive packet events");
|
||||
local_player_events
|
||||
.send(Event::Packet(event.packet.clone()))
|
||||
.unwrap();
|
||||
|
|
|
@ -27,7 +27,6 @@ pub mod packet_handling;
|
|||
pub mod ping;
|
||||
mod player;
|
||||
pub mod raw_connection;
|
||||
pub mod received_registries;
|
||||
pub mod respawn;
|
||||
pub mod task_pool;
|
||||
|
||||
|
@ -42,4 +41,3 @@ pub use movement::{
|
|||
PhysicsState, SprintDirection, StartSprintEvent, StartWalkEvent, WalkDirection,
|
||||
};
|
||||
pub use player::PlayerInfo;
|
||||
pub use received_registries::ReceivedRegistries;
|
||||
|
|
|
@ -26,7 +26,7 @@ use crate::{
|
|||
|
||||
/// A component that keeps strong references to our [`PartialInstance`] and
|
||||
/// [`Instance`] for local players.
|
||||
#[derive(Component)]
|
||||
#[derive(Component, Clone)]
|
||||
pub struct InstanceHolder {
|
||||
/// The partial instance is the world this client currently has loaded. It
|
||||
/// has a limited render distance.
|
||||
|
|
|
@ -20,7 +20,6 @@ use crate::disconnect::DisconnectEvent;
|
|||
use crate::local_player::Hunger;
|
||||
use crate::packet_handling::game::KeepAliveEvent;
|
||||
use crate::raw_connection::RawConnection;
|
||||
use crate::ReceivedRegistries;
|
||||
|
||||
#[derive(Event, Debug, Clone)]
|
||||
pub struct PacketEvent {
|
||||
|
@ -78,19 +77,21 @@ pub fn process_packet_events(ecs: &mut World) {
|
|||
for (player_entity, packet) in events_owned {
|
||||
match packet {
|
||||
ClientboundConfigurationPacket::RegistryData(p) => {
|
||||
let mut system_state: SystemState<Query<&mut ReceivedRegistries>> =
|
||||
SystemState::new(ecs);
|
||||
let mut query = system_state.get_mut(ecs);
|
||||
let mut received_registries = query.get_mut(player_entity).unwrap();
|
||||
let mut instance = Instance::default();
|
||||
|
||||
let new_received_registries = p.registry_holder.registries;
|
||||
// override the old registries with the new ones
|
||||
// but if a registry wasn't sent, keep the old one
|
||||
for (registry_name, registry) in new_received_registries {
|
||||
received_registries
|
||||
.registries
|
||||
.insert(registry_name, registry);
|
||||
for (registry_name, registry) in p.registry_holder.map {
|
||||
instance.registries.map.insert(registry_name, registry);
|
||||
}
|
||||
|
||||
let instance_holder = crate::local_player::InstanceHolder::new(
|
||||
player_entity,
|
||||
// default to an empty world, it'll be set correctly later when we
|
||||
// get the login packet
|
||||
Arc::new(RwLock::new(instance)),
|
||||
);
|
||||
ecs.entity_mut(player_entity).insert(instance_holder);
|
||||
}
|
||||
|
||||
ClientboundConfigurationPacket::CustomPayload(p) => {
|
||||
|
@ -113,13 +114,6 @@ pub fn process_packet_events(ecs: &mut World) {
|
|||
let mut query = system_state.get_mut(ecs);
|
||||
let mut raw_connection = query.get_mut(player_entity).unwrap();
|
||||
|
||||
let instance_holder = crate::local_player::InstanceHolder::new(
|
||||
player_entity,
|
||||
// default to an empty world, it'll be set correctly later when we
|
||||
// get the login packet
|
||||
Arc::new(RwLock::new(Instance::default())),
|
||||
);
|
||||
|
||||
raw_connection
|
||||
.write_packet(ServerboundFinishConfigurationPacket {}.get())
|
||||
.expect(
|
||||
|
@ -131,7 +125,6 @@ pub fn process_packet_events(ecs: &mut World) {
|
|||
ecs.entity_mut(player_entity)
|
||||
.remove::<InConfigurationState>()
|
||||
.insert(crate::JoinedClientBundle {
|
||||
instance_holder,
|
||||
physics_state: crate::PhysicsState::default(),
|
||||
inventory: crate::inventory::InventoryComponent::default(),
|
||||
tab_list: crate::local_player::TabList::default(),
|
||||
|
|
|
@ -45,7 +45,7 @@ use crate::{
|
|||
},
|
||||
movement::{KnockbackEvent, KnockbackType},
|
||||
raw_connection::RawConnection,
|
||||
ClientInformation, PlayerInfo, ReceivedRegistries,
|
||||
ClientInformation, PlayerInfo,
|
||||
};
|
||||
|
||||
/// An event that's sent when we receive a packet.
|
||||
|
@ -174,16 +174,18 @@ pub fn send_packet_events(
|
|||
}
|
||||
|
||||
pub fn process_packet_events(ecs: &mut World) {
|
||||
let mut events_owned = Vec::new();
|
||||
let mut system_state: SystemState<EventReader<PacketEvent>> = SystemState::new(ecs);
|
||||
let mut events = system_state.get_mut(ecs);
|
||||
for PacketEvent {
|
||||
entity: player_entity,
|
||||
packet,
|
||||
} in events.read()
|
||||
let mut events_owned = Vec::<(Entity, Arc<ClientboundGamePacket>)>::new();
|
||||
{
|
||||
// we do this so `ecs` isn't borrowed for the whole loop
|
||||
events_owned.push((*player_entity, packet.clone()));
|
||||
let mut system_state = SystemState::<EventReader<PacketEvent>>::new(ecs);
|
||||
let mut events = system_state.get_mut(ecs);
|
||||
for PacketEvent {
|
||||
entity: player_entity,
|
||||
packet,
|
||||
} in events.read()
|
||||
{
|
||||
// we do this so `ecs` isn't borrowed for the whole loop
|
||||
events_owned.push((*player_entity, packet.clone()));
|
||||
}
|
||||
}
|
||||
for (player_entity, packet) in events_owned {
|
||||
let packet_clone = packet.clone();
|
||||
|
@ -198,7 +200,6 @@ pub fn process_packet_events(ecs: &mut World) {
|
|||
Query<(
|
||||
&GameProfileComponent,
|
||||
&ClientInformation,
|
||||
&ReceivedRegistries,
|
||||
Option<&mut InstanceName>,
|
||||
Option<&mut LoadedBy>,
|
||||
&mut EntityIdIndex,
|
||||
|
@ -220,7 +221,6 @@ pub fn process_packet_events(ecs: &mut World) {
|
|||
let (
|
||||
game_profile,
|
||||
client_information,
|
||||
received_registries,
|
||||
instance_name,
|
||||
loaded_by,
|
||||
mut entity_id_index,
|
||||
|
@ -238,7 +238,9 @@ pub fn process_packet_events(ecs: &mut World) {
|
|||
.insert(InstanceName(new_instance_name.clone()));
|
||||
}
|
||||
|
||||
let Some(dimension_type) = received_registries.dimension_type() else {
|
||||
let Some(dimension_type) =
|
||||
instance_holder.instance.read().registries.dimension_type()
|
||||
else {
|
||||
error!("Server didn't send dimension type registry, can't log in");
|
||||
continue;
|
||||
};
|
||||
|
@ -276,6 +278,17 @@ pub fn process_packet_events(ecs: &mut World) {
|
|||
// in a shared instance
|
||||
Some(player_entity),
|
||||
);
|
||||
{
|
||||
let new_registries = &mut weak_instance.write().registries;
|
||||
// add the registries from this instance to the weak instance
|
||||
for (registry_name, registry) in
|
||||
&instance_holder.instance.read().registries.map
|
||||
{
|
||||
new_registries
|
||||
.map
|
||||
.insert(registry_name.clone(), registry.clone());
|
||||
}
|
||||
}
|
||||
instance_holder.instance = weak_instance;
|
||||
|
||||
let player_bundle = PlayerBundle {
|
||||
|
@ -295,8 +308,6 @@ pub fn process_packet_events(ecs: &mut World) {
|
|||
current: p.common.game_type,
|
||||
previous: p.common.previous_game_type.into(),
|
||||
},
|
||||
// this gets overwritten later by the SetHealth packet
|
||||
received_registries.clone(),
|
||||
player_bundle,
|
||||
));
|
||||
|
||||
|
@ -583,10 +594,12 @@ pub fn process_packet_events(ecs: &mut World) {
|
|||
let mut system_state: SystemState<Query<&mut InstanceHolder>> =
|
||||
SystemState::new(ecs);
|
||||
let mut query = system_state.get_mut(ecs);
|
||||
let local_player = query.get_mut(player_entity).unwrap();
|
||||
let mut partial_world = local_player.partial_instance.write();
|
||||
let instance_holder = query.get_mut(player_entity).unwrap();
|
||||
let mut partial_world = instance_holder.partial_instance.write();
|
||||
|
||||
partial_world.chunks.view_center = ChunkPos::new(p.x, p.z);
|
||||
partial_world
|
||||
.chunks
|
||||
.update_view_center(ChunkPos::new(p.x, p.z));
|
||||
}
|
||||
ClientboundGamePacket::ChunksBiomes(_) => {}
|
||||
ClientboundGamePacket::LightUpdate(_p) => {
|
||||
|
@ -1167,7 +1180,18 @@ pub fn process_packet_events(ecs: &mut World) {
|
|||
|
||||
system_state.apply(ecs);
|
||||
}
|
||||
ClientboundGamePacket::ForgetLevelChunk(_) => {}
|
||||
ClientboundGamePacket::ForgetLevelChunk(p) => {
|
||||
debug!("Got forget level chunk packet {p:?}");
|
||||
|
||||
let mut system_state: SystemState<Query<&mut InstanceHolder>> =
|
||||
SystemState::new(ecs);
|
||||
let mut query = system_state.get_mut(ecs);
|
||||
let local_player = query.get_mut(player_entity).unwrap();
|
||||
|
||||
let mut partial_instance = local_player.partial_instance.write();
|
||||
|
||||
partial_instance.chunks.limited_set(&p.pos, None);
|
||||
}
|
||||
ClientboundGamePacket::HorseScreenOpen(_) => {}
|
||||
ClientboundGamePacket::MapItemData(_) => {}
|
||||
ClientboundGamePacket::MerchantOffers(_) => {}
|
||||
|
@ -1255,20 +1279,21 @@ pub fn process_packet_events(ecs: &mut World) {
|
|||
&mut InstanceHolder,
|
||||
&GameProfileComponent,
|
||||
&ClientInformation,
|
||||
&ReceivedRegistries,
|
||||
)>,
|
||||
EventWriter<InstanceLoadedEvent>,
|
||||
ResMut<InstanceContainer>,
|
||||
)> = SystemState::new(ecs);
|
||||
let (mut commands, mut query, mut instance_loaded_events, mut instance_container) =
|
||||
system_state.get_mut(ecs);
|
||||
let (mut instance_holder, game_profile, client_information, received_registries) =
|
||||
let (mut instance_holder, game_profile, client_information) =
|
||||
query.get_mut(player_entity).unwrap();
|
||||
|
||||
{
|
||||
let new_instance_name = p.common.dimension.clone();
|
||||
|
||||
let Some(dimension_type) = received_registries.dimension_type() else {
|
||||
let Some(dimension_type) =
|
||||
instance_holder.instance.read().registries.dimension_type()
|
||||
else {
|
||||
error!("Server didn't send dimension type registry, can't log in");
|
||||
continue;
|
||||
};
|
||||
|
@ -1298,6 +1323,7 @@ pub fn process_packet_events(ecs: &mut World) {
|
|||
// set the partial_world to an empty world
|
||||
// (when we add chunks or entities those will be in the
|
||||
// instance_container)
|
||||
|
||||
*instance_holder.partial_instance.write() = PartialInstance::new(
|
||||
azalea_world::chunk_storage::calculate_chunk_storage_range(
|
||||
client_information.view_distance.into(),
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use azalea_core::resource_location::ResourceLocation;
|
||||
use azalea_nbt::Nbt;
|
||||
use azalea_protocol::packets::configuration::clientbound_registry_data_packet::registry::{
|
||||
DimensionTypeElement, RegistryType,
|
||||
};
|
||||
use bevy_ecs::prelude::*;
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
/// The registries that were sent to us during the configuration state.
|
||||
#[derive(Default, Component, Clone)]
|
||||
pub struct ReceivedRegistries {
|
||||
pub registries: HashMap<ResourceLocation, Nbt>,
|
||||
}
|
||||
|
||||
impl ReceivedRegistries {
|
||||
fn get<T: DeserializeOwned>(&self, name: &ResourceLocation) -> Option<T> {
|
||||
let nbt = self.registries.get(name)?;
|
||||
serde_json::from_value(serde_json::to_value(nbt).ok()?).ok()
|
||||
}
|
||||
|
||||
/// Get the dimension type registry, or `None` if it doesn't exist. You
|
||||
/// should do some type of error handling if this returns `None`.
|
||||
pub fn dimension_type(&self) -> Option<RegistryType<DimensionTypeElement>> {
|
||||
self.get(&ResourceLocation::new("minecraft:dimension_type"))
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ nohash-hasher = "0.2.0"
|
|||
num-traits = "0.2.17"
|
||||
serde = { version = "^1.0", optional = true }
|
||||
uuid = "^1.5.0"
|
||||
serde_json = "^1.0.108"
|
||||
|
||||
[features]
|
||||
bevy_ecs = ["dep:bevy_ecs"]
|
||||
|
|
|
@ -14,5 +14,6 @@ pub mod game_type;
|
|||
pub mod math;
|
||||
pub mod particle;
|
||||
pub mod position;
|
||||
pub mod registry_holder;
|
||||
pub mod resource_location;
|
||||
pub mod tier;
|
||||
|
|
412
azalea-core/src/registry_holder.rs
Normal file
412
azalea-core/src/registry_holder.rs
Normal file
|
@ -0,0 +1,412 @@
|
|||
//! The data sent to the client in the `ClientboundRegistryDataPacket`.
|
||||
//!
|
||||
//! This module contains the structures used to represent the registry
|
||||
//! sent to the client upon login. This contains a lot of information about
|
||||
//! the game, including the types of chat messages, dimensions, and
|
||||
//! biomes.
|
||||
|
||||
use azalea_buf::{BufReadError, McBufReadable, McBufWritable};
|
||||
use azalea_nbt::Nbt;
|
||||
use serde::{
|
||||
de::{self, DeserializeOwned},
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
};
|
||||
use std::{collections::HashMap, io::Cursor};
|
||||
|
||||
use crate::resource_location::ResourceLocation;
|
||||
|
||||
/// The base of the registry.
|
||||
///
|
||||
/// This is the registry that is sent to the client upon login.
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RegistryHolder {
|
||||
pub map: HashMap<ResourceLocation, Nbt>,
|
||||
}
|
||||
|
||||
impl RegistryHolder {
|
||||
fn get<T: DeserializeOwned>(&self, name: &ResourceLocation) -> Option<T> {
|
||||
let nbt = self.map.get(name)?;
|
||||
serde_json::from_value(serde_json::to_value(nbt).ok()?).ok()
|
||||
}
|
||||
|
||||
/// Get the dimension type registry, or `None` if it doesn't exist. You
|
||||
/// should do some type of error handling if this returns `None`.
|
||||
pub fn dimension_type(&self) -> Option<RegistryType<DimensionTypeElement>> {
|
||||
self.get(&ResourceLocation::new("minecraft:dimension_type"))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Nbt> for RegistryHolder {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(value: Nbt) -> Result<Self, Self::Error> {
|
||||
Ok(RegistryHolder {
|
||||
map: serde_json::from_value(serde_json::to_value(value)?)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<Nbt> for RegistryHolder {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_into(self) -> Result<Nbt, Self::Error> {
|
||||
serde_json::from_value(serde_json::to_value(self.map)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufReadable for RegistryHolder {
|
||||
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
||||
RegistryHolder::try_from(Nbt::read_from(buf)?)
|
||||
.map_err(|e| BufReadError::Deserialization { source: e })
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufWritable for RegistryHolder {
|
||||
fn write_into(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
|
||||
TryInto::<Nbt>::try_into(self.clone())?.write_into(buf)
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of values for a certain type of registry data.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct RegistryType<T> {
|
||||
#[serde(rename = "type")]
|
||||
pub kind: ResourceLocation,
|
||||
pub value: Vec<TypeValue<T>>,
|
||||
}
|
||||
|
||||
/// A value for a certain type of registry data.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct TypeValue<T> {
|
||||
pub id: u32,
|
||||
pub name: ResourceLocation,
|
||||
pub element: T,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct TrimMaterialElement {
|
||||
pub asset_name: String,
|
||||
pub ingredient: ResourceLocation,
|
||||
pub item_model_index: f32,
|
||||
pub override_armor_materials: HashMap<String, String>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
/// Data about a kind of chat message
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct ChatTypeElement {
|
||||
pub chat: ChatTypeData,
|
||||
pub narration: ChatTypeData,
|
||||
}
|
||||
|
||||
/// Data about a chat message.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct ChatTypeData {
|
||||
pub translation_key: String,
|
||||
pub parameters: Vec<String>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub style: Option<ChatTypeStyle>,
|
||||
}
|
||||
|
||||
/// The style of a chat message.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct ChatTypeStyle {
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub color: Option<String>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(with = "Convert")]
|
||||
pub bold: Option<bool>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(with = "Convert")]
|
||||
pub italic: Option<bool>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(with = "Convert")]
|
||||
pub underlined: Option<bool>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(with = "Convert")]
|
||||
pub strikethrough: Option<bool>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(with = "Convert")]
|
||||
pub obfuscated: Option<bool>,
|
||||
}
|
||||
|
||||
/// Dimension attributes.
|
||||
#[cfg(feature = "strict_registry")]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct DimensionTypeElement {
|
||||
pub ambient_light: f32,
|
||||
#[serde(with = "Convert")]
|
||||
pub bed_works: bool,
|
||||
pub coordinate_scale: f32,
|
||||
pub effects: ResourceLocation,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub fixed_time: Option<u32>,
|
||||
#[serde(with = "Convert")]
|
||||
pub has_ceiling: bool,
|
||||
#[serde(with = "Convert")]
|
||||
pub has_raids: bool,
|
||||
#[serde(with = "Convert")]
|
||||
pub has_skylight: bool,
|
||||
pub height: u32,
|
||||
pub infiniburn: ResourceLocation,
|
||||
pub logical_height: u32,
|
||||
pub min_y: i32,
|
||||
pub monster_spawn_block_light_limit: u32,
|
||||
pub monster_spawn_light_level: MonsterSpawnLightLevel,
|
||||
#[serde(with = "Convert")]
|
||||
pub natural: bool,
|
||||
#[serde(with = "Convert")]
|
||||
pub piglin_safe: bool,
|
||||
#[serde(with = "Convert")]
|
||||
pub respawn_anchor_works: bool,
|
||||
#[serde(with = "Convert")]
|
||||
pub ultrawarm: bool,
|
||||
}
|
||||
|
||||
/// Dimension attributes.
|
||||
#[cfg(not(feature = "strict_registry"))]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DimensionTypeElement {
|
||||
pub height: u32,
|
||||
pub min_y: i32,
|
||||
#[serde(flatten)]
|
||||
pub _extra: HashMap<String, Nbt>,
|
||||
}
|
||||
|
||||
/// The light level at which monsters can spawn.
|
||||
///
|
||||
/// This can be either a single minimum value, or a formula with a min and
|
||||
/// max.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub enum MonsterSpawnLightLevel {
|
||||
/// A simple minimum value.
|
||||
Simple(u32),
|
||||
/// A complex value with a type, minimum, and maximum.
|
||||
/// Vanilla minecraft only uses one type, "minecraft:uniform".
|
||||
Complex {
|
||||
#[serde(rename = "type")]
|
||||
kind: ResourceLocation,
|
||||
value: MonsterSpawnLightLevelValues,
|
||||
},
|
||||
}
|
||||
|
||||
/// The min and max light levels at which monsters can spawn.
|
||||
///
|
||||
/// Values are inclusive.
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct MonsterSpawnLightLevelValues {
|
||||
#[serde(rename = "min_inclusive")]
|
||||
pub min: u32,
|
||||
#[serde(rename = "max_inclusive")]
|
||||
pub max: u32,
|
||||
}
|
||||
|
||||
/// Biome attributes.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct WorldTypeElement {
|
||||
#[serde(with = "Convert")]
|
||||
pub has_precipitation: bool,
|
||||
pub temperature: f32,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub temperature_modifier: Option<String>,
|
||||
pub downfall: f32,
|
||||
pub effects: BiomeEffects,
|
||||
}
|
||||
|
||||
/// The precipitation of a biome.
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub enum BiomePrecipitation {
|
||||
#[serde(rename = "none")]
|
||||
None,
|
||||
#[serde(rename = "rain")]
|
||||
Rain,
|
||||
#[serde(rename = "snow")]
|
||||
Snow,
|
||||
}
|
||||
|
||||
/// The effects of a biome.
|
||||
///
|
||||
/// This includes the sky, fog, water, and grass color,
|
||||
/// as well as music and other sound effects.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct BiomeEffects {
|
||||
pub sky_color: u32,
|
||||
pub fog_color: u32,
|
||||
pub water_color: u32,
|
||||
pub water_fog_color: u32,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub foliage_color: Option<u32>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub grass_color: Option<u32>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub grass_color_modifier: Option<String>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub music: Option<BiomeMusic>,
|
||||
pub mood_sound: BiomeMoodSound,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub additions_sound: Option<AdditionsSound>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ambient_sound: Option<ResourceLocation>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub particle: Option<BiomeParticle>,
|
||||
}
|
||||
|
||||
/// The music of the biome.
|
||||
///
|
||||
/// Some biomes have unique music that only play when inside them.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct BiomeMusic {
|
||||
#[serde(with = "Convert")]
|
||||
pub replace_current_music: bool,
|
||||
pub max_delay: u32,
|
||||
pub min_delay: u32,
|
||||
pub sound: azalea_registry::SoundEvent,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct BiomeMoodSound {
|
||||
pub tick_delay: u32,
|
||||
pub block_search_extent: u32,
|
||||
pub offset: f32,
|
||||
pub sound: azalea_registry::SoundEvent,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct AdditionsSound {
|
||||
pub tick_chance: f32,
|
||||
pub sound: azalea_registry::SoundEvent,
|
||||
}
|
||||
|
||||
/// Biome particles.
|
||||
///
|
||||
/// Some biomes have particles that spawn in the air.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct BiomeParticle {
|
||||
pub probability: f32,
|
||||
pub options: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct TrimPatternElement {
|
||||
#[serde(flatten)]
|
||||
pub pattern: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct DamageTypeElement {
|
||||
pub message_id: String,
|
||||
pub scaling: String,
|
||||
pub exhaustion: f32,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub effects: Option<String>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub death_message_type: Option<String>,
|
||||
}
|
||||
|
||||
// Using a trait because you can't implement methods for
|
||||
// types you don't own, in this case Option<bool> and bool.
|
||||
trait Convert: Sized {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer;
|
||||
|
||||
fn deserialize<'de, D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>;
|
||||
}
|
||||
|
||||
// Convert between bool and u8
|
||||
impl Convert for bool {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_u8(if *self { 1 } else { 0 })
|
||||
}
|
||||
|
||||
fn deserialize<'de, D>(deserializer: D) -> Result<bool, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
convert::<D>(u8::deserialize(deserializer)?)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert between Option<bool> and u8
|
||||
impl Convert for Option<bool> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
if let Some(value) = self {
|
||||
Convert::serialize(value, serializer)
|
||||
} else {
|
||||
serializer.serialize_none()
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize<'de, D>(deserializer: D) -> Result<Option<bool>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
if let Some(value) = Option::<u8>::deserialize(deserializer)? {
|
||||
Ok(Some(convert::<D>(value)?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deserializing logic here to deduplicate code
|
||||
fn convert<'de, D>(value: u8) -> Result<bool, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
match value {
|
||||
0 => Ok(false),
|
||||
1 => Ok(true),
|
||||
other => Err(de::Error::invalid_value(
|
||||
de::Unexpected::Unsigned(other as u64),
|
||||
&"zero or one",
|
||||
)),
|
||||
}
|
||||
}
|
|
@ -52,7 +52,7 @@ pub fn box_shape_unchecked(
|
|||
|
||||
if x_bits < 0 || y_bits < 0 || z_bits < 0 {
|
||||
return VoxelShape::Array(ArrayVoxelShape::new(
|
||||
BLOCK_SHAPE.shape(),
|
||||
BLOCK_SHAPE.shape().to_owned(),
|
||||
vec![min_x, max_x],
|
||||
vec![min_y, max_y],
|
||||
vec![min_z, max_z],
|
||||
|
@ -253,7 +253,14 @@ impl Shapes {
|
|||
op_false_true,
|
||||
);
|
||||
|
||||
Self::matches_anywhere_with_mergers(x_merger, y_merger, z_merger, a.shape(), b.shape(), op)
|
||||
Self::matches_anywhere_with_mergers(
|
||||
x_merger,
|
||||
y_merger,
|
||||
z_merger,
|
||||
a.shape().to_owned(),
|
||||
b.shape().to_owned(),
|
||||
op,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn matches_anywhere_with_mergers(
|
||||
|
@ -347,7 +354,7 @@ impl VoxelShape {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn shape(&self) -> DiscreteVoxelShape {
|
||||
pub fn shape(&self) -> &DiscreteVoxelShape {
|
||||
match self {
|
||||
VoxelShape::Array(s) => s.shape(),
|
||||
VoxelShape::Cube(s) => s.shape(),
|
||||
|
@ -372,7 +379,7 @@ impl VoxelShape {
|
|||
}
|
||||
|
||||
VoxelShape::Array(ArrayVoxelShape::new(
|
||||
self.shape(),
|
||||
self.shape().to_owned(),
|
||||
self.get_coords(Axis::X).iter().map(|c| c + x).collect(),
|
||||
self.get_coords(Axis::Y).iter().map(|c| c + y).collect(),
|
||||
self.get_coords(Axis::Z).iter().map(|c| c + z).collect(),
|
||||
|
@ -648,8 +655,8 @@ impl CubeVoxelShape {
|
|||
}
|
||||
|
||||
impl ArrayVoxelShape {
|
||||
fn shape(&self) -> DiscreteVoxelShape {
|
||||
self.shape.clone()
|
||||
fn shape(&self) -> &DiscreteVoxelShape {
|
||||
&self.shape
|
||||
}
|
||||
|
||||
fn get_coords(&self, axis: Axis) -> Vec<f64> {
|
||||
|
@ -658,8 +665,8 @@ impl ArrayVoxelShape {
|
|||
}
|
||||
|
||||
impl CubeVoxelShape {
|
||||
fn shape(&self) -> DiscreteVoxelShape {
|
||||
self.shape.clone()
|
||||
fn shape(&self) -> &DiscreteVoxelShape {
|
||||
&self.shape
|
||||
}
|
||||
|
||||
fn get_coords(&self, axis: Axis) -> Vec<f64> {
|
||||
|
|
|
@ -1,408 +1,8 @@
|
|||
use azalea_buf::McBuf;
|
||||
use azalea_core::registry_holder::RegistryHolder;
|
||||
use azalea_protocol_macros::ClientboundConfigurationPacket;
|
||||
|
||||
use self::registry::RegistryHolder;
|
||||
|
||||
#[derive(Clone, Debug, McBuf, ClientboundConfigurationPacket)]
|
||||
pub struct ClientboundRegistryDataPacket {
|
||||
pub registry_holder: RegistryHolder,
|
||||
}
|
||||
|
||||
pub mod registry {
|
||||
//! [ClientboundRegistryDataPacket](super::ClientboundRegistryDataPacket)
|
||||
//! Registry Structures
|
||||
//!
|
||||
//! This module contains the structures used to represent the registry
|
||||
//! sent to the client upon login. This contains a lot of information about
|
||||
//! the game, including the types of chat messages, dimensions, and
|
||||
//! biomes.
|
||||
|
||||
use azalea_buf::{BufReadError, McBufReadable, McBufWritable};
|
||||
use azalea_core::resource_location::ResourceLocation;
|
||||
use azalea_nbt::Nbt;
|
||||
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::{collections::HashMap, io::Cursor};
|
||||
|
||||
/// The base of the registry.
|
||||
///
|
||||
/// This is the registry that is sent to the client upon login.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct RegistryHolder {
|
||||
pub registries: HashMap<ResourceLocation, Nbt>,
|
||||
}
|
||||
|
||||
impl TryFrom<Nbt> for RegistryHolder {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_from(value: Nbt) -> Result<Self, Self::Error> {
|
||||
Ok(RegistryHolder {
|
||||
registries: serde_json::from_value(serde_json::to_value(value)?)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<Nbt> for RegistryHolder {
|
||||
type Error = serde_json::Error;
|
||||
|
||||
fn try_into(self) -> Result<Nbt, Self::Error> {
|
||||
serde_json::from_value(serde_json::to_value(self.registries)?)
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufReadable for RegistryHolder {
|
||||
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
||||
RegistryHolder::try_from(Nbt::read_from(buf)?)
|
||||
.map_err(|e| BufReadError::Deserialization { source: e })
|
||||
}
|
||||
}
|
||||
|
||||
impl McBufWritable for RegistryHolder {
|
||||
fn write_into(&self, buf: &mut impl std::io::Write) -> Result<(), std::io::Error> {
|
||||
TryInto::<Nbt>::try_into(self.clone())?.write_into(buf)
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of values for a certain type of registry data.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct RegistryType<T> {
|
||||
#[serde(rename = "type")]
|
||||
pub kind: ResourceLocation,
|
||||
pub value: Vec<TypeValue<T>>,
|
||||
}
|
||||
|
||||
/// A value for a certain type of registry data.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct TypeValue<T> {
|
||||
pub id: u32,
|
||||
pub name: ResourceLocation,
|
||||
pub element: T,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct TrimMaterialElement {
|
||||
pub asset_name: String,
|
||||
pub ingredient: ResourceLocation,
|
||||
pub item_model_index: f32,
|
||||
pub override_armor_materials: HashMap<String, String>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
}
|
||||
|
||||
/// Data about a kind of chat message
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct ChatTypeElement {
|
||||
pub chat: ChatTypeData,
|
||||
pub narration: ChatTypeData,
|
||||
}
|
||||
|
||||
/// Data about a chat message.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct ChatTypeData {
|
||||
pub translation_key: String,
|
||||
pub parameters: Vec<String>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub style: Option<ChatTypeStyle>,
|
||||
}
|
||||
|
||||
/// The style of a chat message.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct ChatTypeStyle {
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub color: Option<String>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(with = "Convert")]
|
||||
pub bold: Option<bool>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(with = "Convert")]
|
||||
pub italic: Option<bool>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(with = "Convert")]
|
||||
pub underlined: Option<bool>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(with = "Convert")]
|
||||
pub strikethrough: Option<bool>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[serde(with = "Convert")]
|
||||
pub obfuscated: Option<bool>,
|
||||
}
|
||||
|
||||
/// Dimension attributes.
|
||||
#[cfg(feature = "strict_registry")]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct DimensionTypeElement {
|
||||
pub ambient_light: f32,
|
||||
#[serde(with = "Convert")]
|
||||
pub bed_works: bool,
|
||||
pub coordinate_scale: f32,
|
||||
pub effects: ResourceLocation,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub fixed_time: Option<u32>,
|
||||
#[serde(with = "Convert")]
|
||||
pub has_ceiling: bool,
|
||||
#[serde(with = "Convert")]
|
||||
pub has_raids: bool,
|
||||
#[serde(with = "Convert")]
|
||||
pub has_skylight: bool,
|
||||
pub height: u32,
|
||||
pub infiniburn: ResourceLocation,
|
||||
pub logical_height: u32,
|
||||
pub min_y: i32,
|
||||
pub monster_spawn_block_light_limit: u32,
|
||||
pub monster_spawn_light_level: MonsterSpawnLightLevel,
|
||||
#[serde(with = "Convert")]
|
||||
pub natural: bool,
|
||||
#[serde(with = "Convert")]
|
||||
pub piglin_safe: bool,
|
||||
#[serde(with = "Convert")]
|
||||
pub respawn_anchor_works: bool,
|
||||
#[serde(with = "Convert")]
|
||||
pub ultrawarm: bool,
|
||||
}
|
||||
|
||||
/// Dimension attributes.
|
||||
#[cfg(not(feature = "strict_registry"))]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DimensionTypeElement {
|
||||
pub height: u32,
|
||||
pub min_y: i32,
|
||||
#[serde(flatten)]
|
||||
pub _extra: HashMap<String, Nbt>,
|
||||
}
|
||||
|
||||
/// The light level at which monsters can spawn.
|
||||
///
|
||||
/// This can be either a single minimum value, or a formula with a min and
|
||||
/// max.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub enum MonsterSpawnLightLevel {
|
||||
/// A simple minimum value.
|
||||
Simple(u32),
|
||||
/// A complex value with a type, minimum, and maximum.
|
||||
/// Vanilla minecraft only uses one type, "minecraft:uniform".
|
||||
Complex {
|
||||
#[serde(rename = "type")]
|
||||
kind: ResourceLocation,
|
||||
value: MonsterSpawnLightLevelValues,
|
||||
},
|
||||
}
|
||||
|
||||
/// The min and max light levels at which monsters can spawn.
|
||||
///
|
||||
/// Values are inclusive.
|
||||
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct MonsterSpawnLightLevelValues {
|
||||
#[serde(rename = "min_inclusive")]
|
||||
pub min: u32,
|
||||
#[serde(rename = "max_inclusive")]
|
||||
pub max: u32,
|
||||
}
|
||||
|
||||
/// Biome attributes.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct WorldTypeElement {
|
||||
#[serde(with = "Convert")]
|
||||
pub has_precipitation: bool,
|
||||
pub temperature: f32,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub temperature_modifier: Option<String>,
|
||||
pub downfall: f32,
|
||||
pub effects: BiomeEffects,
|
||||
}
|
||||
|
||||
/// The precipitation of a biome.
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub enum BiomePrecipitation {
|
||||
#[serde(rename = "none")]
|
||||
None,
|
||||
#[serde(rename = "rain")]
|
||||
Rain,
|
||||
#[serde(rename = "snow")]
|
||||
Snow,
|
||||
}
|
||||
|
||||
/// The effects of a biome.
|
||||
///
|
||||
/// This includes the sky, fog, water, and grass color,
|
||||
/// as well as music and other sound effects.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct BiomeEffects {
|
||||
pub sky_color: u32,
|
||||
pub fog_color: u32,
|
||||
pub water_color: u32,
|
||||
pub water_fog_color: u32,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub foliage_color: Option<u32>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub grass_color: Option<u32>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub grass_color_modifier: Option<String>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub music: Option<BiomeMusic>,
|
||||
pub mood_sound: BiomeMoodSound,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub additions_sound: Option<AdditionsSound>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub ambient_sound: Option<ResourceLocation>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub particle: Option<BiomeParticle>,
|
||||
}
|
||||
|
||||
/// The music of the biome.
|
||||
///
|
||||
/// Some biomes have unique music that only play when inside them.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct BiomeMusic {
|
||||
#[serde(with = "Convert")]
|
||||
pub replace_current_music: bool,
|
||||
pub max_delay: u32,
|
||||
pub min_delay: u32,
|
||||
pub sound: azalea_registry::SoundEvent,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct BiomeMoodSound {
|
||||
pub tick_delay: u32,
|
||||
pub block_search_extent: u32,
|
||||
pub offset: f32,
|
||||
pub sound: azalea_registry::SoundEvent,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct AdditionsSound {
|
||||
pub tick_chance: f32,
|
||||
pub sound: azalea_registry::SoundEvent,
|
||||
}
|
||||
|
||||
/// Biome particles.
|
||||
///
|
||||
/// Some biomes have particles that spawn in the air.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct BiomeParticle {
|
||||
pub probability: f32,
|
||||
pub options: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct TrimPatternElement {
|
||||
#[serde(flatten)]
|
||||
pub pattern: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "strict_registry", serde(deny_unknown_fields))]
|
||||
pub struct DamageTypeElement {
|
||||
pub message_id: String,
|
||||
pub scaling: String,
|
||||
pub exhaustion: f32,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub effects: Option<String>,
|
||||
#[serde(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub death_message_type: Option<String>,
|
||||
}
|
||||
|
||||
// Using a trait because you can't implement methods for
|
||||
// types you don't own, in this case Option<bool> and bool.
|
||||
trait Convert: Sized {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer;
|
||||
|
||||
fn deserialize<'de, D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>;
|
||||
}
|
||||
|
||||
// Convert between bool and u8
|
||||
impl Convert for bool {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_u8(if *self { 1 } else { 0 })
|
||||
}
|
||||
|
||||
fn deserialize<'de, D>(deserializer: D) -> Result<bool, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
convert::<D>(u8::deserialize(deserializer)?)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert between Option<bool> and u8
|
||||
impl Convert for Option<bool> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
if let Some(value) = self {
|
||||
Convert::serialize(value, serializer)
|
||||
} else {
|
||||
serializer.serialize_none()
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize<'de, D>(deserializer: D) -> Result<Option<bool>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
if let Some(value) = Option::<u8>::deserialize(deserializer)? {
|
||||
Ok(Some(convert::<D>(value)?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Deserializing logic here to deduplicate code
|
||||
fn convert<'de, D>(value: u8) -> Result<bool, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
match value {
|
||||
0 => Ok(false),
|
||||
1 => Ok(true),
|
||||
other => Err(de::Error::invalid_value(
|
||||
de::Unexpected::Unsigned(other as u64),
|
||||
&"zero or one",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,8 @@ once_cell = "1.18.0"
|
|||
parking_lot = "^0.12.1"
|
||||
thiserror = "1.0.50"
|
||||
uuid = "1.5.0"
|
||||
serde_json = "1.0.108"
|
||||
serde = "1.0.192"
|
||||
|
||||
[dev-dependencies]
|
||||
azalea-client = { path = "../azalea-client" }
|
||||
|
|
|
@ -23,9 +23,8 @@ const SECTION_HEIGHT: u32 = 16;
|
|||
/// An efficient storage of chunks for a client that has a limited render
|
||||
/// distance. This has support for using a shared [`ChunkStorage`].
|
||||
pub struct PartialChunkStorage {
|
||||
/// The center of the view, i.e. the chunk the player is currently in. You
|
||||
/// can safely modify this.
|
||||
pub view_center: ChunkPos,
|
||||
/// The center of the view, i.e. the chunk the player is currently in.
|
||||
view_center: ChunkPos,
|
||||
chunk_radius: u32,
|
||||
view_range: u32,
|
||||
// chunks is a list of size chunk_radius * chunk_radius
|
||||
|
@ -99,14 +98,47 @@ impl PartialChunkStorage {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_index(&self, chunk_pos: &ChunkPos) -> usize {
|
||||
/// Update the chunk to center the view on. This should be called when the
|
||||
/// client receives a `SetChunkCacheCenter` packet.
|
||||
pub fn update_view_center(&mut self, view_center: ChunkPos) {
|
||||
// this code block makes it force unload the chunks that are out of range after
|
||||
// updating the view center. it's usually fine without it but the commented code
|
||||
// is there in case you want to temporarily uncomment to test something
|
||||
|
||||
// ```
|
||||
// for index in 0..self.chunks.len() {
|
||||
// let chunk_pos = self.chunk_pos_from_index(index);
|
||||
// if !in_range_for_view_center_and_radius(&chunk_pos, view_center, self.chunk_radius) {
|
||||
// self.chunks[index] = None;
|
||||
// }
|
||||
// }
|
||||
// ```
|
||||
|
||||
self.view_center = view_center;
|
||||
}
|
||||
|
||||
/// Get the center of the view. This is usually the chunk that the player is
|
||||
/// in.
|
||||
pub fn view_center(&self) -> ChunkPos {
|
||||
self.view_center
|
||||
}
|
||||
|
||||
pub fn index_from_chunk_pos(&self, chunk_pos: &ChunkPos) -> usize {
|
||||
(i32::rem_euclid(chunk_pos.x, self.view_range as i32) * (self.view_range as i32)
|
||||
+ i32::rem_euclid(chunk_pos.z, self.view_range as i32)) as usize
|
||||
}
|
||||
|
||||
pub fn chunk_pos_from_index(&self, index: usize) -> ChunkPos {
|
||||
let x = index as i32 % self.view_range as i32;
|
||||
let z = index as i32 / self.view_range as i32;
|
||||
ChunkPos::new(
|
||||
x + self.view_center.x - self.chunk_radius as i32,
|
||||
z + self.view_center.z - self.chunk_radius as i32,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn in_range(&self, chunk_pos: &ChunkPos) -> bool {
|
||||
(chunk_pos.x - self.view_center.x).unsigned_abs() <= self.chunk_radius
|
||||
&& (chunk_pos.z - self.view_center.z).unsigned_abs() <= self.chunk_radius
|
||||
in_range_for_view_center_and_radius(chunk_pos, self.view_center, self.chunk_radius)
|
||||
}
|
||||
|
||||
pub fn set_block_state(
|
||||
|
@ -163,7 +195,7 @@ impl PartialChunkStorage {
|
|||
return None;
|
||||
}
|
||||
|
||||
let index = self.get_index(pos);
|
||||
let index = self.index_from_chunk_pos(pos);
|
||||
self.chunks[index].as_ref()
|
||||
}
|
||||
/// Get a mutable reference to a [`Chunk`] within render distance, or
|
||||
|
@ -174,7 +206,7 @@ impl PartialChunkStorage {
|
|||
return None;
|
||||
}
|
||||
|
||||
let index = self.get_index(pos);
|
||||
let index = self.index_from_chunk_pos(pos);
|
||||
Some(&mut self.chunks[index])
|
||||
}
|
||||
|
||||
|
@ -187,12 +219,13 @@ impl PartialChunkStorage {
|
|||
pub fn set(&mut self, pos: &ChunkPos, chunk: Option<Chunk>, chunk_storage: &mut ChunkStorage) {
|
||||
let new_chunk;
|
||||
|
||||
// add the chunk to the shared storage
|
||||
if let Some(chunk) = chunk {
|
||||
match chunk_storage.map.entry(*pos) {
|
||||
Entry::Occupied(mut e) => {
|
||||
if let Some(old_chunk) = e.get_mut().upgrade() {
|
||||
*old_chunk.write() = chunk;
|
||||
new_chunk = Some(old_chunk.clone())
|
||||
new_chunk = Some(old_chunk);
|
||||
} else {
|
||||
let chunk_lock = Arc::new(RwLock::new(chunk));
|
||||
e.insert(Arc::downgrade(&chunk_lock));
|
||||
|
@ -211,33 +244,28 @@ impl PartialChunkStorage {
|
|||
|
||||
new_chunk = None;
|
||||
}
|
||||
if let Some(chunk_mut) = self.limited_get_mut(pos) {
|
||||
*chunk_mut = new_chunk;
|
||||
}
|
||||
|
||||
self.limited_set(pos, new_chunk);
|
||||
}
|
||||
|
||||
/// Set a chunk in the shared storage and reference it from the limited
|
||||
/// storage. Use [`Self::set`] if you don't already have an
|
||||
/// `Arc<RwLock<Chunk>>` (it'll make it for you).
|
||||
/// Set a chunk in our limited storage, useful if your chunk is already
|
||||
/// referenced somewhere else and you want to make it also be referenced by
|
||||
/// this storage.
|
||||
///
|
||||
/// Use [`Self::set`] if you don't already have an `Arc<RwLock<Chunk>>`.
|
||||
///
|
||||
/// # Panics
|
||||
/// If the chunk is not in the render distance.
|
||||
pub fn set_with_shared_reference(
|
||||
&mut self,
|
||||
pos: &ChunkPos,
|
||||
chunk: Option<Arc<RwLock<Chunk>>>,
|
||||
chunk_storage: &mut ChunkStorage,
|
||||
) {
|
||||
if let Some(chunk) = &chunk {
|
||||
chunk_storage.map.insert(*pos, Arc::downgrade(chunk));
|
||||
} else {
|
||||
// don't remove it from the shared storage, since it'll be removed
|
||||
// automatically if this was the last reference
|
||||
}
|
||||
pub fn limited_set(&mut self, pos: &ChunkPos, chunk: Option<Arc<RwLock<Chunk>>>) {
|
||||
if let Some(chunk_mut) = self.limited_get_mut(pos) {
|
||||
*chunk_mut = chunk;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get an iterator over all the chunks in the storage.
|
||||
pub fn chunks(&self) -> impl Iterator<Item = &Option<Arc<RwLock<Chunk>>>> {
|
||||
self.chunks.iter()
|
||||
}
|
||||
}
|
||||
impl ChunkStorage {
|
||||
pub fn new(height: u32, min_y: i32) -> Self {
|
||||
|
@ -270,6 +298,15 @@ impl ChunkStorage {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn in_range_for_view_center_and_radius(
|
||||
chunk_pos: &ChunkPos,
|
||||
view_center: ChunkPos,
|
||||
chunk_radius: u32,
|
||||
) -> bool {
|
||||
(chunk_pos.x - view_center.x).unsigned_abs() <= chunk_radius
|
||||
&& (chunk_pos.z - view_center.z).unsigned_abs() <= chunk_radius
|
||||
}
|
||||
|
||||
impl Chunk {
|
||||
pub fn read_with_dimension_height(
|
||||
buf: &mut Cursor<&[u8]>,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use azalea_core::resource_location::ResourceLocation;
|
||||
use azalea_core::{registry_holder::RegistryHolder, resource_location::ResourceLocation};
|
||||
use bevy_ecs::{component::Component, system::Resource};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use nohash_hasher::IntMap;
|
||||
|
@ -70,6 +70,7 @@ impl InstanceContainer {
|
|||
chunks: ChunkStorage::new(height, min_y),
|
||||
entities_by_chunk: HashMap::new(),
|
||||
entity_by_id: IntMap::default(),
|
||||
registries: RegistryHolder::default(),
|
||||
}));
|
||||
self.instances.insert(name, Arc::downgrade(&world));
|
||||
world
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::{iterators::ChunkIterator, palette::Palette, ChunkStorage, PartialChunkStorage};
|
||||
use azalea_block::{BlockState, BlockStates, FluidState};
|
||||
use azalea_core::position::{BlockPos, ChunkPos};
|
||||
use azalea_core::registry_holder::RegistryHolder;
|
||||
use bevy_ecs::{component::Component, entity::Entity};
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use nohash_hasher::IntMap;
|
||||
|
@ -87,6 +88,8 @@ pub struct Instance {
|
|||
/// An index of Minecraft entity IDs to Azalea ECS entities. You should
|
||||
/// avoid using this and instead use `azalea_entity::EntityIdIndex`
|
||||
pub entity_by_id: IntMap<MinecraftEntityId, Entity>,
|
||||
|
||||
pub registries: RegistryHolder,
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
|
@ -237,6 +240,7 @@ impl From<ChunkStorage> for Instance {
|
|||
chunks,
|
||||
entities_by_chunk: HashMap::new(),
|
||||
entity_by_id: IntMap::default(),
|
||||
registries: RegistryHolder::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use azalea::inventory::ItemSlot;
|
|||
use azalea::pathfinder::goals::BlockPosGoal;
|
||||
use azalea::{prelude::*, swarm::prelude::*, BlockPos, GameProfileComponent, WalkDirection};
|
||||
use azalea::{Account, Client, Event};
|
||||
use azalea_client::SprintDirection;
|
||||
use azalea_client::{InstanceHolder, SprintDirection};
|
||||
use azalea_core::position::{ChunkBlockPos, ChunkPos, Vec3};
|
||||
use azalea_protocol::packets::game::ClientboundGamePacket;
|
||||
use azalea_world::heightmap::HeightmapKind;
|
||||
|
@ -47,7 +47,7 @@ async fn main() -> anyhow::Result<()> {
|
|||
|
||||
let mut accounts = Vec::new();
|
||||
|
||||
for i in 0..3 {
|
||||
for i in 0..100 {
|
||||
accounts.push(Account::offline(&format!("bot{i}")));
|
||||
}
|
||||
|
||||
|
@ -56,7 +56,7 @@ async fn main() -> anyhow::Result<()> {
|
|||
.add_accounts(accounts.clone())
|
||||
.set_handler(handle)
|
||||
.set_swarm_handler(swarm_handle)
|
||||
.join_delay(Duration::from_millis(100))
|
||||
// .join_delay(Duration::from_millis(1000))
|
||||
.start("localhost")
|
||||
.await;
|
||||
// let e = azalea::ClientBuilder::new()
|
||||
|
@ -80,7 +80,7 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<
|
|||
bot.chat("Hello world");
|
||||
}
|
||||
Event::Chat(m) => {
|
||||
println!("client chat message: {}", m.content());
|
||||
// println!("client chat message: {}", m.content());
|
||||
if m.content() == bot.profile.name {
|
||||
bot.chat("Bye");
|
||||
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||
|
@ -100,7 +100,6 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<
|
|||
let entity = bot.entity_by::<With<Player>, (&GameProfileComponent,)>(
|
||||
|(profile,): &(&GameProfileComponent,)| profile.name == sender,
|
||||
);
|
||||
println!("sender entity: {entity:?}");
|
||||
match m.content().as_str() {
|
||||
"whereami" => {
|
||||
let Some(entity) = entity else {
|
||||
|
@ -308,6 +307,45 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<
|
|||
bot.chat("no chunk found");
|
||||
}
|
||||
}
|
||||
"debugchunks" => {
|
||||
println!("shared:");
|
||||
|
||||
let partial_instance_lock = bot.component::<InstanceHolder>().partial_instance;
|
||||
let local_chunk_storage = &partial_instance_lock.read().chunks;
|
||||
|
||||
let mut total_loaded_chunks_count = 0;
|
||||
for (chunk_pos, chunk) in &bot.world().read().chunks.map {
|
||||
if let Some(chunk) = chunk.upgrade() {
|
||||
let in_range = local_chunk_storage.in_range(chunk_pos);
|
||||
println!(
|
||||
"{chunk_pos:?} has {} references{}",
|
||||
std::sync::Arc::strong_count(&chunk) - 1,
|
||||
if in_range { "" } else { " (out of range)" }
|
||||
);
|
||||
total_loaded_chunks_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
println!("local:");
|
||||
|
||||
let mut local_loaded_chunks_count = 0;
|
||||
for (i, chunk) in local_chunk_storage.chunks().enumerate() {
|
||||
if let Some(chunk) = chunk {
|
||||
let chunk_pos = local_chunk_storage.chunk_pos_from_index(i);
|
||||
println!(
|
||||
"{chunk_pos:?} has {} references",
|
||||
std::sync::Arc::strong_count(&chunk)
|
||||
);
|
||||
local_loaded_chunks_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
println!("total loaded chunks: {total_loaded_chunks_count}");
|
||||
println!(
|
||||
"local loaded chunks: {local_loaded_chunks_count}/{}",
|
||||
local_chunk_storage.chunks().collect::<Vec<_>>().len()
|
||||
);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -394,9 +394,7 @@ where
|
|||
let first_bot_state = first_bot.component::<S>();
|
||||
let first_bot_entity = first_bot.entity;
|
||||
|
||||
let mut tasks = Vec::new();
|
||||
|
||||
tasks.push((handler)(first_bot, first_event, first_bot_state.clone()));
|
||||
tokio::spawn((handler)(first_bot, first_event, first_bot_state.clone()));
|
||||
|
||||
// this makes it not have to keep locking the ecs
|
||||
let mut states = HashMap::new();
|
||||
|
@ -405,10 +403,8 @@ where
|
|||
let state = states
|
||||
.entry(bot.entity)
|
||||
.or_insert_with(|| bot.component::<S>().clone());
|
||||
tasks.push((handler)(bot, event, state.clone()));
|
||||
tokio::spawn((handler)(bot, event, state.clone()));
|
||||
}
|
||||
|
||||
tokio::spawn(join_all(tasks));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue