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

start figuring out how functions in Client will work

generally just lifetimes being annoying but i think i can get it all to work
This commit is contained in:
mat 2023-01-02 23:16:45 -06:00
parent 50f6578794
commit 21c08013b7
8 changed files with 123 additions and 141 deletions

View file

@ -1,6 +1,5 @@
//! Implementations of chat-related features.
use crate::LocalPlayer;
use azalea_chat::FormattedText;
use azalea_protocol::packets::game::{
clientbound_player_chat_packet::ClientboundPlayerChatPacket,
@ -13,6 +12,8 @@ use std::{
time::{SystemTime, UNIX_EPOCH},
};
use crate::client::Client;
/// A chat packet, either a system message or a chat message.
#[derive(Debug, Clone, PartialEq)]
pub enum ChatPacket {
@ -89,7 +90,7 @@ impl ChatPacket {
}
}
impl LocalPlayer {
impl Client {
/// Sends chat message to the server. This only sends the chat packet and
/// not the command packet. The [`Client::chat`] function handles checking
/// whether the message is a command and using the proper packet for you,

View file

@ -1,24 +1,19 @@
pub use crate::chat::ChatPacket;
use crate::{
local_player::LocalPlayer,
movement::{send_position, WalkDirection},
local_player::{send_tick_event, update_in_loaded_chunk, LocalPlayer},
movement::send_position,
packet_handling,
plugins::PluginStates,
Account, PlayerInfo,
};
use azalea_auth::{game_profile::GameProfile, sessionserver::SessionServerError};
use azalea_core::{ChunkPos, ResourceLocation, Vec3};
use azalea_protocol::{
connect::{Connection, ConnectionError, ReadConnection, WriteConnection},
connect::{Connection, ConnectionError},
packets::{
game::{
clientbound_player_combat_kill_packet::ClientboundPlayerCombatKillPacket,
serverbound_accept_teleportation_packet::ServerboundAcceptTeleportationPacket,
serverbound_client_information_packet::ServerboundClientInformationPacket,
serverbound_custom_payload_packet::ServerboundCustomPayloadPacket,
serverbound_keep_alive_packet::ServerboundKeepAlivePacket,
serverbound_move_player_pos_rot_packet::ServerboundMovePlayerPosRotPacket,
ClientboundGamePacket, ServerboundGamePacket,
},
handshake::{
@ -32,45 +27,31 @@ use azalea_protocol::{
},
ConnectionProtocol, PROTOCOL_VERSION,
},
read::ReadPacketError,
resolver, ServerAddress,
};
use azalea_world::{
entity::{
self,
metadata::{self, PlayerMetadataBundle},
Entity,
},
EntityInfos, PartialChunkStorage, PartialWorld, World, WorldContainer,
};
use azalea_world::{entity::Entity, EntityInfos, PartialWorld, World, WorldContainer};
use bevy_app::App;
use bevy_ecs::{
prelude::Component,
query::{QueryState, WorldQuery},
schedule::{IntoSystemDescriptor, Schedule, Stage, StageLabel, SystemStage},
system::{Query, SystemState},
schedule::{IntoSystemDescriptor, Schedule, Stage, SystemSet},
};
use futures::{stream::FuturesUnordered, StreamExt};
use iyes_loopless::prelude::*;
use log::{debug, error, info, trace, warn};
use parking_lot::{Mutex, RwLock};
use log::{debug, error, warn};
use parking_lot::{Mutex, MutexGuard, RwLock};
use std::{
any,
backtrace::Backtrace,
collections::HashMap,
fmt::Debug,
io::{self, Cursor},
ops::DerefMut,
io::{self},
marker::PhantomData,
ops::{Deref, DerefMut},
sync::Arc,
time::Duration,
};
use thiserror::Error;
use tokio::{
sync::mpsc::{self, Receiver, Sender},
sync::mpsc::{self, Receiver},
task::JoinHandle,
time::{self},
};
use uuid::Uuid;
pub type ClientInformation = ServerboundClientInformationPacket;
@ -202,7 +183,7 @@ impl Client {
pub async fn join(
account: &Account,
address: impl TryInto<ServerAddress>,
) -> Result<(Self, Receiver<Event>), JoinError> {
) -> Result<(Self, mpsc::UnboundedReceiver<Event>), JoinError> {
let address: ServerAddress = address.try_into().map_err(|_| JoinError::InvalidAddress)?;
let resolved_address = resolver::resolve_address(&address).await?;
@ -213,8 +194,8 @@ impl Client {
// The buffer has to be 1 to avoid a bug where if it lags events are
// received a bit later instead of the instant they were fired.
// That bug especially causes issues with the pathfinder.
let (tx, rx) = mpsc::channel(1);
tx.send(Event::Init).await.expect("Failed to send event");
let (tx, rx) = mpsc::unbounded_channel();
tx.send(Event::Init).unwrap();
// An event that causes the schedule to run. This is only used internally.
let (run_schedule_sender, run_schedule_receiver) = mpsc::unbounded_channel();
@ -228,7 +209,7 @@ impl Client {
let entity = entity_mut.id();
// we got the GameConnection, so the server is now connected :)
let client = Client::new(game_profile.clone(), None, entity, ecs_lock);
let client = Client::new(game_profile.clone(), None, entity, ecs_lock.clone());
let world = client.world();
@ -247,7 +228,7 @@ impl Client {
run_schedule_sender,
};
tokio::spawn(packet_receiver.read_task(read_conn));
tokio::spawn(packet_receiver.clone().read_task(read_conn));
ecs.entity_mut(entity)
.insert((local_player, packet_receiver));
@ -377,12 +358,19 @@ impl Client {
/// Write a packet directly to the server.
pub async fn write_packet(&self, packet: ServerboundGamePacket) -> Result<(), std::io::Error> {
self.local_player_mut().write_packet_async(packet).await
self.local_player_mut(&self.ecs.lock())
.write_packet_async(packet)
.await
}
/// Disconnect this client from the server, ending all tasks.
pub async fn disconnect(&self) -> Result<(), std::io::Error> {
if let Err(e) = self.local_player_mut().write_conn.shutdown().await {
if let Err(e) = self
.local_player_mut(&self.ecs.lock())
.write_conn
.shutdown()
.await
{
warn!(
"Error shutting down connection, but it might be fine: {}",
e
@ -395,11 +383,14 @@ impl Client {
Ok(())
}
pub fn local_player(&self) -> &LocalPlayer {
self.query::<&LocalPlayer>()
pub fn local_player(&self, ecs: &bevy_ecs::world::World) -> &LocalPlayer {
self.query::<&LocalPlayer>(ecs)
}
pub fn local_player_mut(&self) -> &mut LocalPlayer {
self.query::<&mut LocalPlayer>().into_inner()
pub fn local_player_mut(
&self,
ecs: &bevy_ecs::world::World,
) -> bevy_ecs::world::Mut<LocalPlayer> {
self.query::<&mut LocalPlayer>(ecs)
}
/// Get a reference to our (potentially shared) world.
@ -410,25 +401,18 @@ impl Client {
/// superset of the client's world.
pub fn world(&self) -> Arc<RwLock<World>> {
let world_name = self
.local_player()
.local_player(&self.ecs.lock())
.world_name
.as_ref()
.expect("World name must be known if we're doing Client::world");
let world_container = self.world_container.read();
world_container.get(&world_name).unwrap()
}
/// Query data of our player's entity.
pub fn query<'w, 's, Q: WorldQuery>(&'w self) -> <Q as WorldQuery>::Item<'_> {
let mut ecs = &mut self.ecs.lock();
QueryState::<Q>::new(ecs)
.get_mut(ecs, self.entity)
.expect("Player entity should always exist when being queried")
}
/// Returns whether we have a received the login packet yet.
pub fn logged_in(&self) -> bool {
// the login packet tells us the world name
self.local_player().world_name.is_some()
self.local_player(&self.ecs.lock()).world_name.is_some()
}
/// Tell the server we changed our game options (i.e. render distance, main
@ -451,11 +435,15 @@ impl Client {
client_information: ServerboundClientInformationPacket,
) -> Result<(), std::io::Error> {
{
self.local_player_mut().client_information = client_information;
self.local_player_mut(&self.ecs.lock()).client_information = client_information;
}
if self.logged_in() {
let client_information_packet = self.local_player().client_information.clone().get();
let client_information_packet = self
.local_player(&self.ecs.lock())
.client_information
.clone()
.get();
log::debug!(
"Sending client information (already logged in): {:?}",
client_information_packet
@ -465,6 +453,16 @@ impl Client {
Ok(())
}
/// Query data of our player's entity.
pub fn query<'w, Q: WorldQuery>(
&self,
ecs: &'w bevy_ecs::world::World,
) -> <Q as WorldQuery>::Item<'w> {
ecs.query::<Q>()
.get_mut(&mut ecs, self.entity)
.expect("Player entity should always exist when being queried")
}
}
/// Start the protocol and game tick loop.
@ -477,18 +475,20 @@ pub async fn start_ecs(
// you might be able to just drop the lock or put it in its own scope to fix
let mut app = App::new();
app.add_fixed_timestep(Duration::from_millis(50), "tick");
app.add_fixed_timestep_system("tick", 0, send_position);
// .add_system(LocalPlayer::update_in_loaded_chunk)
// .add_system(send_position)
// .add_system(LocalPlayer::ai_step);
app.add_system(packet_handling::handle_packets.label("handle_packets"));
// should happen last
app.add_system(packet_handling::clear_packets.after("handle_packets"));
app.add_fixed_timestep(Duration::from_millis(50), "tick")
.add_fixed_timestep_system_set(
"tick",
0,
SystemSet::new()
.with_system(send_position)
.with_system(update_in_loaded_chunk)
.with_system(send_position)
.with_system(LocalPlayer::ai_step)
.with_system(send_tick_event),
)
.add_system(packet_handling::handle_packets.label("handle_packets"))
// should happen last
.add_system(packet_handling::clear_packets.after("handle_packets"));
// all resources should have been added by now so we can take the ecs from the
// app

View file

@ -22,7 +22,7 @@ mod player;
mod plugins;
pub use account::Account;
pub use client::{ChatPacket, ClientInformation, Event, JoinError, LocalPlayer, PhysicsState};
pub use client::{ChatPacket, ClientInformation, Event, JoinError};
pub use movement::{SprintDirection, WalkDirection};
pub use player::PlayerInfo;
pub use plugins::{Plugin, PluginState, PluginStates, Plugins};

View file

@ -1,30 +1,19 @@
use std::{collections::HashMap, io, sync::Arc};
use azalea_auth::game_profile::GameProfile;
use azalea_core::{ChunkPos, ResourceLocation, Vec3};
use azalea_protocol::{
connect::{Connection, ReadConnection, WriteConnection},
packets::game::{
serverbound_keep_alive_packet::ServerboundKeepAlivePacket, ClientboundGamePacket,
ServerboundGamePacket,
},
};
use azalea_core::{ChunkPos, ResourceLocation};
use azalea_protocol::{connect::WriteConnection, packets::game::ServerboundGamePacket};
use azalea_world::{
entity::{self, metadata::PlayerMetadataBundle, Entity, EntityId},
EntityInfos, PartialWorld, World, WorldContainer,
entity::{self, Entity},
EntityInfos, PartialWorld, World,
};
use bevy_ecs::{
component::Component,
event::EventReader,
system::{Query, Res, ResMut},
};
use log::debug;
use bevy_ecs::{component::Component, system::Query};
use parking_lot::RwLock;
use thiserror::Error;
use tokio::sync::mpsc;
use uuid::Uuid;
use crate::{ChatPacket, ClientInformation, Event, PlayerInfo, WalkDirection};
use crate::{ClientInformation, Event, PlayerInfo, WalkDirection};
/// A player that you control that is currently in a Minecraft server.
#[derive(Component)]
@ -44,7 +33,7 @@ pub struct LocalPlayer {
pub world: Arc<RwLock<World>>,
pub world_name: Option<ResourceLocation>,
pub tx: mpsc::Sender<Event>,
pub tx: mpsc::UnboundedSender<Event>,
}
#[derive(Default)]
@ -78,7 +67,7 @@ impl LocalPlayer {
write_conn: WriteConnection<ServerboundGamePacket>,
world: Arc<RwLock<World>>,
entity_infos: &mut EntityInfos,
tx: mpsc::Sender<Event>,
tx: mpsc::UnboundedSender<Event>,
) -> Self {
let client_information = ClientInformation::default();
@ -117,40 +106,35 @@ impl LocalPlayer {
pub fn write_packet(&mut self, packet: ServerboundGamePacket) {
tokio::spawn(self.write_packet_async(packet));
}
}
/// Update the [`LocalPlayerInLoadedChunk`] component for all
/// [`LocalPlayer`]s.
fn update_in_loaded_chunk(
mut commands: bevy_ecs::system::Commands,
query: Query<(entity::EcsEntityId, &LocalPlayer, &entity::Position)>,
) {
for (ecs_entity_id, local_player, position) in &query {
let player_chunk_pos = ChunkPos::from(position);
let in_loaded_chunk = local_player
.world
.read()
.get_chunk(&player_chunk_pos)
.is_some();
if in_loaded_chunk {
commands
.entity(ecs_entity_id)
.insert(LocalPlayerInLoadedChunk);
} else {
commands
.entity(ecs_entity_id)
.remove::<LocalPlayerInLoadedChunk>();
}
}
pub fn send_tick_event(query: Query<&LocalPlayer>) {
for local_player in &query {
local_player.tx.send(Event::Tick).unwrap();
}
}
pub(crate) fn send_event(event: Event, tx: &mpsc::Sender<Event>) {
tokio::spawn(tx.send(event));
}
fn send_tick_event(query: Query<&LocalPlayer>) {
for local_player in &query {
let tx = local_player.tx.clone();
Self::send_event(Event::Tick, &tx);
/// Update the [`LocalPlayerInLoadedChunk`] component for all [`LocalPlayer`]s.
pub fn update_in_loaded_chunk(
mut commands: bevy_ecs::system::Commands,
query: Query<(Entity, &LocalPlayer, &entity::Position)>,
) {
for (ecs_entity_id, local_player, position) in &query {
let player_chunk_pos = ChunkPos::from(position);
let in_loaded_chunk = local_player
.world
.read()
.chunks
.get(&player_chunk_pos)
.is_some();
if in_loaded_chunk {
commands
.entity(ecs_entity_id)
.insert(LocalPlayerInLoadedChunk);
} else {
commands
.entity(ecs_entity_id)
.remove::<LocalPlayerInLoadedChunk>();
}
}
}

View file

@ -1,6 +1,5 @@
use crate::client::Client;
use crate::local_player::{LocalPlayer, LocalPlayerInLoadedChunk, PhysicsState};
use azalea_core::Vec3;
use azalea_protocol::packets::game::serverbound_player_command_packet::ServerboundPlayerCommandPacket;
use azalea_protocol::packets::game::{
serverbound_move_player_pos_packet::ServerboundMovePlayerPosPacket,
@ -39,13 +38,15 @@ impl Client {
/// If you're making a realistic client, calling this function every tick is
/// recommended.
pub fn set_jumping(&mut self, jumping: bool) {
let physics = self.query::<&mut entity::Physics>();
let ecs = self.ecs.lock();
let mut physics = self.query::<&mut entity::Physics>(&ecs).into_inner();
physics.jumping = jumping;
}
/// Returns whether the player will try to jump next tick.
pub fn jumping(&self) -> bool {
let physics = self.query::<&mut entity::Physics>();
let ecs = self.ecs.lock();
let physics = self.query::<&mut entity::Physics>(&ecs).into_inner();
physics.jumping
}
@ -54,7 +55,8 @@ impl Client {
/// f3 screen.
/// `y_rot` goes from -180 to 180, and `x_rot` goes from -90 to 90.
pub fn set_rotation(&mut self, y_rot: f32, x_rot: f32) {
let mut physics = self.query::<&mut entity::Physics>();
let ecs = self.ecs.lock();
let mut physics = self.query::<&mut entity::Physics>(&ecs);
entity::set_rotation(&mut physics, y_rot, x_rot);
}
@ -65,22 +67,19 @@ pub(crate) fn send_position(
(
Entity,
&MinecraftEntityId,
&LocalPlayer,
&mut LocalPlayer,
&entity::Position,
&entity::LastSentPosition,
&mut entity::LastSentPosition,
&mut entity::Physics,
&entity::metadata::Sprinting,
),
&LocalPlayerInLoadedChunk,
>,
) {
for (entity, id, local_player, position, last_sent_position, physics, sprinting) in &query {
local_player.send_sprinting_if_needed(
entity.into(),
id,
sprinting,
&mut local_player.physics_state,
);
for (entity, id, mut local_player, position, mut last_sent_position, mut physics, sprinting) in
query.iter_mut()
{
local_player.send_sprinting_if_needed(entity.into(), id, sprinting);
let packet = {
// TODO: the camera being able to be controlled by other entities isn't
@ -173,9 +172,8 @@ impl LocalPlayer {
entity: Entity,
id: &MinecraftEntityId,
sprinting: &entity::metadata::Sprinting,
physics_state: &mut PhysicsState,
) {
let was_sprinting = physics_state.was_sprinting;
let was_sprinting = self.physics_state.was_sprinting;
if **sprinting != was_sprinting {
let sprinting_action = if **sprinting {
azalea_protocol::packets::game::serverbound_player_command_packet::Action::StartSprinting
@ -190,14 +188,14 @@ impl LocalPlayer {
}
.get(),
);
physics_state.was_sprinting = **sprinting;
self.physics_state.was_sprinting = **sprinting;
}
}
/// Makes the bot do one physics tick. Note that this is already handled
/// automatically by the client.
pub fn ai_step(
query: Query<
mut query: Query<
(
Entity,
&mut LocalPlayer,
@ -216,7 +214,7 @@ impl LocalPlayer {
mut position,
mut sprinting,
mut attributes,
) in &mut query
) in query.iter_mut()
{
let physics_state = &mut local_player.physics_state;

View file

@ -598,7 +598,7 @@ pub fn handle_packet(entity: Entity, packet: &ClientboundGamePacket) {
/// A system that clears all packets in the clientbound packet events.
pub fn clear_packets(mut query: Query<&mut PacketReceiver>) {
for mut packets in query.iter_mut() {
for packets in query.iter_mut() {
packets.packets.lock().clear();
}
}
@ -606,7 +606,7 @@ pub fn clear_packets(mut query: Query<&mut PacketReceiver>) {
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>) {
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

View file

@ -1,7 +1,6 @@
use azalea_auth::game_profile::GameProfile;
use azalea_chat::FormattedText;
use azalea_core::GameType;
use azalea_world::PartialWorld;
use uuid::Uuid;
/// A player in the tab list.

View file

@ -1,4 +1,4 @@
use crate::{Event, LocalPlayer};
use crate::{client::Client, Event};
use async_trait::async_trait;
use nohash_hasher::NoHashHasher;
use std::{
@ -81,7 +81,7 @@ impl IntoIterator for PluginStates {
/// fields must be atomic. Unique `PluginState`s are built from [`Plugin`]s.
#[async_trait]
pub trait PluginState: Send + Sync + PluginStateClone + Any + 'static {
async fn handle(self: Box<Self>, event: Event, bot: LocalPlayer);
async fn handle(self: Box<Self>, event: Event, bot: Client);
}
/// Plugins can keep their own personal state, listen to [`Event`]s, and add