// Copyright 2015 Matthew Collins // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use crate::protocol::{self, mojang, packet}; use crate::world; use crate::world::block; use rand::{self, Rng}; use std::sync::{Arc, RwLock}; use std::sync::mpsc; use std::thread; use std::collections::HashMap; use std::hash::BuildHasherDefault; use crate::types::hash::FNVHash; use crate::resources; use crate::render; use crate::settings::Stevenkey; use crate::ecs; use crate::entity; use cgmath::prelude::*; use crate::types::Gamemode; use crate::shared::{Axis, Position}; use crate::format; use rsa_public_encrypt_pkcs1; use log::{error, debug, warn}; mod sun; pub mod plugin_messages; pub mod target; pub struct Server { uuid: protocol::UUID, conn: Option, protocol_version: i32, read_queue: Option>>, pub disconnect_reason: Option, just_disconnected: bool, pub world: world::World, pub entities: ecs::Manager, world_age: i64, world_time: f64, world_time_target: f64, tick_time: bool, resources: Arc>, version: usize, // Entity accessors game_info: ecs::Key, player_movement: ecs::Key, gravity: ecs::Key, position: ecs::Key, target_position: ecs::Key, velocity: ecs::Key, gamemode: ecs::Key, pub rotation: ecs::Key, target_rotation: ecs::Key, // pub player: Option, entity_map: HashMap>, players: HashMap>, tick_timer: f64, entity_tick_timer: f64, sun_model: Option, target_info: target::Info, } pub struct PlayerInfo { name: String, uuid: protocol::UUID, skin_url: Option, display_name: Option, ping: i32, gamemode: Gamemode, } macro_rules! handle_packet { ($s:ident $pck:ident { $($packet:ident => $func:ident,)* }) => ( match $pck { $( protocol::packet::Packet::$packet(val) => $s.$func(val), )* _ => {}, } ) } impl Server { pub fn connect(resources: Arc>, profile: mojang::Profile, address: &str, protocol_version: i32) -> Result { let mut conn = protocol::Conn::new(address, protocol_version)?; let host = conn.host.clone(); let port = conn.port; conn.write_packet(protocol::packet::handshake::serverbound::Handshake { protocol_version: protocol::VarInt(protocol_version), host, port, next: protocol::VarInt(2), })?; conn.state = protocol::State::Login; conn.write_packet(protocol::packet::login::serverbound::LoginStart { username: profile.username.clone(), })?; use std::rc::Rc; let (server_id, public_key, verify_token); loop { match conn.read_packet()? { protocol::packet::Packet::SetInitialCompression(val) => { conn.set_compresssion(val.threshold.0); }, protocol::packet::Packet::EncryptionRequest(val) => { server_id = Rc::new(val.server_id); public_key = Rc::new(val.public_key.data); verify_token = Rc::new(val.verify_token.data); break; }, protocol::packet::Packet::EncryptionRequest_i16(val) => { server_id = Rc::new(val.server_id); public_key = Rc::new(val.public_key.data); verify_token = Rc::new(val.verify_token.data); break; }, protocol::packet::Packet::LoginSuccess(val) => { warn!("Server is running in offline mode"); debug!("Login: {} {}", val.username, val.uuid); let mut read = conn.clone(); let mut write = conn.clone(); read.state = protocol::State::Play; write.state = protocol::State::Play; let rx = Self::spawn_reader(read); return Ok(Server::new(protocol_version, protocol::UUID::from_str(&val.uuid), resources, Some(write), Some(rx))); } protocol::packet::Packet::LoginDisconnect(val) => return Err(protocol::Error::Disconnect(val.reason)), val => return Err(protocol::Error::Err(format!("Wrong packet: {:?}", val))), }; } let mut shared = [0; 16]; // TODO: is this cryptographically secure enough? rand::thread_rng().fill(&mut shared); let shared_e = rsa_public_encrypt_pkcs1::encrypt(&public_key, &shared).unwrap(); let token_e = rsa_public_encrypt_pkcs1::encrypt(&public_key, &verify_token).unwrap(); #[cfg(not(target_arch = "wasm32"))] { profile.join_server(&server_id, &shared, &public_key)?; } 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), })?; } else { conn.write_packet(protocol::packet::login::serverbound::EncryptionResponse_i16 { shared_secret: protocol::LenPrefixedBytes::new(shared_e), verify_token: protocol::LenPrefixedBytes::new(token_e), })?; } let mut read = conn.clone(); let mut write = conn.clone(); read.enable_encyption(&shared, true); write.enable_encyption(&shared, false); let uuid; loop { match read.read_packet()? { protocol::packet::Packet::SetInitialCompression(val) => { read.set_compresssion(val.threshold.0); write.set_compresssion(val.threshold.0); } protocol::packet::Packet::LoginSuccess(val) => { debug!("Login: {} {}", val.username, val.uuid); 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)), val => return Err(protocol::Error::Err(format!("Wrong packet: {:?}", val))), } } let rx = Self::spawn_reader(read); Ok(Server::new(protocol_version, protocol::UUID::from_str(&uuid), resources, Some(write), Some(rx))) } fn spawn_reader(mut read: protocol::Conn) -> mpsc::Receiver> { let (tx, rx) = mpsc::channel(); thread::spawn(move || { loop { let pck = read.read_packet(); let was_error = pck.is_err(); if let Err(_) = tx.send(pck) { return; } if was_error { return; } } }); rx } pub fn dummy_server(resources: Arc>) -> Server { let mut server = Server::new(protocol::SUPPORTED_PROTOCOLS[0], protocol::UUID::default(), resources, None, None); let mut rng = rand::thread_rng(); for x in -7*16 .. 7*16 { for z in -7*16 .. 7*16 { let h = 5 + (6.0 * (x as f64 / 16.0).cos() * (z as f64 / 16.0).sin()) as i32; for y in 0 .. h { server.world.set_block(Position::new(x, y, z), block::Dirt{ snowy: false, variant: block::DirtVariant::Normal }); } server.world.set_block(Position::new(x, h, z), block::Grass{ snowy: false }); if x*x + z*z > 16*16 && rng.gen_bool(1.0 / 80.0) { for i in 0 .. 5 { server.world.set_block(Position::new(x, h + 1 + i, z), block::Log{ axis: Axis::Y, variant: block::TreeVariant::Oak }); } for xx in -2 .. 3 { for zz in -2 .. 3 { if xx == 0 && z == 0 { continue; } server.world.set_block(Position::new(x + xx, h + 3, z + zz), block::Leaves{ variant: block::TreeVariant::Oak, check_decay: false, decayable: false, distance: 1 }); server.world.set_block(Position::new(x + xx, h + 4, z + zz), block::Leaves{ variant: block::TreeVariant::Oak, check_decay: false, decayable: false, distance: 1 }); if xx.abs() <= 1 && zz.abs() <= 1 { server.world.set_block(Position::new(x + xx, h + 5, z + zz), block::Leaves{ variant: block::TreeVariant::Oak, check_decay: false, decayable: false, distance: 1 }); } if xx * xx + zz * zz <= 1 { server.world.set_block(Position::new(x + xx, h + 6, z + zz), block::Leaves{ variant: block::TreeVariant::Oak, check_decay: false, decayable: false, distance: 1 }); } } } } } } server } fn new( protocol_version: i32, uuid: protocol::UUID, resources: Arc>, conn: Option, read_queue: Option>> ) -> Server { let mut entities = ecs::Manager::new(); entity::add_systems(&mut entities); let world_entity = entities.get_world(); let game_info = entities.get_key(); entities.add_component(world_entity, game_info, entity::GameInfo::new()); let version = resources.read().unwrap().version(); Server { uuid, conn, protocol_version, read_queue, disconnect_reason: None, just_disconnected: false, world: world::World::new(protocol_version), world_age: 0, world_time: 0.0, world_time_target: 0.0, tick_time: true, version, resources, // Entity accessors game_info, player_movement: entities.get_key(), gravity: entities.get_key(), position: entities.get_key(), target_position: entities.get_key(), velocity: entities.get_key(), gamemode: entities.get_key(), rotation: entities.get_key(), target_rotation: entities.get_key(), // entities, player: None, entity_map: HashMap::with_hasher(BuildHasherDefault::default()), players: HashMap::with_hasher(BuildHasherDefault::default()), tick_timer: 0.0, entity_tick_timer: 0.0, sun_model: None, target_info: target::Info::new(), } } pub fn disconnect(&mut self, reason: Option) { self.conn = None; self.disconnect_reason = reason; if let Some(player) = self.player.take() { self.entities.remove_entity(player); } self.just_disconnected = true; } pub fn is_connected(&self) -> bool { self.conn.is_some() } pub fn tick(&mut self, renderer: &mut render::Renderer, delta: f64) { let version = self.resources.read().unwrap().version(); if version != self.version { self.version = version; self.world.flag_dirty_all(); } // TODO: Check if the world type actually needs a sun if self.sun_model.is_none() { self.sun_model = Some(sun::SunModel::new(renderer)); } // Copy to camera if let Some(player) = self.player { let position = self.entities.get_component(player, self.position).unwrap(); let rotation = self.entities.get_component(player, self.rotation).unwrap(); renderer.camera.pos = cgmath::Point3::from_vec(position.position + cgmath::Vector3::new(0.0, 1.62, 0.0)); renderer.camera.yaw = rotation.yaw; renderer.camera.pitch = rotation.pitch; } self.entity_tick(renderer, delta); self.tick_timer += delta; while self.tick_timer >= 3.0 && self.is_connected() { self.minecraft_tick(); self.tick_timer -= 3.0; } self.update_time(renderer, delta); if let Some(sun_model) = self.sun_model.as_mut() { sun_model.tick(renderer, self.world_time, self.world_age); } self.world.tick(&mut self.entities); if self.player.is_some() { if let Some((pos, bl, _, _)) = target::trace_ray(&self.world, 4.0, renderer.camera.pos.to_vec(), renderer.view_vector.cast().unwrap(), target::test_block) { self.target_info.update(renderer, pos, bl); } else { self.target_info.clear(renderer); } } else { self.target_info.clear(renderer); } } fn entity_tick(&mut self, renderer: &mut render::Renderer, delta: f64) { let world_entity = self.entities.get_world(); // Update the game's state for entities to read self.entities.get_component_mut(world_entity, self.game_info) .unwrap().delta = delta; // Packets modify entities so need to handled here if let Some(rx) = self.read_queue.take() { while let Ok(pck) = rx.try_recv() { match pck { Ok(pck) => handle_packet!{ self pck { JoinGame_i32 => on_game_join_i32, JoinGame_i8 => on_game_join_i8, JoinGame_i8_NoDebug => on_game_join_i8_nodebug, Respawn => on_respawn, KeepAliveClientbound_i64 => on_keep_alive_i64, KeepAliveClientbound_VarInt => on_keep_alive_varint, KeepAliveClientbound_i32 => on_keep_alive_i32, ChunkData => on_chunk_data, ChunkData_HeightMap => on_chunk_data_heightmap, ChunkData_NoEntities => on_chunk_data_no_entities, ChunkData_NoEntities_u16 => on_chunk_data_no_entities_u16, ChunkData_17 => on_chunk_data_17, ChunkDataBulk => on_chunk_data_bulk, ChunkDataBulk_17 => on_chunk_data_bulk_17, ChunkUnload => on_chunk_unload, BlockChange_VarInt => on_block_change_varint, BlockChange_u8 => on_block_change_u8, MultiBlockChange_VarInt => on_multi_block_change_varint, MultiBlockChange_u16 => on_multi_block_change_u16, TeleportPlayer_WithConfirm => on_teleport_player_withconfirm, TeleportPlayer_NoConfirm => on_teleport_player_noconfirm, TimeUpdate => on_time_update, ChangeGameState => on_game_state_change, UpdateBlockEntity => on_block_entity_update, UpdateBlockEntity_Data => on_block_entity_update_data, UpdateSign => on_sign_update, UpdateSign_u16 => on_sign_update_u16, PlayerInfo => on_player_info, PlayerInfo_String => on_player_info_string, Disconnect => on_disconnect, // Entities EntityDestroy => on_entity_destroy, EntityDestroy_u8 => on_entity_destroy_u8, SpawnPlayer_f64 => on_player_spawn_f64, SpawnPlayer_i32 => on_player_spawn_i32, SpawnPlayer_i32_HeldItem => on_player_spawn_i32_helditem, SpawnPlayer_i32_HeldItem_String => on_player_spawn_i32_helditem_string, EntityTeleport_f64 => on_entity_teleport_f64, EntityTeleport_i32 => on_entity_teleport_i32, EntityTeleport_i32_i32_NoGround => on_entity_teleport_i32_i32_noground, EntityMove_i16 => on_entity_move_i16, EntityMove_i8 => on_entity_move_i8, EntityMove_i8_i32_NoGround => on_entity_move_i8_i32_noground, EntityLook_VarInt => on_entity_look_varint, EntityLook_i32_NoGround => on_entity_look_i32_noground, EntityLookAndMove_i16 => on_entity_look_and_move_i16, EntityLookAndMove_i8 => on_entity_look_and_move_i8, EntityLookAndMove_i8_i32_NoGround => on_entity_look_and_move_i8_i32_noground, } }, Err(err) => panic!("Err: {:?}", err), } // Disconnected if self.conn.is_none() { break; } } if self.conn.is_some() { self.read_queue = Some(rx); } } if self.is_connected() || self.just_disconnected { // Allow an extra tick when disconnected to clean up self.just_disconnected = false; self.entity_tick_timer += delta; while self.entity_tick_timer >= 3.0 { self.entities.tick(&mut self.world, renderer); self.entity_tick_timer -= 3.0; } self.entities.render_tick(&mut self.world, renderer); } } pub fn remove(&mut self, renderer: &mut render::Renderer) { self.entities.remove_all_entities(&mut self.world, renderer); if let Some(mut sun_model) = self.sun_model.take() { sun_model.remove(renderer); } self.target_info.clear(renderer); } fn update_time(&mut self, renderer: &mut render::Renderer, delta: f64) { if self.tick_time { self.world_time_target += delta / 3.0; self.world_time_target = (24000.0 + self.world_time_target) % 24000.0; let mut diff = self.world_time_target - self.world_time; if diff < -12000.0 { diff += 24000.0 } else if diff > 12000.0 { diff -= 24000.0 } self.world_time += diff * (1.5 / 60.0) * delta; self.world_time = (24000.0 + self.world_time) % 24000.0; } else { self.world_time = self.world_time_target; } renderer.sky_offset = self.calculate_sky_offset(); } fn calculate_sky_offset(&self) -> f32 { use std::f32::consts::PI; let mut offset = ((1.0 + self.world_time as f32) / 24000.0) - 0.25; if offset < 0.0 { offset += 1.0; } else if offset > 1.0 { offset -= 1.0; } let prev_offset = offset; offset = 1.0 - (((offset * PI).cos() + 1.0) / 2.0); offset = prev_offset + (offset - prev_offset) / 3.0; offset = 1.0 - ((offset * PI * 2.0).cos() * 2.0 + 0.2); if offset > 1.0 { offset = 1.0; } else if offset < 0.0 { offset = 0.0; } offset = 1.0 - offset; offset * 0.8 + 0.2 } pub fn minecraft_tick(&mut self) { use std::f32::consts::PI; if let Some(player) = self.player { let movement = self.entities.get_component_mut(player, self.player_movement).unwrap(); let on_ground = self.entities.get_component(player, self.gravity).map_or(false, |v| v.on_ground); let position = self.entities.get_component(player, self.target_position).unwrap(); let rotation = self.entities.get_component(player, self.rotation).unwrap(); // Force the server to know when touched the ground // otherwise if it happens between ticks the server // will think we are flying. let on_ground = if movement.did_touch_ground { movement.did_touch_ground = false; true } else { on_ground }; // Sync our position to the server // Use the smaller packets when possible if self.protocol_version >= 47 { let packet = packet::play::serverbound::PlayerPositionLook { x: position.position.x, y: position.position.y, z: position.position.z, yaw: -(rotation.yaw as f32) * (180.0 / PI), pitch: (-rotation.pitch as f32) * (180.0 / PI) + 180.0, on_ground, }; self.write_packet(packet); } else { let packet = packet::play::serverbound::PlayerPositionLook_HeadY { x: position.position.x, feet_y: position.position.y - 1.62, head_y: position.position.y, z: position.position.z, yaw: -(rotation.yaw as f32) * (180.0 / PI), pitch: (-rotation.pitch as f32) * (180.0 / PI) + 180.0, on_ground, }; self.write_packet(packet); } } } pub fn key_press(&mut self, down: bool, key: Stevenkey) { if let Some(player) = self.player { if let Some(movement) = self.entities.get_component_mut(player, self.player_movement) { movement.pressed_keys.insert(key, down); } } } pub fn on_right_click(&mut self, renderer: &mut render::Renderer) { use crate::shared::Direction; if self.player.is_some() { if let Some((pos, _, face, at)) = target::trace_ray(&self.world, 4.0, renderer.camera.pos.to_vec(), renderer.view_vector.cast().unwrap(), target::test_block) { if self.protocol_version >= 315 { self.write_packet(packet::play::serverbound::PlayerBlockPlacement_f32 { location: pos, face: protocol::VarInt(match face { Direction::Down => 0, Direction::Up => 1, Direction::North => 2, Direction::South => 3, Direction::West => 4, Direction::East => 5, _ => unreachable!(), }), hand: protocol::VarInt(0), cursor_x: at.x as f32, cursor_y: at.y as f32, cursor_z: at.z as f32, }); } else if self.protocol_version >= 49 { self.write_packet(packet::play::serverbound::PlayerBlockPlacement_u8 { location: pos, face: protocol::VarInt(match face { Direction::Down => 0, Direction::Up => 1, Direction::North => 2, Direction::South => 3, Direction::West => 4, Direction::East => 5, _ => unreachable!(), }), hand: protocol::VarInt(0), cursor_x: (at.x * 16.0) as u8, cursor_y: (at.y * 16.0) as u8, cursor_z: (at.z * 16.0) as u8, }); } else if self.protocol_version >= 47 { self.write_packet(packet::play::serverbound::PlayerBlockPlacement_u8_Item { location: pos, face: match face { Direction::Down => 0, Direction::Up => 1, Direction::North => 2, Direction::South => 3, Direction::West => 4, Direction::East => 5, _ => unreachable!(), }, hand: None, cursor_x: (at.x * 16.0) as u8, cursor_y: (at.y * 16.0) as u8, cursor_z: (at.z * 16.0) as u8, }); } else { self.write_packet(packet::play::serverbound::PlayerBlockPlacement_u8_Item_u8y { x: pos.x, y: pos.y as u8, z: pos.x, face: match face { Direction::Down => 0, Direction::Up => 1, Direction::North => 2, Direction::South => 3, Direction::West => 4, Direction::East => 5, _ => unreachable!(), }, hand: None, cursor_x: (at.x * 16.0) as u8, cursor_y: (at.y * 16.0) as u8, cursor_z: (at.z * 16.0) as u8, }); } } } } pub fn write_packet(&mut self, p: T) { let _ = self.conn.as_mut().unwrap().write_packet(p); // TODO handle errors } fn on_keep_alive_i64(&mut self, keep_alive: packet::play::clientbound::KeepAliveClientbound_i64) { self.write_packet(packet::play::serverbound::KeepAliveServerbound_i64 { id: keep_alive.id, }); } fn on_keep_alive_varint(&mut self, keep_alive: packet::play::clientbound::KeepAliveClientbound_VarInt) { self.write_packet(packet::play::serverbound::KeepAliveServerbound_VarInt { id: keep_alive.id, }); } fn on_keep_alive_i32(&mut self, keep_alive: packet::play::clientbound::KeepAliveClientbound_i32) { self.write_packet(packet::play::serverbound::KeepAliveServerbound_i32 { id: keep_alive.id, }); } fn on_game_join_i32(&mut self, join: packet::play::clientbound::JoinGame_i32) { self.on_game_join(join.gamemode, join.entity_id) } fn on_game_join_i8(&mut self, join: packet::play::clientbound::JoinGame_i8) { self.on_game_join(join.gamemode, join.entity_id) } fn on_game_join_i8_nodebug(&mut self, join: packet::play::clientbound::JoinGame_i8_NoDebug) { self.on_game_join(join.gamemode, join.entity_id) } fn on_game_join(&mut self, gamemode: u8, entity_id: i32) { let gamemode = Gamemode::from_int((gamemode & 0x7) as i32); let player = entity::player::create_local(&mut self.entities); if let Some(info) = self.players.get(&self.uuid) { let model = self.entities.get_component_mut_direct::(player).unwrap(); model.set_skin(info.skin_url.clone()); } *self.entities.get_component_mut(player, self.gamemode).unwrap() = gamemode; // TODO: Temp self.entities.get_component_mut(player, self.player_movement).unwrap().flying = gamemode.can_fly(); self.entity_map.insert(entity_id, player); self.player = Some(player); // Let the server know who we are let brand = plugin_messages::Brand { brand: "Steven".into(), }; if self.protocol_version >= 47 { self.write_packet(brand.as_message()); } else { self.write_packet(brand.as_message17()); } } fn on_respawn(&mut self, respawn: packet::play::clientbound::Respawn) { self.world = world::World::new(self.protocol_version); let gamemode = Gamemode::from_int((respawn.gamemode & 0x7) as i32); if let Some(player) = self.player { *self.entities.get_component_mut(player, self.gamemode).unwrap() = gamemode; // TODO: Temp self.entities.get_component_mut(player, self.player_movement).unwrap().flying = gamemode.can_fly(); } } fn on_disconnect(&mut self, disconnect: packet::play::clientbound::Disconnect) { self.disconnect(Some(disconnect.reason)); } fn on_time_update(&mut self, time_update: packet::play::clientbound::TimeUpdate) { self.world_age = time_update.time_of_day; self.world_time_target = (time_update.time_of_day % 24000) as f64; if self.world_time_target < 0.0 { self.world_time_target = -self.world_time_target; self.tick_time = false; } else { self.tick_time = true; } } fn on_game_state_change(&mut self, game_state: packet::play::clientbound::ChangeGameState) { if game_state.reason == 3 { if let Some(player) = self.player { let gamemode = Gamemode::from_int(game_state.value as i32); *self.entities.get_component_mut(player, self.gamemode).unwrap() = gamemode; // TODO: Temp self.entities.get_component_mut(player, self.player_movement).unwrap().flying = gamemode.can_fly(); } } } fn on_entity_destroy(&mut self, entity_destroy: packet::play::clientbound::EntityDestroy) { for id in entity_destroy.entity_ids.data { if let Some(entity) = self.entity_map.remove(&id.0) { self.entities.remove_entity(entity); } } } fn on_entity_destroy_u8(&mut self, entity_destroy: packet::play::clientbound::EntityDestroy_u8) { for id in entity_destroy.entity_ids.data { if let Some(entity) = self.entity_map.remove(&id) { self.entities.remove_entity(entity); } } } fn on_entity_teleport_f64(&mut self, entity_telport: packet::play::clientbound::EntityTeleport_f64) { self.on_entity_teleport(entity_telport.entity_id.0, entity_telport.x, entity_telport.y, entity_telport.z, entity_telport.yaw as f64, entity_telport.pitch as f64, entity_telport.on_ground) } fn on_entity_teleport_i32(&mut self, entity_telport: packet::play::clientbound::EntityTeleport_i32) { self.on_entity_teleport(entity_telport.entity_id.0, entity_telport.x as f64, entity_telport.y as f64, entity_telport.z as f64, entity_telport.yaw as f64, entity_telport.pitch as f64, entity_telport.on_ground) } fn on_entity_teleport_i32_i32_noground(&mut self, entity_telport: packet::play::clientbound::EntityTeleport_i32_i32_NoGround) { let on_ground = true; // TODO: how is this supposed to be set? (for 1.7) self.on_entity_teleport(entity_telport.entity_id, entity_telport.x as f64, entity_telport.y as f64, entity_telport.z as f64, entity_telport.yaw as f64, entity_telport.pitch as f64, on_ground) } fn on_entity_teleport(&mut self, entity_id: i32, x: f64, y: f64, z: f64, yaw: f64, pitch: f64, _on_ground: bool) { use std::f64::consts::PI; if let Some(entity) = self.entity_map.get(&entity_id) { let target_position = self.entities.get_component_mut(*entity, self.target_position).unwrap(); let target_rotation = self.entities.get_component_mut(*entity, self.target_rotation).unwrap(); target_position.position.x = x; target_position.position.y = y; target_position.position.z = z; target_rotation.yaw = -(yaw / 256.0) * PI * 2.0; target_rotation.pitch = -(pitch / 256.0) * PI * 2.0; } } fn on_entity_move_i16(&mut self, m: packet::play::clientbound::EntityMove_i16) { self.on_entity_move(m.entity_id.0, m.delta_x as f64, m.delta_y as f64, m.delta_z as f64) } fn on_entity_move_i8(&mut self, m: packet::play::clientbound::EntityMove_i8) { self.on_entity_move(m.entity_id.0, m.delta_x as f64, m.delta_y as f64, m.delta_z as f64) } fn on_entity_move_i8_i32_noground(&mut self, m: packet::play::clientbound::EntityMove_i8_i32_NoGround) { self.on_entity_move(m.entity_id, m.delta_x as f64, m.delta_y as f64, m.delta_z as f64) } fn on_entity_move(&mut self, entity_id: i32, delta_x: f64, delta_y: f64, delta_z: f64) { if let Some(entity) = self.entity_map.get(&entity_id) { let position = self.entities.get_component_mut(*entity, self.target_position).unwrap(); position.position.x += delta_x / (32.0 * 128.0); position.position.y += delta_y / (32.0 * 128.0); position.position.z += delta_z / (32.0 * 128.0); } } fn on_entity_look(&mut self, entity_id: i32, yaw: f64, pitch: f64) { use std::f64::consts::PI; if let Some(entity) = self.entity_map.get(&entity_id) { let rotation = self.entities.get_component_mut(*entity, self.target_rotation).unwrap(); rotation.yaw = -(yaw / 256.0) * PI * 2.0; rotation.pitch = -(pitch / 256.0) * PI * 2.0; } } fn on_entity_look_varint(&mut self, look: packet::play::clientbound::EntityLook_VarInt) { self.on_entity_look(look.entity_id.0, look.yaw as f64, look.pitch as f64) } fn on_entity_look_i32_noground(&mut self, look: packet::play::clientbound::EntityLook_i32_NoGround) { self.on_entity_look(look.entity_id, look.yaw as f64, look.pitch as f64) } fn on_entity_look_and_move_i16(&mut self, lookmove: packet::play::clientbound::EntityLookAndMove_i16) { self.on_entity_look_and_move(lookmove.entity_id.0, lookmove.delta_x as f64, lookmove.delta_y as f64, lookmove.delta_z as f64, lookmove.yaw as f64, lookmove.pitch as f64) } fn on_entity_look_and_move_i8(&mut self, lookmove: packet::play::clientbound::EntityLookAndMove_i8) { self.on_entity_look_and_move(lookmove.entity_id.0, lookmove.delta_x as f64, lookmove.delta_y as f64, lookmove.delta_z as f64, lookmove.yaw as f64, lookmove.pitch as f64) } fn on_entity_look_and_move_i8_i32_noground(&mut self, lookmove: packet::play::clientbound::EntityLookAndMove_i8_i32_NoGround) { self.on_entity_look_and_move(lookmove.entity_id, lookmove.delta_x as f64, lookmove.delta_y as f64, lookmove.delta_z as f64, lookmove.yaw as f64, lookmove.pitch as f64) } fn on_entity_look_and_move(&mut self, entity_id: i32, delta_x: f64, delta_y: f64, delta_z: f64, yaw: f64, pitch: f64) { use std::f64::consts::PI; if let Some(entity) = self.entity_map.get(&entity_id) { let position = self.entities.get_component_mut(*entity, self.target_position).unwrap(); let rotation = self.entities.get_component_mut(*entity, self.target_rotation).unwrap(); position.position.x += delta_x / (32.0 * 128.0); position.position.y += delta_y / (32.0 * 128.0); position.position.z += delta_z / (32.0 * 128.0); rotation.yaw = -(yaw / 256.0) * PI * 2.0; rotation.pitch = -(pitch / 256.0) * PI * 2.0; } } fn on_player_spawn_f64(&mut self, spawn: packet::play::clientbound::SpawnPlayer_f64) { self.on_player_spawn(spawn.entity_id.0, spawn.uuid, spawn.x, spawn.y, spawn.z, spawn.yaw as f64, spawn.pitch as f64) } fn on_player_spawn_i32(&mut self, spawn: packet::play::clientbound::SpawnPlayer_i32) { self.on_player_spawn(spawn.entity_id.0, spawn.uuid, spawn.x as f64, spawn.y as f64, spawn.z as f64, spawn.yaw as f64, spawn.pitch as f64) } fn on_player_spawn_i32_helditem(&mut self, spawn: packet::play::clientbound::SpawnPlayer_i32_HeldItem) { self.on_player_spawn(spawn.entity_id.0, spawn.uuid, spawn.x as f64, spawn.y as f64, spawn.z as f64, spawn.yaw as f64, spawn.pitch as f64) } fn on_player_spawn_i32_helditem_string(&mut self, spawn: packet::play::clientbound::SpawnPlayer_i32_HeldItem_String) { self.on_player_spawn(spawn.entity_id.0, protocol::UUID::from_str(&spawn.uuid), spawn.x as f64, spawn.y as f64, spawn.z as f64, spawn.yaw as f64, spawn.pitch as f64) } fn on_player_spawn(&mut self, entity_id: i32, uuid: protocol::UUID, x: f64, y: f64, z: f64, pitch: f64, yaw: f64) { use std::f64::consts::PI; if let Some(entity) = self.entity_map.remove(&entity_id) { self.entities.remove_entity(entity); } let entity = entity::player::create_remote(&mut self.entities, self.players.get(&uuid).map_or("MISSING", |v| &v.name)); let position = self.entities.get_component_mut(entity, self.position).unwrap(); let target_position = self.entities.get_component_mut(entity, self.target_position).unwrap(); let rotation = self.entities.get_component_mut(entity, self.rotation).unwrap(); let target_rotation = self.entities.get_component_mut(entity, self.target_rotation).unwrap(); position.position.x = x; position.position.y = y; position.position.z = z; target_position.position.x = x; target_position.position.y = y; target_position.position.z = z; rotation.yaw = -(yaw / 256.0) * PI * 2.0; rotation.pitch = -(pitch / 256.0) * PI * 2.0; target_rotation.yaw = rotation.yaw; target_rotation.pitch = rotation.pitch; if let Some(info) = self.players.get(&uuid) { let model = self.entities.get_component_mut_direct::(entity).unwrap(); model.set_skin(info.skin_url.clone()); } self.entity_map.insert(entity_id, entity); } fn on_teleport_player_withconfirm(&mut self, teleport: packet::play::clientbound::TeleportPlayer_WithConfirm) { self.on_teleport_player(teleport.x, teleport.y, teleport.z, teleport.yaw as f64, teleport.pitch as f64, teleport.flags, Some(teleport.teleport_id)) } fn on_teleport_player_noconfirm(&mut self, teleport: packet::play::clientbound::TeleportPlayer_NoConfirm) { self.on_teleport_player(teleport.x, teleport.y, teleport.z, teleport.yaw as f64, teleport.pitch as f64, teleport.flags, None) } fn on_teleport_player(&mut self, x: f64, y: f64, z: f64, yaw: f64, pitch: f64, flags: u8, teleport_id: Option) { use std::f64::consts::PI; if let Some(player) = self.player { let position = self.entities.get_component_mut(player, self.target_position).unwrap(); let rotation = self.entities.get_component_mut(player, self.rotation).unwrap(); let velocity = self.entities.get_component_mut(player, self.velocity).unwrap(); position.position.x = calculate_relative_teleport(TeleportFlag::RelX, flags, position.position.x, x); position.position.y = calculate_relative_teleport(TeleportFlag::RelY, flags, position.position.y, y); position.position.z = calculate_relative_teleport(TeleportFlag::RelZ, flags, position.position.z, z); rotation.yaw = calculate_relative_teleport(TeleportFlag::RelYaw, flags, rotation.yaw, -yaw as f64 * (PI / 180.0)); rotation.pitch = -((calculate_relative_teleport( TeleportFlag::RelPitch, flags, (-rotation.pitch) * (180.0 / PI) + 180.0, pitch ) - 180.0) * (PI / 180.0)); if (flags & (TeleportFlag::RelX as u8)) == 0 { velocity.velocity.x = 0.0; } if (flags & (TeleportFlag::RelY as u8)) == 0 { velocity.velocity.y = 0.0; } if (flags & (TeleportFlag::RelZ as u8)) == 0 { velocity.velocity.z = 0.0; } if let Some(teleport_id) = teleport_id { self.write_packet(packet::play::serverbound::TeleportConfirm { teleport_id, }); } } } fn on_block_entity_update(&mut self, block_update: packet::play::clientbound::UpdateBlockEntity) { match block_update.nbt { None => { // NBT is null, so we need to remove the block entity self.world.add_block_entity_action(world::BlockEntityAction::Remove( block_update.location, )); }, Some(nbt) => { if block_update.action == 9 { use crate::format; let line1 = format::Component::from_string(nbt.1.get("Text1").unwrap().as_str().unwrap()); let line2 = format::Component::from_string(nbt.1.get("Text2").unwrap().as_str().unwrap()); let line3 = format::Component::from_string(nbt.1.get("Text3").unwrap().as_str().unwrap()); let line4 = format::Component::from_string(nbt.1.get("Text4").unwrap().as_str().unwrap()); self.world.add_block_entity_action(world::BlockEntityAction::UpdateSignText( block_update.location, line1, line2, line3, line4, )); } } } } fn on_block_entity_update_data(&mut self, _block_update: packet::play::clientbound::UpdateBlockEntity_Data) { // TODO: handle UpdateBlockEntity_Data for 1.7, decompress gzipped_nbt } fn on_sign_update(&mut self, mut update_sign: packet::play::clientbound::UpdateSign) { use crate::format; format::convert_legacy(&mut update_sign.line1); format::convert_legacy(&mut update_sign.line2); format::convert_legacy(&mut update_sign.line3); format::convert_legacy(&mut update_sign.line4); self.world.add_block_entity_action(world::BlockEntityAction::UpdateSignText( update_sign.location, update_sign.line1, update_sign.line2, update_sign.line3, update_sign.line4, )); } fn on_sign_update_u16(&mut self, mut update_sign: packet::play::clientbound::UpdateSign_u16) { use crate::format; format::convert_legacy(&mut update_sign.line1); format::convert_legacy(&mut update_sign.line2); format::convert_legacy(&mut update_sign.line3); format::convert_legacy(&mut update_sign.line4); self.world.add_block_entity_action(world::BlockEntityAction::UpdateSignText( Position::new(update_sign.x, update_sign.y as i32, update_sign.z), update_sign.line1, update_sign.line2, update_sign.line3, update_sign.line4, )); } fn on_player_info_string(&mut self, _player_info: packet::play::clientbound::PlayerInfo_String) { // TODO: support PlayerInfo_String for 1.7 } fn on_player_info(&mut self, player_info: packet::play::clientbound::PlayerInfo) { use crate::protocol::packet::PlayerDetail::*; use base64; use serde_json; for detail in player_info.inner.players { match detail { Add { name, uuid, properties, display, gamemode, ping} => { let info = self.players.entry(uuid.clone()).or_insert(PlayerInfo { name: name.clone(), uuid, skin_url: None, display_name: display.clone(), ping: ping.0, gamemode: Gamemode::from_int(gamemode.0), }); // Re-set the props of the player in case of dodgy server implementations info.name = name; info.display_name = display; info.ping = ping.0; info.gamemode = Gamemode::from_int(gamemode.0); for prop in properties { if prop.name != "textures" { continue; } // Ideally we would check the signature of the blob to // verify it was from Mojang and not faked by the server // but this requires the public key which is distributed // authlib. We could download authlib on startup and extract // the key but this seems like overkill compared to just // whitelisting Mojang's texture servers instead. let skin_blob_result = &base64::decode(&prop.value); let skin_blob = match skin_blob_result { Ok(val) => val, Err(err) => { error!("Failed to decode skin blob, {:?}", err); continue; }, }; let skin_blob: serde_json::Value = match serde_json::from_slice(&skin_blob) { Ok(val) => val, Err(err) => { error!("Failed to parse skin blob, {:?}", err); continue; }, }; if let Some(skin_url) = skin_blob.pointer("/textures/SKIN/url").and_then(|v| v.as_str()) { info.skin_url = Some(skin_url.to_owned()); } } // Refresh our own skin when the server sends it to us. // The join game packet can come before this packet meaning // we may not have the skin in time for spawning ourselves. // This isn't an issue for other players because this packet // must come before the spawn player packet. if info.uuid == self.uuid { let model = self.entities.get_component_mut_direct::(self.player.unwrap()).unwrap(); model.set_skin(info.skin_url.clone()); } }, UpdateGamemode { uuid, gamemode } => { if let Some(info) = self.players.get_mut(&uuid) { info.gamemode = Gamemode::from_int(gamemode.0); } }, UpdateLatency { uuid, ping } => { if let Some(info) = self.players.get_mut(&uuid) { info.ping = ping.0; } }, UpdateDisplayName { uuid, display } => { if let Some(info) = self.players.get_mut(&uuid) { info.display_name = display; } }, Remove { uuid } => { self.players.remove(&uuid); }, } } } fn load_block_entities(&mut self, block_entities: Vec>) { for optional_block_entity in block_entities { if let Some(block_entity) = optional_block_entity { let x = block_entity.1.get("x").unwrap().as_int().unwrap(); let y = block_entity.1.get("y").unwrap().as_int().unwrap(); let z = block_entity.1.get("z").unwrap().as_int().unwrap(); let tile_id = block_entity.1.get("id").unwrap().as_str().unwrap(); let action; match tile_id { // Fake a sign update "Sign" => action = 9, // Not something we care about, so break the loop _ => continue, } self.on_block_entity_update(packet::play::clientbound::UpdateBlockEntity { location: Position::new(x, y, z), action, nbt: Some(block_entity.clone()), }); } } } fn on_chunk_data(&mut self, chunk_data: packet::play::clientbound::ChunkData) { self.world.load_chunk19( chunk_data.chunk_x, chunk_data.chunk_z, chunk_data.new, chunk_data.bitmask.0 as u16, chunk_data.data.data ).unwrap(); self.load_block_entities(chunk_data.block_entities.data); } fn on_chunk_data_heightmap(&mut self, chunk_data: packet::play::clientbound::ChunkData_HeightMap) { self.world.load_chunk19( chunk_data.chunk_x, chunk_data.chunk_z, chunk_data.new, chunk_data.bitmask.0 as u16, chunk_data.data.data ).unwrap(); self.load_block_entities(chunk_data.block_entities.data); } fn on_chunk_data_no_entities(&mut self, chunk_data: packet::play::clientbound::ChunkData_NoEntities) { self.world.load_chunk19( chunk_data.chunk_x, chunk_data.chunk_z, chunk_data.new, chunk_data.bitmask.0 as u16, chunk_data.data.data ).unwrap(); } fn on_chunk_data_no_entities_u16(&mut self, chunk_data: packet::play::clientbound::ChunkData_NoEntities_u16) { let chunk_meta = vec![crate::protocol::packet::ChunkMeta { x: chunk_data.chunk_x, z: chunk_data.chunk_z, bitmask: chunk_data.bitmask, }]; let skylight = false; self.world.load_chunks18(chunk_data.new, skylight, &chunk_meta, chunk_data.data.data).unwrap(); } fn on_chunk_data_17(&mut self, chunk_data: packet::play::clientbound::ChunkData_17) { self.world.load_chunk17(chunk_data.chunk_x, chunk_data.chunk_z, chunk_data.new, chunk_data.bitmask, chunk_data.add_bitmask, chunk_data.compressed_data.data).unwrap(); } fn on_chunk_data_bulk(&mut self, bulk: packet::play::clientbound::ChunkDataBulk) { let new = true; self.world.load_chunks18(new, bulk.skylight, &bulk.chunk_meta.data, bulk.chunk_data.to_vec()).unwrap(); } fn on_chunk_data_bulk_17(&mut self, bulk: packet::play::clientbound::ChunkDataBulk_17) { self.world.load_chunks17(bulk.chunk_column_count, bulk.data_length, bulk.skylight, &bulk.chunk_data_and_meta).unwrap(); } fn on_chunk_unload(&mut self, chunk_unload: packet::play::clientbound::ChunkUnload) { self.world.unload_chunk(chunk_unload.x, chunk_unload.z, &mut self.entities); } fn on_block_change(&mut self, location: Position, id: i32) { self.world.set_block(location, block::Block::by_vanilla_id(id as usize, self.protocol_version)) } fn on_block_change_varint(&mut self, block_change: packet::play::clientbound::BlockChange_VarInt) { self.on_block_change(block_change.location, block_change.block_id.0) } fn on_block_change_u8(&mut self, block_change: packet::play::clientbound::BlockChange_u8) { self.on_block_change( crate::shared::Position::new(block_change.x, block_change.y as i32, block_change.z), (block_change.block_id.0 << 4) | (block_change.block_metadata as i32) ); } fn on_multi_block_change_varint(&mut self, block_change: packet::play::clientbound::MultiBlockChange_VarInt) { let ox = block_change.chunk_x << 4; let oz = block_change.chunk_z << 4; for record in block_change.records.data { self.world.set_block( Position::new( ox + (record.xz >> 4) as i32, record.y as i32, oz + (record.xz & 0xF) as i32 ), block::Block::by_vanilla_id(record.block_id.0 as usize, self.protocol_version) ); } } fn on_multi_block_change_u16(&mut self, block_change: packet::play::clientbound::MultiBlockChange_u16) { let ox = block_change.chunk_x << 4; let oz = block_change.chunk_z << 4; let mut data = std::io::Cursor::new(block_change.data); for _ in 0 .. block_change.record_count { use byteorder::{BigEndian, ReadBytesExt}; let record = data.read_u32::().unwrap(); let id = record & 0x0000_ffff; let y = ((record & 0x00ff_0000) >> 16) as i32; let z = oz + ((record & 0x0f00_0000) >> 24) as i32; let x = ox + ((record & 0xf000_0000) >> 28) as i32; self.world.set_block( Position::new(x, y, z), block::Block::by_vanilla_id(id as usize, self.protocol_version) ); } } } #[derive(Debug, Clone, Copy)] enum TeleportFlag { RelX = 0b00001, RelY = 0b00010, RelZ = 0b00100, RelYaw = 0b01000, RelPitch = 0b10000, } fn calculate_relative_teleport(flag: TeleportFlag, flags: u8, base: f64, val: f64) -> f64 { if (flags & (flag as u8)) == 0 { val } else { base + val } }