mirror of
https://github.com/mat-1/azalea.git
synced 2025-08-02 14:26:04 +00:00
merge main
This commit is contained in:
commit
7124e65857
26 changed files with 439 additions and 132 deletions
|
@ -3,19 +3,31 @@
|
|||
# Add the contents of this file to `config.toml` to enable "fast build" configuration. Please read the notes below.
|
||||
|
||||
# NOTE: For maximum performance, build using a nightly compiler
|
||||
# If you are using rust stable, remove the "-Zshare-generics=y" below.
|
||||
|
||||
[target.x86_64-unknown-linux-gnu]
|
||||
linker = "/usr/bin/clang"
|
||||
rustflags = ["-Clink-arg=-fuse-ld=lld"]
|
||||
linker = "clang"
|
||||
rustflags = ["-Clink-arg=-fuse-ld=lld", "-Zshare-generics=y"]
|
||||
|
||||
# NOTE: you must manually install https://github.com/michaeleisel/zld on mac. you can easily do this with the "brew" package manager:
|
||||
# `brew install michaeleisel/zld/zld`
|
||||
# NOTE: you must install [Mach-O LLD Port](https://lld.llvm.org/MachO/index.html) on mac. you can easily do this by installing llvm which includes lld with the "brew" package manager:
|
||||
# `brew install llvm`
|
||||
[target.x86_64-apple-darwin]
|
||||
rustflags = ["-C", "link-arg=-fuse-ld=/usr/local/bin/zld"]
|
||||
rustflags = [
|
||||
"-C",
|
||||
"link-arg=-fuse-ld=/usr/local/opt/llvm/bin/ld64.lld",
|
||||
"-Zshare-generics=y",
|
||||
]
|
||||
|
||||
[target.aarch64-apple-darwin]
|
||||
rustflags = [
|
||||
"-C",
|
||||
"link-arg=-fuse-ld=/opt/homebrew/opt/llvm/bin/ld64.lld",
|
||||
"-Zshare-generics=y",
|
||||
]
|
||||
|
||||
[target.x86_64-pc-windows-msvc]
|
||||
linker = "rust-lld.exe"
|
||||
rustflags = ["-Zshare-generics=y"]
|
||||
rustflags = ["-Zshare-generics=n"]
|
||||
|
||||
# Optional: Uncommenting the following improves compile times, but reduces the amount of debug info to 'line number tables only'
|
||||
# In most cases the gains are negligible, but if you are on macos and have slow compile times you should see significant gains.
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
//! Implementations of chat-related features.
|
||||
|
||||
use azalea_chat::FormattedText;
|
||||
use azalea_ecs::{
|
||||
app::{App, Plugin},
|
||||
entity::Entity,
|
||||
event::{EventReader, EventWriter},
|
||||
schedule::IntoSystemDescriptor,
|
||||
};
|
||||
use azalea_protocol::packets::game::{
|
||||
clientbound_player_chat_packet::ClientboundPlayerChatPacket,
|
||||
clientbound_system_chat_packet::ClientboundSystemChatPacket,
|
||||
|
@ -13,7 +19,7 @@ use std::{
|
|||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::client::Client;
|
||||
use crate::{client::Client, local_player::SendPacketEvent};
|
||||
|
||||
/// A chat packet, either a system message or a chat message.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
|
@ -107,42 +113,23 @@ impl Client {
|
|||
/// whether the message is a command and using the proper packet for you,
|
||||
/// so you should use that instead.
|
||||
pub fn send_chat_packet(&self, message: &str) {
|
||||
// TODO: chat signing
|
||||
// let signature = sign_message();
|
||||
let packet = ServerboundChatPacket {
|
||||
message: message.to_string(),
|
||||
timestamp: SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("Time shouldn't be before epoch")
|
||||
.as_millis()
|
||||
.try_into()
|
||||
.expect("Instant should fit into a u64"),
|
||||
salt: azalea_crypto::make_salt(),
|
||||
signature: None,
|
||||
last_seen_messages: LastSeenMessagesUpdate::default(),
|
||||
}
|
||||
.get();
|
||||
self.write_packet(packet);
|
||||
self.ecs.lock().send_event(SendChatKindEvent {
|
||||
entity: self.entity,
|
||||
content: message.to_string(),
|
||||
kind: ChatPacketKind::Message,
|
||||
});
|
||||
self.run_schedule_sender.send(()).unwrap();
|
||||
}
|
||||
|
||||
/// Send a command packet to the server. The `command` argument should not
|
||||
/// include the slash at the front.
|
||||
pub fn send_command_packet(&self, command: &str) {
|
||||
// TODO: chat signing
|
||||
let packet = ServerboundChatCommandPacket {
|
||||
command: command.to_string(),
|
||||
timestamp: SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("Time shouldn't be before epoch")
|
||||
.as_millis()
|
||||
.try_into()
|
||||
.expect("Instant should fit into a u64"),
|
||||
salt: azalea_crypto::make_salt(),
|
||||
argument_signatures: vec![],
|
||||
last_seen_messages: LastSeenMessagesUpdate::default(),
|
||||
}
|
||||
.get();
|
||||
self.write_packet(packet);
|
||||
self.ecs.lock().send_event(SendChatKindEvent {
|
||||
entity: self.entity,
|
||||
content: command.to_string(),
|
||||
kind: ChatPacketKind::Command,
|
||||
});
|
||||
self.run_schedule_sender.send(()).unwrap();
|
||||
}
|
||||
|
||||
/// Send a message in chat.
|
||||
|
@ -154,15 +141,132 @@ impl Client {
|
|||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn chat(&self, message: &str) {
|
||||
if let Some(command) = message.strip_prefix('/') {
|
||||
self.send_command_packet(command);
|
||||
pub fn chat(&self, content: &str) {
|
||||
self.ecs.lock().send_event(SendChatEvent {
|
||||
entity: self.entity,
|
||||
content: content.to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ChatPlugin;
|
||||
impl Plugin for ChatPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_event::<SendChatEvent>()
|
||||
.add_event::<SendChatKindEvent>()
|
||||
.add_event::<ChatReceivedEvent>()
|
||||
.add_system(
|
||||
handle_send_chat_event
|
||||
.label("handle_send_chat_event")
|
||||
.after("packet"),
|
||||
)
|
||||
.add_system(
|
||||
handle_send_chat_kind_event
|
||||
.label("handle_send_chat_kind_event")
|
||||
.after("handle_send_chat_event"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A client received a chat message packet.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ChatReceivedEvent {
|
||||
pub entity: Entity,
|
||||
pub packet: ChatPacket,
|
||||
}
|
||||
|
||||
/// Send a chat message (or command, if it starts with a slash) to the server.
|
||||
pub struct SendChatEvent {
|
||||
pub entity: Entity,
|
||||
pub content: String,
|
||||
}
|
||||
|
||||
fn handle_send_chat_event(
|
||||
mut events: EventReader<SendChatEvent>,
|
||||
mut send_chat_kind_events: EventWriter<SendChatKindEvent>,
|
||||
) {
|
||||
for event in events.iter() {
|
||||
if event.content.starts_with('/') {
|
||||
send_chat_kind_events.send(SendChatKindEvent {
|
||||
entity: event.entity,
|
||||
content: event.content[1..].to_string(),
|
||||
kind: ChatPacketKind::Command,
|
||||
});
|
||||
} else {
|
||||
self.send_chat_packet(message);
|
||||
send_chat_kind_events.send(SendChatKindEvent {
|
||||
entity: event.entity,
|
||||
content: event.content.clone(),
|
||||
kind: ChatPacketKind::Message,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Send a chat packet to the server of a specific kind (chat message or
|
||||
/// command). Usually you just want [`SendChatEvent`] instead.
|
||||
///
|
||||
/// Usually setting the kind to `Message` will make it send a chat message even
|
||||
/// if it starts with a slash, but some server implementations will always do a
|
||||
/// command if it starts with a slash.
|
||||
///
|
||||
/// If you're wondering why this isn't two separate events, it's so ordering is
|
||||
/// preserved if multiple chat messages and commands are sent at the same time.
|
||||
pub struct SendChatKindEvent {
|
||||
pub entity: Entity,
|
||||
pub content: String,
|
||||
pub kind: ChatPacketKind,
|
||||
}
|
||||
|
||||
/// A kind of chat packet, either a chat message or a command.
|
||||
pub enum ChatPacketKind {
|
||||
Message,
|
||||
Command,
|
||||
}
|
||||
|
||||
fn handle_send_chat_kind_event(
|
||||
mut events: EventReader<SendChatKindEvent>,
|
||||
mut send_packet_events: EventWriter<SendPacketEvent>,
|
||||
) {
|
||||
for event in events.iter() {
|
||||
let packet = match event.kind {
|
||||
ChatPacketKind::Message => ServerboundChatPacket {
|
||||
message: event.content.clone(),
|
||||
timestamp: SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("Time shouldn't be before epoch")
|
||||
.as_millis()
|
||||
.try_into()
|
||||
.expect("Instant should fit into a u64"),
|
||||
salt: azalea_crypto::make_salt(),
|
||||
signature: None,
|
||||
last_seen_messages: LastSeenMessagesUpdate::default(),
|
||||
}
|
||||
.get(),
|
||||
ChatPacketKind::Command => {
|
||||
// TODO: chat signing
|
||||
ServerboundChatCommandPacket {
|
||||
command: event.content.clone(),
|
||||
timestamp: SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("Time shouldn't be before epoch")
|
||||
.as_millis()
|
||||
.try_into()
|
||||
.expect("Instant should fit into a u64"),
|
||||
salt: azalea_crypto::make_salt(),
|
||||
argument_signatures: vec![],
|
||||
last_seen_messages: LastSeenMessagesUpdate::default(),
|
||||
}
|
||||
.get()
|
||||
}
|
||||
};
|
||||
|
||||
send_packet_events.send(SendPacketEvent {
|
||||
entity: event.entity,
|
||||
packet,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
// MessageSigner, ChatMessageContent, LastSeenMessages
|
||||
// fn sign_message() -> MessageSignature {
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
pub use crate::chat::ChatPacket;
|
||||
use crate::{
|
||||
chat::ChatPlugin,
|
||||
disconnect::{DisconnectEvent, DisconnectPlugin},
|
||||
events::{Event, EventPlugin, LocalPlayerEvents},
|
||||
inventory::{InventoryComponent, InventoryPlugin},
|
||||
local_player::{
|
||||
death_event, update_in_loaded_chunk, GameProfileComponent, LocalPlayer, PhysicsState,
|
||||
death_event, handle_send_packet_event, update_in_loaded_chunk, GameProfileComponent,
|
||||
LocalPlayer, PhysicsState, SendPacketEvent,
|
||||
},
|
||||
movement::{local_player_ai_step, send_position, sprint_listener, walk_listener},
|
||||
packet_handling::{self, PacketHandlerPlugin},
|
||||
packet_handling::{self, PacketHandlerPlugin, PacketReceiver},
|
||||
player::retroactively_add_game_profile_component,
|
||||
task_pool::TaskPoolPlugin,
|
||||
Account, PlayerInfo, StartSprintEvent, StartWalkEvent,
|
||||
|
@ -16,6 +18,7 @@ use azalea_auth::{game_profile::GameProfile, sessionserver::ClientSessionServerE
|
|||
use azalea_chat::FormattedText;
|
||||
use azalea_ecs::{
|
||||
app::{App, Plugin, PluginGroup, PluginGroupBuilder},
|
||||
bundle::Bundle,
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
schedule::{IntoSystemDescriptor, Schedule, Stage, SystemSet},
|
||||
|
@ -81,6 +84,9 @@ pub struct Client {
|
|||
/// directly. Note that if you're using a shared world (i.e. a swarm), this
|
||||
/// will contain all entities in all worlds.
|
||||
pub ecs: Arc<Mutex<Ecs>>,
|
||||
|
||||
/// Use this to force the client to run the schedule outside of a tick.
|
||||
pub run_schedule_sender: mpsc::UnboundedSender<()>,
|
||||
}
|
||||
|
||||
/// An error that happened while joining the server.
|
||||
|
@ -108,7 +114,12 @@ impl Client {
|
|||
/// Create a new client from the given GameProfile, Connection, and World.
|
||||
/// You should only use this if you want to change these fields from the
|
||||
/// defaults, otherwise use [`Client::join`].
|
||||
pub fn new(profile: GameProfile, entity: Entity, ecs: Arc<Mutex<Ecs>>) -> Self {
|
||||
pub fn new(
|
||||
profile: GameProfile,
|
||||
entity: Entity,
|
||||
ecs: Arc<Mutex<Ecs>>,
|
||||
run_schedule_sender: mpsc::UnboundedSender<()>,
|
||||
) -> Self {
|
||||
Self {
|
||||
profile,
|
||||
// default our id to 0, it'll be set later
|
||||
|
@ -116,6 +127,8 @@ impl Client {
|
|||
world: Arc::new(RwLock::new(PartialWorld::default())),
|
||||
|
||||
ecs,
|
||||
|
||||
run_schedule_sender,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -147,7 +160,7 @@ impl Client {
|
|||
let resolved_address = resolver::resolve_address(&address).await?;
|
||||
|
||||
// An event that causes the schedule to run. This is only used internally.
|
||||
let (run_schedule_sender, run_schedule_receiver) = mpsc::channel(1);
|
||||
let (run_schedule_sender, run_schedule_receiver) = mpsc::unbounded_channel();
|
||||
let app = init_ecs_app();
|
||||
let ecs_lock = start_ecs(app, run_schedule_receiver, run_schedule_sender.clone());
|
||||
|
||||
|
@ -168,7 +181,7 @@ impl Client {
|
|||
account: &Account,
|
||||
address: &ServerAddress,
|
||||
resolved_address: &SocketAddr,
|
||||
run_schedule_sender: mpsc::Sender<()>,
|
||||
run_schedule_sender: mpsc::UnboundedSender<()>,
|
||||
) -> Result<(Self, mpsc::UnboundedReceiver<Event>), JoinError> {
|
||||
let conn = Connection::new(resolved_address).await?;
|
||||
let (conn, game_profile) = Self::handshake(conn, account, address).await?;
|
||||
|
@ -183,7 +196,12 @@ impl Client {
|
|||
let entity = entity_mut.id();
|
||||
|
||||
// we got the GameConnection, so the server is now connected :)
|
||||
let client = Client::new(game_profile.clone(), entity, ecs_lock.clone());
|
||||
let client = Client::new(
|
||||
game_profile.clone(),
|
||||
entity,
|
||||
ecs_lock.clone(),
|
||||
run_schedule_sender.clone(),
|
||||
);
|
||||
|
||||
let (packet_writer_sender, packet_writer_receiver) = mpsc::unbounded_channel();
|
||||
|
||||
|
@ -210,15 +228,15 @@ impl Client {
|
|||
local_player.tasks.push(read_packets_task);
|
||||
local_player.tasks.push(write_packets_task);
|
||||
|
||||
ecs.entity_mut(entity).insert((
|
||||
ecs.entity_mut(entity).insert(JoinedClientBundle {
|
||||
local_player,
|
||||
packet_receiver,
|
||||
GameProfileComponent(game_profile),
|
||||
PhysicsState::default(),
|
||||
Local,
|
||||
LocalPlayerEvents(tx),
|
||||
InventoryComponent::default(),
|
||||
));
|
||||
game_profile: GameProfileComponent(game_profile),
|
||||
physics_state: PhysicsState::default(),
|
||||
local_player_events: LocalPlayerEvents(tx),
|
||||
inventory: InventoryComponent::default(),
|
||||
_local: Local,
|
||||
});
|
||||
|
||||
Ok((client, rx))
|
||||
}
|
||||
|
@ -357,7 +375,9 @@ impl Client {
|
|||
/// The OwnedReadHalf for the TCP connection is in one of the tasks, so it
|
||||
/// automatically closes the connection when that's dropped.
|
||||
pub fn disconnect(&self) {
|
||||
self.local_player_mut(&mut self.ecs.lock()).disconnect();
|
||||
self.ecs.lock().send_event(DisconnectEvent {
|
||||
entity: self.entity,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn local_player<'a>(&'a self, ecs: &'a mut Ecs) -> &'a LocalPlayer {
|
||||
|
@ -454,14 +474,25 @@ impl Client {
|
|||
}
|
||||
}
|
||||
|
||||
/// A bundle for the components that are present on a local player that received
|
||||
/// a login packet. If you want to filter for this, just use [`Local`].
|
||||
#[derive(Bundle)]
|
||||
pub struct JoinedClientBundle {
|
||||
pub local_player: LocalPlayer,
|
||||
pub packet_receiver: PacketReceiver,
|
||||
pub game_profile: GameProfileComponent,
|
||||
pub physics_state: PhysicsState,
|
||||
pub local_player_events: LocalPlayerEvents,
|
||||
pub inventory: InventoryComponent,
|
||||
pub _local: Local,
|
||||
}
|
||||
|
||||
pub struct AzaleaPlugin;
|
||||
impl Plugin for AzaleaPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_event::<StartWalkEvent>()
|
||||
.add_event::<StartSprintEvent>();
|
||||
|
||||
app.add_plugins(DefaultPlugins);
|
||||
|
||||
app.add_tick_system_set(
|
||||
SystemSet::new()
|
||||
.with_system(send_position)
|
||||
|
@ -492,6 +523,9 @@ impl Plugin for AzaleaPlugin {
|
|||
.after("packet"),
|
||||
);
|
||||
|
||||
app.add_event::<SendPacketEvent>()
|
||||
.add_system(handle_send_packet_event.after("tick").after("packet"));
|
||||
|
||||
app.init_resource::<WorldContainer>();
|
||||
}
|
||||
}
|
||||
|
@ -511,7 +545,7 @@ pub fn init_ecs_app() -> App {
|
|||
// 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_plugin(AzaleaPlugin);
|
||||
app.add_plugins(DefaultPlugins);
|
||||
app
|
||||
}
|
||||
|
||||
|
@ -520,8 +554,8 @@ pub fn init_ecs_app() -> App {
|
|||
#[doc(hidden)]
|
||||
pub fn start_ecs(
|
||||
app: App,
|
||||
run_schedule_receiver: mpsc::Receiver<()>,
|
||||
run_schedule_sender: mpsc::Sender<()>,
|
||||
run_schedule_receiver: mpsc::UnboundedReceiver<()>,
|
||||
run_schedule_sender: mpsc::UnboundedSender<()>,
|
||||
) -> Arc<Mutex<Ecs>> {
|
||||
// all resources should have been added by now so we can take the ecs from the
|
||||
// app
|
||||
|
@ -540,7 +574,7 @@ pub fn start_ecs(
|
|||
async fn run_schedule_loop(
|
||||
ecs: Arc<Mutex<Ecs>>,
|
||||
mut schedule: Schedule,
|
||||
mut run_schedule_receiver: mpsc::Receiver<()>,
|
||||
mut run_schedule_receiver: mpsc::UnboundedReceiver<()>,
|
||||
) {
|
||||
loop {
|
||||
// whenever we get an event from run_schedule_receiver, run the schedule
|
||||
|
@ -551,14 +585,14 @@ async fn run_schedule_loop(
|
|||
|
||||
/// Send an event to run the schedule every 50 milliseconds. It will stop when
|
||||
/// the receiver is dropped.
|
||||
pub async fn tick_run_schedule_loop(run_schedule_sender: mpsc::Sender<()>) {
|
||||
pub async fn tick_run_schedule_loop(run_schedule_sender: mpsc::UnboundedSender<()>) {
|
||||
let mut game_tick_interval = time::interval(time::Duration::from_millis(50));
|
||||
// TODO: Minecraft bursts up to 10 ticks and then skips, we should too
|
||||
game_tick_interval.set_missed_tick_behavior(time::MissedTickBehavior::Burst);
|
||||
|
||||
loop {
|
||||
game_tick_interval.tick().await;
|
||||
if let Err(e) = run_schedule_sender.send(()).await {
|
||||
if let Err(e) = run_schedule_sender.send(()) {
|
||||
println!("tick_run_schedule_loop error: {e}");
|
||||
// the sender is closed so end the task
|
||||
return;
|
||||
|
@ -574,11 +608,14 @@ impl PluginGroup for DefaultPlugins {
|
|||
fn build(self) -> PluginGroupBuilder {
|
||||
PluginGroupBuilder::start::<Self>()
|
||||
.add(TickPlugin::default())
|
||||
.add(AzaleaPlugin)
|
||||
.add(PacketHandlerPlugin)
|
||||
.add(EntityPlugin)
|
||||
.add(PhysicsPlugin)
|
||||
.add(EventPlugin)
|
||||
.add(TaskPoolPlugin::default())
|
||||
.add(InventoryPlugin)
|
||||
.add(ChatPlugin)
|
||||
.add(DisconnectPlugin)
|
||||
}
|
||||
}
|
||||
|
|
31
azalea-client/src/disconnect.rs
Normal file
31
azalea-client/src/disconnect.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
//! Disconnect a client from the server.
|
||||
|
||||
use azalea_ecs::{
|
||||
app::{App, CoreStage, Plugin},
|
||||
entity::Entity,
|
||||
event::EventReader,
|
||||
system::Commands,
|
||||
};
|
||||
|
||||
use crate::client::JoinedClientBundle;
|
||||
|
||||
pub struct DisconnectPlugin;
|
||||
impl Plugin for DisconnectPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_event::<DisconnectEvent>()
|
||||
.add_system_to_stage(CoreStage::PostUpdate, handle_disconnect);
|
||||
}
|
||||
}
|
||||
|
||||
/// An event sent when a client is getting disconnected.
|
||||
pub struct DisconnectEvent {
|
||||
pub entity: Entity,
|
||||
}
|
||||
|
||||
/// System that removes the [`JoinedClientBundle`] from the entity when it
|
||||
/// receives a [`DisconnectEvent`].
|
||||
pub fn handle_disconnect(mut commands: Commands, mut events: EventReader<DisconnectEvent>) {
|
||||
for DisconnectEvent { entity } in events.iter() {
|
||||
commands.entity(*entity).remove::<JoinedClientBundle>();
|
||||
}
|
||||
}
|
|
@ -19,11 +19,12 @@ use derive_more::{Deref, DerefMut};
|
|||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::{
|
||||
chat::{ChatPacket, ChatReceivedEvent},
|
||||
packet_handling::{
|
||||
AddPlayerEvent, ChatReceivedEvent, DeathEvent, KeepAliveEvent, PacketReceiver,
|
||||
RemovePlayerEvent, UpdatePlayerEvent,
|
||||
AddPlayerEvent, DeathEvent, KeepAliveEvent, PacketReceiver, RemovePlayerEvent,
|
||||
UpdatePlayerEvent,
|
||||
},
|
||||
ChatPacket, PlayerInfo,
|
||||
PlayerInfo,
|
||||
};
|
||||
|
||||
// (for contributors):
|
||||
|
|
|
@ -12,8 +12,9 @@
|
|||
#![feature(type_alias_impl_trait)]
|
||||
|
||||
mod account;
|
||||
mod chat;
|
||||
pub mod chat;
|
||||
mod client;
|
||||
pub mod disconnect;
|
||||
mod entity_query;
|
||||
mod events;
|
||||
mod get_mc_dir;
|
||||
|
@ -27,7 +28,7 @@ pub mod task_pool;
|
|||
|
||||
pub use account::Account;
|
||||
pub use azalea_ecs as ecs;
|
||||
pub use client::{init_ecs_app, start_ecs, ChatPacket, Client, ClientInformation, JoinError};
|
||||
pub use client::{init_ecs_app, start_ecs, Client, ClientInformation, JoinError};
|
||||
pub use events::Event;
|
||||
pub use local_player::{GameProfileComponent, LocalPlayer};
|
||||
pub use movement::{SprintDirection, StartSprintEvent, StartWalkEvent, WalkDirection};
|
||||
|
|
|
@ -4,6 +4,7 @@ use azalea_auth::game_profile::GameProfile;
|
|||
use azalea_core::ChunkPos;
|
||||
use azalea_ecs::component::Component;
|
||||
use azalea_ecs::entity::Entity;
|
||||
use azalea_ecs::event::EventReader;
|
||||
use azalea_ecs::{query::Added, system::Query};
|
||||
use azalea_protocol::packets::game::ServerboundGamePacket;
|
||||
use azalea_world::{
|
||||
|
@ -111,12 +112,11 @@ impl LocalPlayer {
|
|||
.send(packet)
|
||||
.expect("write_packet shouldn't be able to be called if the connection is closed");
|
||||
}
|
||||
}
|
||||
|
||||
/// Disconnect this client from the server by ending all tasks.
|
||||
///
|
||||
/// The OwnedReadHalf for the TCP connection is in one of the tasks, so it
|
||||
/// automatically closes the connection when that's dropped.
|
||||
pub fn disconnect(&self) {
|
||||
impl Drop for LocalPlayer {
|
||||
/// Stop every active task when the `LocalPlayer` is dropped.
|
||||
fn drop(&mut self) {
|
||||
for task in &self.tasks {
|
||||
task.abort();
|
||||
}
|
||||
|
@ -168,3 +168,20 @@ impl<T> From<std::sync::PoisonError<T>> for HandlePacketError {
|
|||
HandlePacketError::Poison(e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Event for sending a packet to the server.
|
||||
pub struct SendPacketEvent {
|
||||
pub entity: Entity,
|
||||
pub packet: ServerboundGamePacket,
|
||||
}
|
||||
|
||||
pub fn handle_send_packet_event(
|
||||
mut send_packet_events: EventReader<SendPacketEvent>,
|
||||
mut query: Query<&mut LocalPlayer>,
|
||||
) {
|
||||
for event in send_packet_events.iter() {
|
||||
if let Ok(mut local_player) = query.get_mut(event.entity) {
|
||||
local_player.write_packet(event.packet.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,9 +37,11 @@ use parking_lot::Mutex;
|
|||
use tokio::sync::mpsc;
|
||||
|
||||
use crate::{
|
||||
chat::{ChatPacket, ChatReceivedEvent},
|
||||
disconnect::DisconnectEvent,
|
||||
inventory::{ClientSideCloseContainerEvent, InventoryComponent},
|
||||
local_player::{GameProfileComponent, LocalPlayer},
|
||||
ChatPacket, ClientInformation, PlayerInfo,
|
||||
ClientInformation, PlayerInfo,
|
||||
};
|
||||
|
||||
pub struct PacketHandlerPlugin;
|
||||
|
@ -83,13 +85,6 @@ pub struct UpdatePlayerEvent {
|
|||
pub info: PlayerInfo,
|
||||
}
|
||||
|
||||
/// A client received a chat message packet.
|
||||
#[derive(Debug, Clone)]
|
||||
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.
|
||||
|
@ -113,7 +108,7 @@ pub struct KeepAliveEvent {
|
|||
#[derive(Component, Clone)]
|
||||
pub struct PacketReceiver {
|
||||
pub packets: Arc<Mutex<Vec<ClientboundGamePacket>>>,
|
||||
pub run_schedule_sender: mpsc::Sender<()>,
|
||||
pub run_schedule_sender: mpsc::UnboundedSender<()>,
|
||||
}
|
||||
|
||||
fn handle_packets(ecs: &mut Ecs) {
|
||||
|
@ -293,10 +288,14 @@ fn handle_packets(ecs: &mut Ecs) {
|
|||
}
|
||||
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();
|
||||
let mut system_state: SystemState<EventWriter<DisconnectEvent>> =
|
||||
SystemState::new(ecs);
|
||||
let mut disconnect_events = system_state.get_mut(ecs);
|
||||
disconnect_events.send(DisconnectEvent {
|
||||
entity: player_entity,
|
||||
});
|
||||
// bye
|
||||
return;
|
||||
}
|
||||
ClientboundGamePacket::UpdateRecipes(_p) => {
|
||||
debug!("Got update recipes packet");
|
||||
|
@ -938,7 +937,9 @@ fn handle_packets(ecs: &mut Ecs) {
|
|||
ClientboundGamePacket::BlockChangedAck(_) => {}
|
||||
ClientboundGamePacket::BlockDestruction(_) => {}
|
||||
ClientboundGamePacket::BlockEntityData(_) => {}
|
||||
ClientboundGamePacket::BlockEvent(_) => {}
|
||||
ClientboundGamePacket::BlockEvent(p) => {
|
||||
debug!("Got block event packet {:?}", p);
|
||||
}
|
||||
ClientboundGamePacket::BossEvent(_) => {}
|
||||
ClientboundGamePacket::CommandSuggestions(_) => {}
|
||||
ClientboundGamePacket::Cooldown(_) => {}
|
||||
|
@ -1031,16 +1032,20 @@ impl PacketReceiver {
|
|||
Ok(packet) => {
|
||||
self.packets.lock().push(packet);
|
||||
// tell the client to run all the systems
|
||||
self.run_schedule_sender.send(()).await.unwrap();
|
||||
self.run_schedule_sender.send(()).unwrap();
|
||||
}
|
||||
Err(error) => {
|
||||
if !matches!(*error, ReadPacketError::ConnectionClosed) {
|
||||
error!("Error reading packet from Client: {error:?}");
|
||||
}
|
||||
return;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: it should send a DisconnectEvent here somehow
|
||||
// maybe use a tokio::sync::oneshot that tells it to close and have the
|
||||
// receiver in localplayer and have a system that watches that or
|
||||
// something?
|
||||
}
|
||||
|
||||
/// Consume the [`ServerboundGamePacket`] queue and actually write the
|
||||
|
@ -1057,6 +1062,7 @@ impl PacketReceiver {
|
|||
break;
|
||||
};
|
||||
}
|
||||
println!("Write task finished");
|
||||
// receiver is automatically closed when it's dropped
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use crate::{BlockPos, Direction, Vec3};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BlockHitResult {
|
||||
pub location: Vec3,
|
||||
pub direction: Direction,
|
||||
|
|
|
@ -5,7 +5,7 @@ use std::{
|
|||
|
||||
use azalea_buf::{BufReadError, McBufReadable, McBufWritable};
|
||||
|
||||
#[derive(Hash, Clone, Debug, PartialEq, Eq)]
|
||||
#[derive(Hash, Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Difficulty {
|
||||
PEACEFUL = 0,
|
||||
EASY = 1,
|
||||
|
|
|
@ -97,7 +97,7 @@ impl McBufWritable for GameType {
|
|||
/// Rust doesn't let us `impl McBufReadable for Option<GameType>` so we have to
|
||||
/// make a new type :(
|
||||
#[derive(Hash, Copy, Clone, Debug)]
|
||||
pub struct OptionalGameType(Option<GameType>);
|
||||
pub struct OptionalGameType(pub Option<GameType>);
|
||||
|
||||
impl From<Option<GameType>> for OptionalGameType {
|
||||
fn from(game_type: Option<GameType>) -> Self {
|
||||
|
|
|
@ -20,13 +20,13 @@ pub struct BlockCollisions<'a> {
|
|||
|
||||
impl<'a> BlockCollisions<'a> {
|
||||
pub fn new(world: &'a World, aabb: AABB) -> Self {
|
||||
let origin_x = (aabb.min_x - EPSILON) as i32 - 1;
|
||||
let origin_y = (aabb.min_y - EPSILON) as i32 - 1;
|
||||
let origin_z = (aabb.min_z - EPSILON) as i32 - 1;
|
||||
let origin_x = (aabb.min_x - EPSILON).floor() as i32 - 1;
|
||||
let origin_y = (aabb.min_y - EPSILON).floor() as i32 - 1;
|
||||
let origin_z = (aabb.min_z - EPSILON).floor() as i32 - 1;
|
||||
|
||||
let end_x = (aabb.max_x + EPSILON) as i32 + 1;
|
||||
let end_y = (aabb.max_y + EPSILON) as i32 + 1;
|
||||
let end_z = (aabb.max_z + EPSILON) as i32 + 1;
|
||||
let end_x = (aabb.max_x + EPSILON).floor() as i32 + 1;
|
||||
let end_y = (aabb.max_y + EPSILON).floor() as i32 + 1;
|
||||
let end_z = (aabb.max_z + EPSILON).floor() as i32 + 1;
|
||||
|
||||
let cursor = Cursor3d::new(origin_x, origin_y, origin_z, end_x, end_y, end_z);
|
||||
|
||||
|
|
|
@ -588,7 +588,68 @@ mod tests {
|
|||
block_state.is_some(),
|
||||
"Block state should exist, if this fails that means the chunk wasn't loaded and the block didn't get placed"
|
||||
);
|
||||
// do a few steps so we fall on the slab
|
||||
// do a few steps so we fall on the wall
|
||||
for _ in 0..20 {
|
||||
app.update();
|
||||
}
|
||||
|
||||
let entity_pos = app.world.get::<Position>(entity).unwrap();
|
||||
assert_eq!(entity_pos.y, 70.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_negative_coordinates_weird_wall_collision() {
|
||||
let mut app = make_test_app();
|
||||
let world_lock = app.world.resource_mut::<WorldContainer>().insert(
|
||||
ResourceLocation::new("minecraft:overworld").unwrap(),
|
||||
384,
|
||||
-64,
|
||||
);
|
||||
let mut partial_world = PartialWorld::default();
|
||||
|
||||
partial_world.chunks.set(
|
||||
&ChunkPos { x: -1, z: -1 },
|
||||
Some(Chunk::default()),
|
||||
&mut world_lock.write().chunks,
|
||||
);
|
||||
let entity = app
|
||||
.world
|
||||
.spawn((
|
||||
EntityBundle::new(
|
||||
Uuid::nil(),
|
||||
Vec3 {
|
||||
x: -7.5,
|
||||
y: 73.,
|
||||
z: -7.5,
|
||||
},
|
||||
azalea_registry::EntityKind::Player,
|
||||
ResourceLocation::new("minecraft:overworld").unwrap(),
|
||||
),
|
||||
MinecraftEntityId(0),
|
||||
Local,
|
||||
))
|
||||
.id();
|
||||
let block_state = world_lock.write().chunks.set_block_state(
|
||||
&BlockPos {
|
||||
x: -8,
|
||||
y: 69,
|
||||
z: -8,
|
||||
},
|
||||
azalea_block::CobblestoneWallBlock {
|
||||
east: azalea_block::EastWall::Low,
|
||||
north: azalea_block::NorthWall::Low,
|
||||
south: azalea_block::SouthWall::Low,
|
||||
west: azalea_block::WestWall::Low,
|
||||
up: false,
|
||||
waterlogged: false,
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
assert!(
|
||||
block_state.is_some(),
|
||||
"Block state should exist, if this fails that means the chunk wasn't loaded and the block didn't get placed"
|
||||
);
|
||||
// do a few steps so we fall on the wall
|
||||
for _ in 0..20 {
|
||||
app.update();
|
||||
}
|
||||
|
|
|
@ -79,14 +79,18 @@ impl Display for ServerAddress {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::io::Cursor;
|
||||
use std::{
|
||||
io::Cursor,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
packets::login::{
|
||||
serverbound_hello_packet::ServerboundHelloPacket, ServerboundLoginPacket,
|
||||
packets::{
|
||||
game::serverbound_chat_packet::{LastSeenMessagesUpdate, ServerboundChatPacket},
|
||||
login::{serverbound_hello_packet::ServerboundHelloPacket, ServerboundLoginPacket},
|
||||
},
|
||||
read::read_packet,
|
||||
write::write_packet,
|
||||
read::{compression_decoder, read_packet},
|
||||
write::{compression_encoder, packet_encoder, write_packet},
|
||||
};
|
||||
use bytes::BytesMut;
|
||||
use uuid::Uuid;
|
||||
|
@ -140,4 +144,29 @@ mod tests {
|
|||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_read_long_compressed_chat() {
|
||||
let compression_threshold = 256;
|
||||
|
||||
let buf = packet_encoder(
|
||||
&ServerboundChatPacket {
|
||||
message: "a".repeat(256),
|
||||
timestamp: 0,
|
||||
salt: 0,
|
||||
signature: None,
|
||||
last_seen_messages: LastSeenMessagesUpdate::default(),
|
||||
}
|
||||
.get(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let buf = compression_encoder(&buf, compression_threshold)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
println!("{:?}", buf);
|
||||
|
||||
compression_decoder(&mut Cursor::new(&buf), compression_threshold).unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use azalea_block::BlockState;
|
||||
use azalea_buf::McBuf;
|
||||
use azalea_core::BlockPos;
|
||||
use azalea_protocol_macros::ClientboundGamePacket;
|
||||
use azalea_registry::Block;
|
||||
|
||||
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
|
||||
pub struct ClientboundBlockEventPacket {
|
||||
pub pos: BlockPos,
|
||||
pub b0: u8,
|
||||
pub b1: u8,
|
||||
pub block: BlockState,
|
||||
pub action_id: u8,
|
||||
pub action_parameter: u8,
|
||||
pub block: Block,
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ pub struct Tags {
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TagMap(HashMap<ResourceLocation, Vec<Tags>>);
|
||||
pub struct TagMap(pub HashMap<ResourceLocation, Vec<Tags>>);
|
||||
|
||||
impl McBufReadable for TagMap {
|
||||
fn read_from(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
|
||||
|
|
|
@ -4,5 +4,5 @@ use azalea_protocol_macros::ServerboundGamePacket;
|
|||
#[derive(Clone, Debug, McBuf, ServerboundGamePacket)]
|
||||
pub struct ServerboundChatAckPacket {
|
||||
#[var]
|
||||
pub offset: u32,
|
||||
pub messages: u32,
|
||||
}
|
||||
|
|
|
@ -15,6 +15,6 @@ pub struct ServerboundChatPacket {
|
|||
#[derive(Clone, Debug, McBuf, Default)]
|
||||
pub struct LastSeenMessagesUpdate {
|
||||
#[var]
|
||||
pub offset: u32,
|
||||
pub messages: u32,
|
||||
pub acknowledged: FixedBitSet<20>,
|
||||
}
|
||||
|
|
|
@ -163,7 +163,7 @@ pub enum DecompressionError {
|
|||
|
||||
/// Get the decompressed bytes from a packet. It must have been decrypted
|
||||
/// first.
|
||||
fn compression_decoder(
|
||||
pub fn compression_decoder(
|
||||
stream: &mut Cursor<&[u8]>,
|
||||
compression_threshold: u32,
|
||||
) -> Result<Vec<u8>, DecompressionError> {
|
||||
|
@ -264,8 +264,7 @@ mod tests {
|
|||
use crate::packets::game::ClientboundGamePacket;
|
||||
use std::io::Cursor;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_read_packet() {
|
||||
fn test_read_packet() {
|
||||
let mut buf: Cursor<&[u8]> = Cursor::new(&[
|
||||
56, 64, 85, 58, 141, 138, 71, 146, 193, 64, 88, 0, 0, 0, 0, 0, 0, 64, 60, 224, 105, 34,
|
||||
119, 8, 228, 67, 50, 51, 68, 194, 177, 230, 101, 0, 17, 0,
|
||||
|
|
|
@ -29,7 +29,7 @@ pub enum PacketEncodeError {
|
|||
},
|
||||
}
|
||||
|
||||
fn packet_encoder<P: ProtocolPacket + std::fmt::Debug>(
|
||||
pub fn packet_encoder<P: ProtocolPacket + std::fmt::Debug>(
|
||||
packet: &P,
|
||||
) -> Result<Vec<u8>, PacketEncodeError> {
|
||||
let mut buf = Vec::new();
|
||||
|
@ -51,7 +51,7 @@ pub enum PacketCompressError {
|
|||
Io(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
async fn compression_encoder(
|
||||
pub async fn compression_encoder(
|
||||
data: &[u8],
|
||||
compression_threshold: u32,
|
||||
) -> Result<Vec<u8>, PacketCompressError> {
|
||||
|
@ -60,15 +60,21 @@ async fn compression_encoder(
|
|||
if n < compression_threshold as usize {
|
||||
let mut buf = Vec::new();
|
||||
0.var_write_into(&mut buf)?;
|
||||
buf.write_all(data).await?;
|
||||
std::io::Write::write_all(&mut buf, data)?;
|
||||
Ok(buf)
|
||||
} else {
|
||||
// otherwise, compress
|
||||
let mut deflater = ZlibEncoder::new(data);
|
||||
// write deflated data to buf
|
||||
let mut buf = Vec::new();
|
||||
deflater.read_to_end(&mut buf).await?;
|
||||
Ok(buf)
|
||||
let mut compressed_data = Vec::new();
|
||||
deflater.read_to_end(&mut compressed_data).await?;
|
||||
|
||||
// prepend the length
|
||||
let mut len_prepended_compressed_data = Vec::new();
|
||||
(data.len() as u32).var_write_into(&mut len_prepended_compressed_data)?;
|
||||
len_prepended_compressed_data.append(&mut compressed_data);
|
||||
|
||||
Ok(len_prepended_compressed_data)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,7 +88,7 @@ where
|
|||
P: ProtocolPacket + Debug,
|
||||
W: AsyncWrite + Unpin + Send,
|
||||
{
|
||||
trace!("Sending packet: {:?}", packet);
|
||||
trace!("Sending packet: {:?}", packet,);
|
||||
let mut buf = packet_encoder(packet).unwrap();
|
||||
if let Some(threshold) = compression_threshold {
|
||||
buf = compression_encoder(&buf, threshold).await.unwrap();
|
||||
|
|
|
@ -7,13 +7,13 @@ mod bit_storage;
|
|||
mod chunk_storage;
|
||||
mod container;
|
||||
pub mod entity;
|
||||
mod palette;
|
||||
pub mod palette;
|
||||
mod world;
|
||||
|
||||
use std::backtrace::Backtrace;
|
||||
|
||||
pub use bit_storage::BitStorage;
|
||||
pub use chunk_storage::{Chunk, ChunkStorage, PartialChunkStorage};
|
||||
pub use chunk_storage::{Chunk, ChunkStorage, PartialChunkStorage, Section};
|
||||
pub use container::*;
|
||||
use thiserror::Error;
|
||||
pub use world::*;
|
||||
|
|
|
@ -77,7 +77,7 @@ Azalea lets you create "swarms", which are a group of bots in the same world tha
|
|||
|
||||
# Plugins
|
||||
|
||||
Azalea uses [Bevy ECS](https://docs.rs/bevy_ecs) internally to store information about the world and clients. Bevy plugins are more powerful than async handler functions, but more difficult to use. See [pathfinder](azalea/src/pathfinder/mod.rs) as an example of how to make a plugin. You can then enable a plugin by adding `.add_plugin(ExamplePlugin)` in your client/swarm builder.
|
||||
Azalea uses [Bevy ECS](https://docs.rs/bevy_ecs) internally to store information about the world and clients. Bevy plugins are more powerful than async handler functions, but more difficult to use. See [pathfinder](https://github.com/mat-1/azalea/blob/main/azalea/src/pathfinder/mod.rs) as an example of how to make a plugin. You can then enable a plugin by adding `.add_plugin(ExamplePlugin)` in your client/swarm builder.
|
||||
|
||||
Also note that just because something is an entity in the ECS doesn't mean that it's a Minecraft entity. You can filter for that by having `With<MinecraftEntityId>` as a filter.
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@ use azalea::prelude::*;
|
|||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
env_logger::init();
|
||||
|
||||
let account = Account::offline("bot");
|
||||
// or let account = Account::microsoft("email").await;
|
||||
|
||||
|
|
|
@ -135,7 +135,7 @@ where
|
|||
let resolved_address = resolver::resolve_address(&address).await?;
|
||||
|
||||
// An event that causes the schedule to run. This is only used internally.
|
||||
let (run_schedule_sender, run_schedule_receiver) = mpsc::channel(1);
|
||||
let (run_schedule_sender, run_schedule_receiver) = mpsc::unbounded_channel();
|
||||
let ecs_lock = start_ecs(self.app, run_schedule_receiver, run_schedule_sender.clone());
|
||||
|
||||
let (bot, mut rx) = Client::start_client(
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
// in Swarm that's set to the smallest index of all the bots, and we remove all
|
||||
// messages from the queue that are before that index.
|
||||
|
||||
use azalea_client::{packet_handling::ChatReceivedEvent, ChatPacket};
|
||||
use azalea_client::chat::{ChatPacket, ChatReceivedEvent};
|
||||
use azalea_ecs::{
|
||||
app::{App, Plugin},
|
||||
component::Component,
|
||||
|
|
|
@ -5,7 +5,7 @@ mod events;
|
|||
pub mod prelude;
|
||||
|
||||
use crate::{bot::DefaultBotPlugins, HandleFn};
|
||||
use azalea_client::{init_ecs_app, start_ecs, Account, ChatPacket, Client, Event, JoinError};
|
||||
use azalea_client::{chat::ChatPacket, init_ecs_app, start_ecs, Account, Client, Event, JoinError};
|
||||
use azalea_ecs::{
|
||||
app::{App, Plugin, PluginGroup, PluginGroupBuilder},
|
||||
component::Component,
|
||||
|
@ -47,7 +47,7 @@ pub struct Swarm {
|
|||
bots_tx: mpsc::UnboundedSender<(Option<Event>, Client)>,
|
||||
swarm_tx: mpsc::UnboundedSender<SwarmEvent>,
|
||||
|
||||
run_schedule_sender: mpsc::Sender<()>,
|
||||
run_schedule_sender: mpsc::UnboundedSender<()>,
|
||||
}
|
||||
|
||||
/// Create a new [`Swarm`].
|
||||
|
@ -253,7 +253,7 @@ where
|
|||
let (bots_tx, mut bots_rx) = mpsc::unbounded_channel();
|
||||
let (swarm_tx, mut swarm_rx) = mpsc::unbounded_channel();
|
||||
|
||||
let (run_schedule_sender, run_schedule_receiver) = mpsc::channel(1);
|
||||
let (run_schedule_sender, run_schedule_receiver) = mpsc::unbounded_channel();
|
||||
let ecs_lock = start_ecs(self.app, run_schedule_receiver, run_schedule_sender.clone());
|
||||
|
||||
let swarm = Swarm {
|
||||
|
@ -509,8 +509,8 @@ impl Swarm {
|
|||
Ok(bot) => return bot,
|
||||
Err(e) => {
|
||||
disconnects += 1;
|
||||
let delay = (Duration::from_secs(5) * 2u32.pow(disconnects))
|
||||
.min(Duration::from_secs(120));
|
||||
let delay = (Duration::from_secs(5) * 2u32.pow(disconnects.min(16)))
|
||||
.min(Duration::from_secs(15));
|
||||
let username = account.username.clone();
|
||||
error!("Error joining as {username}: {e}. Waiting {delay:?} and trying again.");
|
||||
tokio::time::sleep(delay).await;
|
||||
|
|
Loading…
Add table
Reference in a new issue