2021-11-16 18:08:47 +01:00

748 lines
20 KiB
Rust

use crate::data::chat::Message;
use crate::decoder::Decoder;
use crate::decoder::DecoderReadExt;
use crate::encoder::EncoderWriteExt;
use crate::error::DecodeError;
use crate::{trait_packet_id, version::PacketId};
use byteorder::{ReadBytesExt, WriteBytesExt};
use minecraft_protocol_derive::{Decoder, Encoder};
use nbt::CompoundTag;
use std::io::Read;
use uuid::Uuid;
pub enum GameServerBoundPacket {
ServerBoundChatMessage(ServerBoundChatMessage),
ServerBoundKeepAlive(ServerBoundKeepAlive),
ServerBoundAbilities(ServerBoundAbilities),
}
pub enum GameClientBoundPacket {
ClientBoundChatMessage(ClientBoundChatMessage),
JoinGame(JoinGame),
ClientBoundKeepAlive(ClientBoundKeepAlive),
ChunkData(ChunkData),
GameDisconnect(GameDisconnect),
BossBar(BossBar),
EntityAction(EntityAction),
}
impl GameServerBoundPacket {
pub fn get_type_id(&self) -> u8 {
match self {
GameServerBoundPacket::ServerBoundChatMessage(_) => 0x03,
GameServerBoundPacket::ServerBoundKeepAlive(_) => 0x0F,
GameServerBoundPacket::ServerBoundAbilities(_) => 0x19,
}
}
pub fn decode<R: Read>(type_id: u8, reader: &mut R) -> Result<Self, DecodeError> {
match type_id {
0x03 => {
let chat_message = ServerBoundChatMessage::decode(reader)?;
Ok(GameServerBoundPacket::ServerBoundChatMessage(chat_message))
}
0x0F => {
let keep_alive = ServerBoundKeepAlive::decode(reader)?;
Ok(GameServerBoundPacket::ServerBoundKeepAlive(keep_alive))
}
_ => Err(DecodeError::UnknownPacketType { type_id }),
}
}
}
impl GameClientBoundPacket {
pub fn get_type_id(&self) -> u8 {
match self {
GameClientBoundPacket::ClientBoundChatMessage(_) => 0x0F,
GameClientBoundPacket::GameDisconnect(_) => 0x1A,
GameClientBoundPacket::ClientBoundKeepAlive(_) => 0x20,
GameClientBoundPacket::ChunkData(_) => 0x21,
GameClientBoundPacket::JoinGame(_) => 0x26,
GameClientBoundPacket::BossBar(_) => 0x0D,
GameClientBoundPacket::EntityAction(_) => 0x1B,
}
}
pub fn decode<R: Read>(type_id: u8, reader: &mut R) -> Result<Self, DecodeError> {
match type_id {
0x0E => {
let chat_message = ClientBoundChatMessage::decode(reader)?;
Ok(GameClientBoundPacket::ClientBoundChatMessage(chat_message))
}
0x1A => {
let game_disconnect = GameDisconnect::decode(reader)?;
Ok(GameClientBoundPacket::GameDisconnect(game_disconnect))
}
0x20 => {
let keep_alive = ClientBoundKeepAlive::decode(reader)?;
Ok(GameClientBoundPacket::ClientBoundKeepAlive(keep_alive))
}
0x21 => {
let chunk_data = ChunkData::decode(reader)?;
Ok(GameClientBoundPacket::ChunkData(chunk_data))
}
0x25 => {
let join_game = JoinGame::decode(reader)?;
Ok(GameClientBoundPacket::JoinGame(join_game))
}
_ => Err(DecodeError::UnknownPacketType { type_id }),
}
}
}
#[derive(Encoder, Decoder, Debug)]
pub struct ServerBoundChatMessage {
#[data_type(max_length = 256)]
pub message: String,
}
impl ServerBoundChatMessage {
pub fn new(message: String) -> GameServerBoundPacket {
let chat_message = ServerBoundChatMessage { message };
GameServerBoundPacket::ServerBoundChatMessage(chat_message)
}
}
#[derive(Encoder, Decoder, Debug)]
pub struct ClientBoundChatMessage {
pub message: Message,
pub position: MessagePosition,
}
#[derive(Encoder, Decoder, Debug, Eq, PartialEq)]
pub enum MessagePosition {
Chat,
System,
HotBar,
}
impl ClientBoundChatMessage {
pub fn new(message: Message, position: MessagePosition) -> GameClientBoundPacket {
let chat_message = ClientBoundChatMessage { message, position };
GameClientBoundPacket::ClientBoundChatMessage(chat_message)
}
}
#[derive(Encoder, Decoder, Debug)]
pub struct JoinGame {
pub entity_id: u32,
pub game_mode: GameMode,
pub dimension: i32,
pub max_players: u8,
#[data_type(max_length = 16)]
pub level_type: String,
#[data_type(with = "var_int")]
pub view_distance: i32,
pub reduced_debug_info: bool,
}
#[derive(Encoder, Decoder, Debug, Eq, PartialEq)]
pub enum GameMode {
Survival = 0,
Creative = 1,
Adventure = 2,
Spectator = 3,
Hardcore = 8,
}
impl JoinGame {
pub fn new(
entity_id: u32,
game_mode: GameMode,
dimension: i32,
max_players: u8,
level_type: String,
view_distance: i32,
reduced_debug_info: bool,
) -> GameClientBoundPacket {
let join_game = JoinGame {
entity_id,
game_mode,
dimension,
max_players,
level_type,
view_distance,
reduced_debug_info,
};
GameClientBoundPacket::JoinGame(join_game)
}
}
#[derive(Encoder, Decoder)]
pub struct ServerBoundKeepAlive {
pub id: u64,
}
impl ServerBoundKeepAlive {
pub fn new(id: u64) -> GameServerBoundPacket {
let keep_alive = ServerBoundKeepAlive { id };
GameServerBoundPacket::ServerBoundKeepAlive(keep_alive)
}
}
#[derive(Encoder, Decoder)]
pub struct ClientBoundKeepAlive {
pub id: u64,
}
impl ClientBoundKeepAlive {
pub fn new(id: u64) -> GameClientBoundPacket {
let keep_alive = ClientBoundKeepAlive { id };
GameClientBoundPacket::ClientBoundKeepAlive(keep_alive)
}
}
#[derive(Encoder, Decoder, Debug)]
pub struct ChunkData {
pub x: i32,
pub z: i32,
pub full: bool,
#[data_type(with = "var_int")]
pub primary_mask: i32,
pub heights: CompoundTag,
pub data: Vec<u8>,
pub tiles: Vec<CompoundTag>,
}
impl ChunkData {
pub fn new(
x: i32,
z: i32,
full: bool,
primary_mask: i32,
heights: CompoundTag,
data: Vec<u8>,
tiles: Vec<CompoundTag>,
) -> GameClientBoundPacket {
let chunk_data = ChunkData {
x,
z,
full,
primary_mask,
heights,
data,
tiles,
};
GameClientBoundPacket::ChunkData(chunk_data)
}
}
#[derive(Encoder, Decoder, Debug)]
pub struct GameDisconnect {
pub reason: Message,
}
impl GameDisconnect {
pub fn new(reason: Message) -> GameClientBoundPacket {
let game_disconnect = GameDisconnect { reason };
GameClientBoundPacket::GameDisconnect(game_disconnect)
}
}
#[derive(Encoder, Decoder, Debug, PartialEq)]
pub struct BossBar {
pub id: Uuid,
pub action: BossBarAction,
}
#[derive(Encoder, Decoder, Debug, PartialEq)]
pub enum BossBarAction {
Add {
title: Message,
health: f32,
color: BossBarColor,
division: BossBarDivision,
flags: u8,
},
Remove,
UpdateHealth {
health: f32,
},
UpdateTitle {
title: Message,
},
UpdateStyle {
color: BossBarColor,
division: BossBarDivision,
},
UpdateFlags {
flags: u8,
},
}
#[derive(Encoder, Decoder, Debug, PartialEq)]
pub enum BossBarColor {
Pink,
Blue,
Red,
Green,
Yellow,
Purple,
White,
}
#[derive(Encoder, Decoder, Debug, PartialEq)]
pub enum BossBarDivision {
None,
Notches6,
Notches10,
Notches12,
Notches20,
}
impl BossBar {
pub fn new(id: Uuid, action: BossBarAction) -> GameClientBoundPacket {
let boss_bar = BossBar { id, action };
GameClientBoundPacket::BossBar(boss_bar)
}
}
#[derive(Encoder, Decoder, Debug, PartialEq)]
pub struct EntityAction {
#[data_type(with = "var_int")]
pub entity_id: i32,
pub action_id: EntityActionId,
#[data_type(with = "var_int")]
pub jump_boost: i32,
}
#[derive(Encoder, Decoder, Debug, PartialEq)]
#[data_type(with = "var_int")]
pub enum EntityActionId {
StartSneaking,
StopSneaking,
LeaveBad,
StartSprinting,
StopSprinting,
StartJumpWithHorse,
StopJumpWithHorse,
OpenHorseInventory,
StartFlyingWithElytra,
}
#[derive(Encoder, Decoder, Debug, PartialEq)]
pub struct ServerBoundAbilities {
#[data_type(bitfield)]
pub invulnerable: bool,
#[data_type(bitfield)]
pub allow_flying: bool,
#[data_type(bitfield)]
pub flying: bool,
#[data_type(bitfield)]
pub creative_mode: bool,
pub fly_speed: f32,
pub walk_speed: f32,
}
#[cfg(test)]
mod tests {
use crate::data::chat::Payload;
use crate::decoder::Decoder;
use crate::encoder::Encoder;
use crate::encoder::EncoderWriteExt;
use crate::error::{DecodeError, EncodeError};
use crate::version::v1_14_4::game::*;
use crate::STRING_MAX_LENGTH;
use nbt::CompoundTag;
use std::io::Cursor;
use std::str::FromStr;
#[test]
fn test_server_bound_chat_message_encode() {
let chat_message = ServerBoundChatMessage {
message: String::from("hello server!"),
};
let mut vec = Vec::new();
chat_message.encode(&mut vec).unwrap();
assert_eq!(
vec,
include_bytes!("../../../test/packet/game/server_bound_chat_message.dat").to_vec()
);
}
#[test]
fn test_server_bound_chat_message_decode() {
let mut cursor = Cursor::new(
include_bytes!("../../../test/packet/game/server_bound_chat_message.dat").to_vec(),
);
let chat_message = ServerBoundChatMessage::decode(&mut cursor).unwrap();
assert_eq!(chat_message.message, "hello server!");
}
#[test]
fn test_server_bound_chat_message_encode_invalid_length() {
let chat_message = ServerBoundChatMessage {
message: "abc".repeat(100),
};
let mut vec = Vec::new();
let encode_error = chat_message
.encode(&mut vec)
.err()
.expect("Expected error `StringTooLong` because message has invalid length");
match encode_error {
EncodeError::StringTooLong { length, max_length } => {
assert_eq!(length, 300);
assert_eq!(max_length, 256);
}
_ => panic!("Expected `StringTooLong` but got `{:?}`", encode_error),
}
}
#[test]
fn test_server_bound_chat_message_decode_invalid_length() {
let message = "abc".repeat(100);
let mut vec = Vec::new();
vec.write_string(&message, STRING_MAX_LENGTH).unwrap();
let mut cursor = Cursor::new(vec);
let decode_error = ServerBoundChatMessage::decode(&mut cursor)
.err()
.expect("Expected error `StringTooLong` because message has invalid length");
match decode_error {
DecodeError::StringTooLong { length, max_length } => {
assert_eq!(length, 300);
assert_eq!(max_length, 256);
}
_ => panic!("Expected `StringTooLong` but got `{:?}`", decode_error),
}
}
#[test]
fn test_client_bound_chat_message_encode() {
let chat_message = ClientBoundChatMessage {
message: Message::new(Payload::text("hello client!")),
position: MessagePosition::System,
};
let mut vec = Vec::new();
chat_message.encode(&mut vec).unwrap();
assert_eq!(
vec,
include_bytes!("../../../test/packet/game/client_bound_chat_message.dat").to_vec()
);
}
#[test]
fn test_client_bound_chat_message_decode() {
let mut cursor = Cursor::new(
include_bytes!("../../../test/packet/game/client_bound_chat_message.dat").to_vec(),
);
let chat_message = ClientBoundChatMessage::decode(&mut cursor).unwrap();
assert_eq!(
chat_message.message,
Message::new(Payload::text("hello client!"))
);
assert_eq!(chat_message.position, MessagePosition::System);
}
#[test]
fn test_server_bound_keep_alive_encode() {
let keep_alive = ServerBoundKeepAlive { id: 31122019 };
let mut vec = Vec::new();
keep_alive.encode(&mut vec).unwrap();
assert_eq!(
vec,
include_bytes!("../../../test/packet/game/server_bound_keep_alive.dat").to_vec()
);
}
#[test]
fn test_server_bound_keep_alive_decode() {
let mut cursor = Cursor::new(
include_bytes!("../../../test/packet/game/server_bound_keep_alive.dat").to_vec(),
);
let keep_alive = ServerBoundKeepAlive::decode(&mut cursor).unwrap();
assert_eq!(keep_alive.id, 31122019);
}
#[test]
fn test_client_bound_keep_alive_encode() {
let keep_alive = ClientBoundKeepAlive { id: 240714 };
let mut vec = Vec::new();
keep_alive.encode(&mut vec).unwrap();
assert_eq!(
vec,
include_bytes!("../../../test/packet/game/client_bound_keep_alive.dat").to_vec()
);
}
#[test]
fn test_client_bound_keep_alive_decode() {
let mut cursor = Cursor::new(
include_bytes!("../../../test/packet/game/client_bound_keep_alive.dat").to_vec(),
);
let keep_alive = ClientBoundKeepAlive::decode(&mut cursor).unwrap();
assert_eq!(keep_alive.id, 240714);
}
#[test]
fn test_join_game_encode() {
let join_game = JoinGame {
entity_id: 27,
game_mode: GameMode::Hardcore,
dimension: 23,
max_players: 100,
level_type: String::from("default"),
view_distance: 10,
reduced_debug_info: true,
};
let mut vec = Vec::new();
join_game.encode(&mut vec).unwrap();
assert_eq!(
vec,
include_bytes!("../../../test/packet/game/join_game.dat").to_vec()
);
}
#[test]
fn test_join_game_decode() {
let mut cursor =
Cursor::new(include_bytes!("../../../test/packet/game/join_game.dat").to_vec());
let join_game = JoinGame::decode(&mut cursor).unwrap();
assert_eq!(join_game.entity_id, 27);
assert_eq!(join_game.game_mode, GameMode::Hardcore);
assert_eq!(join_game.dimension, 23);
assert_eq!(join_game.max_players, 100);
assert_eq!(join_game.level_type, String::from("default"));
assert_eq!(join_game.view_distance, 10);
assert!(join_game.reduced_debug_info);
}
#[test]
fn test_chunk_data_encode() {
let chunk_data = ChunkData {
x: -2,
z: 5,
full: true,
primary_mask: 65535,
heights: CompoundTag::named("HeightMaps"),
data: vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
tiles: vec![CompoundTag::named("TileEntity")],
};
let mut vec = Vec::new();
chunk_data.encode(&mut vec).unwrap();
assert_eq!(
vec,
include_bytes!("../../../test/packet/game/chunk_data.dat").to_vec()
);
}
#[test]
fn test_chunk_data_decode() {
let mut cursor =
Cursor::new(include_bytes!("../../../test/packet/game/chunk_data.dat").to_vec());
let chunk_data = ChunkData::decode(&mut cursor).unwrap();
assert_eq!(chunk_data.x, -2);
assert_eq!(chunk_data.z, 5);
assert!(chunk_data.full);
assert_eq!(chunk_data.heights.name, Some(String::from("HeightMaps")));
assert_eq!(chunk_data.data, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
assert_eq!(chunk_data.primary_mask, 65535);
assert_eq!(chunk_data.tiles[0].name, Some(String::from("TileEntity")));
}
#[test]
fn test_game_disconnect_encode() {
let game_disconnect = GameDisconnect {
reason: Message::new(Payload::text("Message")),
};
let mut vec = Vec::new();
game_disconnect.encode(&mut vec).unwrap();
assert_eq!(
vec,
include_bytes!("../../../test/packet/game/game_disconnect.dat").to_vec()
);
}
#[test]
fn test_game_disconnect_decode() {
let mut cursor =
Cursor::new(include_bytes!("../../../test/packet/game/game_disconnect.dat").to_vec());
let game_disconnect = GameDisconnect::decode(&mut cursor).unwrap();
assert_eq!(
game_disconnect.reason,
Message::new(Payload::text("Message"))
);
}
#[test]
fn test_boss_bar_add_encode() {
let boss_bar_add = create_boss_bar_add_packet();
let mut vec = Vec::new();
boss_bar_add.encode(&mut vec).unwrap();
assert_eq!(
vec,
include_bytes!("../../../test/packet/game/boss_bar_add.dat").to_vec()
);
}
#[test]
fn test_boss_bar_add_decode() {
let mut cursor =
Cursor::new(include_bytes!("../../../test/packet/game/boss_bar_add.dat").to_vec());
let boss_bar_add = BossBar::decode(&mut cursor).unwrap();
assert_eq!(boss_bar_add, create_boss_bar_add_packet());
}
fn create_boss_bar_add_packet() -> BossBar {
BossBar {
id: Uuid::from_str("afa32ac8-d3bf-47f3-99eb-294d60b3dca2").unwrap(),
action: BossBarAction::Add {
title: Message::from_str("Boss title"),
health: 123.45,
color: BossBarColor::Yellow,
division: BossBarDivision::Notches10,
flags: 7,
},
}
}
#[test]
fn test_boss_bar_remove_encode() {
let boss_bar_remove = create_boss_bar_remove_packet();
let mut vec = Vec::new();
boss_bar_remove.encode(&mut vec).unwrap();
assert_eq!(
vec,
include_bytes!("../../../test/packet/game/boss_bar_remove.dat").to_vec()
);
}
#[test]
fn test_boss_bar_remove_decode() {
let mut cursor =
Cursor::new(include_bytes!("../../../test/packet/game/boss_bar_remove.dat").to_vec());
let boss_bar_remove = BossBar::decode(&mut cursor).unwrap();
assert_eq!(boss_bar_remove, create_boss_bar_remove_packet());
}
fn create_boss_bar_remove_packet() -> BossBar {
BossBar {
id: Uuid::from_str("afa32ac8-d3bf-47f3-99eb-294d60b3dca2").unwrap(),
action: BossBarAction::Remove,
}
}
#[test]
fn test_entity_action_encode() {
let entity_action = EntityAction {
entity_id: 12345,
action_id: EntityActionId::StartFlyingWithElytra,
jump_boost: i32::MAX,
};
let mut vec = Vec::new();
entity_action.encode(&mut vec).unwrap();
assert_eq!(
vec,
include_bytes!("../../../test/packet/game/entity_action.dat").to_vec()
);
}
#[test]
fn test_entity_action_decode() {
let mut cursor =
Cursor::new(include_bytes!("../../../test/packet/game/entity_action.dat").to_vec());
let entity_action = EntityAction::decode(&mut cursor).unwrap();
assert_eq!(
entity_action,
EntityAction {
entity_id: 12345,
action_id: EntityActionId::StartFlyingWithElytra,
jump_boost: i32::MAX,
}
);
}
#[test]
fn test_serverbound_abilities_encode() {
let abilities = ServerBoundAbilities {
invulnerable: true,
flying: true,
allow_flying: false,
creative_mode: true,
fly_speed: 0.0,
walk_speed: 0.0,
};
let mut vec = Vec::new();
abilities.encode(&mut vec).unwrap();
assert_eq!(vec, [13, 0, 0, 0, 0, 0, 0, 0, 0]);
}
#[test]
fn test_serverbound_abilities_decode() {
let vec = [13, 0, 0, 0, 0, 0, 0, 0, 0].to_vec();
let mut cursor = Cursor::new(vec);
let abilities = ServerBoundAbilities::decode(&mut cursor).unwrap();
assert!(abilities.invulnerable);
assert!(!abilities.allow_flying);
assert!(abilities.flying);
assert!(abilities.creative_mode);
}
}
trait_packet_id!(ServerBoundChatMessage, 0x03);
trait_packet_id!(ServerBoundKeepAlive, 0x0F);
trait_packet_id!(ServerBoundAbilities, 0x19);
trait_packet_id!(ClientBoundChatMessage, 0x0F);
trait_packet_id!(GameDisconnect, 0x1A);
trait_packet_id!(ClientBoundKeepAlive, 0x21);
trait_packet_id!(ChunkData, 0x21);
trait_packet_id!(JoinGame, 0x25);
trait_packet_id!(BossBar, 0x0D);
trait_packet_id!(EntityAction, 0x1B);