tokio: Support RCON

This commit is contained in:
Snowiiii 2024-10-19 18:12:06 +02:00
parent 2287564973
commit 75e7b0a8e8
15 changed files with 166 additions and 152 deletions

View File

@ -40,6 +40,13 @@ impl<'a> TextComponent<'a> {
}
}
pub fn text_string(text: String) -> Self {
Self {
content: TextContent::Text { text: text.into() },
style: Style::default(),
}
}
pub fn to_pretty_console(self) -> String {
let style = self.style;
let color = style.color;

View File

@ -6,7 +6,9 @@ use rayon::prelude::*;
use tokio::sync::mpsc;
use crate::{
chunk::{anvil::AnvilChunkReader, ChunkData, ChunkParsingError, ChunkReader, ChunkReadingError},
chunk::{
anvil::AnvilChunkReader, ChunkData, ChunkParsingError, ChunkReader, ChunkReadingError,
},
world_gen::{get_world_gen, Seed, WorldGenerator},
};
@ -96,7 +98,10 @@ impl Level {
let data = match &self.save_file {
Some(save_file) => {
match self.chunk_reader.read_chunk(save_file, at) {
Err(ChunkReadingError::ParsingError(ChunkParsingError::ChunkNotGenerated) | ChunkReadingError::ChunkNotExist) => {
Err(
ChunkReadingError::ParsingError(ChunkParsingError::ChunkNotGenerated)
| ChunkReadingError::ChunkNotExist,
) => {
// This chunk was not generated yet.
Ok(self.world_gen.generate_chunk(at))
}

View File

@ -1,6 +1,7 @@
use crate::entity::player::Player;
use crate::server::Server;
use itertools::Itertools;
use pumpkin_core::text::TextComponent;
use pumpkin_core::GameMode;
use pumpkin_inventory::container_click::{
Click, ClickType, KeyClick, MouseClick, MouseDragState, MouseDragType,
@ -10,7 +11,7 @@ use pumpkin_inventory::window_property::{WindowProperty, WindowPropertyTrait};
use pumpkin_inventory::Container;
use pumpkin_inventory::{container_click, InventoryError, OptionallyCombinedContainer};
use pumpkin_protocol::client::play::{
CCloseContainer, CSetContainerContent, CSetContainerProperty, CSetContainerSlot,
CCloseContainer, COpenScreen, CSetContainerContent, CSetContainerProperty, CSetContainerSlot,
};
use pumpkin_protocol::server::play::SClickContainer;
use pumpkin_protocol::slot::Slot;
@ -26,26 +27,28 @@ impl Player {
let total_opened_containers = inventory.total_opened_containers;
let container = self.get_open_container(server);
let container = container.as_ref().map(|container| container.lock());
// let menu_protocol_id = (*pumpkin_world::global_registry::REGISTRY
// .get("minecraft:menu")
// .unwrap()
// .entries
// .get(minecraft_menu_id)
// .expect("Should be a valid menu id")
// .get("protocol_id")
// .unwrap())
// .into();
// let window_title = container
// .as_ref()
// .map(|container| container.window_name())
// .unwrap_or_else(|| inventory.window_name());
// let title = TextComponent::text(window_title);
let menu_protocol_id = (*pumpkin_world::global_registry::REGISTRY
.get("minecraft:menu")
.unwrap()
.entries
.get(minecraft_menu_id)
.expect("Should be a valid menu id")
.get("protocol_id")
.unwrap())
.into();
let window_title = match container {
Some(container) => container.await.window_name(),
None => inventory.window_name(),
};
let title = TextComponent::text(window_title);
// self.client.send_packet(&COpenScreen::new(
// total_opened_containers.into(),
// menu_protocol_id,
// title,
// ));
self.client
.send_packet(&COpenScreen::new(
total_opened_containers.into(),
menu_protocol_id,
title,
))
.await;
drop(inventory);
// self.set_container_content(container.as_deref_mut());
}

View File

@ -98,17 +98,17 @@ pub fn init_command_tree<'a>() -> CommandTree<'a> {
let target = parse_arg_player(sender, server, ARG_TARGET, args)?;
if target.gamemode.load() == gamemode {
sender.send_message(TextComponent::text(&format!(
"{} is already in {:?} gamemode",
target.gameprofile.name, gamemode
)));
// sender.send_message(TextComponent::text(&format!(
// "{} is already in {:?} gamemode",
// target.gameprofile.name, gamemode
// )));
} else {
// TODO
target.set_gamemode(gamemode);
sender.send_message(TextComponent::text(&format!(
"{}'s Game mode was set to {:?}",
target.gameprofile.name, gamemode
)));
// sender.send_message(TextComponent::text(&format!(
// "{}'s Game mode was set to {:?}",
// target.gameprofile.name, gamemode
// )));
}
Ok(())

View File

@ -43,12 +43,12 @@ pub fn init_command_tree<'a>() -> CommandTree<'a> {
argument(ARG_COMMAND, consume_arg_command).execute(&|sender, server, args| {
let tree = parse_arg_command(args, &server.command_dispatcher)?;
sender.send_message(TextComponent::text(&format!(
"{} - {} Usage: {}",
tree.names.join("/"),
tree.description,
tree
)));
// sender.send_message(TextComponent::text(&format!(
// "{} - {} Usage: {}",
// tree.names.join("/"),
// tree.description,
// tree
// )));
Ok(())
}),
@ -62,12 +62,12 @@ pub fn init_command_tree<'a>() -> CommandTree<'a> {
continue;
};
sender.send_message(TextComponent::text(&format!(
"{} - {} Usage: {}",
tree.names.join("/"),
tree.description,
tree
)));
// sender.send_message(TextComponent::text(&format!(
// "{} - {} Usage: {}",
// tree.names.join("/"),
// tree.description,
// tree
// )));
}
Ok(())

View File

@ -1,4 +1,7 @@
use crate::commands::tree::CommandTree;
use pumpkin_core::text::{color::NamedColor, TextComponent};
use pumpkin_protocol::CURRENT_MC_PROTOCOL;
use crate::{commands::tree::CommandTree, server::CURRENT_MC_VERSION};
const NAMES: [&str; 1] = ["pumpkin"];
@ -11,7 +14,7 @@ pub fn init_command_tree<'a>() -> CommandTree<'a> {
// sender.send_message(TextComponent::text(
// &format!("Pumpkin {version}, {description} (Minecraft {CURRENT_MC_VERSION}, Protocol {CURRENT_MC_PROTOCOL})")
// ).color_named(NamedColor::Green));
// ).color_named(NamedColor::Green)).await;
Ok(())
})

View File

@ -1,3 +1,5 @@
use pumpkin_core::text::TextComponent;
use crate::commands::dispatcher::InvalidTreeError::{
InvalidConsumptionError, InvalidRequirementError,
};
@ -24,11 +26,14 @@ pub struct CommandDispatcher<'a> {
/// Stores registered [CommandTree]s and dispatches commands to them.
impl<'a> CommandDispatcher<'a> {
pub fn handle_command(&self, sender: &mut CommandSender, server: &Server, cmd: &str) {
pub async fn handle_command(&self, sender: &mut CommandSender<'a>, server: &Server, cmd: &str) {
if let Err(err) = self.dispatch(sender, server, cmd) {
// sender.send_message(
// TextComponent::text(&err).color_named(pumpkin_core::text::color::NamedColor::Red),
// )
sender
.send_message(
TextComponent::text_string(err)
.color_named(pumpkin_core::text::color::NamedColor::Red),
)
.await;
}
}

View File

@ -27,11 +27,10 @@ pub enum CommandSender<'a> {
}
impl<'a> CommandSender<'a> {
pub fn send_message(&mut self, text: TextComponent) {
pub async fn send_message(&mut self, text: TextComponent<'a>) {
match self {
// TODO: add color and stuff to console
CommandSender::Console => log::info!("{}", text.to_pretty_console()),
CommandSender::Player(c) => c.send_system_message(&text),
CommandSender::Player(c) => c.send_system_message(&text).await,
CommandSender::Rcon(s) => s.push(text.to_pretty_console()),
}
}

View File

@ -276,7 +276,7 @@ impl Player {
.await;
}
pub async fn send_system_message<'a>(&self, text: TextComponent<'a>) {
pub async fn send_system_message<'a>(&self, text: &TextComponent<'a>) {
self.client
.send_packet(&CSystemChatMessage::new(text, false))
.await;
@ -284,7 +284,7 @@ impl Player {
}
impl Player {
pub async fn process_packets(&self, server: &Arc<Server>) {
pub async fn process_packets(self: &Arc<Self>, server: &Arc<Server>) {
let mut packets = self.client.client_packets_queue.lock().await;
while let Some(mut packet) = packets.pop_back() {
match self.handle_play_packet(server, &mut packet).await {
@ -308,7 +308,7 @@ impl Player {
}
pub async fn handle_play_packet(
&self,
self: &Arc<Self>,
server: &Arc<Server>,
packet: &mut RawPacket,
) -> Result<(), Box<dyn PumpkinError>> {

View File

@ -11,7 +11,6 @@
#![expect(clippy::significant_drop_tightening)]
#![expect(clippy::future_not_send)]
#![expect(clippy::single_call_fn)]
#![expect(clippy::await_holding_lock)]
#[cfg(target_os = "wasi")]
compile_error!("Compiling for WASI targets is not supported!");
@ -150,7 +149,7 @@ async fn main() -> io::Result<()> {
if rcon.enabled {
let server = server.clone();
tokio::spawn(async move {
RCONServer::new(&rcon, &server).await.unwrap();
RCONServer::new(&rcon, server).await.unwrap();
});
}
let mut unique_id = 0;

View File

@ -1,11 +1,11 @@
use std::{
collections::HashMap,
io::{self},
net::SocketAddr,
};
use packet::{ClientboundPacket, Packet, PacketError, ServerboundPacket};
use pumpkin_config::{RCONConfig, ADVANCED_CONFIG};
use std::sync::Arc;
use thiserror::Error;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
@ -26,40 +26,29 @@ pub enum RCONError {
pub struct RCONServer;
impl RCONServer {
pub async fn new(config: &RCONConfig, server: &Server) -> Result<Self, io::Error> {
pub async fn new(config: &RCONConfig, server: Arc<Server>) -> Result<Self, io::Error> {
assert!(config.enabled, "RCON is not enabled");
let listener = tokio::net::TcpListener::bind(config.address).await.unwrap();
let mut connections: HashMap<usize, RCONClient> = HashMap::new();
let password = Arc::new(config.password.clone());
let password = config.password.clone();
let mut unique_id = 0;
let mut connections = 0;
loop {
// Asynchronously wait for an inbound socket.
let (connection, address) = listener.accept().await?;
if config.max_connections != 0 && connections.len() >= config.max_connections as usize {
if config.max_connections != 0 && connections >= config.max_connections {
continue;
}
unique_id += 1;
let token = unique_id;
connections.insert(token, RCONClient::new(connection, address));
connections += 1;
let mut client = RCONClient::new(connection, address);
let done = if let Some(client) = connections.get_mut(&token) {
client.handle(server, &password).await
} else {
false
};
if done {
if let Some(client) = connections.remove(&token) {
let config = &ADVANCED_CONFIG.rcon;
if config.logging.log_quit {
log::info!("RCON ({}): Client closed connection", client.address);
}
}
}
let password = password.clone();
let server = server.clone();
tokio::spawn(async move { while !client.handle(&server, &password).await {} });
dbg!("closed");
connections -= 1;
}
}
}
@ -83,20 +72,19 @@ impl RCONClient {
}
}
/// Returns if client is closed or not
pub async fn handle(&mut self, server: &Server, password: &str) -> bool {
if !self.closed {
loop {
match self.read_bytes().await {
// Stream closed, so we can't reply, so we just close everything.
Ok(true) => return true,
Ok(false) => {}
Err(e) if e.kind() == io::ErrorKind::WouldBlock => break,
Err(e) => {
log::error!("could not read packet: {e}");
return true;
}
}
}
dbg!("a");
// If we get a close here, we might have a reply, which we still want to write.
let _ = self.poll(server, password).await.map_err(|e| {
log::error!("RCON error: {e}");
@ -107,17 +95,14 @@ impl RCONClient {
}
async fn poll(&mut self, server: &Server, password: &str) -> Result<(), PacketError> {
loop {
let packet = match self.receive_packet().await? {
Some(p) => p,
None => return Ok(()),
};
let config = &ADVANCED_CONFIG.rcon;
match packet.get_type() {
ServerboundPacket::Auth => {
let body = packet.get_body();
if !body.is_empty() && packet.get_body() == password {
if packet.get_body() == password {
self.send(ClientboundPacket::AuthResponse, packet.get_id(), "".into())
.await?;
if config.logging.log_logged_successfully {
@ -137,11 +122,13 @@ impl RCONClient {
if self.logged_in {
let mut output = Vec::new();
let dispatcher = server.command_dispatcher.clone();
dispatcher.handle_command(
dispatcher
.handle_command(
&mut crate::commands::CommandSender::Rcon(&mut output),
server,
packet.get_body(),
);
)
.await;
for line in output {
if config.logging.log_commands {
log::info!("RCON ({}): {}", self.address, line);
@ -152,7 +139,7 @@ impl RCONClient {
}
}
}
}
Ok(())
}
async fn read_bytes(&mut self) -> io::Result<bool> {

View File

@ -131,7 +131,7 @@ impl Server {
/// Searches every world for a player by name
pub async fn get_player_by_name(&self, name: &str) -> Option<Arc<Player>> {
for world in self.worlds.iter() {
if let Some(player) = world.get_player_by_name(name).await {
if let Some(player) = world.get_player_by_name(name) {
return Some(player);
}
}

View File

@ -298,8 +298,9 @@ impl World {
}
/// Gets a Player by name
pub async fn get_player_by_name(&self, name: &str) -> Option<Arc<Player>> {
for player in self.current_players.lock().await.values() {
pub fn get_player_by_name(&self, name: &str) -> Option<Arc<Player>> {
// not sure of blocking lock
for player in self.current_players.blocking_lock().values() {
if player.gameprofile.name == name {
return Some(player.clone());
}

View File

@ -78,8 +78,7 @@ pub async fn update_position(entity: &Entity, player: &Player) {
Vector2::new(current_watched.x, current_watched.z),
view_distance,
);
let new_cylindrical =
Cylindrical::new(chunk_pos, view_distance);
let new_cylindrical = Cylindrical::new(chunk_pos, view_distance);
player.watched_section.store(new_watched);

View File

@ -26,7 +26,7 @@ impl Scoreboard {
}
}
pub fn add_objective(&mut self, world: &World, objective: ScoreboardObjective) {
pub async fn add_objective<'a>(&mut self, world: &World, objective: ScoreboardObjective<'a>) {
if self.objectives.contains_key(objective.name) {
// Maybe make this an error ?
log::warn!(
@ -35,20 +35,24 @@ impl Scoreboard {
);
return;
}
world.broadcast_packet_all(&CUpdateObjectives::new(
world
.broadcast_packet_all(&CUpdateObjectives::new(
objective.name,
pumpkin_protocol::client::play::Mode::Add,
objective.display_name,
objective.render_type,
objective.number_format,
));
world.broadcast_packet_all(&CDisplayObjective::new(
))
.await;
world
.broadcast_packet_all(&CDisplayObjective::new(
pumpkin_protocol::client::play::DisplaySlot::Sidebar,
"test",
));
))
.await;
}
pub fn update_score(&self, world: &World, score: ScoreboardScore) {
pub async fn update_score<'a>(&self, world: &World, score: ScoreboardScore<'a>) {
if self.objectives.contains_key(score.objective_name) {
log::warn!(
"Tried to place a score into a Objective which does not exist, {}",
@ -56,13 +60,15 @@ impl Scoreboard {
);
return;
}
world.broadcast_packet_all(&CUpdateScore::new(
world
.broadcast_packet_all(&CUpdateScore::new(
score.entity_name,
score.objective_name,
score.value,
score.display_name,
score.number_format,
));
))
.await;
}
// pub fn add_team(&mut self, name: String) {