use std::fmt;
use std::str::FromStr;
use ghast_protocol::{DecodeError, EncodeError, FrameEncoder, PacketFilter, RawPacket, Version};
use crate::common::{Inbound, LoginInbound, Outbound, Pose, Teleport};
use crate::interest::Interests;
mod v1_16_5;
mod v1_8;
pub use ghast_protocol::v1_8::V1_8;
pub use ghast_protocol::v1_16_5::V1_16_5;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum GameVersion {
V1_8_9,
V1_16_5,
}
impl GameVersion {
pub const ALL: [GameVersion; 2] = [GameVersion::V1_8_9, GameVersion::V1_16_5];
#[must_use]
pub const fn protocol(self) -> i32 {
match self {
Self::V1_8_9 => <V1_8 as Version>::PROTOCOL,
Self::V1_16_5 => <V1_16_5 as Version>::PROTOCOL,
}
}
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::V1_8_9 => "1.8.9",
Self::V1_16_5 => "1.16.5",
}
}
#[must_use]
pub fn eye_height(self) -> f64 {
f64::from(1.62_f32)
}
#[must_use]
pub fn sneak_eye_height(self) -> f64 {
match self {
Self::V1_8_9 => f64::from(1.62_f32 - 0.08_f32),
Self::V1_16_5 => f64::from(1.27_f32),
}
}
}
impl fmt::Display for GameVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("unsupported Minecraft version `{0}` (supported: 1.8.9, 1.16.5)")]
pub struct UnknownVersion(pub String);
impl FromStr for GameVersion {
type Err = UnknownVersion;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.trim() {
"1.8" | "1.8.0" | "1.8.1" | "1.8.2" | "1.8.3" | "1.8.4" | "1.8.5" | "1.8.6"
| "1.8.7" | "1.8.8" | "1.8.9" | "47" => Ok(Self::V1_8_9),
"1.16.4" | "1.16.5" | "754" => Ok(Self::V1_16_5),
other => Err(UnknownVersion(other.to_owned())),
}
}
}
pub trait ClientVersion: Version + sealed::Sealed {
const GAME: GameVersion;
const CHAT_LIMIT: usize;
fn write_handshake(enc: &mut FrameEncoder, host: &str, port: u16) -> Result<(), EncodeError>;
fn write_login_start(enc: &mut FrameEncoder, username: &str) -> Result<(), EncodeError>;
fn write_encryption_response(
enc: &mut FrameEncoder,
shared_secret: &[u8],
verify_token: &[u8],
) -> Result<(), EncodeError>;
fn write_plugin_response(enc: &mut FrameEncoder, message_id: i32) -> Result<(), EncodeError>;
fn decode_login(raw: &RawPacket) -> Result<Option<LoginInbound>, DecodeError>;
fn play_filter(interests: Interests) -> PacketFilter;
fn decode_play(raw: &RawPacket) -> Result<Option<Inbound>, DecodeError>;
fn inventory_packet(
inv: &mut ghast_inventory::Inventory,
raw: &RawPacket,
) -> Result<Option<ghast_inventory::Outgoing>, DecodeError>;
const INVENTORY: ghast_inventory::Version;
fn write_play(enc: &mut FrameEncoder, out: &Outbound) -> Result<(), EncodeError>;
fn apply_teleport(pose: &mut Pose, t: &Teleport);
}
pub(crate) trait HasSim {
type Sim: crate::sim::Sim;
fn new_sim(opts: &crate::sim::SimOptions) -> Self::Sim;
}
mod sealed {
#[allow(unnameable_types, reason = "sealed trait pattern")]
pub trait Sealed {}
impl Sealed for super::V1_8 {}
impl Sealed for super::V1_16_5 {}
}
pub(crate) fn truncate_utf16(s: &str, max: usize) -> &str {
let mut units = 0;
for (i, c) in s.char_indices() {
units += c.len_utf16();
if units > max {
return &s[..i];
}
}
s
}
pub(crate) fn teleport_target(pose: &mut Pose, t: &Teleport) -> (f32, f32) {
use crate::common::teleport_flags as fl;
let mut x = t.x;
let mut y = t.y;
let mut z = t.z;
if t.flags & fl::X != 0 {
x += pose.pos.x;
} else {
pose.motion.x = 0.0;
}
if t.flags & fl::Y != 0 {
y += pose.pos.y;
} else {
pose.motion.y = 0.0;
}
if t.flags & fl::Z != 0 {
z += pose.pos.z;
} else {
pose.motion.z = 0.0;
}
pose.pos = crate::common::Vec3::new(x, y, z);
let mut yaw = t.yaw;
let mut pitch = t.pitch;
if t.flags & fl::PITCH != 0 {
pitch += pose.pitch;
}
if t.flags & fl::YAW != 0 {
yaw += pose.yaw;
}
(yaw, pitch)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_versions() {
assert_eq!("1.8.8".parse::<GameVersion>().unwrap(), GameVersion::V1_8_9);
assert_eq!("754".parse::<GameVersion>().unwrap(), GameVersion::V1_16_5);
assert!("1.12.2".parse::<GameVersion>().is_err());
assert_eq!(GameVersion::V1_8_9.protocol(), 47);
assert_eq!(GameVersion::V1_16_5.protocol(), 754);
}
#[test]
fn utf16_truncation() {
assert_eq!(truncate_utf16("hello", 3), "hel");
assert_eq!(truncate_utf16("hello", 10), "hello");
assert_eq!(truncate_utf16("a\u{1F600}b", 2), "a");
assert_eq!(truncate_utf16("a\u{1F600}b", 3), "a\u{1F600}");
}
}