mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 06:16:04 +00:00
* add EntityData::kind * start making metadata use hecs * make entity codegen generate ecs stuff * fix registry codegen * get rid of worldhaver it's not even used * add bevy_ecs to deps * rename Component to FormattedText also start making the metadata use bevy_ecs but bevy_ecs doesn't let you query on Bundles so it's annoying * generate metadata.rs correctly for bevy_ecs * start switching more entity stuff to use ecs * more ecs stuff for entity storage * ok well it compiles but it definitely doesn't work * random fixes * change a bunch of entity things to use the components * some ecs stuff in az-client * packet handler uses the ecs now and other fun changes i still need to make ticking use the ecs but that's tricker, i'm considering using bevy_ecs systems for those bevy_ecs systems can't be async but the only async things in ticking is just sending packets which can just be done as a tokio task so that's not a big deal * start converting some functions in az-client into systems committing because i'm about to try something that might go horribly wrong * start splitting client i'm probably gonna change it so azalea entity ids are separate from minecraft entity ids next (so stuff like player ids can be consistent and we don't have to wait for the login packet) * separate minecraft entity ids from azalea entity ids + more ecs stuff i guess i'm using bevy_app now too huh it's necessary for plugins and it lets us control the tick rate anyways so it's fine i think i'm still not 100% sure how packet handling that interacts with the world will work, but i think if i can sneak the ecs world into there it'll be fine. Can't put packet handling in the schedule because that'd make it tick-bound, which it's not (technically it'd still work but it'd be wrong and anticheats might realize). * packet handling now it runs the schedule only when we get a tick or packet 😄 also i systemified some more functions and did other random fixes so az-world and az-physics compile making azalea-client use the ecs is almost done! all the hard parts are done now i hope, i just have to finish writing all the code so it actually works * start figuring out how functions in Client will work generally just lifetimes being annoying but i think i can get it all to work * make writing packets work synchronously* * huh az-client compiles * start fixing stuff * start fixing some packets * make packet handler work i still haven't actually tested any of this yet lol but in theory it should all work i'll probably either actually test az-client and fix all the remaining issues or update the azalea crate next ok also one thing that i'm not particularly happy with is how the packet handlers are doing ugly queries like ```rs let local_player = ecs .query::<&LocalPlayer>() .get_mut(ecs, player_entity) .unwrap(); ``` i think the right way to solve it would be by putting every packet handler in its own system but i haven't come up with a way to make that not be really annoying yet * fix warnings * ok what if i just have a bunch of queries and a single packet handler system * simple example for azalea-client * 🐛 * maybe fix deadlock idk can't test it rn lmao * make physicsstate its own component * use the default plugins * azalea compiles lol * use systemstate for packet handler * fix entities basically moved some stuff from being in the world to just being components * physics (ticking) works * try to add a .entity_by function still doesn't work because i want to make the predicate magic * try to make entity_by work well it does work but i couldn't figure out how to make it look not terrible. Will hopefully change in the future * everything compiles * start converting swarm to use builder * continue switching swarm to builder and fix stuff * make swarm use builder still have to fix some stuff and make client use builder * fix death event * client builder * fix some warnings * document plugins a bit * start trying to fix tests * azalea-ecs * azalea-ecs stuff compiles * az-physics tests pass 🎉 * fix all the tests * clippy on azalea-ecs-macros * remove now-unnecessary trait_upcasting feature * fix some clippy::pedantic warnings lol * why did cargo fmt not remove the trailing spaces * FIX ALL THE THINGS * when i said 'all' i meant non-swarm bugs * start adding task pool * fix entity deduplication * fix pathfinder not stopping * fix some more random bugs * fix panic that sometimes happens in swarms * make pathfinder run in task * fix some tests * fix doctests and clippy * deadlock * fix systems running in wrong order * fix non-swarm bots
935 lines
42 KiB
Rust
935 lines
42 KiB
Rust
use std::{collections::HashSet, io::Cursor, sync::Arc};
|
|
|
|
use azalea_core::{ChunkPos, ResourceLocation, Vec3};
|
|
use azalea_ecs::{
|
|
app::{App, Plugin},
|
|
component::Component,
|
|
ecs::Ecs,
|
|
entity::Entity,
|
|
event::EventWriter,
|
|
query::Changed,
|
|
schedule::{IntoSystemDescriptor, SystemSet},
|
|
system::{Commands, Query, ResMut, SystemState},
|
|
};
|
|
use azalea_protocol::{
|
|
connect::{ReadConnection, WriteConnection},
|
|
packets::game::{
|
|
clientbound_player_combat_kill_packet::ClientboundPlayerCombatKillPacket,
|
|
serverbound_accept_teleportation_packet::ServerboundAcceptTeleportationPacket,
|
|
serverbound_custom_payload_packet::ServerboundCustomPayloadPacket,
|
|
serverbound_keep_alive_packet::ServerboundKeepAlivePacket,
|
|
serverbound_move_player_pos_rot_packet::ServerboundMovePlayerPosRotPacket,
|
|
ClientboundGamePacket, ServerboundGamePacket,
|
|
},
|
|
};
|
|
use azalea_world::{
|
|
entity::{
|
|
metadata::{apply_metadata, Health, PlayerMetadataBundle},
|
|
set_rotation, Dead, EntityBundle, EntityKind, LastSentPosition, MinecraftEntityId, Physics,
|
|
PlayerBundle, Position,
|
|
},
|
|
LoadedBy, PartialWorld, RelativeEntityUpdate, WorldContainer,
|
|
};
|
|
use log::{debug, error, trace, warn};
|
|
use parking_lot::Mutex;
|
|
use tokio::sync::mpsc;
|
|
|
|
use crate::{
|
|
local_player::{GameProfileComponent, LocalPlayer},
|
|
ChatPacket, ClientInformation, PlayerInfo,
|
|
};
|
|
|
|
pub struct PacketHandlerPlugin;
|
|
|
|
impl Plugin for PacketHandlerPlugin {
|
|
fn build(&self, app: &mut App) {
|
|
app.add_system_set(
|
|
SystemSet::new().with_system(handle_packets.label("packet").before("tick")),
|
|
)
|
|
.add_event::<AddPlayerEvent>()
|
|
.add_event::<RemovePlayerEvent>()
|
|
.add_event::<UpdatePlayerEvent>()
|
|
.add_event::<ChatReceivedEvent>()
|
|
.add_event::<DeathEvent>();
|
|
}
|
|
}
|
|
|
|
/// A player joined the game (or more specifically, was added to the tab
|
|
/// list of a local player).
|
|
#[derive(Debug)]
|
|
pub struct AddPlayerEvent {
|
|
/// The local player entity that received this event.
|
|
pub entity: Entity,
|
|
pub info: PlayerInfo,
|
|
}
|
|
/// A player left the game (or maybe is still in the game and was just
|
|
/// removed from the tab list of a local player).
|
|
#[derive(Debug)]
|
|
pub struct RemovePlayerEvent {
|
|
/// The local player entity that received this event.
|
|
pub entity: Entity,
|
|
pub info: PlayerInfo,
|
|
}
|
|
/// A player was updated in the tab list of a local player (gamemode, display
|
|
/// name, or latency changed).
|
|
#[derive(Debug)]
|
|
pub struct UpdatePlayerEvent {
|
|
/// The local player entity that received this event.
|
|
pub entity: Entity,
|
|
pub info: PlayerInfo,
|
|
}
|
|
|
|
/// A client received a chat message packet.
|
|
#[derive(Debug)]
|
|
pub struct ChatReceivedEvent {
|
|
pub entity: Entity,
|
|
pub packet: ChatPacket,
|
|
}
|
|
|
|
/// Event for when an entity dies. dies. If it's a local player and there's a
|
|
/// reason in the death screen, the [`ClientboundPlayerCombatKillPacket`] will
|
|
/// be included.
|
|
pub struct DeathEvent {
|
|
pub entity: Entity,
|
|
pub packet: Option<ClientboundPlayerCombatKillPacket>,
|
|
}
|
|
|
|
/// Something that receives packets from the server.
|
|
#[derive(Component, Clone)]
|
|
pub struct PacketReceiver {
|
|
pub packets: Arc<Mutex<Vec<ClientboundGamePacket>>>,
|
|
pub run_schedule_sender: mpsc::Sender<()>,
|
|
}
|
|
|
|
fn handle_packets(ecs: &mut Ecs) {
|
|
let mut events_owned = Vec::new();
|
|
|
|
{
|
|
let mut system_state: SystemState<
|
|
Query<(Entity, &PacketReceiver), Changed<PacketReceiver>>,
|
|
> = SystemState::new(ecs);
|
|
let query = system_state.get(ecs);
|
|
for (player_entity, packet_events) in &query {
|
|
let mut packets = packet_events.packets.lock();
|
|
if !packets.is_empty() {
|
|
events_owned.push((player_entity, packets.clone()));
|
|
// clear the packets right after we read them
|
|
packets.clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
for (player_entity, packets) in events_owned {
|
|
for packet in &packets {
|
|
match packet {
|
|
ClientboundGamePacket::Login(p) => {
|
|
debug!("Got login packet");
|
|
|
|
#[allow(clippy::type_complexity)]
|
|
let mut system_state: SystemState<(
|
|
Commands,
|
|
Query<(&mut LocalPlayer, &GameProfileComponent)>,
|
|
ResMut<WorldContainer>,
|
|
)> = SystemState::new(ecs);
|
|
let (mut commands, mut query, mut world_container) = system_state.get_mut(ecs);
|
|
let (mut local_player, game_profile) = query.get_mut(player_entity).unwrap();
|
|
|
|
{
|
|
// TODO: have registry_holder be a struct because this sucks rn
|
|
// best way would be to add serde support to azalea-nbt
|
|
|
|
let registry_holder = p
|
|
.registry_holder
|
|
.as_compound()
|
|
.expect("Registry holder is not a compound")
|
|
.get("")
|
|
.expect("No \"\" tag")
|
|
.as_compound()
|
|
.expect("\"\" tag is not a compound");
|
|
let dimension_types = registry_holder
|
|
.get("minecraft:dimension_type")
|
|
.expect("No dimension_type tag")
|
|
.as_compound()
|
|
.expect("dimension_type is not a compound")
|
|
.get("value")
|
|
.expect("No dimension_type value")
|
|
.as_list()
|
|
.expect("dimension_type value is not a list");
|
|
let dimension_type = dimension_types
|
|
.iter()
|
|
.find(|t| {
|
|
t.as_compound()
|
|
.expect("dimension_type value is not a compound")
|
|
.get("name")
|
|
.expect("No name tag")
|
|
.as_string()
|
|
.expect("name is not a string")
|
|
== p.dimension_type.to_string()
|
|
})
|
|
.unwrap_or_else(|| {
|
|
panic!("No dimension_type with name {}", p.dimension_type)
|
|
})
|
|
.as_compound()
|
|
.unwrap()
|
|
.get("element")
|
|
.expect("No element tag")
|
|
.as_compound()
|
|
.expect("element is not a compound");
|
|
let height = (*dimension_type
|
|
.get("height")
|
|
.expect("No height tag")
|
|
.as_int()
|
|
.expect("height tag is not an int"))
|
|
.try_into()
|
|
.expect("height is not a u32");
|
|
let min_y = *dimension_type
|
|
.get("min_y")
|
|
.expect("No min_y tag")
|
|
.as_int()
|
|
.expect("min_y tag is not an int");
|
|
|
|
let world_name = p.dimension.clone();
|
|
|
|
local_player.world_name = Some(world_name.clone());
|
|
// add this world to the world_container (or don't if it's already
|
|
// there)
|
|
let weak_world = world_container.insert(world_name.clone(), height, min_y);
|
|
// set the partial_world to an empty world
|
|
// (when we add chunks or entities those will be in the
|
|
// world_container)
|
|
|
|
*local_player.partial_world.write() = PartialWorld::new(
|
|
local_player.client_information.view_distance.into(),
|
|
// this argument makes it so other clients don't update this
|
|
// player entity
|
|
// in a shared world
|
|
Some(player_entity),
|
|
);
|
|
local_player.world = weak_world;
|
|
|
|
let player_bundle = PlayerBundle {
|
|
entity: EntityBundle::new(
|
|
game_profile.uuid,
|
|
Vec3::default(),
|
|
azalea_registry::EntityKind::Player,
|
|
world_name,
|
|
),
|
|
metadata: PlayerMetadataBundle::default(),
|
|
};
|
|
// insert our components into the ecs :)
|
|
commands
|
|
.entity(player_entity)
|
|
.insert((MinecraftEntityId(p.player_id), player_bundle));
|
|
}
|
|
|
|
// send the client information that we have set
|
|
let client_information_packet: ClientInformation =
|
|
local_player.client_information.clone();
|
|
log::debug!(
|
|
"Sending client information because login: {:?}",
|
|
client_information_packet
|
|
);
|
|
local_player.write_packet(client_information_packet.get());
|
|
|
|
// brand
|
|
local_player.write_packet(
|
|
ServerboundCustomPayloadPacket {
|
|
identifier: ResourceLocation::new("brand").unwrap(),
|
|
// they don't have to know :)
|
|
data: "vanilla".into(),
|
|
}
|
|
.get(),
|
|
);
|
|
|
|
system_state.apply(ecs);
|
|
}
|
|
ClientboundGamePacket::SetChunkCacheRadius(p) => {
|
|
debug!("Got set chunk cache radius packet {:?}", p);
|
|
}
|
|
ClientboundGamePacket::CustomPayload(p) => {
|
|
debug!("Got custom payload packet {:?}", p);
|
|
}
|
|
ClientboundGamePacket::ChangeDifficulty(p) => {
|
|
debug!("Got difficulty packet {:?}", p);
|
|
}
|
|
ClientboundGamePacket::Commands(_p) => {
|
|
debug!("Got declare commands packet");
|
|
}
|
|
ClientboundGamePacket::PlayerAbilities(p) => {
|
|
debug!("Got player abilities packet {:?}", p);
|
|
}
|
|
ClientboundGamePacket::SetCarriedItem(p) => {
|
|
debug!("Got set carried item packet {:?}", p);
|
|
}
|
|
ClientboundGamePacket::UpdateTags(_p) => {
|
|
debug!("Got update tags packet");
|
|
}
|
|
ClientboundGamePacket::Disconnect(p) => {
|
|
debug!("Got disconnect packet {:?}", p);
|
|
let mut system_state: SystemState<Query<&LocalPlayer>> = SystemState::new(ecs);
|
|
let query = system_state.get(ecs);
|
|
let local_player = query.get(player_entity).unwrap();
|
|
local_player.disconnect();
|
|
}
|
|
ClientboundGamePacket::UpdateRecipes(_p) => {
|
|
debug!("Got update recipes packet");
|
|
}
|
|
ClientboundGamePacket::EntityEvent(_p) => {
|
|
// debug!("Got entity event packet {:?}", p);
|
|
}
|
|
ClientboundGamePacket::Recipe(_p) => {
|
|
debug!("Got recipe packet");
|
|
}
|
|
ClientboundGamePacket::PlayerPosition(p) => {
|
|
// TODO: reply with teleport confirm
|
|
debug!("Got player position packet {:?}", p);
|
|
|
|
let mut system_state: SystemState<
|
|
Query<(
|
|
&mut LocalPlayer,
|
|
&mut Physics,
|
|
&mut Position,
|
|
&mut LastSentPosition,
|
|
)>,
|
|
> = SystemState::new(ecs);
|
|
let mut query = system_state.get_mut(ecs);
|
|
let Ok((mut local_player, mut physics, mut position, mut last_sent_position)) =
|
|
query.get_mut(player_entity) else {
|
|
continue;
|
|
};
|
|
|
|
let delta_movement = physics.delta;
|
|
|
|
let is_x_relative = p.relative_arguments.x;
|
|
let is_y_relative = p.relative_arguments.y;
|
|
let is_z_relative = p.relative_arguments.z;
|
|
|
|
let (delta_x, new_pos_x) = if is_x_relative {
|
|
last_sent_position.x += p.x;
|
|
(delta_movement.x, position.x + p.x)
|
|
} else {
|
|
last_sent_position.x = p.x;
|
|
(0.0, p.x)
|
|
};
|
|
let (delta_y, new_pos_y) = if is_y_relative {
|
|
last_sent_position.y += p.y;
|
|
(delta_movement.y, position.y + p.y)
|
|
} else {
|
|
last_sent_position.y = p.y;
|
|
(0.0, p.y)
|
|
};
|
|
let (delta_z, new_pos_z) = if is_z_relative {
|
|
last_sent_position.z += p.z;
|
|
(delta_movement.z, position.z + p.z)
|
|
} else {
|
|
last_sent_position.z = p.z;
|
|
(0.0, p.z)
|
|
};
|
|
|
|
let mut y_rot = p.y_rot;
|
|
let mut x_rot = p.x_rot;
|
|
if p.relative_arguments.x_rot {
|
|
x_rot += physics.x_rot;
|
|
}
|
|
if p.relative_arguments.y_rot {
|
|
y_rot += physics.y_rot;
|
|
}
|
|
|
|
physics.delta = Vec3 {
|
|
x: delta_x,
|
|
y: delta_y,
|
|
z: delta_z,
|
|
};
|
|
// we call a function instead of setting the fields ourself since the
|
|
// function makes sure the rotations stay in their
|
|
// ranges
|
|
set_rotation(&mut physics, y_rot, x_rot);
|
|
// TODO: minecraft sets "xo", "yo", and "zo" here but idk what that means
|
|
// so investigate that ig
|
|
let new_pos = Vec3 {
|
|
x: new_pos_x,
|
|
y: new_pos_y,
|
|
z: new_pos_z,
|
|
};
|
|
|
|
**position = new_pos;
|
|
|
|
local_player
|
|
.write_packet(ServerboundAcceptTeleportationPacket { id: p.id }.get());
|
|
local_player.write_packet(
|
|
ServerboundMovePlayerPosRotPacket {
|
|
x: new_pos.x,
|
|
y: new_pos.y,
|
|
z: new_pos.z,
|
|
y_rot,
|
|
x_rot,
|
|
// this is always false
|
|
on_ground: false,
|
|
}
|
|
.get(),
|
|
);
|
|
}
|
|
ClientboundGamePacket::PlayerInfoUpdate(p) => {
|
|
debug!("Got player info packet {:?}", p);
|
|
|
|
let mut system_state: SystemState<(
|
|
Query<&mut LocalPlayer>,
|
|
EventWriter<AddPlayerEvent>,
|
|
EventWriter<UpdatePlayerEvent>,
|
|
)> = SystemState::new(ecs);
|
|
let (mut query, mut add_player_events, mut update_player_events) =
|
|
system_state.get_mut(ecs);
|
|
let mut local_player = query.get_mut(player_entity).unwrap();
|
|
|
|
for updated_info in &p.entries {
|
|
// add the new player maybe
|
|
if p.actions.add_player {
|
|
let info = PlayerInfo {
|
|
profile: updated_info.profile.clone(),
|
|
uuid: updated_info.profile.uuid,
|
|
gamemode: updated_info.game_mode,
|
|
latency: updated_info.latency,
|
|
display_name: updated_info.display_name.clone(),
|
|
};
|
|
local_player
|
|
.players
|
|
.insert(updated_info.profile.uuid, info.clone());
|
|
add_player_events.send(AddPlayerEvent {
|
|
entity: player_entity,
|
|
info: info.clone(),
|
|
});
|
|
} else if let Some(info) =
|
|
local_player.players.get_mut(&updated_info.profile.uuid)
|
|
{
|
|
// `else if` because the block for add_player above
|
|
// already sets all the fields
|
|
if p.actions.update_game_mode {
|
|
info.gamemode = updated_info.game_mode;
|
|
}
|
|
if p.actions.update_latency {
|
|
info.latency = updated_info.latency;
|
|
}
|
|
if p.actions.update_display_name {
|
|
info.display_name = updated_info.display_name.clone();
|
|
}
|
|
update_player_events.send(UpdatePlayerEvent {
|
|
entity: player_entity,
|
|
info: info.clone(),
|
|
});
|
|
} else {
|
|
warn!(
|
|
"Ignoring PlayerInfoUpdate for unknown player {}",
|
|
updated_info.profile.uuid
|
|
);
|
|
}
|
|
}
|
|
}
|
|
ClientboundGamePacket::PlayerInfoRemove(p) => {
|
|
let mut system_state: SystemState<(
|
|
Query<&mut LocalPlayer>,
|
|
EventWriter<RemovePlayerEvent>,
|
|
)> = SystemState::new(ecs);
|
|
let (mut query, mut remove_player_events) = system_state.get_mut(ecs);
|
|
let mut local_player = query.get_mut(player_entity).unwrap();
|
|
|
|
for uuid in &p.profile_ids {
|
|
if let Some(info) = local_player.players.remove(uuid) {
|
|
remove_player_events.send(RemovePlayerEvent {
|
|
entity: player_entity,
|
|
info,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
ClientboundGamePacket::SetChunkCacheCenter(p) => {
|
|
debug!("Got chunk cache center packet {:?}", p);
|
|
|
|
let mut system_state: SystemState<Query<&mut LocalPlayer>> =
|
|
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_world.write();
|
|
|
|
partial_world.chunks.view_center = ChunkPos::new(p.x, p.z);
|
|
}
|
|
ClientboundGamePacket::LevelChunkWithLight(p) => {
|
|
debug!("Got chunk with light packet {} {}", p.x, p.z);
|
|
let pos = ChunkPos::new(p.x, p.z);
|
|
|
|
let mut system_state: SystemState<Query<&mut LocalPlayer>> =
|
|
SystemState::new(ecs);
|
|
let mut query = system_state.get_mut(ecs);
|
|
let local_player = query.get_mut(player_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.world.read().chunks.get(&pos);
|
|
let this_client_has_chunk = local_player
|
|
.partial_world
|
|
.read()
|
|
.chunks
|
|
.limited_get(&pos)
|
|
.is_some();
|
|
|
|
let mut world = local_player.world.write();
|
|
let mut partial_world = local_player.partial_world.write();
|
|
|
|
if !this_client_has_chunk {
|
|
if let Some(shared_chunk) = shared_chunk {
|
|
trace!(
|
|
"Skipping parsing chunk {:?} because we already know about it",
|
|
pos
|
|
);
|
|
partial_world.chunks.set_with_shared_reference(
|
|
&pos,
|
|
Some(shared_chunk.clone()),
|
|
&mut world.chunks,
|
|
);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if let Err(e) = partial_world.chunks.replace_with_packet_data(
|
|
&pos,
|
|
&mut Cursor::new(&p.chunk_data.data),
|
|
&mut world.chunks,
|
|
) {
|
|
error!("Couldn't set chunk data: {}", e);
|
|
}
|
|
}
|
|
ClientboundGamePacket::LightUpdate(_p) => {
|
|
// debug!("Got light update packet {:?}", p);
|
|
}
|
|
ClientboundGamePacket::AddEntity(p) => {
|
|
debug!("Got add entity packet {:?}", p);
|
|
|
|
let mut system_state: SystemState<(Commands, Query<&mut LocalPlayer>)> =
|
|
SystemState::new(ecs);
|
|
let (mut commands, mut query) = system_state.get_mut(ecs);
|
|
let local_player = query.get_mut(player_entity).unwrap();
|
|
|
|
if let Some(world_name) = &local_player.world_name {
|
|
let bundle = p.as_entity_bundle(world_name.clone());
|
|
let mut entity_commands = commands.spawn((
|
|
MinecraftEntityId(p.id),
|
|
LoadedBy(HashSet::from([player_entity])),
|
|
bundle,
|
|
));
|
|
// the bundle doesn't include the default entity metadata so we add that
|
|
// separately
|
|
p.apply_metadata(&mut entity_commands);
|
|
} else {
|
|
warn!("got add player packet but we haven't gotten a login packet yet");
|
|
}
|
|
|
|
system_state.apply(ecs);
|
|
}
|
|
ClientboundGamePacket::SetEntityData(p) => {
|
|
debug!("Got set entity data packet {:?}", p);
|
|
|
|
let mut system_state: SystemState<(
|
|
Commands,
|
|
Query<&mut LocalPlayer>,
|
|
Query<&EntityKind>,
|
|
)> = SystemState::new(ecs);
|
|
let (mut commands, mut query, entity_kind_query) = system_state.get_mut(ecs);
|
|
let local_player = query.get_mut(player_entity).unwrap();
|
|
|
|
let world = local_player.world.read();
|
|
let entity = world.entity_by_id(&MinecraftEntityId(p.id));
|
|
drop(world);
|
|
|
|
if let Some(entity) = entity {
|
|
let entity_kind = entity_kind_query.get(entity).unwrap();
|
|
let mut entity_commands = commands.entity(entity);
|
|
if let Err(e) = apply_metadata(
|
|
&mut entity_commands,
|
|
**entity_kind,
|
|
(*p.packed_items).clone(),
|
|
) {
|
|
warn!("{e}");
|
|
}
|
|
} else {
|
|
warn!("Server sent an entity data packet for an entity id ({}) that we don't know about", p.id);
|
|
}
|
|
|
|
system_state.apply(ecs);
|
|
}
|
|
ClientboundGamePacket::UpdateAttributes(_p) => {
|
|
// debug!("Got update attributes packet {:?}", p);
|
|
}
|
|
ClientboundGamePacket::SetEntityMotion(_p) => {
|
|
// debug!("Got entity velocity packet {:?}", p);
|
|
}
|
|
ClientboundGamePacket::SetEntityLink(p) => {
|
|
debug!("Got set entity link packet {:?}", p);
|
|
}
|
|
ClientboundGamePacket::AddPlayer(p) => {
|
|
debug!("Got add player packet {:?}", p);
|
|
|
|
let mut system_state: SystemState<(Commands, Query<&mut LocalPlayer>)> =
|
|
SystemState::new(ecs);
|
|
let (mut commands, mut query) = system_state.get_mut(ecs);
|
|
let local_player = query.get_mut(player_entity).unwrap();
|
|
|
|
if let Some(world_name) = &local_player.world_name {
|
|
let bundle = p.as_player_bundle(world_name.clone());
|
|
let mut spawned = commands.spawn((
|
|
MinecraftEntityId(p.id),
|
|
LoadedBy(HashSet::from([player_entity])),
|
|
bundle,
|
|
));
|
|
|
|
if let Some(player_info) = local_player.players.get(&p.uuid) {
|
|
spawned.insert(GameProfileComponent(player_info.profile.clone()));
|
|
}
|
|
} else {
|
|
warn!("got add player packet but we haven't gotten a login packet yet");
|
|
}
|
|
|
|
system_state.apply(ecs);
|
|
}
|
|
ClientboundGamePacket::InitializeBorder(p) => {
|
|
debug!("Got initialize border packet {:?}", p);
|
|
}
|
|
ClientboundGamePacket::SetTime(_p) => {
|
|
// debug!("Got set time packet {:?}", p);
|
|
}
|
|
ClientboundGamePacket::SetDefaultSpawnPosition(p) => {
|
|
debug!("Got set default spawn position packet {:?}", p);
|
|
}
|
|
ClientboundGamePacket::ContainerSetContent(p) => {
|
|
debug!("Got container set content packet {:?}", p);
|
|
}
|
|
ClientboundGamePacket::SetHealth(p) => {
|
|
debug!("Got set health packet {:?}", p);
|
|
|
|
let mut system_state: SystemState<(
|
|
Query<&mut Health>,
|
|
EventWriter<DeathEvent>,
|
|
)> = SystemState::new(ecs);
|
|
let (mut query, mut death_events) = system_state.get_mut(ecs);
|
|
let mut health = query.get_mut(player_entity).unwrap();
|
|
|
|
if p.health == 0. && **health != 0. {
|
|
death_events.send(DeathEvent {
|
|
entity: player_entity,
|
|
packet: None,
|
|
});
|
|
}
|
|
|
|
**health = p.health;
|
|
|
|
// the `Dead` component is added by the `update_dead` system
|
|
// in azalea-world and then the `dead_event` system fires
|
|
// the Death event.
|
|
}
|
|
ClientboundGamePacket::SetExperience(p) => {
|
|
debug!("Got set experience packet {:?}", p);
|
|
}
|
|
ClientboundGamePacket::TeleportEntity(p) => {
|
|
let mut system_state: SystemState<(Commands, Query<&mut LocalPlayer>)> =
|
|
SystemState::new(ecs);
|
|
let (mut commands, mut query) = system_state.get_mut(ecs);
|
|
let local_player = query.get_mut(player_entity).unwrap();
|
|
|
|
let world = local_player.world.read();
|
|
let entity = world.entity_by_id(&MinecraftEntityId(p.id));
|
|
drop(world);
|
|
|
|
if let Some(entity) = entity {
|
|
let new_position = p.position;
|
|
commands.add(RelativeEntityUpdate {
|
|
entity,
|
|
partial_world: local_player.partial_world.clone(),
|
|
update: Box::new(move |entity| {
|
|
let mut position = entity.get_mut::<Position>().unwrap();
|
|
**position = new_position;
|
|
}),
|
|
});
|
|
} else {
|
|
warn!("Got teleport entity packet for unknown entity id {}", p.id);
|
|
}
|
|
|
|
system_state.apply(ecs);
|
|
}
|
|
ClientboundGamePacket::UpdateAdvancements(p) => {
|
|
debug!("Got update advancements packet {:?}", p);
|
|
}
|
|
ClientboundGamePacket::RotateHead(_p) => {
|
|
// debug!("Got rotate head packet {:?}", p);
|
|
}
|
|
ClientboundGamePacket::MoveEntityPos(p) => {
|
|
let mut system_state: SystemState<(Commands, Query<&LocalPlayer>)> =
|
|
SystemState::new(ecs);
|
|
let (mut commands, mut query) = system_state.get_mut(ecs);
|
|
let local_player = query.get_mut(player_entity).unwrap();
|
|
|
|
let world = local_player.world.read();
|
|
let entity = world.entity_by_id(&MinecraftEntityId(p.entity_id));
|
|
drop(world);
|
|
|
|
if let Some(entity) = entity {
|
|
let delta = p.delta.clone();
|
|
commands.add(RelativeEntityUpdate {
|
|
entity,
|
|
partial_world: local_player.partial_world.clone(),
|
|
update: Box::new(move |entity_mut| {
|
|
let mut position = entity_mut.get_mut::<Position>().unwrap();
|
|
**position = position.with_delta(&delta);
|
|
}),
|
|
});
|
|
} else {
|
|
warn!(
|
|
"Got move entity pos packet for unknown entity id {}",
|
|
p.entity_id
|
|
);
|
|
}
|
|
|
|
system_state.apply(ecs);
|
|
}
|
|
ClientboundGamePacket::MoveEntityPosRot(p) => {
|
|
let mut system_state: SystemState<(Commands, Query<&mut LocalPlayer>)> =
|
|
SystemState::new(ecs);
|
|
let (mut commands, mut query) = system_state.get_mut(ecs);
|
|
let local_player = query.get_mut(player_entity).unwrap();
|
|
|
|
let world = local_player.world.read();
|
|
let entity = world.entity_by_id(&MinecraftEntityId(p.entity_id));
|
|
drop(world);
|
|
|
|
if let Some(entity) = entity {
|
|
let delta = p.delta.clone();
|
|
commands.add(RelativeEntityUpdate {
|
|
entity,
|
|
partial_world: local_player.partial_world.clone(),
|
|
update: Box::new(move |entity_mut| {
|
|
let mut position = entity_mut.get_mut::<Position>().unwrap();
|
|
**position = position.with_delta(&delta);
|
|
}),
|
|
});
|
|
} else {
|
|
warn!(
|
|
"Got move entity pos rot packet for unknown entity id {}",
|
|
p.entity_id
|
|
);
|
|
}
|
|
|
|
system_state.apply(ecs);
|
|
}
|
|
|
|
ClientboundGamePacket::MoveEntityRot(_p) => {
|
|
// debug!("Got move entity rot packet {:?}", p);
|
|
}
|
|
ClientboundGamePacket::KeepAlive(p) => {
|
|
debug!("Got keep alive packet {p:?} for {player_entity:?}");
|
|
|
|
let mut system_state: SystemState<Query<&mut LocalPlayer>> =
|
|
SystemState::new(ecs);
|
|
let mut query = system_state.get_mut(ecs);
|
|
let mut local_player = query.get_mut(player_entity).unwrap();
|
|
|
|
local_player.write_packet(ServerboundKeepAlivePacket { id: p.id }.get());
|
|
debug!("Sent keep alive packet {p:?} for {player_entity:?}");
|
|
}
|
|
ClientboundGamePacket::RemoveEntities(p) => {
|
|
debug!("Got remove entities packet {:?}", p);
|
|
}
|
|
ClientboundGamePacket::PlayerChat(p) => {
|
|
debug!("Got player chat packet {:?}", p);
|
|
|
|
let mut system_state: SystemState<EventWriter<ChatReceivedEvent>> =
|
|
SystemState::new(ecs);
|
|
let mut chat_events = system_state.get_mut(ecs);
|
|
|
|
chat_events.send(ChatReceivedEvent {
|
|
entity: player_entity,
|
|
packet: ChatPacket::Player(Arc::new(p.clone())),
|
|
});
|
|
}
|
|
ClientboundGamePacket::SystemChat(p) => {
|
|
debug!("Got system chat packet {:?}", p);
|
|
|
|
let mut system_state: SystemState<EventWriter<ChatReceivedEvent>> =
|
|
SystemState::new(ecs);
|
|
let mut chat_events = system_state.get_mut(ecs);
|
|
|
|
chat_events.send(ChatReceivedEvent {
|
|
entity: player_entity,
|
|
packet: ChatPacket::System(Arc::new(p.clone())),
|
|
});
|
|
}
|
|
ClientboundGamePacket::Sound(_p) => {
|
|
// debug!("Got sound packet {:?}", p);
|
|
}
|
|
ClientboundGamePacket::LevelEvent(p) => {
|
|
debug!("Got level event packet {:?}", p);
|
|
}
|
|
ClientboundGamePacket::BlockUpdate(p) => {
|
|
debug!("Got block update packet {:?}", p);
|
|
|
|
let mut system_state: SystemState<Query<&mut LocalPlayer>> =
|
|
SystemState::new(ecs);
|
|
let mut query = system_state.get_mut(ecs);
|
|
let local_player = query.get_mut(player_entity).unwrap();
|
|
|
|
let world = local_player.world.write();
|
|
|
|
world.chunks.set_block_state(&p.pos, p.block_state);
|
|
}
|
|
ClientboundGamePacket::Animate(p) => {
|
|
debug!("Got animate packet {:?}", p);
|
|
}
|
|
ClientboundGamePacket::SectionBlocksUpdate(p) => {
|
|
debug!("Got section blocks update packet {:?}", p);
|
|
let mut system_state: SystemState<Query<&mut LocalPlayer>> =
|
|
SystemState::new(ecs);
|
|
let mut query = system_state.get_mut(ecs);
|
|
let local_player = query.get_mut(player_entity).unwrap();
|
|
|
|
let world = local_player.world.write();
|
|
|
|
for state in &p.states {
|
|
world
|
|
.chunks
|
|
.set_block_state(&(p.section_pos + state.pos.clone()), state.state);
|
|
}
|
|
}
|
|
ClientboundGamePacket::GameEvent(p) => {
|
|
debug!("Got game event packet {:?}", p);
|
|
}
|
|
ClientboundGamePacket::LevelParticles(p) => {
|
|
debug!("Got level particles packet {:?}", p);
|
|
}
|
|
ClientboundGamePacket::ServerData(p) => {
|
|
debug!("Got server data packet {:?}", p);
|
|
}
|
|
ClientboundGamePacket::SetEquipment(p) => {
|
|
debug!("Got set equipment packet {:?}", p);
|
|
}
|
|
ClientboundGamePacket::UpdateMobEffect(p) => {
|
|
debug!("Got update mob effect packet {:?}", p);
|
|
}
|
|
ClientboundGamePacket::AddExperienceOrb(_) => {}
|
|
ClientboundGamePacket::AwardStats(_) => {}
|
|
ClientboundGamePacket::BlockChangedAck(_) => {}
|
|
ClientboundGamePacket::BlockDestruction(_) => {}
|
|
ClientboundGamePacket::BlockEntityData(_) => {}
|
|
ClientboundGamePacket::BlockEvent(_) => {}
|
|
ClientboundGamePacket::BossEvent(_) => {}
|
|
ClientboundGamePacket::CommandSuggestions(_) => {}
|
|
ClientboundGamePacket::ContainerSetData(_) => {}
|
|
ClientboundGamePacket::ContainerSetSlot(_) => {}
|
|
ClientboundGamePacket::Cooldown(_) => {}
|
|
ClientboundGamePacket::CustomChatCompletions(_) => {}
|
|
ClientboundGamePacket::DeleteChat(_) => {}
|
|
ClientboundGamePacket::Explode(_) => {}
|
|
ClientboundGamePacket::ForgetLevelChunk(_) => {}
|
|
ClientboundGamePacket::HorseScreenOpen(_) => {}
|
|
ClientboundGamePacket::MapItemData(_) => {}
|
|
ClientboundGamePacket::MerchantOffers(_) => {}
|
|
ClientboundGamePacket::MoveVehicle(_) => {}
|
|
ClientboundGamePacket::OpenBook(_) => {}
|
|
ClientboundGamePacket::OpenScreen(_) => {}
|
|
ClientboundGamePacket::OpenSignEditor(_) => {}
|
|
ClientboundGamePacket::Ping(_) => {}
|
|
ClientboundGamePacket::PlaceGhostRecipe(_) => {}
|
|
ClientboundGamePacket::PlayerCombatEnd(_) => {}
|
|
ClientboundGamePacket::PlayerCombatEnter(_) => {}
|
|
ClientboundGamePacket::PlayerCombatKill(p) => {
|
|
debug!("Got player kill packet {:?}", p);
|
|
|
|
#[allow(clippy::type_complexity)]
|
|
let mut system_state: SystemState<(
|
|
Commands,
|
|
Query<(&MinecraftEntityId, Option<&Dead>)>,
|
|
EventWriter<DeathEvent>,
|
|
)> = SystemState::new(ecs);
|
|
let (mut commands, mut query, mut death_events) = system_state.get_mut(ecs);
|
|
let (entity_id, dead) = query.get_mut(player_entity).unwrap();
|
|
|
|
if **entity_id == p.player_id && dead.is_none() {
|
|
commands.entity(player_entity).insert(Dead);
|
|
death_events.send(DeathEvent {
|
|
entity: player_entity,
|
|
packet: Some(p.clone()),
|
|
});
|
|
}
|
|
|
|
system_state.apply(ecs);
|
|
}
|
|
ClientboundGamePacket::PlayerLookAt(_) => {}
|
|
ClientboundGamePacket::RemoveMobEffect(_) => {}
|
|
ClientboundGamePacket::ResourcePack(_) => {}
|
|
ClientboundGamePacket::Respawn(p) => {
|
|
debug!("Got respawn packet {:?}", p);
|
|
|
|
let mut system_state: SystemState<Commands> = SystemState::new(ecs);
|
|
let mut commands = system_state.get(ecs);
|
|
|
|
// Remove the Dead marker component from the player.
|
|
commands.entity(player_entity).remove::<Dead>();
|
|
|
|
system_state.apply(ecs);
|
|
}
|
|
ClientboundGamePacket::SelectAdvancementsTab(_) => {}
|
|
ClientboundGamePacket::SetActionBarText(_) => {}
|
|
ClientboundGamePacket::SetBorderCenter(_) => {}
|
|
ClientboundGamePacket::SetBorderLerpSize(_) => {}
|
|
ClientboundGamePacket::SetBorderSize(_) => {}
|
|
ClientboundGamePacket::SetBorderWarningDelay(_) => {}
|
|
ClientboundGamePacket::SetBorderWarningDistance(_) => {}
|
|
ClientboundGamePacket::SetCamera(_) => {}
|
|
ClientboundGamePacket::SetDisplayObjective(_) => {}
|
|
ClientboundGamePacket::SetObjective(_) => {}
|
|
ClientboundGamePacket::SetPassengers(_) => {}
|
|
ClientboundGamePacket::SetPlayerTeam(_) => {}
|
|
ClientboundGamePacket::SetScore(_) => {}
|
|
ClientboundGamePacket::SetSimulationDistance(_) => {}
|
|
ClientboundGamePacket::SetSubtitleText(_) => {}
|
|
ClientboundGamePacket::SetTitleText(_) => {}
|
|
ClientboundGamePacket::SetTitlesAnimation(_) => {}
|
|
ClientboundGamePacket::SoundEntity(_) => {}
|
|
ClientboundGamePacket::StopSound(_) => {}
|
|
ClientboundGamePacket::TabList(_) => {}
|
|
ClientboundGamePacket::TagQuery(_) => {}
|
|
ClientboundGamePacket::TakeItemEntity(_) => {}
|
|
ClientboundGamePacket::DisguisedChat(_) => {}
|
|
ClientboundGamePacket::UpdateEnabledFeatures(_) => {}
|
|
ClientboundGamePacket::ContainerClose(_) => {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PacketReceiver {
|
|
/// Loop that reads from the connection and adds the packets to the queue +
|
|
/// runs the schedule.
|
|
pub async fn read_task(self, mut read_conn: ReadConnection<ClientboundGamePacket>) {
|
|
while let Ok(packet) = read_conn.read().await {
|
|
self.packets.lock().push(packet);
|
|
// tell the client to run all the systems
|
|
self.run_schedule_sender.send(()).await.unwrap();
|
|
}
|
|
}
|
|
|
|
/// Consume the [`ServerboundGamePacket`] queue and actually write the
|
|
/// packets to the server. It's like this so writing packets doesn't need to
|
|
/// be awaited.
|
|
pub async fn write_task(
|
|
self,
|
|
mut write_conn: WriteConnection<ServerboundGamePacket>,
|
|
mut write_receiver: mpsc::UnboundedReceiver<ServerboundGamePacket>,
|
|
) {
|
|
while let Some(packet) = write_receiver.recv().await {
|
|
if let Err(err) = write_conn.write(packet).await {
|
|
error!("Disconnecting because we couldn't write a packet: {err}.");
|
|
break;
|
|
};
|
|
}
|
|
// receiver is automatically closed when it's dropped
|
|
}
|
|
}
|