Compare commits

...

13 Commits

14 changed files with 562 additions and 283 deletions

View File

@ -2,7 +2,7 @@ use bevy::prelude::*;
use bevy_xpbd_3d::prelude::*;
use bevy::scene::SceneInstance;
use bevy::math::DVec3;
use crate::{actor, audio, camera, chat, commands, effects, hud, nature, settings, world};
use crate::{actor, audio, camera, chat, commands, effects, hud, nature, var, world};
pub const ENGINE_SPEED_FACTOR: f32 = 30.0;
const MAX_TRANSMISSION_DISTANCE: f32 = 60.0;
@ -234,7 +234,7 @@ pub fn update_physics_lifeforms(
pub fn handle_input(
mut commands: Commands,
keyboard_input: Res<ButtonInput<KeyCode>>,
mut settings: ResMut<settings::Settings>,
mut settings: ResMut<var::Settings>,
q_talker: Query<(&chat::Talker, &Transform), (Without<actor::Player>, Without<Camera>)>,
player: Query<Entity, With<actor::Player>>,
q_camera: Query<&Transform, With<Camera>>,
@ -424,7 +424,7 @@ fn handle_player_death(
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_effect: EventWriter<effects::SpawnEffectEvent>,
mut log: ResMut<hud::Log>,
mut settings: ResMut<settings::Settings>,
mut settings: ResMut<var::Settings>,
) {
for death in er_playerdies.read() {
if settings.god_mode {
@ -472,7 +472,7 @@ fn handle_player_death(
fn handle_damage(
mut ew_playerdies: EventWriter<PlayerDiesEvent>,
mut q_hp: Query<(&mut HitPoints, Option<&Player>), Changed<HitPoints>>,
settings: Res<settings::Settings>,
settings: Res<var::Settings>,
) {
for (mut hp, player_maybe) in &mut q_hp {
if player_maybe.is_some() {

View File

@ -1,6 +1,6 @@
use bevy::prelude::*;
use bevy::audio::{PlaybackMode, Volume};
use crate::settings;
use crate::var;
const ASSET_CLICK: &str = "sounds/click-button-140881-crop.ogg";
const ASSET_SWITCH: &str = "sounds/typosonic-typing-192811-crop.ogg";
@ -59,7 +59,7 @@ pub enum Sfx {
pub fn setup(
mut commands: Commands,
settings: Res<settings::Settings>,
settings: Res<var::Settings>,
asset_server: Res<AssetServer>,
) {
commands.spawn((
@ -132,7 +132,7 @@ pub fn toggle_bgm(
keyboard_input: Res<ButtonInput<KeyCode>>,
mut evwriter_toggle: EventWriter<ToggleMusicEvent>,
mut evwriter_sfx: EventWriter<PlaySfxEvent>,
mut settings: ResMut<settings::Settings>,
mut settings: ResMut<var::Settings>,
) {
if keyboard_input.just_pressed(KeyCode::KeyT) {
settings.mute_music ^= true;
@ -148,7 +148,7 @@ pub fn toggle_bgm(
pub fn play_sfx(
mut commands: Commands,
settings: Res<settings::Settings>,
settings: Res<var::Settings>,
mut events_sfx: EventReader<PlaySfxEvent>,
sound_click: Res<SoundClick>,
sound_switch: Res<SoundSwitch>,
@ -200,7 +200,7 @@ pub fn str2sfx(sfx_label: &str) -> Sfx {
pub fn update_music(
mut events: EventReader<ToggleMusicEvent>,
bgm_controller: Query<&AudioSink, With<ComponentBGM>>,
settings: Res<settings::Settings>,
settings: Res<var::Settings>,
) {
if !events.is_empty() {
events.clear();

View File

@ -8,7 +8,7 @@ use bevy::transform::TransformSystem;
use bevy::math::{DVec3, DQuat};
use bevy_xpbd_3d::prelude::*;
use std::f32::consts::PI;
use crate::{actor, audio, hud, settings};
use crate::{actor, audio, hud, var};
pub struct CameraPlugin;
@ -68,7 +68,7 @@ pub fn setup_camera(
}
pub fn sync_camera_to_player(
settings: Res<settings::Settings>,
settings: Res<var::Settings>,
mut q_camera: Query<&mut Transform, (With<Camera>, Without<actor::PlayerCamera>)>,
q_playercam: Query<(&actor::Actor, &Transform), (With<actor::PlayerCamera>, Without<Camera>)>,
) {
@ -93,7 +93,7 @@ pub fn sync_camera_to_player(
pub fn update_fov(
q_player: Query<&actor::ExperiencesGForce, With<actor::Player>>,
mouse_input: Res<ButtonInput<MouseButton>>,
mut settings: ResMut<settings::Settings>,
mut settings: ResMut<var::Settings>,
mut q_camera: Query<&mut Projection, With<Camera>>,
) {
if let (Ok(gforce), Ok(mut projection)) = (q_player.get_single(), q_camera.get_single_mut())
@ -116,7 +116,7 @@ pub fn update_fov(
pub fn handle_input(
keyboard_input: Res<ButtonInput<KeyCode>>,
mut settings: ResMut<settings::Settings>,
mut settings: ResMut<var::Settings>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
) {
if keyboard_input.just_pressed(settings.key_camera) {
@ -129,7 +129,7 @@ pub fn handle_input(
}
fn manage_player_actor(
settings: Res<settings::Settings>,
settings: Res<var::Settings>,
mut q_playercam: Query<&mut Visibility, With<actor::PlayerCamera>>,
mut q_hiddenplayer: Query<(&mut Visibility, &mut Position, &mut Rotation, &mut LinearVelocity, &mut AngularVelocity), (With<actor::Player>, Without<actor::PlayerCamera>)>,
q_ride: Query<(&Transform, &Position, &Rotation, &LinearVelocity, &AngularVelocity), (With<actor::PlayerDrivesThis>, Without<actor::Player>)>,
@ -161,7 +161,7 @@ fn manage_player_actor(
#[allow(clippy::too_many_arguments)]
pub fn apply_input_to_player(
time: Res<Time>,
settings: Res<settings::Settings>,
settings: Res<var::Settings>,
windows: Query<&Window, With<PrimaryWindow>>,
mut mouse_events: EventReader<MouseMotion>,
key_input: Res<ButtonInput<KeyCode>>,

View File

@ -1,4 +1,4 @@
use crate::{actor, audio, hud, settings, world, effects};
use crate::{actor, audio, effects, hud, var, world};
use bevy::prelude::*;
use bevy::math::DVec3;
use bevy_xpbd_3d::prelude::*;
@ -18,6 +18,7 @@ pub const TOKEN_WARN: &str = "warn";
pub const TOKEN_SLEEP: &str = "sleep";
pub const TOKEN_SET: &str = "set";
pub const TOKEN_IF: &str = "if";
pub const TOKEN_THEN: &str = "then";
pub const TOKEN_GOTO: &str = "goto";
pub const TOKEN_LABEL: &str = "label";
pub const TOKEN_SCRIPT: &str = "script";
@ -26,13 +27,14 @@ pub const TOKEN_NOWAIT: &str = "nowait";
pub const TOKEN_INCLUDE: &str = "include";
pub const TOKEN_GOTO_EXIT: &str = "EXIT";
pub const TOKEN_IF_INLINE: &str = "if "; // for lines like `- if foo:`
pub const DEFAULT_SOUND: &str = "chat";
pub const MAX_BRANCH_DEPTH: usize = 64;
pub const CHOICE_TIMER: f64 = 40.0 * settings::DEFAULT_CHAT_SPEED as f64;
pub const CHOICE_TIMER: f64 = 40.0 * var::DEFAULT_CHAT_SPEED as f64;
pub const LETTERS_PER_SECOND: f32 = 17.0;
pub const TALKER_SPEED_FACTOR: f32 = settings::DEFAULT_CHAT_SPEED / LETTERS_PER_SECOND;
pub const TALKER_SPEED_FACTOR: f32 = var::DEFAULT_CHAT_SPEED / LETTERS_PER_SECOND;
pub const CHAT_SPEED_MIN_LEN: f32 = 40.0;
pub const NON_CHOICE_TOKENS: &[&str] = &[
@ -43,6 +45,7 @@ pub const NON_CHOICE_TOKENS: &[&str] = &[
TOKEN_SLEEP,
TOKEN_SET,
TOKEN_IF,
TOKEN_THEN,
TOKEN_GOTO,
TOKEN_LABEL,
TOKEN_SCRIPT,
@ -78,7 +81,7 @@ type ChatPos = Vec<usize>;
#[derive(Component)]
pub struct Chat {
pub id: usize,
pub internal_id: usize,
pub position: ChatPos,
pub timer: f64,
pub talker: Talker,
@ -94,7 +97,8 @@ pub struct Choice {
#[derive(Component)]
#[derive(Clone)]
pub struct Talker {
pub conv_id: String,
pub chat_name: String,
pub actor_id: String,
pub name: Option<String>,
pub pronoun: Option<String>,
pub talking_speed: f32,
@ -113,12 +117,29 @@ pub enum ChatEvent {
DespawnAllChoices,
DespawnAllChats,
SpawnMessage(String, hud::LogLevel, String),
SpawnChoice(String, usize, ChatPos, bool),
SpawnChoice(String, usize, ChatPos, bool, Option<String>),
RunScript(String),
Sleep(f64),
//Script(String, String, String),
SleepSeconds(f64),
SetVariable(String),
GotoIf(String, ChatPos),
}
pub struct Extracted {
choice_text: Option<String>,
nowait: bool,
condition: Option<String>,
}
impl Default for Extracted {
fn default() -> Self {
Self {
choice_text: None,
nowait: false,
condition: None,
}
}
}
// This is the only place where any YAML interaction should be happening.
#[derive(Resource)]
pub struct ChatDB(Vec<Value>);
@ -191,7 +212,9 @@ impl ChatDB {
for (index, label) in changes {
if index < vector.len() {
vector.remove(index);
vector.splice(index..index, include_db[&label].iter().cloned());
if let Some(chat) = include_db.get(&label) {
vector.splice(index..index, chat.iter().cloned());
}
}
}
}
@ -222,25 +245,38 @@ impl ChatDB {
// Not acceptable:
// - `"What's up?"`
// - `{"goto": "foo"}`
// Returns (choice text, sub-conversation branch, nowait flag)
fn search_choice(&self, yaml: Option<&Value>) -> Option<(String, Value, bool)> {
fn is_choice(&self, yaml: Option<&Value>) -> bool {
if let Some(data) = self.extract(yaml) {
return data.choice_text.is_some();
}
return false;
}
fn extract(&self, yaml: Option<&Value>) -> Option<Extracted> {
let non_choice_tokens = NON_CHOICE_TOKENS.to_vec();
let mut result: Option<(String, Value, bool)> = None;
let mut nowait = false;
if let Some(Value::Mapping(map)) = yaml {
let mut result: Extracted = Extracted::default();
for (key, value) in map {
if let Value::String(key) = key {
if key == TOKEN_NOWAIT && value.as_bool() == Some(true) {
nowait = true;
result.nowait = true;
}
if non_choice_tokens.contains(&key.as_str()) {
continue;
else if key == TOKEN_IF {
if let Some(condition) = value.as_str() {
result.condition = Some(condition.to_string());
}
}
else if non_choice_tokens.contains(&key.as_str()) {
// skip over the other non-choice tokens
}
else {
result.choice_text = Some(key.to_string());
}
result = Some((key.into(), map[key].clone(), nowait));
}
}
return Some(result);
}
return result;
return None;
}
fn search_label_recursively(&self, sequence: &Value, label: &String, mut pos: ChatPos) -> Option<ChatPos> {
@ -296,7 +332,7 @@ impl ChatDB {
let mut popped = false;
let mut seek_past_dialog_choices = false;
while chat.position.len() > 0 {
match self.at(chat.id, &chat.position) {
match self.at(chat.internal_id, &chat.position) {
None => {
chat.position.pop();
popped = true;
@ -307,7 +343,7 @@ impl ChatDB {
}
},
Some(Value::Mapping(map)) => {
if seek_past_dialog_choices && self.search_choice(Some(&Value::Mapping(map))).is_some() {
if seek_past_dialog_choices && self.is_choice(Some(&Value::Mapping(map))) {
// we just dropped out of a branch and ended up in a dialog
// choice. let's seek past all the choices until we find
// the next non-dialog-choice item.
@ -348,12 +384,12 @@ impl ChatDB {
result = Some(Value::String(value_string.into()));
}
Some(Value::Mapping(mapping)) => {
if let Some((_choicetext, subconversation, _)) = self.search_choice(value) {
result = Some(Value::Mapping(mapping.clone()));
next_pointer = Some(subconversation);
}
else {
result = Some(Value::Mapping(mapping.clone()));
result = Some(Value::Mapping(mapping.clone()));
for value in mapping.values() {
if let Some(list) = value.as_sequence() {
next_pointer = Some(Value::Sequence(list.clone()));
break;
}
}
}
None => {
@ -379,7 +415,7 @@ impl ChatDB {
// This includes flow control tokens like "goto", no-op tokens like "label",
// but not something with a side effect like "script", and especially not "if".
fn is_skippable(&self, chat: &mut Chat) -> bool {
let current_item = self.at(chat.id, &chat.position);
let current_item = self.at(chat.internal_id, &chat.position);
if current_item.is_none() {
return false;
}
@ -405,7 +441,7 @@ impl ChatDB {
chat: &mut Chat,
event: &mut EventWriter<ChatEvent>,
) -> bool {
let current_item = self.at(chat.id, &chat.position);
let current_item = self.at(chat.internal_id, &chat.position);
let mut processed_a_choice = false;
match current_item {
Some(Value::String(message)) => {
@ -416,7 +452,7 @@ impl ChatDB {
let mut sound = DEFAULT_SOUND.to_string();
// Is this a dialog choice?
if let Some(_) = self.search_choice(Some(&Value::Mapping(map.clone()))) {
if self.is_choice(Some(&Value::Mapping(map.clone()))) {
processed_a_choice = true;
}
@ -427,12 +463,25 @@ impl ChatDB {
for (key, value) in &map {
let key = key.as_str();
match (key, value) {
(Some(TOKEN_IF), _) => {} // TODO
(Some(TOKEN_IF), Value::String(condition)) => {
let mut pos = chat.position.clone();
pos.push(0);
event.send(ChatEvent::GotoIf(condition.into(), pos));
}
(Some(TOKEN_SOUND), Value::String(sound_name)) => {
sound = sound_name.clone();
}
_ => {}
}
if let Some(key) = key {
if key.starts_with(TOKEN_IF_INLINE) {
let condition: &str = &key[TOKEN_IF_INLINE.len()..];
let mut pos = chat.position.clone();
pos.push(0);
event.send(ChatEvent::GotoIf(condition.into(), pos));
}
}
}
// Second pass
@ -451,7 +500,9 @@ impl ChatDB {
event.send(ChatEvent::SpawnMessage(
message.to_string(), hud::LogLevel::Warning, sound.clone()));
}
(Some(TOKEN_SET), _) => {} // TODO
(Some(TOKEN_SET), Value::String(instructions)) => {
event.send(ChatEvent::SetVariable(instructions.to_string()));
}
_ => {}
}
}
@ -462,11 +513,11 @@ impl ChatDB {
match (key, value) {
(Some(TOKEN_SLEEP), Value::Number(time)) => {
if let Some(time_f64) = time.as_f64() {
event.send(ChatEvent::Sleep(time_f64));
event.send(ChatEvent::SleepSeconds(time_f64));
}
}
(Some(TOKEN_GOTO), Value::String(label)) => {
match self.search_label(chat.id, &label) {
match self.search_label(chat.internal_id, &label) {
Some(pos) => {
chat.position = pos;
}
@ -517,16 +568,21 @@ impl ChatDB {
// Spawn choices until we reach a non-choice item or the end of the branch
let mut key: usize = 0;
let mut reached_end_of_branch = false;
while let Some((choice, _, nowait)) =
self.search_choice(self.at(chat.id, &chat.position).as_ref()) {
if reached_end_of_branch {
while let Some(data) = self.extract(self.at(chat.internal_id, &chat.position).as_ref()) {
if let Some(choice_text) = data.choice_text {
if reached_end_of_branch {
break;
}
let mut goto: Vec<usize> = chat.position.clone();
goto.push(0);
event.send(ChatEvent::SpawnChoice(choice_text,
key, goto, data.nowait, data.condition));
key += 1;
reached_end_of_branch = self.advance_pointer(chat);
}
else {
break;
}
let mut goto: Vec<usize> = chat.position.clone();
goto.push(0);
event.send(ChatEvent::SpawnChoice(choice, key, goto, nowait));
key += 1;
reached_end_of_branch = self.advance_pointer(chat);
}
}
}
@ -555,10 +611,10 @@ pub fn handle_new_conversations(
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Ping));
return;
}
match (*chatdb).get_chat_by_id(&event.talker.conv_id) {
match (*chatdb).get_chat_by_id(&event.talker.chat_name) {
Ok(chat_id) => {
let mut chat = Chat {
id: chat_id,
internal_id: chat_id,
position: vec![0],
timer: time.elapsed_seconds_f64(),
talker: event.talker.clone(),
@ -596,10 +652,11 @@ pub fn handle_chat_events(
mut ew_chatscript: EventWriter<ChatScriptEvent>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut log: ResMut<hud::Log>,
mut vars: ResMut<var::GameVars>,
q_choices: Query<Entity, With<Choice>>,
mut q_chats: Query<(Entity, &mut Chat)>,
time: Res<Time>,
settings: Res<settings::Settings>,
settings: Res<var::Settings>,
) {
for event in er_chatevent.read() {
let now = time.elapsed_seconds_f64();
@ -635,35 +692,54 @@ pub fn handle_chat_events(
let sfx = audio::str2sfx(sound);
ew_sfx.send(audio::PlaySfxEvent(sfx));
}
ChatEvent::SpawnChoice(replytext, key, goto, nowait) => {
commands.spawn((
world::DespawnOnPlayerDeath,
Choice {
text: replytext.into(),
key: *key,
goto: goto.clone(),
ChatEvent::SpawnChoice(replytext, key, goto, nowait, condition) => {
'out: {
dbg!(condition);
if let Some(condition) = condition {
if !vars.evaluate_condition(condition, &chat.talker.actor_id) {
break 'out;
}
}
commands.spawn((
world::DespawnOnPlayerDeath,
Choice {
text: replytext.into(),
key: *key,
goto: goto.clone(),
}
));
if !nowait {
chat.timer = now + CHOICE_TIMER / settings.chat_speed as f64;
}
));
if !nowait {
chat.timer = now + CHOICE_TIMER / settings.chat_speed as f64;
}
}
ChatEvent::RunScript(script) => {
ew_chatscript.send(ChatScriptEvent(script.clone()));
}
ChatEvent::Sleep(sleep_duration) => {
ChatEvent::SleepSeconds(sleep_duration) => {
chat.timer = now + sleep_duration;
}
ChatEvent::SetVariable(string) => {
if let Some((key, value)) = string.split_once(" ") {
vars.set_in_scope(&chat.talker.actor_id, key, value.into());
} else {
vars.set_in_scope(&chat.talker.actor_id, string, "".into());
}
}
ChatEvent::GotoIf(condition, goto) => {
if vars.evaluate_condition(condition, &chat.talker.actor_id) {
chat.position = goto.clone();
}
}
}
}
}
fn handle_reply_keys(
keyboard_input: Res<ButtonInput<KeyCode>>,
settings: ResMut<settings::Settings>,
settings: ResMut<var::Settings>,
q_choices: Query<&Choice>,
mut q_chats: Query<&mut Chat>,
//mut evwriter_sendmsg: EventWriter<SendMessageEvent>,
mut evwriter_sfx: EventWriter<audio::PlaySfxEvent>,
time: Res<Time>,
) {

View File

@ -17,7 +17,12 @@
- chat: Icarus
- if $met:
- Oh hey, you're back!
- How are you doing?
- goto: howru
- Oh hey, you're awake!
- set: $met
- I found you drifting out cold, and thought, I better watch over you.
- Took us here behind that moonlet, to shield you from the micros.
- Thank you!:

View File

@ -7,7 +7,7 @@
- chat: ClippyTransSerenity
- set: busstop serenity
- set: $busstop serenity
- Welcome at StarTrans Cargo Services! You have reached Serenity Station.
- "Ready for a trip? Available bus stops: Oscillation Station, Metis Prime Station"
- label: startransbusstop
@ -21,7 +21,7 @@
- chat: ClippyTransMetis
- set: busstop metis
- set: $busstop metis
- Welcome at StarTrans Cargo Services! You have reached Metis Prime Station.
- "Ready for a trip? Available bus stops: Oscillation Station, Serenity Station"
- label: startransbusstop
@ -36,7 +36,7 @@
- chat: ClippyTransOscillation
- set: busstop oscillation
- set: $busstop oscillation
- Welcome at StarTrans Cargo Services! You have reached Oscillation Station.
- "Ready for a trip? Available bus stops: Serenity Station, Metis Prime Station"
- label: startransbusstop
@ -57,21 +57,21 @@
- Can I take a spacecraft with me?:
- Absolutely.
- goto: startransbusstop
- if: busstop != "oscillation"
- if: $busstop != oscillation
Take me to Oscillation Station, please.:
- StarTrans wishes you a pleasant journey.
- script: cryofadeout
- sleep: 5
- script: cryotrip oscillation
- goto: EXIT
- if: busstop != "metis"
- if: $busstop != metis
Take me to Metis Prime Station, please.:
- StarTrans wishes you a pleasant journey.
- script: cryofadeout
- sleep: 5
- script: cryotrip metisprime
- goto: EXIT
- if: busstop != "serenity"
- if: $busstop != serenity
Take me to Serenity Station, please.:
- StarTrans wishes you a pleasant journey.
- script: cryofadeout

View File

@ -692,7 +692,8 @@ fn spawn_entities(
}
if !state.chat.is_empty() {
actor.insert(chat::Talker {
conv_id: state.chat.clone(),
actor_id: state.id.clone(),
chat_name: state.chat.clone(),
name: state.name.clone(),
pronoun: Some(state.pronoun.clone()),
talking_speed: 1.0,

View File

@ -199,6 +199,7 @@ actor -3300 10 0 pizzeria
actor 60 -15 -40 suit
relativeto player
name Icarus
id Icarus
chatid Icarus
alive yes
scale 2

View File

@ -1,5 +1,5 @@
use bevy::prelude::*;
use crate::{camera, settings};
use crate::{camera, var};
pub struct EffectsPlugin;
@ -40,7 +40,7 @@ pub struct SpawnEffectEvent {
}
pub fn setup(
settings: Res<settings::Settings>,
settings: Res<var::Settings>,
mut ew_effect: EventWriter<SpawnEffectEvent>,
) {
if !settings.dev_mode {

View File

@ -1,4 +1,4 @@
use crate::{actor, audio, camera, chat, nature, settings, world};
use crate::{actor, audio, camera, chat, nature, var, world};
use bevy::prelude::*;
use bevy::diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin};
use bevy::transform::TransformSystem;
@ -151,7 +151,7 @@ impl Log {
fn setup(
mut commands: Commands,
settings: Res<settings::Settings>,
settings: Res<var::Settings>,
asset_server: Res<AssetServer>,
mut log: ResMut<Log>,
mut ambient_light: ResMut<AmbientLight>,
@ -414,7 +414,7 @@ fn update_hud(
q_choices: Query<&chat::Choice>,
mut query_chat: Query<&mut Text, (With<ChatText>, Without<GaugesText>)>,
query_all_actors: Query<&actor::Actor>,
settings: Res<settings::Settings>,
settings: Res<var::Settings>,
q_target: Query<(&IsClickable, Option<&Position>, Option<&LinearVelocity>), With<IsTargeted>>,
) {
// TODO only when hud is actually on
@ -563,7 +563,7 @@ fn update_hud(
fn handle_input(
keyboard_input: Res<ButtonInput<KeyCode>>,
mouse_input: Res<ButtonInput<MouseButton>>,
mut settings: ResMut<settings::Settings>,
mut settings: ResMut<var::Settings>,
mut q_hud: Query<(&mut Visibility, Option<&OnlyHideWhenTogglingHud>), With<ToggleableHudElement>>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_togglemusic: EventWriter<audio::ToggleMusicEvent>,
@ -607,7 +607,7 @@ fn handle_input(
fn handle_target_event(
mut commands: Commands,
settings: Res<settings::Settings>,
settings: Res<var::Settings>,
mut er_target: EventReader<TargetEvent>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
q_target: Query<Entity, With<IsTargeted>>,
@ -631,7 +631,7 @@ fn handle_target_event(
}
fn update_target_selectagon(
settings: Res<settings::Settings>,
settings: Res<var::Settings>,
mut q_selectagon: Query<(&mut Transform, &mut Visibility), (With<Selectagon>, Without<IsTargeted>, Without<Camera>)>,
q_target: Query<&Transform, (With<IsTargeted>, Without<Camera>, Without<Selectagon>)>,
q_camera: Query<&Transform, (With<Camera>, Without<IsTargeted>, Without<Selectagon>)>,
@ -671,7 +671,7 @@ fn update_target_selectagon(
fn update_ar_overlays (
q_owners: Query<(Entity, &Transform, &Visibility), (With<AugmentedRealityOverlayBroadcaster>, Without<AugmentedRealityOverlay>)>,
mut q_overlays: Query<(&mut Transform, &mut Visibility, &mut AugmentedRealityOverlay)>,
settings: ResMut<settings::Settings>,
settings: ResMut<var::Settings>,
mut state: ResMut<AugmentedRealityState>,
) {
let (need_activate, need_clean, need_update);

View File

@ -5,8 +5,8 @@ mod chat;
mod commands;
mod effects;
mod hud;
mod settings;
mod stars;
mod var;
mod world;
#[allow(dead_code)]
@ -47,7 +47,8 @@ impl Plugin for OutFlyPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, setup);
app.add_systems(Update, handle_input);
app.insert_resource(settings::Settings::default());
app.insert_resource(var::Settings::default());
app.insert_resource(var::GameVars::default());
app.add_plugins((
DefaultPlugins,//.set(ImagePlugin::default_nearest()),
FrameTimeDiagnosticsPlugin,
@ -77,7 +78,7 @@ fn setup(
fn handle_input(
keyboard_input: Res<ButtonInput<KeyCode>>,
settings: Res<settings::Settings>,
settings: Res<var::Settings>,
mut app_exit_events: ResMut<Events<bevy::app::AppExit>>,
mut windows: Query<&mut Window, With<PrimaryWindow>>,
) {

View File

@ -1,190 +0,0 @@
use bevy::prelude::*;
use std::env;
pub const DEFAULT_CHAT_SPEED: f32 = 10.0;
#[derive(Resource)]
pub struct Settings {
pub dev_mode: bool,
pub god_mode: bool,
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 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_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_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;
}
Settings {
dev_mode,
god_mode: false,
mute_sfx: default_mute_sfx,
mute_music: default_mute_music,
volume_sfx: 100,
volume_music: 100,
mouse_sensitivity: 0.7,
fov: 50.0,
fov_highspeed: 25.0,
zoom_fov: 15.0,
zoom_sensitivity_factor: 0.25,
font_size_hud: 32.0,
font_size_conversations: 32.0,
chat_speed: DEFAULT_CHAT_SPEED,
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_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_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,
];
}
}

385
src/var.rs Normal file
View File

@ -0,0 +1,385 @@
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 DEFAULT_CHAT_SPEED: f32 = 10.0;
#[derive(Resource)]
pub struct Settings {
pub dev_mode: bool,
pub god_mode: bool,
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 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_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_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;
}
Settings {
dev_mode,
god_mode: false,
mute_sfx: default_mute_sfx,
mute_music: default_mute_music,
volume_sfx: 100,
volume_music: 100,
mouse_sensitivity: 0.7,
fov: 50.0,
fov_highspeed: 25.0,
zoom_fov: 15.0,
zoom_sensitivity_factor: 0.25,
font_size_hud: 32.0,
font_size_conversations: 32.0,
chat_speed: DEFAULT_CHAT_SPEED,
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_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_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<String, String>,
}
impl Default for GameVars {
fn default() -> Self {
Self {
db: HashMap::new(),
}
}
}
impl GameVars {
#[allow(dead_code)]
pub fn get(&self, key: &str) -> Option<String> {
if let Some(value) = self.db.get(key) {
return Some(value.clone());
}
return None;
}
#[allow(dead_code)]
pub fn getf(&self, key: &str) -> Option<f64> {
if let Some(value) = self.db.get(key) {
if let Ok(float) = value.parse::<f64>() {
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 "<scope>$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];
if part.contains(SCOPE_SEPARATOR) {
let part = Self::normalize_varname(scope, part);
let value_bool = self.getb(part.as_str());
return value_bool;
}
else {
return Self::evaluate_str_as_bool(part);
}
} 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::<f64>(), right.parse::<f64>());
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;
}
}
}
}
}

View File

@ -1,4 +1,4 @@
use crate::{actor, audio, hud, nature, settings, stars};
use crate::{actor, audio, hud, nature, stars, var};
use bevy::prelude::*;
use bevy::render::render_resource::{AsBindGroup, ShaderRef};
use bevy::math::{DVec3, I64Vec3};
@ -385,7 +385,7 @@ fn handle_cheats(
mut q_player: Query<(&Transform, &mut Position, &mut LinearVelocity), With<actor::PlayerCamera>>,
mut q_life: Query<(&mut actor::LifeForm, &mut actor::ExperiencesGForce), With<actor::Player>>,
mut ew_playerdies: EventWriter<actor::PlayerDiesEvent>,
mut settings: ResMut<settings::Settings>,
mut settings: ResMut<var::Settings>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
) {
if q_player.is_empty() || q_life.is_empty() {