Extractor: Parse and use screens.json

This commit is contained in:
Snowiiii 2024-11-03 11:25:15 +01:00
parent 9e7adec16b
commit 8f90b29cfe
16 changed files with 288 additions and 153 deletions

102
assets/screens.json Normal file
View File

@ -0,0 +1,102 @@
[
{
"id": 0,
"name": "minecraft:generic_9x1"
},
{
"id": 1,
"name": "minecraft:generic_9x2"
},
{
"id": 2,
"name": "minecraft:generic_9x3"
},
{
"id": 3,
"name": "minecraft:generic_9x4"
},
{
"id": 4,
"name": "minecraft:generic_9x5"
},
{
"id": 5,
"name": "minecraft:generic_9x6"
},
{
"id": 6,
"name": "minecraft:generic_3x3"
},
{
"id": 7,
"name": "minecraft:crafter_3x3"
},
{
"id": 8,
"name": "minecraft:anvil"
},
{
"id": 9,
"name": "minecraft:beacon"
},
{
"id": 10,
"name": "minecraft:blast_furnace"
},
{
"id": 11,
"name": "minecraft:brewing_stand"
},
{
"id": 12,
"name": "minecraft:crafting"
},
{
"id": 13,
"name": "minecraft:enchantment"
},
{
"id": 14,
"name": "minecraft:furnace"
},
{
"id": 15,
"name": "minecraft:grindstone"
},
{
"id": 16,
"name": "minecraft:hopper"
},
{
"id": 17,
"name": "minecraft:lectern"
},
{
"id": 18,
"name": "minecraft:loom"
},
{
"id": 19,
"name": "minecraft:merchant"
},
{
"id": 20,
"name": "minecraft:shulker_box"
},
{
"id": 21,
"name": "minecraft:smithing"
},
{
"id": 22,
"name": "minecraft:smoker"
},
{
"id": 23,
"name": "minecraft:cartography_table"
},
{
"id": 24,
"name": "minecraft:stonecutter"
}
]

View File

@ -28,6 +28,7 @@ class Extractor : ModInitializer {
Particles(), Particles(),
SyncedRegistries(), SyncedRegistries(),
Packet(), Packet(),
Screen(),
Items(), Items(),
Blocks(), Blocks(),
) )

View File

@ -4,7 +4,6 @@ import com.google.gson.JsonArray
import com.google.gson.JsonElement import com.google.gson.JsonElement
import com.google.gson.JsonObject import com.google.gson.JsonObject
import de.snowii.extractor.Extractor import de.snowii.extractor.Extractor
import net.minecraft.network.packet.s2c.play.InventoryS2CPacket
import net.minecraft.registry.Registries import net.minecraft.registry.Registries
import net.minecraft.server.MinecraftServer import net.minecraft.server.MinecraftServer

View File

@ -0,0 +1,26 @@
package de.snowii.extractor.extractors
import com.google.gson.JsonArray
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import de.snowii.extractor.Extractor
import net.minecraft.registry.Registries
import net.minecraft.server.MinecraftServer
class Screen : Extractor.Extractor {
override fun fileName(): String {
return "screens.json"
}
override fun extract(server: MinecraftServer): JsonElement {
val screensJson = JsonArray()
for (screen in Registries.SCREEN_HANDLER) {
val screenJson = JsonObject()
screenJson.addProperty("id", Registries.SCREEN_HANDLER.getRawId(screen))
screenJson.addProperty("name", Registries.SCREEN_HANDLER.getId(screen)!!.toString())
screensJson.add(screenJson)
}
return screensJson
}
}

View File

@ -6,6 +6,7 @@ edition.workspace = true
[dependencies] [dependencies]
# For items # For items
pumpkin-world = { path = "../pumpkin-world" } pumpkin-world = { path = "../pumpkin-world" }
pumpkin-macros = { path = "../pumpkin-macros" }
log.workspace = true log.workspace = true
itertools.workspace = true itertools.workspace = true

View File

