From d12585b28bfb7787a93835a5a5c7e686e0852ddf Mon Sep 17 00:00:00 2001 From: hut Date: Sun, 31 Mar 2024 22:00:34 +0200 Subject: [PATCH] split off command parser into commands.rs --- src/camera.rs | 1 + src/commands.rs | 611 ++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 2 + src/world.rs | 605 +---------------------------------------------- 4 files changed, 616 insertions(+), 603 deletions(-) create mode 100644 src/commands.rs diff --git a/src/camera.rs b/src/camera.rs index fd26449..05a3ff9 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -22,6 +22,7 @@ impl Plugin for CameraControllerPlugin { app.add_systems(PostUpdate, apply_input_to_player .after(PhysicsSet::Sync) .before(TransformSystem::TransformPropagate)); + app.insert_resource(ClearColor(Color::rgb(0.0, 0.0, 0.0))); } } diff --git a/src/commands.rs b/src/commands.rs new file mode 100644 index 0000000..8b4c040 --- /dev/null +++ b/src/commands.rs @@ -0,0 +1,611 @@ +// This plugin loads "defs.txt" and applies the therein contained commands +extern crate regex; +use bevy::prelude::*; +use bevy_xpbd_3d::prelude::*; +use crate::{actor, nature, world}; +use regex::Regex; +use std::f32::consts::PI; + +pub struct CommandsPlugin; +impl Plugin for CommandsPlugin { + fn build(&self, app: &mut App) { + app.add_systems(Startup, load_defs); + app.add_systems(Update, spawn_entities); + app.add_event::(); + } +} + +#[derive(Event)] pub struct SpawnEvent(ParserState); +#[derive(PartialEq, Clone)] +enum DefClass { + Actor, + Chat, + None, +} + +#[derive(Clone)] +struct ParserState { + class: DefClass, + + // Generic fields + name: String, + chat: String, + + // Actor fields + id: String, + pos: Vec3, + model: String, + model_scale: f32, + rotation: Quat, + angular_momentum: Vec3, + pronoun: String, + is_sphere: bool, + is_player: bool, + is_lifeform: bool, + is_alive: bool, + is_suited: bool, + is_vehicle: bool, + has_physics: bool, + collider_is_mesh: bool, + thrust_forward: f32, + thrust_sideways: f32, + thrust_back: f32, + reaction_wheels: f32, + warmup_seconds: f32, + engine_type: actor::EngineType, + oxygen: f32, + mass: f32, + collider: Collider, + camdistance: f32, + suit_integrity: f32, + light_brightness: f32, + light_color: Option, + + // Chat fields + delay: f64, + text: String, + level: String, + label: String, + goto: String, + is_choice: bool, + stores_item: bool, + script: String, + script_parameter: String, + script_parameter2: String, +} +impl Default for ParserState { + fn default() -> Self { + let default_actor = actor::Actor::default(); + let default_engine = actor::Engine::default(); + Self { + class: DefClass::None, + name: "NONAME".to_string(), + chat: "".to_string(), + + id: "".to_string(), + pos: Vec3::new(0.0, 0.0, 0.0), + model: "".to_string(), + model_scale: 1.0, + rotation: Quat::IDENTITY, + angular_momentum: Vec3::new(0.03, 0.3, 0.09), + pronoun: "they/them".to_string(), + is_sphere: false, + is_player: false, + is_lifeform: false, + is_alive: false, + is_suited: false, + is_vehicle: false, + has_physics: true, + collider_is_mesh: false, + thrust_forward: default_engine.thrust_forward, + thrust_sideways: default_engine.thrust_forward, + thrust_back: default_engine.thrust_back, + reaction_wheels: default_engine.reaction_wheels, + warmup_seconds: default_engine.warmup_seconds, + engine_type: default_engine.engine_type, + oxygen: nature::OXY_D, + mass: 1.0, + collider: Collider::sphere(1.0), + camdistance: default_actor.camdistance, + suit_integrity: 1.0, + light_brightness: 0.0, + light_color: None, + + delay: 0.0, + text: "".to_string(), + level: "chat".to_string(), + label: "".to_string(), + goto: "".to_string(), + is_choice: false, + stores_item: false, + script: "".to_string(), + script_parameter: "".to_string(), + script_parameter2: "".to_string(), + } + } +} +impl ParserState { + fn reset_message(&mut self) { + let default = ParserState::default(); + self.label = default.label; + self.delay = default.delay; + self.goto = default.goto; + self.level = default.level; + self.text = default.text; + self.is_choice = default.is_choice; + } + fn as_chatbranch(&self) -> actor::ChatBranch { + return actor::ChatBranch { + id: self.chat.clone(), + name: self.name.clone(), + label: self.label.clone(), + delay: self.delay.clone(), + sound: if self.is_choice { "".to_string() } else { "chat".to_string() }, + level: self.level.clone(), + reply: if self.is_choice { "".to_string() } else { self.text.clone() }, + choice: if self.is_choice { self.text.clone() } else { "".to_string() }, + goto: self.goto.clone(), + script: self.script.clone(), + script_parameter: self.script_parameter.clone(), + script_parameter2: self.script_parameter2.clone(), + } + } +} + +pub fn load_defs( + mut ew_spawn: EventWriter, +) { + let re1 = Regex::new(r"^\s*([a-z_-]+)\s+(.*)$").unwrap(); + let re2 = Regex::new("\"([^\"]*)\"|(-?[0-9]+(?:\\.[0-9]+)?)|([a-zA-Z_-][a-zA-Z0-9_-]*)").unwrap(); + let defs_string = include_str!("defs.txt"); + let mut lines = defs_string.lines(); + let mut state = ParserState::default(); + let mut command; + let mut parameters; + + let mut line_nr = -1; + while let Some(line) = lines.next() { + line_nr += 1; + let caps = re1.captures(line); + if caps.is_none() { + if line.trim() != "" { + error!("Syntax Error in definitions line {}: `{}`", line_nr, line); + } + continue; + } + if let Some(caps) = caps { + command = caps.get(1).unwrap().as_str(); + parameters = caps.get(2).unwrap().as_str(); + } + else { + error!("Failed to read regex captures in line {}: `{}`", line_nr, line); + continue; + } + + let mut parts: Vec<&str> = Vec::new(); + parts.push(command); + for caps in re2.captures_iter(parameters) { + if let Some(part) = caps.get(1) { + parts.push(&part.as_str()); + } + if let Some(part) = caps.get(2) { + parts.push(&part.as_str()); + } + if let Some(part) = caps.get(3) { + parts.push(&part.as_str()); + } + } + + match parts.as_slice() { + // Parsing actors + ["actor", x, y, z, model] => { + ew_spawn.send(SpawnEvent(state)); + state = ParserState::default(); + state.class = DefClass::Actor; + state.model = model.to_string(); + if let (Ok(x_float), Ok(y_float), Ok(z_float)) = + (x.parse::(), y.parse::(), z.parse::()) { + state.pos = Vec3::new(x_float, y_float, z_float); + } + else { + error!("Can't parse coordinates as floats in def: {line}"); + state = ParserState::default(); + continue; + } + } + ["sphere", "yes"] => { + state.is_sphere = true; + } + ["id", id] => { + state.id = id.to_string(); + } + ["alive", "yes"] => { + state.is_alive = true; + state.is_lifeform = true; + state.is_suited = true; + } + ["vehicle", "yes"] => { + state.is_vehicle = true; + } + ["oxygen", amount] => { + if let Ok(amount) = amount.parse::() { + state.is_lifeform = true; + state.is_suited = true; + state.oxygen = amount; + } + else { + error!("Can't parse float: {line}"); + continue; + } + } + ["pronoun", pronoun] => { + state.pronoun = pronoun.to_string(); + } + ["chatid", chat] => { + state.chat = chat.to_string(); + } + ["scale", scale] => { + if let Ok(scale_float) = scale.parse::() { + state.model_scale = scale_float; + } + else { + error!("Can't parse float: {line}"); + continue; + } + } + ["rotationx", rotation_x] => { + if let Ok(rotation_x_float) = rotation_x.parse::() { + state.rotation *= Quat::from_rotation_x(PI * rotation_x_float); + } + else { + error!("Can't parse float: {line}"); + continue; + } + } + ["rotationy", rotation_y] => { + if let Ok(rotation_y_float) = rotation_y.parse::() { + state.rotation *= Quat::from_rotation_y(PI * rotation_y_float); + } + else { + error!("Can't parse float: {line}"); + continue; + } + } + ["rotationz", rotation_z] => { + if let Ok(rotation_z_float) = rotation_z.parse::() { + state.rotation *= Quat::from_rotation_z(PI * rotation_z_float); + } + else { + error!("Can't parse float: {line}"); + continue; + } + } + ["angularmomentum", x, y, z] => { + if let (Ok(x_float), Ok(y_float), Ok(z_float)) = + (x.parse::(), y.parse::(), z.parse::()) { + state.angular_momentum = Vec3::new(x_float, y_float, z_float); + } + else { + error!("Can't parse float: {line}"); + continue; + } + } + ["thrust", forward, back, sideways, reaction_wheels, warmup_time] => { + if let (Ok(forward_float), Ok(back_float), Ok(sideways_float), Ok(reaction_wheels_float), Ok(warmup_time_float)) = (forward.parse::(), back.parse::(), sideways.parse::(), reaction_wheels.parse::(), warmup_time.parse::()) { + state.thrust_forward = forward_float; + state.thrust_back = back_float; + state.thrust_sideways = sideways_float; + state.reaction_wheels = reaction_wheels_float; + state.warmup_seconds = warmup_time_float; + } + } + ["engine", "rocket"] => { + state.engine_type = actor::EngineType::Rocket; + } + ["engine", "ion"] => { + state.engine_type = actor::EngineType::Ion; + } + ["engine", "monopropellant"] => { + state.engine_type = actor::EngineType::Monopropellant; + } + ["health", value] => { + if let Ok(value_float) = value.parse::() { + state.suit_integrity = value_float; + } + else { + error!("Can't parse float: {line}"); + continue; + } + } + ["mass", value] => { + if let Ok(value_float) = value.parse::() { + state.mass = value_float; + } + else { + error!("Can't parse float: {line}"); + continue; + } + } + ["physics", "off"] => { + state.has_physics = false; + } + ["collider", "sphere", radius] => { + if let Ok(radius_float) = radius.parse::() { + state.collider = Collider::sphere(radius_float); + } + else { + error!("Can't parse float: {line}"); + continue; + } + } + ["collider", "capsule", height, radius] => { + if let (Ok(height_float), Ok(radius_float)) = (height.parse::(), radius.parse::()) { + state.collider = Collider::capsule(height_float, radius_float); + } + else { + error!("Can't parse float: {line}"); + continue; + } + } + ["collider", "mesh"] => { + state.collider_is_mesh = true; + } + ["player", "yes"] => { + state.is_player = true; + state.is_alive = true; + } + ["camdistance", value] => { + if let Ok(value_float) = value.parse::() { + state.camdistance = value_float; + } + else { + error!("Can't parse float: {line}"); + continue; + } + } + ["light", color_hex, brightness] => { + if let Ok(brightness_float) = brightness.parse::() { + if let Ok(color) = Color::hex(color_hex) { + state.light_color = Some(color); + state.light_brightness = brightness_float; + } + else { + error!("Can't parse hexadecimal color code: {line}"); + continue; + } + } + else { + error!("Can't parse float: {line}"); + continue; + } + } + + // Parsing chats + ["chat", chat_name] => { + debug!("Registering chat: {}", chat_name); + ew_spawn.send(SpawnEvent(state)); + state = ParserState::default(); + state.class = DefClass::Chat; + state.chat = chat_name.to_string(); + } + ["name", name] => { + debug!("Registering name: {}", name); + state.name = name.to_string(); + } + ["msg", sleep, text] => { + debug!("Registering message (sleep={}): {}", sleep, text); + ew_spawn.send(SpawnEvent(state.clone())); + if let Ok(sleep_float) = sleep.parse::() { + state.delay = sleep_float; + state.text = text.to_string(); + state.stores_item = true; + state.is_choice = false; + } else { + error!("The 'sleep' value for this message is not a float: {}", line); + continue; + } + } + ["msg", sleep, label, goto, text] => { + debug!("Registering message (sleep={}): {}", sleep, text); + ew_spawn.send(SpawnEvent(state.clone())); + state.reset_message(); + if let Ok(sleep_float) = sleep.parse::() { + state.delay = sleep_float; + state.text = text.to_string(); + state.stores_item = true; + state.is_choice = false; + state.goto = goto.to_string(); + state.label = label.to_string(); + } else { + error!("The 'sleep' value for this message is not a float: {}", line); + continue; + } + } + ["choice", sleep, text] => { + debug!("Registering choice (sleep={}): {}", sleep, text); + ew_spawn.send(SpawnEvent(state.clone())); + state.reset_message(); + if let Ok(sleep_float) = sleep.parse::() { + state.delay = sleep_float; + state.text = text.to_string(); + state.stores_item = true; + state.is_choice = true; + } else { + error!("The 'sleep' value for this message is not a float: {}", line); + continue; + } + } + ["choice", sleep, label, goto, text] => { + debug!("Registering choice (sleep={}): {}", sleep, text); + ew_spawn.send(SpawnEvent(state.clone())); + state.reset_message(); + if let Ok(sleep_float) = sleep.parse::() { + state.delay = sleep_float; + state.text = text.to_string(); + state.stores_item = true; + state.is_choice = true; + state.goto = goto.to_string(); + state.label = label.to_string(); + } else { + error!("The 'sleep' value for this message is not a float: {}", line); + continue; + } + } + ["goto", label] => { + debug!("Registering goto: {}", label); + state.goto = label.to_string(); + } + ["label", label] => { + debug!("Registering label: {}", label); + state.label = label.to_string(); + } + ["lvl", level] => { + debug!("Registering level: {}", level); + state.level = level.to_string(); + } + ["script", scriptname, parameter] => { + state.script = scriptname.to_string(); + state.script_parameter = parameter.to_string(); + state.script_parameter2 = "".to_string(); + } + ["script", scriptname, parameter, parameter2] => { + state.script = scriptname.to_string(); + state.script_parameter = parameter.to_string(); + state.script_parameter2 = parameter2.to_string(); + } + _ => { + error!("No match for [{}]", parts.join(",")); + } + } + } + ew_spawn.send(SpawnEvent(state)); +} + +fn spawn_entities( + mut er_spawn: EventReader, + mut commands: Commands, + asset_server: Res, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + for state_wrapper in er_spawn.read() { + let state = &state_wrapper.0; + if state.class == DefClass::Chat { + if state.stores_item { + commands.spawn(state.as_chatbranch()); + } + } + else if state.class == DefClass::Actor { + let mut actor = commands.spawn_empty(); + actor.insert(actor::Actor { + id: state.id.clone(), + camdistance: state.camdistance, + ..default() + }); + if state.is_sphere { + let sphere_texture_handle: Handle = asset_server.load(format!("textures/{}.jpg", state.model)); + let sphere_handle = meshes.add(Sphere::default().mesh().uv(128, 128)); + let sphere_material_handle = materials.add(StandardMaterial { + base_color_texture: Some(sphere_texture_handle.clone()), + perceptual_roughness: 1.0, + metallic: 0.0, + ..default() + }); + actor.insert(PbrBundle { + mesh: sphere_handle, + material: sphere_material_handle, + transform: Transform { + translation: state.pos, + scale: Vec3::splat(state.model_scale), + rotation: state.rotation, + }, + ..default() + }); + } else { + actor.insert(SceneBundle { + transform: Transform { + translation: state.pos, + scale: Vec3::splat(state.model_scale), + rotation: state.rotation, + }, + scene: asset_server.load(world::asset_name_to_path(state.model.as_str())), + ..default() + }); + } + + // Physics Parameters + if state.has_physics { + let fix_scale = 1.0 / state.model_scale.powf(3.0); + actor.insert(RigidBody::Dynamic); + actor.insert(AngularVelocity(state.angular_momentum)); + actor.insert(ColliderDensity(state.mass * fix_scale)); + if state.collider_is_mesh { + actor.insert(AsyncSceneCollider::new(Some( + ComputedCollider::TriMesh + //ComputedCollider::ConvexDecomposition(VHACDParameters::default()) + ))); + } + else { + actor.insert(state.collider.clone()); + } + } + // TODO: angular velocity for objects without collisions, static objects + + // Optional Components + if state.is_player { + actor.insert(actor::Player); + actor.insert(actor::PlayerCamera); + } + if state.is_player || state.is_vehicle { + // used to apply mouse movement to actor rotation + actor.insert(ExternalTorque::ZERO.with_persistence(false)); + } + if state.is_lifeform { + actor.insert(actor::LifeForm::default()); + actor.insert(actor::Suit { + oxygen: state.oxygen, + oxygen_max: nature::OXY_D, + integrity: state.suit_integrity, + ..default() + }); + } + if let Some(color) = state.light_color { + actor.insert(PointLightBundle { + point_light: PointLight { + intensity: state.light_brightness, + color: color, + range: 100.0, + radius: 100.0, + ..default() + }, + transform: Transform { + translation: state.pos, + scale: Vec3::splat(state.model_scale), + rotation: state.rotation, + }, + ..default() + }); + } + if !state.chat.is_empty() { + actor.insert(actor::Talker { + conv_id: state.chat.clone(), + ..default() + }); + } + if state.is_vehicle { + actor.insert(actor::Vehicle::default()); + } + if state.is_vehicle || state.is_suited { + actor.insert(actor::Engine { + thrust_forward: state.thrust_forward, + thrust_back: state.thrust_back, + thrust_sideways: state.thrust_sideways, + reaction_wheels: state.reaction_wheels, + warmup_seconds: state.warmup_seconds, + engine_type: state.engine_type, + ..default() + }); + } + } + } +} diff --git a/src/main.rs b/src/main.rs index 14740b1..41fabdb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ mod audio; mod camera; +mod commands; mod world; mod settings; mod hud; @@ -51,6 +52,7 @@ impl Plugin for OutFlyPlugin { world::WorldPlugin, camera::CameraControllerPlugin, + commands::CommandsPlugin, hud::HudPlugin, actor::ActorPlugin, audio::AudioPlugin, diff --git a/src/world.rs b/src/world.rs index faae258..d5c96e6 100644 --- a/src/world.rs +++ b/src/world.rs @@ -1,6 +1,4 @@ -extern crate regex; use crate::{actor, nature}; -use regex::Regex; use bevy::prelude::*; //use bevy::core_pipeline::Skybox; //use bevy::asset::LoadState; @@ -19,7 +17,7 @@ const STARS_MAX_MAGNITUDE: f32 = 5.5; //const ASSET_CUBEMAP_AR: &str = "textures/out.png"; const ASSET_ASTEROID1: &str = "models/asteroid.glb#Scene0"; const ASSET_ASTEROID2: &str = "models/asteroid2.glb#Scene0"; -fn asset_name_to_path(name: &str) -> &'static str { +pub fn asset_name_to_path(name: &str) -> &'static str { match name { "suit" => "models/suit.glb#Scene0", "asteroid1" => ASSET_ASTEROID1, @@ -37,20 +35,15 @@ fn asset_name_to_path(name: &str) -> &'static str { pub struct WorldPlugin; impl Plugin for WorldPlugin { fn build(&self, app: &mut App) { - app.add_systems(Startup, (setup, load_defs)); - app.add_systems(Update, spawn_entities); + app.add_systems(Startup, setup); //app.add_systems(Update, asset_loaded.after(load_cubemap_asset)); //app.add_systems(Update, swap_world_on_ar_toggle); app.add_plugins(PhysicsPlugins::default()); //app.add_plugins(PhysicsDebugPlugin::default()); - app.insert_resource(ClearColor(Color::rgb(0.0, 0.0, 0.0))); app.insert_resource(Gravity(Vec3::splat(0.0))); - app.add_event::(); } } -#[derive(Event)] pub struct SpawnEvent(ParserState); - #[derive(Component)] pub struct Star; @@ -187,600 +180,6 @@ pub fn setup( }); } -#[derive(PartialEq, Clone)] -enum DefClass { - Actor, - Chat, - None, -} - -#[derive(Clone)] -struct ParserState { - class: DefClass, - - // Generic fields - name: String, - chat: String, - - // Actor fields - id: String, - pos: Vec3, - model: String, - model_scale: f32, - rotation: Quat, - angular_momentum: Vec3, - pronoun: String, - is_sphere: bool, - is_player: bool, - is_lifeform: bool, - is_alive: bool, - is_suited: bool, - is_vehicle: bool, - has_physics: bool, - collider_is_mesh: bool, - thrust_forward: f32, - thrust_sideways: f32, - thrust_back: f32, - reaction_wheels: f32, - warmup_seconds: f32, - engine_type: actor::EngineType, - oxygen: f32, - mass: f32, - collider: Collider, - camdistance: f32, - suit_integrity: f32, - light_brightness: f32, - light_color: Option, - - // Chat fields - delay: f64, - text: String, - level: String, - label: String, - goto: String, - is_choice: bool, - stores_item: bool, - script: String, - script_parameter: String, - script_parameter2: String, -} -impl Default for ParserState { - fn default() -> Self { - let default_actor = actor::Actor::default(); - let default_engine = actor::Engine::default(); - Self { - class: DefClass::None, - name: "NONAME".to_string(), - chat: "".to_string(), - - id: "".to_string(), - pos: Vec3::new(0.0, 0.0, 0.0), - model: "".to_string(), - model_scale: 1.0, - rotation: Quat::IDENTITY, - angular_momentum: Vec3::new(0.03, 0.3, 0.09), - pronoun: "they/them".to_string(), - is_sphere: false, - is_player: false, - is_lifeform: false, - is_alive: false, - is_suited: false, - is_vehicle: false, - has_physics: true, - collider_is_mesh: false, - thrust_forward: default_engine.thrust_forward, - thrust_sideways: default_engine.thrust_forward, - thrust_back: default_engine.thrust_back, - reaction_wheels: default_engine.reaction_wheels, - warmup_seconds: default_engine.warmup_seconds, - engine_type: default_engine.engine_type, - oxygen: nature::OXY_D, - mass: 1.0, - collider: Collider::sphere(1.0), - camdistance: default_actor.camdistance, - suit_integrity: 1.0, - light_brightness: 0.0, - light_color: None, - - delay: 0.0, - text: "".to_string(), - level: "chat".to_string(), - label: "".to_string(), - goto: "".to_string(), - is_choice: false, - stores_item: false, - script: "".to_string(), - script_parameter: "".to_string(), - script_parameter2: "".to_string(), - } - } -} -impl ParserState { - fn reset_message(&mut self) { - let default = ParserState::default(); - self.label = default.label; - self.delay = default.delay; - self.goto = default.goto; - self.level = default.level; - self.text = default.text; - self.is_choice = default.is_choice; - } - fn as_chatbranch(&self) -> actor::ChatBranch { - return actor::ChatBranch { - id: self.chat.clone(), - name: self.name.clone(), - label: self.label.clone(), - delay: self.delay.clone(), - sound: if self.is_choice { "".to_string() } else { "chat".to_string() }, - level: self.level.clone(), - reply: if self.is_choice { "".to_string() } else { self.text.clone() }, - choice: if self.is_choice { self.text.clone() } else { "".to_string() }, - goto: self.goto.clone(), - script: self.script.clone(), - script_parameter: self.script_parameter.clone(), - script_parameter2: self.script_parameter2.clone(), - } - } -} - -pub fn load_defs( - mut ew_spawn: EventWriter, -) { - let re1 = Regex::new(r"^\s*([a-z_-]+)\s+(.*)$").unwrap(); - let re2 = Regex::new("\"([^\"]*)\"|(-?[0-9]+(?:\\.[0-9]+)?)|([a-zA-Z_-][a-zA-Z0-9_-]*)").unwrap(); - let defs_string = include_str!("defs.txt"); - let mut lines = defs_string.lines(); - let mut state = ParserState::default(); - let mut command; - let mut parameters; - - let mut line_nr = -1; - while let Some(line) = lines.next() { - line_nr += 1; - let caps = re1.captures(line); - if caps.is_none() { - if line.trim() != "" { - error!("Syntax Error in definitions line {}: `{}`", line_nr, line); - } - continue; - } - if let Some(caps) = caps { - command = caps.get(1).unwrap().as_str(); - parameters = caps.get(2).unwrap().as_str(); - } - else { - error!("Failed to read regex captures in line {}: `{}`", line_nr, line); - continue; - } - - let mut parts: Vec<&str> = Vec::new(); - parts.push(command); - for caps in re2.captures_iter(parameters) { - if let Some(part) = caps.get(1) { - parts.push(&part.as_str()); - } - if let Some(part) = caps.get(2) { - parts.push(&part.as_str()); - } - if let Some(part) = caps.get(3) { - parts.push(&part.as_str()); - } - } - - match parts.as_slice() { - // Parsing actors - ["actor", x, y, z, model] => { - ew_spawn.send(SpawnEvent(state)); - state = ParserState::default(); - state.class = DefClass::Actor; - state.model = model.to_string(); - if let (Ok(x_float), Ok(y_float), Ok(z_float)) = - (x.parse::(), y.parse::(), z.parse::()) { - state.pos = Vec3::new(x_float, y_float, z_float); - } - else { - error!("Can't parse coordinates as floats in def: {line}"); - state = ParserState::default(); - continue; - } - } - ["sphere", "yes"] => { - state.is_sphere = true; - } - ["id", id] => { - state.id = id.to_string(); - } - ["alive", "yes"] => { - state.is_alive = true; - state.is_lifeform = true; - state.is_suited = true; - } - ["vehicle", "yes"] => { - state.is_vehicle = true; - } - ["oxygen", amount] => { - if let Ok(amount) = amount.parse::() { - state.is_lifeform = true; - state.is_suited = true; - state.oxygen = amount; - } - else { - error!("Can't parse float: {line}"); - continue; - } - } - ["pronoun", pronoun] => { - state.pronoun = pronoun.to_string(); - } - ["chatid", chat] => { - state.chat = chat.to_string(); - } - ["scale", scale] => { - if let Ok(scale_float) = scale.parse::() { - state.model_scale = scale_float; - } - else { - error!("Can't parse float: {line}"); - continue; - } - } - ["rotationx", rotation_x] => { - if let Ok(rotation_x_float) = rotation_x.parse::() { - state.rotation *= Quat::from_rotation_x(PI * rotation_x_float); - } - else { - error!("Can't parse float: {line}"); - continue; - } - } - ["rotationy", rotation_y] => { - if let Ok(rotation_y_float) = rotation_y.parse::() { - state.rotation *= Quat::from_rotation_y(PI * rotation_y_float); - } - else { - error!("Can't parse float: {line}"); - continue; - } - } - ["rotationz", rotation_z] => { - if let Ok(rotation_z_float) = rotation_z.parse::() { - state.rotation *= Quat::from_rotation_z(PI * rotation_z_float); - } - else { - error!("Can't parse float: {line}"); - continue; - } - } - ["angularmomentum", x, y, z] => { - if let (Ok(x_float), Ok(y_float), Ok(z_float)) = - (x.parse::(), y.parse::(), z.parse::()) { - state.angular_momentum = Vec3::new(x_float, y_float, z_float); - } - else { - error!("Can't parse float: {line}"); - continue; - } - } - ["thrust", forward, back, sideways, reaction_wheels, warmup_time] => { - if let (Ok(forward_float), Ok(back_float), Ok(sideways_float), Ok(reaction_wheels_float), Ok(warmup_time_float)) = (forward.parse::(), back.parse::(), sideways.parse::(), reaction_wheels.parse::(), warmup_time.parse::()) { - state.thrust_forward = forward_float; - state.thrust_back = back_float; - state.thrust_sideways = sideways_float; - state.reaction_wheels = reaction_wheels_float; - state.warmup_seconds = warmup_time_float; - } - } - ["engine", "rocket"] => { - state.engine_type = actor::EngineType::Rocket; - } - ["engine", "ion"] => { - state.engine_type = actor::EngineType::Ion; - } - ["engine", "monopropellant"] => { - state.engine_type = actor::EngineType::Monopropellant; - } - ["health", value] => { - if let Ok(value_float) = value.parse::() { - state.suit_integrity = value_float; - } - else { - error!("Can't parse float: {line}"); - continue; - } - } - ["mass", value] => { - if let Ok(value_float) = value.parse::() { - state.mass = value_float; - } - else { - error!("Can't parse float: {line}"); - continue; - } - } - ["physics", "off"] => { - state.has_physics = false; - } - ["collider", "sphere", radius] => { - if let Ok(radius_float) = radius.parse::() { - state.collider = Collider::sphere(radius_float); - } - else { - error!("Can't parse float: {line}"); - continue; - } - } - ["collider", "capsule", height, radius] => { - if let (Ok(height_float), Ok(radius_float)) = (height.parse::(), radius.parse::()) { - state.collider = Collider::capsule(height_float, radius_float); - } - else { - error!("Can't parse float: {line}"); - continue; - } - } - ["collider", "mesh"] => { - state.collider_is_mesh = true; - } - ["player", "yes"] => { - state.is_player = true; - state.is_alive = true; - } - ["camdistance", value] => { - if let Ok(value_float) = value.parse::() { - state.camdistance = value_float; - } - else { - error!("Can't parse float: {line}"); - continue; - } - } - ["light", color_hex, brightness] => { - if let Ok(brightness_float) = brightness.parse::() { - if let Ok(color) = Color::hex(color_hex) { - state.light_color = Some(color); - state.light_brightness = brightness_float; - } - else { - error!("Can't parse hexadecimal color code: {line}"); - continue; - } - } - else { - error!("Can't parse float: {line}"); - continue; - } - } - - // Parsing chats - ["chat", chat_name] => { - debug!("Registering chat: {}", chat_name); - ew_spawn.send(SpawnEvent(state)); - state = ParserState::default(); - state.class = DefClass::Chat; - state.chat = chat_name.to_string(); - } - ["name", name] => { - debug!("Registering name: {}", name); - state.name = name.to_string(); - } - ["msg", sleep, text] => { - debug!("Registering message (sleep={}): {}", sleep, text); - ew_spawn.send(SpawnEvent(state.clone())); - if let Ok(sleep_float) = sleep.parse::() { - state.delay = sleep_float; - state.text = text.to_string(); - state.stores_item = true; - state.is_choice = false; - } else { - error!("The 'sleep' value for this message is not a float: {}", line); - continue; - } - } - ["msg", sleep, label, goto, text] => { - debug!("Registering message (sleep={}): {}", sleep, text); - ew_spawn.send(SpawnEvent(state.clone())); - state.reset_message(); - if let Ok(sleep_float) = sleep.parse::() { - state.delay = sleep_float; - state.text = text.to_string(); - state.stores_item = true; - state.is_choice = false; - state.goto = goto.to_string(); - state.label = label.to_string(); - } else { - error!("The 'sleep' value for this message is not a float: {}", line); - continue; - } - } - ["choice", sleep, text] => { - debug!("Registering choice (sleep={}): {}", sleep, text); - ew_spawn.send(SpawnEvent(state.clone())); - state.reset_message(); - if let Ok(sleep_float) = sleep.parse::() { - state.delay = sleep_float; - state.text = text.to_string(); - state.stores_item = true; - state.is_choice = true; - } else { - error!("The 'sleep' value for this message is not a float: {}", line); - continue; - } - } - ["choice", sleep, label, goto, text] => { - debug!("Registering choice (sleep={}): {}", sleep, text); - ew_spawn.send(SpawnEvent(state.clone())); - state.reset_message(); - if let Ok(sleep_float) = sleep.parse::() { - state.delay = sleep_float; - state.text = text.to_string(); - state.stores_item = true; - state.is_choice = true; - state.goto = goto.to_string(); - state.label = label.to_string(); - } else { - error!("The 'sleep' value for this message is not a float: {}", line); - continue; - } - } - ["goto", label] => { - debug!("Registering goto: {}", label); - state.goto = label.to_string(); - } - ["label", label] => { - debug!("Registering label: {}", label); - state.label = label.to_string(); - } - ["lvl", level] => { - debug!("Registering level: {}", level); - state.level = level.to_string(); - } - ["script", scriptname, parameter] => { - state.script = scriptname.to_string(); - state.script_parameter = parameter.to_string(); - state.script_parameter2 = "".to_string(); - } - ["script", scriptname, parameter, parameter2] => { - state.script = scriptname.to_string(); - state.script_parameter = parameter.to_string(); - state.script_parameter2 = parameter2.to_string(); - } - _ => { - error!("No match for [{}]", parts.join(",")); - } - } - } - ew_spawn.send(SpawnEvent(state)); -} - -fn spawn_entities( - mut er_spawn: EventReader, - mut commands: Commands, - asset_server: Res, - mut meshes: ResMut>, - mut materials: ResMut>, -) { - for state_wrapper in er_spawn.read() { - let state = &state_wrapper.0; - if state.class == DefClass::Chat { - if state.stores_item { - commands.spawn(state.as_chatbranch()); - } - } - else if state.class == DefClass::Actor { - let mut actor = commands.spawn_empty(); - actor.insert(actor::Actor { - id: state.id.clone(), - camdistance: state.camdistance, - ..default() - }); - if state.is_sphere { - let sphere_texture_handle: Handle = asset_server.load(format!("textures/{}.jpg", state.model)); - let sphere_handle = meshes.add(Sphere::default().mesh().uv(128, 128)); - let sphere_material_handle = materials.add(StandardMaterial { - base_color_texture: Some(sphere_texture_handle.clone()), - perceptual_roughness: 1.0, - metallic: 0.0, - ..default() - }); - actor.insert(PbrBundle { - mesh: sphere_handle, - material: sphere_material_handle, - transform: Transform { - translation: state.pos, - scale: Vec3::splat(state.model_scale), - rotation: state.rotation, - }, - ..default() - }); - } else { - actor.insert(SceneBundle { - transform: Transform { - translation: state.pos, - scale: Vec3::splat(state.model_scale), - rotation: state.rotation, - }, - scene: asset_server.load(asset_name_to_path(state.model.as_str())), - ..default() - }); - } - - // Physics Parameters - if state.has_physics { - let fix_scale = 1.0 / state.model_scale.powf(3.0); - actor.insert(RigidBody::Dynamic); - actor.insert(AngularVelocity(state.angular_momentum)); - actor.insert(ColliderDensity(state.mass * fix_scale)); - if state.collider_is_mesh { - actor.insert(AsyncSceneCollider::new(Some( - ComputedCollider::TriMesh - //ComputedCollider::ConvexDecomposition(VHACDParameters::default()) - ))); - } - else { - actor.insert(state.collider.clone()); - } - } - // TODO: angular velocity for objects without collisions, static objects - - // Optional Components - if state.is_player { - actor.insert(actor::Player); - actor.insert(actor::PlayerCamera); - } - if state.is_player || state.is_vehicle { - // used to apply mouse movement to actor rotation - actor.insert(ExternalTorque::ZERO.with_persistence(false)); - } - if state.is_lifeform { - actor.insert(actor::LifeForm::default()); - actor.insert(actor::Suit { - oxygen: state.oxygen, - oxygen_max: nature::OXY_D, - integrity: state.suit_integrity, - ..default() - }); - } - if let Some(color) = state.light_color { - actor.insert(PointLightBundle { - point_light: PointLight { - intensity: state.light_brightness, - color: color, - range: 100.0, - radius: 100.0, - ..default() - }, - transform: Transform { - translation: state.pos, - scale: Vec3::splat(state.model_scale), - rotation: state.rotation, - }, - ..default() - }); - } - if !state.chat.is_empty() { - actor.insert(actor::Talker { - conv_id: state.chat.clone(), - ..default() - }); - } - if state.is_vehicle { - actor.insert(actor::Vehicle::default()); - } - if state.is_vehicle || state.is_suited { - actor.insert(actor::Engine { - thrust_forward: state.thrust_forward, - thrust_back: state.thrust_back, - thrust_sideways: state.thrust_sideways, - reaction_wheels: state.reaction_wheels, - warmup_seconds: state.warmup_seconds, - engine_type: state.engine_type, - ..default() - }); - } - } - } -} - //pub fn swap_world_on_ar_toggle( // asset_server: Res, // mut images: ResMut>,