|
|
|
@ -1,5 +1,5 @@ |
|
|
|
//! Handles client-side input using [`lightyear`], updating the
|
|
|
|
//! Handles client-side input using [`lightyear`], updating the
|
|
|
|
//! [`ActionState`] of the affected entities, such as the `Player`.
|
|
|
|
//! [`ActionState`] of the affected entities, such as the [`Player`].
|
|
|
|
|
|
|
|
|
|
|
|
use bevy::prelude::*; |
|
|
|
use bevy::prelude::*; |
|
|
|
use common::prelude::*; |
|
|
|
use common::prelude::*; |
|
|
|
@ -9,10 +9,55 @@ use lightyear::prelude::input::native::*; |
|
|
|
use bevy::window::{CursorGrabMode, CursorOptions}; |
|
|
|
use bevy::window::{CursorGrabMode, CursorOptions}; |
|
|
|
|
|
|
|
|
|
|
|
pub fn plugin(app: &mut App) { |
|
|
|
pub fn plugin(app: &mut App) { |
|
|
|
|
|
|
|
app.init_resource::<CurrentAction>(); |
|
|
|
app.add_systems( |
|
|
|
app.add_systems( |
|
|
|
FixedPreUpdate, |
|
|
|
FixedPreUpdate, |
|
|
|
(buffer_input, buffer_action).in_set(InputSystems::WriteClientInputs), |
|
|
|
(buffer_input, buffer_action).in_set(InputSystems::WriteClientInputs), |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
app.add_observer(insert_block_pickable); |
|
|
|
|
|
|
|
app.add_observer(place_or_break_blocks); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn insert_block_pickable(event: On<Add, Block>, mut commands: Commands) { |
|
|
|
|
|
|
|
let mut block = commands.entity(event.entity); |
|
|
|
|
|
|
|
block.insert(Pickable::default()); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Resource, Default)] |
|
|
|
|
|
|
|
struct CurrentAction(Action); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fn place_or_break_blocks( |
|
|
|
|
|
|
|
mut event: On<Pointer<Press>>, |
|
|
|
|
|
|
|
mut current_action: ResMut<CurrentAction>, |
|
|
|
|
|
|
|
cursor: Single<&CursorOptions>, |
|
|
|
|
|
|
|
blocks: Blocks, |
|
|
|
|
|
|
|
) { |
|
|
|
|
|
|
|
let is_place = match event.button { |
|
|
|
|
|
|
|
PointerButton::Primary => false, // left-click
|
|
|
|
|
|
|
|
PointerButton::Secondary => true, // right-click
|
|
|
|
|
|
|
|
PointerButton::Middle => return, // not handled
|
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
if cursor.grab_mode != CursorGrabMode::Locked { |
|
|
|
|
|
|
|
return; // Cursor is not grabbed.
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
let Some(block_pos) = blocks.position(event.entity) else { |
|
|
|
|
|
|
|
return; // Clicked entity is not a block.
|
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
let Some(normal) = event.hit.normal else { |
|
|
|
|
|
|
|
return; // Picking system didn't return a normal.
|
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
current_action.0 = if is_place { |
|
|
|
|
|
|
|
// FIXME: This only works for axis-aligned normals.
|
|
|
|
|
|
|
|
let offset = normal.normalize().round().as_ivec3(); |
|
|
|
|
|
|
|
// TODO: Don't hardcode block type.
|
|
|
|
|
|
|
|
Action::PlaceBlock(block_pos + offset, Block::DEFAULT) |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
Action::BreakBlock(block_pos) |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Handle the event.
|
|
|
|
|
|
|
|
event.propagate(false); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
fn buffer_input( |
|
|
|
fn buffer_input( |
|
|
|
@ -44,50 +89,10 @@ fn buffer_input( |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// TODO: Use picking system instead of manually raycasting.
|
|
|
|
fn buffer_action( |
|
|
|
pub fn buffer_action( |
|
|
|
|
|
|
|
buttons: Res<ButtonInput<MouseButton>>, |
|
|
|
|
|
|
|
mut player: Single<&mut ActionState<Action>, With<InputMarker<Action>>>, |
|
|
|
mut player: Single<&mut ActionState<Action>, With<InputMarker<Action>>>, |
|
|
|
cursor: Single<&CursorOptions>, |
|
|
|
mut current_action: ResMut<CurrentAction>, |
|
|
|
window: Single<(&Window, &CursorOptions)>, |
|
|
|
|
|
|
|
camera: Single<(&GlobalTransform, &Camera)>, |
|
|
|
|
|
|
|
mut ray_cast: MeshRayCast, |
|
|
|
|
|
|
|
blocks: Blocks, |
|
|
|
|
|
|
|
) { |
|
|
|
) { |
|
|
|
player.0 = Action::None; |
|
|
|
player.0 = current_action.0.clone(); |
|
|
|
|
|
|
|
current_action.0 = Action::None; |
|
|
|
if cursor.grab_mode == CursorGrabMode::None { |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if !buttons.any_just_pressed([MouseButton::Right, MouseButton::Left]) { |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let (window, cursor) = window.into_inner(); |
|
|
|
|
|
|
|
let (cam_transform, camera) = camera.into_inner(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let ray = if cursor.grab_mode == CursorGrabMode::Locked { |
|
|
|
|
|
|
|
Ray3d::new(cam_transform.translation(), cam_transform.forward()) |
|
|
|
|
|
|
|
} else if let Some(cursor_pos) = window.cursor_position() { |
|
|
|
|
|
|
|
camera.viewport_to_world(cam_transform, cursor_pos).unwrap() |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
return; // cursor outside window area
|
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let settings = MeshRayCastSettings::default(); |
|
|
|
|
|
|
|
let Some((block, hit)) = ray_cast.cast_ray(ray, &settings).first() else { |
|
|
|
|
|
|
|
return; // ray didn't hit anything
|
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
let Some(block_pos) = blocks.position(*block) else { |
|
|
|
|
|
|
|
return; // entity hit is not a block
|
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
player.0 = if buttons.just_pressed(MouseButton::Right) { |
|
|
|
|
|
|
|
// FIXME: This only works for axis-aligned normals.
|
|
|
|
|
|
|
|
let offset = hit.normal.normalize().round().as_ivec3(); |
|
|
|
|
|
|
|
// TODO: Don't hardcode block type.
|
|
|
|
|
|
|
|
Action::PlaceBlock(block_pos + offset, Block::DEFAULT) |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
Action::BreakBlock(block_pos) |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|