From dc33e76509fa2ba44f1b278acf427445f8b196fb Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Fri, 3 Feb 2023 12:38:26 -0500 Subject: [PATCH] Use bevy_mod_raycast to handle tile selection (#262) * Add bevy_mod_picking * More explicit usage * Remove CursorWorldPos * Cleanup * Update cursor position using bevy_mod_picking * WIP tile highlights * Add tests for tile selection * Bind Esc to clear selection * Rearrange tile selection logic for clarity * Swap to simple bevy_mod_raycast solution * Remove debug cursor * Actually store tile handles * Remove redundant TileSelectionActionModify --- deny.toml | 1 + emergence_game/src/main.rs | 1 + emergence_lib/Cargo.toml | 1 + emergence_lib/src/asset_management.rs | 40 ++++ emergence_lib/src/graphics/mod.rs | 14 +- emergence_lib/src/lib.rs | 1 + .../src/player_interaction/abilities.rs | 4 +- .../src/player_interaction/camera.rs | 5 +- .../src/player_interaction/cursor.rs | 108 +++++----- emergence_lib/src/player_interaction/mod.rs | 2 +- .../player_interaction/organism_details.rs | 4 +- .../src/player_interaction/tile_selection.rs | 203 +++++++++++------- .../src/player_interaction/zoning.rs | 4 +- emergence_lib/src/terrain/mod.rs | 4 + 14 files changed, 248 insertions(+), 144 deletions(-) create mode 100644 emergence_lib/src/asset_management.rs diff --git a/deny.toml b/deny.toml index ccdb57404..5c9a20757 100644 --- a/deny.toml +++ b/deny.toml @@ -51,6 +51,7 @@ skip = [ { name = "rustc_version", version = "0.2" }, # from postcard v1.0.2 { name = "semver", version = "0.9" }, # from postcard v1.0.2 { name = "windows_aarch64_msvc", version = "0.36" }, # from notify v5.0.0 + { name = "windows_sys", version = "0.42" }, # from notify v5.0.0 { name = "windows_i686_gnu", version = "0.36" }, # from notify v5.0.0 { name = "windows_i686_msvc", version = "0.36" }, # from notify v5.0.0 { name = "windows_x86_64_gnu", version = "0.36" }, # from notify v5.0.0 diff --git a/emergence_game/src/main.rs b/emergence_game/src/main.rs index 89d5da257..69f306dc0 100644 --- a/emergence_game/src/main.rs +++ b/emergence_game/src/main.rs @@ -18,5 +18,6 @@ fn main() { }) .add_plugin(emergence_lib::player_interaction::InteractionPlugin) .add_plugin(emergence_lib::graphics::GraphicsPlugin) + .add_plugin(emergence_lib::asset_management::AssetManagementPlugin) .run(); } diff --git a/emergence_lib/Cargo.toml b/emergence_lib/Cargo.toml index 04404b9e5..5002dbd2a 100644 --- a/emergence_lib/Cargo.toml +++ b/emergence_lib/Cargo.toml @@ -23,3 +23,4 @@ serde = "1.0.152" leafwing_abilities = "0.3.0" derive_more = "0.99.17" hexx = "0.2.0" +bevy_mod_raycast = "0.7.0" diff --git a/emergence_lib/src/asset_management.rs b/emergence_lib/src/asset_management.rs new file mode 100644 index 000000000..de3a3763a --- /dev/null +++ b/emergence_lib/src/asset_management.rs @@ -0,0 +1,40 @@ +//! Code related to loading, storing and tracking assets + +use bevy::{prelude::*, utils::HashMap}; + +use crate::terrain::Terrain; + +/// Collects asset management systems and resources. +pub struct AssetManagementPlugin; + +impl Plugin for AssetManagementPlugin { + fn build(&self, app: &mut App) { + app.init_resource::(); + } +} + +/// Stores material handles for the different tile types. +#[derive(Resource)] +pub(crate) struct TileHandles { + /// The material used for each type of terrain + pub(crate) terrain_handles: HashMap>, + /// The material used for tiles when they are selected + pub(crate) selected_tile_handle: Handle, +} + +impl FromWorld for TileHandles { + fn from_world(world: &mut World) -> Self { + let mut materials = world.resource_mut::>(); + let mut terrain_handles = HashMap::new(); + terrain_handles.insert(Terrain::Plain, materials.add(Color::WHITE.into())); + terrain_handles.insert(Terrain::Rocky, materials.add(Color::BEIGE.into())); + terrain_handles.insert(Terrain::High, materials.add(Color::RED.into())); + + let selected_tile_handle = materials.add(Color::SEA_GREEN.into()); + + TileHandles { + terrain_handles, + selected_tile_handle, + } + } +} diff --git a/emergence_lib/src/graphics/mod.rs b/emergence_lib/src/graphics/mod.rs index 60df6d850..e775157f7 100644 --- a/emergence_lib/src/graphics/mod.rs +++ b/emergence_lib/src/graphics/mod.rs @@ -7,6 +7,7 @@ use bevy::{ use hexx::{Hex, HexLayout, MeshInfo}; use crate::{ + asset_management::TileHandles, organisms::units::Unit, simulation::geometry::{MapGeometry, TilePos}, structures::Structure, @@ -36,7 +37,7 @@ fn populate_terrain( new_terrain: Query<(Entity, &TilePos, &Terrain), Added>, mut commands: Commands, mut meshes: ResMut>, - mut materials: ResMut>, + materials: Res, map_geometry: Res, ) { // mesh @@ -46,18 +47,9 @@ fn populate_terrain( for (terrain_entity, tile_pos, terrain) in new_terrain.iter() { let pos = map_geometry.layout.hex_to_world_pos(tile_pos.hex); - let color = match terrain { - Terrain::Plain => Color::WHITE, - Terrain::High => Color::YELLOW, - Terrain::Rocky => Color::RED, - }; - - // PERF: this is wildly inefficient and lazy. Store the handles instead! - let material = materials.add(color.into()); - commands.entity(terrain_entity).insert(PbrBundle { mesh: mesh_handle.clone(), - material: material.clone(), + material: materials.terrain_handles.get(terrain).unwrap().clone_weak(), transform: Transform::from_xyz(pos.x, 0.0, pos.y), ..default() }); diff --git a/emergence_lib/src/lib.rs b/emergence_lib/src/lib.rs index f024c248e..6d482e09c 100644 --- a/emergence_lib/src/lib.rs +++ b/emergence_lib/src/lib.rs @@ -6,6 +6,7 @@ // Often exceeded by queries #![allow(clippy::type_complexity)] +pub mod asset_management; pub mod curves; pub mod enum_iter; pub mod graphics; diff --git a/emergence_lib/src/player_interaction/abilities.rs b/emergence_lib/src/player_interaction/abilities.rs index f31a63295..bb2fe6f0e 100644 --- a/emergence_lib/src/player_interaction/abilities.rs +++ b/emergence_lib/src/player_interaction/abilities.rs @@ -1,6 +1,6 @@ //! Abilities spend intent, modifying the behavior of allied organisms in an area. -use super::cursor::CursorTilePos; +use super::cursor::CursorPos; use super::intent::{Intent, IntentPool}; use super::InteractionSystem; use bevy::prelude::*; @@ -53,7 +53,7 @@ impl IntentAbility { /// Uses abilities when pressed at the cursor's location. fn use_ability( - cursor_tile_pos: Res, + cursor_tile_pos: Res, ability_state: Res>, mut intent_pool: ResMut, ) { diff --git a/emergence_lib/src/player_interaction/camera.rs b/emergence_lib/src/player_interaction/camera.rs index cfe184df5..d484f1137 100644 --- a/emergence_lib/src/player_interaction/camera.rs +++ b/emergence_lib/src/player_interaction/camera.rs @@ -5,6 +5,7 @@ use std::f32::consts::PI; use bevy::prelude::*; +use bevy_mod_raycast::RaycastSource; use leafwing_input_manager::axislike::SingleAxis; use leafwing_input_manager::input_map::InputMap; use leafwing_input_manager::plugin::InputManagerPlugin; @@ -17,6 +18,7 @@ use crate::simulation::geometry::angle; use crate::simulation::geometry::clockwise; use crate::simulation::geometry::counterclockwise; use crate::simulation::geometry::Facing; +use crate::terrain::Terrain; use super::InteractionSystem; @@ -63,7 +65,8 @@ fn setup(mut commands: Commands) { }) .insert(CameraSettings::default()) .insert(CameraFocus::default()) - .insert(Facing::default()); + .insert(Facing::default()) + .insert(RaycastSource::::new()); } /// Actions that manipulate the camera diff --git a/emergence_lib/src/player_interaction/cursor.rs b/emergence_lib/src/player_interaction/cursor.rs index f2b42d6d8..a7a3b6c85 100644 --- a/emergence_lib/src/player_interaction/cursor.rs +++ b/emergence_lib/src/player_interaction/cursor.rs @@ -1,20 +1,22 @@ //! Keep track of the mouse cursor in world space, and convert it into a tile position, if //! available. -use bevy::math::{Vec2, Vec3}; use bevy::prelude::*; - -use crate::simulation::geometry::TilePos; +use bevy_mod_raycast::{DefaultRaycastingPlugin, RaycastMethod, RaycastSource, RaycastSystem}; use super::InteractionSystem; +use crate::{simulation::geometry::TilePos, terrain::Terrain}; -/// Initializes the [`CursorWorldPos`] and [`CursorTilePos`] resources, which are kept updated -/// updated using [`update_cursor_pos`]. -pub struct CursorTilePosPlugin; +/// Controls raycasting and cursor aethetics. +pub struct CursorPlugin; -impl Plugin for CursorTilePosPlugin { +impl Plugin for CursorPlugin { fn build(&self, app: &mut App) { - app.init_resource::() - .init_resource::() + app.init_resource::() + .add_plugin(DefaultRaycastingPlugin::::default()) + .add_system_to_stage( + CoreStage::First, + update_raycast_with_cursor.before(RaycastSystem::BuildRays::), + ) .add_system( update_cursor_pos .label(InteractionSystem::ComputeCursorPos) @@ -23,54 +25,64 @@ impl Plugin for CursorTilePosPlugin { } } -/// Converts cursor screen position into a world position, taking into account any transforms -/// applied to the camera. -pub fn cursor_pos_in_world( - windows: &Windows, - cursor_pos: Vec2, - cam_t: &Transform, - cam: &Camera, -) -> Vec3 { - let window = windows.primary(); - - let window_size = Vec2::new(window.width(), window.height()); - - // Convert screen position [0..resolution] to ndc [-1..1] - // (ndc = normalized device coordinates) - let ndc_to_world = cam_t.compute_matrix() * cam.projection_matrix().inverse(); - let ndc = (cursor_pos / window_size) * 2.0 - Vec2::ONE; - ndc_to_world.project_point3(ndc.extend(0.0)) -} - -/// The world position of the mouse cursor. -#[derive(Resource, Clone, Copy, Deref, DerefMut)] -pub struct CursorWorldPos(Vec3); - -impl Default for CursorWorldPos { - fn default() -> Self { - Self(Vec3::new(f32::INFINITY, f32::INFINITY, 0.0)) - } -} - /// The tile position of the mouse cursor, if it lies over the map. -#[derive(Resource, Default, Clone, Copy)] -pub struct CursorTilePos(Option); +#[derive(Resource, Default, Debug, Clone, Copy)] +pub struct CursorPos { + /// The terrain entity that the cursor is over top of. + terrain_entity: Option, + /// The tile position that the cursor is over top of. + tile_pos: Option, +} -impl CursorTilePos { +impl CursorPos { /// The position of the cursor in hex coordinates, if it is on the hex map. /// /// If the cursor is outside the map, this will return `None`. pub fn maybe_tile_pos(&self) -> Option { - self.0 + self.tile_pos + } + + /// The terrain entity under the cursor, if any. + /// + /// If the cursor is outside the map, this will return `None`. + pub fn maybe_entity(&self) -> Option { + self.terrain_entity } } -/// Updates which tile the cursor is hovering over -pub fn update_cursor_pos() { - // FIXME: rewrite +/// Updates the raycast with the cursor position +/// +/// This system was borrowed from +/// and used under the MIT License. Thanks! +fn update_raycast_with_cursor( + mut cursor: EventReader, + mut query: Query<&mut RaycastSource>, +) { + // Grab the most recent cursor event if it exists: + let cursor_position = match cursor.iter().last() { + Some(cursor_moved) => cursor_moved.position, + None => return, + }; + + for mut pick_source in &mut query { + pick_source.cast_method = RaycastMethod::Screenspace(cursor_position); + } } -/// Highlights the current set of selected tiles -pub fn highlight_selected_tiles() { - // FIXME: rewrite +/// Records which tile is currently under the cursor, if any +fn update_cursor_pos( + mut cursor_pos: ResMut, + camera_query: Query<&RaycastSource, With>, + terrain_query: Query<&TilePos, With>, +) { + let raycast_source = camera_query.single(); + let maybe_intersection = raycast_source.get_nearest_intersection(); + + if let Some((terrain_entity, _intersection_data)) = maybe_intersection { + cursor_pos.terrain_entity = Some(terrain_entity); + cursor_pos.tile_pos = terrain_query.get(terrain_entity).ok().copied(); + } else { + cursor_pos.terrain_entity = None; + cursor_pos.tile_pos = None; + } } diff --git a/emergence_lib/src/player_interaction/mod.rs b/emergence_lib/src/player_interaction/mod.rs index 723405826..4f49aeda5 100644 --- a/emergence_lib/src/player_interaction/mod.rs +++ b/emergence_lib/src/player_interaction/mod.rs @@ -17,7 +17,7 @@ impl Plugin for InteractionPlugin { fn build(&self, app: &mut App) { app.add_plugin(camera::CameraPlugin) .add_plugin(abilities::AbilitiesPlugin) - .add_plugin(cursor::CursorTilePosPlugin) + .add_plugin(cursor::CursorPlugin) .add_plugin(intent::IntentPlugin) .add_plugin(organism_details::DetailsPlugin) .add_plugin(tile_selection::TileSelectionPlugin) diff --git a/emergence_lib/src/player_interaction/organism_details.rs b/emergence_lib/src/player_interaction/organism_details.rs index 057d697b6..9887c4d0c 100644 --- a/emergence_lib/src/player_interaction/organism_details.rs +++ b/emergence_lib/src/player_interaction/organism_details.rs @@ -9,7 +9,7 @@ use crate::{ units::Ant, OrganismType, }, - player_interaction::cursor::CursorTilePos, + player_interaction::cursor::CursorPos, simulation::geometry::TilePos, structures::crafting::{ ActiveRecipe, CraftTimer, CraftingState, InputInventory, OutputInventory, @@ -67,7 +67,7 @@ impl Plugin for DetailsPlugin { /// Get details about the hovered entity. fn hover_details( - cursor_pos: Res, + cursor_pos: Res, mut hover_details: ResMut, query: Query<( Entity, diff --git a/emergence_lib/src/player_interaction/tile_selection.rs b/emergence_lib/src/player_interaction/tile_selection.rs index 869e45ac8..75464dfad 100644 --- a/emergence_lib/src/player_interaction/tile_selection.rs +++ b/emergence_lib/src/player_interaction/tile_selection.rs @@ -7,27 +7,20 @@ use leafwing_input_manager::{ Actionlike, }; -use crate::simulation::geometry::TilePos; +use crate::{asset_management::TileHandles, terrain::Terrain}; -use super::{ - cursor::{highlight_selected_tiles, CursorTilePos}, - InteractionSystem, -}; +use super::{cursor::CursorPos, InteractionSystem}; /// Actions that can be used to select tiles. /// /// If a tile is not selected, it will be added to the selection. /// If it is already selected, it will be removed from the selection. -#[derive(Actionlike, Clone)] +#[derive(Actionlike, Clone, Debug)] pub enum TileSelectionAction { /// Selects a single tile, deselecting any others. /// /// If the tile is already selected, it will be unselected. Single, - /// Adds or subtracts tiles from the selection. - /// - /// Unselected tiles will be selected, and selected tiles be unslected. - Modify, /// Selects or deselects a group of hex tiles by dragging over them /// /// This action will track whether you are selecting or deselecting tiles based on the state of the first tile modified with this action. @@ -56,14 +49,14 @@ impl TileSelectionAction { UserInput::Single(InputKind::Mouse(MouseButton::Left)), TileSelectionAction::Single, ), - ( - UserInput::modified(Modifier::Control, MouseButton::Left), - TileSelectionAction::Modify, - ), ( UserInput::modified(Modifier::Shift, MouseButton::Left), TileSelectionAction::Multiple, ), + ( + UserInput::Single(InputKind::Keyboard(KeyCode::Escape)), + TileSelectionAction::Clear, + ), ]) } } @@ -72,23 +65,18 @@ impl TileSelectionAction { #[derive(Resource, Debug, Default)] pub struct SelectedTiles { /// Actively selected tiles - selection: HashSet, - // TODO: use this for more efficient tile selection toggling - /// Most recently deselected tiles - previous_selection: HashSet, + selection: HashSet, } impl SelectedTiles { /// Selects a single tile - pub fn add_tile(&mut self, tile_pos: TilePos) { - self.cache_selection(); - self.selection.insert(tile_pos); + pub fn add_tile(&mut self, tile_entity: Entity) { + self.selection.insert(tile_entity); } /// Deselects a single tile - pub fn remove_tile(&mut self, tile_pos: TilePos) { - self.cache_selection(); - self.selection.remove(&tile_pos); + pub fn remove_tile(&mut self, tile_entity: Entity) { + self.selection.remove(&tile_entity); } /// Selects a single tile, at the expense of any other tiles already selected. @@ -97,15 +85,14 @@ impl SelectedTiles { /// If a tile is already selected, remove it from the selection. /// /// This is the behavior controlled by [`TileSelectionAction::Single`]. - pub fn select_single(&mut self, tile_pos: TilePos) { - self.cache_selection(); - if self.selection.contains(&tile_pos) { + pub fn select_single(&mut self, tile_entity: Entity) { + if self.selection.contains(&tile_entity) { self.selection.clear(); } else { // Clear cache then reinsert in the previous cache structure rather than making a new one // to avoid a pointless reallocation self.selection.clear(); - self.selection.insert(tile_pos); + self.selection.insert(tile_entity); } } @@ -113,35 +100,21 @@ impl SelectedTiles { /// /// If it is not selected, select it. /// If it is already selected, remove it from the selection. - /// - /// This is the behavior controlled by [`TileSelectionAction::Modify`]. - pub fn modify_selection(&mut self, tile_pos: TilePos) { - self.cache_selection(); - if self.selection.contains(&tile_pos) { - self.selection.remove(&tile_pos); + pub fn modify_selection(&mut self, tile_entity: Entity) { + if self.selection.contains(&tile_entity) { + self.selection.remove(&tile_entity); } else { - self.selection.insert(tile_pos); + self.selection.insert(tile_entity); } } /// The current set of selected tiles - pub fn selection(&self) -> &HashSet { + pub fn selection(&self) -> &HashSet { &self.selection } - /// The previous set of selected tiles - pub fn previous_selection(&self) -> &HashSet { - &self.previous_selection - } - - /// Stores the current tile selection to be used to compute the set of changed tiles efficiently - fn cache_selection(&mut self) { - self.previous_selection = self.selection.clone(); - } - /// Clears the set of selected tiles. pub fn clear_selection(&mut self) { - self.cache_selection(); self.selection.clear(); } @@ -156,24 +129,8 @@ impl SelectedTiles { } /// Is the given tile in the selection? - pub fn contains_pos(&self, tile_pos: &TilePos) -> bool { - self.selection.contains(tile_pos) - } - - /// Compute the set of newly added tiles - pub fn added_tiles(&self) -> HashSet { - self.selection - .difference(self.previous_selection()) - .copied() - .collect() - } - - /// Compute the set of newly removed tiles - pub fn removed_tiles(&self) -> HashSet { - self.previous_selection - .difference(self.selection()) - .copied() - .collect() + pub fn contains_tile(&self, tile_entity: Entity) -> bool { + self.selection.contains(&tile_entity) } } @@ -197,17 +154,19 @@ impl Plugin for TileSelectionPlugin { /// Integrates user input into tile selection actions to let other systems handle what happens to a selected tile fn select_tiles( - cursor_tile_pos: Res, + cursor_tile_entity: Res, mut selected_tiles: ResMut, actions: Res>, mut selection_mode: Local, ) { - if let Some(cursor_tile) = cursor_tile_pos.maybe_tile_pos() { + if let Some(cursor_entity) = cursor_tile_entity.maybe_entity() { if actions.pressed(TileSelectionAction::Clear) { selected_tiles.clear_selection(); - } else if actions.pressed(TileSelectionAction::Multiple) { + }; + + if actions.pressed(TileSelectionAction::Multiple) { if *selection_mode == SelectMode::None { - *selection_mode = match selected_tiles.contains_pos(&cursor_tile) { + *selection_mode = match selected_tiles.contains_tile(cursor_entity) { // If you start with a selected tile, subtract from the selection true => SelectMode::Deselect, // If you start with an unselected tile, add to the selection @@ -215,18 +174,108 @@ fn select_tiles( } } match *selection_mode { - SelectMode::Select => selected_tiles.add_tile(cursor_tile), - SelectMode::Deselect => selected_tiles.remove_tile(cursor_tile), + SelectMode::Select => selected_tiles.add_tile(cursor_entity), + SelectMode::Deselect => selected_tiles.remove_tile(cursor_entity), SelectMode::None => unreachable!(), } - } else if actions.just_pressed(TileSelectionAction::Modify) { - selected_tiles.modify_selection(cursor_tile); - } else if actions.just_pressed(TileSelectionAction::Single) { - selected_tiles.select_single(cursor_tile); + } else { + *selection_mode = SelectMode::None; + }; + + if actions.just_pressed(TileSelectionAction::Single) { + selected_tiles.select_single(cursor_entity); } + } +} - if actions.released(TileSelectionAction::Multiple) { - *selection_mode = SelectMode::None; +/// Highlights the current set of selected tiles +fn highlight_selected_tiles( + selected_tiles: Res, + mut terrain_query: Query<(Entity, &mut Handle, &Terrain)>, + materials: Res, +) { + if selected_tiles.is_changed() { + let selection = selected_tiles.selection(); + // PERF: We should probably avoid a linear scan over all tiles here + for (terrain_entity, mut material, terrain) in terrain_query.iter_mut() { + if selection.contains(&terrain_entity) { + *material = materials.selected_tile_handle.clone_weak(); + } else { + // FIXME: reset to the correct material + *material = materials.terrain_handles.get(terrain).unwrap().clone_weak(); + } } } } + +#[cfg(test)] +mod tests { + use super::SelectedTiles; + use bevy::ecs::entity::Entity; + + #[test] + fn simple_selection() { + let mut selected_tiles = SelectedTiles::default(); + let tile_entity = Entity::from_bits(0); + + selected_tiles.add_tile(tile_entity); + assert!(selected_tiles.contains_tile(tile_entity)); + assert!(!selected_tiles.is_empty()); + assert_eq!(selected_tiles.selection().len(), 1); + + selected_tiles.remove_tile(tile_entity); + assert!(!selected_tiles.contains_tile(tile_entity)); + assert!(selected_tiles.is_empty()); + assert_eq!(selected_tiles.selection().len(), 0); + } + + #[test] + fn multi_select() { + let mut selected_tiles = SelectedTiles::default(); + selected_tiles.add_tile(Entity::from_bits(0)); + // Intentionally doubled + selected_tiles.add_tile(Entity::from_bits(0)); + selected_tiles.add_tile(Entity::from_bits(1)); + selected_tiles.add_tile(Entity::from_bits(2)); + + assert_eq!(selected_tiles.selection().len(), 3); + } + + #[test] + fn clear_selection() { + let mut selected_tiles = SelectedTiles::default(); + selected_tiles.add_tile(Entity::from_bits(0)); + selected_tiles.add_tile(Entity::from_bits(1)); + selected_tiles.add_tile(Entity::from_bits(2)); + + assert_eq!(selected_tiles.selection().len(), 3); + selected_tiles.clear_selection(); + assert_eq!(selected_tiles.selection().len(), 0); + } + + #[test] + fn select_single_not_yet_selected() { + let mut selected_tiles = SelectedTiles::default(); + let existing_entity = Entity::from_bits(0); + let new_entity = Entity::from_bits(1); + + selected_tiles.add_tile(existing_entity); + + selected_tiles.select_single(new_entity); + assert_eq!(selected_tiles.selection().len(), 1); + assert!(!selected_tiles.contains_tile(existing_entity)); + assert!(selected_tiles.contains_tile(new_entity)); + } + + #[test] + fn select_single_already_selected() { + let mut selected_tiles = SelectedTiles::default(); + let existing_entity = Entity::from_bits(0); + + selected_tiles.add_tile(existing_entity); + + selected_tiles.select_single(existing_entity); + assert_eq!(selected_tiles.selection().len(), 0); + assert!(!selected_tiles.contains_tile(existing_entity)); + } +} diff --git a/emergence_lib/src/player_interaction/zoning.rs b/emergence_lib/src/player_interaction/zoning.rs index bfa101c1f..e4c1cd97d 100644 --- a/emergence_lib/src/player_interaction/zoning.rs +++ b/emergence_lib/src/player_interaction/zoning.rs @@ -5,7 +5,7 @@ use leafwing_input_manager::prelude::*; use crate::{organisms::OrganismType, simulation::geometry::TilePos}; -use super::{cursor::CursorTilePos, tile_selection::SelectedTiles, InteractionSystem}; +use super::{cursor::CursorPos, tile_selection::SelectedTiles, InteractionSystem}; /// Logic and resources for structure selection and placement. pub struct ZoningPlugin; @@ -69,7 +69,7 @@ impl ZoningAction { fn set_selected_structure( zoning_actions: Res>, mut selected_structure: ResMut, - cursor_pos: Res, + cursor_pos: Res, structure_query: Query<(&TilePos, &OrganismType)>, ) { // Clearing should take priority over selecting a new item (on the same frame) diff --git a/emergence_lib/src/terrain/mod.rs b/emergence_lib/src/terrain/mod.rs index 2008dfb8f..0f199db1c 100644 --- a/emergence_lib/src/terrain/mod.rs +++ b/emergence_lib/src/terrain/mod.rs @@ -1,6 +1,7 @@ //! Generating and representing terrain as game objects. use bevy::prelude::*; +use bevy_mod_raycast::RaycastMesh; use crate as emergence_lib; @@ -27,6 +28,8 @@ pub struct TerrainBundle { terrain_type: Terrain, /// The location of this terrain hex tile_pos: TilePos, + /// Makes the tiles pickable + raycast_mesh: RaycastMesh, } impl TerrainBundle { @@ -35,6 +38,7 @@ impl TerrainBundle { TerrainBundle { terrain_type, tile_pos, + raycast_mesh: RaycastMesh::::default(), } } }