use bevy::prelude::*; use std::collections::HashMap; use std::env; pub const SCOPE_SEPARATOR: &str = "$"; pub const TOKEN_EQUALS: &str = "=="; pub const TOKEN_EQUALS_NOT: &str = "!="; pub const TOKEN_GREATER_THAN: &str = ">"; pub const TOKEN_LESS_THAN: &str = "<"; pub const TOKEN_GREATER_EQUALS: &str = ">="; pub const TOKEN_LESS_EQUALS: &str = "<="; pub const TOKEN_NEGATE: &str = "~"; pub const DEFAULT_CHAT_SPEED: f32 = 10.0; #[derive(Resource)] pub struct Settings { pub dev_mode: bool, pub god_mode: bool, pub version: String, pub mute_sfx: bool, pub mute_music: bool, pub volume_sfx: u8, pub volume_music: u8, pub mouse_sensitivity: f32, pub fov: f32, pub fov_highspeed: f32, pub zoom_fov: f32, pub zoom_sensitivity_factor: f32, pub font_size_hud: f32, pub font_size_conversations: f32, pub font_size_choices: f32, pub font_size_console: f32, pub hud_color: Color, pub hud_color_console: Color, pub hud_color_console_warn: Color, pub hud_color_console_system: Color, pub hud_color_alert: Color, pub hud_color_subtitles: Color, pub hud_color_choices: Color, pub chat_speed: f32, pub hud_active: bool, pub is_zooming: bool, pub third_person: bool, pub rotation_stabilizer_active: bool, pub key_selectobject: MouseButton, pub key_zoom: MouseButton, pub key_togglehud: KeyCode, pub key_exit: KeyCode, pub key_restart: KeyCode, pub key_fullscreen: KeyCode, pub key_help: KeyCode, pub key_forward: KeyCode, pub key_back: KeyCode, pub key_left: KeyCode, pub key_right: KeyCode, pub key_up: KeyCode, pub key_down: KeyCode, pub key_run: KeyCode, pub key_stop: KeyCode, pub key_interact: KeyCode, pub key_vehicle: KeyCode, pub key_camera: KeyCode, pub key_rotate: KeyCode, pub key_rotation_stabilizer: KeyCode, pub key_mouseup: KeyCode, pub key_mousedown: KeyCode, pub key_mouseleft: KeyCode, pub key_mouseright: KeyCode, pub key_rotateleft: KeyCode, pub key_rotateright: KeyCode, pub key_reply1: KeyCode, pub key_reply2: KeyCode, pub key_reply3: KeyCode, pub key_reply4: KeyCode, pub key_reply5: KeyCode, pub key_reply6: KeyCode, pub key_reply7: KeyCode, pub key_reply8: KeyCode, pub key_reply9: KeyCode, pub key_reply10: KeyCode, pub key_cheat_god_mode: KeyCode, pub key_cheat_stop: KeyCode, pub key_cheat_speed: KeyCode, pub key_cheat_speed_backward: KeyCode, pub key_cheat_teleport: KeyCode, pub key_cheat_pizza: KeyCode, pub key_cheat_farview1: KeyCode, pub key_cheat_farview2: KeyCode, pub key_cheat_adrenaline_zero: KeyCode, pub key_cheat_adrenaline_mid: KeyCode, pub key_cheat_adrenaline_max: KeyCode, pub key_cheat_die: KeyCode, } impl Default for Settings { fn default() -> Self { let dev_mode; let default_mute_sfx = false; let default_mute_music; if let Ok(_) = env::var("CARGO") { // Mute audio by default when run through `cargo` default_mute_music = cfg!(debug_assertions); // Enable dev mode when running `cargo run` without `--release` dev_mode = cfg!(debug_assertions); } else { default_mute_music = false; dev_mode = false; } let version = if let Some(version) = option_env!("CARGO_PKG_VERSION") { version.to_string() } else { "13.37".to_string() }; Settings { dev_mode, god_mode: false, version, mute_sfx: default_mute_sfx, mute_music: default_mute_music, volume_sfx: 100, volume_music: 100, mouse_sensitivity: 0.4, fov: 50.0, fov_highspeed: 25.0, zoom_fov: 15.0, zoom_sensitivity_factor: 0.25, font_size_hud: 24.0, font_size_conversations: 32.0, font_size_choices: 28.0, font_size_console: 20.0, hud_color: Color::rgb(0.1, 0.5, 0.1), hud_color_console: Color::rgb(0.1, 0.5, 0.1), hud_color_console_warn: Color::rgb(1.0, 0.3, 0.3), hud_color_console_system: Color::rgb(0.5, 0.5, 0.5), hud_color_alert: Color::rgb(0.6, 0.094, 0.322), hud_color_subtitles: Color::rgb(0.8, 0.8, 0.8), hud_color_choices: Color::rgb(0.45, 0.45, 0.45), chat_speed: DEFAULT_CHAT_SPEED * if dev_mode { 2.5 } else { 1.0 }, hud_active: false, is_zooming: false, third_person: false, rotation_stabilizer_active: true, key_selectobject: MouseButton::Left, key_zoom: MouseButton::Right, key_togglehud: KeyCode::Tab, key_exit: KeyCode::Escape, key_restart: KeyCode::F7, key_fullscreen: KeyCode::F11, key_help: KeyCode::F1, key_forward: KeyCode::KeyW, key_back: KeyCode::KeyS, key_left: KeyCode::KeyA, key_right: KeyCode::KeyD, key_up: KeyCode::ShiftLeft, key_down: KeyCode::ControlLeft, key_run: KeyCode::KeyR, key_stop: KeyCode::Space, key_interact: KeyCode::KeyE, key_vehicle: KeyCode::KeyQ, key_camera: KeyCode::KeyF, key_rotate: KeyCode::KeyR, key_rotation_stabilizer: KeyCode::KeyY, key_mouseup: KeyCode::KeyI, key_mousedown: KeyCode::KeyK, key_mouseleft: KeyCode::KeyJ, key_mouseright: KeyCode::KeyL, key_rotateleft: KeyCode::KeyU, key_rotateright: KeyCode::KeyO, key_reply1: KeyCode::Digit1, key_reply2: KeyCode::Digit2, key_reply3: KeyCode::Digit3, key_reply4: KeyCode::Digit4, key_reply5: KeyCode::Digit5, key_reply6: KeyCode::Digit6, key_reply7: KeyCode::Digit7, key_reply8: KeyCode::Digit8, key_reply9: KeyCode::Digit9, key_reply10: KeyCode::Digit0, key_cheat_god_mode: KeyCode::KeyG, key_cheat_stop: KeyCode::KeyC, key_cheat_speed: KeyCode::KeyV, key_cheat_speed_backward: KeyCode::KeyB, key_cheat_teleport: KeyCode::KeyX, key_cheat_pizza: KeyCode::F9, key_cheat_farview1: KeyCode::F10, key_cheat_farview2: KeyCode::F12, key_cheat_adrenaline_zero: KeyCode::F5, key_cheat_adrenaline_mid: KeyCode::F6, key_cheat_adrenaline_max: KeyCode::F8, key_cheat_die: KeyCode::KeyZ, } } } impl Settings { #[allow(dead_code)] pub fn reset(&mut self) { println!("Resetting settings!"); *self = Self::default(); } pub fn reset_player_settings(&mut self) { println!("Resetting player settings!"); let default = Self::default(); self.rotation_stabilizer_active = default.rotation_stabilizer_active; self.third_person = default.third_person; self.is_zooming = default.is_zooming; } pub fn get_reply_keys(&self) -> [KeyCode; 10] { return [ self.key_reply1, self.key_reply2, self.key_reply3, self.key_reply4, self.key_reply5, self.key_reply6, self.key_reply7, self.key_reply8, self.key_reply9, self.key_reply10, ]; } } #[derive(Resource)] pub struct GameVars { pub db: HashMap, } impl Default for GameVars { fn default() -> Self { Self { db: HashMap::new(), } } } impl GameVars { #[allow(dead_code)] pub fn get(&self, key: &str) -> Option { if let Some(value) = self.db.get(key) { return Some(value.clone()); } return None; } #[allow(dead_code)] pub fn getf(&self, key: &str) -> Option { if let Some(value) = self.db.get(key) { if let Ok(float) = value.parse::() { return Some(float); } } return None; } pub fn getb(&self, key: &str) -> bool { if let Some(value) = self.db.get(key) { return Self::evaluate_str_as_bool(value); } return false; } pub fn evaluate_str_as_bool(string: &str) -> bool { return string != "0"; } // This method ensures that the variable name contains a scope separator, // and if a scope is missing, it prefixes the fallback scope. // Should NOT be used on non-variable values, like plain strings. // // Some examples, assuming fallback_scope="Clippy", SCOPE_SEPARATOR="$": // // "" -> "clippy$" // "foo" -> "clippy$foo" // "FOO" -> "clippy$foo" // "$foo" -> "clippy$foo" // "$$foo" -> "$$foo" // "PizzaClippy$foo" -> "pizzaclippy$foo" (unchanged) // "$foo$foo$foo$foo" -> "$foo$foo$foo$foo" (unchanged) pub fn normalize_varname(fallback_scope: &str, key: &str) -> String { let parts: Vec<&str> = key.split(SCOPE_SEPARATOR).collect(); let key: String = if parts.len() == 1 { // we got a key like "foo", turn it into "$foo" fallback_scope.to_string() + SCOPE_SEPARATOR + key } else if parts.len() > 1 { // we got a key with at least one "$" // extract anything before the last "$": let scope_part: String = parts[0..parts.len() - 2].join(SCOPE_SEPARATOR); if scope_part.is_empty() { // we got a key like "$foo", just prefix the fallback scope fallback_scope.to_string() + key } else { // we got a key like "Ke$ha$foo" or "$$foo" (which is the convention for // global variables), leave the scope intact key.to_string() } } else { // we got an empty string. this is bad, but handle gracefully fallback_scope.to_string() + SCOPE_SEPARATOR }; return key.to_lowercase(); } pub fn set_in_scope(&mut self, fallback_scope: &str, key: &str, value: String) { let key = Self::normalize_varname(fallback_scope, key); self.db.insert(key, value); } pub fn evaluate_condition(&self, condition: &str, scope: &str) -> bool { let parts: Vec<&str> = condition.split(" ").collect(); if parts.len() == 0 { // Got an empty string, this is always false. return false; } else if parts.len() == 1 { // Got something like "if $somevar:". // Check whether the variable evaluates to true. let part = parts[0]; let (part, negate) = if part.starts_with(TOKEN_NEGATE) { (&part[1..], true) } else { (part, false) }; if part.contains(SCOPE_SEPARATOR) { let part = Self::normalize_varname(scope, part); let value_bool = self.getb(part.as_str()); return value_bool ^ negate; } else { return Self::evaluate_str_as_bool(part) ^ negate; } } else if parts.len() == 2 { // Got something like "if $something somethingelse" // Check whether the two are identical. let mut left: String = parts[0].to_string(); if left.contains(SCOPE_SEPARATOR) { left = self .get(Self::normalize_varname(scope, left.as_str()).as_str()) .unwrap_or("".to_string()); } let mut right: String = parts[1].to_string(); if right.contains(SCOPE_SEPARATOR) { right = self .get(Self::normalize_varname(scope, right.as_str()).as_str()) .unwrap_or("".to_string()); } return left == right; } else { // Got something like "if $something != somethingelse bla bla" let mut left: String = parts[0].to_string(); if left.contains(SCOPE_SEPARATOR) { left = self .get(Self::normalize_varname(scope, left.as_str()).as_str()) .unwrap_or("".to_string()); } let mut right: String = parts[2..parts.len()].join(" ").to_string(); if right.contains(SCOPE_SEPARATOR) { right = self .get(Self::normalize_varname(scope, right.as_str()).as_str()) .unwrap_or("".to_string()); } let floats = (left.parse::(), right.parse::()); let operator: &str = parts[1]; match operator { TOKEN_EQUALS => { if let (Ok(left), Ok(right)) = floats { return left == right; } return left == right; } TOKEN_EQUALS_NOT => { if let (Ok(left), Ok(right)) = floats { return left != right; } return left != right; } TOKEN_GREATER_THAN => { if let (Ok(left), Ok(right)) = floats { return left > right; } return false; } TOKEN_GREATER_EQUALS => { if let (Ok(left), Ok(right)) = floats { return left >= right; } return false; } TOKEN_LESS_THAN => { if let (Ok(left), Ok(right)) = floats { return left < right; } return false; } TOKEN_LESS_EQUALS => { if let (Ok(left), Ok(right)) = floats { return left <= right; } return false; } _ => { error!("Unknown operator '{operator}' in if-condition!"); return false; } } } } }