().await?;
@@ -303,17 +319,29 @@ impl SharedPlayer {
Err(why)?;
}
Err(why) => {
- warn!("Spawning player {} failed: {why}", self.0.id);
+ warn!("Spawning player {} timed out: {why}", self.0.id);
Err(why)?;
}
}
+ let player_add = PlayerInfoUpdateC {
+ players: &[PlayerStatus::for_player(self.uuid().await).add_player("AFK", &[])],
+ };
+ io.tx(&player_add).await?;
+
// FIXME: GROSS LOL?????? this should(?) change ownership of the player to the server
// thread but realistically who knows burhhhh
state.player_send.send(self.clone()).await?;
loop {
- self.handle_packets().await?;
+ tokio::select! {
+ _ = self.handle_packets() => {
+ time::sleep(Duration::from_millis(50)).await;
+ }
+ _ = state.shutdown_token.cancelled() => {
+ return Ok(());
+ }
+ }
}
}
@@ -331,6 +359,11 @@ impl SharedPlayer {
},
}
}
+
+ async fn uuid(&self) -> Uuid {
+ let uuid = self.0.uuid.read().await;
+ uuid.expect("uuid() called on uninitialized player - only call this after login!")
+ }
}
#[derive(Debug, Error)]
diff --git a/src/protocol/mod.rs b/src/protocol/mod.rs
index ff03a79..1e52158 100644
--- a/src/protocol/mod.rs
+++ b/src/protocol/mod.rs
@@ -47,9 +47,11 @@ pub mod packets {
pub mod play {
mod login;
+ mod status;
mod teleport;
pub use login::*;
+ pub use status::*;
pub use teleport::*;
}
}
@@ -60,7 +62,7 @@ mod encoder;
use std::{fmt::Debug, io::Write};
use color_eyre::eyre::{Context, Result};
-use datatypes::VarInt;
+use datatypes::{Bounded, VarInt};
pub use decoder::*;
pub use encoder::*;
use thiserror::Error;
@@ -127,3 +129,23 @@ pub trait ClientboundPacket: Packet + Encode + Debug {
}
}
impl ClientboundPacket for P where P: Packet + Encode + Debug {}
+
+#[derive(Debug)]
+pub struct Property<'a> {
+ name: Bounded<&'a str, 32767>,
+ value: Bounded<&'a str, 32767>,
+ signature: Option>,
+}
+
+impl Encode for Property<'_> {
+ fn encode(&self, mut w: impl std::io::Write) -> Result<()> {
+ let signed = self.signature.is_some();
+
+ self.name.encode(&mut w)?;
+ self.value.encode(&mut w)?;
+ signed.encode(&mut w)?;
+ self.signature.encode(&mut w)?;
+
+ Ok(())
+ }
+}
diff --git a/src/protocol/packets/login/login.rs b/src/protocol/packets/login/login.rs
index 12ae32e..02ef277 100644
--- a/src/protocol/packets/login/login.rs
+++ b/src/protocol/packets/login/login.rs
@@ -17,12 +17,12 @@
* .
*/
-
use color_eyre::eyre::Result;
use uuid::Uuid;
use crate::protocol::{
- datatypes::{Bounded, Bytes, VarInt}, Decode, Encode, Packet,
+ datatypes::{Bounded, Bytes, VarInt},
+ Decode, Encode, Packet, Property,
};
#[derive(Debug)]
@@ -52,26 +52,6 @@ pub struct LoginSuccessC<'a> {
pub strict_error_handling: bool,
}
-#[derive(Debug)]
-pub struct Property<'a> {
- name: Bounded<&'a str, 32767>,
- value: Bounded<&'a str, 32767>,
- signature: Option>,
-}
-
-impl Encode for Property<'_> {
- fn encode(&self, mut w: impl std::io::Write) -> Result<()> {
- let signed = self.signature.is_some();
-
- self.name.encode(&mut w)?;
- self.value.encode(&mut w)?;
- signed.encode(&mut w)?;
- self.signature.encode(&mut w)?;
-
- Ok(())
- }
-}
-
impl Packet for LoginSuccessC<'_> {
const ID: i32 = 0x02;
}
diff --git a/src/protocol/packets/play/status.rs b/src/protocol/packets/play/status.rs
new file mode 100644
index 0000000..4d404c1
--- /dev/null
+++ b/src/protocol/packets/play/status.rs
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2024 Andrew Brower.
+ * This file is part of Crawlspace.
+ *
+ * Crawlspace is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public
+ * License as published by the Free Software Foundation, either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Crawlspace is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with Crawlspace. If not, see
+ * .
+ */
+
+use uuid::Uuid;
+
+use crate::protocol::{
+ datatypes::{Bounded, VarInt},
+ Encode, Packet, Property,
+};
+
+use super::Gamemode;
+
+#[derive(Debug)]
+pub struct PlayerInfoUpdateC<'a> {
+ pub players: &'a [PlayerStatus<'a>],
+}
+
+#[derive(Debug)]
+pub struct PlayerStatus<'a> {
+ uuid: Uuid,
+ actions: Vec>,
+}
+
+#[derive(Debug)]
+enum PlayerAction<'a> {
+ AddPlayer {
+ name: Bounded<&'a str, 16>,
+ properties: &'a [Property<'a>],
+ },
+ UpdateGamemode {
+ game_mode: VarInt,
+ },
+ UpdateListed {
+ listed: bool,
+ },
+ UpdateLatency {
+ latency: VarInt,
+ },
+}
+
+impl Packet for PlayerInfoUpdateC<'_> {
+ const ID: i32 = 0x3E;
+}
+
+// "I'm a Never-Nester"
+// The unwavering Minecraft Protocol:
+impl Encode for PlayerInfoUpdateC<'_> {
+ fn encode(&self, mut w: impl std::io::Write) -> color_eyre::eyre::Result<()> {
+ let actions = self.players.iter().fold(0, |acc, p| {
+ acc | p.actions.iter().fold(0, |acc2, a| acc2 | a.mask())
+ });
+
+ actions.encode(&mut w)?;
+ VarInt(self.players.len() as i32).encode(&mut w)?;
+
+ for PlayerStatus { uuid, actions } in self.players {
+ uuid.encode(&mut w)?;
+
+ for action in actions {
+ match action {
+ PlayerAction::AddPlayer { name, properties } => {
+ name.encode(&mut w)?;
+ VarInt(properties.len() as i32).encode(&mut w)?;
+ for p in *properties {
+ p.encode(&mut w)?;
+ }
+ }
+ PlayerAction::UpdateGamemode { game_mode } => {
+ game_mode.encode(&mut w)?;
+ }
+ PlayerAction::UpdateListed { listed } => {
+ listed.encode(&mut w)?;
+ }
+ PlayerAction::UpdateLatency { latency } => {
+ latency.encode(&mut w)?;
+ }
+ }
+ }
+ }
+
+ Ok(())
+ }
+}
+
+impl PlayerAction<'_> {
+ const fn mask(&self) -> i8 {
+ match self {
+ PlayerAction::AddPlayer { .. } => 0x01,
+ PlayerAction::UpdateGamemode { .. } => 0x04,
+ PlayerAction::UpdateListed { .. } => 0x08,
+ PlayerAction::UpdateLatency { .. } => 0x10,
+ }
+ }
+}
+
+impl<'a> PlayerStatus<'a> {
+ pub fn for_player(player: Uuid) -> Self {
+ Self {
+ uuid: player,
+ actions: Vec::new(),
+ }
+ }
+
+ pub fn add_player(mut self, name: &'a str, props: &'a [Property]) -> Self {
+ self.actions.push(PlayerAction::AddPlayer {
+ name: Bounded::<&'a str, 16>(name),
+ properties: props,
+ });
+ self
+ }
+
+ pub fn update_gamemode(mut self, gamemode: Gamemode) -> Self {
+ self.actions.push(PlayerAction::UpdateGamemode {
+ game_mode: VarInt(u8::from(gamemode) as i32),
+ });
+ self
+ }
+
+ pub fn update_listed(mut self, listed: bool) -> Self {
+ self.actions.push(PlayerAction::UpdateListed { listed });
+ self
+ }
+
+ pub fn update_latency(mut self, latency: i32) -> Self {
+ self.actions.push(PlayerAction::UpdateLatency {
+ latency: VarInt(latency),
+ });
+ self
+ }
+}
diff --git a/src/protocol/packets/play/teleport.rs b/src/protocol/packets/play/teleport.rs
index 5193c8d..34e61cf 100644
--- a/src/protocol/packets/play/teleport.rs
+++ b/src/protocol/packets/play/teleport.rs
@@ -19,12 +19,7 @@
use std::sync::atomic::{AtomicI32, Ordering};
-use rand::Rng;
-
-use crate::protocol::{
- datatypes::{Bounded, Position, VarInt},
- Decode, Encode, Packet,
-};
+use crate::protocol::{datatypes::VarInt, Decode, Encode, Packet};
static TP_ID: AtomicI32 = AtomicI32::new(0);
diff --git a/src/state.rs b/src/state.rs
index 0ffdd03..02788a6 100644
--- a/src/state.rs
+++ b/src/state.rs
@@ -20,6 +20,7 @@
use std::sync::{atomic::AtomicUsize, Arc};
use tokio::sync::{mpsc, Mutex, Semaphore};
+use tokio_util::sync::CancellationToken;
use crate::net::player::SharedPlayer;
@@ -35,8 +36,7 @@ pub struct State {
pub player_send: mpsc::Sender,
pub player_recv: Mutex>,
- pub shutdown_send: mpsc::UnboundedSender<()>,
- pub shutdown_recv: Mutex>,
+ pub shutdown_token: CancellationToken,
pub net_sema: Arc,
}
@@ -57,7 +57,7 @@ impl State {
}
let (player_send, player_recv) = mpsc::channel(16);
- let (shutdown_send, shutdown_recv) = mpsc::unbounded_channel();
+ let shutdown_token = CancellationToken::new();
Self {
max_players: max,
@@ -70,8 +70,7 @@ impl State {
player_send,
player_recv: Mutex::new(player_recv),
- shutdown_send,
- shutdown_recv: Mutex::new(shutdown_recv),
+ shutdown_token,
net_sema: Arc::new(Semaphore::new(max)),
}