blackout/src/core.rs

750 lines
19 KiB
Rust
Raw Normal View History

2021-05-13 17:25:45 +00:00
use std::{
cmp::{max, min},
f32::consts::PI,
2021-05-13 17:25:45 +00:00
fmt::Display,
2021-06-11 00:15:50 +00:00
marker::PhantomData,
2021-06-08 15:10:59 +00:00
ops::{Add, AddAssign, Sub, SubAssign},
sync::RwLock,
2021-05-13 17:25:45 +00:00
};
2021-06-11 00:15:50 +00:00
use bevy::{core::FloatOrd, ecs::query::WorldQuery, prelude::*, transform::TransformSystem};
2021-06-02 19:47:49 +00:00
use bevy_rapier2d::prelude::*;
2021-05-13 17:25:45 +00:00
use derive_more::{Deref, DerefMut};
use once_cell::sync::Lazy;
2021-06-02 16:40:40 +00:00
use rand::prelude::*;
use serde::{Deserialize, Serialize};
2021-05-13 17:25:45 +00:00
#[derive(
Component, Clone, Copy, Debug, Default, Deref, DerefMut, PartialEq, PartialOrd, Reflect,
)]
2021-05-13 17:25:45 +00:00
#[reflect(Component)]
pub struct Coordinates(pub (f32, f32));
2021-06-08 15:10:59 +00:00
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 {
2021-06-08 15:10:59 +00:00
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<Vec2> for Coordinates {
fn from(v: Vec2) -> Self {
Self((v.x, v.y))
}
}
impl From<Vec3> for Coordinates {
fn from(v: Vec3) -> Self {
Self((v.x, v.y))
}
}
impl From<Coordinates> for Vec2 {
fn from(c: Coordinates) -> Self {
Vec2::new(c.0 .0, c.0 .1)
}
}
impl From<Coordinates> for Vec3 {
fn from(c: Coordinates) -> Self {
Vec3::new(c.0 .0, c.0 .1, 0.)
}
}
2021-05-13 17:25:45 +00:00
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,
}
}
}
2021-05-16 22:24:26 +00:00
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),
},
}
}
}
2021-05-13 17:25:45 +00:00
#[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 From<Angle> for MovementDirection {
fn from(angle: Angle) -> Self {
let heading = angle.degrees();
2021-05-13 17:25:45 +00:00
use MovementDirection::*;
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,
}
}
}
// Converting from strings into directions doesn't make sense.
#[allow(clippy::from_over_into)]
impl Into<String> 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)
}
}
2022-01-10 19:50:52 +00:00
#[derive(Component, Clone, Copy, Debug, Eq, PartialEq)]
2021-05-13 17:25:45 +00:00
pub enum CardinalDirection {
North,
East,
South,
West,
}
impl From<Angle> for CardinalDirection {
fn from(angle: Angle) -> Self {
let heading = angle.degrees();
2021-05-13 17:25:45 +00:00
use CardinalDirection::*;
match heading {
h if h <= 45. => East,
h if h <= 135. => North,
h if h <= 225. => West,
h if h <= 315. => South,
_ => East,
}
}
}
impl From<&CardinalDirection> for Angle {
fn from(direction: &CardinalDirection) -> Self {
use CardinalDirection::*;
match direction {
North => Angle::Radians(PI / 2.),
East => Angle::Radians(0.),
South => Angle::Radians(PI * 1.5),
West => Angle::Radians(PI),
}
2021-05-13 17:25:45 +00:00
}
}
// Converting from strings into directions doesn't make sense.
#[allow(clippy::from_over_into)]
impl Into<String> 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)
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum RelativeDirectionMode {
ClockFacing,
Directional,
}
static RELATIVE_DIRECTION_MODE: Lazy<RwLock<RelativeDirectionMode>> = Lazy::new(|| {
let v = RelativeDirectionMode::Directional;
RwLock::new(v)
});
2021-05-13 17:25:45 +00:00
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
}
2021-05-24 19:02:22 +00:00
fn f32(&self) -> (f32, f32) {
(self.x(), self.y())
}
2021-05-13 17:25:45 +00:00
fn i32(&self) -> (i32, i32) {
(self.x_i32(), self.y_i32())
}
fn floor(&self) -> (f32, f32) {
(self.x().floor(), self.y().floor())
}
2021-05-13 17:25:45 +00:00
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()
}
2021-05-16 22:24:26 +00:00
fn bearing(&self, other: &dyn PointLike) -> Angle {
2021-05-13 17:25:45 +00:00
let y = other.y() - self.y();
let x = other.x() - self.x();
2021-05-16 22:24:26 +00:00
Angle::Radians(y.atan2(x))
2021-05-13 17:25:45 +00:00
}
fn direction(&self, other: &dyn PointLike) -> MovementDirection {
2021-05-16 22:24:26 +00:00
self.bearing(other).into()
2021-05-13 17:25:45 +00:00
}
2021-05-16 22:24:26 +00:00
fn direction_and_distance(&self, other: &dyn PointLike, yaw: Option<Angle>) -> String {
2021-05-13 17:25:45 +00:00
let mut tokens: Vec<String> = vec![];
let distance = self.distance(other).round() as i32;
if distance > 0 {
let tile_or_tiles = if distance == 1 { "tile" } else { "tiles" };
2021-05-16 22:24:26 +00:00
let direction: String = if let Some(yaw) = yaw {
let mode = RELATIVE_DIRECTION_MODE.read().unwrap();
2021-05-16 22:24:26 +00:00
let bearing = self.bearing(other);
let relative = (bearing - yaw).degrees();
match relative {
v if v <= 15. => "ahead",
v if v <= 45. => {
if *mode == RelativeDirectionMode::ClockFacing {
"11:00"
} else {
"ahead and left"
}
}
v if v <= 75. => {
if *mode == RelativeDirectionMode::ClockFacing {
"10:00"
} else {
"left and ahead"
}
}
v if v <= 105. => "left",
v if v <= 135. => {
if *mode == RelativeDirectionMode::ClockFacing {
"8:00"
} else {
"left and behind"
}
}
v if v <= 165. => {
if *mode == RelativeDirectionMode::ClockFacing {
"7:00"
} else {
"behind and left"
}
}
v if v <= 195. => "behind",
v if v <= 225. => {
if *mode == RelativeDirectionMode::ClockFacing {
"5:00"
} else {
"behind and right"
}
}
v if v <= 255. => {
if *mode == RelativeDirectionMode::ClockFacing {
"4:00"
} else {
"right and behind"
}
}
v if v <= 285. => "right",
v if v <= 315. => {
if *mode == RelativeDirectionMode::ClockFacing {
"2:00"
} else {
"right and ahead"
}
}
v if v <= 345. => {
if *mode == RelativeDirectionMode::ClockFacing {
"1:00"
} else {
"ahead and right"
}
}
_ => "ahead",
2021-05-16 22:24:26 +00:00
}
.into()
2021-05-16 22:24:26 +00:00
} else {
self.direction(other).into()
};
2022-01-19 22:38:32 +00:00
tokens.push(format!("{direction} {distance} {tile_or_tiles}"));
2021-05-13 17:25:45 +00:00
}
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
}
}
2021-05-13 17:25:45 +00:00
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
}
}
2022-03-18 20:58:27 +00:00
impl PointLike for here_be_dragons::geometry::Point {
2021-05-13 17:25:45 +00:00
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 {
2021-05-24 19:02:22 +00:00
val.i32()
2021-05-13 17:25:45 +00:00
}
}
2022-01-10 19:50:52 +00:00
#[derive(Component, Clone, Copy, Debug, Default, Reflect)]
2021-05-13 17:25:45 +00:00
#[reflect(Component)]
pub struct Player;
#[derive(Clone, Copy, Debug)]
pub struct Pool<T> {
pub value: T,
pub max: T,
}
impl<T> Default for Pool<T>
where
T: Default,
{
fn default() -> Self {
Self {
value: Default::default(),
max: Default::default(),
}
}
}
2021-06-02 16:40:40 +00:00
#[derive(Clone, Debug)]
pub struct RandomTable<T>(Vec<T>)
where
T: Clone;
impl<T> RandomTable<T>
where
T: Clone,
{
pub fn add(&mut self, value: T, weight: i32) -> &mut Self {
if weight > 0 {
for _ in 0..weight {
self.0.push(value.clone());
}
2021-06-02 16:40:40 +00:00
}
self
}
}
impl<T> Default for RandomTable<T>
where
T: Clone,
{
fn default() -> Self {
Self(vec![])
}
}
impl<T> Iterator for RandomTable<T>
where
T: Clone,
{
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
let mut rng = thread_rng();
self.0.shuffle(&mut rng);
self.0.get(0).cloned()
}
}
2021-06-02 19:47:49 +00:00
fn setup(core_config: Res<CoreConfig>, mut rapier_config: ResMut<RapierConfiguration>) {
rapier_config.scale = core_config.pixels_per_unit as f32;
let mut mode = RELATIVE_DIRECTION_MODE.write().unwrap();
*mode = core_config.relative_direction_mode;
2021-06-02 19:47:49 +00:00
}
2021-05-13 17:25:45 +00:00
fn copy_coordinates_to_transform(
config: Res<CoreConfig>,
mut query: Query<
(&Coordinates, &mut Transform),
(
Changed<Transform>,
2022-01-10 19:50:52 +00:00
Without<RigidBodyPositionComponent>,
2022-01-11 05:05:51 +00:00
Without<RigidBodyPositionSync>,
2022-01-10 19:50:52 +00:00
Without<ColliderPositionComponent>,
2022-01-11 05:05:51 +00:00
Without<ColliderPositionSync>,
),
>,
2021-05-13 17:25:45 +00:00
) {
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;
}
2021-05-13 17:25:45 +00:00
}
}
fn copy_rigid_body_position_to_coordinates(
mut query: Query<
2022-01-10 19:50:52 +00:00
(&mut Coordinates, &RigidBodyPositionComponent),
(
Changed<RigidBodyPositionComponent>,
With<RigidBodyPositionSync>,
),
>,
) {
for (mut coordinates, position) in query.iter_mut() {
2021-06-09 22:24:56 +00:00
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<
2022-01-10 19:50:52 +00:00
(&mut Coordinates, &ColliderPositionComponent),
(
2022-01-10 19:50:52 +00:00
Without<RigidBodyPositionComponent>,
Changed<ColliderPositionComponent>,
2022-01-11 05:05:51 +00:00
With<ColliderPositionSync>,
),
>,
) {
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;
}
}
}
2021-05-13 17:25:45 +00:00
#[derive(Clone, Copy, Debug)]
pub struct CoreConfig {
pub relative_direction_mode: RelativeDirectionMode,
2021-05-13 17:25:45 +00:00
pub pixels_per_unit: u8,
}
impl Default for CoreConfig {
fn default() -> Self {
Self {
relative_direction_mode: RelativeDirectionMode::Directional,
pixels_per_unit: 1,
}
}
}
fn sync_config(config: Res<CoreConfig>) {
if config.is_changed() {
let mut mode = RELATIVE_DIRECTION_MODE.write().unwrap();
*mode = config.relative_direction_mode;
2021-05-13 17:25:45 +00:00
}
}
pub struct CorePlugin;
impl Plugin for CorePlugin {
2022-01-10 19:50:52 +00:00
fn build(&self, app: &mut App) {
2022-01-11 05:05:51 +00:00
if !app.world.contains_resource::<CoreConfig>() {
2021-05-13 17:25:45 +00:00
app.insert_resource(CoreConfig::default());
}
app.register_type::<Coordinates>()
2022-01-10 19:50:52 +00:00
.add_startup_system(setup)
2021-05-13 17:25:45 +00:00
.add_system_to_stage(
CoreStage::PostUpdate,
copy_coordinates_to_transform.before(TransformSystem::TransformPropagate),
)
.add_system_to_stage(
CoreStage::PostUpdate,
2022-01-10 19:50:52 +00:00
copy_rigid_body_position_to_coordinates,
)
2022-01-10 19:50:52 +00:00
.add_system_to_stage(CoreStage::PostUpdate, copy_collider_position_to_coordinates)
.add_system(sync_config);
2021-05-13 17:25:45 +00:00
}
}
2021-06-11 00:15:50 +00:00
pub struct CorePlugins<RapierUserData>(PhantomData<RapierUserData>);
2021-05-13 17:25:45 +00:00
2021-06-11 00:15:50 +00:00
impl<RapierUserData> Default for CorePlugins<RapierUserData> {
fn default() -> Self {
Self(PhantomData)
}
}
impl<RapierUserData: 'static + WorldQuery + Send + Sync> PluginGroup
for CorePlugins<RapierUserData>
{
2021-05-13 17:25:45 +00:00
fn build(&mut self, group: &mut bevy::app::PluginGroupBuilder) {
group
.add(crate::bevy_tts::TtsPlugin)
.add(crate::bevy_openal::OpenAlPlugin)
2021-06-11 00:15:50 +00:00
.add(RapierPhysicsPlugin::<RapierUserData>::default())
.add(CorePlugin);
2021-05-13 17:25:45 +00:00
}
}