From c117f28b2ae898dfd466f1bb63f89732684a0a16 Mon Sep 17 00:00:00 2001 From: Thinkofname Date: Mon, 4 Apr 2016 22:08:24 +0100 Subject: [PATCH] Block entity support, implement signs --- blocks/src/lib.rs | 6 +- shared/src/position.rs | 2 +- src/ecs/mod.rs | 17 ++- src/entity/block_entity/mod.rs | 32 ++++ src/entity/block_entity/sign.rs | 263 ++++++++++++++++++++++++++++++++ src/entity/mod.rs | 3 + src/render/model.rs | 4 +- src/server/mod.rs | 15 +- src/types/bit/set.rs | 8 +- src/world/mod.rs | 106 +++++++++++-- 10 files changed, 434 insertions(+), 22 deletions(-) create mode 100644 src/entity/block_entity/mod.rs create mode 100644 src/entity/block_entity/sign.rs diff --git a/blocks/src/lib.rs b/blocks/src/lib.rs index 55cae75..b0cae9d 100644 --- a/blocks/src/lib.rs +++ b/blocks/src/lib.rs @@ -1303,7 +1303,7 @@ define_blocks! { ], }, data Some(rotation.data()), - material material::NON_SOLID, + material material::INVISIBLE, model { ("minecraft", "standing_sign") }, collision vec![], } @@ -1412,7 +1412,7 @@ define_blocks! { _ => 2, }) }, - material material::NON_SOLID, + material material::INVISIBLE, model { ("minecraft", "wall_sign") }, variant format!("facing={}", facing.as_string()), collision vec![], @@ -5344,7 +5344,7 @@ impl Rotation { } } - fn data(&self) -> usize { + pub fn data(&self) -> usize { match *self { Rotation::South => 0, Rotation::SouthSouthWest => 1, diff --git a/shared/src/position.rs b/shared/src/position.rs index 408ac4a..cd450ac 100644 --- a/shared/src/position.rs +++ b/shared/src/position.rs @@ -3,7 +3,7 @@ use std::fmt; use direction::Direction; use std::ops; -#[derive(Clone, Copy)] +#[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct Position { pub x: i32, pub y: i32, diff --git a/src/ecs/mod.rs b/src/ecs/mod.rs index d4e1362..65045de 100644 --- a/src/ecs/mod.rs +++ b/src/ecs/mod.rs @@ -170,7 +170,13 @@ impl Manager { let changes = self.changed_entity_components.clone(); self.changed_entity_components = HashSet::with_hasher(BuildHasherDefault::default()); for entity in changes { - let state = self.entities[entity.id].0.as_mut().unwrap().clone(); + let (cur, state) = { + let state = self.entities[entity.id].0.as_mut().unwrap(); + let cur = state.components.clone(); + let orig = state.clone(); + state.components.or(&state.last_components); + (cur, orig) + }; self.trigger_add_for_systems(entity, &state.last_components, &state.components, world, renderer); self.trigger_add_for_render_systems(entity, &state.last_components, &state.components, world, renderer); self.trigger_remove_for_systems(entity, &state.last_components, &state.components, world, renderer); @@ -182,12 +188,14 @@ impl Manager { } } + { + let state = self.entities[entity.id].0.as_mut().unwrap(); + state.components = cur; + state.last_components = state.components.clone(); + } if state.removed { self.free_entities.push(entity.id); self.entities[entity.id].0 = None; - } else { - let state = self.entities[entity.id].0.as_mut().unwrap(); - state.last_components = state.components.clone(); } } } @@ -244,6 +252,7 @@ impl Manager { if let Some(set) = self.entities[e.id].0.as_mut() { set.components = BSet::new(self.components.len()); set.removed = true; + self.changed_entity_components.insert(e); } } diff --git a/src/entity/block_entity/mod.rs b/src/entity/block_entity/mod.rs new file mode 100644 index 0000000..7f1814c --- /dev/null +++ b/src/entity/block_entity/mod.rs @@ -0,0 +1,32 @@ + +pub mod sign; + +use world::block::Block; +use shared::Position; +use ecs; + +pub fn add_systems(m: &mut ecs::Manager) { + sign::add_systems(m); +} + +pub enum BlockEntityType { + Sign +} + +impl BlockEntityType { + pub fn get_block_entity(bl: Block) -> Option { + match bl { + Block::StandingSign{..} | Block::WallSign{..} => Some(BlockEntityType::Sign), + _ => None, + } + } + + pub fn create_entity(&self, m: &mut ecs::Manager, pos: Position) -> ecs::Entity { + let e = m.create_entity(); + m.add_component_direct(e, pos); + match *self { + BlockEntityType::Sign => sign::init_entity(m, e), + } + e + } +} diff --git a/src/entity/block_entity/sign.rs b/src/entity/block_entity/sign.rs new file mode 100644 index 0000000..ef476a5 --- /dev/null +++ b/src/entity/block_entity/sign.rs @@ -0,0 +1,263 @@ + +use ecs; +use format::{self, Component}; +use shared::{Direction, Position}; +use world; +use world::block::Block; +use render; +use render::model; + +pub fn add_systems(m: &mut ecs::Manager) { + let sys = SignRenderer::new(m); + m.add_render_system(sys); +} + +pub fn init_entity(m: &mut ecs::Manager, e: ecs::Entity) { + m.add_component_direct(e, SignInfo { + model: None, + lines: [ + Component::Text(format::TextComponent::new("")), + Component::Text(format::TextComponent::new("")), + Component::Text(format::TextComponent::new("")), + Component::Text(format::TextComponent::new("")), + ], + offset_x: 0.0, + offset_y: 0.0, + offset_z: 0.0, + has_stand: false, + rotation: 0.0, + dirty: false, + }); +} + +pub struct SignInfo { + model: Option, + + pub lines: [format::Component; 4], + pub dirty: bool, + + offset_x: f64, + offset_y: f64, + offset_z: f64, + has_stand: bool, + rotation: f64, +} + +struct SignRenderer { + filter: ecs::Filter, + position: ecs::Key, + sign_info: ecs::Key, +} + +impl SignRenderer { + fn new(m: &mut ecs::Manager) -> SignRenderer { + let sign_info = m.get_key(); + let position = m.get_key(); + SignRenderer { + filter: ecs::Filter::new() + .with(position) + .with(sign_info), + position: position, + sign_info: sign_info, + } + } +} + +impl ecs::System for SignRenderer { + + fn filter(&self) -> &ecs::Filter { + &self.filter + } + + fn update(&mut self, m: &mut ecs::Manager, world: &mut world::World, renderer: &mut render::Renderer) { + for e in m.find(&self.filter) { + let position = *m.get_component(e, self.position).unwrap(); + let info = m.get_component_mut(e, self.sign_info).unwrap(); + if info.dirty { + self.entity_removed(m, e, world, renderer); + self.entity_added(m, e, world, renderer); + } + if let Some(model) = info.model { + let mdl = renderer.model.get_model(model).unwrap(); + mdl.block_light = world.get_block_light(position) as f32; + mdl.sky_light = world.get_sky_light(position) as f32; + } + } + } + + fn entity_added(&mut self, m: &mut ecs::Manager, e: ecs::Entity, world: &mut world::World, renderer: &mut render::Renderer) { + use std::f64::consts::PI; + use cgmath::{Vector3, Matrix4, Decomposed, Rotation3, Rad, Angle, Quaternion}; + let position = *m.get_component(e, self.position).unwrap(); + let info = m.get_component_mut(e, self.sign_info).unwrap(); + info.dirty = false; + match world.get_block(position) { + Block::WallSign{facing} => { + info.offset_z = 7.5 / 16.0; + match facing { + Direction::North => {}, + Direction::South => info.rotation = PI, + Direction::West => info.rotation = PI / 2.0, + Direction::East => info.rotation = -PI / 2.0, + _ => unreachable!(), + } + }, + Block::StandingSign{rotation} => { + info.offset_z = 5.0 / 16.0; + info.has_stand = true; + info.rotation = -(rotation.data() as f64 / 16.0) * PI * 2.0 + PI; + } + _ => return, + } + let wood = render::Renderer::get_texture(renderer.get_textures_ref(), "blocks/planks_oak"); + + macro_rules! rel { + ($tex:expr, $x:expr, $y:expr, $w:expr, $h:expr) => ( + Some($tex.relative(($x) / 16.0, ($y) / 16.0, ($w) / 16.0, ($h) / 16.0)) + ); + } + + let mut verts = vec![]; + // Backboard + model::append_box_texture_scale(&mut verts, -0.5, -4.0/16.0, -0.5/16.0, 1.0, 8.0/16.0, 1.0/16.0, [ + rel!(wood, 0.0, 0.0, 16.0, 2.0), // Up + rel!(wood, 0.0, 0.0, 16.0, 2.0), // Down + rel!(wood, 0.0, 4.0, 16.0, 12.0), // North + rel!(wood, 0.0, 4.0, 16.0, 12.0), // South + rel!(wood, 0.0, 0.0, 2.0, 12.0), // West + rel!(wood, 0.0, 0.0, 2.0, 12.0), // East + ], [ + [1.5, 1.0], // Up + [1.5, 1.0], // Down + [1.5, 1.0], // North + [1.5, 1.0], // South + [1.0, 1.0], // West + [1.0, 1.0], // East + ]); + for vert in &mut verts[8..12] { + vert.r = 183; + vert.g = 183; + vert.b = 196; + } + if info.has_stand { + let log = render::Renderer::get_texture(renderer.get_textures_ref(), "blocks/log_oak"); + model::append_box(&mut verts, -0.5/16.0, -0.25-9.0/16.0, -0.5/16.0, 1.0/16.0, 9.0/16.0, 1.0/16.0, [ + rel!(log, 0.0, 0.0, 2.0, 2.0), // Up + rel!(log, 0.0, 0.0, 2.0, 2.0), // Down + rel!(log, 0.0, 4.0, 2.0, 12.0), // North + rel!(log, 0.0, 4.0, 2.0, 12.0), // South + rel!(log, 0.0, 0.0, 2.0, 12.0), // West + rel!(log, 0.0, 0.0, 2.0, 12.0), // East + ]); + } + + for (i, line) in info.lines.iter().enumerate() { + let mut state = FormatState { + line: i as f32, + width: 0.0, + offset: 0.0, + text: Vec::new(), + renderer: renderer, + }; + state.build(line, format::Color::Black); + let width = state.width; + // Center align text + for vert in &mut state.text { + vert.x += width * 0.5; + } + verts.extend_from_slice(&state.text); + } + + let model = renderer.model.create_model( + model::DEFAULT, + vec![verts] + ); + + { + let mdl = renderer.model.get_model(model).unwrap(); + mdl.radius = 2.0; + mdl.x = position.x as f32 + 0.5; + mdl.y = position.y as f32 + 0.5; + mdl.z = position.z as f32 + 0.5; + mdl.matrix[0] = Matrix4::from(Decomposed { + scale: 1.0, + rot: Quaternion::from_angle_y(Rad::new(info.rotation as f32)), + disp: Vector3::new(position.x as f32 + 0.5, -position.y as f32 - 0.5, position.z as f32 + 0.5), + }) * Matrix4::from_translation(Vector3::new(info.offset_x as f32, -info.offset_y as f32, info.offset_z as f32)); + } + + info.model = Some(model); + } + + fn entity_removed(&mut self, m: &mut ecs::Manager, e: ecs::Entity, _: &mut world::World, renderer: &mut render::Renderer) { + let info = m.get_component_mut(e, self.sign_info).unwrap(); + if let Some(model) = info.model { + renderer.model.remove_model(model); + } + info.model = None; + } +} + +struct FormatState<'a> { + line: f32, + offset: f32, + width: f32, + text: Vec, + renderer: &'a mut render::Renderer, +} + +impl <'a> FormatState<'a> { + fn build(&mut self, c: &Component, color: format::Color) { + match c { + &format::Component::Text(ref txt) => { + let col = FormatState::get_color(&txt.modifier, color); + self.append_text(&txt.text, col); + let modi = &txt.modifier; + if let Some(ref extra) = modi.extra { + for e in extra { + self.build(e, col); + } + } + } + } + } + + fn append_text(&mut self, txt: &str, color: format::Color) { + const Y_SCALE: f32 = (6.0 / 16.0) / 4.0; + const X_SCALE: f32 = Y_SCALE / 16.0; + + let (rr, gg, bb) = color.to_rgb(); + for ch in txt.chars() { + if ch == ' ' { + self.offset += 6.0 * X_SCALE; + continue; + } + let texture = self.renderer.ui.character_texture(ch); + let w = self.renderer.ui.size_of_char(ch) as f32; + + for vert in ::model::BlockVertex::face_by_direction(Direction::North) { + self.text.push(model::Vertex { + x: vert.x * X_SCALE * w - (self.offset + w * X_SCALE), + y: vert.y * Y_SCALE - (Y_SCALE + 0.4/16.0) * self.line + 2.1 / 16.0, + z: -0.6 / 16.0, + texture: texture.clone(), + texture_x: vert.toffsetx as f64, + texture_y: vert.toffsety as f64, + r: rr, + g: gg, + b: bb, + a: 255, + id: 0, + }); + } + self.offset += (w + 2.0) * X_SCALE; + } + if self.offset > self.width { + self.width = self.offset; + } + } + + fn get_color(modi: &format::Modifier, color: format::Color) -> format::Color { + modi.color.unwrap_or(color) + } +} diff --git a/src/entity/mod.rs b/src/entity/mod.rs index 831a7e7..db2821b 100644 --- a/src/entity/mod.rs +++ b/src/entity/mod.rs @@ -1,5 +1,6 @@ pub mod player; +pub mod block_entity; use ecs; use cgmath::Vector3; @@ -17,6 +18,8 @@ pub fn add_systems(m: &mut ecs::Manager) { m.add_system(sys); let sys = systems::ApplyGravity::new(m); m.add_system(sys); + + block_entity::add_systems(m); } /// Location of an entity in the world. diff --git a/src/render/model.rs b/src/render/model.rs index 4ef0e6b..fefa5e4 100644 --- a/src/render/model.rs +++ b/src/render/model.rs @@ -204,7 +204,7 @@ impl Manager { } } - pub fn draw(&mut self, frustum: &Frustum, perspective_matrix: &Matrix4, camera_matrix: &Matrix4, sky_offset: f32, light_level: f32) { + pub fn draw(&mut self, frustum: &Frustum, perspective_matrix: &Matrix4, camera_matrix: &Matrix4, light_level: f32, sky_offset: f32) { let m = if self.index_type == gl::UNSIGNED_SHORT { 2 } else { @@ -359,7 +359,7 @@ pub fn append_box_texture_scale( z: vert.z * d + z, texture: tex.clone(), texture_x: (vert.toffsetx as f64) * texture_scale[dir.index()][0], - texture_y: (vert.toffsety as f64) * texture_scale[dir.index()][0], + texture_y: (vert.toffsety as f64) * texture_scale[dir.index()][1], r: rr, g: gg, b: bb, diff --git a/src/server/mod.rs b/src/server/mod.rs index d8fae14..c1cca5c 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -306,7 +306,7 @@ impl Server { sun_model.tick(renderer, self.world_time, self.world_age); } - self.world.tick(); + self.world.tick(&mut self.entities); // Copy to camera if let Some(player) = self.player { @@ -340,6 +340,7 @@ impl Server { TeleportPlayer => on_teleport, TimeUpdate => on_time_update, ChangeGameState => on_game_state_change, + UpdateSign => on_sign_update, } }, Err(err) => panic!("Err: {:?}", err), @@ -520,6 +521,16 @@ impl Server { } } + fn on_sign_update(&mut self, update_sign: packet::play::clientbound::UpdateSign) { + 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_chunk_data(&mut self, chunk_data: packet::play::clientbound::ChunkData) { self.world.load_chunk( chunk_data.chunk_x, @@ -531,7 +542,7 @@ impl Server { } fn on_chunk_unload(&mut self, chunk_unload: packet::play::clientbound::ChunkUnload) { - self.world.unload_chunk(chunk_unload.x, chunk_unload.z); + self.world.unload_chunk(chunk_unload.x, chunk_unload.z, &mut self.entities); } fn on_block_change(&mut self, block_change: packet::play::clientbound::BlockChange) { diff --git a/src/types/bit/set.rs b/src/types/bit/set.rs index 0a620ad..8c9c221 100644 --- a/src/types/bit/set.rs +++ b/src/types/bit/set.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Set { data: Vec, } @@ -65,4 +65,10 @@ impl Set { } true } + + pub fn or(&mut self, other: &Set) { + for (a, b) in self.data.iter_mut().zip(&other.data) { + *a = (*a) | *b; + } + } } diff --git a/src/world/mod.rs b/src/world/mod.rs index 8659a7b..3d12408 100644 --- a/src/world/mod.rs +++ b/src/world/mod.rs @@ -25,6 +25,9 @@ use render; use collision; use cgmath; use chunk_builder; +use ecs; +use entity::block_entity; +use format; pub mod biome; @@ -34,6 +37,15 @@ pub struct World { render_list: Vec<(i32, i32, i32)>, light_updates: VecDeque, + + block_entity_actions: VecDeque, +} + +#[derive(Clone, Debug)] +pub enum BlockEntityAction { + Create(Position), + Remove(Position), + UpdateSignText(Position, format::Component, format::Component, format::Component, format::Component), } #[derive(Clone, Copy, PartialEq, Eq)] @@ -68,6 +80,7 @@ impl World { chunks: HashMap::with_hasher(BuildHasherDefault::default()), render_list: vec![], light_updates: VecDeque::new(), + block_entity_actions: VecDeque::new(), } } @@ -84,7 +97,17 @@ impl World { fn set_block_raw(&mut self, pos: Position, b: block::Block) -> bool { let cpos = CPos(pos.x >> 4, pos.z >> 4); let chunk = self.chunks.entry(cpos).or_insert_with(|| Chunk::new(cpos)); - chunk.set_block(pos.x & 0xF, pos.y, pos.z & 0xF, b) + if chunk.set_block(pos.x & 0xF, pos.y, pos.z & 0xF, b) { + if chunk.block_entities.contains_key(&pos) { + self.block_entity_actions.push_back(BlockEntityAction::Remove(pos)); + } + if block_entity::BlockEntityType::get_block_entity(b).is_some() { + self.block_entity_actions.push_back(BlockEntityAction::Create(pos)); + } + true + } else { + false + } } pub fn update_block(&mut self, pos: Position) { @@ -133,7 +156,7 @@ impl World { chunk.set_block_light(pos.x & 0xF, pos.y, pos.z & 0xF, light); } - fn get_block_light(&self, pos: Position) -> u8 { + pub fn get_block_light(&self, pos: Position) -> u8 { match self.chunks.get(&CPos(pos.x >> 4, pos.z >> 4)) { Some(ref chunk) => chunk.get_block_light(pos.x & 0xF, pos.y, pos.z & 0xF), None => 0, @@ -146,7 +169,7 @@ impl World { chunk.set_sky_light(pos.x & 0xF, pos.y, pos.z & 0xF, light); } - fn get_sky_light(&self, pos: Position) -> u8 { + pub fn get_sky_light(&self, pos: Position) -> u8 { match self.chunks.get(&CPos(pos.x >> 4, pos.z >> 4)) { Some(ref chunk) => chunk.get_sky_light(pos.x & 0xF, pos.y, pos.z & 0xF), None => 15, @@ -160,7 +183,11 @@ impl World { }); } - pub fn tick(&mut self) { + pub fn add_block_entity_action(&mut self, action: BlockEntityAction) { + self.block_entity_actions.push_back(action); + } + + pub fn tick(&mut self, m: &mut ecs::Manager) { use time; let start = time::precise_time_ns(); let mut updates_performed = 0; @@ -174,6 +201,48 @@ impl World { } } } + + let sign_info: ecs::Key = m.get_key(); + + while let Some(action) = self.block_entity_actions.pop_front() { + match action { + BlockEntityAction::Remove(pos) => { + if let Some(chunk) = self.chunks.get_mut(&CPos(pos.x >> 4, pos.z >> 4)) { + if let Some(entity) = chunk.block_entities.remove(&pos) { + m.remove_entity(entity); + } + } + }, + BlockEntityAction::Create(pos) => { + if let Some(chunk) = self.chunks.get_mut(&CPos(pos.x >> 4, pos.z >> 4)) { + // Remove existing entity + if let Some(entity) = chunk.block_entities.remove(&pos) { + m.remove_entity(entity); + } + let block = chunk.get_block(pos.x & 0xF, pos.y, pos.z & 0xF); + if let Some(entity_type) = block_entity::BlockEntityType::get_block_entity(block) { + let entity = entity_type.create_entity(m, pos); + chunk.block_entities.insert(pos, entity); + } + } + }, + BlockEntityAction::UpdateSignText(pos, line1, line2, line3, line4) => { + if let Some(chunk) = self.chunks.get(&CPos(pos.x >> 4, pos.z >> 4)) { + if let Some(entity) = chunk.block_entities.get(&pos) { + if let Some(sign) = m.get_component_mut(*entity, sign_info) { + sign.lines = [ + line1, + line2, + line3, + line4, + ]; + sign.dirty = true; + } + } + } + } + } + } } fn do_light_update(&mut self) { @@ -471,8 +540,12 @@ impl World { snapshot } - pub fn unload_chunk(&mut self, x: i32, z: i32) { - self.chunks.remove(&CPos(x, z)); + pub fn unload_chunk(&mut self, x: i32, z: i32, m: &mut ecs::Manager) { + if let Some(chunk) = self.chunks.remove(&CPos(x, z)) { + for entity in chunk.block_entities.values() { + m.remove_entity(*entity); + } + } } pub fn load_chunk(&mut self, x: i32, z: i32, new: bool, mask: u16, data: Vec) -> Result<(), protocol::Error> { @@ -529,8 +602,8 @@ impl World { let m = bit::Map::from_raw(bits, bit_size as usize); section.blocks = m; - for i in 0 .. 4096 { - let bl_id = section.blocks.get(i); + for bi in 0 .. 4096 { + let bl_id = section.blocks.get(bi); if bit_size == 13 { if section.block_map.get(bl_id) .map(|v| v.1) @@ -543,7 +616,19 @@ impl World { section.rev_block_map.insert(bl, bl_id); } } - section.block_map.get_mut(bl_id).unwrap().1 += 1; + let bmap = section.block_map.get_mut(bl_id).unwrap(); + bmap.1 += 1; + if block_entity::BlockEntityType::get_block_entity(bmap.0).is_some() { + let pos = Position::new( + (bi & 0xF) as i32, + (bi >> 8) as i32, + ((bi >> 4) & 0xF) as i32 + ) + (chunk.position.0 << 4, (i << 4) as i32, chunk.position.1 << 4); + if chunk.block_entities.contains_key(&pos) { + self.block_entity_actions.push_back(BlockEntityAction::Remove(pos)) + } + self.block_entity_actions.push_back(BlockEntityAction::Create(pos)) + } } for entry in §ion.block_map { @@ -675,6 +760,8 @@ pub struct Chunk { heightmap: [u8; 16 * 16], heightmap_dirty: bool, + + block_entities: HashMap>, } impl Chunk { @@ -691,6 +778,7 @@ impl Chunk { biomes: [0; 16 * 16], heightmap: [0; 16 * 16], heightmap_dirty: true, + block_entities: HashMap::with_hasher(BuildHasherDefault::default()), } }