@ -1,6 +1,7 @@
use crate::container_click::MouseClick; use crate::container_click::MouseClick;
use crate::player::PlayerInventory; use crate::player::PlayerInventory;
use num_derive::{FromPrimitive, ToPrimitive}; use num_derive::{FromPrimitive, ToPrimitive};
use pumpkin_macros::screen;
use pumpkin_world::item::ItemStack; use pumpkin_world::item::ItemStack;
pub mod container_click; pub mod container_click;
@ -15,42 +16,43 @@ pub use open_container::OpenContainer;
/// https://wiki.vg/Inventory /// https://wiki.vg/Inventory
#[derive(Debug, ToPrimitive, FromPrimitive, Clone, Copy, Eq, PartialEq)] #[derive(Debug, ToPrimitive, FromPrimitive, Clone, Copy, Eq, PartialEq)]
#[repr(u16)]
pub enum WindowType { pub enum WindowType {
// not used // not used
Generic9x1, Generic9x1 = screen!("minecraft:generic_9x1"),
// not used // not used
Generic9x2, Generic9x2 = screen!("minecraft:generic_9x2"),
// General-purpose 3-row inventory. Used by Chest, minecart with chest, ender chest, and barrel // General-purpose 3-row inventory. Used by Chest, minecart with chest, ender chest, and barrel
Generic9x3, Generic9x3 = screen!("minecraft:generic_9x3"),
// not used // not used
Generic9x4, Generic9x4 = screen!("minecraft:generic_9x4"),
// not used // not used
Generic9x5, Generic9x5 = screen!("minecraft:generic_9x5"),
// Used by large chests // Used by large chests
Generic9x6, Generic9x6 = screen!("minecraft:generic_9x6"),
// General-purpose 3-by-3 square inventory, used by Dispenser and Dropper // General-purpose 3-by-3 square inventory, used by Dispenser and Dropper
Generic3x3, Generic3x3 = screen!("minecraft:generic_3x3"),
// General-purpose 3-by-3 square inventory, used by the Crafter // General-purpose 3-by-3 square inventory, used by the Crafter
Craft3x3, Craft3x3 = screen!("minecraft:crafter_3x3"),
Anvil, Anvil = screen!("minecraft:anvil"),
Beacon, Beacon = screen!("minecraft:beacon"),
BlastFurnace, BlastFurnace = screen!("minecraft:blast_furnace"),
BrewingStand, BrewingStand = screen!("minecraft:brewing_stand"),
CraftingTable, CraftingTable = screen!("minecraft:crafting"),
EnchantmentTable, EnchantmentTable = screen!("minecraft:enchantment"),
Furnace, Furnace = screen!("minecraft:furnace"),
Grindstone, Grindstone = screen!("minecraft:grindstone"),
// Hopper or minecart with hopper // Hopper or minecart with hopper
Hopper, Hopper = screen!("minecraft:hopper"),
Lectern, Lectern = screen!("minecraft:lectern"),
Loom, Loom = screen!("minecraft:loom"),
// Villager, Wandering Trader // Villager, Wandering Trader
Merchant, Merchant = screen!("minecraft:merchant"),
ShulkerBox, ShulkerBox = screen!("minecraft:shulker_box"),
SmithingTable, SmithingTable = screen!("minecraft:smithing"),
Smoker, Smoker = screen!("minecraft:smoker"),
CartographyTable, CartographyTable = screen!("minecraft:cartography_table"),
Stonecutter, Stonecutter = screen!("minecraft:stonecutter"),
} }
pub struct ContainerStruct<const SLOTS: usize>([Option<ItemStack>; SLOTS]); pub struct ContainerStruct<const SLOTS: usize>([Option<ItemStack>; SLOTS]);

View File

@ -25,6 +25,11 @@ pub fn client_packet(input: TokenStream, item: TokenStream) -> TokenStream {
gen.into() gen.into()
} }
mod screen;
#[proc_macro]
pub fn screen(item: TokenStream) -> TokenStream {
screen::screen_impl(item)
}
mod sound; mod sound;
#[proc_macro] #[proc_macro]
pub fn sound(item: TokenStream) -> TokenStream { pub fn sound(item: TokenStream) -> TokenStream {

View File

@ -0,0 +1,27 @@
use std::{collections::HashMap, sync::LazyLock};
use proc_macro::TokenStream;
use quote::quote;
use serde::Deserialize;
#[derive(Deserialize)]
pub struct Screen {
name: String,
id: u16,
}
static SCREENS: LazyLock<HashMap<String, u16>> = LazyLock::new(|| {
serde_json::from_str::<Vec<Screen>>(include_str!("../../assets/screens.json"))
.expect("Could not parse screens.json registry.")
.into_iter()
.map(|val| (val.name, val.id))
.collect()
});
pub(crate) fn screen_impl(item: TokenStream) -> TokenStream {
let input_string = item.to_string();
let screen_name = input_string.trim_matches('"');
let id = SCREENS.get(screen_name).expect("Invalid screen");
quote! { #id }.into()
}

View File

@ -60,7 +60,7 @@ impl AttackType {
} }
} }
pub(super) async fn handle_knockback( pub async fn handle_knockback(
attacker_entity: &Entity, attacker_entity: &Entity,
victim: &Player, victim: &Player,
victim_entity: &Entity, victim_entity: &Entity,
@ -93,11 +93,7 @@ pub(super) async fn handle_knockback(
victim.client.send_packet(packet).await; victim.client.send_packet(packet).await;
} }
pub(super) async fn spawn_sweep_particle( pub async fn spawn_sweep_particle(attacker_entity: &Entity, world: &World, pos: &Vector3<f64>) {
attacker_entity: &Entity,
world: &World,
pos: &Vector3<f64>,
) {
let yaw = attacker_entity.yaw.load(); let yaw = attacker_entity.yaw.load();
let d = -f64::from((yaw * (PI / 180.0)).sin()); let d = -f64::from((yaw * (PI / 180.0)).sin());
let e = f64::from((yaw * (PI / 180.0)).cos()); let e = f64::from((yaw * (PI / 180.0)).cos());
@ -123,11 +119,7 @@ pub(super) async fn spawn_sweep_particle(
.await; .await;
} }
pub(super) async fn player_attack_sound( pub async fn player_attack_sound(pos: &Vector3<f64>, world: &World, attack_type: AttackType) {
pos: &Vector3<f64>,
world: &World,
attack_type: AttackType,
) {
match attack_type { match attack_type {
AttackType::Knockback => { AttackType::Knockback => {
world world

View File

@ -8,8 +8,8 @@ use pumpkin_inventory::container_click::{
}; };
use pumpkin_inventory::drag_handler::DragHandler; use pumpkin_inventory::drag_handler::DragHandler;
use pumpkin_inventory::window_property::{WindowProperty, WindowPropertyTrait}; use pumpkin_inventory::window_property::{WindowProperty, WindowPropertyTrait};
use pumpkin_inventory::Container;
use pumpkin_inventory::{container_click, InventoryError, OptionallyCombinedContainer}; use pumpkin_inventory::{container_click, InventoryError, OptionallyCombinedContainer};
use pumpkin_inventory::{Container, WindowType};
use pumpkin_protocol::client::play::{ use pumpkin_protocol::client::play::{
CCloseContainer, COpenScreen, CSetContainerContent, CSetContainerProperty, CSetContainerSlot, CCloseContainer, COpenScreen, CSetContainerContent, CSetContainerProperty, CSetContainerSlot,
}; };
@ -22,7 +22,7 @@ use std::sync::Arc;
#[expect(unused)] #[expect(unused)]
impl Player { impl Player {
pub async fn open_container(&self, server: &Server, minecraft_menu_id: &str) { pub async fn open_container(&self, server: &Server, window_type: WindowType) {
let inventory = self.inventory.lock().await; let inventory = self.inventory.lock().await;
inventory inventory
.state_id .state_id
@ -30,17 +30,7 @@ impl Player {
let total_opened_containers = inventory.total_opened_containers; let total_opened_containers = inventory.total_opened_containers;
let container = self.get_open_container(server); let container = self.get_open_container(server);
let container = container.as_ref().map(|container| container.lock()); 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();
// TODO // TODO
let menu_protocol_id = VarInt(0);
let window_title = match container { let window_title = match container {
Some(container) => container.await.window_name(), Some(container) => container.await.window_name(),
None => inventory.window_name(), None => inventory.window_name(),
@ -50,7 +40,7 @@ impl Player {
self.client self.client
.send_packet(&COpenScreen::new( .send_packet(&COpenScreen::new(
total_opened_containers.into(), total_opened_containers.into(),
menu_protocol_id, VarInt(window_type as i32),
title, title,
)) ))
.await; .await;

View File

@ -43,7 +43,7 @@ use thiserror::Error;
pub mod authentication; pub mod authentication;
mod client_packet; mod client_packet;
mod combat; pub mod combat;
mod container; mod container;
pub mod player_packet; pub mod player_packet;

View File

@ -7,7 +7,7 @@ use crate::{
world::player_chunker, world::player_chunker,
}; };
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
use pumpkin_config::{PVPConfig, ADVANCED_CONFIG}; use pumpkin_config::ADVANCED_CONFIG;
use pumpkin_core::math::position::WorldPosition; use pumpkin_core::math::position::WorldPosition;
use pumpkin_core::{ use pumpkin_core::{
math::{vector3::Vector3, wrap_degrees}, math::{vector3::Vector3, wrap_degrees},
@ -15,12 +15,11 @@ use pumpkin_core::{
GameMode, GameMode,
}; };
use pumpkin_inventory::{InventoryError, WindowType}; use pumpkin_inventory::{InventoryError, WindowType};
use pumpkin_macros::sound; use pumpkin_protocol::server::play::{SCloseContainer, SKeepAlive, SSetPlayerGround, SUseItem};
use pumpkin_protocol::{ use pumpkin_protocol::{
client::play::{ client::play::{
Animation, CAcknowledgeBlockChange, CEntityAnimation, CHeadRot, CHurtAnimation, Animation, CAcknowledgeBlockChange, CEntityAnimation, CHeadRot, CPingResponse,
CPingResponse, CPlayerChatMessage, CUpdateEntityPos, CUpdateEntityPosRot, CUpdateEntityRot, CPlayerChatMessage, CUpdateEntityPos, CUpdateEntityPosRot, CUpdateEntityRot, FilterType,
FilterType,
}, },
server::play::{ server::play::{
Action, ActionType, SChatCommand, SChatMessage, SClientInformationPlay, SConfirmTeleport, Action, ActionType, SChatCommand, SChatMessage, SClientInformationPlay, SConfirmTeleport,
@ -28,18 +27,10 @@ use pumpkin_protocol::{
SPlayerPosition, SPlayerPositionRotation, SPlayerRotation, SSetCreativeSlot, SSetHeldItem, SPlayerPosition, SPlayerPositionRotation, SPlayerRotation, SSetCreativeSlot, SSetHeldItem,
SSwingArm, SUseItemOn, Status, SSwingArm, SUseItemOn, Status,
}, },
SoundCategory,
};
use pumpkin_protocol::{
server::play::{SCloseContainer, SKeepAlive, SSetPlayerGround, SUseItem},
VarInt,
}; };
use pumpkin_world::block::{block_registry::get_block_by_item, BlockFace}; use pumpkin_world::block::{block_registry::get_block_by_item, BlockFace};
use super::{ use super::PlayerConfig;
combat::{self, player_attack_sound, AttackType},
PlayerConfig,
};
fn modulus(a: f32, b: f32) -> f32 { fn modulus(a: f32, b: f32) -> f32 {
((a % b) + b) % b ((a % b) + b) % b
@ -435,7 +426,7 @@ impl Player {
return; return;
}; };
self.attack(&victim, config).await; self.attack(&victim).await;
} }
ActionType::Interact | ActionType::InteractAt => { ActionType::Interact | ActionType::InteractAt => {
log::debug!("todo"); log::debug!("todo");
@ -443,72 +434,6 @@ impl Player {
} }
} }
pub async fn attack(&self, victim: &Arc<Self>, config: &PVPConfig) {
let world = &self.living_entity.entity.world;
let victim_entity = &victim.living_entity.entity;
let attacker_entity = &self.living_entity.entity;
let pos = victim_entity.pos.load();
let attack_cooldown_progress = self.get_attack_cooldown_progress(0.5);
self.last_attacked_ticks
.store(0, std::sync::atomic::Ordering::Relaxed);
// TODO: attack damage attribute and deal damage
let damage = 2.0;
if !victim.living_entity.damage(damage)
|| (config.protect_creative && victim.gamemode.load() == GameMode::Creative)
{
world
.play_sound(
sound!("minecraft:entity.player.attack.nodamage"),
SoundCategory::Players,
&pos,
)
.await;
return;
}
world
.play_sound(
sound!("minecraft:entity.player.hurt"),
SoundCategory::Players,
&pos,
)
.await;
let attack_type = AttackType::new(self, attack_cooldown_progress).await;
player_attack_sound(&pos, world, attack_type).await;
// if is_crit {
// damage *= 1.5;
// }
let mut knockback_strength = 1.0;
match attack_type {
AttackType::Knockback => knockback_strength += 1.0,
AttackType::Sweeping => {
combat::spawn_sweep_particle(attacker_entity, world, &pos).await;
}
_ => {}
};
if config.knockback {
combat::handle_knockback(attacker_entity, victim, victim_entity, knockback_strength)
.await;
}
if config.hurt_animation {
let entity_id = VarInt(victim_entity.entity_id);
world
.broadcast_packet_all(&CHurtAnimation::new(&entity_id, attacker_entity.yaw.load()))
.await;
}
if config.swing {}
}
pub async fn handle_player_action(&self, player_action: SPlayerAction) { pub async fn handle_player_action(&self, player_action: SPlayerAction) {
match Status::from_i32(player_action.status.0) { match Status::from_i32(player_action.status.0) {
Some(status) => match status { Some(status) => match status {
@ -674,6 +599,10 @@ impl Player {
// This function will in the future be used to keep track of if the client is in a valid state. // This function will in the future be used to keep track of if the client is in a valid state.
// But this is not possible yet // But this is not possible yet
pub async fn handle_close_container(&self, server: &Server, packet: SCloseContainer) { pub async fn handle_close_container(&self, server: &Server, packet: SCloseContainer) {
let Some(_window_type) = WindowType::from_i32(packet.window_id.0) else {
self.kick(TextComponent::text("Invalid window ID")).await;
return;
};
// window_id 0 represents both 9x1 Generic AND inventory here // window_id 0 represents both 9x1 Generic AND inventory here
self.inventory self.inventory
.lock() .lock()
@ -688,9 +617,5 @@ impl Player {
} }
self.open_container.store(None); self.open_container.store(None);
} }
let Some(_window_type) = WindowType::from_i32(packet.window_id.0) else {
self.kick(TextComponent::text("Invalid window ID")).await;
return;
};
} }
} }

View File

@ -32,7 +32,9 @@ impl CommandExecutor for EchestExecutor {
open_containers.insert(0, open_container); open_containers.insert(0, open_container);
} }
} }
player.open_container(server, "minecraft:generic_9x3").await; player
.open_container(server, pumpkin_inventory::WindowType::Generic9x3)
.await;
} }
Ok(()) Ok(())

View File

@ -11,6 +11,7 @@ use crossbeam::atomic::AtomicCell;
use itertools::Itertools; use itertools::Itertools;
use num_derive::FromPrimitive; use num_derive::FromPrimitive;
use num_traits::{FromPrimitive, ToPrimitive}; use num_traits::{FromPrimitive, ToPrimitive};
use pumpkin_config::ADVANCED_CONFIG;
use pumpkin_core::{ use pumpkin_core::{
math::{boundingbox::BoundingBox, position::WorldPosition, vector2::Vector2, vector3::Vector3}, math::{boundingbox::BoundingBox, position::WorldPosition, vector2::Vector2, vector3::Vector3},
text::TextComponent, text::TextComponent,
@ -18,11 +19,13 @@ use pumpkin_core::{
}; };
use pumpkin_entity::{entity_type::EntityType, EntityId}; use pumpkin_entity::{entity_type::EntityType, EntityId};
use pumpkin_inventory::player::PlayerInventory; use pumpkin_inventory::player::PlayerInventory;
use pumpkin_macros::sound;
use pumpkin_protocol::{ use pumpkin_protocol::{
bytebuf::DeserializerError, bytebuf::DeserializerError,
client::play::{ client::play::{
CGameEvent, CKeepAlive, CPlayDisconnect, CPlayerAbilities, CPlayerInfoUpdate, CSetHealth, CGameEvent, CHurtAnimation, CKeepAlive, CPlayDisconnect, CPlayerAbilities,
CSyncPlayerPosition, CSystemChatMessage, GameEvent, PlayerAction, CPlayerInfoUpdate, CSetHealth, CSyncPlayerPosition, CSystemChatMessage, GameEvent,
PlayerAction,
}, },
server::play::{ server::play::{
SChatCommand, SChatMessage, SClientInformationPlay, SConfirmTeleport, SInteract, SChatCommand, SChatMessage, SClientInformationPlay, SConfirmTeleport, SInteract,
@ -30,7 +33,7 @@ use pumpkin_protocol::{
SPlayerRotation, SSetCreativeSlot, SSetHeldItem, SSetPlayerGround, SSwingArm, SUseItem, SPlayerRotation, SSetCreativeSlot, SSetHeldItem, SSetPlayerGround, SSwingArm, SUseItem,
SUseItemOn, ServerboundPlayPackets, SUseItemOn, ServerboundPlayPackets,
}, },
RawPacket, ServerPacket, VarInt, RawPacket, ServerPacket, SoundCategory, VarInt,
}; };
use tokio::sync::{Mutex, Notify}; use tokio::sync::{Mutex, Notify};
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
@ -40,7 +43,11 @@ use pumpkin_world::{cylindrical_chunk_iterator::Cylindrical, item::ItemStack};
use super::Entity; use super::Entity;
use crate::{ use crate::{
client::{authentication::GameProfile, Client, PlayerConfig}, client::{
authentication::GameProfile,
combat::{self, player_attack_sound, AttackType},
Client, PlayerConfig,
},
server::Server, server::Server,
world::World, world::World,
}; };
@ -331,6 +338,73 @@ impl Player {
//self.living_entity.entity.world.level.list_cached(); //self.living_entity.entity.world.level.list_cached();
} }
pub async fn attack(&self, victim: &Arc<Self>) {
let world = &self.living_entity.entity.world;
let victim_entity = &victim.living_entity.entity;
let attacker_entity = &self.living_entity.entity;
let config = &ADVANCED_CONFIG.pvp;
let pos = victim_entity.pos.load();
let attack_cooldown_progress = self.get_attack_cooldown_progress(0.5);
self.last_attacked_ticks
.store(0, std::sync::atomic::Ordering::Relaxed);
// TODO: attack damage attribute and deal damage
let damage = 2.0;
if !victim.living_entity.damage(damage)
|| (config.protect_creative && victim.gamemode.load() == GameMode::Creative)
{
world
.play_sound(
sound!("minecraft:entity.player.attack.nodamage"),
SoundCategory::Players,
&pos,
)
.await;
return;
}
world
.play_sound(
sound!("minecraft:entity.player.hurt"),
SoundCategory::Players,
&pos,
)
.await;
let attack_type = AttackType::new(self, attack_cooldown_progress).await;
player_attack_sound(&pos, world, attack_type).await;
// if is_crit {
// damage *= 1.5;
// }
let mut knockback_strength = 1.0;
match attack_type {
AttackType::Knockback => knockback_strength += 1.0,
AttackType::Sweeping => {
combat::spawn_sweep_particle(attacker_entity, world, &pos).await;
}
_ => {}
};
if config.knockback {
combat::handle_knockback(attacker_entity, victim, victim_entity, knockback_strength)
.await;
}
if config.hurt_animation {
let entity_id = VarInt(victim_entity.entity_id);
world
.broadcast_packet_all(&CHurtAnimation::new(&entity_id, attacker_entity.yaw.load()))
.await;
}
if config.swing {}
}
pub async fn await_cancel(&self) { pub async fn await_cancel(&self) {
self.cancel_tasks.notified().await; self.cancel_tasks.notified().await;
} }

View File

@ -3,23 +3,12 @@ use std::net::SocketAddr;
use packet::{ClientboundPacket, Packet, PacketError, ServerboundPacket}; use packet::{ClientboundPacket, Packet, PacketError, ServerboundPacket};
use pumpkin_config::{RCONConfig, ADVANCED_CONFIG}; use pumpkin_config::{RCONConfig, ADVANCED_CONFIG};
use std::sync::Arc; use std::sync::Arc;
use thiserror::Error;
use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::io::{AsyncReadExt, AsyncWriteExt};
use crate::server::Server; use crate::server::Server;
mod packet; mod packet;
#[derive(Debug, Error)]
pub enum RCONError {
#[error("authentication failed")]
Auth,
#[error("command exceeds the maximum length")]
CommandTooLong,
#[error("{}", _0)]
Io(std::io::Error),
}
pub struct RCONServer; pub struct RCONServer;
impl RCONServer { impl RCONServer {

View File

@ -147,7 +147,7 @@ impl Server {
} }
pub async fn remove_player(&self) { pub async fn remove_player(&self) {
// TODO: Config if we want increase online // TODO: Config if we want decrease online
self.server_listing.lock().await.remove_player(); self.server_listing.lock().await.remove_player();
} }