Compare commits
7 commits
242e06ebf1
...
678979db7e
Author | SHA1 | Date | |
---|---|---|---|
yuni | 678979db7e | ||
yuni | 9ea706c48e | ||
yuni | 52d962a6bb | ||
yuni | c787e7caf4 | ||
yuni | b9708f7839 | ||
yuni | 758114e3a2 | ||
yuni | 55426ba0dd |
|
@ -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
|
||||
|
|
62
src/chat.rs
62
src/chat.rs
|
@ -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(¶m1) {
|
||||
let param1_string = param1.to_string();
|
||||
if !prefs.contacts.contains(¶m1_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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
55
src/chats/phone.yaml
Normal 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
|
|
@ -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;
|
||||
|
|
21
src/game.rs
21
src/game.rs
|
@ -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;
|
||||
|
|
56
src/menu.rs
56
src/menu.rs
|
@ -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,17 +351,20 @@ pub fn setup(
|
|||
},
|
||||
))
|
||||
.with_children(|builder| {
|
||||
builder.spawn((TextBundle {
|
||||
text: Text {
|
||||
sections: vec![TextSection::new(
|
||||
format!("{} {}", GAME_NAME, settings.version.as_str()),
|
||||
style_version,
|
||||
)],
|
||||
justify: JustifyText::Right,
|
||||
builder.spawn((
|
||||
FooterElement,
|
||||
TextBundle {
|
||||
text: Text {
|
||||
sections: vec![TextSection::new(
|
||||
format!("{} {}", GAME_NAME, settings.version.as_str()),
|
||||
style_version,
|
||||
)],
|
||||
justify: JustifyText::Right,
|
||||
..default()
|
||||
},
|
||||
..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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
|
|
Loading…
Reference in a new issue