diff --git a/assets/voxygen/element/misc_bg/crosshair.vox b/assets/voxygen/element/misc_bg/crosshair.vox index 37f0d80d5b..6031040ab2 100644 --- a/assets/voxygen/element/misc_bg/crosshair.vox +++ b/assets/voxygen/element/misc_bg/crosshair.vox @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4ebf4d14b5deaf1e4a5645ebe143024522d66991026328400245f7d4f3e07580 -size 5352 +oid sha256:0776f8ad7b9a0f252335c5b75fe08b75a7f39ca5a30571a52bff934e7b83d366 +size 55912 diff --git a/client/src/lib.rs b/client/src/lib.rs index 748612aa87..28de1ea266 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -12,7 +12,7 @@ use common::{ msg::{ClientMsg, ClientState, ServerInfo, ServerMsg}, net::PostBox, state::State, - terrain::{chonk::ChonkMetrics, TerrainChunk, TerrainChunkSize}, + terrain::{block::Block, chonk::ChonkMetrics, TerrainChunk, TerrainChunkSize}, vol::VolSize, }; use log::{info, log_enabled, warn}; @@ -185,6 +185,14 @@ impl Client { self.pending_chunks.clear(); } + pub fn place_block(&mut self, pos: Vec3, block: Block) { + self.postbox.send_message(ClientMsg::PlaceBlock(pos, block)); + } + + pub fn remove_block(&mut self, pos: Vec3) { + self.postbox.send_message(ClientMsg::BreakBlock(pos)); + } + /// Execute a single client tick, handle input and update the game state by the given duration. #[allow(dead_code)] pub fn tick( diff --git a/common/src/comp/inputs.rs b/common/src/comp/inputs.rs index f03a5706b6..576d23c490 100644 --- a/common/src/comp/inputs.rs +++ b/common/src/comp/inputs.rs @@ -28,6 +28,9 @@ pub struct Rolling { #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct OnGround; +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +pub struct CanBuild; + #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct Jumping; @@ -85,6 +88,10 @@ impl Component for OnGround { type Storage = NullStorage; } +impl Component for CanBuild { + type Storage = FlaggedStorage>; +} + impl Component for Jumping { type Storage = NullStorage; } diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 2ec6fdb8aa..d7d5c211dd 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -15,7 +15,9 @@ pub use agent::Agent; pub use animation::{Animation, AnimationInfo}; pub use body::{humanoid, quadruped, quadruped_medium, Body}; pub use controller::Controller; -pub use inputs::{Attacking, Gliding, Jumping, MoveDir, OnGround, Respawning, Rolling, Wielding}; +pub use inputs::{ + Attacking, CanBuild, Gliding, Jumping, MoveDir, OnGround, Respawning, Rolling, Wielding, +}; pub use inventory::{item, Inventory}; pub use phys::{ForceUpdate, Ori, Pos, Vel}; pub use player::Player; diff --git a/common/src/msg/client.rs b/common/src/msg/client.rs index db1c40d4c7..1da55e7857 100644 --- a/common/src/msg/client.rs +++ b/common/src/msg/client.rs @@ -1,5 +1,6 @@ use super::ClientState; use crate::comp; +use crate::terrain::block::Block; use vek::*; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -14,6 +15,8 @@ pub enum ClientMsg { Controller(comp::Controller), RequestState(ClientState), SetViewDistance(u32), + BreakBlock(Vec3), + PlaceBlock(Vec3, Block), Ping, Pong, Chat(String), diff --git a/common/src/msg/ecs_packet.rs b/common/src/msg/ecs_packet.rs index 76557b2e73..d1d7d25dae 100644 --- a/common/src/msg/ecs_packet.rs +++ b/common/src/msg/ecs_packet.rs @@ -22,6 +22,7 @@ sphynx::sum_type! { Ori(comp::Ori), Body(comp::Body), Player(comp::Player), + CanBuild(comp::CanBuild), Stats(comp::Stats), } } @@ -35,6 +36,7 @@ sphynx::sum_type! { Ori(PhantomData), Body(PhantomData), Player(PhantomData), + CanBuild(PhantomData), Stats(PhantomData), } } diff --git a/common/src/state.rs b/common/src/state.rs index 5e7d3ef45c..2898d78097 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -129,6 +129,7 @@ impl State { ecs.register_synced::(); ecs.register_synced::(); ecs.register_synced::(); + ecs.register_synced::(); // Register components synced by other means ecs.register::(); diff --git a/common/src/volumes/dyna.rs b/common/src/volumes/dyna.rs index 4e7f4982d1..e88ccd3de7 100644 --- a/common/src/volumes/dyna.rs +++ b/common/src/volumes/dyna.rs @@ -2,7 +2,7 @@ use crate::vol::{BaseVol, ReadVol, SizedVol, Vox, WriteVol}; use serde_derive::{Deserialize, Serialize}; use vek::*; -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum DynaErr { OutOfBounds, } diff --git a/common/src/volumes/vol_map_2d.rs b/common/src/volumes/vol_map_2d.rs index c77b98d0ea..28b29fdcbb 100644 --- a/common/src/volumes/vol_map_2d.rs +++ b/common/src/volumes/vol_map_2d.rs @@ -6,7 +6,7 @@ use fxhash::FxHashMap; use std::{collections::hash_map, fmt::Debug, marker::PhantomData, sync::Arc}; use vek::*; -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum VolMap2dErr { NoSuchChunk, ChunkErr(V::Err), diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 90fe706299..9ed396771b 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -7,9 +7,7 @@ use common::{ comp, msg::ServerMsg, npc::{get_npc_name, NpcKind}, - state::{TerrainChange, TimeOfDay}, - terrain::Block, - vol::Vox, + state::TimeOfDay, }; use specs::{Builder, Entity as EcsEntity, Join}; use vek::*; @@ -106,18 +104,6 @@ lazy_static! { "/players : Show the online players list", handle_players, ), - ChatCommand::new( - "solid", - "{}", - "/solid : Make the blocks around you solid", - handle_solid, - ), - ChatCommand::new( - "empty", - "{}", - "/empty : Make the blocks around you empty", - handle_empty, - ), ChatCommand::new( "help", "", "/help: Display this message", handle_help), ChatCommand::new( @@ -125,7 +111,13 @@ lazy_static! { "{}", "/health : Set your current health", handle_health, - ) + ), + ChatCommand::new( + "build", + "", + "/build : Toggles build mode on and off", + handle_build, + ), ]; } @@ -359,41 +351,32 @@ fn handle_players(server: &mut Server, entity: EcsEntity, _args: String, _action } } -fn handle_solid(server: &mut Server, entity: EcsEntity, _args: String, _action: &ChatCommand) { - match server.state.read_component_cloned::(entity) { - Some(current_pos) => { - server.state.ecs().write_resource::().set( - current_pos.0.map(|e| e.floor() as i32), - Block::new(1, Rgb::broadcast(255)), - ); - } - None => server.clients.notify( +fn handle_build(server: &mut Server, entity: EcsEntity, _args: String, _action: &ChatCommand) { + if server + .state + .read_storage::() + .get(entity) + .is_some() + { + server + .state + .ecs() + .write_storage::() + .remove(entity); + server.clients.notify( entity, - ServerMsg::Chat(String::from("You have no position!")), - ), - } -} - -fn handle_empty(server: &mut Server, entity: EcsEntity, _args: String, _action: &ChatCommand) { - match server.state.read_component_cloned::(entity) { - Some(current_pos) => { - let mut terrain_change = server.state.ecs().write_resource::(); - - for i in -1..2 { - for j in -1..2 { - for k in -2..1 { - terrain_change.set( - current_pos.0.map(|e| e.floor() as i32) + Vec3::new(i, j, k), - Block::empty(), - ); - } - } - } - } - None => server.clients.notify( + ServerMsg::Chat(String::from("Toggled off build mode!")), + ); + } else { + let _ = server + .state + .ecs() + .write_storage::() + .insert(entity, comp::CanBuild); + server.clients.notify( entity, - ServerMsg::Chat(String::from("You have no position!")), - ), + ServerMsg::Chat(String::from("Toggled on build mode!")), + ); } } diff --git a/server/src/lib.rs b/server/src/lib.rs index f32530cbf6..7c63f3eef3 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -16,9 +16,10 @@ use common::{ comp, msg::{ClientMsg, ClientState, RequestStateError, ServerInfo, ServerMsg}, net::PostOffice, - state::{State, Uid}, - terrain::{TerrainChunk, TerrainChunkSize, TerrainMap}, + state::{State, TerrainChange, Uid}, + terrain::{block::Block, TerrainChunk, TerrainChunkSize, TerrainMap}, vol::VolSize, + vol::Vox, }; use log::debug; use specs::{join::Join, world::EntityBuilder as EcsEntityBuilder, Builder, Entity as EcsEntity}; @@ -500,6 +501,30 @@ impl Server { // Only characters can send positions. _ => client.error_state(RequestStateError::Impossible), }, + ClientMsg::BreakBlock(pos) => { + if state + .ecs_mut() + .read_storage::() + .get(entity) + .is_some() + { + let mut terrain_change = + state.ecs().write_resource::(); + terrain_change.set(pos, Block::empty()); + } + } + ClientMsg::PlaceBlock(pos, block) => { + if state + .ecs_mut() + .read_storage::() + .get(entity) + .is_some() + { + let mut terrain_change = + state.ecs().write_resource::(); + terrain_change.set(pos, block); + } + } ClientMsg::TerrainChunkRequest { key } => match client.client_state { ClientState::Connected | ClientState::Registered diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index e67a8140c6..fd335641ea 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -49,9 +49,6 @@ image_ids! { chat_arrow_mo: "voxygen/element/buttons/arrow_down_hover.vox", chat_arrow_press: "voxygen/element/buttons/arrow_down_press.vox", - // Crosshair - crosshair: "voxygen/element/misc_bg/crosshair.vox", - //////////////////////////////////////////////////////////////////////// @@ -122,6 +119,9 @@ image_ids! { button_hover: "voxygen/element/buttons/button_hover.vox", button_press: "voxygen/element/buttons/button_press.vox", + // Crosshair + crosshair: "voxygen/element/misc_bg/crosshair.vox", + ////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index a096c61b3d..cc5a55dcef 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -48,6 +48,9 @@ const MANA_COLOR: Color = Color::Rgba(0.42, 0.41, 0.66, 1.0); widget_ids! { struct Ids { + // Crosshair + crosshair, + // Character Names name_tags[], // Health Bars @@ -330,6 +333,13 @@ impl Hud { let mut health_id_walker = self.ids.health_bars.walk(); let mut health_back_id_walker = self.ids.health_bar_backs.walk(); + // Crosshair + Image::new(self.imgs.crosshair) + .w_h(21.0 * 2.0, 21.0 * 2.0) + .middle_of(ui_widgets.window) + .color(Some(Color::Rgba(1.0, 1.0, 1.0, 1.0))) + .set(self.ids.crosshair, ui_widgets); + // Render Name Tags for (pos, name) in (&entities, &pos, &stats, player.maybe()) .join() diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 6592f19588..4acac6be1e 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -8,7 +8,9 @@ use crate::{ Direction, Error, GlobalState, PlayState, PlayStateResult, }; use client::{self, Client}; -use common::{clock::Clock, comp, comp::Pos, msg::ClientState}; +use common::{ + clock::Clock, comp, comp::Pos, msg::ClientState, ray::Ray, terrain::block::Block, vol::ReadVol, +}; use log::{error, warn}; use std::{cell::RefCell, rc::Rc, time::Duration}; use vek::*; @@ -108,17 +110,75 @@ impl PlayState for SessionState { return PlayStateResult::Shutdown; } Event::InputUpdate(GameInput::Attack, state) => { + // Check the existence of CanBuild component. If it's here, use LMB to + // place blocks, if not, use it to attack if state { - if let ClientState::Character = current_client_state { - self.controller.attack = state; + let mut client = self.client.borrow_mut(); + if client + .state() + .read_storage::() + .get(client.entity()) + .is_some() + { + let (view_mat, _, cam_pos) = + self.scene.camera().compute_dependents(&client); + let cam_dir = + (self.scene.camera().get_focus_pos() - cam_pos).normalized(); + + let (d, b) = { + let terrain = client.state().terrain(); + let ray = + terrain.ray(cam_pos, cam_pos + cam_dir * 100.0).cast(); + (ray.0, if let Ok(Some(_)) = ray.1 { true } else { false }) + }; + + if b { + let pos = + (cam_pos + cam_dir * (d - 0.01)).map(|e| e.floor() as i32); + client.place_block(pos, Block::new(1, Rgb::broadcast(255))); // TODO: Handle block color with a command + } } else { - self.controller.respawn = state; // TODO: Don't do both + if let ClientState::Character = current_client_state { + self.controller.attack = state + } else { + self.controller.respawn = state; // TODO: Don't do both + } } } else { self.controller.attack = state; self.controller.respawn = state; } } + Event::InputUpdate(GameInput::SecondAttack, state) => { + if state { + let mut client = self.client.borrow_mut(); + if client + .state() + .read_storage::() + .get(client.entity()) + .is_some() + { + let (view_mat, _, cam_pos) = + self.scene.camera().compute_dependents(&client); + let cam_dir = + (self.scene.camera().get_focus_pos() - cam_pos).normalized(); + + let (d, b) = { + let terrain = client.state().terrain(); + let ray = + terrain.ray(cam_pos, cam_pos + cam_dir * 100.0).cast(); + (ray.0, if let Ok(Some(_)) = ray.1 { true } else { false }) + }; + + if b { + let pos = (cam_pos + cam_dir * d).map(|e| e.floor() as i32); + client.remove_block(pos); + } + } else { + // TODO: Handle secondary attack + } + } + } Event::InputUpdate(GameInput::Roll, state) => { self.controller.roll = state; } diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index 17f08105cd..251e904453 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -31,6 +31,7 @@ pub struct ControlSettings { pub screenshot: KeyMouse, pub toggle_ingame_ui: KeyMouse, pub attack: KeyMouse, + pub second_attack: KeyMouse, pub roll: KeyMouse, } @@ -60,6 +61,7 @@ impl Default for ControlSettings { screenshot: KeyMouse::Key(VirtualKeyCode::F4), toggle_ingame_ui: KeyMouse::Key(VirtualKeyCode::F6), attack: KeyMouse::Mouse(MouseButton::Left), + second_attack: KeyMouse::Mouse(MouseButton::Right), roll: KeyMouse::Mouse(MouseButton::Middle), } } diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index 7259b0b72f..2a6673bcc3 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -34,6 +34,7 @@ pub enum GameInput { Screenshot, ToggleIngameUi, Attack, + SecondAttack, Roll, Respawn, } @@ -138,6 +139,7 @@ impl Window { GameInput::ToggleIngameUi, ); key_map.insert(settings.controls.attack, GameInput::Attack); + key_map.insert(settings.controls.second_attack, GameInput::SecondAttack); key_map.insert(settings.controls.roll, GameInput::Roll); let keypress_map = HashMap::new();