use std::{ cmp::{max, min}, f32::consts::PI, fmt::Display, marker::PhantomData, ops::{Add, AddAssign, Sub, SubAssign}, }; use bevy::{core::FloatOrd, ecs::query::WorldQuery, prelude::*, transform::TransformSystem}; use bevy_rapier2d::prelude::*; use derive_more::{Deref, DerefMut}; use rand::prelude::*; #[derive(Clone, Copy, Debug, Default, Deref, DerefMut, PartialEq, PartialOrd, Reflect)] #[reflect(Component)] pub struct Coordinates(pub (f32, f32)); impl Coordinates { pub fn from_transform(transform: &Transform, config: &CoreConfig) -> Self { Self(( transform.x() / config.pixels_per_unit as f32, transform.y() / config.pixels_per_unit as f32, )) } pub fn to_transform(&self, config: &CoreConfig) -> Transform { Transform::from_translation(Vec3::new( self.0 .0 * config.pixels_per_unit as f32, self.0 .1 * config.pixels_per_unit as f32, 0., )) } } impl Add for Coordinates { type Output = Self; fn add(self, rhs: Self) -> Self::Output { Self((self.0 .0 + rhs.0 .0, self.0 .1 + rhs.0 .1)) } } impl AddAssign for Coordinates { fn add_assign(&mut self, rhs: Self) { *self = *self + rhs } } impl Sub for Coordinates { type Output = Self; fn sub(self, rhs: Self) -> Self::Output { Self((self.0 .0 - rhs.0 .0, self.0 .1 - rhs.0 .1)) } } impl SubAssign for Coordinates { fn sub_assign(&mut self, rhs: Self) { *self = *self - rhs } } impl From for Coordinates { fn from(v: Vec2) -> Self { Self((v.x, v.y)) } } impl From for Coordinates { fn from(v: Vec3) -> Self { Self((v.x, v.y)) } } impl From for Vec2 { fn from(c: Coordinates) -> Self { Vec2::new(c.0 .0, c.0 .1) } } impl From for Vec3 { fn from(c: Coordinates) -> Self { Vec3::new(c.0 .0, c.0 .1, 0.) } } impl From<(f32, f32)> for Coordinates { fn from(v: (f32, f32)) -> Self { Coordinates((v.0, v.1)) } } impl From<(i32, i32)> for Coordinates { fn from(v: (i32, i32)) -> Self { Coordinates((v.0 as f32, v.1 as f32)) } } impl From<(u32, u32)> for Coordinates { fn from(v: (u32, u32)) -> Self { Coordinates((v.0 as f32, v.1 as f32)) } } impl From<(usize, usize)> for Coordinates { fn from(v: (usize, usize)) -> Self { Coordinates((v.0 as f32, v.1 as f32)) } } #[derive(Clone, Copy, Debug, Reflect)] pub enum Angle { Degrees(f32), Radians(f32), } impl Default for Angle { fn default() -> Self { Self::Radians(0.) } } impl Angle { pub fn degrees(&self) -> f32 { use Angle::*; let mut degrees: f32 = match self { Degrees(v) => *v, Radians(v) => v.to_degrees(), }; while degrees < 0. { degrees += 360.; } while degrees >= 360. { degrees %= 360.; } degrees } pub fn degrees_u32(&self) -> u32 { self.degrees() as u32 } pub fn radians(&self) -> f32 { use Angle::*; match self { Degrees(v) => v.to_radians(), Radians(v) => *v, } } } impl Sub for Angle { type Output = Self; fn sub(self, rhs: Self) -> Self::Output { match self { Angle::Degrees(v1) => match rhs { Angle::Degrees(v2) => Angle::Degrees(v1 - v2), Angle::Radians(v2) => Angle::Degrees(v1 - v2.to_degrees()), }, Angle::Radians(v1) => match rhs { Angle::Degrees(v2) => Angle::Radians(v1 - v2.to_radians()), Angle::Radians(v2) => Angle::Radians(v1 - v2), }, } } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum MovementDirection { North, NorthNortheast, Northeast, EastNortheast, East, EastSoutheast, Southeast, SouthSoutheast, South, SouthSouthwest, Southwest, WestSouthwest, West, WestNorthwest, Northwest, NorthNorthwest, } impl MovementDirection { pub fn new(heading: f32) -> Self { use MovementDirection::*; let mut heading = heading; while heading >= 360. { heading -= 360.; } while heading < 0. { heading += 360.; } match heading { h if h < 11.5 => East, h if h < 34.0 => EastNortheast, h if h < 56.5 => Northeast, h if h < 79.0 => NorthNortheast, h if h < 101.5 => North, h if h < 124.0 => NorthNorthwest, h if h < 146.5 => Northwest, h if h < 169.0 => WestNorthwest, h if h < 191.5 => West, h if h < 214.0 => WestSouthwest, h if h < 236.5 => Southwest, h if h < 259.0 => SouthSouthwest, h if h < 281.5 => South, h if h < 304.0 => SouthSoutheast, h if h < 326.5 => Southeast, h if h <= 349.0 => EastSoutheast, _ => East, } } } impl From for MovementDirection { fn from(angle: Angle) -> Self { MovementDirection::new(angle.degrees()) } } // Converting from strings into directions doesn't make sense. #[allow(clippy::from_over_into)] impl Into for MovementDirection { fn into(self) -> String { use MovementDirection::*; match self { North => "north".to_string(), NorthNortheast => "north northeast".to_string(), Northeast => "northeast".to_string(), EastNortheast => "east northeast".to_string(), East => "east".to_string(), EastSoutheast => "east southeast".to_string(), Southeast => "southeast".to_string(), SouthSoutheast => "south southeast".to_string(), South => "south".to_string(), SouthSouthwest => "south southwest".to_string(), Southwest => "southwest".to_string(), WestSouthwest => "west southwest".to_string(), West => "west".to_string(), WestNorthwest => "west northwest".to_string(), Northwest => "northwest".to_string(), NorthNorthwest => "north northwest".to_string(), } } } impl Display for MovementDirection { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let str: String = (*self).into(); write!(f, "{}", str) } } #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub enum CardinalDirection { North, East, South, West, } impl CardinalDirection { pub fn new(heading: f32) -> Self { use CardinalDirection::*; let mut heading = heading; while heading >= 360. { heading -= 360.; } while heading < 0. { heading += 360.; } match heading { h if h <= 45. => East, h if h <= 135. => North, h if h <= 225. => West, h if h <= 315. => South, _ => East, } } pub fn angle(&self) -> Angle { match self { CardinalDirection::North => Angle::Radians(PI / 2.), CardinalDirection::East => Angle::Radians(0.), CardinalDirection::South => Angle::Radians(-PI / 2.), CardinalDirection::West => Angle::Radians(PI), } } } impl From for CardinalDirection { fn from(angle: Angle) -> Self { CardinalDirection::new(angle.degrees()) } } // Converting from strings into directions doesn't make sense. #[allow(clippy::from_over_into)] impl Into for CardinalDirection { fn into(self) -> String { use CardinalDirection::*; match self { North => "north".to_string(), East => "east".to_string(), South => "south".to_string(), West => "west".to_string(), } } } impl Display for CardinalDirection { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let str: String = (*self).into(); write!(f, "{}", str) } } pub trait PointLike { fn x(&self) -> f32; fn y(&self) -> f32; fn x_i32(&self) -> i32 { self.x() as i32 } fn y_i32(&self) -> i32 { self.y() as i32 } fn x_usize(&self) -> usize { self.x() as usize } fn y_usize(&self) -> usize { self.y() as usize } fn f32(&self) -> (f32, f32) { (self.x(), self.y()) } fn i32(&self) -> (i32, i32) { (self.x_i32(), self.y_i32()) } fn to_index(&self, width: usize) -> usize { ((self.y_i32() * width as i32) + self.x_i32()) as usize } fn distance_squared(&self, other: &dyn PointLike) -> f32 { let x1 = FloatOrd(self.x()); let y1 = FloatOrd(self.y()); let x2 = FloatOrd(other.x()); let y2 = FloatOrd(other.y()); let dx = max(x1, x2).0 - min(x1, x2).0; let dy = max(y1, y2).0 - min(y1, y2).0; (dx * dx) + (dy * dy) } fn distance(&self, other: &dyn PointLike) -> f32 { self.distance_squared(other).sqrt() } fn bearing(&self, other: &dyn PointLike) -> Angle { let y = other.y() - self.y(); let x = other.x() - self.x(); Angle::Radians(y.atan2(x)) } fn direction(&self, other: &dyn PointLike) -> MovementDirection { self.bearing(other).into() } fn direction_and_distance(&self, other: &dyn PointLike, yaw: Option) -> String { let mut tokens: Vec = vec![]; let distance = self.distance(other).round() as i32; if distance > 0 { let tile_or_tiles = if distance == 1 { "tile" } else { "tiles" }; let direction: String = if let Some(yaw) = yaw { let bearing = self.bearing(other); let relative = (bearing - yaw).degrees(); match relative { v if v <= 15. => "ahead".into(), v if v <= 45. => "ahead and left".into(), v if v <= 75. => "left and ahead".into(), v if v <= 105. => "left".into(), v if v <= 135. => "left and behind".into(), v if v <= 165. => "behind and left".into(), v if v <= 195. => "behind".into(), v if v <= 225. => "behind and right".into(), v if v <= 255. => "right and behind".into(), v if v <= 285. => "right".into(), v if v <= 315. => "right and ahead".into(), v if v <= 345. => "ahead and right".into(), _ => "ahead".into(), } } else { self.direction(other).into() }; tokens.push(format!("{} {} {}", direction, distance, tile_or_tiles)); } tokens.join(" ") } } impl PointLike for Transform { fn x(&self) -> f32 { self.translation.x } fn y(&self) -> f32 { self.translation.y } } impl PointLike for Vec2 { fn x(&self) -> f32 { self.x } fn y(&self) -> f32 { self.y } } impl PointLike for (i32, i32) { fn x(&self) -> f32 { self.0 as f32 } fn y(&self) -> f32 { self.1 as f32 } } impl PointLike for (f32, f32) { fn x(&self) -> f32 { self.0 } fn y(&self) -> f32 { self.1 } } impl PointLike for (usize, usize) { fn x(&self) -> f32 { self.0 as f32 } fn y(&self) -> f32 { self.1 as f32 } } impl PointLike for &Coordinates { fn x(&self) -> f32 { self.0 .0 } fn y(&self) -> f32 { self.0 .1 } } impl PointLike for mapgen::geometry::Point { fn x(&self) -> f32 { self.x as f32 } fn y(&self) -> f32 { self.y as f32 } } #[macro_export] macro_rules! impl_pointlike_for_tuple_component { ($source:ty) => { impl PointLike for $source { fn x(&self) -> f32 { self.0 .0 as f32 } fn y(&self) -> f32 { self.0 .1 as f32 } } }; } impl_pointlike_for_tuple_component!(Coordinates); impl From<&dyn PointLike> for (i32, i32) { fn from(val: &dyn PointLike) -> Self { val.i32() } } #[derive(Clone, Copy, Debug, Default, Reflect)] #[reflect(Component)] pub struct Player; #[derive(Clone, Copy, Debug)] pub struct Pool { pub value: T, pub max: T, } #[derive(Clone, Debug)] pub struct RandomTable(Vec) where T: Clone; impl RandomTable where T: Clone, { pub fn add(&mut self, value: T, weight: u32) -> &mut Self { for _ in 0..weight { self.0.push(value.clone()); } self } } impl Default for RandomTable where T: Clone, { fn default() -> Self { Self(vec![]) } } impl Iterator for RandomTable where T: Clone, { type Item = T; fn next(&mut self) -> Option { let mut rng = thread_rng(); self.0.shuffle(&mut rng); self.0.get(0).cloned() } } fn setup(core_config: Res, mut rapier_config: ResMut) { rapier_config.scale = core_config.pixels_per_unit as f32; } fn copy_coordinates_to_transform( config: Res, mut query: Query< (&Coordinates, &mut Transform), ( Changed, Without, Without, ), >, ) { for (coordinates, mut transform) in query.iter_mut() { let x = coordinates.0 .0 * config.pixels_per_unit as f32; if transform.translation.x != x { transform.translation.x = x; } let y = coordinates.0 .1 * config.pixels_per_unit as f32; if transform.translation.y != y { transform.translation.y = y; } } } fn copy_rigid_body_position_to_coordinates( mut query: Query<(&mut Coordinates, &RigidBodyPosition), Changed>, ) { for (mut coordinates, position) in query.iter_mut() { if coordinates.0 .0 != position.position.translation.x { coordinates.0 .0 = position.position.translation.x; } if coordinates.0 .1 != position.position.translation.y { coordinates.0 .1 = position.position.translation.y; } } } fn copy_collider_position_to_coordinates( mut query: Query< (&mut Coordinates, &ColliderPosition), (Without, Changed), >, ) { for (mut coordinates, position) in query.iter_mut() { if coordinates.0 .0 != position.translation.x { coordinates.0 .0 = position.translation.x; } if coordinates.0 .1 != position.translation.y { coordinates.0 .1 = position.translation.y; } } } #[derive(Clone, Copy, Debug)] pub struct CoreConfig { pub pixels_per_unit: u8, } impl Default for CoreConfig { fn default() -> Self { Self { pixels_per_unit: 1 } } } pub struct CorePlugin; impl Plugin for CorePlugin { fn build(&self, app: &mut AppBuilder) { if !app.world().contains_resource::() { app.insert_resource(CoreConfig::default()); } app.register_type::() .add_startup_system(setup.system()) .add_system_to_stage( CoreStage::PostUpdate, copy_coordinates_to_transform .system() .before(TransformSystem::TransformPropagate), ) .add_system_to_stage( CoreStage::PostUpdate, copy_rigid_body_position_to_coordinates.system(), ) .add_system_to_stage( CoreStage::PostUpdate, copy_collider_position_to_coordinates.system(), ); } } pub struct CorePlugins(PhantomData); impl Default for CorePlugins { fn default() -> Self { Self(PhantomData) } } impl PluginGroup for CorePlugins { fn build(&mut self, group: &mut bevy::app::PluginGroupBuilder) { group .add(crate::bevy_tts::TtsPlugin) .add(crate::bevy_openal::OpenAlPlugin) .add(RapierPhysicsPlugin::::default()) .add(CorePlugin); } }