Compare commits

...

7 commits

9 changed files with 206 additions and 25 deletions

View file

@ -1,5 +1,7 @@
# v0.14.0-dev
- Implement fast travel (must be unlocked by getting phone number of FASTravel)
- Implement phone calls
- Chats don't automatically advance now, the player has to press "Continue"
- Add sparkles to Jupiter's ring ✨😍✨ best visible from Farview Station
- Add setting to change pointer

View file

@ -21,10 +21,13 @@ use std::collections::HashMap;
pub const CHATS: &[&str] = &[
include_str!("chats/fastravel.yaml"),
include_str!("chats/phone.yaml"),
include_str!("chats/serenity.yaml"),
include_str!("chats/thebe.yaml"),
];
pub const CONTACTS: &[&str] = &["icarus", "travel", "luna", "nox"];
pub const TEXT_CONTINUE: &str = "Continue...";
pub const TOKEN_CHAT: &str = "chat";
@ -890,11 +893,13 @@ pub fn handle_chat_scripts(
With<actor::Player>,
>,
mut q_playercam: Query<(&mut Position, &mut LinearVelocity), With<actor::PlayerCamera>>,
mut q_chats: Query<&mut Chat>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_effect: EventWriter<visual::SpawnEffectEvent>,
mut ew_achievement: EventWriter<game::AchievementEvent>,
id2pos: Res<game::Id2Pos>,
id2v: Res<game::Id2V>,
mut prefs: ResMut<Preferences>,
) {
for script in er_chatscript.read() {
// Parse the script string
@ -1002,6 +1007,33 @@ pub fn handle_chat_scripts(
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Drink));
ew_achievement.send(game::AchievementEvent::DrinkPizza);
}
"changename" => {
let mut new_name = param1.to_string();
if !param2.is_empty() {
if !new_name.is_empty() {
new_name += " ";
}
new_name += param2;
}
for mut chat in &mut q_chats {
if new_name.is_empty() {
chat.talker.name = None;
} else {
chat.talker.name = Some(new_name.clone());
}
}
}
"registercontact" => {
if CONTACTS.contains(&param1) {
let param1_string = param1.to_string();
if !prefs.contacts.contains(&param1_string) {
prefs.contacts.push(param1_string);
prefs.save();
}
} else {
error!("Can't register contact `{param1}', it doesn't exist in the chat::CONTACTS constant.");
}
}
_ => {
error!("Error, undefined chat script {name}");
}
@ -1012,6 +1044,7 @@ pub fn handle_chat_scripts(
pub fn update_chat_variables(
mut vars: ResMut<var::GameVars>,
settings: Res<var::Settings>,
prefs: Res<var::Preferences>,
q_player: Query<&actor::Suit, With<actor::Player>>,
) {
if let Ok(suit) = q_player.get_single() {
@ -1025,15 +1058,7 @@ pub fn update_chat_variables(
"player_suit_health_percent",
((suit.integrity * 100.0).round() as u8).to_string(),
);
vars.set_in_scope(
"$",
"ar",
if settings.hud_active {
String::from("1")
} else {
String::from("0")
},
);
vars.set_in_scope("$", "ar", bool2chatvar(settings.hud_active));
let wears_chefhat = if let Some(ava) = hud::PLAYER_AR_AVATARS.get(settings.ar_avatar) {
match ava.0 {
hud::Avatar::ChefHat => 1,
@ -1043,5 +1068,24 @@ pub fn update_chat_variables(
0
};
vars.set_in_scope("$", "chefhat", wears_chefhat.to_string());
// Set phone variables
let mut any = false;
for contact in CONTACTS {
let value = prefs.contacts.contains(&contact.to_string());
if value {
any = true;
}
vars.set_in_scope("phone", contact, bool2chatvar(value));
}
vars.set_in_scope("phone", "any", bool2chatvar(any));
}
}
fn bool2chatvar(var: bool) -> String {
if var {
String::from("1")
} else {
String::from("0")
}
}

View file

@ -96,6 +96,10 @@
- Can you please fill up my oxygen tank without taking me anywhere?:
- script: refilloxygen 1000
- Of course, you can count on FASTravel!
- I wish I could use your services from anywhere!:
- Actually, you can!
- script: registercontact travel
- Here's our phone number, call us any time!
- Is there anything interesting to do around here?:
- goto: interesting
- No, thank you.:

55
src/chats/phone.yaml Normal file
View file

@ -0,0 +1,55 @@
- chat: phone
- if ~phone$any:
- "Error: Phonebook empty."
- goto: EXIT
- Select contact to call.
- if: phone$travel
FASTravel:
- goto: travel
- "[Cancel]":
- goto: EXIT
- label: travel
- script: changename FASTravel
- Welcome to FASTravel™, how can I help you today?
- label: travel_mainnode
- Can you pick me up please?:
- Yes! Where should we drop you off?
- Serenity Station:
- Ok! Activate cryofreeze and we'll be right there.
- "[Activate Cryofreeze]":
- script: cryofadeout
- sleep: 5
- script: cryotrip serenity
- goto: EXIT
- I changed my mind.:
- Alright. Anything else I can help you with?
- goto: travel_mainnode
- Metis Prime Station:
- Ok! Activate cryofreeze and we'll be right there.
- "[Activate Cryofreeze]":
- script: cryofadeout
- sleep: 5
- script: cryotrip metisprime
- goto: EXIT
- I changed my mind.:
- Alright. Anything else I can help you with?
- goto: travel_mainnode
- Farview Station:
- Ok! Activate cryofreeze and we'll be right there.
- "[Activate Cryofreeze]":
- script: cryofadeout
- sleep: 5
- script: cryotrip farview
- goto: EXIT
- I changed my mind.:
- Alright. Anything else I can help you with?
- goto: travel_mainnode
- I just wanted to say that you're awesome!:
- Thank you so much!
- Hearing this makes my day!
- Satisfying our customers is what I'm programmed to live for.
- Good bye!:
- goto: EXIT
- "[Hang up]":
- goto: EXIT

View file

@ -18,6 +18,7 @@ use bevy_xpbd_3d::prelude::*;
use regex::Regex;
pub const ID_SPECIAL_PLAYERCAM: &str = "PLAYERCAMERA";
pub const ID_PLAYER: &str = "player";
pub const ID_EARTH: &str = "earth";
pub const ID_SOL: &str = "sol";
pub const ID_JUPITER: &str = "jupiter";
@ -901,9 +902,9 @@ fn spawn_entities(
let orbited_mass: Option<f64> = if let Some(id) = &state.orbit_object_id {
match id.as_str() {
"jupiter" => Some(nature::JUPITER_MASS),
"earth" => Some(nature::EARTH_MASS),
"sol" => Some(nature::SOL_MASS),
ID_JUPITER => Some(nature::JUPITER_MASS),
ID_EARTH => Some(nature::EARTH_MASS),
ID_SOL => Some(nature::SOL_MASS),
_ => {
error!("Found no mass for object `{id}`");
continue;

View file

@ -98,6 +98,7 @@ pub enum GameEvent {
SetShadows(Turn),
UpdateFlashlight,
Achievement(String),
PhoneCall,
}
pub enum Turn {
@ -162,6 +163,7 @@ pub fn setup(mut settings: ResMut<Settings>, prefs: ResMut<var::Preferences>) {
pub fn handle_game_event(
mut settings: ResMut<Settings>,
mut er_game: EventReader<GameEvent>,
mut ew_conv: EventWriter<chat::StartConversationEvent>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_updateoverlays: EventWriter<hud::UpdateOverlayVisibility>,
mut ew_updatemenu: EventWriter<menu::UpdateMenuEvent>,
@ -270,6 +272,19 @@ pub fn handle_game_event(
spotlight.intensity = actor::FLASHLIGHT_INTENSITY[prefs.flashlight_power];
}
}
GameEvent::PhoneCall => {
let talker = chat::Talker {
chat_name: "phone".to_string(),
actor_id: "".to_string(),
name: Some("Phone".to_string()),
counts_towards_achievement: false,
pronoun: None,
talking_speed: 0.0,
};
ew_conv.send(chat::StartConversationEvent {
talker: talker,
});
}
}
}
}
@ -486,7 +501,7 @@ fn update_id2pos(
id2pos.0.clear();
for (pos, id) in &q_id {
id2pos.0.insert(id.0.clone(), pos.0);
if id.0 == "jupiter" {
if id.0 == cmd::ID_JUPITER {
jupiterpos.0 = pos.0;
}
}
@ -594,12 +609,12 @@ fn check_achievements(
} else {
return;
};
let pos_sun = if let Some(pos) = id2pos.0.get("sol") {
let pos_sun = if let Some(pos) = id2pos.0.get(cmd::ID_SOL) {
pos
} else {
return;
};
let pos_jupiter = if let Some(pos) = id2pos.0.get("jupiter") {
let pos_jupiter = if let Some(pos) = id2pos.0.get(cmd::ID_JUPITER) {
pos
} else {
return;

View file

@ -49,6 +49,8 @@ pub struct MenuElement;
#[derive(Component)]
pub struct MenuTopLevel;
#[derive(Component)]
pub struct FooterElement;
#[derive(Component)]
pub struct MenuAchievements;
#[derive(Component)]
pub struct DeathScreenElement;
@ -63,6 +65,7 @@ pub enum DeathScreenEvent {
}
pub const MENUDEF: &[(&str, MenuAction)] = &[
("Phone Call", MenuAction::PhoneCall),
("", MenuAction::ToggleAR),
("", MenuAction::ChangeARAvatar),
("", MenuAction::ChangePointer),
@ -92,6 +95,7 @@ pub enum MenuAction {
ModReactor,
ToggleSound,
ToggleMusic,
PhoneCall,
ToggleCamera,
ToggleFullscreen,
ToggleShadows,
@ -347,7 +351,9 @@ pub fn setup(
},
))
.with_children(|builder| {
builder.spawn((TextBundle {
builder.spawn((
FooterElement,
TextBundle {
text: Text {
sections: vec![TextSection::new(
format!("{} {}", GAME_NAME, settings.version.as_str()),
@ -357,7 +363,8 @@ pub fn setup(
..default()
},
..default()
},));
},
));
});
}
@ -433,8 +440,17 @@ pub struct MenuState {
pub fn update_menu(
mut q_text: Query<&mut Text, With<MenuTopLevel>>,
mut q_achievement_text: Query<&mut Text, (With<MenuAchievements>, Without<MenuTopLevel>)>,
mut q_footer: Query<&mut Text, (With<FooterElement>, Without<MenuTopLevel>)>,
mut q_achievement_text: Query<
&mut Text,
(
With<MenuAchievements>,
Without<MenuTopLevel>,
Without<FooterElement>,
),
>,
mut q_vis: Query<&mut Visibility, With<menu::MenuElement>>,
id2pos: Res<game::Id2Pos>,
achievement_tracker: Res<var::AchievementTracker>,
menustate: Res<MenuState>,
settings: Res<Settings>,
@ -449,6 +465,21 @@ pub fn update_menu(
let bools = achievement_tracker.to_bool_vec();
let rendered = achievement_tracker.to_textsections();
if let (Ok(mut text), Some(player_pos), Some(jupiter_pos)) = (
q_footer.get_single_mut(),
id2pos.0.get(cmd::ID_PLAYER),
id2pos.0.get(cmd::ID_JUPITER),
) {
let (clock_max, clock_current) =
nature::pos_to_orbit_time(*player_pos, *jupiter_pos, nature::JUPITER_MASS);
text.sections[0].value = format!(
"Orbital Clock:\n{} of {}\n{} {}",
nature::format_seconds_to_hour_min(clock_current),
nature::format_seconds_to_hour_min(clock_max),
GAME_NAME,
settings.version.as_str()
);
}
if let Ok(mut text) = q_achievement_text.get_single_mut() {
for i in 0..text.sections.len() - 1 {
text.sections[i + 1].style.color = if bools[i] {
@ -713,6 +744,11 @@ pub fn handle_input(
ew_game.send(GameEvent::SetShadows(Toggle));
ew_updatemenu.send(UpdateMenuEvent);
}
MenuAction::PhoneCall => {
ew_game.send(GameEvent::PhoneCall);
ew_game.send(GameEvent::SetMenu(Turn::Off));
ew_updatemenu.send(UpdateMenuEvent);
}
MenuAction::Restart => {
settings.god_mode = false;
ew_playerdies.send(game::PlayerDiesEvent(actor::DamageType::Depressurization));

View file

@ -242,3 +242,26 @@ pub fn rotation_for_orbiting_body(orbit_distance: f64, mass: f64) -> f64 {
0.0
}
}
/// Assumes a circular 2D orbit in the invariable plane of the solar system
/// Returns (seconds for a single orbit, current seconds of orbit)
pub fn pos_to_orbit_time(pos: DVec3, orbited_pos: DVec3, orbited_mass: f64) -> (f64, f64) {
let rel_x = pos.x - orbited_pos.x;
let rel_z = pos.z - orbited_pos.z;
let orbit_distance = (rel_x * rel_x + rel_z * rel_z).sqrt();
// get total orbit seconds
let period = simple_orbital_period(orbited_mass, orbit_distance);
// get current orbital phase
let angle_radians = (rel_z).atan2(-rel_x);
let mut fraction = angle_radians / (PI * 2.0);
if fraction < 0.0 {
fraction += 1.0;
}
return (period, period * fraction);
}
pub fn format_seconds_to_hour_min(seconds: f64) -> String {
return format!("{:02.0}:{:02.0}", seconds / 3600.0, (seconds % 3600.0) / 60.0);
}

View file

@ -477,6 +477,7 @@ pub struct Preferences {
#[serde(default = "Preferences::default_flashlight_power")]
pub flashlight_power: usize, // 0-2
pub thruster_boost: usize, // 0-2
pub contacts: Vec<String>,
#[serde(skip)]
pub source_file: Option<String>,