ghast_client/version/
mod.rs1use std::fmt;
8use std::str::FromStr;
9
10use ghast_protocol::{DecodeError, EncodeError, FrameEncoder, PacketFilter, RawPacket, Version};
11
12use crate::common::{Inbound, LoginInbound, Outbound, Pose, Teleport};
13use crate::interest::Interests;
14
15mod v1_16_5;
16mod v1_8;
17
18pub use ghast_protocol::v1_8::V1_8;
19pub use ghast_protocol::v1_16_5::V1_16_5;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23#[non_exhaustive]
24pub enum GameVersion {
25 V1_8_9,
27 V1_16_5,
29}
30
31impl GameVersion {
32 pub const ALL: [GameVersion; 2] = [GameVersion::V1_8_9, GameVersion::V1_16_5];
34
35 #[must_use]
37 pub const fn protocol(self) -> i32 {
38 match self {
39 Self::V1_8_9 => <V1_8 as Version>::PROTOCOL,
40 Self::V1_16_5 => <V1_16_5 as Version>::PROTOCOL,
41 }
42 }
43
44 #[must_use]
46 pub const fn name(self) -> &'static str {
47 match self {
48 Self::V1_8_9 => "1.8.9",
49 Self::V1_16_5 => "1.16.5",
50 }
51 }
52
53 #[must_use]
56 pub fn eye_height(self) -> f64 {
57 f64::from(1.62_f32)
58 }
59
60 #[must_use]
63 pub fn sneak_eye_height(self) -> f64 {
64 match self {
65 Self::V1_8_9 => f64::from(1.62_f32 - 0.08_f32),
67 Self::V1_16_5 => f64::from(1.27_f32),
68 }
69 }
70}
71
72impl fmt::Display for GameVersion {
73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74 f.write_str(self.name())
75 }
76}
77
78#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
80#[error("unsupported Minecraft version `{0}` (supported: 1.8.9, 1.16.5)")]
81pub struct UnknownVersion(pub String);
82
83impl FromStr for GameVersion {
84 type Err = UnknownVersion;
85
86 fn from_str(s: &str) -> Result<Self, Self::Err> {
88 match s.trim() {
89 "1.8" | "1.8.0" | "1.8.1" | "1.8.2" | "1.8.3" | "1.8.4" | "1.8.5" | "1.8.6"
90 | "1.8.7" | "1.8.8" | "1.8.9" | "47" => Ok(Self::V1_8_9),
91 "1.16.4" | "1.16.5" | "754" => Ok(Self::V1_16_5),
92 other => Err(UnknownVersion(other.to_owned())),
93 }
94 }
95}
96
97pub trait ClientVersion: Version + sealed::Sealed {
101 const GAME: GameVersion;
103 const CHAT_LIMIT: usize;
105
106 fn write_handshake(enc: &mut FrameEncoder, host: &str, port: u16) -> Result<(), EncodeError>;
108 fn write_login_start(enc: &mut FrameEncoder, username: &str) -> Result<(), EncodeError>;
110 fn write_encryption_response(
112 enc: &mut FrameEncoder,
113 shared_secret: &[u8],
114 verify_token: &[u8],
115 ) -> Result<(), EncodeError>;
116 fn write_plugin_response(enc: &mut FrameEncoder, message_id: i32) -> Result<(), EncodeError>;
118 fn decode_login(raw: &RawPacket) -> Result<Option<LoginInbound>, DecodeError>;
120
121 fn play_filter(interests: Interests) -> PacketFilter;
124 fn decode_play(raw: &RawPacket) -> Result<Option<Inbound>, DecodeError>;
126 fn inventory_packet(
130 inv: &mut ghast_inventory::Inventory,
131 raw: &RawPacket,
132 ) -> Result<Option<ghast_inventory::Outgoing>, DecodeError>;
133 const INVENTORY: ghast_inventory::Version;
135 fn write_play(enc: &mut FrameEncoder, out: &Outbound) -> Result<(), EncodeError>;
137 fn apply_teleport(pose: &mut Pose, t: &Teleport);
140}
141
142pub(crate) trait HasSim {
144 type Sim: crate::sim::Sim;
146 fn new_sim(opts: &crate::sim::SimOptions) -> Self::Sim;
148}
149
150mod sealed {
151 #[allow(unnameable_types, reason = "sealed trait pattern")]
153 pub trait Sealed {}
154 impl Sealed for super::V1_8 {}
155 impl Sealed for super::V1_16_5 {}
156}
157
158pub(crate) fn truncate_utf16(s: &str, max: usize) -> &str {
161 let mut units = 0;
162 for (i, c) in s.char_indices() {
163 units += c.len_utf16();
164 if units > max {
165 return &s[..i];
166 }
167 }
168 s
169}
170
171pub(crate) fn teleport_target(pose: &mut Pose, t: &Teleport) -> (f32, f32) {
175 use crate::common::teleport_flags as fl;
176 let mut x = t.x;
177 let mut y = t.y;
178 let mut z = t.z;
179 if t.flags & fl::X != 0 {
180 x += pose.pos.x;
181 } else {
182 pose.motion.x = 0.0;
183 }
184 if t.flags & fl::Y != 0 {
185 y += pose.pos.y;
186 } else {
187 pose.motion.y = 0.0;
188 }
189 if t.flags & fl::Z != 0 {
190 z += pose.pos.z;
191 } else {
192 pose.motion.z = 0.0;
193 }
194 pose.pos = crate::common::Vec3::new(x, y, z);
195 let mut yaw = t.yaw;
196 let mut pitch = t.pitch;
197 if t.flags & fl::PITCH != 0 {
198 pitch += pose.pitch;
199 }
200 if t.flags & fl::YAW != 0 {
201 yaw += pose.yaw;
202 }
203 (yaw, pitch)
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn parse_versions() {
212 assert_eq!("1.8.8".parse::<GameVersion>().unwrap(), GameVersion::V1_8_9);
213 assert_eq!("754".parse::<GameVersion>().unwrap(), GameVersion::V1_16_5);
214 assert!("1.12.2".parse::<GameVersion>().is_err());
215 assert_eq!(GameVersion::V1_8_9.protocol(), 47);
216 assert_eq!(GameVersion::V1_16_5.protocol(), 754);
217 }
218
219 #[test]
220 fn utf16_truncation() {
221 assert_eq!(truncate_utf16("hello", 3), "hel");
222 assert_eq!(truncate_utf16("hello", 10), "hello");
223 assert_eq!(truncate_utf16("a\u{1F600}b", 2), "a");
225 assert_eq!(truncate_utf16("a\u{1F600}b", 3), "a\u{1F600}");
226 }
227}