1use bytes::BytesMut;
4use ghast_protocol::types::ByteString;
5use ghast_protocol::v1_16_5::{handshaking, login, play};
6use ghast_protocol::{
7 DecodeError, EncodeError, FrameEncoder, McWrite, PacketFilter, PacketSet, RawPacket,
8};
9
10use super::{ClientVersion, GameVersion, V1_16_5, teleport_target, truncate_utf16};
11use crate::common::{
12 Abilities, ChatMessage, ChatPosition, EntityAction, GameMode, Health, Inbound, JoinGame,
13 LoginInbound, MovePacket, Outbound, Pose, Teleport, Vec3,
14};
15use crate::interest::Interests;
16
17use play::clientbound as cb;
18use play::serverbound as sb;
19
20const COMBAT_ENTITY_DEAD: i32 = 2;
22
23impl ClientVersion for V1_16_5 {
24 const GAME: GameVersion = GameVersion::V1_16_5;
25 const CHAT_LIMIT: usize = 256;
26
27 fn write_handshake(enc: &mut FrameEncoder, host: &str, port: u16) -> Result<(), EncodeError> {
28 enc.write_packet(&handshaking::serverbound::SetProtocol {
29 protocol_version: 754,
30 server_host: ByteString::from(host),
31 server_port: port,
32 next_state: 2,
33 })
34 }
35
36 fn write_login_start(enc: &mut FrameEncoder, username: &str) -> Result<(), EncodeError> {
37 enc.write_packet(&login::serverbound::LoginStart {
38 username: ByteString::from(username),
39 })
40 }
41
42 fn write_encryption_response(
43 enc: &mut FrameEncoder,
44 shared_secret: &[u8],
45 verify_token: &[u8],
46 ) -> Result<(), EncodeError> {
47 enc.write_packet(&login::serverbound::EncryptionBegin {
48 shared_secret: bytes::Bytes::copy_from_slice(shared_secret),
49 verify_token: bytes::Bytes::copy_from_slice(verify_token),
50 })
51 }
52
53 fn write_plugin_response(enc: &mut FrameEncoder, message_id: i32) -> Result<(), EncodeError> {
54 enc.write_packet(&login::serverbound::LoginPluginResponse {
56 message_id,
57 data: None,
58 })
59 }
60
61 fn decode_login(raw: &RawPacket) -> Result<Option<LoginInbound>, DecodeError> {
62 use login::clientbound as lc;
63 Ok(Some(match raw.id {
64 lc::id::DISCONNECT => {
65 LoginInbound::Disconnect(raw.decode_as::<lc::Disconnect>()?.reason)
66 }
67 lc::id::ENCRYPTION_BEGIN => {
68 let p: lc::EncryptionBegin = raw.decode_as()?;
69 LoginInbound::EncryptionRequest {
70 server_id: p.server_id,
71 public_key: p.public_key,
72 verify_token: p.verify_token,
73 }
74 }
75 lc::id::SUCCESS => {
76 let p: lc::Success = raw.decode_as()?;
77 LoginInbound::Success {
78 uuid: Some(p.uuid),
79 username: p.username,
80 }
81 }
82 lc::id::COMPRESS => LoginInbound::Compress(raw.decode_as::<lc::Compress>()?.threshold),
83 lc::id::LOGIN_PLUGIN_REQUEST => {
84 let p: lc::LoginPluginRequest = raw.decode_as()?;
85 LoginInbound::PluginRequest {
86 message_id: p.message_id,
87 channel: p.channel,
88 }
89 }
90 _ => return Ok(None),
91 }))
92 }
93
94 fn play_filter(interests: Interests) -> PacketFilter {
95 let mut f = PacketFilter::none()
96 .with(cb::id::KEEP_ALIVE)
97 .with(cb::id::LOGIN)
98 .with(cb::id::UPDATE_HEALTH)
99 .with(cb::id::RESPAWN)
100 .with(cb::id::POSITION)
101 .with(cb::id::HELD_ITEM_SLOT)
102 .with(cb::id::GAME_STATE_CHANGE)
103 .with(cb::id::OPEN_WINDOW)
104 .with(cb::id::OPEN_HORSE_WINDOW)
105 .with(cb::id::CLOSE_WINDOW)
106 .with(cb::id::TRANSACTION)
107 .with(cb::id::WINDOW_ITEMS)
108 .with(cb::id::SET_SLOT)
109 .with(cb::id::CRAFT_PROGRESS_BAR)
110 .with(cb::id::ABILITIES)
111 .with(cb::id::KICK_DISCONNECT)
112 .with(cb::id::COMBAT_EVENT)
113 .with(cb::id::ENTITY_STATUS)
114 .with(cb::id::ENTITY_UPDATE_ATTRIBUTES)
116 .with(cb::id::ENTITY_EFFECT)
117 .with(cb::id::REMOVE_ENTITY_EFFECT)
118 .with(cb::id::ENTITY_VELOCITY);
120 for id in ghast_world::v1_16_5::PACKET_IDS {
123 f.insert(id);
124 }
125 if interests.contains(Interests::CHAT) {
126 f.insert(cb::id::CHAT);
127 }
128 f
129 }
130
131 fn decode_play(raw: &RawPacket) -> Result<Option<Inbound>, DecodeError> {
132 Ok(Some(match raw.id {
133 cb::id::KEEP_ALIVE => {
134 Inbound::KeepAlive(raw.decode_as::<cb::KeepAlive>()?.keep_alive_id)
135 }
136 cb::id::LOGIN => {
137 let p: cb::Login = raw.decode_as()?;
138 Inbound::JoinGame(JoinGame {
139 entity_id: p.entity_id,
140 game_mode: GameMode::from_wire(p.game_mode),
141 dimension: p.world_name.to_string(),
142 hardcore: p.is_hardcore,
143 dimension_nbt: Some((p.dimension_codec, p.dimension)),
144 respawn_screen: p.enable_respawn_screen,
145 })
146 }
147 cb::id::CHAT => {
148 let p: cb::Chat = raw.decode_as()?;
149 Inbound::Chat(ChatMessage {
150 json: p.message,
151 position: ChatPosition::from_wire(p.position),
152 sender: (!p.sender.is_nil()).then_some(p.sender),
153 })
154 }
155 cb::id::UPDATE_HEALTH => {
156 let p: cb::UpdateHealth = raw.decode_as()?;
157 Inbound::Health(Health {
158 health: p.health,
159 food: p.food,
160 saturation: p.food_saturation,
161 })
162 }
163 cb::id::RESPAWN => {
164 let p: cb::Respawn = raw.decode_as()?;
165 Inbound::Respawn {
166 dimension: p.world_name.to_string(),
167 game_mode: GameMode::from_wire(p.gamemode),
168 }
169 }
170 cb::id::POSITION => {
171 let p: cb::Position = raw.decode_as()?;
172 Inbound::Teleport(Teleport {
173 x: p.x,
174 y: p.y,
175 z: p.z,
176 yaw: p.yaw,
177 pitch: p.pitch,
178 flags: p.flags,
179 teleport_id: Some(p.teleport_id),
180 })
181 }
182 cb::id::HELD_ITEM_SLOT => Inbound::HeldSlot(raw.decode_as::<cb::HeldItemSlot>()?.slot),
183 cb::id::GAME_STATE_CHANGE => {
184 let p: cb::GameStateChange = raw.decode_as()?;
185 Inbound::GameState {
186 reason: p.reason.0,
187 value: p.game_mode,
188 }
189 }
190 cb::id::OPEN_WINDOW => {
191 Inbound::OpenWindow(raw.decode_as::<cb::OpenWindow>()?.window_id)
192 }
193 cb::id::OPEN_HORSE_WINDOW => {
194 Inbound::OpenWindow(i32::from(raw.decode_as::<cb::OpenHorseWindow>()?.window_id))
195 }
196 cb::id::CLOSE_WINDOW => {
197 Inbound::CloseWindow(i32::from(raw.decode_as::<cb::CloseWindow>()?.window_id))
198 }
199 cb::id::TRANSACTION => {
200 let p: cb::Transaction = raw.decode_as()?;
201 Inbound::Transaction {
202 window_id: p.window_id,
203 action: p.action,
204 accepted: p.accepted,
205 }
206 }
207 cb::id::ABILITIES => {
208 let p: cb::Abilities = raw.decode_as()?;
209 Inbound::Abilities(Abilities {
210 flags: p.flags,
211 flying_speed: p.flying_speed,
212 walking_speed: p.walking_speed,
213 })
214 }
215 cb::id::KICK_DISCONNECT => {
216 Inbound::Disconnect(raw.decode_as::<cb::KickDisconnect>()?.reason)
217 }
218 cb::id::ENTITY_VELOCITY => {
219 let p: cb::EntityVelocity = raw.decode_as()?;
220 Inbound::Velocity {
221 entity_id: p.entity_id,
222 motion: Vec3::new(
223 f64::from(p.velocity.x) / 8000.0,
224 f64::from(p.velocity.y) / 8000.0,
225 f64::from(p.velocity.z) / 8000.0,
226 ),
227 }
228 }
229 cb::id::EXPLOSION => {
230 let p: cb::Explosion = raw.decode_as()?;
231 Inbound::ExplosionPush(Vec3::new(
232 f64::from(p.player_motion_x),
233 f64::from(p.player_motion_y),
234 f64::from(p.player_motion_z),
235 ))
236 }
237 cb::id::ENTITY_UPDATE_ATTRIBUTES => {
238 let p: cb::EntityUpdateAttributes = raw.decode_as()?;
239 let Some(speed) = p.properties.iter().find(|a| {
240 a.key.ends_with("generic.movementSpeed")
241 || a.key.ends_with("generic.movement_speed")
242 }) else {
243 return Ok(None);
244 };
245 Inbound::MovementSpeed {
246 entity_id: p.entity_id,
247 base: speed.value,
248 modifiers: speed
249 .modifiers
250 .iter()
251 .map(|m| (m.uuid, m.amount, m.operation))
252 .collect(),
253 }
254 }
255 cb::id::ENTITY_EFFECT => {
256 let p: cb::EntityEffect = raw.decode_as()?;
257 Inbound::Effect {
258 entity_id: p.entity_id,
259 id: p.effect_id,
260 amplifier: p.amplifier,
261 }
262 }
263 cb::id::REMOVE_ENTITY_EFFECT => {
264 let p: cb::RemoveEntityEffect = raw.decode_as()?;
265 Inbound::EffectRemoved {
266 entity_id: p.entity_id,
267 id: p.effect_id,
268 }
269 }
270 cb::id::ENTITY_STATUS => {
271 let p: cb::EntityStatus = raw.decode_as()?;
272 Inbound::EntityStatus {
273 entity_id: p.entity_id,
274 status: p.entity_status,
275 }
276 }
277 cb::id::COMBAT_EVENT => {
278 let p: cb::CombatEvent = raw.decode_as()?;
279 match (p.event, p.player_id) {
280 (COMBAT_ENTITY_DEAD, Some(entity_id)) => Inbound::Died { entity_id },
281 _ => return Ok(None),
282 }
283 }
284 _ => return Ok(None),
285 }))
286 }
287
288 const INVENTORY: ghast_inventory::Version = ghast_inventory::Version::V1_16_5;
289
290 fn inventory_packet(
291 inv: &mut ghast_inventory::Inventory,
292 raw: &RawPacket,
293 ) -> Result<Option<ghast_inventory::Outgoing>, DecodeError> {
294 match raw.id {
295 cb::id::LOGIN
296 | cb::id::RESPAWN
297 | cb::id::OPEN_WINDOW
298 | cb::id::CLOSE_WINDOW
299 | cb::id::WINDOW_ITEMS
300 | cb::id::SET_SLOT
301 | cb::id::TRANSACTION
302 | cb::id::HELD_ITEM_SLOT
303 | cb::id::CRAFT_PROGRESS_BAR
304 | cb::id::OPEN_HORSE_WINDOW => {
305 let p = cb::ClientboundPlayPacket::decode_raw(raw)?;
306 Ok(inv.apply_v1_16_5(&p))
307 }
308 _ => Ok(None),
309 }
310 }
311
312 fn write_play(enc: &mut FrameEncoder, out: &Outbound) -> Result<(), EncodeError> {
313 match out {
314 Outbound::KeepAlive(id) => enc.write_packet(&sb::KeepAlive { keep_alive_id: *id }),
315 Outbound::TeleportConfirm(teleport_id) => enc.write_packet(&sb::TeleportConfirm {
316 teleport_id: *teleport_id,
317 }),
318 Outbound::Move(m) => match *m {
319 MovePacket::Flying { on_ground } => enc.write_packet(&sb::Flying { on_ground }),
320 MovePacket::Position { pos, on_ground } => enc.write_packet(&sb::Position {
321 x: pos.x,
322 y: pos.y,
323 z: pos.z,
324 on_ground,
325 }),
326 MovePacket::Look {
327 yaw,
328 pitch,
329 on_ground,
330 } => enc.write_packet(&sb::Look {
331 yaw,
332 pitch,
333 on_ground,
334 }),
335 MovePacket::PositionLook {
336 pos,
337 yaw,
338 pitch,
339 on_ground,
340 } => enc.write_packet(&sb::PositionLook {
341 x: pos.x,
342 y: pos.y,
343 z: pos.z,
344 yaw,
345 pitch,
346 on_ground,
347 }),
348 },
349 Outbound::EntityAction { entity_id, action } => {
350 let action_id = match action {
351 EntityAction::StartSneaking => sb::EntityActionActionId::START_SNEAKING,
352 EntityAction::StopSneaking => sb::EntityActionActionId::STOP_SNEAKING,
353 EntityAction::StartSprinting => sb::EntityActionActionId::START_SPRINTING,
354 EntityAction::StopSprinting => sb::EntityActionActionId::STOP_SPRINTING,
355 };
356 enc.write_packet(&sb::EntityAction {
357 entity_id: *entity_id,
358 action_id,
359 jump_boost: 0,
360 })
361 }
362 Outbound::Chat(text) => enc.write_packet(&sb::Chat {
363 message: ByteString::from(truncate_utf16(text, Self::CHAT_LIMIT)),
364 }),
365 Outbound::Respawn => enc.write_packet(&sb::ClientCommand {
366 action_id: sb::ClientCommandActionId::PERFORM_RESPAWN,
367 }),
368 Outbound::Transaction {
369 window_id,
370 action,
371 accepted,
372 } => enc.write_packet(&sb::Transaction {
373 window_id: *window_id,
374 action: *action,
375 accepted: *accepted,
376 }),
377 Outbound::Abilities { flags, .. } => enc.write_packet(&sb::Abilities { flags: *flags }),
378 Outbound::Inventory(o) => match o.to_v1_16_5() {
379 Some(p) => enc.write_set(&p),
380 None => Ok(()),
381 },
382 Outbound::UseEntity {
383 target,
384 action,
385 off_hand,
386 sneaking,
387 } => {
388 let (mouse, at) = match action {
389 crate::common::EntityUse::Interact => (0, None),
390 crate::common::EntityUse::Attack => (1, None),
391 crate::common::EntityUse::InteractAt(v) => (2, Some(*v)),
392 };
393 enc.write_packet(&sb::UseEntity {
394 target: *target,
395 mouse,
396 x: at.map(|v| v.x as f32),
397 y: at.map(|v| v.y as f32),
398 z: at.map(|v| v.z as f32),
399 hand: match action {
400 crate::common::EntityUse::Attack => None,
401 _ => Some(i32::from(*off_hand)),
402 },
403 sneaking: *sneaking,
404 })
405 }
406 Outbound::HeldSlot(slot) => enc.write_packet(&sb::HeldItemSlot { slot_id: *slot }),
407 Outbound::Swing => enc.write_packet(&sb::ArmAnimation { hand: 0 }),
408 Outbound::Dig { status, pos, face } => enc.write_packet(&sb::BlockDig {
409 status: status.id(),
410 location: *pos,
411 face: face.id(),
412 }),
413 Outbound::Place {
414 pos,
415 face,
416 cursor,
417 off_hand,
418 ..
419 } => enc.write_packet(&sb::BlockPlace {
420 hand: i32::from(*off_hand),
421 location: *pos,
422 direction: i32::from(face.id()),
423 cursor_x: cursor.x as f32,
424 cursor_y: cursor.y as f32,
425 cursor_z: cursor.z as f32,
426 inside_block: false,
427 }),
428 Outbound::UseItem { off_hand, .. } => enc.write_packet(&sb::UseItem {
429 hand: i32::from(*off_hand),
430 }),
431 Outbound::SwingOffHand => enc.write_packet(&sb::ArmAnimation { hand: 1 }),
432 Outbound::Settings(s) => enc.write_packet(&sb::Settings {
433 locale: ByteString::from(s.locale.to_ascii_lowercase()),
435 view_distance: s.view_distance,
436 chat_flags: i32::from(s.chat_mode),
437 chat_colors: s.chat_colors,
438 skin_parts: s.skin_parts,
439 main_hand: s.main_hand,
440 }),
441 Outbound::Brand(brand) => {
442 let mut data = BytesMut::with_capacity(brand.len() + 1);
443 data.put_string(brand)?;
444 enc.write_packet(&sb::CustomPayload {
445 channel: ByteString::from_static("minecraft:brand"),
446 data: data.freeze(),
447 })
448 }
449 }
450 }
451
452 fn apply_teleport(pose: &mut Pose, t: &Teleport) {
453 let (yaw, pitch) = teleport_target(pose, t);
454 pose.pos.x = pose.pos.x.clamp(-3.0e7, 3.0e7);
457 pose.pos.z = pose.pos.z.clamp(-3.0e7, 3.0e7);
458 pose.yaw = yaw % 360.0;
459 pose.pitch = pitch.clamp(-90.0, 90.0) % 360.0;
460 }
461}
462
463impl super::HasSim for V1_16_5 {
464 type Sim = crate::sim::Sim16;
465 fn new_sim(opts: &crate::sim::SimOptions) -> Self::Sim {
466 crate::sim::Sim16::with(opts)
467 }
468}