diff --git a/protocol/src/protocol/mod.rs b/protocol/src/protocol/mod.rs index 33377f53..4da7da6c 100644 --- a/protocol/src/protocol/mod.rs +++ b/protocol/src/protocol/mod.rs @@ -39,9 +39,9 @@ use std::io::{Read, Write}; use std::net::TcpStream; use std::sync::atomic::{AtomicBool, AtomicI32, Ordering}; -pub const SUPPORTED_PROTOCOLS: [i32; 27] = [ - 758, 757, 756, 754, 753, 751, 736, 735, 578, 575, 498, 490, 485, 480, 477, 452, 451, 404, 340, - 316, 315, 210, 109, 107, 74, 47, 5, +pub const SUPPORTED_PROTOCOLS: [i32; 28] = [ + 759, 758, 757, 756, 754, 753, 751, 736, 735, 578, 575, 498, 490, 485, 480, 477, 452, 451, 404, + 340, 316, 315, 210, 109, 107, 74, 47, 5, ]; static CURRENT_PROTOCOL_VERSION: AtomicI32 = AtomicI32::new(SUPPORTED_PROTOCOLS[0]); diff --git a/protocol/src/protocol/packet.rs b/protocol/src/protocol/packet.rs index eacd6928..20befa63 100644 --- a/protocol/src/protocol/packet.rs +++ b/protocol/src/protocol/packet.rs @@ -911,6 +911,13 @@ state_packets!( field nodes: LenPrefixed =, field root_index: VarInt =, } + /// SystemChatMessage is sent by the server for system messages, the system/chat + /// distinction exists to respect the user's chat visibility settings. + packet SystemChatMessage { + field message: format::Component =, + /// Whether the message is an actionbar or chat message + field overlay: bool =, + } /// ServerMessage is a message sent by the server. It could be from a player /// or just a system message. The Type field controls the location the /// message is displayed at and when the message is displayed. @@ -1344,6 +1351,48 @@ state_packets!( } /// JoinGame is sent after completing the login process. This /// sets the initial state for the client. + packet JoinGame_WorldNames_IsHard_SimDist_HasDeath { + /// The entity id the client will be referenced by + field entity_id: i32 =, + /// Whether hardcore mode is enabled + field is_hardcore: bool =, + /// The starting gamemode of the client + field gamemode: u8 =, + /// The previous gamemode of the client + field previous_gamemode: u8 =, + /// Identifiers for all worlds on the server + field world_names: LenPrefixed =, + /// Represents a dimension registry + field registry_codec: Option =, + /// Name of the dimension type being spawned into + field dimension_type: String =, + /// The world being spawned into + field world_name: String =, + /// Truncated SHA-256 hash of world's seed + field hashed_seed: i64 =, + /// The max number of players on the server + field max_players: VarInt =, + /// The render distance (2-32) + field view_distance: VarInt =, + /// The distance the client will process entities + field simulation_distance: VarInt =, + /// Whether the client should reduce the amount of debug + /// information it displays in F3 mode + field reduced_debug_info: bool =, + /// Whether to prompt or immediately respawn + field enable_respawn_screen: bool =, + /// Whether the world is in debug mode + field is_debug: bool =, + /// Whether the world is a superflat world + field is_flat: bool =, + /// If true, then the next two fields are present. + field has_death_location: bool =, + /// Name of the dimension the player died in + field death_dimension_name: String = when(|p: &JoinGame_WorldNames_IsHard_SimDist_HasDeath| p.has_death_location), + /// The location that the player died at + field death_location: Position = when(|p: &JoinGame_WorldNames_IsHard_SimDist_HasDeath| p.has_death_location), + } + packet JoinGame_WorldNames_IsHard_SimDist { /// The entity id the client will be referenced by field entity_id: i32 =, @@ -1878,6 +1927,13 @@ state_packets!( field has_id: bool =, field tab_id: String = when(|p: &SelectAdvancementTab| p.has_id), } + packet ServerData { + field has_motd: bool =, + field motd: Option = when(|p: &ServerData| p.has_motd), + field has_icon: bool =, + field icon: Option = when(|p: &ServerData| p.has_icon), + field previews_chat: bool =, + } packet ActionBar { field text: String =, } @@ -2359,15 +2415,34 @@ state_packets!( } login Login { serverbound Serverbound { - /// LoginStart is sent immeditately after switching into the login + /// LoginStart is sent immediately after switching into the login /// state. The passed username is used by the server to authenticate /// the player in online mode. + packet LoginStart_Sig { + field username: String =, + field has_sign_data: bool =, + field timestamp: Option = when(|p: &LoginStart_Sig| p.has_sign_data), + field public_key: Option> = when(|p: &LoginStart_Sig| p.has_sign_data), + field signature: Option> = when(|p: &LoginStart_Sig| p.has_sign_data), + } packet LoginStart { field username: String =, } /// EncryptionResponse is sent as a reply to EncryptionRequest. All /// packets following this one must be encrypted with AES/CFB8 /// encryption. + packet EncryptionResponse_Sig { + /// The key for the AES/CFB8 cipher encrypted with the + /// public key + field shared_secret: LenPrefixedBytes =, + /// Whether or not the Verify Token should be sent. If not, then the salt and signature will be sent. + field has_verify_token: bool =, + /// The verify token from the request encrypted with the + /// public key + field verify_token: Option> = when(|p: &EncryptionResponse_Sig| p.has_verify_token), + field salt: Option = when(|p: &EncryptionResponse_Sig| !p.has_verify_token), + field signature: Option> = when(|p: &EncryptionResponse_Sig| !p.has_verify_token), + } packet EncryptionResponse { /// The key for the AES/CFB8 cipher encrypted with the /// public key @@ -2414,6 +2489,11 @@ state_packets!( /// LoginSuccess is sent by the server if the player successfully /// authenicates with the session servers (online mode) or straight /// after LoginStart (offline mode). + packet LoginSuccess_Sig { + field uuid: UUID =, + field username: String =, + field properties: LenPrefixed =, + } packet LoginSuccess_String { /// String encoding of a uuid (with hyphens) field uuid: String =, @@ -2491,6 +2571,42 @@ state_packets!( } ); +#[derive(Debug, Default, Clone, PartialEq, Eq)] +pub struct LoginProperty { + pub name: String, + pub value: String, + pub is_signed: bool, + pub signature: Option, +} +impl Serializable for LoginProperty { + fn read_from(buf: &mut R) -> Result { + let name: String = Serializable::read_from(buf)?; + let value: String = Serializable::read_from(buf)?; + let is_signed: bool = Serializable::read_from(buf)?; + let signature: Option = if is_signed { + Some(Serializable::read_from(buf)?) + } else { + None + }; + + Ok(LoginProperty { + name, + value, + is_signed, + signature, + }) + } + + fn write_to(&self, buf: &mut W) -> Result<(), Error> { + self.name.write_to(buf)?; + self.value.write_to(buf)?; + if self.is_signed { + self.signature.as_ref().unwrap().write_to(buf)?; + } + Ok(()) + } +} + #[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct SpawnProperty { pub name: String, @@ -2969,19 +3085,37 @@ impl Serializable for PlayerInfoData { } props.push(prop); } + + let gamemode: VarInt = Serializable::read_from(buf)?; + let ping: VarInt = Serializable::read_from(buf)?; + let display: Option = if bool::read_from(buf)? { + Some(Serializable::read_from(buf)?) + } else { + None + }; + let mut timestamp: Option = None; + let mut public_key: Option> = None; + let mut signature: Option> = None; + + if super::current_protocol_version() >= 759 { + let has_sig_data: bool = Serializable::read_from(buf)?; + if has_sig_data { + timestamp = Some(Serializable::read_from(buf)?); + public_key = Some(Serializable::read_from(buf)?); + signature = Some(Serializable::read_from(buf)?); + } + } + let p = PlayerDetail::Add { uuid, name, properties: props, - gamemode: Serializable::read_from(buf)?, - ping: Serializable::read_from(buf)?, - display: { - if bool::read_from(buf)? { - Some(Serializable::read_from(buf)?) - } else { - None - } - }, + gamemode, + ping, + display, + timestamp, + public_key, + signature, }; m.players.push(p); } @@ -3033,6 +3167,9 @@ pub enum PlayerDetail { gamemode: VarInt, ping: VarInt, display: Option, + timestamp: Option, + public_key: Option>, + signature: Option>, }, UpdateGamemode { uuid: UUID, @@ -3423,6 +3560,12 @@ pub enum CommandProperty { EntitySummon, Dimension, UUID, + ResourceOrTag { + registry: String, + }, + Resource { + registry: String, + }, ForgeModId, ForgeEnum { cls: String, @@ -3456,8 +3599,75 @@ impl Serializable for CommandNode { } else { None }; + let parser: Option = if node_type == CommandNodeType::Argument { - Serializable::read_from(buf)? + if super::current_protocol_version() >= 759 { + let parser_id: VarInt = Serializable::read_from(buf)?; + // https://wiki.vg/Command_Data#Parsers + // "In 1.19, Parsers are identified by a varint id. + // Pre-1.19, parsers are identified by a string id." + // Map ID to string + let parsers = [ + "brigadier:bool", + "brigadier:float", + "brigadier:double", + "brigadier:integer", + "brigadier:long", + "brigadier:string", + "minecraft:entity", + "minecraft:game_profile", + "minecraft:block_pos", + "minecraft:column_pos", + "minecraft:vec3", + "minecraft:vec2", + "minecraft:block_state", + "minecraft:block_predicate", + "minecraft:item_stack", + "minecraft:item_predicate", + "minecraft:color", + "minecraft:component", + "minecraft:message", + "minecraft:nbt", + "minecraft:nbt_tag", + "minecraft:nbt_path", + "minecraft:objective", + "minecraft:objective_criteria", + "minecraft:operation", + "minecraft:particle", + "minecraft:angle", + "minecraft:rotation", + "minecraft:scoreboard_slot", + "minecraft:score_holder", + "minecraft:swizzle", + "minecraft:team", + "minecraft:item_slot", + "minecraft:resource_location", + "minecraft:mob_effect", + "minecraft:function", + "minecraft:entity_anchor", + "minecraft:int_range", + "minecraft:float_range", + "minecraft:item_enchantment", + "minecraft:entity_summon", + "minecraft:dimension", + "minecraft:time", + "minecraft:resource_or_tag", + "minecraft:resource", + "(added in 1.19) Template mirror", + "(added in 1.19) Template rotation", + "minecraft:uuid", + ]; + if parser_id.0 < 0 || parser_id.0 > parsers.len().try_into().unwrap() { + panic!( + "command node identifier out of range {} > {}", + parser_id.0, + parsers.len() + ); + } + Some(parsers[parser_id.0 as usize].to_string()) + } else { + Serializable::read_from(buf)? + } } else { None }; @@ -3556,11 +3766,17 @@ impl Serializable for CommandNode { "minecraft:entity_summon" => CommandProperty::EntitySummon, "minecraft:dimension" => CommandProperty::Dimension, "minecraft:uuid" => CommandProperty::UUID, + "minecraft:resource_or_tag" => CommandProperty::ResourceOrTag { + registry: Serializable::read_from(buf)?, + }, + "minecraft:resource" => CommandProperty::Resource { + registry: Serializable::read_from(buf)?, + }, "forge:modid" => CommandProperty::ForgeModId, "forge:enum" => CommandProperty::ForgeEnum { cls: Serializable::read_from(buf)?, }, - _ => panic!("unsupported command node parser {}", parse), + _ => panic!("unsupported command node parser {:?}", parse), }) } else { None diff --git a/protocol/src/protocol/versions.rs b/protocol/src/protocol/versions.rs index 0bb9b4be..a57e9d59 100644 --- a/protocol/src/protocol/versions.rs +++ b/protocol/src/protocol/versions.rs @@ -18,6 +18,7 @@ mod v1_16_4; mod v1_17_1; mod v1_18_1; mod v1_18_2; +mod v1_19; mod v1_7_10; mod v1_8_9; mod v1_9; @@ -29,6 +30,7 @@ mod v1_9_2; pub fn protocol_name_to_protocol_version(s: String) -> i32 { match s.as_ref() { "" => SUPPORTED_PROTOCOLS[0], + "1.19" => 759, "1.18.2" => 758, "1.18.1" => 757, "1.17.1" => 756, @@ -75,6 +77,7 @@ pub fn translate_internal_packet_id_for_version( to_internal: bool, ) -> i32 { match version { + 759 => v1_19::translate_internal_packet_id(state, dir, id, to_internal), 758 => v1_18_2::translate_internal_packet_id(state, dir, id, to_internal), 757 => v1_18_1::translate_internal_packet_id(state, dir, id, to_internal), 756 => v1_17_1::translate_internal_packet_id(state, dir, id, to_internal), diff --git a/protocol/src/protocol/versions/v1_19.rs b/protocol/src/protocol/versions/v1_19.rs new file mode 100644 index 00000000..0b881292 --- /dev/null +++ b/protocol/src/protocol/versions/v1_19.rs @@ -0,0 +1,194 @@ +protocol_packet_ids!( + handshake Handshaking { + serverbound Serverbound { + 0x00 => Handshake + } + clientbound Clientbound { + } + } + play Play { + serverbound Serverbound { + 0x00 => TeleportConfirm + 0x01 => QueryBlockNBT + 0x02 => SetDifficulty + //TODO 0x03 => ChatCommand + 0x04 => ChatMessage + //TODO 0x05 => ChatPreview + //TODO 0x06 => ClientCommand + 0x07 => ClientSettings_Filtering + 0x08 => TabComplete + 0x09 => ClickWindowButton + 0x0a => ClickWindow_State + 0x0b => CloseWindow + 0x0c => PluginMessageServerbound + 0x0d => EditBook_Pages + 0x0e => QueryEntityNBT + 0x0f => UseEntity_Sneakflag + 0x10 => GenerateStructure + 0x11 => KeepAliveServerbound_i64 + 0x12 => LockDifficulty + 0x13 => PlayerPosition + 0x14 => PlayerPositionLook + 0x15 => PlayerLook + 0x16 => Player + 0x17 => VehicleMove + 0x18 => SteerBoat + 0x19 => PickItem + 0x1a => CraftRecipeRequest + 0x1b => ClientAbilities_u8 + 0x1c => PlayerDigging + 0x1d => PlayerAction + 0x1e => SteerVehicle + 0x1f => WindowPong + 0x20 => SetDisplayedRecipe + 0x21 => SetRecipeBookState + 0x22 => NameItem + 0x23 => ResourcePackStatus + 0x24 => AdvancementTab + 0x25 => SelectTrade + 0x26 => SetBeaconEffect + 0x27 => HeldItemChange + 0x28 => UpdateCommandBlock + 0x29 => UpdateCommandBlockMinecart + 0x2a => CreativeInventoryAction + 0x2b => UpdateJigsawBlock_Joint + 0x2c => UpdateStructureBlock + 0x2d => SetSign + 0x2e => ArmSwing + 0x2f => SpectateTeleport + 0x30 => PlayerBlockPlacement_insideblock + 0x31 => UseItem + } + clientbound Clientbound { + 0x00 => SpawnObject_VarInt + 0x01 => SpawnExperienceOrb + 0x02 => SpawnPlayer_f64_NoMeta + 0x03 => Animation + 0x04 => Statistics + 0x05 => AcknowledgePlayerDigging // TODO: Acknowledge Block Change + 0x06 => BlockBreakAnimation + 0x07 => UpdateBlockEntity_VarInt + 0x08 => BlockAction + 0x09 => BlockChange_VarInt + 0x0a => BossBar + 0x0b => ServerDifficulty_Locked + 0x0c => ServerMessage_Sender // TODO: Chat Preview + 0x0d => ClearTitles + 0x0e => TabCompleteReply + 0x0f => DeclareCommands + 0x10 => WindowClose + 0x11 => WindowItems_StateCarry + 0x12 => WindowProperty + 0x13 => WindowSetSlot_State + 0x14 => SetCooldown + 0x15 => PluginMessageClientbound + 0x16 => NamedSoundEffect + 0x17 => Disconnect + 0x18 => EntityAction + 0x19 => Explosion_VarInt + 0x1a => ChunkUnload + 0x1b => ChangeGameState + 0x1c => WindowOpenHorse + 0x1d => WorldBorderInit + 0x1e => KeepAliveClientbound_i64 + 0x1f => ChunkData_AndLight + 0x20 => Effect + 0x21 => Particle_f64 + 0x22 => UpdateLight_Arrays + 0x23 => JoinGame_WorldNames_IsHard_SimDist_HasDeath + 0x24 => Maps + 0x25 => TradeList_WithRestock + 0x26 => EntityMove_i16 + 0x27 => EntityLookAndMove_i16 + 0x28 => EntityLook_VarInt + 0x29 => VehicleTeleport + 0x2a => OpenBook + 0x2b => WindowOpen_VarInt + 0x2c => SignEditorOpen + 0x2d => WindowPing + 0x2e => CraftRecipeResponse + 0x2f => PlayerAbilities + //0x30 => PlayerChatMessage // TODO + 0x31 => CombatEventEnd + 0x32 => CombatEventEnter + 0x33 => CombatEventDeath + 0x34 => PlayerInfo + 0x35 => FacePlayer + 0x36 => TeleportPlayer_WithDismount + 0x37 => UnlockRecipes_WithBlastSmoker + 0x38 => EntityDestroy + 0x39 => EntityRemoveEffect_VarInt + 0x3a => ResourcePackSend_Prompt + 0x3b => Respawn_NBT + 0x3c => EntityHeadLook + 0x3d => MultiBlockChange_Packed + 0x3e => SelectAdvancementTab + 0x3f => ServerData + 0x40 => ActionBar + 0x41 => WorldBorderCenter + 0x42 => WorldBorderLerpSize + 0x43 => WorldBorderSize + 0x44 => WorldBorderWarningDelay + 0x45 => WorldBorderWarningReach + 0x46 => Camera + 0x47 => SetCurrentHotbarSlot + 0x48 => UpdateViewPosition + 0x49 => UpdateViewDistance + 0x4a => SpawnPosition_Angle + //0x4b => SetDisplayChatPreview // TODO + 0x4c => ScoreboardDisplay + 0x4d => EntityMetadata + 0x4e => EntityAttach + 0x4f => EntityVelocity + 0x50 => EntityEquipment_Array + 0x51 => SetExperience + 0x52 => UpdateHealth + 0x53 => ScoreboardObjective + 0x54 => SetPassengers + 0x55 => Teams_VarInt + 0x56 => UpdateScore_VarInt + 0x57 => UpdateSimulationDistance + 0x58 => TitleSubtitle + 0x59 => TimeUpdate + 0x5a => Title + 0x5b => TitleTimes + 0x5c => EntitySoundEffect + 0x5d => SoundEffect + 0x5e => StopSound + 0x5f => SystemChatMessage + 0x60 => PlayerListHeaderFooter + 0x61 => NBTQueryResponse + 0x62 => CollectItem + 0x63 => EntityTeleport_f64 + 0x64 => Advancements + 0x65 => EntityProperties_VarIntVarInt + 0x66 => EntityEffect_VarInt + 0x67 => DeclareRecipes + 0x68 => Tags_Nested + } + } + login Login { + serverbound Serverbound { + 0x00 => LoginStart_Sig + 0x01 => EncryptionResponse_Sig + 0x02 => LoginPluginResponse + } + clientbound Clientbound { + 0x00 => LoginDisconnect + 0x01 => EncryptionRequest + 0x02 => LoginSuccess_Sig + 0x03 => SetInitialCompression + 0x04 => LoginPluginRequest + } + } + status Status { + serverbound Serverbound { + 0x00 => StatusRequest + 0x01 => StatusPing + } + clientbound Clientbound { + 0x00 => StatusResponse + 0x01 => StatusPong + } + } +); diff --git a/src/server/mod.rs b/src/server/mod.rs index c49d5ebb..a05034d4 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -15,6 +15,7 @@ use crate::ecs; use crate::entity; use crate::format; +use crate::nbt; use crate::protocol::{self, forge, mojang, packet}; use crate::render; use crate::resources; @@ -133,9 +134,19 @@ impl Server { next: protocol::VarInt(2), })?; conn.state = protocol::State::Login; - conn.write_packet(protocol::packet::login::serverbound::LoginStart { - username: profile.username.clone(), - })?; + if protocol_version >= 759 { + conn.write_packet(protocol::packet::login::serverbound::LoginStart_Sig { + username: profile.username.clone(), + has_sign_data: false, + public_key: None, + signature: None, + timestamp: None, + })?; + } else { + conn.write_packet(protocol::packet::login::serverbound::LoginStart { + username: profile.username.clone(), + })?; + } use std::rc::Rc; let (server_id, public_key, verify_token); @@ -190,6 +201,26 @@ impl Server { Some(rx), )); } + protocol::packet::Packet::LoginSuccess_Sig(val) => { + warn!("Server is running in offline mode"); + debug!( + "Login: {} {:?} {:?}", + val.username, val.uuid, val.properties + ); + let mut read = conn.clone(); + let mut write = conn; + read.state = protocol::State::Play; + write.state = protocol::State::Play; + let rx = Self::spawn_reader(read); + return Ok(Server::new( + protocol_version, + forge_mods, + val.uuid, + resources, + Arc::new(RwLock::new(Some(write))), + Some(rx), + )); + } protocol::packet::Packet::LoginDisconnect(val) => { return Err(protocol::Error::Disconnect(val.reason)) } @@ -208,7 +239,17 @@ impl Server { profile.join_server(&server_id, &shared, &public_key)?; } - if protocol_version >= 47 { + if protocol_version >= 759 { + conn.write_packet( + protocol::packet::login::serverbound::EncryptionResponse_Sig { + shared_secret: protocol::LenPrefixedBytes::new(shared_e), + verify_token: Some(protocol::LenPrefixedBytes::new(token_e)), + has_verify_token: true, + salt: None, + signature: None, + }, + )?; + } else if protocol_version >= 47 { conn.write_packet(protocol::packet::login::serverbound::EncryptionResponse { shared_secret: protocol::LenPrefixedBytes::new(shared_e), verify_token: protocol::LenPrefixedBytes::new(token_e), @@ -250,6 +291,16 @@ impl Server { write.state = protocol::State::Play; break; } + protocol::packet::Packet::LoginSuccess_Sig(val) => { + debug!( + "Login: {} {:?} {:?}", + val.username, val.uuid, val.properties + ); + uuid = val.uuid; + read.state = protocol::State::Play; + write.state = protocol::State::Play; + break; + } protocol::packet::Packet::LoginDisconnect(val) => { return Err(protocol::Error::Disconnect(val.reason)) } @@ -584,6 +635,7 @@ impl Server { self pck { PluginMessageClientbound_i16 => on_plugin_message_clientbound_i16, PluginMessageClientbound => on_plugin_message_clientbound_1, + JoinGame_WorldNames_IsHard_SimDist_HasDeath => on_game_join_worldnames_ishard_simdist_hasdeath, JoinGame_WorldNames_IsHard_SimDist => on_game_join_worldnames_ishard_simdist, JoinGame_WorldNames_IsHard => on_game_join_worldnames_ishard, JoinGame_WorldNames => on_game_join_worldnames, @@ -592,6 +644,7 @@ impl Server { JoinGame_i32 => on_game_join_i32, JoinGame_i8 => on_game_join_i8, JoinGame_i8_NoDebug => on_game_join_i8_nodebug, + ServerData => on_server_data, Respawn_Gamemode => on_respawn_gamemode, Respawn_HashedSeed => on_respawn_hashedseed, Respawn_WorldName => on_respawn_worldname, @@ -631,6 +684,7 @@ impl Server { UpdateSign_u16 => on_sign_update_u16, PlayerInfo => on_player_info, PlayerInfo_String => on_player_info_string, + SystemChatMessage => on_system_chat_message, ServerMessage_NoPosition => on_servermessage_noposition, ServerMessage_Position => on_servermessage_position, ServerMessage_Sender => on_servermessage_sender, @@ -1079,6 +1133,45 @@ impl Server { let _ = conn.as_mut().unwrap().write_plugin_message(channel, data); // TODO handle errors } + fn on_game_join_worldnames_ishard_simdist_hasdeath( + &mut self, + join: packet::play::clientbound::JoinGame_WorldNames_IsHard_SimDist_HasDeath, + ) { + let mut found_dimension = false; + if let Some(nbt::NamedTag(_, nbt::Tag::Compound(registries))) = join.registry_codec { + if let Some(nbt::Tag::Compound(dimension_types)) = + registries.get("minecraft:dimension_type") + { + if let Some(nbt::Tag::List(list)) = dimension_types.get("value") { + for item in list { + if let nbt::Tag::Compound(item) = item { + if let Some(nbt::Tag::String(name)) = item.get("name") { + debug!("Dimension {:?} = {:?}", name, item); + if name == &join.world_name { + if let Some(nbt::Tag::Compound(dimension_type)) = + item.get("element") + { + self.world.load_dimension_type_tags(dimension_type); + found_dimension = true; + } + } + } + } + } + } + } + + let _biome_registry = registries.get("minecraft:worldgen/biome"); + let _chat_type_registry = registries.get("minecraft:chat_type"); + } + if !found_dimension { + warn!("Failed to find world name {} in dimension types of JoinGame - expect broken offsets", join.world_name); + //self.world.min_y = -64; + } + + self.on_game_join(join.gamemode, join.entity_id) + } + fn on_game_join_worldnames_ishard_simdist( &mut self, join: packet::play::clientbound::JoinGame_WorldNames_IsHard_SimDist, @@ -1160,6 +1253,13 @@ impl Server { } } + fn on_server_data(&mut self, server_data: packet::play::clientbound::ServerData) { + if let Some(motd) = server_data.motd { + info!("Server MOTD: {:?}", motd); + } + // TODO: show server_data.icon (base64 string) + } + fn on_respawn_hashedseed(&mut self, respawn: packet::play::clientbound::Respawn_HashedSeed) { self.respawn(respawn.gamemode) } @@ -1851,6 +1951,9 @@ impl Server { display, gamemode, ping, + timestamp: _, + public_key: _, + signature: _, } => { let info = self.players.entry(uuid.clone()).or_insert(PlayerInfo { name: name.clone(), @@ -1936,6 +2039,11 @@ impl Server { } } + fn on_system_chat_message(&mut self, m: packet::play::clientbound::SystemChatMessage) { + // TODO: flag as system message (vs chat message) + self.on_servermessage(&m.message, None, None); + } + fn on_servermessage_noposition( &mut self, m: packet::play::clientbound::ServerMessage_NoPosition, @@ -1961,7 +2069,7 @@ impl Server { self.received_chat_at = Some(Instant::now()); } - fn load_block_entities(&mut self, block_entities: Vec>) { + fn load_block_entities(&mut self, block_entities: Vec>) { for block_entity in block_entities.into_iter().flatten() { let x = block_entity.1.get("x").unwrap().as_int().unwrap(); let y = block_entity.1.get("y").unwrap().as_int().unwrap(); diff --git a/src/world/mod.rs b/src/world/mod.rs index 46d2b30f..6f8228b7 100644 --- a/src/world/mod.rs +++ b/src/world/mod.rs @@ -1023,17 +1023,21 @@ impl World { pub fn load_dimension_type(&mut self, dimension_tags: Option) { if let Some(crate::nbt::NamedTag(_, crate::nbt::Tag::Compound(tags))) = dimension_tags { - info!("Dimension type: {:?}", tags); + self.load_dimension_type_tags(&tags); + } + } - if let Some(crate::nbt::Tag::Int(min_y)) = tags.get("min_y") { - self.min_y = *min_y; - } + pub fn load_dimension_type_tags(&mut self, tags: &HashMap) { + info!("Dimension type: {:?}", tags); - if let Some(crate::nbt::Tag::Int(height)) = tags.get("height") { - self.height = *height; - } - // TODO: More tags https://wiki.vg/Protocol#Login_.28play.29 + if let Some(crate::nbt::Tag::Int(min_y)) = tags.get("min_y") { + self.min_y = *min_y; + } + + if let Some(crate::nbt::Tag::Int(height)) = tags.get("height") { + self.height = *height; } + // TODO: More tags https://wiki.vg/Protocol#Login_.28play.29 } #[allow(clippy::or_fun_call)]