use std::{error::Error, fmt::Debug, hash::Hash, marker::PhantomData}; use bevy::prelude::*; use bevy_input_actionmap::InputMap; use bevy_rapier2d::prelude::*; use bevy_tts::Tts; use derive_more::{Deref, DerefMut}; use mapgen::Tile; use crate::{ core::{Coordinates, Player, PointLike}, error::error_handler, map::Map, pathfinding::Destination, visibility::{RevealedTiles, Viewshed, VisibleTiles}, }; #[derive(Clone, Copy, Debug, Default, PartialEq, Reflect)] #[reflect(Component)] pub struct ExplorationFocused; #[allow(dead_code)] #[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Reflect)] pub enum ExplorationType { Portal = 0, Item = 1, Character = 2, Ally = 3, Enemy = 4, } // Doesn't make sense to create from a `String`. #[allow(clippy::from_over_into)] impl Into for ExplorationType { fn into(self) -> String { match self { ExplorationType::Portal => "Portal".into(), ExplorationType::Item => "Item".into(), ExplorationType::Character => "Character".into(), ExplorationType::Ally => "Ally".into(), ExplorationType::Enemy => "Enemy".into(), } } } // Likewise. #[allow(clippy::from_over_into)] impl Into<&str> for ExplorationType { fn into(self) -> &'static str { match self { ExplorationType::Portal => "exit", ExplorationType::Item => "item", ExplorationType::Character => "character", ExplorationType::Ally => "ally", ExplorationType::Enemy => "enemy", } } } #[derive(Clone, Copy, Debug, Default, Deref, DerefMut, Reflect)] #[reflect(Component)] pub struct Exploring(pub (f32, f32)); impl_pointlike_for_tuple_component!(Exploring); #[derive(Clone, Debug, Default, Deref, DerefMut)] pub struct FocusedExplorationType(pub Option); #[derive(Clone, Copy, Debug, Default, Reflect)] #[reflect(Component)] pub struct Mappable; fn exploration_type_change( config: Res>, mut tts: ResMut, input: Res>, mut explorers: Query<(&Player, &Viewshed, &mut FocusedExplorationType)>, features: Query<(&Coordinates, &ExplorationType)>, ) -> Result<(), Box> where S: bevy::ecs::component::Component + Clone + Debug + Eq + Hash, A: Hash + Eq + Clone + Send + Sync, { if let (Some(select_next_type), Some(select_prev_type)) = ( config.action_explore_select_next_type.clone(), config.action_explore_select_prev_type.clone(), ) { let changed = input.just_active(select_next_type.clone()) || input.just_active(select_prev_type.clone()); if !changed { return Ok(()); } for (_, viewshed, mut focused) in explorers.iter_mut() { let mut types: Vec = vec![]; for (coordinates, t) in features.iter() { let (x, y) = **coordinates; let x = x as i32; let y = y as i32; if viewshed.is_point_visible(&(x, y)) { types.push(*t); } } types.sort(); types.dedup(); if types.is_empty() { tts.speak("Nothing visible.", true)?; } else if input.just_active(select_prev_type.clone()) { if let Some(t) = &focused.0 { if let Some(i) = types.iter().position(|v| *v == *t) { if i == 0 { focused.0 = None; } else { let t = &types[i - 1]; focused.0 = Some(*t); } } else { let t = types.last().unwrap(); focused.0 = Some(*t); } } else { let t = types.last().unwrap(); focused.0 = Some(*t); } } else if input.just_active(select_next_type.clone()) { if let Some(t) = &focused.0 { if let Some(i) = types.iter().position(|v| *v == *t) { if i == types.len() - 1 { focused.0 = None; } else { let t = &types[i + 1]; focused.0 = Some(*t); } } else { let t = types.first().unwrap(); focused.0 = Some(*t); } } else { let t = types.first().unwrap(); focused.0 = Some(*t) } } } } Ok(()) } fn exploration_type_focus( mut commands: Commands, config: Res>, input: Res>, mut tts: ResMut, explorers: Query<( Entity, &Player, &Viewshed, &FocusedExplorationType, Option<&Exploring>, )>, features: Query<(&Coordinates, &ExplorationType)>, ) -> Result<(), Box> where S: bevy::ecs::component::Component + Clone + Debug + Eq + Hash, A: Hash + Eq + Clone + Send + Sync, { if let (Some(explore_focus_next), Some(explore_focus_prev)) = ( config.action_explore_focus_next.clone(), config.action_explore_focus_prev.clone(), ) { let changed = input.just_active(explore_focus_next.clone()) || input.just_active(explore_focus_prev.clone()); if !changed { return Ok(()); } for (entity, _, viewshed, focused, exploring) in explorers.iter() { let mut features = features .iter() .filter(|(coordinates, _)| { let (x, y) = ***coordinates; let x = x as i32; let y = y as i32; viewshed.is_point_visible(&(x, y)) }) .collect::>(); features.sort_by(|(c1, _), (c2, _)| c1.partial_cmp(c2).unwrap()); if let Some(focused) = &focused.0 { features.retain(|(_, t)| **t == *focused); } if features.is_empty() { tts.speak("Nothing visible.", true)?; } else { let mut target: Option<&(&Coordinates, &ExplorationType)> = None; if input.just_active(explore_focus_next.clone()) { if let Some(exploring) = exploring { target = features.iter().find(|(c, _)| ***c > **exploring); if target.is_none() { target = features.first(); } } else { target = features.first(); } } else if input.just_active(explore_focus_prev.clone()) { if let Some(exploring) = exploring { features.reverse(); target = features.iter().find(|(c, _)| ***c < **exploring); if target.is_none() { target = features.first(); } } else { target = features.last(); } } if let Some((coordinates, _)) = target { commands.entity(entity).insert(Exploring(***coordinates)); } } } } Ok(()) } fn exploration_type_changed_announcement( mut tts: ResMut, focused: Query< ( &FocusedExplorationType, ChangeTrackers, ), Changed, >, ) -> Result<(), Box> { for (focused, changed) in focused.iter() { if changed.is_added() { return Ok(()); } match &focused.0 { Some(v) => { let v: String = (*v).into(); tts.speak(v, true)?; } None => { tts.speak("Everything", true)?; } }; } Ok(()) } fn exploration_focus( mut commands: Commands, config: Res>, input: Res>, map: Query<&Map>, explorers: Query<(Entity, &Coordinates, Option<&Exploring>), With>, ) where S: bevy::ecs::component::Component + Clone + Debug + Eq + Hash, A: Hash + Eq + Clone + Send + Sync, { if let ( Some(explore_forward), Some(explore_backward), Some(explore_left), Some(explore_right), ) = ( config.action_explore_forward.clone(), config.action_explore_backward.clone(), config.action_explore_left.clone(), config.action_explore_right.clone(), ) { println!("Here1"); for map in map.iter() { println!("Here2"); if let Ok((entity, coordinates, exploring)) = explorers.single() { println!("Here"); let coordinates = coordinates.i32(); let coordinates = (coordinates.x() + 0.5, coordinates.y() + 0.5); let mut exploring = if let Some(exploring) = exploring { **exploring } else { coordinates }; let orig = exploring; if input.just_active(explore_forward.clone()) { exploring.1 += 1.; } else if input.just_active(explore_backward.clone()) { exploring.1 -= 1.; } else if input.just_active(explore_left.clone()) { exploring.0 -= 1.; } else if input.just_active(explore_right.clone()) { exploring.0 += 1.; } if orig != exploring && exploring.0 >= 0. && exploring.0 < map.width as f32 && exploring.1 >= 0. && exploring.1 < map.height as f32 { commands.entity(entity).insert(Exploring(exploring)); } } } } } fn navigate_to_explored( mut commands: Commands, config: Res>, input: Res>, map: Query<(&Map, &RevealedTiles)>, explorers: Query<(Entity, &Exploring)>, ) where S: bevy::ecs::component::Component + Clone + Debug + Eq + Hash, A: Hash + Eq + Clone + Send + Sync, { if let Some(navigate_to_explored) = config.action_navigate_to_explored.clone() { for (entity, exploring) in explorers.iter() { for (map, revealed_tiles) in map.iter() { let point = **exploring; let idx = point.to_index(map.width); let known = revealed_tiles[idx]; if input.just_active(navigate_to_explored.clone()) && known { commands .entity(entity) .insert(Destination((point.x_i32(), point.y_i32()))); } } } } } fn exploration_changed_announcement( mut commands: Commands, mut tts: ResMut, map: Query<(&Map, &RevealedTiles, &VisibleTiles)>, explorer: Query<(&Coordinates, &Exploring), Changed>, focused: Query>, names: Query<&Name>, types: Query<&ExplorationType>, mappables: Query<&Mappable>, query_pipeline: Res, collider_query: QueryPipelineColliderComponentsQuery, ) -> Result<(), Box> { if let Ok((coordinates, exploring)) = explorer.single() { let collider_set = QueryPipelineColliderComponentsSet(&collider_query); let coordinates = **coordinates; let coordinates = (coordinates.0.floor(), coordinates.1.floor()); for (map, revealed_tiles, visible_tiles) in map.iter() { let point = **exploring; let idx = point.to_index(map.width); let shape = Cuboid::new(Vec2::new(0.5, 0.5).into()); let known = revealed_tiles[idx]; let visible = visible_tiles[idx]; let fog_of_war = known && !visible; let description = if known { let mut tokens: Vec<&str> = vec![]; for entity in focused.iter() { commands.entity(entity).remove::(); } let shape_pos = (Vec2::new(exploring.x(), exploring.y()), 0.); query_pipeline.intersections_with_shape( &collider_set, &shape_pos.into(), &shape, InteractionGroups::all(), None, |handle| { let entity = handle.entity(); commands .entity(entity) .insert(ExplorationFocused::default()); if visible || mappables.get(entity).is_ok() { if let Ok(name) = names.get(entity) { tokens.push(name.as_str()); } if tokens.is_empty() { if let Ok(t) = types.get(entity) { tokens.push((*t).into()); } } } true }, ); if tokens.is_empty() { let tile = map.tiles[idx]; if tile.is_blocked() { "wall".to_string() } else { "floor".to_string() } } else { tokens.join(": ") } } else { "Unknown".to_string() }; let mut tokens: Vec = vec![coordinates.direction_and_distance(exploring, None)]; if fog_of_war { tokens.push("in the fog of war".into()); } tts.speak(format!("{}: {}", description, tokens.join(", ")), true)?; } } Ok(()) } fn cleanup( mut commands: Commands, explorers: Query>, focus: Query>, ) { for entity in explorers.iter() { commands.entity(entity).remove::(); } for entity in focus.iter() { commands.entity(entity).despawn_recursive(); } } #[derive(Clone, Debug)] pub struct ExplorationConfig { pub exploration_control_states: Vec, pub action_explore_forward: Option, pub action_explore_backward: Option, pub action_explore_left: Option, pub action_explore_right: Option, pub action_explore_focus_next: Option, pub action_explore_focus_prev: Option, pub action_explore_select_next_type: Option, pub action_explore_select_prev_type: Option, pub action_navigate_to_explored: Option, } impl Default for ExplorationConfig { fn default() -> Self { Self { exploration_control_states: vec![], action_explore_forward: None, action_explore_backward: None, action_explore_left: None, action_explore_right: None, action_explore_focus_next: None, action_explore_focus_prev: None, action_explore_select_next_type: None, action_explore_select_prev_type: None, action_navigate_to_explored: None, } } } pub struct ExplorationPlugin<'a, S, A>(PhantomData<&'a S>, PhantomData<&'a A>); impl<'a, S, A> Default for ExplorationPlugin<'a, S, A> { fn default() -> Self { Self(PhantomData, PhantomData) } } impl<'a, S, A> Plugin for ExplorationPlugin<'a, S, A> where S: bevy::ecs::component::Component + Clone + Debug + Eq + Hash, A: Hash + Eq + Clone + Send + Sync, 'a: 'static, { fn build(&self, app: &mut AppBuilder) { if !app.world().contains_resource::>() { app.insert_resource(ExplorationConfig::::default()); } let config = app .world() .get_resource::>() .unwrap() .clone(); app.register_type::() .register_type::() .register_type::() .add_system_to_stage( CoreStage::PostUpdate, exploration_changed_announcement .system() .chain(error_handler.system()), ); if config.exploration_control_states.is_empty() { app.add_system(exploration_focus::.system()) .add_system( exploration_type_focus:: .system() .chain(error_handler.system()), ) .add_system( exploration_type_change:: .system() .chain(error_handler.system()), ) .add_system(navigate_to_explored::.system()) .add_system_to_stage( CoreStage::PostUpdate, exploration_type_changed_announcement .system() .chain(error_handler.system()), ); } else { let states = config.exploration_control_states; for state in states { app.add_system_set( SystemSet::on_update(state.clone()) .with_system(exploration_focus::.system()) .with_system( exploration_type_focus:: .system() .chain(error_handler.system()), ) .with_system( exploration_type_change:: .system() .chain(error_handler.system()), ) .with_system(navigate_to_explored::.system()) .with_system( exploration_type_changed_announcement .system() .chain(error_handler.system()), ), ) .add_system_set(SystemSet::on_exit(state).with_system(cleanup.system())); } } } }