Bevy migration. Improved many things on the way.
parent
f256c54fa7
commit
bb73f8d589
|
|
@ -1 +1,3 @@
|
||||||
/target
|
/target
|
||||||
|
/graphs
|
||||||
|
*.bin
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
14
Cargo.toml
14
Cargo.toml
|
|
@ -6,8 +6,22 @@ edition = "2021"
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
bevy = "0.16.1"
|
||||||
|
bevy-inspector-egui = "0.32.0"
|
||||||
|
bevy_egui = "0.35.1"
|
||||||
|
bevy_mod_debugdump = "0.13.0"
|
||||||
|
bevy_prototype_lyon = "0.13.0"
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
enum_dispatch = "0.3.12"
|
enum_dispatch = "0.3.12"
|
||||||
fastrand = "2.0.1"
|
fastrand = "2.0.1"
|
||||||
|
num-traits = "0.2.19"
|
||||||
serde = { version = "1.0.197", features = [ "serde_derive" ] }
|
serde = { version = "1.0.197", features = [ "serde_derive" ] }
|
||||||
uuid = { version = "1.7.0", features = [ "v4", "fast-rng", "macro-diagnostics", "serde" ] }
|
uuid = { version = "1.7.0", features = [ "v4", "fast-rng", "macro-diagnostics", "serde" ] }
|
||||||
|
|
||||||
|
# Enable a small amount of optimization in debug mode
|
||||||
|
[profile.dev]
|
||||||
|
opt-level = 1
|
||||||
|
|
||||||
|
# Enable high optimizations for dependencies (incl. Bevy), but not for our code:
|
||||||
|
[profile.dev.package."*"]
|
||||||
|
opt-level = 3
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
[SITE:CAVE]
|
[SITE:CAVE]
|
||||||
[SIZE:1:3]
|
[SIZE:1:3]
|
||||||
[UNDERGROUND]
|
# [UNDERGROUND]
|
||||||
[NATURAL]
|
[NATURAL]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub struct DebugPlugin;
|
||||||
|
|
||||||
|
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
||||||
|
pub struct DebugSet;
|
||||||
|
|
||||||
|
impl Plugin for DebugPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.configure_sets(Last, DebugSet.run_if(is_debug_enabled));
|
||||||
|
|
||||||
|
app.insert_resource(DebugMode::on())
|
||||||
|
.add_plugins((
|
||||||
|
// // WorldInspector requires EguiPlugin plugin to be added before it
|
||||||
|
bevy_egui::EguiPlugin::default(),
|
||||||
|
bevy_inspector_egui::quick::WorldInspectorPlugin::new().run_if(is_debug_enabled),
|
||||||
|
))
|
||||||
|
.add_systems(Update, debug_toggle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Reflect, Resource, Default)]
|
||||||
|
#[reflect(Resource)]
|
||||||
|
pub struct DebugMode {
|
||||||
|
pub enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebugMode {
|
||||||
|
pub fn on() -> Self {
|
||||||
|
Self { enabled: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn off() -> Self {
|
||||||
|
Self { enabled: false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_debug_enabled(debug_mode: Res<DebugMode>) -> bool {
|
||||||
|
debug_mode.enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_toggle(input: Res<ButtonInput<KeyCode>>, mut debug_mode: ResMut<DebugMode>) {
|
||||||
|
if input.just_pressed(KeyCode::KeyP) {
|
||||||
|
debug_mode.enabled = !debug_mode.enabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
use crate::sim::prelude as sim;
|
||||||
|
use crate::{debug, game_setup, util};
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
mod prefab;
|
||||||
|
mod systems;
|
||||||
|
|
||||||
|
pub fn init(app: &mut App) {
|
||||||
|
let app = app.add_plugins((
|
||||||
|
game_setup::GameSetupPlugin,
|
||||||
|
util::UtilPlugin,
|
||||||
|
debug::DebugPlugin,
|
||||||
|
));
|
||||||
|
|
||||||
|
// app.add_systems(Startup, systems::setup_demo_2d)
|
||||||
|
// .add_systems(Update, systems::demo_2d);
|
||||||
|
|
||||||
|
let definitions = sim::Definitions::from_default_directory().unwrap();
|
||||||
|
let game_world = sim::GameWorld::generate_mock(&definitions);
|
||||||
|
// let game_world = match std::fs::read(sim::SAVE_FILE) {
|
||||||
|
// Ok(data) => {
|
||||||
|
// info!("Reading world data from \"{}\"", sim::SAVE_FILE);
|
||||||
|
// bincode::deserialize(&data).expect("Loading world data from file")
|
||||||
|
// }
|
||||||
|
// Err(_) => {
|
||||||
|
// info!("Generating mock world...");
|
||||||
|
// let world = sim::GameWorld::generate_mock(&definitions);
|
||||||
|
// info!("Writing new world data to \"{}\"", sim::SAVE_FILE);
|
||||||
|
// std::fs::write(sim::SAVE_FILE, bincode::serialize(&world).unwrap()).unwrap();
|
||||||
|
// world
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
app.register_type::<sim::Definitions>()
|
||||||
|
.register_type::<sim::GameWorld>()
|
||||||
|
.insert_resource(definitions)
|
||||||
|
.insert_resource(game_world)
|
||||||
|
.add_systems(Startup, systems::setup_demo_3d)
|
||||||
|
.add_systems(Update, (systems::demo_3d, systems::advance_time));
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
use bevy::pbr::Atmosphere;
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, Component, Reflect)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
#[require(
|
||||||
|
Name = Name::from("2D Camera"),
|
||||||
|
Camera2d,
|
||||||
|
Transform = Transform::from_xyz(0., 0., 10.),
|
||||||
|
)]
|
||||||
|
pub struct DemoCamera2d;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default, Component, Reflect)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
#[require(
|
||||||
|
Name = Name::from("3D Camera"),
|
||||||
|
// Atmosphere causes a panic if hdr is not true: https://github.com/bevyengine/bevy/issues/18959
|
||||||
|
Camera = Camera {
|
||||||
|
hdr: true,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
Camera3d,
|
||||||
|
Atmosphere = Atmosphere::EARTH,
|
||||||
|
Transform = Transform::from_xyz(1., 2., 10.).looking_at(Vec3::ZERO, Dir3::Y),
|
||||||
|
PointLight,
|
||||||
|
)]
|
||||||
|
pub struct DemoCamera3d;
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
mod demo;
|
||||||
|
mod sim;
|
||||||
|
|
||||||
|
pub use demo::*;
|
||||||
|
pub use sim::*;
|
||||||
|
|
@ -0,0 +1,136 @@
|
||||||
|
pub use bevy::prelude::*;
|
||||||
|
|
||||||
|
use crate::game::prefab::{DemoCamera2d, DemoCamera3d};
|
||||||
|
|
||||||
|
pub fn setup_demo_2d(mut commands: Commands) {
|
||||||
|
commands.spawn((
|
||||||
|
Name::from("2D Demo Scene"),
|
||||||
|
Transform::default(),
|
||||||
|
Visibility::default(),
|
||||||
|
children![
|
||||||
|
DemoCamera2d,
|
||||||
|
(Name::from("2D sprite"), Sprite::sized(Vec2::splat(256.0)),)
|
||||||
|
],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn demo_2d(
|
||||||
|
mut camera_query: Query<(&mut Transform, &mut Projection)>,
|
||||||
|
mut mouse_events: EventReader<bevy::input::mouse::MouseMotion>,
|
||||||
|
mut scroll_events: EventReader<bevy::input::mouse::MouseWheel>,
|
||||||
|
mouse_input: Res<ButtonInput<MouseButton>>,
|
||||||
|
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||||
|
) {
|
||||||
|
let raw_mouse_motion: Vec2 = mouse_events.read().map(|e| e.delta).sum();
|
||||||
|
let raw_scroll_motion: f32 = scroll_events
|
||||||
|
.read()
|
||||||
|
.map(|event| match event.unit {
|
||||||
|
bevy::input::mouse::MouseScrollUnit::Line => event.y * -0.1,
|
||||||
|
bevy::input::mouse::MouseScrollUnit::Pixel => event.y * -0.05,
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
for (mut transform, mut projection) in camera_query.iter_mut() {
|
||||||
|
let projection = match projection.as_mut() {
|
||||||
|
Projection::Orthographic(projection) => projection,
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
if keyboard_input.pressed(KeyCode::ControlLeft) {
|
||||||
|
projection.scale += raw_scroll_motion * projection.scale;
|
||||||
|
}
|
||||||
|
let mouse_motion = raw_mouse_motion * projection.scale * Vec2::new(-1.0, 1.0);
|
||||||
|
if mouse_input.pressed(MouseButton::Middle) {
|
||||||
|
transform.translation += mouse_motion.extend(0.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setup_demo_3d(
|
||||||
|
mut commands: Commands,
|
||||||
|
mut meshes: ResMut<Assets<Mesh>>,
|
||||||
|
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||||
|
) {
|
||||||
|
commands.spawn((
|
||||||
|
Name::from("3D Demo Scene"),
|
||||||
|
Transform::from_xyz(0., 300.0, 0.),
|
||||||
|
Visibility::default(),
|
||||||
|
children![
|
||||||
|
(
|
||||||
|
DemoCamera3d,
|
||||||
|
Transform::from_xyz(-6.225, 2.197, -10.470).looking_at(Vec3::ZERO, Dir3::Y),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Name::from("Cuboid"),
|
||||||
|
Transform::from_xyz(0., 0.5, 0.),
|
||||||
|
Mesh3d(meshes.add(Cuboid::from_length(1.0))),
|
||||||
|
MeshMaterial3d(materials.add(Color::from(bevy::color::palettes::css::GREY))),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Name::from("Floor"),
|
||||||
|
Transform::from_xyz(0., -0.25, 0.),
|
||||||
|
Mesh3d(meshes.add(Cuboid::from_size(Vec3::new(10., 0.5, 10.)))),
|
||||||
|
MeshMaterial3d(materials.add(Color::from(bevy::color::palettes::css::GREY))),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
Name::from("Sun"),
|
||||||
|
Transform::from_rotation(Quat::from_euler(
|
||||||
|
EulerRot::default(),
|
||||||
|
f32::to_radians(160.0),
|
||||||
|
f32::to_radians(-20.0),
|
||||||
|
0.0,
|
||||||
|
)),
|
||||||
|
DirectionalLight {
|
||||||
|
shadows_enabled: true,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn demo_3d(
|
||||||
|
mut camera_query: Query<&mut Transform, With<Camera3d>>,
|
||||||
|
mut mouse_events: EventReader<bevy::input::mouse::MouseMotion>,
|
||||||
|
mouse_input: Res<ButtonInput<MouseButton>>,
|
||||||
|
key_input: Res<ButtonInput<KeyCode>>,
|
||||||
|
time: Res<Time>,
|
||||||
|
) {
|
||||||
|
let raw_mouse_motion: Vec2 = mouse_events.read().map(|e| e.delta).sum();
|
||||||
|
|
||||||
|
if mouse_input.pressed(MouseButton::Right) {
|
||||||
|
let move_forward = key_input.pressed(KeyCode::KeyW);
|
||||||
|
let move_back = key_input.pressed(KeyCode::KeyS);
|
||||||
|
let move_left = key_input.pressed(KeyCode::KeyA);
|
||||||
|
let move_right = key_input.pressed(KeyCode::KeyD);
|
||||||
|
let move_up = key_input.pressed(KeyCode::Space) || key_input.pressed(KeyCode::KeyE);
|
||||||
|
let move_down = key_input.pressed(KeyCode::ControlLeft) || key_input.pressed(KeyCode::KeyQ);
|
||||||
|
|
||||||
|
let raw_movement = Vec3 {
|
||||||
|
x: match (move_right, move_left) {
|
||||||
|
(true, false) => 1.0,
|
||||||
|
(false, true) => -1.0,
|
||||||
|
_ => 0.0,
|
||||||
|
},
|
||||||
|
y: match (move_up, move_down) {
|
||||||
|
(true, false) => 1.0,
|
||||||
|
(false, true) => -1.0,
|
||||||
|
_ => 0.0,
|
||||||
|
},
|
||||||
|
z: match (move_back, move_forward) {
|
||||||
|
(true, false) => 1.0,
|
||||||
|
(false, true) => -1.0,
|
||||||
|
_ => 0.0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
for mut transform in camera_query.iter_mut() {
|
||||||
|
let mouse_motion = raw_mouse_motion * Vec2::new(-1.0, -1.0) * 0.002;
|
||||||
|
transform.rotate_axis(Dir3::Y, mouse_motion.x);
|
||||||
|
transform.rotate_local_x(mouse_motion.y);
|
||||||
|
|
||||||
|
let local_movement = raw_movement * time.delta_secs() * 10.0;
|
||||||
|
let movement = transform.rotation * local_movement;
|
||||||
|
transform.translation += movement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use crate::sim::prelude::*;
|
||||||
|
|
||||||
|
pub fn advance_time(input: Res<ButtonInput<KeyCode>>, mut game_world: ResMut<GameWorld>) {
|
||||||
|
if !input.just_pressed(KeyCode::Space) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
game_world.advance_time(Duration::from_secs(3600));
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
use bevy::{prelude::*, window::WindowResolution};
|
||||||
|
|
||||||
|
pub struct GameSetupPlugin;
|
||||||
|
|
||||||
|
impl Plugin for GameSetupPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.insert_resource(ClearColor(Color::BLACK)).add_plugins(
|
||||||
|
DefaultPlugins
|
||||||
|
.set(WindowPlugin {
|
||||||
|
primary_window: Some(Window {
|
||||||
|
resolution: WindowResolution::new(512.0 * 2.0, 320.0 * 2.0),
|
||||||
|
title: "Bevy template <press P to toggle debug mode>".to_string(), // NOTE: Replace this
|
||||||
|
resizable: false,
|
||||||
|
..default()
|
||||||
|
}),
|
||||||
|
..default()
|
||||||
|
})
|
||||||
|
.set(ImagePlugin::default_nearest()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
117
src/main.rs
117
src/main.rs
|
|
@ -1,111 +1,16 @@
|
||||||
use std::{collections::HashMap, io::BufReader, path::PathBuf, time::Duration};
|
use bevy::prelude::*;
|
||||||
|
use util::create_app_graphs;
|
||||||
use sim::definitions::parser::DefinitionParser;
|
|
||||||
use sim::prelude::{Creature, Site, SiteArea, World};
|
|
||||||
|
|
||||||
|
pub mod debug;
|
||||||
|
pub mod game;
|
||||||
|
pub mod game_setup;
|
||||||
pub mod sim;
|
pub mod sim;
|
||||||
|
pub mod sim_runner;
|
||||||
const SAVE_FILE: &str = "world.bin";
|
pub mod util;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut parse_error_files = vec![];
|
let mut app = App::new();
|
||||||
let mut site_definitions = HashMap::new();
|
game::init(&mut app);
|
||||||
let mut creature_definitions = HashMap::new();
|
create_app_graphs(&mut app);
|
||||||
for entry in std::fs::read_dir(resources_path()).unwrap().flatten() {
|
app.run();
|
||||||
if entry.file_name().to_string_lossy().ends_with(".def") {
|
|
||||||
let source = BufReader::new(std::fs::File::open(entry.path()).unwrap());
|
|
||||||
match DefinitionParser::parse(source) {
|
|
||||||
Ok(defs) => {
|
|
||||||
for def in defs.0 {
|
|
||||||
if let Some(prev) = site_definitions.insert(def.id.clone(), def) {
|
|
||||||
eprintln!("Duplicate site definition '{}'", prev.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for def in defs.1 {
|
|
||||||
if let Some(prev) = creature_definitions.insert(def.id.clone(), def) {
|
|
||||||
eprintln!("Duplicate site definition '{}'", prev.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
parse_error_files.push((err, entry.path()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (err, path) in parse_error_files.iter() {
|
|
||||||
eprintln!("Error\t{path:?}\n\t{err:?}")
|
|
||||||
}
|
|
||||||
if !parse_error_files.is_empty() {
|
|
||||||
eprintln!("{} file(s) had parsing errors!", parse_error_files.len())
|
|
||||||
}
|
|
||||||
|
|
||||||
let site_def = site_definitions.get("CAVE").unwrap();
|
|
||||||
let mut world = match std::fs::read(SAVE_FILE) {
|
|
||||||
Ok(data) => bincode::deserialize(&data).expect("Loading world data from file"),
|
|
||||||
Err(_) => {
|
|
||||||
let mut world = World::default();
|
|
||||||
|
|
||||||
let mut site = Site::generate_from_def(site_def);
|
|
||||||
site.populate_randomly(&creature_definitions.values().collect::<Vec<_>>());
|
|
||||||
|
|
||||||
let bandit_def = creature_definitions.get("BANDIT").unwrap();
|
|
||||||
let spider_def = creature_definitions.get("SPIDER").unwrap();
|
|
||||||
let site = Site::new(
|
|
||||||
site_def.clone(),
|
|
||||||
"Gorbo's cave",
|
|
||||||
vec![
|
|
||||||
SiteArea::from_creatures(&vec![
|
|
||||||
Creature::generate_from_def(
|
|
||||||
creature_definitions.get("DRAGON").unwrap().clone(),
|
|
||||||
),
|
|
||||||
Creature::generate_from_def(
|
|
||||||
creature_definitions.get("INDESTRUCTIBLE").unwrap().clone(),
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
SiteArea::from_creatures(&vec![
|
|
||||||
Creature::generate_from_def(bandit_def.clone()),
|
|
||||||
Creature::generate_from_def(spider_def.clone()),
|
|
||||||
]),
|
|
||||||
SiteArea::from_creatures(&vec![
|
|
||||||
Creature::generate_from_def(bandit_def.clone()),
|
|
||||||
Creature::generate_from_def(bandit_def.clone()),
|
|
||||||
Creature::generate_from_def(bandit_def.clone()),
|
|
||||||
]),
|
|
||||||
SiteArea::from_creatures(&vec![
|
|
||||||
Creature::generate_from_def(spider_def.clone()),
|
|
||||||
Creature::generate_from_def(spider_def.clone()),
|
|
||||||
]),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
world.sites.push(site);
|
|
||||||
std::fs::write("world.bin", bincode::serialize(&world).unwrap()).unwrap();
|
|
||||||
world
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
for site in world.sites.iter() {
|
|
||||||
println!("{site}")
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut input = String::new();
|
|
||||||
loop {
|
|
||||||
std::io::stdin().read_line(&mut input).unwrap();
|
|
||||||
if ["q", "quit", "exit"]
|
|
||||||
.iter()
|
|
||||||
.any(|quit_cmd| input.trim() == *quit_cmd)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let start = std::time::Instant::now();
|
|
||||||
world.advance_time(Duration::from_secs(3600));
|
|
||||||
let end = std::time::Instant::now();
|
|
||||||
println!("World tick: {}us", (end - start).as_micros());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resources_path() -> PathBuf {
|
|
||||||
PathBuf::from(".").join("defs")
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,17 @@
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use bevy::prelude::Reflect;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
|
|
||||||
#[derive(Clone, Default, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Default, Eq, PartialEq, Hash, Debug, Serialize, Deserialize, Reflect)]
|
||||||
pub struct CreatureState {
|
pub struct CreatureState {
|
||||||
pub damage: i32,
|
pub damage: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug, Serialize, Deserialize, Reflect)]
|
||||||
pub struct CreatureId(Uuid);
|
pub struct CreatureId(Uuid);
|
||||||
|
|
||||||
impl CreatureId {
|
impl CreatureId {
|
||||||
|
|
@ -25,7 +26,7 @@ impl Display for CreatureId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize, Reflect)]
|
||||||
pub struct Creature {
|
pub struct Creature {
|
||||||
id: CreatureId,
|
id: CreatureId,
|
||||||
pub definition: CreatureDef,
|
pub definition: CreatureDef,
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,66 @@
|
||||||
use parser::{SliceParseError, TagParseError};
|
use std::{collections::HashMap, fmt::Display};
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use parser::{DefinitionParser, SliceParseError, TagParseError};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use self::parser::{FilePosition, ParseError, SliceParse};
|
use self::parser::{FilePosition, ParseError, SliceParse};
|
||||||
|
use super::util;
|
||||||
|
|
||||||
|
pub mod creature;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
|
pub mod site;
|
||||||
|
|
||||||
|
pub use creature::*;
|
||||||
|
pub use site::*;
|
||||||
|
|
||||||
|
pub type DefinitionId = String;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Resource, Serialize, Deserialize, Reflect)]
|
||||||
|
#[reflect(Resource)]
|
||||||
|
pub struct Definitions {
|
||||||
|
pub sites: DefinitionMap<SiteDef>,
|
||||||
|
pub creatures: DefinitionMap<CreatureDef>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Definitions {
|
||||||
|
pub fn from_default_directory() -> Option<Self> {
|
||||||
|
match DefinitionParser::parse_directory(&util::definitions_path()) {
|
||||||
|
Ok(defs) => Some(defs),
|
||||||
|
Err(err) => {
|
||||||
|
match err {
|
||||||
|
parser::DirectoryParseError::Io(err) => {
|
||||||
|
error!("Failed to read definitions: {err:?}");
|
||||||
|
}
|
||||||
|
parser::DirectoryParseError::Files {
|
||||||
|
parser_errors,
|
||||||
|
duplicates,
|
||||||
|
} => {
|
||||||
|
if !parser_errors.is_empty() {
|
||||||
|
error!("{} parse error(s):", parser_errors.len());
|
||||||
|
for parser_error in parser_errors.iter() {
|
||||||
|
error!("- {:?}: {}", parser_error.file_path, parser_error.error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !duplicates.is_empty() {
|
||||||
|
error!("{} duplicate definition(s)", duplicates.len());
|
||||||
|
for duplicate in duplicates.iter() {
|
||||||
|
error!(
|
||||||
|
"- {:?}: {} ({})",
|
||||||
|
duplicate.file_path, duplicate.id, duplicate.category
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Deref, DerefMut, Serialize, Deserialize, Reflect)]
|
||||||
|
pub struct DefinitionMap<T>(pub HashMap<DefinitionId, T>);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum TopLevelTag {
|
pub enum TopLevelTag {
|
||||||
|
|
@ -11,6 +68,19 @@ pub enum TopLevelTag {
|
||||||
Creature,
|
Creature,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for TopLevelTag {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match self {
|
||||||
|
TopLevelTag::Site => "SITE",
|
||||||
|
TopLevelTag::Creature => "CREATURE",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Clone)]
|
#[derive(Default, Debug, Clone)]
|
||||||
pub struct Tag {
|
pub struct Tag {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
@ -21,96 +91,22 @@ pub struct Tag {
|
||||||
impl Tag {
|
impl Tag {
|
||||||
fn parse_value<T: SliceParse>(&self) -> Result<T, ParseError> {
|
fn parse_value<T: SliceParse>(&self) -> Result<T, ParseError> {
|
||||||
T::parse(&self.values).map_err(|err| {
|
T::parse(&self.values).map_err(|err| {
|
||||||
ParseError::TagParseError(match err {
|
ParseError::TagParse(match err {
|
||||||
SliceParseError::ParseError => TagParseError::ParseError(self.clone()),
|
SliceParseError::ParseError => TagParseError::ParseError(self.clone()),
|
||||||
SliceParseError::MissingValue => TagParseError::MissingValue(self.clone()),
|
SliceParseError::MissingValue => TagParseError::MissingValue(self.clone()),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Default, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
|
pub fn as_def_string(&self) -> String {
|
||||||
pub struct SiteDef {
|
format!(
|
||||||
pub id: String,
|
"[{name}{values}]",
|
||||||
pub min_size: i32,
|
name = self.name,
|
||||||
pub max_size: i32,
|
values = self
|
||||||
pub is_underground: bool,
|
.values
|
||||||
pub is_natural: bool,
|
.iter()
|
||||||
}
|
.map(|value| format!(":{value}"))
|
||||||
|
.collect::<String>()
|
||||||
impl SiteDef {
|
)
|
||||||
pub fn parse(header: Tag, tags: &[Tag]) -> Result<Self, ParseError> {
|
|
||||||
let mut result = Self {
|
|
||||||
id: header.parse_value::<String>().unwrap(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
for tag in tags {
|
|
||||||
match tag.name.as_str() {
|
|
||||||
"SIZE" => (result.min_size, result.max_size) = tag.parse_value().unwrap(),
|
|
||||||
"NATURAL" => result.is_natural = true,
|
|
||||||
_ => {
|
|
||||||
eprintln!("Unknown tag '{}' in SITE defs", tag.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Default, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub struct CreatureDef {
|
|
||||||
pub id: String,
|
|
||||||
pub name_singular: String,
|
|
||||||
pub name_plural: String,
|
|
||||||
pub health: i32,
|
|
||||||
pub armor_class: i8,
|
|
||||||
pub to_hit_modifier: i8,
|
|
||||||
pub min_damage: i32,
|
|
||||||
pub max_damage: i32,
|
|
||||||
pub multiattack: Option<u32>,
|
|
||||||
pub is_humanoid: bool,
|
|
||||||
pub is_animal: bool,
|
|
||||||
pub is_intelligent: bool,
|
|
||||||
pub patrols: bool,
|
|
||||||
pub prefers_dark: bool,
|
|
||||||
pub invulnerable: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CreatureDef {
|
|
||||||
pub fn parse(header: Tag, tags: &[Tag]) -> Result<Self, ParseError> {
|
|
||||||
let mut result = Self {
|
|
||||||
id: header.parse_value().unwrap(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
for tag in tags {
|
|
||||||
match tag.name.as_str() {
|
|
||||||
"NAME" => (result.name_singular, result.name_plural) = tag.parse_value()?,
|
|
||||||
"HEALTH" => result.health = tag.parse_value()?,
|
|
||||||
"AC" => result.armor_class = tag.parse_value()?,
|
|
||||||
"ATTACK" => {
|
|
||||||
(result.to_hit_modifier, result.min_damage, result.max_damage) =
|
|
||||||
tag.parse_value()?
|
|
||||||
}
|
|
||||||
"MULTIATTACK" => result.multiattack = Some(tag.parse_value()?),
|
|
||||||
"HUMANOID" => result.is_humanoid = true,
|
|
||||||
"ANIMAL" => result.is_animal = true,
|
|
||||||
"INTELLIGENT" => result.is_intelligent = true,
|
|
||||||
"PATROL" => result.patrols = true,
|
|
||||||
"PREFER_DARK" => result.prefers_dark = true,
|
|
||||||
"INVULNERABLE" => result.invulnerable = true,
|
|
||||||
_ => {
|
|
||||||
eprintln!("Unknown tag '{}' in CREATURE defs", tag.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn attack_count(&self) -> u32 {
|
|
||||||
self.multiattack.unwrap_or(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn damage_roll(&self) -> i32 {
|
|
||||||
fastrand::i32(self.min_damage..=self.max_damage)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Default, Debug, Hash, Eq, PartialEq, Serialize, Deserialize, Reflect)]
|
||||||
|
pub struct CreatureDef {
|
||||||
|
pub id: String,
|
||||||
|
pub name_singular: String,
|
||||||
|
pub name_plural: String,
|
||||||
|
pub health: i32,
|
||||||
|
pub armor_class: i8,
|
||||||
|
pub to_hit_modifier: i8,
|
||||||
|
pub min_damage: i32,
|
||||||
|
pub max_damage: i32,
|
||||||
|
pub multiattack: Option<u32>,
|
||||||
|
pub is_humanoid: bool,
|
||||||
|
pub is_animal: bool,
|
||||||
|
pub is_intelligent: bool,
|
||||||
|
pub patrols: bool,
|
||||||
|
pub prefers_dark: bool,
|
||||||
|
pub invulnerable: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CreatureDef {
|
||||||
|
pub const CATEGORY: TopLevelTag = TopLevelTag::Creature;
|
||||||
|
|
||||||
|
pub fn parse(header: Tag, tags: &[Tag]) -> Result<Self, ParseError> {
|
||||||
|
let mut result = Self {
|
||||||
|
id: header.parse_value().unwrap(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
for tag in tags {
|
||||||
|
match tag.name.as_str() {
|
||||||
|
"NAME" => (result.name_singular, result.name_plural) = tag.parse_value()?,
|
||||||
|
"HEALTH" => result.health = tag.parse_value()?,
|
||||||
|
"AC" => result.armor_class = tag.parse_value()?,
|
||||||
|
"ATTACK" => {
|
||||||
|
(result.to_hit_modifier, result.min_damage, result.max_damage) =
|
||||||
|
tag.parse_value()?
|
||||||
|
}
|
||||||
|
"MULTIATTACK" => result.multiattack = Some(tag.parse_value()?),
|
||||||
|
"HUMANOID" => result.is_humanoid = true,
|
||||||
|
"ANIMAL" => result.is_animal = true,
|
||||||
|
"INTELLIGENT" => result.is_intelligent = true,
|
||||||
|
"PATROL" => result.patrols = true,
|
||||||
|
"PREFER_DARK" => result.prefers_dark = true,
|
||||||
|
"INVULNERABLE" => result.invulnerable = true,
|
||||||
|
_ => {
|
||||||
|
return Err(ParseError::UnknownTag {
|
||||||
|
tag: tag.name.clone(),
|
||||||
|
id: Some(result.id),
|
||||||
|
category: Some(Self::CATEGORY),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn attack_count(&self) -> u32 {
|
||||||
|
self.multiattack.unwrap_or(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn damage_roll(&self) -> i32 {
|
||||||
|
fastrand::i32(self.min_damage..=self.max_damage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,12 @@
|
||||||
use std::{borrow::BorrowMut, io::Read, str::FromStr};
|
use std::{
|
||||||
|
borrow::BorrowMut,
|
||||||
|
fmt::Display,
|
||||||
|
io::{BufReader, Read},
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
use super::{CreatureDef, SiteDef, Tag, TopLevelTag};
|
use super::{CreatureDef, Definitions, SiteDef, Tag, TopLevelTag};
|
||||||
|
|
||||||
pub trait SliceParse: Sized {
|
pub trait SliceParse: Sized {
|
||||||
fn parse(values: &[String]) -> Result<Self, SliceParseError>;
|
fn parse(values: &[String]) -> Result<Self, SliceParseError>;
|
||||||
|
|
@ -64,11 +70,53 @@ impl Default for FilePosition {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ParseError {
|
pub enum ParseError {
|
||||||
LexerError(LexerError),
|
Lexer(LexerError),
|
||||||
SyntaxError(String),
|
Syntax {
|
||||||
TagParseError(TagParseError),
|
error: String,
|
||||||
NoDefinitionsError,
|
},
|
||||||
UnexpectedError,
|
TagParse(TagParseError),
|
||||||
|
NoDefinitions,
|
||||||
|
UnknownTag {
|
||||||
|
tag: String,
|
||||||
|
id: Option<String>,
|
||||||
|
category: Option<TopLevelTag>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ParseError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
// TODO: Format rest of errors some day
|
||||||
|
ParseError::Lexer(err) => write!(f, "Lexer error: {err:?}"),
|
||||||
|
ParseError::Syntax { error } => write!(f, "Syntax error: {error}"),
|
||||||
|
ParseError::TagParse(err) => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"(line {line}, column {column}) ",
|
||||||
|
line = err.tag().file_position.line,
|
||||||
|
column = err.tag().file_position.column,
|
||||||
|
)?;
|
||||||
|
match err {
|
||||||
|
TagParseError::MissingValue(tag) => {
|
||||||
|
write!(f, "Missing values from {}", tag.as_def_string())?;
|
||||||
|
}
|
||||||
|
TagParseError::ParseError(tag) => {
|
||||||
|
write!(f, "Could not parse {}", tag.as_def_string())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write!(f, "")
|
||||||
|
}
|
||||||
|
ParseError::NoDefinitions => write!(f, "No definitions found"),
|
||||||
|
ParseError::UnknownTag { tag, id, category } => match category {
|
||||||
|
Some(category) => write!(
|
||||||
|
f,
|
||||||
|
"Unknown tag '{tag}' in [{category}:{}] ",
|
||||||
|
id.as_ref().unwrap_or(&"<unknown>".into())
|
||||||
|
),
|
||||||
|
None => write!(f, "Unknown top-level-tag '{tag}'"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum SliceParseError {
|
pub enum SliceParseError {
|
||||||
|
|
@ -82,6 +130,15 @@ pub enum TagParseError {
|
||||||
ParseError(Tag),
|
ParseError(Tag),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TagParseError {
|
||||||
|
pub fn tag(&self) -> &Tag {
|
||||||
|
match self {
|
||||||
|
Self::MissingValue(tag) => tag,
|
||||||
|
Self::ParseError(tag) => tag,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum LexerError {
|
pub enum LexerError {
|
||||||
Io(std::io::Error),
|
Io(std::io::Error),
|
||||||
|
|
@ -89,13 +146,41 @@ pub enum LexerError {
|
||||||
MissingTagStart(FilePosition),
|
MissingTagStart(FilePosition),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ParserErrorLocation {
|
||||||
|
pub file_path: PathBuf,
|
||||||
|
pub error: ParseError,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DuplicateError {
|
||||||
|
pub file_path: PathBuf,
|
||||||
|
pub category: TopLevelTag,
|
||||||
|
pub id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum DirectoryParseError {
|
||||||
|
Io(std::io::Error),
|
||||||
|
Files {
|
||||||
|
parser_errors: Vec<ParserErrorLocation>,
|
||||||
|
duplicates: Vec<DuplicateError>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::io::Error> for DirectoryParseError {
|
||||||
|
fn from(value: std::io::Error) -> Self {
|
||||||
|
Self::Io(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct DefinitionParser;
|
pub struct DefinitionParser;
|
||||||
|
|
||||||
impl DefinitionParser {
|
impl DefinitionParser {
|
||||||
pub fn parse(source: impl Read) -> Result<(Vec<SiteDef>, Vec<CreatureDef>), ParseError> {
|
pub fn parse(source: impl Read) -> Result<(Vec<SiteDef>, Vec<CreatureDef>), ParseError> {
|
||||||
let tags = match Self::lexer(source) {
|
let tags = match Self::lexer(source) {
|
||||||
Ok(tags) => tags,
|
Ok(tags) => tags,
|
||||||
Err(err) => return Err(ParseError::LexerError(err)),
|
Err(err) => return Err(ParseError::Lexer(err)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut unparsed_defs: Vec<(Tag, Vec<Tag>)> = Vec::new();
|
let mut unparsed_defs: Vec<(Tag, Vec<Tag>)> = Vec::new();
|
||||||
|
|
@ -112,9 +197,9 @@ impl DefinitionParser {
|
||||||
None => match definition_builder.borrow_mut() {
|
None => match definition_builder.borrow_mut() {
|
||||||
Some(builder) => builder.1.push(tag.clone()),
|
Some(builder) => builder.1.push(tag.clone()),
|
||||||
None => {
|
None => {
|
||||||
return Err(ParseError::SyntaxError(
|
return Err(ParseError::Syntax {
|
||||||
"First tag must be a top-level-tag".into(),
|
error: "First tag must be a top-level-tag".into(),
|
||||||
))
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -123,7 +208,7 @@ impl DefinitionParser {
|
||||||
match definition_builder {
|
match definition_builder {
|
||||||
Some(builder) => unparsed_defs.push(builder),
|
Some(builder) => unparsed_defs.push(builder),
|
||||||
None => {
|
None => {
|
||||||
return Err(ParseError::NoDefinitionsError);
|
return Err(ParseError::NoDefinitions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,26 +221,91 @@ impl DefinitionParser {
|
||||||
TopLevelTag::Creature => creatures.push(CreatureDef::parse(header, &tags)?),
|
TopLevelTag::Creature => creatures.push(CreatureDef::parse(header, &tags)?),
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
return Err(ParseError::UnexpectedError);
|
return Err(ParseError::UnknownTag {
|
||||||
|
tag: header.name,
|
||||||
|
id: None,
|
||||||
|
category: None,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok((sites, creatures))
|
Ok((sites, creatures))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses every file with `.def` extension in given directory.
|
||||||
|
pub fn parse_directory(directory: &Path) -> Result<Definitions, DirectoryParseError> {
|
||||||
|
let mut parse_error_files = vec![];
|
||||||
|
let mut duplicate_error_files = vec![];
|
||||||
|
let mut result = Definitions::default();
|
||||||
|
// NOTE: `.flatten()` hides any error results from `read_dir()` elements.
|
||||||
|
// This shouldn't be a problem, but it could be the source of hard-to-find errors.
|
||||||
|
for entry in std::fs::read_dir(directory)
|
||||||
|
.map_err(DirectoryParseError::from)?
|
||||||
|
.flatten()
|
||||||
|
{
|
||||||
|
if entry.file_name().to_string_lossy().ends_with(".def") {
|
||||||
|
let source = BufReader::new(
|
||||||
|
std::fs::File::open(entry.path()).map_err(DirectoryParseError::from)?,
|
||||||
|
);
|
||||||
|
match Self::parse(source) {
|
||||||
|
Ok(defs) => {
|
||||||
|
for def in defs.0 {
|
||||||
|
if let Some(duplicate) = result.sites.insert(def.id.clone(), def) {
|
||||||
|
duplicate_error_files.push(DuplicateError {
|
||||||
|
file_path: entry.path(),
|
||||||
|
category: TopLevelTag::Site,
|
||||||
|
id: duplicate.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for def in defs.1 {
|
||||||
|
if let Some(duplicate) = result.creatures.insert(def.id.clone(), def) {
|
||||||
|
duplicate_error_files.push(DuplicateError {
|
||||||
|
file_path: entry.path(),
|
||||||
|
category: TopLevelTag::Creature,
|
||||||
|
id: duplicate.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
parse_error_files.push(ParserErrorLocation {
|
||||||
|
file_path: entry.path(),
|
||||||
|
error: err,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !parse_error_files.is_empty() || !duplicate_error_files.is_empty() {
|
||||||
|
return Err(DirectoryParseError::Files {
|
||||||
|
parser_errors: parse_error_files,
|
||||||
|
duplicates: duplicate_error_files,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
fn lexer(mut source: impl Read) -> Result<Vec<Tag>, LexerError> {
|
fn lexer(mut source: impl Read) -> Result<Vec<Tag>, LexerError> {
|
||||||
let mut tags = vec![];
|
let mut tags = vec![];
|
||||||
let mut tag_builder: Option<Tag> = None;
|
let mut tag_builder: Option<Tag> = None;
|
||||||
let mut buffer = String::new();
|
let mut buffer = String::new();
|
||||||
source.read_to_string(&mut buffer).map_err(LexerError::Io)?;
|
source.read_to_string(&mut buffer).map_err(LexerError::Io)?;
|
||||||
let mut file_position = FilePosition::default();
|
let mut file_position = FilePosition::default();
|
||||||
|
let mut in_comment = false;
|
||||||
for c in buffer.chars() {
|
for c in buffer.chars() {
|
||||||
if c == '\n' {
|
if c == '\n' {
|
||||||
|
in_comment = false;
|
||||||
file_position.line += 1;
|
file_position.line += 1;
|
||||||
file_position.column = 0;
|
file_position.column = 0;
|
||||||
} else {
|
} else {
|
||||||
file_position.column += 1;
|
file_position.column += 1;
|
||||||
}
|
}
|
||||||
|
if in_comment {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
match tag_builder.borrow_mut() {
|
match tag_builder.borrow_mut() {
|
||||||
None => match c {
|
None => match c {
|
||||||
'[' => {
|
'[' => {
|
||||||
|
|
@ -165,6 +315,7 @@ impl DefinitionParser {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
']' => return Err(LexerError::MissingTagStart(file_position.clone())),
|
']' => return Err(LexerError::MissingTagStart(file_position.clone())),
|
||||||
|
'#' => in_comment = true,
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
Some(tag) => match c {
|
Some(tag) => match c {
|
||||||
|
|
@ -175,10 +326,10 @@ impl DefinitionParser {
|
||||||
':' => tag.values.push("".into()),
|
':' => tag.values.push("".into()),
|
||||||
'[' => return Err(LexerError::UnclosedTag(tag.clone())),
|
'[' => return Err(LexerError::UnclosedTag(tag.clone())),
|
||||||
_ => {
|
_ => {
|
||||||
if tag.values.is_empty() {
|
if let Some(last) = tag.values.last_mut() {
|
||||||
tag.name.push(c)
|
last.push(c)
|
||||||
} else {
|
} else {
|
||||||
tag.values.last_mut().unwrap().push(c)
|
tag.name.push(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Default, Debug, Hash, Eq, PartialEq, Serialize, Deserialize, Reflect)]
|
||||||
|
pub struct SiteDef {
|
||||||
|
pub id: String,
|
||||||
|
pub min_size: i32,
|
||||||
|
pub max_size: i32,
|
||||||
|
pub is_underground: bool,
|
||||||
|
pub is_natural: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SiteDef {
|
||||||
|
pub const CATEGORY: TopLevelTag = TopLevelTag::Site;
|
||||||
|
|
||||||
|
pub fn parse(header: Tag, tags: &[Tag]) -> Result<Self, ParseError> {
|
||||||
|
let mut result = Self {
|
||||||
|
id: header.parse_value::<String>().unwrap(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
for tag in tags {
|
||||||
|
match tag.name.as_str() {
|
||||||
|
"SIZE" => (result.min_size, result.max_size) = tag.parse_value().unwrap(),
|
||||||
|
"NATURAL" => result.is_natural = true,
|
||||||
|
_ => {
|
||||||
|
// warn!("Unknown tag '{}' in SITE defs", tag.name);
|
||||||
|
return Err(ParseError::UnknownTag {
|
||||||
|
tag: tag.name.clone(),
|
||||||
|
id: Some(result.id),
|
||||||
|
category: Some(Self::CATEGORY),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
use std::{collections::HashMap, fmt::Display, time::Duration};
|
use std::{collections::HashMap, fmt::Display, time::Duration};
|
||||||
|
|
||||||
|
use bevy::prelude::Reflect;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, Reflect)]
|
||||||
pub struct SiteId(Uuid);
|
pub struct SiteId(Uuid);
|
||||||
|
|
||||||
impl SiteId {
|
impl SiteId {
|
||||||
|
|
@ -20,7 +21,7 @@ impl Display for SiteId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, Reflect)]
|
||||||
pub struct Site {
|
pub struct Site {
|
||||||
id: SiteId,
|
id: SiteId,
|
||||||
areas: Vec<SiteArea>,
|
areas: Vec<SiteArea>,
|
||||||
|
|
@ -135,7 +136,7 @@ impl Site {
|
||||||
.push(creature);
|
.push(creature);
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("Tick! {}", self);
|
println!("Tick! {self}");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_from_def(site_def: &SiteDef) -> Self {
|
pub fn generate_from_def(site_def: &SiteDef) -> Self {
|
||||||
|
|
@ -205,9 +206,9 @@ impl Display for Site {
|
||||||
id = self.id
|
id = self.id
|
||||||
)?;
|
)?;
|
||||||
for (index, area) in self.areas.iter().enumerate() {
|
for (index, area) in self.areas.iter().enumerate() {
|
||||||
write!(f, "\n\tarea {} [", index)?;
|
write!(f, "\n\tarea {index} [")?;
|
||||||
for creature in area.population.iter() {
|
for creature in area.population.iter() {
|
||||||
write!(f, "\n\t\t{}", creature)?;
|
write!(f, "\n\t\t{creature}")?;
|
||||||
}
|
}
|
||||||
if !area.population.is_empty() {
|
if !area.population.is_empty() {
|
||||||
write!(f, "\n\t")?;
|
write!(f, "\n\t")?;
|
||||||
|
|
@ -221,7 +222,7 @@ impl Display for Site {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, Reflect)]
|
||||||
pub struct SiteArea {
|
pub struct SiteArea {
|
||||||
pub population: Vec<Creature>,
|
pub population: Vec<Creature>,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
use std::{fmt::Display, time::Duration};
|
use std::{fmt::Display, time::Duration};
|
||||||
|
|
||||||
|
use bevy::prelude::Reflect;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize, Reflect)]
|
||||||
pub struct TravelGroupId(Uuid);
|
pub struct TravelGroupId(Uuid);
|
||||||
|
|
||||||
impl TravelGroupId {
|
impl TravelGroupId {
|
||||||
|
|
@ -20,13 +21,22 @@ impl Display for TravelGroupId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Eq, PartialEq, Debug, Serialize, Deserialize, Reflect)]
|
||||||
pub enum WorldPoint {
|
pub enum WorldPoint {
|
||||||
Coords(WorldCoords),
|
Coords(WorldCoords),
|
||||||
Site(SiteId),
|
Site(SiteId),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Debug, Serialize, Deserialize)]
|
impl Display for WorldPoint {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match *self {
|
||||||
|
Self::Site(site_id) => write!(f, "[{site_id}]"),
|
||||||
|
Self::Coords(coords) => write!(f, "{coords}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug, Serialize, Deserialize, Reflect)]
|
||||||
pub struct TravelGroup {
|
pub struct TravelGroup {
|
||||||
id: TravelGroupId,
|
id: TravelGroupId,
|
||||||
accumulated_movement: Kilometer,
|
accumulated_movement: Kilometer,
|
||||||
|
|
@ -65,6 +75,10 @@ impl TravelGroup {
|
||||||
|
|
||||||
pub fn advance_time(&mut self, time: Duration) {
|
pub fn advance_time(&mut self, time: Duration) {
|
||||||
self.accumulated_movement += time.as_secs_f32() / 3600. * self.speed();
|
self.accumulated_movement += time.as_secs_f32() / 3600. * self.speed();
|
||||||
|
// while self.accumulated_movement >= Self::minimum_simulated_distance() {
|
||||||
|
// self.inner_tick();
|
||||||
|
// self.accumulated_movement -= Self::minimum_simulated_distance();
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(&mut self, creature: Creature) {
|
pub fn insert(&mut self, creature: Creature) {
|
||||||
|
|
@ -74,4 +88,31 @@ impl TravelGroup {
|
||||||
pub fn remove(&mut self, id: CreatureId) {
|
pub fn remove(&mut self, id: CreatureId) {
|
||||||
self.creatures.retain(|creature| creature.id() != id);
|
self.creatures.retain(|creature| creature.id() != id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fn inner_tick(&mut self) {
|
||||||
|
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for TravelGroup {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"travel group from {origin} to {destination} [{id}] [",
|
||||||
|
origin = self
|
||||||
|
.origin
|
||||||
|
.map_or("nowhere".to_string(), |point| format!("{point}")),
|
||||||
|
destination = self
|
||||||
|
.destination
|
||||||
|
.map_or("nowhere".to_string(), |point| format!("{point}")),
|
||||||
|
id = self.id
|
||||||
|
)?;
|
||||||
|
for creature in self.creatures.iter() {
|
||||||
|
write!(f, "\n\t{creature}")?;
|
||||||
|
}
|
||||||
|
if !self.creatures.is_empty() {
|
||||||
|
writeln!(f)?;
|
||||||
|
}
|
||||||
|
write!(f, "]")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,28 @@
|
||||||
|
use std::{fmt::Display, path::PathBuf};
|
||||||
|
|
||||||
|
use bevy::prelude::Reflect;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub type Kilometer = f32;
|
pub type Kilometer = f32;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, Hash, Reflect)]
|
||||||
pub struct WorldCoords {
|
pub struct WorldCoords {
|
||||||
pub x: i32,
|
pub x: i32,
|
||||||
pub y: i32,
|
pub y: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for WorldCoords {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "({}, {})", self.x, self.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn roll_d20() -> i8 {
|
pub fn roll_d20() -> i8 {
|
||||||
fastrand::i8(1..=20)
|
fastrand::i8(1..=20)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn definitions_path() -> PathBuf {
|
||||||
|
PathBuf::from(".").join("defs")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const SAVE_FILE: &str = "world.bin";
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,73 @@
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use bevy::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::prelude::*;
|
use super::prelude::*;
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
#[derive(Debug, Default, Resource, Serialize, Deserialize, Reflect)]
|
||||||
pub struct World {
|
#[reflect(Resource)]
|
||||||
|
pub struct GameWorld {
|
||||||
pub sites: Vec<Site>,
|
pub sites: Vec<Site>,
|
||||||
pub travel_groups: Vec<TravelGroup>,
|
pub travel_groups: Vec<TravelGroup>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl World {
|
impl GameWorld {
|
||||||
pub fn advance_time(&mut self, time: Duration) {
|
pub fn advance_time(&mut self, time: Duration) {
|
||||||
for site in self.sites.iter_mut() {
|
for site in self.sites.iter_mut() {
|
||||||
site.advance_time(time);
|
site.advance_time(time);
|
||||||
}
|
}
|
||||||
|
for travel_group in self.travel_groups.iter_mut() {
|
||||||
|
travel_group.advance_time(time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_mock(defs: &Definitions) -> Self {
|
||||||
|
let cave_def = defs.sites.get("CAVE").unwrap();
|
||||||
|
let mut site = Site::generate_from_def(cave_def);
|
||||||
|
site.populate_randomly(&defs.creatures.values().collect::<Vec<_>>());
|
||||||
|
|
||||||
|
let bandit_def = defs.creatures.get("BANDIT").unwrap();
|
||||||
|
let spider_def = defs.creatures.get("SPIDER").unwrap();
|
||||||
|
let cave = Site::new(
|
||||||
|
cave_def.clone(),
|
||||||
|
"Gorbo's cave",
|
||||||
|
vec![
|
||||||
|
SiteArea::from_creatures(&vec![
|
||||||
|
Creature::generate_from_def(defs.creatures.get("DRAGON").unwrap().clone()),
|
||||||
|
Creature::generate_from_def(
|
||||||
|
defs.creatures.get("INDESTRUCTIBLE").unwrap().clone(),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
SiteArea::from_creatures(&vec![
|
||||||
|
Creature::generate_from_def(bandit_def.clone()),
|
||||||
|
Creature::generate_from_def(spider_def.clone()),
|
||||||
|
]),
|
||||||
|
SiteArea::from_creatures(&vec![
|
||||||
|
Creature::generate_from_def(bandit_def.clone()),
|
||||||
|
Creature::generate_from_def(bandit_def.clone()),
|
||||||
|
Creature::generate_from_def(bandit_def.clone()),
|
||||||
|
]),
|
||||||
|
SiteArea::from_creatures(&vec![
|
||||||
|
Creature::generate_from_def(spider_def.clone()),
|
||||||
|
Creature::generate_from_def(spider_def.clone()),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
let group = TravelGroup::new(
|
||||||
|
vec![
|
||||||
|
Creature::generate_from_def(bandit_def.clone()),
|
||||||
|
Creature::generate_from_def(bandit_def.clone()),
|
||||||
|
Creature::generate_from_def(bandit_def.clone()),
|
||||||
|
],
|
||||||
|
WorldCoords { x: 0, y: 0 },
|
||||||
|
Some(WorldPoint::Coords(WorldCoords { x: 0, y: 0 })),
|
||||||
|
Some(WorldPoint::Site(cave.id())),
|
||||||
|
);
|
||||||
|
Self {
|
||||||
|
sites: vec![cave],
|
||||||
|
travel_groups: vec![group],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,126 @@
|
||||||
|
use crate::sim;
|
||||||
|
use std::{collections::HashMap, io::BufReader, path::PathBuf, time::Duration};
|
||||||
|
|
||||||
|
use sim::definitions::parser::DefinitionParser;
|
||||||
|
use sim::prelude::*;
|
||||||
|
|
||||||
|
const SAVE_FILE: &str = "world.bin";
|
||||||
|
|
||||||
|
pub fn run_interactive_simulation() {
|
||||||
|
let mut parse_error_files = vec![];
|
||||||
|
let mut site_definitions = HashMap::new();
|
||||||
|
let mut creature_definitions = HashMap::new();
|
||||||
|
for entry in std::fs::read_dir(resources_path()).unwrap().flatten() {
|
||||||
|
if entry.file_name().to_string_lossy().ends_with(".def") {
|
||||||
|
let source = BufReader::new(std::fs::File::open(entry.path()).unwrap());
|
||||||
|
match DefinitionParser::parse(source) {
|
||||||
|
Ok(defs) => {
|
||||||
|
for def in defs.0 {
|
||||||
|
if let Some(prev) = site_definitions.insert(def.id.clone(), def) {
|
||||||
|
eprintln!("Duplicate site definition '{}'", prev.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for def in defs.1 {
|
||||||
|
if let Some(prev) = creature_definitions.insert(def.id.clone(), def) {
|
||||||
|
eprintln!("Duplicate site definition '{}'", prev.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
parse_error_files.push((err, entry.path()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (err, path) in parse_error_files.iter() {
|
||||||
|
eprintln!("Error\t{path:?}\n\t{err:?}")
|
||||||
|
}
|
||||||
|
if !parse_error_files.is_empty() {
|
||||||
|
eprintln!("{} file(s) had parsing errors!", parse_error_files.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
let site_def = site_definitions.get("CAVE").unwrap();
|
||||||
|
let mut world = match std::fs::read(SAVE_FILE) {
|
||||||
|
Ok(data) => bincode::deserialize(&data).expect("Loading world data from file"),
|
||||||
|
Err(_) => {
|
||||||
|
let mut world = GameWorld::default();
|
||||||
|
|
||||||
|
let mut site = Site::generate_from_def(site_def);
|
||||||
|
site.populate_randomly(&creature_definitions.values().collect::<Vec<_>>());
|
||||||
|
|
||||||
|
let bandit_def = creature_definitions.get("BANDIT").unwrap();
|
||||||
|
let spider_def = creature_definitions.get("SPIDER").unwrap();
|
||||||
|
let site = Site::new(
|
||||||
|
site_def.clone(),
|
||||||
|
"Gorbo's cave",
|
||||||
|
vec![
|
||||||
|
SiteArea::from_creatures(&vec![
|
||||||
|
Creature::generate_from_def(
|
||||||
|
creature_definitions.get("DRAGON").unwrap().clone(),
|
||||||
|
),
|
||||||
|
Creature::generate_from_def(
|
||||||
|
creature_definitions.get("INDESTRUCTIBLE").unwrap().clone(),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
SiteArea::from_creatures(&vec![
|
||||||
|
Creature::generate_from_def(bandit_def.clone()),
|
||||||
|
Creature::generate_from_def(spider_def.clone()),
|
||||||
|
]),
|
||||||
|
SiteArea::from_creatures(&vec![
|
||||||
|
Creature::generate_from_def(bandit_def.clone()),
|
||||||
|
Creature::generate_from_def(bandit_def.clone()),
|
||||||
|
Creature::generate_from_def(bandit_def.clone()),
|
||||||
|
]),
|
||||||
|
SiteArea::from_creatures(&vec![
|
||||||
|
Creature::generate_from_def(spider_def.clone()),
|
||||||
|
Creature::generate_from_def(spider_def.clone()),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
let group = TravelGroup::new(
|
||||||
|
vec![
|
||||||
|
Creature::generate_from_def(bandit_def.clone()),
|
||||||
|
Creature::generate_from_def(bandit_def.clone()),
|
||||||
|
Creature::generate_from_def(bandit_def.clone()),
|
||||||
|
],
|
||||||
|
WorldCoords { x: 0, y: 0 },
|
||||||
|
Some(WorldPoint::Coords(WorldCoords { x: 0, y: 0 })),
|
||||||
|
Some(WorldPoint::Site(site.id())),
|
||||||
|
);
|
||||||
|
|
||||||
|
world.sites.push(site);
|
||||||
|
world.travel_groups.push(group);
|
||||||
|
std::fs::write("world.bin", bincode::serialize(&world).unwrap()).unwrap();
|
||||||
|
world
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for site in world.sites.iter() {
|
||||||
|
println!("{site}");
|
||||||
|
}
|
||||||
|
|
||||||
|
for group in world.travel_groups.iter() {
|
||||||
|
println!("{group}");
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut input = String::new();
|
||||||
|
loop {
|
||||||
|
std::io::stdin().read_line(&mut input).unwrap();
|
||||||
|
if ["q", "quit", "exit"]
|
||||||
|
.iter()
|
||||||
|
.any(|quit_cmd| input.trim() == *quit_cmd)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
let start = std::time::Instant::now();
|
||||||
|
world.advance_time(Duration::from_secs(3600));
|
||||||
|
let end = std::time::Instant::now();
|
||||||
|
println!("World tick: {}us", (end - start).as_micros());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resources_path() -> PathBuf {
|
||||||
|
PathBuf::from(".").join("defs")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,173 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
use bevy_mod_debugdump::{render_graph, render_graph_dot};
|
||||||
|
use core::{fmt, ops};
|
||||||
|
use std::{fs, path::Path};
|
||||||
|
|
||||||
|
mod basis;
|
||||||
|
mod plugin;
|
||||||
|
mod transform_f64;
|
||||||
|
mod types;
|
||||||
|
mod vector2;
|
||||||
|
mod vector2_i32;
|
||||||
|
mod vector3;
|
||||||
|
mod vector3_i32;
|
||||||
|
|
||||||
|
pub use basis::*;
|
||||||
|
pub use plugin::*;
|
||||||
|
pub use transform_f64::*;
|
||||||
|
pub use types::*;
|
||||||
|
pub use vector2::*;
|
||||||
|
pub use vector2_i32::*;
|
||||||
|
pub use vector3::*;
|
||||||
|
pub use vector3_i32::*;
|
||||||
|
|
||||||
|
pub trait VectorComponent:
|
||||||
|
Sized
|
||||||
|
+ Copy
|
||||||
|
+ PartialOrd
|
||||||
|
+ Reflect
|
||||||
|
+ fmt::Display
|
||||||
|
+ ops::Add<Output = Self>
|
||||||
|
+ ops::Neg<Output = Self>
|
||||||
|
+ ops::Sub<Output = Self>
|
||||||
|
+ ops::Mul<Output = Self>
|
||||||
|
+ ops::Div<Output = Self>
|
||||||
|
+ num_traits::identities::Zero
|
||||||
|
+ num_traits::identities::One
|
||||||
|
+ num_traits::sign::Signed
|
||||||
|
{
|
||||||
|
fn min(self, b: Self) -> Self;
|
||||||
|
fn max(self, b: Self) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> VectorComponent for T
|
||||||
|
where
|
||||||
|
T: Sized
|
||||||
|
+ Copy
|
||||||
|
+ PartialOrd
|
||||||
|
+ Reflect
|
||||||
|
+ fmt::Display
|
||||||
|
+ ops::Neg<Output = T>
|
||||||
|
+ ops::Add<Output = T>
|
||||||
|
+ ops::Sub<Output = T>
|
||||||
|
+ ops::Mul<Output = T>
|
||||||
|
+ ops::Div<Output = T>
|
||||||
|
+ num_traits::identities::Zero
|
||||||
|
+ num_traits::identities::One
|
||||||
|
+ num_traits::sign::Signed,
|
||||||
|
{
|
||||||
|
fn min(self, b: Self) -> Self {
|
||||||
|
if self < b {
|
||||||
|
self
|
||||||
|
} else {
|
||||||
|
b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn max(self, b: Self) -> Self {
|
||||||
|
if self > b {
|
||||||
|
self
|
||||||
|
} else {
|
||||||
|
b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lerp<
|
||||||
|
T: Copy
|
||||||
|
+ ops::Add<Output = T>
|
||||||
|
+ ops::Sub<Output = T>
|
||||||
|
+ ops::Mul<Output = T>
|
||||||
|
+ num_traits::identities::One,
|
||||||
|
>(
|
||||||
|
a: T,
|
||||||
|
b: T,
|
||||||
|
t: T,
|
||||||
|
) -> T {
|
||||||
|
a * (T::one() - t) + b * t
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inverse_lerp<T: Copy + ops::Sub<Output = T> + ops::Div<Output = T>>(
|
||||||
|
a: T,
|
||||||
|
b: T,
|
||||||
|
value: T,
|
||||||
|
) -> T {
|
||||||
|
(value - a) / (b - a)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vec2_lerp(a: Vec2, b: Vec2, t: f32) -> Vec2 {
|
||||||
|
Vec2 {
|
||||||
|
x: lerp(a.x, b.x, t),
|
||||||
|
y: lerp(a.y, b.y, t),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vec3_lerp(a: Vec3, b: Vec3, t: f32) -> Vec3 {
|
||||||
|
Vec3 {
|
||||||
|
x: lerp(a.x, b.x, t),
|
||||||
|
y: lerp(a.y, b.y, t),
|
||||||
|
z: lerp(a.z, b.z, t),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_towards_vec2(from: Vec2, to: Vec2, amount: f32) -> Vec2 {
|
||||||
|
let diff = to - from;
|
||||||
|
let length = diff.length();
|
||||||
|
if length <= f32::EPSILON {
|
||||||
|
return from;
|
||||||
|
}
|
||||||
|
from + diff.normalize() * length.min(amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn move_towards_vec3(from: Vec3, to: Vec3, amount: f32) -> Vec3 {
|
||||||
|
let diff = to - from;
|
||||||
|
let length = diff.length();
|
||||||
|
if length <= f32::EPSILON {
|
||||||
|
return from;
|
||||||
|
}
|
||||||
|
from + diff.normalize() * length.min(amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the intersection point (if any) of 2d lines a and b.
|
||||||
|
/// Lines are defined by 2 points on the line
|
||||||
|
pub fn vec2_intersection(a1: Vec2, a2: Vec2, b1: Vec2, b2: Vec2) -> Option<Vec2> {
|
||||||
|
let a_dir = a2 - a1;
|
||||||
|
let b_dir = b2 - b1;
|
||||||
|
let determinant = a_dir.perp_dot(b_dir);
|
||||||
|
if determinant.abs() <= f32::EPSILON {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(
|
||||||
|
Vec2 {
|
||||||
|
x: a_dir.x * (b1.x * b2.y - b1.y * b2.x) - (a1.x * a2.y - a1.y * a2.x) * b_dir.x,
|
||||||
|
y: (a1.x * a2.y - a1.y * a2.x) * -b_dir.y + a_dir.y * (b1.x * b2.y - b1.y * b2.x),
|
||||||
|
} / determinant,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn loop_value(from: f32, to: f32, value: f32) -> f32 {
|
||||||
|
let range = to - from;
|
||||||
|
if !range.is_normal() {
|
||||||
|
return from;
|
||||||
|
}
|
||||||
|
value - inverse_lerp(from, to, value).floor() * range
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_app_graphs(app: &mut App) {
|
||||||
|
// TODO: Figure out how to list schedules under the new interned ScheduleLabel system
|
||||||
|
println!("Writing render graph");
|
||||||
|
{
|
||||||
|
let path = Path::new("graphs").join("render.dot");
|
||||||
|
write_render_graph(app, &path);
|
||||||
|
println!("\t- {}", path.to_string_lossy());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_render_graph(app: &mut App, path: &Path) {
|
||||||
|
fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||||
|
fs::write(
|
||||||
|
path,
|
||||||
|
render_graph_dot(app, &render_graph::settings::Settings::default()),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use super::{Vector3, VectorComponent};
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug, Reflect)]
|
||||||
|
pub struct Basis<T: VectorComponent> {
|
||||||
|
pub x: Vector3<T>,
|
||||||
|
pub y: Vector3<T>,
|
||||||
|
pub z: Vector3<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> Default for Basis<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
x: Vector3::x(),
|
||||||
|
y: Vector3::y(),
|
||||||
|
z: Vector3::z(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> Basis<T> {
|
||||||
|
pub fn identity() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inverse(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
x: self.x * -T::one(),
|
||||||
|
y: self.y * -T::one(),
|
||||||
|
z: self.z * -T::one(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transform(&self, point: Vector3<T>) -> Vector3<T> {
|
||||||
|
self.x * point.x + self.y * point.y + self.z * point.z
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inverse_transform(&self, point: Vector3<T>) -> Vector3<T> {
|
||||||
|
self.inverse().transform(point)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
pub struct UtilPlugin;
|
||||||
|
|
||||||
|
impl Plugin for UtilPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.register_type::<SpriteLoader>()
|
||||||
|
.add_systems(PostUpdate, load_sprite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Automatically sets the `Sprite` components `image` to given asset path upon being added.
|
||||||
|
///
|
||||||
|
/// Can be used to set default sprite in required components without having access to AssetServer.
|
||||||
|
#[derive(Clone, Debug, Default, Component, Reflect)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
#[require(Sprite)]
|
||||||
|
pub struct SpriteLoader(pub String);
|
||||||
|
|
||||||
|
impl SpriteLoader {
|
||||||
|
pub fn from(path: impl Into<String>) -> Self {
|
||||||
|
Self(path.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_sprite(
|
||||||
|
mut sprite_query: Query<(&mut Sprite, &SpriteLoader), Added<SpriteLoader>>,
|
||||||
|
assets: Res<AssetServer>,
|
||||||
|
) {
|
||||||
|
for (mut sprite, loader) in sprite_query.iter_mut() {
|
||||||
|
sprite.image = assets.load(&loader.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
mod basis_f64;
|
||||||
|
mod vector3_f64;
|
||||||
|
|
||||||
|
pub use basis_f64::*;
|
||||||
|
pub use vector3_f64::*;
|
||||||
|
|
||||||
|
pub struct DoublePrecisionPlugin;
|
||||||
|
|
||||||
|
impl Plugin for DoublePrecisionPlugin {
|
||||||
|
fn build(&self, app: &mut App) {
|
||||||
|
app.insert_resource(WorldOffset::default())
|
||||||
|
.register_type::<TranslationD>()
|
||||||
|
.add_systems(PostUpdate, translation_system);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource, Reflect, Default)]
|
||||||
|
#[reflect(Resource)]
|
||||||
|
pub struct WorldOffset(pub Vector3D);
|
||||||
|
|
||||||
|
#[derive(Component, Reflect, Default)]
|
||||||
|
#[reflect(Component)]
|
||||||
|
pub struct TranslationD(pub Vector3D);
|
||||||
|
|
||||||
|
fn translation_system(
|
||||||
|
mut query: Query<(&mut Transform, &TranslationD)>,
|
||||||
|
world_offset: Res<WorldOffset>,
|
||||||
|
) {
|
||||||
|
for (mut real_transform, translation) in query.iter_mut() {
|
||||||
|
real_transform.translation = Vec3::from(translation.0 - world_offset.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
use crate::util::Basis;
|
||||||
|
|
||||||
|
use super::Vector3D;
|
||||||
|
|
||||||
|
pub type BasisD = Basis<f64>;
|
||||||
|
|
||||||
|
impl BasisD {
|
||||||
|
pub fn from_euler(yaw: f64, pitch: f64, roll: f64) -> Self {
|
||||||
|
let basis = Self::identity();
|
||||||
|
let basis = basis.rotate_by_axis(&basis.y, yaw);
|
||||||
|
let basis = basis.rotate_by_axis(&basis.x, pitch);
|
||||||
|
basis.rotate_by_axis(&basis.z, roll)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rotate_by_axis(&self, axis: &Vector3D, angle: f64) -> Self {
|
||||||
|
Self {
|
||||||
|
x: self.x.rotate_around_axis(axis, angle),
|
||||||
|
y: self.y.rotate_around_axis(axis, angle),
|
||||||
|
z: self.z.rotate_around_axis(axis, angle),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,74 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use crate::util::Vector3;
|
||||||
|
|
||||||
|
pub type Vector3D = Vector3<f64>;
|
||||||
|
|
||||||
|
impl Vector3D {
|
||||||
|
pub const ZERO: Self = Self {
|
||||||
|
x: 0.0,
|
||||||
|
y: 0.0,
|
||||||
|
z: 0.0,
|
||||||
|
};
|
||||||
|
pub const ONE: Self = Self {
|
||||||
|
x: 1.0,
|
||||||
|
y: 1.0,
|
||||||
|
z: 1.0,
|
||||||
|
};
|
||||||
|
pub const UP: Self = Self {
|
||||||
|
x: 0.0,
|
||||||
|
y: 1.0,
|
||||||
|
z: 0.0,
|
||||||
|
};
|
||||||
|
pub const DOWN: Self = Self {
|
||||||
|
x: 0.0,
|
||||||
|
y: -1.0,
|
||||||
|
z: 0.0,
|
||||||
|
};
|
||||||
|
pub const LEFT: Self = Self {
|
||||||
|
x: -1.0,
|
||||||
|
y: 0.0,
|
||||||
|
z: 0.0,
|
||||||
|
};
|
||||||
|
pub const RIGHT: Self = Self {
|
||||||
|
x: 1.0,
|
||||||
|
y: 0.0,
|
||||||
|
z: 0.0,
|
||||||
|
};
|
||||||
|
pub const FORWARD: Self = Self {
|
||||||
|
x: 0.0,
|
||||||
|
y: 0.0,
|
||||||
|
z: -1.0,
|
||||||
|
};
|
||||||
|
pub const BACK: Self = Self {
|
||||||
|
x: 0.0,
|
||||||
|
y: 0.0,
|
||||||
|
z: 1.0,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn rotate_around_axis(&self, axis: &Self, angle: f64) -> Self {
|
||||||
|
*self * angle.cos()
|
||||||
|
+ axis.cross(self) * angle.sin()
|
||||||
|
+ *axis * axis.dot(self) * (1.0 - angle.cos())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec3> for Vector3D {
|
||||||
|
fn from(vec: Vec3) -> Self {
|
||||||
|
Self {
|
||||||
|
x: vec.x as f64,
|
||||||
|
y: vec.y as f64,
|
||||||
|
z: vec.y as f64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vector3D> for Vec3 {
|
||||||
|
fn from(vec: Vector3D) -> Self {
|
||||||
|
Vec3 {
|
||||||
|
x: vec.x as f32,
|
||||||
|
y: vec.y as f32,
|
||||||
|
z: vec.z as f32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
pub type Seconds = f32;
|
||||||
|
pub type Kilograms = f32;
|
||||||
|
|
@ -0,0 +1,152 @@
|
||||||
|
use bevy::reflect::Reflect;
|
||||||
|
use core::{fmt, ops};
|
||||||
|
|
||||||
|
use super::VectorComponent;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Hash, Clone, Copy, Default, Debug, Reflect)]
|
||||||
|
pub struct Vector2<T: VectorComponent> {
|
||||||
|
pub x: T,
|
||||||
|
pub y: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> Vector2<T> {
|
||||||
|
pub fn x() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::one(),
|
||||||
|
y: T::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn y() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::zero(),
|
||||||
|
y: T::one(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn zero() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::zero(),
|
||||||
|
y: T::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn one() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::one(),
|
||||||
|
y: T::one(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn up() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::zero(),
|
||||||
|
y: T::one(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn down() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::zero(),
|
||||||
|
y: -T::one(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn left() -> Self {
|
||||||
|
Self {
|
||||||
|
x: -T::one(),
|
||||||
|
y: T::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn right() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::one(),
|
||||||
|
y: T::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(x: T, y: T) -> Vector2<T> {
|
||||||
|
Vector2 { x, y }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn min(&self, other: &Vector2<T>) -> Vector2<T> {
|
||||||
|
Vector2 {
|
||||||
|
x: VectorComponent::min(self.x, other.x),
|
||||||
|
y: VectorComponent::min(self.y, other.y),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn max(&self, other: &Vector2<T>) -> Vector2<T> {
|
||||||
|
Vector2 {
|
||||||
|
x: VectorComponent::max(self.x, other.x),
|
||||||
|
y: VectorComponent::max(self.y, other.y),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> fmt::Display for Vector2<T> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "({}, {})", self.x, self.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> ops::Add<Vector2<T>> for Vector2<T> {
|
||||||
|
type Output = Vector2<T>;
|
||||||
|
fn add(self, rhs: Vector2<T>) -> Self::Output {
|
||||||
|
Vector2 {
|
||||||
|
x: self.x + rhs.x,
|
||||||
|
y: self.y + rhs.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> ops::Neg for Vector2<T> {
|
||||||
|
type Output = Vector2<T>;
|
||||||
|
fn neg(self) -> Self::Output {
|
||||||
|
Vector2 {
|
||||||
|
x: -self.x,
|
||||||
|
y: -self.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> ops::Sub<Vector2<T>> for Vector2<T> {
|
||||||
|
type Output = Vector2<T>;
|
||||||
|
fn sub(self, rhs: Vector2<T>) -> Self::Output {
|
||||||
|
self + (-rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> ops::Mul<Vector2<T>> for Vector2<T> {
|
||||||
|
type Output = Vector2<T>;
|
||||||
|
fn mul(self, rhs: Vector2<T>) -> Self::Output {
|
||||||
|
Vector2 {
|
||||||
|
x: self.x * rhs.x,
|
||||||
|
y: self.y * rhs.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> ops::Mul<T> for Vector2<T> {
|
||||||
|
type Output = Vector2<T>;
|
||||||
|
fn mul(self, rhs: T) -> Self::Output {
|
||||||
|
Vector2 {
|
||||||
|
x: self.x * rhs,
|
||||||
|
y: self.y * rhs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> ops::Div<Vector2<T>> for Vector2<T> {
|
||||||
|
type Output = Vector2<T>;
|
||||||
|
fn div(self, rhs: Vector2<T>) -> Self::Output {
|
||||||
|
Vector2 {
|
||||||
|
x: self.x / rhs.x,
|
||||||
|
y: self.y / rhs.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> ops::Div<T> for Vector2<T> {
|
||||||
|
type Output = Vector2<T>;
|
||||||
|
fn div(self, rhs: T) -> Self::Output {
|
||||||
|
Vector2 {
|
||||||
|
x: self.x / rhs,
|
||||||
|
y: self.y / rhs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use super::Vector2;
|
||||||
|
|
||||||
|
pub type Vector2I = Vector2<i32>;
|
||||||
|
|
||||||
|
impl Vector2I {
|
||||||
|
pub const ZERO: Vector2I = Vector2I { x: 0, y: 0 };
|
||||||
|
pub const ONE: Vector2I = Vector2I { x: 1, y: 1 };
|
||||||
|
pub const UP: Vector2I = Vector2I { x: 0, y: 1 };
|
||||||
|
pub const DOWN: Vector2I = Vector2I { x: 0, y: -1 };
|
||||||
|
pub const LEFT: Vector2I = Vector2I { x: -1, y: 0 };
|
||||||
|
pub const RIGHT: Vector2I = Vector2I { x: 1, y: 0 };
|
||||||
|
|
||||||
|
pub fn angle(&self) -> f32 {
|
||||||
|
(self.y as f32).atan2(self.x as f32)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec2> for Vector2I {
|
||||||
|
fn from(vec: Vec2) -> Self {
|
||||||
|
Self {
|
||||||
|
x: vec.x as i32,
|
||||||
|
y: vec.y as i32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vector2I> for Vec2 {
|
||||||
|
fn from(vec: Vector2I) -> Self {
|
||||||
|
Vec2 {
|
||||||
|
x: vec.x as f32,
|
||||||
|
y: vec.y as f32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vector2I> for Vec3 {
|
||||||
|
fn from(vec: Vector2I) -> Self {
|
||||||
|
Vec3 {
|
||||||
|
x: vec.x as f32,
|
||||||
|
y: vec.y as f32,
|
||||||
|
z: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,202 @@
|
||||||
|
use bevy::reflect::Reflect;
|
||||||
|
use core::{fmt, ops};
|
||||||
|
|
||||||
|
use super::VectorComponent;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Hash, Clone, Copy, Default, Debug, Reflect)]
|
||||||
|
pub struct Vector3<T: VectorComponent> {
|
||||||
|
pub x: T,
|
||||||
|
pub y: T,
|
||||||
|
pub z: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> Vector3<T> {
|
||||||
|
pub fn x() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::one(),
|
||||||
|
y: T::zero(),
|
||||||
|
z: T::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn y() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::zero(),
|
||||||
|
y: T::one(),
|
||||||
|
z: T::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn z() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::zero(),
|
||||||
|
y: T::zero(),
|
||||||
|
z: T::one(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn zero() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::zero(),
|
||||||
|
y: T::zero(),
|
||||||
|
z: T::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn one() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::one(),
|
||||||
|
y: T::one(),
|
||||||
|
z: T::one(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn up() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::zero(),
|
||||||
|
y: T::one(),
|
||||||
|
z: T::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn down() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::zero(),
|
||||||
|
y: -T::one(),
|
||||||
|
z: T::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn left() -> Self {
|
||||||
|
Self {
|
||||||
|
x: -T::one(),
|
||||||
|
y: T::zero(),
|
||||||
|
z: T::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn right() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::one(),
|
||||||
|
y: T::zero(),
|
||||||
|
z: T::zero(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn forward() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::zero(),
|
||||||
|
y: T::zero(),
|
||||||
|
z: -T::one(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn back() -> Self {
|
||||||
|
Self {
|
||||||
|
x: T::zero(),
|
||||||
|
y: T::zero(),
|
||||||
|
z: T::one(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(x: T, y: T, z: T) -> Self {
|
||||||
|
Vector3 { x, y, z }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn min(&self, other: &Self) -> Self {
|
||||||
|
Vector3 {
|
||||||
|
x: VectorComponent::min(self.x, other.x),
|
||||||
|
y: VectorComponent::min(self.y, other.y),
|
||||||
|
z: VectorComponent::min(self.z, other.z),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn max(&self, other: &Self) -> Self {
|
||||||
|
Vector3 {
|
||||||
|
x: VectorComponent::max(self.x, other.x),
|
||||||
|
y: VectorComponent::max(self.y, other.y),
|
||||||
|
z: VectorComponent::max(self.z, other.z),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dot(&self, other: &Self) -> T {
|
||||||
|
self.x * other.x + self.y * other.y + self.z * other.z
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cross(&self, other: &Self) -> Self {
|
||||||
|
Self {
|
||||||
|
x: self.y * other.z - self.z * other.y,
|
||||||
|
y: self.z * other.x - self.x * other.z,
|
||||||
|
z: self.x * other.y - self.y * other.x,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> fmt::Display for Vector3<T> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "({}, {}, {})", self.x, self.y, self.z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> ops::Add<Vector3<T>> for Vector3<T> {
|
||||||
|
type Output = Self;
|
||||||
|
fn add(self, rhs: Self) -> Self::Output {
|
||||||
|
Vector3 {
|
||||||
|
x: self.x + rhs.x,
|
||||||
|
y: self.y + rhs.y,
|
||||||
|
z: self.z + rhs.z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> ops::Neg for Vector3<T> {
|
||||||
|
type Output = Self;
|
||||||
|
fn neg(self) -> Self::Output {
|
||||||
|
Vector3 {
|
||||||
|
x: -self.x,
|
||||||
|
y: -self.y,
|
||||||
|
z: -self.z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> ops::Sub<Vector3<T>> for Vector3<T> {
|
||||||
|
type Output = Self;
|
||||||
|
fn sub(self, rhs: Self) -> Self::Output {
|
||||||
|
self + (-rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> ops::Mul<Vector3<T>> for Vector3<T> {
|
||||||
|
type Output = Self;
|
||||||
|
fn mul(self, rhs: Self) -> Self::Output {
|
||||||
|
Vector3 {
|
||||||
|
x: self.x * rhs.x,
|
||||||
|
y: self.y * rhs.y,
|
||||||
|
z: self.z * rhs.z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> ops::Mul<T> for Vector3<T> {
|
||||||
|
type Output = Self;
|
||||||
|
fn mul(self, rhs: T) -> Self::Output {
|
||||||
|
Vector3 {
|
||||||
|
x: self.x * rhs,
|
||||||
|
y: self.y * rhs,
|
||||||
|
z: self.z * rhs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> ops::Div<Vector3<T>> for Vector3<T> {
|
||||||
|
type Output = Self;
|
||||||
|
fn div(self, rhs: Vector3<T>) -> Self::Output {
|
||||||
|
Vector3 {
|
||||||
|
x: self.x / rhs.x,
|
||||||
|
y: self.y / rhs.y,
|
||||||
|
z: self.z / rhs.z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: VectorComponent> ops::Div<T> for Vector3<T> {
|
||||||
|
type Output = Self;
|
||||||
|
fn div(self, rhs: T) -> Self::Output {
|
||||||
|
Vector3 {
|
||||||
|
x: self.x / rhs,
|
||||||
|
y: self.y / rhs,
|
||||||
|
z: self.z / rhs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
use bevy::prelude::*;
|
||||||
|
|
||||||
|
use super::Vector3;
|
||||||
|
|
||||||
|
pub type Vector3I = Vector3<i32>;
|
||||||
|
|
||||||
|
impl Vector3I {
|
||||||
|
pub const ZERO: Self = Self { x: 0, y: 0, z: 0 };
|
||||||
|
pub const ONE: Self = Self { x: 1, y: 1, z: 1 };
|
||||||
|
pub const UP: Self = Self { x: 0, y: 1, z: 0 };
|
||||||
|
pub const DOWN: Self = Self { x: 0, y: -1, z: 0 };
|
||||||
|
pub const LEFT: Self = Self { x: -1, y: 0, z: 0 };
|
||||||
|
pub const RIGHT: Self = Self { x: 1, y: 0, z: 0 };
|
||||||
|
pub const FORWARD: Self = Self { x: 0, y: 0, z: -1 };
|
||||||
|
pub const BACK: Self = Self { x: 0, y: 0, z: 1 };
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec3> for Vector3I {
|
||||||
|
fn from(vec: Vec3) -> Self {
|
||||||
|
Self {
|
||||||
|
x: vec.x as i32,
|
||||||
|
y: vec.y as i32,
|
||||||
|
z: vec.y as i32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vector3I> for Vec3 {
|
||||||
|
fn from(vec: Vector3I) -> Self {
|
||||||
|
Vec3 {
|
||||||
|
x: vec.x as f32,
|
||||||
|
y: vec.y as f32,
|
||||||
|
z: vec.z as f32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue