apply cargo fmt
This commit is contained in:
parent
b0ac508d91
commit
87199f41db
237
src/actor.rs
237
src/actor.rs
|
@ -14,9 +14,9 @@
|
|||
//
|
||||
// This module should never handle any visual aspects directly.
|
||||
|
||||
use crate::prelude::*;
|
||||
use bevy::prelude::*;
|
||||
use bevy_xpbd_3d::prelude::*;
|
||||
use crate::prelude::*;
|
||||
|
||||
pub const ENGINE_SPEED_FACTOR: f32 = 30.0;
|
||||
const MAX_TRANSMISSION_DISTANCE: f32 = 100.0;
|
||||
|
@ -25,23 +25,30 @@ const MAX_INTERACT_DISTANCE: f32 = 50.0;
|
|||
pub struct ActorPlugin;
|
||||
impl Plugin for ActorPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(FixedUpdate, (
|
||||
update_physics_lifeforms,
|
||||
update_power,
|
||||
handle_wants_maxrotation,
|
||||
handle_wants_maxvelocity,
|
||||
));
|
||||
app.add_systems(PostUpdate, handle_gforce
|
||||
.after(PhysicsSet::Sync)
|
||||
.after(sync::position_to_transform));
|
||||
app.add_systems(Update, (
|
||||
handle_input.run_if(in_control),
|
||||
handle_collisions,
|
||||
handle_damage,
|
||||
));
|
||||
app.add_systems(PostUpdate, (
|
||||
handle_vehicle_enter_exit,
|
||||
));
|
||||
app.add_systems(
|
||||
FixedUpdate,
|
||||
(
|
||||
update_physics_lifeforms,
|
||||
update_power,
|
||||
handle_wants_maxrotation,
|
||||
handle_wants_maxvelocity,
|
||||
),
|
||||
);
|
||||
app.add_systems(
|
||||
PostUpdate,
|
||||
handle_gforce
|
||||
.after(PhysicsSet::Sync)
|
||||
.after(sync::position_to_transform),
|
||||
);
|
||||
app.add_systems(
|
||||
Update,
|
||||
(
|
||||
handle_input.run_if(in_control),
|
||||
handle_collisions,
|
||||
handle_damage,
|
||||
),
|
||||
);
|
||||
app.add_systems(PostUpdate, (handle_vehicle_enter_exit,));
|
||||
app.add_event::<VehicleEnterExitEvent>();
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +73,7 @@ pub struct VehicleEnterExitEvent {
|
|||
driver: Entity,
|
||||
name: Option<String>,
|
||||
is_entering: bool,
|
||||
is_player: bool
|
||||
is_player: bool,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
|
@ -112,26 +119,41 @@ pub struct ExperiencesGForce {
|
|||
pub last_linear_velocity: DVec3,
|
||||
pub ignore_gforce_seconds: f32,
|
||||
}
|
||||
impl Default for ExperiencesGForce { fn default() -> Self { Self {
|
||||
gforce: 0.0,
|
||||
damage_threshold: 100.0,
|
||||
visual_effect_threshold: 20.0,
|
||||
visual_effect: 0.0,
|
||||
last_linear_velocity: DVec3::splat(0.0),
|
||||
ignore_gforce_seconds: 0.0,
|
||||
}}}
|
||||
impl Default for ExperiencesGForce {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
gforce: 0.0,
|
||||
damage_threshold: 100.0,
|
||||
visual_effect_threshold: 20.0,
|
||||
visual_effect: 0.0,
|
||||
last_linear_velocity: DVec3::splat(0.0),
|
||||
ignore_gforce_seconds: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)] pub struct Player; // Attached to the suit of the player
|
||||
#[derive(Component)] pub struct PlayerCollider; // Attached to the collider of the suit of the player
|
||||
#[derive(Component)] pub struct PlayerDrivesThis; // Attached to the entered vehicle
|
||||
#[derive(Component)] pub struct PlayerCamera; // Attached to the actor to use as point of view
|
||||
#[derive(Component)] pub struct JustNowEnteredVehicle;
|
||||
#[derive(Component)] pub struct ActorEnteringVehicle;
|
||||
#[derive(Component)] pub struct ActorVehicleBeingEntered;
|
||||
#[derive(Component)] pub struct PlayersFlashLight;
|
||||
#[derive(Component)] pub struct WantsMaxRotation(pub f64);
|
||||
#[derive(Component)] pub struct WantsMaxVelocity(pub f64);
|
||||
#[derive(Component)] pub struct Identifier(pub String);
|
||||
#[derive(Component)]
|
||||
pub struct Player; // Attached to the suit of the player
|
||||
#[derive(Component)]
|
||||
pub struct PlayerCollider; // Attached to the collider of the suit of the player
|
||||
#[derive(Component)]
|
||||
pub struct PlayerDrivesThis; // Attached to the entered vehicle
|
||||
#[derive(Component)]
|
||||
pub struct PlayerCamera; // Attached to the actor to use as point of view
|
||||
#[derive(Component)]
|
||||
pub struct JustNowEnteredVehicle;
|
||||
#[derive(Component)]
|
||||
pub struct ActorEnteringVehicle;
|
||||
#[derive(Component)]
|
||||
pub struct ActorVehicleBeingEntered;
|
||||
#[derive(Component)]
|
||||
pub struct PlayersFlashLight;
|
||||
#[derive(Component)]
|
||||
pub struct WantsMaxRotation(pub f64);
|
||||
#[derive(Component)]
|
||||
pub struct WantsMaxVelocity(pub f64);
|
||||
#[derive(Component)]
|
||||
pub struct Identifier(pub String);
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct LifeForm {
|
||||
|
@ -140,20 +162,28 @@ pub struct LifeForm {
|
|||
pub adrenaline_baseline: f32,
|
||||
pub adrenaline_jolt: f32,
|
||||
}
|
||||
impl Default for LifeForm { fn default() -> Self { Self {
|
||||
is_alive: true,
|
||||
adrenaline: 0.3,
|
||||
adrenaline_baseline: 0.3,
|
||||
adrenaline_jolt: 0.0,
|
||||
}}}
|
||||
impl Default for LifeForm {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
is_alive: true,
|
||||
adrenaline: 0.3,
|
||||
adrenaline_baseline: 0.3,
|
||||
adrenaline_jolt: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Vehicle {
|
||||
stored_drivers_collider: Option<Collider>,
|
||||
}
|
||||
impl Default for Vehicle { fn default() -> Self { Self {
|
||||
stored_drivers_collider: None,
|
||||
}}}
|
||||
impl Default for Vehicle {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
stored_drivers_collider: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum EngineType {
|
||||
|
@ -192,7 +222,11 @@ pub struct Suit {
|
|||
pub oxygen_max: f32,
|
||||
pub integrity: f32, // [0.0 - 1.0]
|
||||
}
|
||||
impl Default for Suit { fn default() -> Self { SUIT_SIMPLE } }
|
||||
impl Default for Suit {
|
||||
fn default() -> Self {
|
||||
SUIT_SIMPLE
|
||||
}
|
||||
}
|
||||
|
||||
const SUIT_SIMPLE: Suit = Suit {
|
||||
oxygen: nature::OXY_D,
|
||||
|
@ -202,9 +236,9 @@ const SUIT_SIMPLE: Suit = Suit {
|
|||
|
||||
#[derive(Component)]
|
||||
pub struct Battery {
|
||||
pub power: f32, // Watt-seconds
|
||||
pub power: f32, // Watt-seconds
|
||||
pub capacity: f32, // Watt-seconds
|
||||
pub reactor: f32, // Watt (production)
|
||||
pub reactor: f32, // Watt (production)
|
||||
}
|
||||
|
||||
impl Default for Battery {
|
||||
|
@ -212,7 +246,7 @@ impl Default for Battery {
|
|||
Self {
|
||||
power: 10e3 * 3600.0,
|
||||
capacity: 10e3 * 3600.0, // 10kWh
|
||||
reactor: 2000e3, // 2MW
|
||||
reactor: 2000e3, // 2MW
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -236,8 +270,7 @@ pub fn update_power(
|
|||
}
|
||||
}
|
||||
}
|
||||
battery.power = (battery.power + battery.reactor * d)
|
||||
.clamp(0.0, battery.capacity);
|
||||
battery.power = (battery.power + battery.reactor * d).clamp(0.0, battery.capacity);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -249,15 +282,15 @@ pub fn update_physics_lifeforms(
|
|||
for (mut lifeform, mut hp, mut suit, velocity) in query.iter_mut() {
|
||||
if lifeform.adrenaline_jolt.abs() > 1e-3 {
|
||||
lifeform.adrenaline_jolt *= 0.99;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
lifeform.adrenaline_jolt = 0.0
|
||||
}
|
||||
let speed = velocity.length();
|
||||
if speed > 1000.0 {
|
||||
lifeform.adrenaline += 0.001;
|
||||
}
|
||||
lifeform.adrenaline = (lifeform.adrenaline - 0.0001 + lifeform.adrenaline_jolt * 0.01).clamp(0.0, 1.0);
|
||||
lifeform.adrenaline =
|
||||
(lifeform.adrenaline - 0.0001 + lifeform.adrenaline_jolt * 0.01).clamp(0.0, 1.0);
|
||||
|
||||
let mut oxygen_drain = nature::OXY_S;
|
||||
let integr_threshold = 0.5;
|
||||
|
@ -278,7 +311,7 @@ pub fn update_physics_lifeforms(
|
|||
let drain_scaling = (2.0 - 2.0 * suit.integrity / integr_threshold).powf(4.0);
|
||||
oxygen_drain += suit.oxygen * 0.01 * drain_scaling;
|
||||
}
|
||||
suit.oxygen = (suit.oxygen - oxygen_drain*d).clamp(0.0, suit.oxygen_max);
|
||||
suit.oxygen = (suit.oxygen - oxygen_drain * d).clamp(0.0, suit.oxygen_max);
|
||||
if suit.oxygen <= 0.0 {
|
||||
hp.damage += 1.0 * d;
|
||||
hp.damagetype = DamageType::Asphyxiation;
|
||||
|
@ -294,7 +327,14 @@ pub fn handle_input(
|
|||
player: Query<Entity, With<actor::Player>>,
|
||||
q_camera: Query<&Transform, With<Camera>>,
|
||||
mut q_flashlight: Query<&mut Visibility, With<PlayersFlashLight>>,
|
||||
q_vehicles: Query<(Entity, &Actor, &Transform), (With<actor::Vehicle>, Without<actor::Player>, Without<Camera>)>,
|
||||
q_vehicles: Query<
|
||||
(Entity, &Actor, &Transform),
|
||||
(
|
||||
With<actor::Vehicle>,
|
||||
Without<actor::Player>,
|
||||
Without<Camera>,
|
||||
),
|
||||
>,
|
||||
mut ew_conv: EventWriter<chat::StartConversationEvent>,
|
||||
mut ew_vehicle: EventWriter<VehicleEnterExitEvent>,
|
||||
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
|
||||
|
@ -313,9 +353,12 @@ pub fn handle_input(
|
|||
.map(|(talker, transform)| (talker.clone(), transform))
|
||||
.collect();
|
||||
// TODO: replace Transform.translation with Position
|
||||
if let (Some(talker), dist) = camera::find_closest_target::<chat::Talker>(objects, camtrans) {
|
||||
if let (Some(talker), dist) = camera::find_closest_target::<chat::Talker>(objects, camtrans)
|
||||
{
|
||||
if dist <= MAX_TRANSMISSION_DISTANCE {
|
||||
ew_conv.send(chat::StartConversationEvent{talker: talker.clone()});
|
||||
ew_conv.send(chat::StartConversationEvent {
|
||||
talker: talker.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
// Entering Vehicles
|
||||
|
@ -325,12 +368,12 @@ pub fn handle_input(
|
|||
.map(|(entity, actor, transform)| ((entity, actor), transform))
|
||||
.collect();
|
||||
if let (Some((entity, actor)), dist) =
|
||||
camera::find_closest_target::<(Entity, &Actor)>(objects, camtrans)
|
||||
camera::find_closest_target::<(Entity, &Actor)>(objects, camtrans)
|
||||
{
|
||||
if dist <= MAX_INTERACT_DISTANCE {
|
||||
commands.entity(entity).insert(ActorVehicleBeingEntered);
|
||||
commands.entity(player_entity).insert(ActorEnteringVehicle);
|
||||
ew_vehicle.send(VehicleEnterExitEvent{
|
||||
ew_vehicle.send(VehicleEnterExitEvent {
|
||||
vehicle: entity,
|
||||
driver: player_entity,
|
||||
name: actor.name.clone(),
|
||||
|
@ -340,13 +383,14 @@ pub fn handle_input(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if keyboard_input.just_pressed(settings.key_vehicle) {
|
||||
} else if keyboard_input.just_pressed(settings.key_vehicle) {
|
||||
// Exiting Vehicles
|
||||
for vehicle_entity in &q_player_drives {
|
||||
commands.entity(vehicle_entity).insert(ActorVehicleBeingEntered);
|
||||
commands
|
||||
.entity(vehicle_entity)
|
||||
.insert(ActorVehicleBeingEntered);
|
||||
commands.entity(player_entity).insert(ActorEnteringVehicle);
|
||||
ew_vehicle.send(VehicleEnterExitEvent{
|
||||
ew_vehicle.send(VehicleEnterExitEvent {
|
||||
vehicle: vehicle_entity,
|
||||
driver: player_entity,
|
||||
name: None,
|
||||
|
@ -355,8 +399,7 @@ pub fn handle_input(
|
|||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if keyboard_input.just_pressed(settings.key_flashlight) {
|
||||
} else if keyboard_input.just_pressed(settings.key_flashlight) {
|
||||
for mut flashlight_vis in &mut q_flashlight {
|
||||
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Switch));
|
||||
if *flashlight_vis == Visibility::Hidden {
|
||||
|
@ -367,8 +410,7 @@ pub fn handle_input(
|
|||
settings.flashlight_active = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if keyboard_input.just_pressed(settings.key_cruise_control) {
|
||||
} else if keyboard_input.just_pressed(settings.key_cruise_control) {
|
||||
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Switch));
|
||||
settings.cruise_control_active ^= true;
|
||||
}
|
||||
|
@ -379,9 +421,28 @@ pub fn handle_vehicle_enter_exit(
|
|||
mut settings: ResMut<Settings>,
|
||||
mut er_vehicle: EventReader<VehicleEnterExitEvent>,
|
||||
mut ew_achievement: EventWriter<game::AchievementEvent>,
|
||||
mut q_playerflashlight: Query<&mut Visibility, (With<PlayersFlashLight>, Without<ActorVehicleBeingEntered>, Without<ActorEnteringVehicle>)>,
|
||||
mut q_drivers: Query<(Entity, &mut Visibility, Option<&Collider>), (Without<ActorVehicleBeingEntered>, With<ActorEnteringVehicle>)>,
|
||||
mut q_vehicles: Query<(Entity, &mut Vehicle, &mut Visibility), (With<ActorVehicleBeingEntered>, Without<ActorEnteringVehicle>)>,
|
||||
mut q_playerflashlight: Query<
|
||||
&mut Visibility,
|
||||
(
|
||||
With<PlayersFlashLight>,
|
||||
Without<ActorVehicleBeingEntered>,
|
||||
Without<ActorEnteringVehicle>,
|
||||
),
|
||||
>,
|
||||
mut q_drivers: Query<
|
||||
(Entity, &mut Visibility, Option<&Collider>),
|
||||
(
|
||||
Without<ActorVehicleBeingEntered>,
|
||||
With<ActorEnteringVehicle>,
|
||||
),
|
||||
>,
|
||||
mut q_vehicles: Query<
|
||||
(Entity, &mut Vehicle, &mut Visibility),
|
||||
(
|
||||
With<ActorVehicleBeingEntered>,
|
||||
Without<ActorEnteringVehicle>,
|
||||
),
|
||||
>,
|
||||
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
|
||||
) {
|
||||
for event in er_vehicle.read() {
|
||||
|
@ -410,11 +471,11 @@ pub fn handle_vehicle_enter_exit(
|
|||
settings.flashlight_active = false;
|
||||
}
|
||||
if let Some(vehicle_name) = &event.name {
|
||||
ew_achievement.send(game::AchievementEvent
|
||||
::RideVehicle(vehicle_name.clone()));
|
||||
ew_achievement.send(game::AchievementEvent::RideVehicle(
|
||||
vehicle_name.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Exiting Vehicle
|
||||
if let Some(collider) = &vehicle_component.stored_drivers_collider {
|
||||
commands.entity(driver).insert(collider.clone());
|
||||
|
@ -439,7 +500,9 @@ fn handle_collisions(
|
|||
q_player: Query<Entity, With<PlayerCollider>>,
|
||||
mut q_player_lifeform: Query<(&mut LifeForm, &mut Suit), With<Player>>,
|
||||
) {
|
||||
if let (Ok(player), Ok((mut lifeform, mut suit))) = (q_player.get_single(), q_player_lifeform.get_single_mut()) {
|
||||
if let (Ok(player), Ok((mut lifeform, mut suit))) =
|
||||
(q_player.get_single(), q_player_lifeform.get_single_mut())
|
||||
{
|
||||
for CollisionStarted(entity1, entity2) in collision_event_reader.read() {
|
||||
if *entity1 == player || *entity2 == player {
|
||||
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Crash));
|
||||
|
@ -461,9 +524,9 @@ fn handle_wants_maxrotation(
|
|||
if total > maxrot.0 {
|
||||
v_ang.0 = DVec3::splat(0.0);
|
||||
}
|
||||
}
|
||||
else {
|
||||
let angular_slowdown: f64 = (2.0 - engine.reaction_wheels.powf(0.01).clamp(1.001, 1.1)) as f64;
|
||||
} else {
|
||||
let angular_slowdown: f64 =
|
||||
(2.0 - engine.reaction_wheels.powf(0.01).clamp(1.001, 1.1)) as f64;
|
||||
v_ang.0 *= angular_slowdown;
|
||||
}
|
||||
}
|
||||
|
@ -485,7 +548,8 @@ fn handle_wants_maxvelocity(
|
|||
}
|
||||
|
||||
// TODO: respect engine parameters for different thrusts for different directions
|
||||
let avg_thrust = (engine.thrust_forward + engine.thrust_back + engine.thrust_sideways) / 3.0;
|
||||
let avg_thrust =
|
||||
(engine.thrust_forward + engine.thrust_back + engine.thrust_sideways) / 3.0;
|
||||
let acceleration = (avg_thrust * dt) as f64 * -v.0;
|
||||
v.0 += acceleration;
|
||||
if v.0.length() + EPSILON < acceleration.length() {
|
||||
|
@ -507,8 +571,7 @@ fn handle_damage(
|
|||
if hp.current <= 0.0 {
|
||||
ew_playerdies.send(game::PlayerDiesEvent(hp.damagetype));
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
hp.current -= hp.damage;
|
||||
}
|
||||
hp.damage = 0.0;
|
||||
|
@ -535,12 +598,12 @@ fn handle_gforce(
|
|||
|
||||
if gforce.visual_effect > 0.0001 {
|
||||
gforce.visual_effect *= 0.984;
|
||||
}
|
||||
else if gforce.visual_effect > 0.0 {
|
||||
} else if gforce.visual_effect > 0.0 {
|
||||
gforce.visual_effect = 0.0;
|
||||
}
|
||||
if gforce.gforce > gforce.visual_effect_threshold {
|
||||
gforce.visual_effect += (gforce.gforce - gforce.visual_effect_threshold).powf(2.0) / 300000.0
|
||||
gforce.visual_effect +=
|
||||
(gforce.gforce - gforce.visual_effect_threshold).powf(2.0) / 300000.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
77
src/audio.rs
77
src/audio.rs
|
@ -10,16 +10,19 @@
|
|||
//
|
||||
// This module manages sound effects and music.
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy::audio::{PlaybackMode, Volume};
|
||||
use crate::prelude::*;
|
||||
use bevy::audio::{PlaybackMode, Volume};
|
||||
use bevy::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct AudioPlugin;
|
||||
impl Plugin for AudioPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(Startup, setup);
|
||||
app.add_systems(Update, respawn_sinks.run_if(on_event::<RespawnSinksEvent>()));
|
||||
app.add_systems(
|
||||
Update,
|
||||
respawn_sinks.run_if(on_event::<RespawnSinksEvent>()),
|
||||
);
|
||||
app.add_systems(Update, play_zoom_sfx);
|
||||
app.add_systems(Update, pause_all.run_if(on_event::<PauseAllSfxEvent>()));
|
||||
app.add_systems(PostUpdate, play_sfx);
|
||||
|
@ -28,27 +31,47 @@ impl Plugin for AudioPlugin {
|
|||
app.add_event::<PauseAllSfxEvent>();
|
||||
app.add_event::<ToggleMusicEvent>();
|
||||
app.add_event::<RespawnSinksEvent>();
|
||||
app.insert_resource(ZoomTimer(
|
||||
Timer::from_seconds(0.09, TimerMode::Repeating)));
|
||||
app.insert_resource(ZoomTimer(Timer::from_seconds(0.09, TimerMode::Repeating)));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource)] pub struct ZoomTimer(Timer);
|
||||
#[derive(Resource)]
|
||||
pub struct ZoomTimer(Timer);
|
||||
|
||||
const PATHS: &[(SfxType, Sfx, &str)] = &[
|
||||
(SfxType::BGM, Sfx::BGM, "music/Aleksey Chistilin - Cinematic Cello.ogg"),
|
||||
(SfxType::LoopSfx, Sfx::ElectricMotor, "sounds/electricmotor.ogg"),
|
||||
(
|
||||
SfxType::BGM,
|
||||
Sfx::BGM,
|
||||
"music/Aleksey Chistilin - Cinematic Cello.ogg",
|
||||
),
|
||||
(
|
||||
SfxType::LoopSfx,
|
||||
Sfx::ElectricMotor,
|
||||
"sounds/electricmotor.ogg",
|
||||
),
|
||||
(SfxType::LoopSfx, Sfx::Ion, "sounds/ion.ogg"),
|
||||
(SfxType::LoopSfx, Sfx::Rocket, "sounds/rocket.ogg"),
|
||||
(SfxType::LoopSfx, Sfx::Thruster, "sounds/thruster.ogg"),
|
||||
(SfxType::OneOff, Sfx::Achieve, "sounds/achieve.ogg"),
|
||||
(SfxType::OneOff, Sfx::Click, "sounds/click-button-140881-crop.ogg"),
|
||||
(
|
||||
SfxType::OneOff,
|
||||
Sfx::Click,
|
||||
"sounds/click-button-140881-crop.ogg",
|
||||
),
|
||||
(SfxType::OneOff, Sfx::Connect, "sounds/connect.ogg"),
|
||||
(SfxType::OneOff, Sfx::Crash, "sounds/crash.ogg"),
|
||||
(SfxType::OneOff, Sfx::EnterVehicle, "sounds/bikestart.ogg"),
|
||||
(SfxType::OneOff, Sfx::IncomingChatMessage, "sounds/connect.ogg"),
|
||||
(
|
||||
SfxType::OneOff,
|
||||
Sfx::IncomingChatMessage,
|
||||
"sounds/connect.ogg",
|
||||
),
|
||||
(SfxType::OneOff, Sfx::Ping, "sounds/connect.ogg"),
|
||||
(SfxType::OneOff, Sfx::Switch, "sounds/typosonic-typing-192811-crop.ogg"),
|
||||
(
|
||||
SfxType::OneOff,
|
||||
Sfx::Switch,
|
||||
"sounds/typosonic-typing-192811-crop.ogg",
|
||||
),
|
||||
(SfxType::OneOff, Sfx::WakeUp, "sounds/wakeup.ogg"),
|
||||
(SfxType::OneOff, Sfx::Woosh, "sounds/woosh.ogg"),
|
||||
(SfxType::OneOff, Sfx::Zoom, "sounds/zoom.ogg"),
|
||||
|
@ -96,11 +119,16 @@ pub enum SfxType {
|
|||
OneOff,
|
||||
}
|
||||
|
||||
#[derive(Event)] pub struct PlaySfxEvent(pub Sfx);
|
||||
#[derive(Event)] pub struct PauseAllSfxEvent;
|
||||
#[derive(Event)] pub struct RespawnSinksEvent;
|
||||
#[derive(Event)] pub struct ToggleMusicEvent();
|
||||
#[derive(Resource)] pub struct Sounds(HashMap<Sfx, Handle<AudioSource>>);
|
||||
#[derive(Event)]
|
||||
pub struct PlaySfxEvent(pub Sfx);
|
||||
#[derive(Event)]
|
||||
pub struct PauseAllSfxEvent;
|
||||
#[derive(Event)]
|
||||
pub struct RespawnSinksEvent;
|
||||
#[derive(Event)]
|
||||
pub struct ToggleMusicEvent();
|
||||
#[derive(Resource)]
|
||||
pub struct Sounds(HashMap<Sfx, Handle<AudioSource>>);
|
||||
|
||||
pub fn setup(
|
||||
mut commands: Commands,
|
||||
|
@ -140,7 +168,7 @@ pub fn respawn_sinks(
|
|||
},
|
||||
},
|
||||
));
|
||||
},
|
||||
}
|
||||
SfxType::LoopSfx => {
|
||||
commands.spawn((
|
||||
*sfx,
|
||||
|
@ -154,8 +182,8 @@ pub fn respawn_sinks(
|
|||
},
|
||||
},
|
||||
));
|
||||
},
|
||||
SfxType::OneOff => ()
|
||||
}
|
||||
SfxType::OneOff => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -192,8 +220,7 @@ pub fn update_music(
|
|||
}
|
||||
if settings.mute_music {
|
||||
bgm_sink.pause();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
bgm_sink.play();
|
||||
}
|
||||
}
|
||||
|
@ -211,7 +238,9 @@ pub fn play_zoom_sfx(
|
|||
if !timer.0.tick(time.delta()).just_finished() {
|
||||
return;
|
||||
}
|
||||
if settings.map_active && (*last_zoom_level - mapcam.target_zoom_level).abs() > mapcam.target_zoom_level * 0.2 {
|
||||
if settings.map_active
|
||||
&& (*last_zoom_level - mapcam.target_zoom_level).abs() > mapcam.target_zoom_level * 0.2
|
||||
{
|
||||
if *last_zoom_level != 0.0 && mapcam.target_zoom_level != camera::INITIAL_ZOOM_LEVEL {
|
||||
ew_sfx.send(PlaySfxEvent(Sfx::Zoom));
|
||||
}
|
||||
|
@ -219,9 +248,7 @@ pub fn play_zoom_sfx(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn pause_all(
|
||||
q_audiosinks: Query<&AudioSink, With<Sfx>>,
|
||||
) {
|
||||
pub fn pause_all(q_audiosinks: Query<&AudioSink, With<Sfx>>) {
|
||||
for sink in &q_audiosinks {
|
||||
sink.pause();
|
||||
}
|
||||
|
|
269
src/camera.rs
269
src/camera.rs
|
@ -12,16 +12,16 @@
|
|||
// movement-related keyboard input, and provides some camera-
|
||||
// related computation functions.
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy::input::mouse::{MouseMotion, MouseWheel};
|
||||
use bevy::window::PrimaryWindow;
|
||||
use crate::prelude::*;
|
||||
use bevy::core_pipeline::bloom::{BloomCompositeMode, BloomSettings};
|
||||
use bevy::core_pipeline::tonemapping::Tonemapping;
|
||||
use bevy::input::mouse::{MouseMotion, MouseWheel};
|
||||
use bevy::pbr::{CascadeShadowConfigBuilder, DirectionalLightShadowMap};
|
||||
use bevy::prelude::*;
|
||||
use bevy::transform::TransformSystem;
|
||||
use bevy_xpbd_3d::prelude::*;
|
||||
use bevy::window::PrimaryWindow;
|
||||
use bevy_xpbd_3d::plugins::sync;
|
||||
use crate::prelude::*;
|
||||
use bevy_xpbd_3d::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub const INITIAL_ZOOM_LEVEL: f64 = 10.0;
|
||||
|
@ -34,13 +34,19 @@ impl Plugin for CameraPlugin {
|
|||
app.add_systems(Update, handle_input.run_if(in_control));
|
||||
app.add_systems(Update, update_map_only_object_visibility.run_if(alive));
|
||||
app.add_systems(Update, manage_player_actor.after(handle_input));
|
||||
app.add_systems(PostUpdate, sync_camera_to_player
|
||||
.after(PhysicsSet::Sync)
|
||||
.after(apply_input_to_player)
|
||||
.before(TransformSystem::TransformPropagate));
|
||||
app.add_systems(PostUpdate, update_mapcam_center
|
||||
.before(sync::position_to_transform)
|
||||
.in_set(sync::SyncSet::PositionToTransform));
|
||||
app.add_systems(
|
||||
PostUpdate,
|
||||
sync_camera_to_player
|
||||
.after(PhysicsSet::Sync)
|
||||
.after(apply_input_to_player)
|
||||
.before(TransformSystem::TransformPropagate),
|
||||
);
|
||||
app.add_systems(
|
||||
PostUpdate,
|
||||
update_mapcam_center
|
||||
.before(sync::position_to_transform)
|
||||
.in_set(sync::SyncSet::PositionToTransform),
|
||||
);
|
||||
app.add_systems(Update, update_map_camera.run_if(in_control));
|
||||
app.add_systems(Update, update_fov.run_if(alive));
|
||||
app.add_systems(PreUpdate, apply_input_to_player);
|
||||
|
@ -53,9 +59,12 @@ impl Plugin for CameraPlugin {
|
|||
transform_to_position: false,
|
||||
});
|
||||
// 2. Add own position->transform sync function
|
||||
app.add_systems(PostUpdate, position_to_transform
|
||||
.after(sync::position_to_transform)
|
||||
.in_set(sync::SyncSet::PositionToTransform));
|
||||
app.add_systems(
|
||||
PostUpdate,
|
||||
position_to_transform
|
||||
.after(sync::position_to_transform)
|
||||
.in_set(sync::SyncSet::PositionToTransform),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,10 +102,7 @@ impl Default for MapCam {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn setup_camera(
|
||||
mut commands: Commands,
|
||||
settings: Res<var::Settings>,
|
||||
) {
|
||||
pub fn setup_camera(mut commands: Commands, settings: Res<var::Settings>) {
|
||||
// Add player
|
||||
commands.spawn((
|
||||
Camera3dBundle {
|
||||
|
@ -122,13 +128,14 @@ pub fn setup_camera(
|
|||
shadows_enabled: settings.shadows_sun,
|
||||
..default()
|
||||
},
|
||||
transform: Transform::from_rotation(Quat::from_rotation_y(PI32/2.0)),
|
||||
transform: Transform::from_rotation(Quat::from_rotation_y(PI32 / 2.0)),
|
||||
cascade_shadow_config: CascadeShadowConfigBuilder {
|
||||
num_cascades: 4,
|
||||
minimum_distance: 0.1,
|
||||
maximum_distance: 5000.0,
|
||||
..default()
|
||||
}.into(),
|
||||
}
|
||||
.into(),
|
||||
..default()
|
||||
});
|
||||
|
||||
|
@ -153,10 +160,10 @@ pub fn sync_camera_to_player(
|
|||
|
||||
// Translation
|
||||
if settings.third_person {
|
||||
camera_transform.translation = player_transform.translation + rotation * (actor.camdistance * Vec3::new(0.0, 0.2, 1.0));
|
||||
camera_transform.translation = player_transform.translation
|
||||
+ rotation * (actor.camdistance * Vec3::new(0.0, 0.2, 1.0));
|
||||
camera_transform.rotation = rotation * Quat::from_euler(EulerRot::XYZ, -0.02, 0.0, 0.0);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
camera_transform.translation = player_transform.translation;
|
||||
camera_transform.rotation = rotation;
|
||||
}
|
||||
|
@ -167,7 +174,14 @@ pub fn update_map_camera(
|
|||
mut mapcam: ResMut<MapCam>,
|
||||
mut q_camera: Query<&mut Transform, (With<Camera>, Without<actor::PlayerCamera>)>,
|
||||
q_playercam: Query<(Entity, &Transform), (With<actor::PlayerCamera>, Without<Camera>)>,
|
||||
q_target: Query<(Entity, &Transform), (With<hud::IsTargeted>, Without<Camera>, Without<actor::PlayerCamera>)>,
|
||||
q_target: Query<
|
||||
(Entity, &Transform),
|
||||
(
|
||||
With<hud::IsTargeted>,
|
||||
Without<Camera>,
|
||||
Without<actor::PlayerCamera>,
|
||||
),
|
||||
>,
|
||||
q_target_changed: Query<(), Changed<hud::IsTargeted>>,
|
||||
mut mouse_events: EventReader<MouseMotion>,
|
||||
mut er_mousewheel: EventReader<MouseWheel>,
|
||||
|
@ -195,7 +209,9 @@ pub fn update_map_camera(
|
|||
// at the extreme values and the orientation will flicker back/forth.
|
||||
let min_zoom: f64 = target_trans.scale.x as f64 * 2.0;
|
||||
let max_zoom: f64 = 17e18; // at this point, camera starts glitching
|
||||
mapcam.pitch = (mapcam.pitch + mouse_delta.y as f64 / 180.0 * settings.mouse_sensitivity as f64).clamp(-PI / 2.0 + EPSILON, PI / 2.0 - EPSILON);
|
||||
mapcam.pitch = (mapcam.pitch
|
||||
+ mouse_delta.y as f64 / 180.0 * settings.mouse_sensitivity as f64)
|
||||
.clamp(-PI / 2.0 + EPSILON, PI / 2.0 - EPSILON);
|
||||
mapcam.yaw += mouse_delta.x as f64 / 180.0 * settings.mouse_sensitivity as f64;
|
||||
|
||||
// Reset movement offset if target changes
|
||||
|
@ -226,7 +242,11 @@ pub fn update_map_camera(
|
|||
|
||||
// Update zoom level
|
||||
if !mapcam.initialized {
|
||||
let factor: f64 = if target_trans == player_trans { 7.0 } else { 1.0 };
|
||||
let factor: f64 = if target_trans == player_trans {
|
||||
7.0
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
mapcam.target_zoom_level *= target_trans.scale.x as f64 * factor;
|
||||
mapcam.zoom_level *= target_trans.scale.x as f64 * factor;
|
||||
mapcam.initialized = true;
|
||||
|
@ -241,12 +261,16 @@ pub fn update_map_camera(
|
|||
for wheel_event in er_mousewheel.read() {
|
||||
change_zoom -= wheel_event.y as f64 * 3.0;
|
||||
}
|
||||
mapcam.target_zoom_level = (mapcam.target_zoom_level * 1.1f64.powf(change_zoom)).clamp(min_zoom, max_zoom);
|
||||
mapcam.target_zoom_level =
|
||||
(mapcam.target_zoom_level * 1.1f64.powf(change_zoom)).clamp(min_zoom, max_zoom);
|
||||
let zoom_speed = 0.05; // should be between 0.0001 (slow) and 1.0 (instant)
|
||||
mapcam.zoom_level = (zoom_speed * mapcam.target_zoom_level + (1.0 - zoom_speed) * mapcam.zoom_level).clamp(min_zoom, max_zoom);
|
||||
mapcam.zoom_level = (zoom_speed * mapcam.target_zoom_level
|
||||
+ (1.0 - zoom_speed) * mapcam.zoom_level)
|
||||
.clamp(min_zoom, max_zoom);
|
||||
|
||||
// Update point of view
|
||||
let pov_rotation = DQuat::from_euler(EulerRot::XYZ, 0.0, mapcam.yaw as f64, mapcam.pitch as f64);
|
||||
let pov_rotation =
|
||||
DQuat::from_euler(EulerRot::XYZ, 0.0, mapcam.yaw as f64, mapcam.pitch as f64);
|
||||
let point_of_view = pov_rotation * (mapcam.zoom_level as f64 * DVec3::new(1.0, 0.0, 0.0));
|
||||
|
||||
// Update movement offset
|
||||
|
@ -285,8 +309,7 @@ pub fn update_fov(
|
|||
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())
|
||||
{
|
||||
if let (Ok(gforce), Ok(mut projection)) = (q_player.get_single(), q_camera.get_single_mut()) {
|
||||
let fov: f32;
|
||||
if mouse_input.pressed(settings.key_zoom) {
|
||||
fov = settings.zoom_fov.to_radians();
|
||||
|
@ -294,12 +317,16 @@ pub fn update_fov(
|
|||
settings.is_zooming = true;
|
||||
}
|
||||
} else {
|
||||
fov = (gforce.visual_effect.clamp(0.0, 1.0) * settings.fov_highspeed + settings.fov).to_radians();
|
||||
fov = (gforce.visual_effect.clamp(0.0, 1.0) * settings.fov_highspeed + settings.fov)
|
||||
.to_radians();
|
||||
if settings.is_zooming {
|
||||
settings.is_zooming = false;
|
||||
}
|
||||
};
|
||||
*projection = Projection::Perspective(PerspectiveProjection { fov: fov, ..default() });
|
||||
*projection = Projection::Perspective(PerspectiveProjection {
|
||||
fov: fov,
|
||||
..default()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -325,18 +352,40 @@ fn manage_player_actor(
|
|||
mut commands: Commands,
|
||||
settings: Res<var::Settings>,
|
||||
mut q_playercam: Query<&mut Visibility, With<actor::PlayerCamera>>,
|
||||
mut q_hiddenplayer: Query<(Entity, &mut Visibility, &mut Position, &mut Rotation, &mut LinearVelocity, &mut AngularVelocity, Option<&mut actor::ExperiencesGForce>, Option<&actor::JustNowEnteredVehicle>), (With<actor::Player>, Without<actor::PlayerCamera>)>,
|
||||
q_ride: Query<(&Transform, &Position, &Rotation, &LinearVelocity, &AngularVelocity), (With<actor::PlayerDrivesThis>, Without<actor::Player>)>,
|
||||
mut q_hiddenplayer: Query<
|
||||
(
|
||||
Entity,
|
||||
&mut Visibility,
|
||||
&mut Position,
|
||||
&mut Rotation,
|
||||
&mut LinearVelocity,
|
||||
&mut AngularVelocity,
|
||||
Option<&mut actor::ExperiencesGForce>,
|
||||
Option<&actor::JustNowEnteredVehicle>,
|
||||
),
|
||||
(With<actor::Player>, Without<actor::PlayerCamera>),
|
||||
>,
|
||||
q_ride: Query<
|
||||
(
|
||||
&Transform,
|
||||
&Position,
|
||||
&Rotation,
|
||||
&LinearVelocity,
|
||||
&AngularVelocity,
|
||||
),
|
||||
(With<actor::PlayerDrivesThis>, Without<actor::Player>),
|
||||
>,
|
||||
) {
|
||||
for mut vis in &mut q_playercam {
|
||||
if settings.third_person || settings.map_active {
|
||||
*vis = Visibility::Inherited;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
*vis = Visibility::Hidden;
|
||||
}
|
||||
}
|
||||
for (entity, mut vis, mut pos, mut rot, mut v, mut angv, mut gforce, entering) in &mut q_hiddenplayer {
|
||||
for (entity, mut vis, mut pos, mut rot, mut v, mut angv, mut gforce, entering) in
|
||||
&mut q_hiddenplayer
|
||||
{
|
||||
// If we are riding a vehicle, place the player at the position where
|
||||
// it would be after exiting the vehicle.
|
||||
// I would rather place it in the center of the vehicle, but at the time
|
||||
|
@ -344,7 +393,8 @@ fn manage_player_actor(
|
|||
// exiting the vehicle, so I'm doing it here instead, as a workaround.
|
||||
*vis = Visibility::Hidden;
|
||||
if let Ok((ride_trans, ride_pos, ride_rot, ride_v, ride_angv)) = q_ride.get_single() {
|
||||
pos.0 = ride_pos.0 + DVec3::from(ride_trans.rotation * Vec3::new(0.0, 0.0, ride_trans.scale.z * 2.0));
|
||||
pos.0 = ride_pos.0
|
||||
+ DVec3::from(ride_trans.rotation * Vec3::new(0.0, 0.0, ride_trans.scale.z * 2.0));
|
||||
rot.0 = ride_rot.0 * DQuat::from_array([-1.0, 0.0, 0.0, 0.0]);
|
||||
*v = ride_v.clone();
|
||||
*angv = ride_angv.clone();
|
||||
|
@ -353,7 +403,9 @@ fn manage_player_actor(
|
|||
// vehicles at high relative speed, even though they probably should.
|
||||
if let (Some(gforce), Some(_)) = (&mut gforce, entering) {
|
||||
gforce.last_linear_velocity = v.0;
|
||||
commands.entity(entity).remove::<actor::JustNowEnteredVehicle>();
|
||||
commands
|
||||
.entity(entity)
|
||||
.remove::<actor::JustNowEnteredVehicle>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -368,14 +420,17 @@ pub fn apply_input_to_player(
|
|||
key_input: Res<ButtonInput<KeyCode>>,
|
||||
q_audiosinks: Query<(&audio::Sfx, &AudioSink)>,
|
||||
q_target: Query<&LinearVelocity, (With<hud::IsTargeted>, Without<actor::PlayerCamera>)>,
|
||||
mut q_playercam: Query<(
|
||||
mut q_playercam: Query<
|
||||
(
|
||||
&Transform,
|
||||
&mut actor::Engine,
|
||||
&mut AngularVelocity,
|
||||
&mut LinearVelocity,
|
||||
&mut ExternalTorque,
|
||||
Option<&actor::PlayerDrivesThis>,
|
||||
), (With<actor::PlayerCamera>, Without<Camera>)>,
|
||||
),
|
||||
(With<actor::PlayerCamera>, Without<Camera>),
|
||||
>,
|
||||
) {
|
||||
if settings.map_active || !settings.in_control() {
|
||||
return;
|
||||
|
@ -390,8 +445,7 @@ pub fn apply_input_to_player(
|
|||
focused = window.focused;
|
||||
win_res_x = window.resolution.width();
|
||||
win_res_y = window.resolution.height();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
win_res_x = 1920.0;
|
||||
win_res_y = 1050.0;
|
||||
}
|
||||
|
@ -402,7 +456,9 @@ pub fn apply_input_to_player(
|
|||
DVec3::splat(0.0)
|
||||
};
|
||||
|
||||
if let Ok((player_transform, mut engine, mut angularvelocity, mut v, mut torque, bike)) = q_playercam.get_single_mut() {
|
||||
if let Ok((player_transform, mut engine, mut angularvelocity, mut v, mut torque, bike)) =
|
||||
q_playercam.get_single_mut()
|
||||
{
|
||||
// Handle key input
|
||||
if focused {
|
||||
if key_input.pressed(settings.key_forward) || settings.cruise_control_active {
|
||||
|
@ -426,7 +482,10 @@ pub fn apply_input_to_player(
|
|||
if key_input.pressed(settings.key_stop) {
|
||||
let stop_direction = (target_v - v.0).normalize();
|
||||
if stop_direction.length_squared() > 0.3 {
|
||||
axis_input += 1.0 * DVec3::from(player_transform.rotation.inverse() * stop_direction.as_vec3());
|
||||
axis_input += 1.0
|
||||
* DVec3::from(
|
||||
player_transform.rotation.inverse() * stop_direction.as_vec3(),
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -442,18 +501,21 @@ pub fn apply_input_to_player(
|
|||
axis_input = axis_input.clamp(DVec3::splat(-1.0), DVec3::splat(1.0));
|
||||
|
||||
// Apply movement update
|
||||
let forward_factor = engine.current_warmup * (if axis_input.z > 0.0 {
|
||||
engine.thrust_forward
|
||||
} else {
|
||||
engine.thrust_back
|
||||
});
|
||||
let forward_factor = engine.current_warmup
|
||||
* (if axis_input.z > 0.0 {
|
||||
engine.thrust_forward
|
||||
} else {
|
||||
engine.thrust_back
|
||||
});
|
||||
let right_factor = engine.thrust_sideways * engine.current_warmup;
|
||||
let up_factor = engine.thrust_sideways * engine.current_warmup;
|
||||
let factor = DVec3::new(right_factor as f64, up_factor as f64, forward_factor as f64);
|
||||
|
||||
if axis_input.length_squared() > 0.003 {
|
||||
let acceleration_global: DVec3 = DVec3::from(player_transform.rotation * (axis_input * factor).as_vec3());
|
||||
let mut acceleration_total: DVec3 = (actor::ENGINE_SPEED_FACTOR * dt) as f64 * acceleration_global;
|
||||
let acceleration_global: DVec3 =
|
||||
DVec3::from(player_transform.rotation * (axis_input * factor).as_vec3());
|
||||
let mut acceleration_total: DVec3 =
|
||||
(actor::ENGINE_SPEED_FACTOR * dt) as f64 * acceleration_global;
|
||||
let threshold = 1e-5;
|
||||
if key_input.pressed(settings.key_stop) {
|
||||
// Decelerate (or match velocity to target_v)
|
||||
|
@ -461,8 +523,7 @@ pub fn apply_input_to_player(
|
|||
for i in 0..3 {
|
||||
if dv[i].abs() < threshold {
|
||||
v[i] = target_v[i];
|
||||
}
|
||||
else if dv[i].signum() != (dv[i] + acceleration_total[i]).signum() {
|
||||
} else if dv[i].signum() != (dv[i] + acceleration_total[i]).signum() {
|
||||
// Almost stopped, but we overshot v=0
|
||||
v[i] = target_v[i];
|
||||
acceleration_total[i] = 0.0;
|
||||
|
@ -471,18 +532,23 @@ pub fn apply_input_to_player(
|
|||
}
|
||||
// TODO: handle mass
|
||||
v.0 += acceleration_total;
|
||||
engine.current_warmup = (engine.current_warmup + dt / engine.warmup_seconds).clamp(0.0, 1.0);
|
||||
engine.current_warmup =
|
||||
(engine.current_warmup + dt / engine.warmup_seconds).clamp(0.0, 1.0);
|
||||
play_thruster_sound = true;
|
||||
}
|
||||
else {
|
||||
engine.current_warmup = (engine.current_warmup - dt / engine.warmup_seconds).clamp(0.0, 1.0);
|
||||
} else {
|
||||
engine.current_warmup =
|
||||
(engine.current_warmup - dt / engine.warmup_seconds).clamp(0.0, 1.0);
|
||||
}
|
||||
|
||||
// Handle mouse input and mouse-like key bindings
|
||||
let mut play_reactionwheel_sound = false;
|
||||
let mut mouse_delta = Vec2::ZERO;
|
||||
let mut pitch_yaw_rot = Vec3::ZERO;
|
||||
let sensitivity_factor = if settings.is_zooming { settings.zoom_sensitivity_factor } else { 1.0 };
|
||||
let sensitivity_factor = if settings.is_zooming {
|
||||
settings.zoom_sensitivity_factor
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
let mouseless_sensitivity = 8.0 * sensitivity_factor;
|
||||
let mouseless_rotation_sensitivity = 40.0 * sensitivity_factor;
|
||||
if key_input.pressed(settings.key_mouseup) {
|
||||
|
@ -492,7 +558,8 @@ pub fn apply_input_to_player(
|
|||
pitch_yaw_rot[0] += mouseless_sensitivity;
|
||||
}
|
||||
if key_input.pressed(settings.key_mouseleft) {
|
||||
pitch_yaw_rot[1] += mouseless_sensitivity; }
|
||||
pitch_yaw_rot[1] += mouseless_sensitivity;
|
||||
}
|
||||
if key_input.pressed(settings.key_mouseright) {
|
||||
pitch_yaw_rot[1] -= mouseless_sensitivity;
|
||||
}
|
||||
|
@ -522,19 +589,17 @@ pub fn apply_input_to_player(
|
|||
};
|
||||
if pitch_yaw_rot.length_squared() > 1.0e-18 {
|
||||
play_reactionwheel_sound = true;
|
||||
pitch_yaw_rot *= settings.mouse_sensitivity * sensitivity_factor * engine.reaction_wheels;
|
||||
pitch_yaw_rot *=
|
||||
settings.mouse_sensitivity * sensitivity_factor * engine.reaction_wheels;
|
||||
torque.apply_torque(DVec3::from(
|
||||
player_transform.rotation * Vec3::new(
|
||||
pitch_yaw_rot[0],
|
||||
pitch_yaw_rot[1],
|
||||
pitch_yaw_rot[2])));
|
||||
player_transform.rotation
|
||||
* Vec3::new(pitch_yaw_rot[0], pitch_yaw_rot[1], pitch_yaw_rot[2]),
|
||||
));
|
||||
angularvelocity.0 *= angular_slowdown.clamp(0.97, 1.0) as f64;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
if angularvelocity.length_squared() > 1.0e-18 {
|
||||
angularvelocity.0 *= angular_slowdown;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
angularvelocity.0 = DVec3::splat(0.0);
|
||||
}
|
||||
}
|
||||
|
@ -563,8 +628,16 @@ pub fn apply_input_to_player(
|
|||
}
|
||||
}
|
||||
let sinks = vec![
|
||||
(1.2, actor::EngineType::Monopropellant, sinks.get(&audio::Sfx::Thruster)),
|
||||
(1.0, actor::EngineType::Rocket, sinks.get(&audio::Sfx::Rocket)),
|
||||
(
|
||||
1.2,
|
||||
actor::EngineType::Monopropellant,
|
||||
sinks.get(&audio::Sfx::Thruster),
|
||||
),
|
||||
(
|
||||
1.0,
|
||||
actor::EngineType::Rocket,
|
||||
sinks.get(&audio::Sfx::Rocket),
|
||||
),
|
||||
(1.4, actor::EngineType::Ion, sinks.get(&audio::Sfx::Ion)),
|
||||
];
|
||||
let seconds_to_max_vol = 0.05;
|
||||
|
@ -573,8 +646,7 @@ pub fn apply_input_to_player(
|
|||
if let (vol_boost, engine_type, Some(sink)) = sink_data {
|
||||
if settings.mute_sfx {
|
||||
sink.pause();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
let volume = sink.volume();
|
||||
if engine.engine_type == engine_type {
|
||||
if play_thruster_sound {
|
||||
|
@ -582,13 +654,14 @@ pub fn apply_input_to_player(
|
|||
if volume < 1.0 {
|
||||
let maxvol = vol_boost;
|
||||
//let maxvol = engine.current_warmup * vol_boost;
|
||||
sink.set_volume((volume + dt / seconds_to_max_vol).clamp(0.0, maxvol));
|
||||
sink.set_volume(
|
||||
(volume + dt / seconds_to_max_vol).clamp(0.0, maxvol),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
sink.set_volume((volume - dt / seconds_to_min_vol).clamp(0.0, 1.0));
|
||||
}
|
||||
}
|
||||
else if volume > 0.0 {
|
||||
} else if volume > 0.0 {
|
||||
sink.set_volume((volume - dt / seconds_to_min_vol).clamp(0.0, 1.0));
|
||||
}
|
||||
if volume < 0.0001 {
|
||||
|
@ -619,12 +692,14 @@ pub fn update_map_only_object_visibility(
|
|||
let dist = cam_pos.distance(pos.as_vec3());
|
||||
if dist >= onlyinmap.min_distance as f32 {
|
||||
*vis = Visibility::Inherited;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
*vis = Visibility::Hidden;
|
||||
}
|
||||
} else {
|
||||
error!("Failed get position of actor ID '{}'", &onlyinmap.distance_to_id);
|
||||
error!(
|
||||
"Failed get position of actor ID '{}'",
|
||||
&onlyinmap.distance_to_id
|
||||
);
|
||||
*vis = Visibility::Hidden;
|
||||
}
|
||||
} else {
|
||||
|
@ -639,21 +714,22 @@ pub fn find_closest_target<TargetSpecifier>(
|
|||
objects: Vec<(TargetSpecifier, &Transform)>,
|
||||
camera_transform: &Transform,
|
||||
) -> (Option<TargetSpecifier>, f32)
|
||||
where TargetSpecifier: Clone
|
||||
where
|
||||
TargetSpecifier: Clone,
|
||||
{
|
||||
let mut closest_entity: Option<TargetSpecifier> = None;
|
||||
let mut closest_distance: f32 = f32::MAX;
|
||||
let target_vector: Vec3 = (camera_transform.rotation * Vec3::new(0.0, 0.0, -1.0))
|
||||
.normalize_or_zero();
|
||||
let target_vector: Vec3 =
|
||||
(camera_transform.rotation * Vec3::new(0.0, 0.0, -1.0)).normalize_or_zero();
|
||||
for (entity, trans) in objects {
|
||||
// Use Transform instead of Position because we're basing this
|
||||
// not on the player mesh but on the camera, which doesn't have a position.
|
||||
let (angular_diameter, angle, distance) = calc_angular_diameter_known_target_vector(
|
||||
trans, camera_transform, &target_vector);
|
||||
let (angular_diameter, angle, distance) =
|
||||
calc_angular_diameter_known_target_vector(trans, camera_transform, &target_vector);
|
||||
if angle <= angular_diameter.clamp(0.01, PI32) {
|
||||
// It's in the field of view!
|
||||
//commands.entity(entity).insert(IsTargeted);
|
||||
let distance_to_surface = distance - trans.scale.x;
|
||||
let distance_to_surface = distance - trans.scale.x;
|
||||
if distance_to_surface < closest_distance {
|
||||
closest_distance = distance_to_surface;
|
||||
closest_entity = Some(entity);
|
||||
|
@ -669,8 +745,7 @@ pub fn calc_angular_diameter_known_target_vector(
|
|||
camera: &Transform,
|
||||
target_vector: &Vec3,
|
||||
) -> (f32, f32, f32) {
|
||||
let pos_vector: Vec3 = (target.translation - camera.translation)
|
||||
.normalize_or_zero();
|
||||
let pos_vector: Vec3 = (target.translation - camera.translation).normalize_or_zero();
|
||||
let cosine_of_angle: f32 = target_vector.dot(pos_vector);
|
||||
let angle: f32 = cosine_of_angle.acos();
|
||||
let distance: f32 = target.translation.distance(camera.translation);
|
||||
|
@ -678,20 +753,15 @@ pub fn calc_angular_diameter_known_target_vector(
|
|||
let angular_diameter: f32 = if distance > 0.0 {
|
||||
// Angular Diameter
|
||||
leeway * (target.scale[0] / distance).asin()
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
return (angular_diameter, angle, distance);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn calc_angular_diameter(
|
||||
target: &Transform,
|
||||
camera: &Transform,
|
||||
) -> (f32, f32, f32) {
|
||||
let target_vector: Vec3 = (camera.rotation * Vec3::new(0.0, 0.0, -1.0))
|
||||
.normalize_or_zero();
|
||||
pub fn calc_angular_diameter(target: &Transform, camera: &Transform) -> (f32, f32, f32) {
|
||||
let target_vector: Vec3 = (camera.rotation * Vec3::new(0.0, 0.0, -1.0)).normalize_or_zero();
|
||||
return calc_angular_diameter_known_target_vector(target, camera, &target_vector);
|
||||
}
|
||||
|
||||
|
@ -702,7 +772,10 @@ pub fn position_to_transform(
|
|||
mapcam: Res<MapCam>,
|
||||
settings: Res<var::Settings>,
|
||||
q_player: Query<&Position, With<actor::PlayerCamera>>,
|
||||
mut q_trans: Query<(&'static mut Transform, &'static Position, &'static Rotation), Without<Parent>>,
|
||||
mut q_trans: Query<
|
||||
(&'static mut Transform, &'static Position, &'static Rotation),
|
||||
Without<Parent>,
|
||||
>,
|
||||
) {
|
||||
let center: DVec3 = if settings.map_active {
|
||||
mapcam.center
|
||||
|
|
257
src/chat.rs
257
src/chat.rs
|
@ -14,8 +14,8 @@
|
|||
use crate::prelude::*;
|
||||
use bevy::prelude::*;
|
||||
use bevy_xpbd_3d::prelude::*;
|
||||
use serde_yaml::Value;
|
||||
use serde::Deserialize;
|
||||
use serde_yaml::Value;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub const CHATS: &[&str] = &[
|
||||
|
@ -40,7 +40,7 @@ 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 TOKEN_IF_INLINE: &str = "if "; // for lines like `- if foo:`
|
||||
|
||||
pub const DEFAULT_SOUND: &str = "chat";
|
||||
pub const MAX_BRANCH_DEPTH: usize = 64;
|
||||
|
@ -65,24 +65,22 @@ pub const NON_CHOICE_TOKENS: &[&str] = &[
|
|||
TOKEN_SOUND,
|
||||
TOKEN_NOWAIT,
|
||||
];
|
||||
pub const SKIPPABLE_TOKENS: &[&str] = &[
|
||||
TOKEN_CHAT,
|
||||
TOKEN_LABEL,
|
||||
TOKEN_GOTO,
|
||||
TOKEN_NOWAIT,
|
||||
];
|
||||
pub const SKIPPABLE_TOKENS: &[&str] = &[TOKEN_CHAT, TOKEN_LABEL, TOKEN_GOTO, TOKEN_NOWAIT];
|
||||
|
||||
pub struct ChatPlugin;
|
||||
impl Plugin for ChatPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(Startup, load_chats);
|
||||
app.add_systems(Update, (
|
||||
handle_reply_keys.before(handle_chat_timer),
|
||||
handle_chat_timer.before(handle_chat_events),
|
||||
handle_new_conversations.before(handle_chat_events),
|
||||
handle_chat_events.before(handle_chat_scripts),
|
||||
handle_chat_scripts,
|
||||
));
|
||||
app.add_systems(
|
||||
Update,
|
||||
(
|
||||
handle_reply_keys.before(handle_chat_timer),
|
||||
handle_chat_timer.before(handle_chat_events),
|
||||
handle_new_conversations.before(handle_chat_events),
|
||||
handle_chat_events.before(handle_chat_scripts),
|
||||
handle_chat_scripts,
|
||||
),
|
||||
);
|
||||
app.add_event::<StartConversationEvent>();
|
||||
app.add_event::<ChatEvent>();
|
||||
app.add_event::<ChatScriptEvent>();
|
||||
|
@ -107,8 +105,7 @@ pub struct Choice {
|
|||
pub goto: ChatPos,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
#[derive(Clone)]
|
||||
#[derive(Component, Clone)]
|
||||
pub struct Talker {
|
||||
pub chat_name: String,
|
||||
pub actor_id: String,
|
||||
|
@ -152,7 +149,6 @@ impl Default for Extracted {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// This is the only place where any YAML interaction should be happening.
|
||||
#[derive(Resource)]
|
||||
pub struct ChatDB(Vec<Value>);
|
||||
|
@ -165,8 +161,7 @@ impl ChatDB {
|
|||
if let Value::Sequence(yaml_sequence) = yaml_data {
|
||||
self.0.push(Value::Sequence(yaml_sequence));
|
||||
count += 1;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
error!("Could not load YAML: {:?}", yaml_data);
|
||||
}
|
||||
}
|
||||
|
@ -202,7 +197,10 @@ impl ChatDB {
|
|||
}
|
||||
}
|
||||
|
||||
fn preprocess_includes_recursively(sequence: &mut Value, include_db: &HashMap<String, Vec<Value>>) {
|
||||
fn preprocess_includes_recursively(
|
||||
sequence: &mut Value,
|
||||
include_db: &HashMap<String, Vec<Value>>,
|
||||
) {
|
||||
let mut changes: Vec<(usize, String)> = Vec::new();
|
||||
if let Some(vector) = sequence.as_sequence_mut() {
|
||||
for (index, item) in vector.iter_mut().enumerate() {
|
||||
|
@ -213,8 +211,7 @@ impl ChatDB {
|
|||
if key == TOKEN_INCLUDE {
|
||||
changes.push((index, value.to_string()));
|
||||
}
|
||||
}
|
||||
else if value.is_sequence() {
|
||||
} else if value.is_sequence() {
|
||||
ChatDB::preprocess_includes_recursively(value, include_db);
|
||||
}
|
||||
}
|
||||
|
@ -248,7 +245,9 @@ impl ChatDB {
|
|||
if let Some(result) = found {
|
||||
return Ok(result);
|
||||
}
|
||||
return Err(format!("No chat with the conversation ID `{id}` was found."));
|
||||
return Err(format!(
|
||||
"No chat with the conversation ID `{id}` was found."
|
||||
));
|
||||
}
|
||||
|
||||
// For a given Value, check whether it's a Value::Mapping and whether it
|
||||
|
@ -273,19 +272,15 @@ impl ChatDB {
|
|||
if let Value::String(key) = key {
|
||||
if key == TOKEN_NOWAIT && value.as_bool() == Some(true) {
|
||||
result.nowait = true;
|
||||
}
|
||||
else if key == TOKEN_IF {
|
||||
} 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()) {
|
||||
} else if non_choice_tokens.contains(&key.as_str()) {
|
||||
// skip over the other non-choice tokens
|
||||
}
|
||||
else if key.as_str().starts_with(TOKEN_IF_INLINE) {
|
||||
} else if key.as_str().starts_with(TOKEN_IF_INLINE) {
|
||||
// skip over inlined if-statements
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
result.choice_text = Some(key.to_string());
|
||||
}
|
||||
}
|
||||
|
@ -295,7 +290,12 @@ impl ChatDB {
|
|||
return None;
|
||||
}
|
||||
|
||||
fn search_label_recursively(&self, sequence: &Value, label: &String, mut pos: ChatPos) -> Option<ChatPos> {
|
||||
fn search_label_recursively(
|
||||
&self,
|
||||
sequence: &Value,
|
||||
label: &String,
|
||||
mut pos: ChatPos,
|
||||
) -> Option<ChatPos> {
|
||||
if pos.len() > MAX_BRANCH_DEPTH {
|
||||
return None;
|
||||
}
|
||||
|
@ -315,9 +315,10 @@ impl ChatDB {
|
|||
}
|
||||
if value.is_sequence() {
|
||||
pos.push(index);
|
||||
if let Some(result) = self.search_label_recursively(
|
||||
value, label, pos.clone()) {
|
||||
return Some(result)
|
||||
if let Some(result) =
|
||||
self.search_label_recursively(value, label, pos.clone())
|
||||
{
|
||||
return Some(result);
|
||||
}
|
||||
pos.pop();
|
||||
}
|
||||
|
@ -357,7 +358,7 @@ impl ChatDB {
|
|||
let index = chat.position.len() - 1;
|
||||
chat.position[index] += 1;
|
||||
}
|
||||
},
|
||||
}
|
||||
Some(Value::Mapping(map)) => {
|
||||
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
|
||||
|
@ -367,8 +368,7 @@ impl ChatDB {
|
|||
let index = chat.position.len() - 1;
|
||||
chat.position[index] += 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -441,8 +441,7 @@ impl ChatDB {
|
|||
if !SKIPPABLE_TOKENS.contains(&key) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -452,17 +451,16 @@ impl ChatDB {
|
|||
return false;
|
||||
}
|
||||
|
||||
fn process_yaml_entry(
|
||||
&self,
|
||||
chat: &mut Chat,
|
||||
event: &mut EventWriter<ChatEvent>,
|
||||
) -> bool {
|
||||
fn process_yaml_entry(&self, chat: &mut Chat, event: &mut EventWriter<ChatEvent>) -> bool {
|
||||
let current_item = self.at(chat.internal_id, &chat.position);
|
||||
let mut processed_a_choice = false;
|
||||
match current_item {
|
||||
Some(Value::String(message)) => {
|
||||
event.send(ChatEvent::SpawnMessage(message.to_string(),
|
||||
hud::LogLevel::Chat, DEFAULT_SOUND.to_string()));
|
||||
event.send(ChatEvent::SpawnMessage(
|
||||
message.to_string(),
|
||||
hud::LogLevel::Chat,
|
||||
DEFAULT_SOUND.to_string(),
|
||||
));
|
||||
}
|
||||
Some(Value::Mapping(map)) => {
|
||||
let mut sound = DEFAULT_SOUND.to_string();
|
||||
|
@ -506,15 +504,24 @@ impl ChatDB {
|
|||
match (key, value) {
|
||||
(Some(TOKEN_MSG), Value::String(message)) => {
|
||||
event.send(ChatEvent::SpawnMessage(
|
||||
message.to_string(), hud::LogLevel::Chat, sound.clone()));
|
||||
message.to_string(),
|
||||
hud::LogLevel::Chat,
|
||||
sound.clone(),
|
||||
));
|
||||
}
|
||||
(Some(TOKEN_SYSTEM), Value::String(message)) => {
|
||||
event.send(ChatEvent::SpawnMessage(
|
||||
message.to_string(), hud::LogLevel::Info, sound.clone()));
|
||||
message.to_string(),
|
||||
hud::LogLevel::Info,
|
||||
sound.clone(),
|
||||
));
|
||||
}
|
||||
(Some(TOKEN_WARN), Value::String(message)) => {
|
||||
event.send(ChatEvent::SpawnMessage(
|
||||
message.to_string(), hud::LogLevel::Warning, sound.clone()));
|
||||
message.to_string(),
|
||||
hud::LogLevel::Warning,
|
||||
sound.clone(),
|
||||
));
|
||||
}
|
||||
(Some(TOKEN_SET), Value::String(instructions)) => {
|
||||
event.send(ChatEvent::SetVariable(instructions.to_string()));
|
||||
|
@ -551,8 +558,11 @@ impl ChatDB {
|
|||
}
|
||||
None => {
|
||||
if chat.position.len() == 0 {
|
||||
event.send(ChatEvent::SpawnMessage("Disconnected.".to_string(),
|
||||
hud::LogLevel::Info, DEFAULT_SOUND.to_string()));
|
||||
event.send(ChatEvent::SpawnMessage(
|
||||
"Disconnected.".to_string(),
|
||||
hud::LogLevel::Info,
|
||||
DEFAULT_SOUND.to_string(),
|
||||
));
|
||||
event.send(ChatEvent::DespawnAllChats);
|
||||
}
|
||||
}
|
||||
|
@ -584,19 +594,25 @@ 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(data) = self.extract_choice(self.at(chat.internal_id, &chat.position).as_ref()) {
|
||||
while let Some(data) =
|
||||
self.extract_choice(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));
|
||||
event.send(ChatEvent::SpawnChoice(
|
||||
choice_text,
|
||||
key,
|
||||
goto,
|
||||
data.nowait,
|
||||
data.condition,
|
||||
));
|
||||
key += 1;
|
||||
reached_end_of_branch = self.advance_pointer(chat);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -640,10 +656,7 @@ pub fn handle_new_conversations(
|
|||
talker: event.talker.clone(),
|
||||
};
|
||||
chatdb.advance_chat(&mut chat, &mut ew_chatevent);
|
||||
commands.spawn((
|
||||
chat,
|
||||
world::DespawnOnPlayerDeath,
|
||||
));
|
||||
commands.spawn((chat, world::DespawnOnPlayerDeath));
|
||||
}
|
||||
Err(error) => {
|
||||
error!("Error while looking for chat ID: {error}");
|
||||
|
@ -700,7 +713,10 @@ pub fn handle_chat_events(
|
|||
ChatEvent::SpawnMessage(message, level, sound) => {
|
||||
match level {
|
||||
hud::LogLevel::Chat => {
|
||||
log.chat(message.into(), chat.talker.name.clone().unwrap_or("".to_string()));
|
||||
log.chat(
|
||||
message.into(),
|
||||
chat.talker.name.clone().unwrap_or("".to_string()),
|
||||
);
|
||||
}
|
||||
hud::LogLevel::Info => {
|
||||
log.info(message.into());
|
||||
|
@ -712,36 +728,40 @@ pub fn handle_chat_events(
|
|||
log.warning(message.into());
|
||||
}
|
||||
hud::LogLevel::Always => {
|
||||
log.add(message.into(),
|
||||
log.add(
|
||||
message.into(),
|
||||
chat.talker.name.clone().unwrap_or("".to_string()),
|
||||
hud::LogLevel::Always);
|
||||
hud::LogLevel::Always,
|
||||
);
|
||||
}
|
||||
}
|
||||
chat.timer = now + ((message.len() as f32).max(CHAT_SPEED_MIN_LEN) * TALKER_SPEED_FACTOR * chat.talker.talking_speed / settings.chat_speed) as f64;
|
||||
chat.timer = now
|
||||
+ ((message.len() as f32).max(CHAT_SPEED_MIN_LEN)
|
||||
* TALKER_SPEED_FACTOR
|
||||
* chat.talker.talking_speed
|
||||
/ settings.chat_speed) as f64;
|
||||
|
||||
let sfx = audio::str2sfx(sound);
|
||||
ew_sfx.send(audio::PlaySfxEvent(sfx));
|
||||
}
|
||||
ChatEvent::SpawnChoice(replytext, _key, goto, nowait, condition) => {
|
||||
'out: {
|
||||
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: choice_key,
|
||||
goto: goto.clone(),
|
||||
}
|
||||
));
|
||||
choice_key += 1;
|
||||
if !nowait {
|
||||
chat.timer = now + CHOICE_TIMER / settings.chat_speed as f64;
|
||||
ChatEvent::SpawnChoice(replytext, _key, goto, nowait, condition) => 'out: {
|
||||
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: choice_key,
|
||||
goto: goto.clone(),
|
||||
},
|
||||
));
|
||||
choice_key += 1;
|
||||
if !nowait {
|
||||
chat.timer = now + CHOICE_TIMER / settings.chat_speed as f64;
|
||||
}
|
||||
}
|
||||
ChatEvent::RunScript(script) => {
|
||||
ew_chatscript.send(ChatScriptEvent(script.clone()));
|
||||
|
@ -794,7 +814,14 @@ fn handle_reply_keys(
|
|||
pub fn handle_chat_scripts(
|
||||
mut er_chatscript: EventReader<ChatScriptEvent>,
|
||||
mut q_actor: Query<(&mut actor::Actor, &mut actor::Suit), Without<actor::Player>>,
|
||||
mut q_player: Query<(&mut actor::Actor, &mut actor::Suit, &mut actor::ExperiencesGForce), With<actor::Player>>,
|
||||
mut q_player: Query<
|
||||
(
|
||||
&mut actor::Actor,
|
||||
&mut actor::Suit,
|
||||
&mut actor::ExperiencesGForce,
|
||||
),
|
||||
With<actor::Player>,
|
||||
>,
|
||||
mut q_playercam: Query<(&mut Position, &mut LinearVelocity), With<actor::PlayerCamera>>,
|
||||
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
|
||||
mut ew_effect: EventWriter<visual::SpawnEffectEvent>,
|
||||
|
@ -815,35 +842,37 @@ pub fn handle_chat_scripts(
|
|||
|
||||
// Process the script
|
||||
match name {
|
||||
"refilloxygen" => if let Ok(mut amount) = param1.to_string().parse::<f32>() {
|
||||
for (_, mut suit, _) in q_player.iter_mut() {
|
||||
if param2.is_empty() {
|
||||
suit.oxygen = (suit.oxygen + amount).clamp(0.0, suit.oxygen_max);
|
||||
}
|
||||
else {
|
||||
let mut found_other = false;
|
||||
info!("param2={}", param2);
|
||||
for (other_actor, mut other_suit) in q_actor.iter_mut() {
|
||||
if !other_actor.id.is_empty() {
|
||||
info!("ID={}", other_actor.id);
|
||||
"refilloxygen" => {
|
||||
if let Ok(mut amount) = param1.to_string().parse::<f32>() {
|
||||
for (_, mut suit, _) in q_player.iter_mut() {
|
||||
if param2.is_empty() {
|
||||
suit.oxygen = (suit.oxygen + amount).clamp(0.0, suit.oxygen_max);
|
||||
} else {
|
||||
let mut found_other = false;
|
||||
info!("param2={}", param2);
|
||||
for (other_actor, mut other_suit) in q_actor.iter_mut() {
|
||||
if !other_actor.id.is_empty() {
|
||||
info!("ID={}", other_actor.id);
|
||||
}
|
||||
if other_actor.id == param2 {
|
||||
found_other = true;
|
||||
amount = amount
|
||||
.clamp(0.0, other_suit.oxygen)
|
||||
.clamp(0.0, suit.oxygen_max - suit.oxygen);
|
||||
other_suit.oxygen = other_suit.oxygen - amount;
|
||||
suit.oxygen =
|
||||
(suit.oxygen + amount).clamp(0.0, suit.oxygen_max);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if other_actor.id == param2 {
|
||||
found_other = true;
|
||||
amount = amount
|
||||
.clamp(0.0, other_suit.oxygen)
|
||||
.clamp(0.0, suit.oxygen_max - suit.oxygen);
|
||||
other_suit.oxygen = other_suit.oxygen - amount;
|
||||
suit.oxygen = (suit.oxygen + amount).clamp(0.0, suit.oxygen_max);
|
||||
break;
|
||||
if !found_other {
|
||||
error!("Script error: could not find actor with ID `{}`", param2);
|
||||
}
|
||||
}
|
||||
if !found_other {
|
||||
error!("Script error: could not find actor with ID `{}`", param2);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error!("Invalid parameter for command `{}`: `{}`", name, param1);
|
||||
}
|
||||
} else {
|
||||
error!("Invalid parameter for command `{}`: `{}`", name, param1);
|
||||
}
|
||||
"repairsuit" => {
|
||||
ew_achievement.send(game::AchievementEvent::RepairSuit);
|
||||
|
@ -854,21 +883,23 @@ pub fn handle_chat_scripts(
|
|||
"cryotrip" => {
|
||||
if param1.is_empty() {
|
||||
error!("Chat script cryotrip needs a parameter");
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
if let Ok((mut pos, mut v)) = q_playercam.get_single_mut() {
|
||||
let busstop = match param1 {
|
||||
"serenity" => Some("busstopclippy"),
|
||||
"farview" => Some("busstopclippy2"),
|
||||
"metisprime" => Some("busstopclippy3"),
|
||||
_ => None
|
||||
_ => None,
|
||||
};
|
||||
if let Some(station) = busstop {
|
||||
if let Some(target) = id2pos.0.get(&station.to_string()) {
|
||||
pos.0 = *target + DVec3::new(0.0, -1000.0, 0.0);
|
||||
v.0 = DVec3::ZERO;
|
||||
} else {
|
||||
error!("Could not determine position of actor with ID: '{}'", station);
|
||||
error!(
|
||||
"Could not determine position of actor with ID: '{}'",
|
||||
station
|
||||
);
|
||||
}
|
||||
} else {
|
||||
error!("Invalid destination for cryotrip chat script: '{}'", param1);
|
||||
|
|
566
src/cmd.rs
566
src/cmd.rs
|
@ -11,10 +11,10 @@
|
|||
// This module populates the world with actors as defined in "defs.txt"
|
||||
|
||||
extern crate regex;
|
||||
use crate::prelude::*;
|
||||
use bevy::pbr::{NotShadowCaster, NotShadowReceiver};
|
||||
use bevy::prelude::*;
|
||||
use bevy_xpbd_3d::prelude::*;
|
||||
use bevy::pbr::{NotShadowCaster, NotShadowReceiver};
|
||||
use crate::prelude::*;
|
||||
use regex::Regex;
|
||||
use std::time::SystemTime;
|
||||
|
||||
|
@ -28,14 +28,18 @@ impl Plugin for CmdPlugin {
|
|||
app.add_systems(Startup, load_defs);
|
||||
app.add_systems(Update, spawn_entities);
|
||||
app.add_systems(Update, process_mesh);
|
||||
app.add_systems(PreUpdate, hide_colliders
|
||||
.run_if(any_with_component::<NeedsSceneColliderRemoved>));
|
||||
app.add_systems(
|
||||
PreUpdate,
|
||||
hide_colliders.run_if(any_with_component::<NeedsSceneColliderRemoved>),
|
||||
);
|
||||
app.add_event::<SpawnEvent>();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)] pub struct NeedsSceneColliderRemoved;
|
||||
#[derive(Event)] pub struct SpawnEvent(ParserState);
|
||||
#[derive(Component)]
|
||||
pub struct NeedsSceneColliderRemoved;
|
||||
#[derive(Event)]
|
||||
pub struct SpawnEvent(ParserState);
|
||||
#[derive(PartialEq, Clone)]
|
||||
enum DefClass {
|
||||
Actor,
|
||||
|
@ -157,11 +161,11 @@ impl Default for ParserState {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn load_defs(
|
||||
mut ew_spawn: EventWriter<SpawnEvent>,
|
||||
) {
|
||||
pub fn load_defs(mut ew_spawn: EventWriter<SpawnEvent>) {
|
||||
let re1 = Regex::new(r"^\s*([a-z_-]+)\s+(.*)$").unwrap();
|
||||
let re2 = Regex::new("\"([^\"]*)\"|(-?[0-9]+[0-9e-]*(?:\\.[0-9e-]+)?)|([a-zA-Z_-][a-zA-Z0-9_-]*)").unwrap();
|
||||
let re2 =
|
||||
Regex::new("\"([^\"]*)\"|(-?[0-9]+[0-9e-]*(?:\\.[0-9e-]+)?)|([a-zA-Z_-][a-zA-Z0-9_-]*)")
|
||||
.unwrap();
|
||||
let defs_string = include_str!("data/defs.txt");
|
||||
let mut lines = defs_string.lines();
|
||||
let mut state = ParserState::default();
|
||||
|
@ -181,9 +185,11 @@ pub fn load_defs(
|
|||
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);
|
||||
} else {
|
||||
error!(
|
||||
"Failed to read regex captures in line {}: `{}`",
|
||||
line_nr, line
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -214,10 +220,10 @@ pub fn load_defs(
|
|||
state.class = DefClass::Actor;
|
||||
state.model = Some(model.to_string());
|
||||
if let (Ok(x_float), Ok(y_float), Ok(z_float)) =
|
||||
(x.parse::<f64>(), y.parse::<f64>(), z.parse::<f64>()) {
|
||||
(x.parse::<f64>(), y.parse::<f64>(), z.parse::<f64>())
|
||||
{
|
||||
state.pos = DVec3::new(x_float, y_float, z_float);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
error!("Can't parse coordinates as floats in def: {line}");
|
||||
state = ParserState::default();
|
||||
continue;
|
||||
|
@ -228,10 +234,10 @@ pub fn load_defs(
|
|||
state = ParserState::default();
|
||||
state.class = DefClass::Actor;
|
||||
if let (Ok(x_float), Ok(y_float), Ok(z_float)) =
|
||||
(x.parse::<f64>(), y.parse::<f64>(), z.parse::<f64>()) {
|
||||
(x.parse::<f64>(), y.parse::<f64>(), z.parse::<f64>())
|
||||
{
|
||||
state.pos = DVec3::new(x_float, y_float, z_float);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
error!("Can't parse coordinates as floats in def: {line}");
|
||||
state = ParserState::default();
|
||||
continue;
|
||||
|
@ -244,8 +250,7 @@ pub fn load_defs(
|
|||
if let Ok(r) = radius_str.parse::<f64>() {
|
||||
state.orbit_distance = Some(r);
|
||||
state.orbit_object_id = Some(object_id.to_string());
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
error!("Can't parse float: {line}");
|
||||
continue;
|
||||
}
|
||||
|
@ -254,8 +259,7 @@ pub fn load_defs(
|
|||
if let (Ok(r), Ok(phase)) = (radius_str.parse::<f64>(), phase_str.parse::<f64>()) {
|
||||
state.orbit_distance = Some(r);
|
||||
state.orbit_phase = Some(phase * PI * 2.0);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
error!("Can't parse float: {line}");
|
||||
continue;
|
||||
}
|
||||
|
@ -265,12 +269,10 @@ pub fn load_defs(
|
|||
let offset_radians = 2.0 * PI * value_float;
|
||||
if let Some(phase_radians) = state.orbit_phase {
|
||||
state.orbit_phase = Some(phase_radians + offset_radians);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
state.orbit_phase = Some(offset_radians);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
error!("Can't parse float: {line}");
|
||||
continue;
|
||||
}
|
||||
|
@ -312,8 +314,7 @@ pub fn load_defs(
|
|||
state.is_lifeform = true;
|
||||
state.is_suited = true;
|
||||
state.oxygen = amount;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
error!("Can't parse float: {line}");
|
||||
continue;
|
||||
}
|
||||
|
@ -327,8 +328,7 @@ pub fn load_defs(
|
|||
["scale", scale] => {
|
||||
if let Ok(scale_float) = scale.parse::<f32>() {
|
||||
state.model_scale = scale_float;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
error!("Can't parse float: {line}");
|
||||
continue;
|
||||
}
|
||||
|
@ -336,8 +336,7 @@ pub fn load_defs(
|
|||
["rotationx", rotation_x] => {
|
||||
if let Ok(rotation_x_float) = rotation_x.parse::<f32>() {
|
||||
state.rotation *= Quat::from_rotation_x(rotation_x_float.to_radians());
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
error!("Can't parse float: {line}");
|
||||
continue;
|
||||
}
|
||||
|
@ -345,8 +344,7 @@ pub fn load_defs(
|
|||
["rotationy", rotation_y] => {
|
||||
if let Ok(rotation_y_float) = rotation_y.parse::<f32>() {
|
||||
state.rotation *= Quat::from_rotation_y(rotation_y_float.to_radians());
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
error!("Can't parse float: {line}");
|
||||
continue;
|
||||
}
|
||||
|
@ -354,8 +352,7 @@ pub fn load_defs(
|
|||
["rotationz", rotation_z] => {
|
||||
if let Ok(rotation_z_float) = rotation_z.parse::<f32>() {
|
||||
state.rotation *= Quat::from_rotation_z(rotation_z_float.to_radians());
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
error!("Can't parse float: {line}");
|
||||
continue;
|
||||
}
|
||||
|
@ -364,34 +361,45 @@ pub fn load_defs(
|
|||
if let Ok(rotation_y_float) = rotation_y.parse::<f32>() {
|
||||
state.rotation *= Quat::from_rotation_y(rotation_y_float.to_radians());
|
||||
state.axialtilt = rotation_y_float;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
error!("Can't parse float: {line}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
["velocity", x, y, z] => {
|
||||
if let (Ok(x_float), Ok(y_float), Ok(z_float)) =
|
||||
(x.parse::<f64>(), y.parse::<f64>(), z.parse::<f64>()) {
|
||||
(x.parse::<f64>(), y.parse::<f64>(), z.parse::<f64>())
|
||||
{
|
||||
state.velocity = DVec3::new(x_float, y_float, z_float);
|
||||
}
|
||||
else {
|
||||
} 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::<f64>(), y.parse::<f64>(), z.parse::<f64>()) {
|
||||
(x.parse::<f64>(), y.parse::<f64>(), z.parse::<f64>())
|
||||
{
|
||||
state.angular_momentum = DVec3::new(x_float, y_float, z_float);
|
||||
}
|
||||
else {
|
||||
} 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::<f32>(), back.parse::<f32>(), sideways.parse::<f32>(), reaction_wheels.parse::<f32>(), warmup_time.parse::<f32>()) {
|
||||
if let (
|
||||
Ok(forward_float),
|
||||
Ok(back_float),
|
||||
Ok(sideways_float),
|
||||
Ok(reaction_wheels_float),
|
||||
Ok(warmup_time_float),
|
||||
) = (
|
||||
forward.parse::<f32>(),
|
||||
back.parse::<f32>(),
|
||||
sideways.parse::<f32>(),
|
||||
reaction_wheels.parse::<f32>(),
|
||||
warmup_time.parse::<f32>(),
|
||||
) {
|
||||
state.thrust_forward = forward_float;
|
||||
state.thrust_back = back_float;
|
||||
state.thrust_sideways = sideways_float;
|
||||
|
@ -411,8 +419,7 @@ pub fn load_defs(
|
|||
["health", value] => {
|
||||
if let Ok(value_float) = value.parse::<f32>() {
|
||||
state.suit_integrity = value_float;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
error!("Can't parse float: {line}");
|
||||
continue;
|
||||
}
|
||||
|
@ -420,8 +427,7 @@ pub fn load_defs(
|
|||
["density", value] => {
|
||||
if let Ok(value_float) = value.parse::<f64>() {
|
||||
state.density = value_float;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
error!("Can't parse float: {line}");
|
||||
continue;
|
||||
}
|
||||
|
@ -432,17 +438,17 @@ pub fn load_defs(
|
|||
["collider", "sphere", radius] => {
|
||||
if let Ok(radius_float) = radius.parse::<f64>() {
|
||||
state.collider = Collider::sphere(radius_float);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
error!("Can't parse float: {line}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
["collider", "capsule", height, radius] => {
|
||||
if let (Ok(height_float), Ok(radius_float)) = (height.parse::<f64>(), radius.parse::<f64>()) {
|
||||
if let (Ok(height_float), Ok(radius_float)) =
|
||||
(height.parse::<f64>(), radius.parse::<f64>())
|
||||
{
|
||||
state.collider = Collider::capsule(height_float, radius_float);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
error!("Can't parse float: {line}");
|
||||
continue;
|
||||
}
|
||||
|
@ -460,8 +466,7 @@ pub fn load_defs(
|
|||
["camdistance", value] => {
|
||||
if let Ok(value_float) = value.parse::<f32>() {
|
||||
state.camdistance = value_float;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
error!("Can't parse float: {line}");
|
||||
continue;
|
||||
}
|
||||
|
@ -471,13 +476,11 @@ pub fn load_defs(
|
|||
if let Ok(color) = Color::hex(color_hex) {
|
||||
state.light_color = Some(color);
|
||||
state.light_brightness = brightness_float;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
error!("Can't parse hexadecimal color code: {line}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
error!("Can't parse float: {line}");
|
||||
continue;
|
||||
}
|
||||
|
@ -486,8 +489,7 @@ pub fn load_defs(
|
|||
// NOTE: requires an engine to slow down velocity
|
||||
if let Ok(value_float) = value.parse::<f64>() {
|
||||
state.wants_maxrotation = Some(value_float);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
error!("Can't parse float: {line}");
|
||||
continue;
|
||||
}
|
||||
|
@ -496,8 +498,7 @@ pub fn load_defs(
|
|||
// NOTE: requires an engine to slow down velocity
|
||||
if let Ok(value_float) = value.parse::<f64>() {
|
||||
state.wants_maxvelocity = Some(value_float);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
error!("Can't parse float: {line}");
|
||||
continue;
|
||||
}
|
||||
|
@ -511,8 +512,7 @@ pub fn load_defs(
|
|||
["only_in_map_at_dist", value, id] => {
|
||||
if let Ok(value_float) = value.parse::<f64>() {
|
||||
state.show_only_in_map_at_distance = Some((value_float, id.to_string()));
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
error!("Can't parse float: {line}");
|
||||
continue;
|
||||
}
|
||||
|
@ -543,9 +543,7 @@ fn spawn_entities(
|
|||
// Preprocessing
|
||||
let mut absolute_pos = if let Some(id) = &state.relative_to {
|
||||
match id2pos.0.get(&id.to_string()) {
|
||||
Some(pos) => {
|
||||
state.pos + *pos
|
||||
}
|
||||
Some(pos) => state.pos + *pos,
|
||||
None => {
|
||||
error!("Specified `relativeto` command but could not find id `{id}`");
|
||||
continue;
|
||||
|
@ -569,7 +567,9 @@ fn spawn_entities(
|
|||
}
|
||||
};
|
||||
let orbital_period = nature::simple_orbital_period(mass, r);
|
||||
phase_radians += if let Ok(epoch) = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
|
||||
phase_radians += if let Ok(epoch) =
|
||||
SystemTime::now().duration_since(SystemTime::UNIX_EPOCH)
|
||||
{
|
||||
let now = epoch.as_secs_f64() + 614533234154.0; // random
|
||||
PI * 2.0 * (now % orbital_period) / orbital_period
|
||||
} else {
|
||||
|
@ -579,223 +579,223 @@ fn spawn_entities(
|
|||
}
|
||||
absolute_pos += nature::phase_dist_to_coords(-phase_radians, r);
|
||||
}
|
||||
let scale = Vec3::splat(if state.is_sun {
|
||||
5.0
|
||||
} else if state.is_moon && settings.large_moons {
|
||||
3.0
|
||||
} else {
|
||||
1.0
|
||||
} * state.model_scale);
|
||||
let scale = Vec3::splat(
|
||||
if state.is_sun {
|
||||
5.0
|
||||
} else if state.is_moon && settings.large_moons {
|
||||
3.0
|
||||
} else {
|
||||
1.0
|
||||
} * state.model_scale,
|
||||
);
|
||||
|
||||
// Spawn the actor
|
||||
let actor_entity;
|
||||
{
|
||||
let mut actor = commands.spawn_empty();
|
||||
actor.insert(actor::Actor {
|
||||
id: state.id.clone(),
|
||||
name: state.name.clone(),
|
||||
camdistance: state.camdistance,
|
||||
..default()
|
||||
});
|
||||
actor.insert(SleepingDisabled);
|
||||
actor.insert(world::DespawnOnPlayerDeath);
|
||||
actor.insert(actor::HitPoints::default());
|
||||
actor.insert(Position::from(absolute_pos));
|
||||
if state.is_sphere {
|
||||
let sphere_texture_handle = if let Some(model) = &state.model {
|
||||
Some(asset_server.load(format!("textures/{}.jpg", model)))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
rotation = Quat::from_rotation_x(-90f32.to_radians()) * rotation;
|
||||
let sphere_handle = meshes.add(Sphere::new(1.0).mesh().uv(128, 128));
|
||||
let sphere_material_handle = materials.add(StandardMaterial {
|
||||
base_color_texture: sphere_texture_handle,
|
||||
perceptual_roughness: 1.0,
|
||||
metallic: 0.0,
|
||||
..default()
|
||||
});
|
||||
actor.insert(PbrBundle {
|
||||
mesh: sphere_handle,
|
||||
material: sphere_material_handle,
|
||||
transform: Transform::from_scale(scale),
|
||||
..default()
|
||||
});
|
||||
} else if let Some(model) = &state.model {
|
||||
actor.insert(SpatialBundle {
|
||||
transform: Transform::from_scale(scale),
|
||||
..default()
|
||||
});
|
||||
load_asset(model.as_str(), &mut actor, &*asset_server);
|
||||
}
|
||||
actor.insert(Rotation::from(rotation));
|
||||
|
||||
// Physics Parameters
|
||||
if state.has_physics {
|
||||
actor.insert(RigidBody::Dynamic);
|
||||
actor.insert(LinearVelocity(state.velocity));
|
||||
actor.insert(AngularVelocity(state.angular_momentum));
|
||||
actor.insert(ColliderDensity(state.density));
|
||||
if state.collider_is_mesh {
|
||||
actor.insert(MassPropertiesBundle::new_computed(
|
||||
&Collider::sphere(0.5 * state.model_scale as f64), state.density));
|
||||
actor.insert(AsyncSceneCollider::new(Some(
|
||||
ComputedCollider::TriMesh
|
||||
//ComputedCollider::ConvexDecomposition(VHACDParameters::default())
|
||||
)));
|
||||
}
|
||||
else if state.collider_is_one_mesh_of_scene {
|
||||
actor.insert(MassPropertiesBundle::new_computed(
|
||||
&Collider::sphere(0.5 * state.model_scale as f64), state.density));
|
||||
actor.insert(AsyncSceneCollider::new(None)
|
||||
.with_shape_for_name("Collider", ComputedCollider::TriMesh)
|
||||
.with_layers_for_name("Collider", CollisionLayers::ALL)
|
||||
//.with_density_for_name("Collider", state.density)
|
||||
);
|
||||
actor.insert(NeedsSceneColliderRemoved);
|
||||
}
|
||||
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_sun {
|
||||
let (r, g, b) = nature::star_color_index_to_rgb(0.656);
|
||||
actor.insert(materials.add(StandardMaterial {
|
||||
base_color: Color::rgb(r, g, b) * 13.0,
|
||||
unlit: true,
|
||||
..default()
|
||||
}));
|
||||
actor.insert((
|
||||
NotShadowCaster,
|
||||
NotShadowReceiver,
|
||||
));
|
||||
}
|
||||
if state.is_targeted_on_startup {
|
||||
actor.insert(hud::IsTargeted);
|
||||
}
|
||||
if let Some((mindist, id)) = &state.show_only_in_map_at_distance {
|
||||
actor.insert(camera::ShowOnlyInMap {
|
||||
min_distance: *mindist,
|
||||
distance_to_id: id.clone()
|
||||
});
|
||||
}
|
||||
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::ExperiencesGForce::default());
|
||||
actor.insert(actor::Suit {
|
||||
oxygen: state.oxygen,
|
||||
oxygen_max: nature::OXY_D,
|
||||
integrity: state.suit_integrity,
|
||||
..default()
|
||||
});
|
||||
actor.insert(actor::Battery::default());
|
||||
}
|
||||
if state.is_clickable {
|
||||
actor.insert(hud::IsClickable {
|
||||
let mut actor = commands.spawn_empty();
|
||||
actor.insert(actor::Actor {
|
||||
id: state.id.clone(),
|
||||
name: state.name.clone(),
|
||||
pronoun: state.pronoun.clone(),
|
||||
camdistance: state.camdistance,
|
||||
..default()
|
||||
});
|
||||
}
|
||||
if let Some(value) = state.wants_maxrotation {
|
||||
actor.insert(actor::WantsMaxRotation(value));
|
||||
}
|
||||
if let Some(value) = state.wants_maxvelocity {
|
||||
actor.insert(actor::WantsMaxVelocity(value));
|
||||
}
|
||||
if let Some(color) = state.light_color {
|
||||
actor.insert((
|
||||
PointLight {
|
||||
intensity: state.light_brightness,
|
||||
color,
|
||||
range: 2000.0,
|
||||
shadows_enabled: settings.shadows_pointlights,
|
||||
actor.insert(SleepingDisabled);
|
||||
actor.insert(world::DespawnOnPlayerDeath);
|
||||
actor.insert(actor::HitPoints::default());
|
||||
actor.insert(Position::from(absolute_pos));
|
||||
if state.is_sphere {
|
||||
let sphere_texture_handle = if let Some(model) = &state.model {
|
||||
Some(asset_server.load(format!("textures/{}.jpg", model)))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
rotation = Quat::from_rotation_x(-90f32.to_radians()) * rotation;
|
||||
let sphere_handle = meshes.add(Sphere::new(1.0).mesh().uv(128, 128));
|
||||
let sphere_material_handle = materials.add(StandardMaterial {
|
||||
base_color_texture: sphere_texture_handle,
|
||||
perceptual_roughness: 1.0,
|
||||
metallic: 0.0,
|
||||
..default()
|
||||
},
|
||||
bevy::pbr::CubemapVisibleEntities::default(),
|
||||
bevy::render::primitives::CubemapFrusta::default(),
|
||||
));
|
||||
}
|
||||
if !state.id.is_empty() {
|
||||
actor.insert(actor::Identifier(state.id.clone()));
|
||||
id2pos.0.insert(state.id.clone(), absolute_pos);
|
||||
}
|
||||
if !state.chat.is_empty() {
|
||||
actor.insert(chat::Talker {
|
||||
actor_id: state.id.clone(),
|
||||
chat_name: state.chat.clone(),
|
||||
name: state.name.clone(),
|
||||
pronoun: state.pronoun.clone(),
|
||||
talking_speed: 1.0,
|
||||
});
|
||||
if let Some(name) = &state.name {
|
||||
achievement_tracker.all_people.insert(name.clone());
|
||||
});
|
||||
actor.insert(PbrBundle {
|
||||
mesh: sphere_handle,
|
||||
material: sphere_material_handle,
|
||||
transform: Transform::from_scale(scale),
|
||||
..default()
|
||||
});
|
||||
} else if let Some(model) = &state.model {
|
||||
actor.insert(SpatialBundle {
|
||||
transform: Transform::from_scale(scale),
|
||||
..default()
|
||||
});
|
||||
load_asset(model.as_str(), &mut actor, &*asset_server);
|
||||
}
|
||||
}
|
||||
if state.is_vehicle {
|
||||
actor.insert(actor::Vehicle::default());
|
||||
if let Some(name) = &state.name {
|
||||
achievement_tracker.all_vehicles.insert(name.clone());
|
||||
actor.insert(Rotation::from(rotation));
|
||||
|
||||
// Physics Parameters
|
||||
if state.has_physics {
|
||||
actor.insert(RigidBody::Dynamic);
|
||||
actor.insert(LinearVelocity(state.velocity));
|
||||
actor.insert(AngularVelocity(state.angular_momentum));
|
||||
actor.insert(ColliderDensity(state.density));
|
||||
if state.collider_is_mesh {
|
||||
actor.insert(MassPropertiesBundle::new_computed(
|
||||
&Collider::sphere(0.5 * state.model_scale as f64),
|
||||
state.density,
|
||||
));
|
||||
actor.insert(AsyncSceneCollider::new(Some(
|
||||
ComputedCollider::TriMesh, //ComputedCollider::ConvexDecomposition(VHACDParameters::default())
|
||||
)));
|
||||
} else if state.collider_is_one_mesh_of_scene {
|
||||
actor.insert(MassPropertiesBundle::new_computed(
|
||||
&Collider::sphere(0.5 * state.model_scale as f64),
|
||||
state.density,
|
||||
));
|
||||
actor.insert(
|
||||
AsyncSceneCollider::new(None)
|
||||
.with_shape_for_name("Collider", ComputedCollider::TriMesh)
|
||||
.with_layers_for_name("Collider", CollisionLayers::ALL), //.with_density_for_name("Collider", state.density)
|
||||
);
|
||||
actor.insert(NeedsSceneColliderRemoved);
|
||||
} else {
|
||||
actor.insert(state.collider.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
if state.is_vehicle
|
||||
|| state.is_suited
|
||||
|| state.thrust_forward > 0.0
|
||||
|| state.thrust_sideways > 0.0
|
||||
|| state.thrust_back > 0.0
|
||||
|| state.reaction_wheels > 0.0
|
||||
{
|
||||
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()
|
||||
});
|
||||
}
|
||||
if let Some(_) = state.ar_model {
|
||||
actor.insert(hud::AugmentedRealityOverlayBroadcaster);
|
||||
}
|
||||
if state.is_player {
|
||||
actor.with_children(|builder| {
|
||||
builder.spawn((
|
||||
world::DespawnOnPlayerDeath,
|
||||
actor::PlayersFlashLight,
|
||||
SpotLightBundle {
|
||||
transform: Transform {
|
||||
translation: Vec3::new(0.0, 0.0, 1.0),
|
||||
rotation: Quat::from_rotation_y(180f32.to_radians()),
|
||||
..default()
|
||||
},
|
||||
spot_light: SpotLight {
|
||||
intensity: 40_000_000.0, // lumens
|
||||
color: Color::WHITE,
|
||||
shadows_enabled: true,
|
||||
inner_angle: PI32 / 8.0 * 0.85,
|
||||
outer_angle: PI32 / 4.0,
|
||||
range: 2000.0,
|
||||
..default()
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
// 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_sun {
|
||||
let (r, g, b) = nature::star_color_index_to_rgb(0.656);
|
||||
actor.insert(materials.add(StandardMaterial {
|
||||
base_color: Color::rgb(r, g, b) * 13.0,
|
||||
unlit: true,
|
||||
..default()
|
||||
}));
|
||||
actor.insert((NotShadowCaster, NotShadowReceiver));
|
||||
}
|
||||
if state.is_targeted_on_startup {
|
||||
actor.insert(hud::IsTargeted);
|
||||
}
|
||||
if let Some((mindist, id)) = &state.show_only_in_map_at_distance {
|
||||
actor.insert(camera::ShowOnlyInMap {
|
||||
min_distance: *mindist,
|
||||
distance_to_id: id.clone(),
|
||||
});
|
||||
}
|
||||
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::ExperiencesGForce::default());
|
||||
actor.insert(actor::Suit {
|
||||
oxygen: state.oxygen,
|
||||
oxygen_max: nature::OXY_D,
|
||||
integrity: state.suit_integrity,
|
||||
..default()
|
||||
});
|
||||
actor.insert(actor::Battery::default());
|
||||
}
|
||||
if state.is_clickable {
|
||||
actor.insert(hud::IsClickable {
|
||||
name: state.name.clone(),
|
||||
pronoun: state.pronoun.clone(),
|
||||
..default()
|
||||
});
|
||||
}
|
||||
if let Some(value) = state.wants_maxrotation {
|
||||
actor.insert(actor::WantsMaxRotation(value));
|
||||
}
|
||||
if let Some(value) = state.wants_maxvelocity {
|
||||
actor.insert(actor::WantsMaxVelocity(value));
|
||||
}
|
||||
if let Some(color) = state.light_color {
|
||||
actor.insert((
|
||||
PointLight {
|
||||
intensity: state.light_brightness,
|
||||
color,
|
||||
range: 2000.0,
|
||||
shadows_enabled: settings.shadows_pointlights,
|
||||
..default()
|
||||
}
|
||||
},
|
||||
bevy::pbr::CubemapVisibleEntities::default(),
|
||||
bevy::render::primitives::CubemapFrusta::default(),
|
||||
));
|
||||
});
|
||||
}
|
||||
actor_entity = actor.id();
|
||||
}
|
||||
if !state.id.is_empty() {
|
||||
actor.insert(actor::Identifier(state.id.clone()));
|
||||
id2pos.0.insert(state.id.clone(), absolute_pos);
|
||||
}
|
||||
if !state.chat.is_empty() {
|
||||
actor.insert(chat::Talker {
|
||||
actor_id: state.id.clone(),
|
||||
chat_name: state.chat.clone(),
|
||||
name: state.name.clone(),
|
||||
pronoun: state.pronoun.clone(),
|
||||
talking_speed: 1.0,
|
||||
});
|
||||
if let Some(name) = &state.name {
|
||||
achievement_tracker.all_people.insert(name.clone());
|
||||
}
|
||||
}
|
||||
if state.is_vehicle {
|
||||
actor.insert(actor::Vehicle::default());
|
||||
if let Some(name) = &state.name {
|
||||
achievement_tracker.all_vehicles.insert(name.clone());
|
||||
}
|
||||
}
|
||||
if state.is_vehicle
|
||||
|| state.is_suited
|
||||
|| state.thrust_forward > 0.0
|
||||
|| state.thrust_sideways > 0.0
|
||||
|| state.thrust_back > 0.0
|
||||
|| state.reaction_wheels > 0.0
|
||||
{
|
||||
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()
|
||||
});
|
||||
}
|
||||
if let Some(_) = state.ar_model {
|
||||
actor.insert(hud::AugmentedRealityOverlayBroadcaster);
|
||||
}
|
||||
if state.is_player {
|
||||
actor.with_children(|builder| {
|
||||
builder.spawn((
|
||||
world::DespawnOnPlayerDeath,
|
||||
actor::PlayersFlashLight,
|
||||
SpotLightBundle {
|
||||
transform: Transform {
|
||||
translation: Vec3::new(0.0, 0.0, 1.0),
|
||||
rotation: Quat::from_rotation_y(180f32.to_radians()),
|
||||
..default()
|
||||
},
|
||||
spot_light: SpotLight {
|
||||
intensity: 40_000_000.0, // lumens
|
||||
color: Color::WHITE,
|
||||
shadows_enabled: true,
|
||||
inner_angle: PI32 / 8.0 * 0.85,
|
||||
outer_angle: PI32 / 4.0,
|
||||
range: 2000.0,
|
||||
..default()
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
..default()
|
||||
},
|
||||
));
|
||||
});
|
||||
}
|
||||
actor_entity = actor.id();
|
||||
}
|
||||
|
||||
if let Some(ar_asset_name) = &state.ar_model {
|
||||
|
@ -837,7 +837,8 @@ fn spawn_entities(
|
|||
}
|
||||
|
||||
if state.has_ring {
|
||||
let ring_radius = state.model_scale * (nature::JUPITER_RING_RADIUS / nature::JUPITER_RADIUS) as f32;
|
||||
let ring_radius = state.model_scale
|
||||
* (nature::JUPITER_RING_RADIUS / nature::JUPITER_RADIUS) as f32;
|
||||
commands.spawn((
|
||||
world::DespawnOnPlayerDeath,
|
||||
MaterialMeshBundle {
|
||||
|
@ -860,7 +861,9 @@ fn spawn_entities(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn hide_colliders(mut q_mesh: Query<(&mut Visibility, &Name), (Added<Visibility>, With<Handle<Mesh>>)>) {
|
||||
pub fn hide_colliders(
|
||||
mut q_mesh: Query<(&mut Visibility, &Name), (Added<Visibility>, With<Handle<Mesh>>)>,
|
||||
) {
|
||||
for (mut visibility, name) in &mut q_mesh {
|
||||
if name.as_str() == "Collider" {
|
||||
*visibility = Visibility::Hidden;
|
||||
|
@ -871,7 +874,12 @@ pub fn hide_colliders(mut q_mesh: Query<(&mut Visibility, &Name), (Added<Visibil
|
|||
pub fn process_mesh(
|
||||
mut commands: Commands,
|
||||
mut q_mesh: Query<(Entity, &Name, &Parent), Added<Handle<Mesh>>>,
|
||||
q_parents: Query<(Option<&Parent>, Option<&actor::Player>, Option<&NotShadowCaster>, Option<&NotShadowReceiver>)>,
|
||||
q_parents: Query<(
|
||||
Option<&Parent>,
|
||||
Option<&actor::Player>,
|
||||
Option<&NotShadowCaster>,
|
||||
Option<&NotShadowReceiver>,
|
||||
)>,
|
||||
) {
|
||||
// Add "PlayerCollider" component to the player's collider mesh entity
|
||||
for (child_entity, child_name, child_parent) in &mut q_mesh {
|
||||
|
|
|
@ -10,10 +10,10 @@
|
|||
//
|
||||
// Various common functions and constants
|
||||
|
||||
use bevy::prelude::*;
|
||||
use crate::prelude::*;
|
||||
use bevy::prelude::*;
|
||||
|
||||
pub use bevy::math::{DVec3, DQuat};
|
||||
pub use bevy::math::{DQuat, DVec3};
|
||||
pub use std::f32::consts::PI as PI32;
|
||||
pub use std::f64::consts::PI;
|
||||
|
||||
|
@ -77,7 +77,7 @@ pub fn in_shadow(
|
|||
light_source_r: f64,
|
||||
shadow_caster_pos: DVec3,
|
||||
shadow_caster_r: f64,
|
||||
camera_pos: DVec3
|
||||
camera_pos: DVec3,
|
||||
) -> bool {
|
||||
if light_source_r < shadow_caster_r {
|
||||
error!("common::in_shadow only works if light_source_r > shadow_caster_r");
|
||||
|
@ -101,7 +101,9 @@ pub fn in_shadow(
|
|||
let closest_vector = shadow_caster_to_player - projection;
|
||||
|
||||
let distance_between_light_and_caster = (shadow_caster_pos - light_source_pos).length();
|
||||
let max_distance = shadow_caster_r + ((shadow_caster_r - light_source_r) / distance_between_light_and_caster) * projection_length;
|
||||
let max_distance = shadow_caster_r
|
||||
+ ((shadow_caster_r - light_source_r) / distance_between_light_and_caster)
|
||||
* projection_length;
|
||||
|
||||
return closest_vector.length() < max_distance;
|
||||
}
|
||||
|
|
104
src/game.rs
104
src/game.rs
|
@ -11,10 +11,10 @@
|
|||
// This module handles player input, and coordinates interplay between other modules
|
||||
|
||||
use crate::prelude::*;
|
||||
use bevy::prelude::*;
|
||||
use bevy::pbr::ExtendedMaterial;
|
||||
use bevy::prelude::*;
|
||||
use bevy::scene::SceneInstance;
|
||||
use bevy::window::{Window, WindowMode, PrimaryWindow};
|
||||
use bevy::window::{PrimaryWindow, Window, WindowMode};
|
||||
use bevy_xpbd_3d::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
@ -26,21 +26,29 @@ impl Plugin for GamePlugin {
|
|||
app.add_systems(PostUpdate, handle_game_event);
|
||||
app.add_systems(PreUpdate, handle_player_death);
|
||||
app.add_systems(PostUpdate, update_id2pos);
|
||||
app.add_systems(Update, handle_achievement_event.run_if(on_event::<AchievementEvent>()));
|
||||
app.add_systems(
|
||||
Update,
|
||||
handle_achievement_event.run_if(on_event::<AchievementEvent>()),
|
||||
);
|
||||
app.add_systems(Update, check_achievements);
|
||||
app.insert_resource(Id2Pos(HashMap::new()));
|
||||
app.insert_resource(var::AchievementTracker::default());
|
||||
app.insert_resource(AchievementCheckTimer(
|
||||
Timer::from_seconds(1.0, TimerMode::Repeating)));
|
||||
app.insert_resource(AchievementCheckTimer(Timer::from_seconds(
|
||||
1.0,
|
||||
TimerMode::Repeating,
|
||||
)));
|
||||
app.add_event::<PlayerDiesEvent>();
|
||||
app.add_event::<GameEvent>();
|
||||
app.add_event::<AchievementEvent>();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Event)] pub struct PlayerDiesEvent(pub actor::DamageType);
|
||||
#[derive(Resource)] pub struct Id2Pos(pub HashMap<String, DVec3>);
|
||||
#[derive(Resource)] pub struct AchievementCheckTimer(pub Timer);
|
||||
#[derive(Event)]
|
||||
pub struct PlayerDiesEvent(pub actor::DamageType);
|
||||
#[derive(Resource)]
|
||||
pub struct Id2Pos(pub HashMap<String, DVec3>);
|
||||
#[derive(Resource)]
|
||||
pub struct AchievementCheckTimer(pub Timer);
|
||||
|
||||
#[derive(Event)]
|
||||
pub enum AchievementEvent {
|
||||
|
@ -125,7 +133,7 @@ pub fn handle_game_event(
|
|||
let current_state = window.mode != WindowMode::Windowed;
|
||||
window.mode = match turn.to_bool(current_state) {
|
||||
true => opt.window_mode_fullscreen,
|
||||
false => WindowMode::Windowed
|
||||
false => WindowMode::Windowed,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -137,8 +145,8 @@ pub fn handle_game_event(
|
|||
settings.third_person = turn.to_bool(settings.third_person);
|
||||
}
|
||||
GameEvent::SetRotationStabilizer(turn) => {
|
||||
settings.rotation_stabilizer_active
|
||||
= turn.to_bool(settings.rotation_stabilizer_active);
|
||||
settings.rotation_stabilizer_active =
|
||||
turn.to_bool(settings.rotation_stabilizer_active);
|
||||
}
|
||||
GameEvent::SetShadows(turn) => {
|
||||
settings.shadows_sun = turn.to_bool(settings.shadows_sun);
|
||||
|
@ -148,8 +156,11 @@ pub fn handle_game_event(
|
|||
}
|
||||
GameEvent::Achievement(name) => {
|
||||
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Achieve));
|
||||
log.add(format!("Achievement accomplished: {name}!"),
|
||||
"".to_string(), hud::LogLevel::Achievement);
|
||||
log.add(
|
||||
format!("Achievement accomplished: {name}!"),
|
||||
"".to_string(),
|
||||
hud::LogLevel::Achievement,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -234,9 +245,15 @@ fn handle_player_death(
|
|||
|
||||
fn handle_cheats(
|
||||
key_input: Res<ButtonInput<KeyCode>>,
|
||||
mut q_player: Query<(&Transform, &mut Position, &mut LinearVelocity), With<actor::PlayerCamera>>,
|
||||
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>>,
|
||||
q_target: Query<(&Transform, &Position, Option<&LinearVelocity>), (With<hud::IsTargeted>, Without<actor::PlayerCamera>)>,
|
||||
q_target: Query<
|
||||
(&Transform, &Position, Option<&LinearVelocity>),
|
||||
(With<hud::IsTargeted>, Without<actor::PlayerCamera>),
|
||||
>,
|
||||
mut ew_playerdies: EventWriter<PlayerDiesEvent>,
|
||||
mut settings: ResMut<Settings>,
|
||||
id2pos: Res<Id2Pos>,
|
||||
|
@ -266,9 +283,15 @@ fn handle_cheats(
|
|||
gforce.ignore_gforce_seconds = 1.0;
|
||||
v.0 = DVec3::ZERO;
|
||||
}
|
||||
if key_input.pressed(settings.key_cheat_speed) || key_input.pressed(settings.key_cheat_speed_backward) {
|
||||
if key_input.pressed(settings.key_cheat_speed)
|
||||
|| key_input.pressed(settings.key_cheat_speed_backward)
|
||||
{
|
||||
gforce.ignore_gforce_seconds = 1.0;
|
||||
let sign = if key_input.pressed(settings.key_cheat_speed) { 1.0 } else { -1.0 };
|
||||
let sign = if key_input.pressed(settings.key_cheat_speed) {
|
||||
1.0
|
||||
} else {
|
||||
-1.0
|
||||
};
|
||||
let dv = DVec3::from(trans.rotation * Vec3::new(0.0, 0.0, sign * boost));
|
||||
let current_speed = v.0.length();
|
||||
let next_speed = (v.0 + dv).length();
|
||||
|
@ -278,7 +301,8 @@ fn handle_cheats(
|
|||
}
|
||||
if key_input.just_pressed(settings.key_cheat_teleport) {
|
||||
if let Ok((transform, target_pos, target_v)) = q_target.get_single() {
|
||||
let offset: DVec3 = 4.0 * (**pos - **target_pos).normalize() * transform.scale.as_dvec3();
|
||||
let offset: DVec3 =
|
||||
4.0 * (**pos - **target_pos).normalize() * transform.scale.as_dvec3();
|
||||
pos.0 = **target_pos + offset;
|
||||
if let Some(target_v) = target_v {
|
||||
*v = target_v.clone();
|
||||
|
@ -323,10 +347,7 @@ fn handle_cheats(
|
|||
}
|
||||
}
|
||||
|
||||
fn update_id2pos(
|
||||
mut id2pos: ResMut<Id2Pos>,
|
||||
q_id: Query<(&Position, &actor::Identifier)>,
|
||||
) {
|
||||
fn update_id2pos(mut id2pos: ResMut<Id2Pos>, q_id: Query<(&Position, &actor::Identifier)>) {
|
||||
id2pos.0.clear();
|
||||
for (pos, id) in &q_id {
|
||||
id2pos.0.insert(id.0.clone(), pos.0);
|
||||
|
@ -337,7 +358,9 @@ fn debug(
|
|||
settings: Res<var::Settings>,
|
||||
keyboard_input: Res<ButtonInput<KeyCode>>,
|
||||
mut commands: Commands,
|
||||
mut extended_materials: ResMut<Assets<ExtendedMaterial<StandardMaterial, load::AsteroidSurface>>>,
|
||||
mut extended_materials: ResMut<
|
||||
Assets<ExtendedMaterial<StandardMaterial, load::AsteroidSurface>>,
|
||||
>,
|
||||
mut achievement_tracker: ResMut<var::AchievementTracker>,
|
||||
materials: Query<(Entity, Option<&Name>, &Handle<Mesh>)>,
|
||||
) {
|
||||
|
@ -370,7 +393,9 @@ fn handle_achievement_event(
|
|||
}
|
||||
AchievementEvent::InJupitersShadow => {
|
||||
if !tracker.in_jupiters_shadow {
|
||||
ew_game.send(GameEvent::Achievement("Eclipse the sun with Jupiter".into()));
|
||||
ew_game.send(GameEvent::Achievement(
|
||||
"Eclipse the sun with Jupiter".into(),
|
||||
));
|
||||
}
|
||||
tracker.in_jupiters_shadow = true;
|
||||
}
|
||||
|
@ -415,13 +440,32 @@ fn check_achievements(
|
|||
mut ew_achievement: EventWriter<AchievementEvent>,
|
||||
mut timer: ResMut<AchievementCheckTimer>,
|
||||
) {
|
||||
if !timer.0.tick(time.delta()).just_finished() { return; }
|
||||
let pos_player = if let Ok(pos) = q_player.get_single() { pos } else { return; };
|
||||
let pos_sun = if let Some(pos) = id2pos.0.get("sol") { pos } else { return; };
|
||||
let pos_jupiter = if let Some(pos) = id2pos.0.get("jupiter") { pos } else { return; };
|
||||
if !timer.0.tick(time.delta()).just_finished() {
|
||||
return;
|
||||
}
|
||||
let pos_player = if let Ok(pos) = q_player.get_single() {
|
||||
pos
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
let pos_sun = if let Some(pos) = id2pos.0.get("sol") {
|
||||
pos
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
let pos_jupiter = if let Some(pos) = id2pos.0.get("jupiter") {
|
||||
pos
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
let shadowed = in_shadow(*pos_sun, nature::SOL_RADIUS,
|
||||
*pos_jupiter, nature::JUPITER_RADIUS, **pos_player);
|
||||
let shadowed = in_shadow(
|
||||
*pos_sun,
|
||||
nature::SOL_RADIUS,
|
||||
*pos_jupiter,
|
||||
nature::JUPITER_RADIUS,
|
||||
**pos_player,
|
||||
);
|
||||
|
||||
if shadowed {
|
||||
ew_achievement.send(AchievementEvent::InJupitersShadow);
|
||||
|
|
753
src/hud.rs
753
src/hud.rs
File diff suppressed because it is too large
Load diff
23
src/load.rs
23
src/load.rs
|
@ -11,16 +11,18 @@
|
|||
// This module manages asset loading.
|
||||
|
||||
use bevy::ecs::system::EntityCommands;
|
||||
use bevy::render::render_resource::{AsBindGroup, ShaderRef};
|
||||
use bevy::pbr::{ExtendedMaterial, MaterialExtension, OpaqueRendererMethod};
|
||||
use bevy::prelude::*;
|
||||
use bevy::render::render_resource::{AsBindGroup, ShaderRef};
|
||||
|
||||
pub struct LoadPlugin;
|
||||
impl Plugin for LoadPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins(MaterialPlugin::<JupitersRing>::default());
|
||||
app.add_plugins(MaterialPlugin::<SkyBox>::default());
|
||||
app.add_plugins(MaterialPlugin::<ExtendedMaterial<StandardMaterial, AsteroidSurface, >>::default());
|
||||
app.add_plugins(MaterialPlugin::<
|
||||
ExtendedMaterial<StandardMaterial, AsteroidSurface>,
|
||||
>::default());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,19 +57,12 @@ pub fn asset_name_to_path(name: &str) -> &'static str {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn load_asset(
|
||||
name: &str,
|
||||
entity_commands: &mut EntityCommands,
|
||||
asset_server: &AssetServer,
|
||||
) {
|
||||
pub fn load_asset(name: &str, entity_commands: &mut EntityCommands, asset_server: &AssetServer) {
|
||||
entity_commands.insert(load_scene_by_path(asset_name_to_path(name), asset_server));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn load_scene_by_path(
|
||||
path: &str,
|
||||
asset_server: &AssetServer
|
||||
) -> Handle<Scene> {
|
||||
pub fn load_scene_by_path(path: &str, asset_server: &AssetServer) -> Handle<Scene> {
|
||||
let path_string = path.to_string();
|
||||
if let Some(handle) = asset_server.get_handle(&path_string) {
|
||||
handle
|
||||
|
@ -108,7 +103,7 @@ impl Material for SkyBox {
|
|||
#[derive(Asset, Reflect, AsBindGroup, Debug, Clone)]
|
||||
pub struct AsteroidSurface {
|
||||
#[uniform(100)]
|
||||
quantize_steps: u32
|
||||
quantize_steps: u32,
|
||||
}
|
||||
|
||||
impl MaterialExtension for AsteroidSurface {
|
||||
|
@ -135,8 +130,6 @@ impl AsteroidSurface {
|
|||
}
|
||||
impl Default for AsteroidSurface {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
quantize_steps: 3,
|
||||
}
|
||||
Self { quantize_steps: 3 }
|
||||
}
|
||||
}
|
||||
|
|
39
src/main.rs
39
src/main.rs
|
@ -28,18 +28,19 @@ pub mod visual;
|
|||
pub mod world;
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::{actor, audio, camera, chat, cmd, common, game, hud,
|
||||
load, menu, nature, var, visual, world};
|
||||
pub use crate::common::*;
|
||||
pub use crate::var::Settings;
|
||||
pub use crate::load::load_asset;
|
||||
pub use game::{GameEvent, Turn};
|
||||
pub use crate::var::Settings;
|
||||
pub use crate::{
|
||||
actor, audio, camera, chat, cmd, common, game, hud, load, menu, nature, var, visual, world,
|
||||
};
|
||||
pub use game::Turn::Toggle;
|
||||
pub use game::{GameEvent, Turn};
|
||||
}
|
||||
|
||||
use bevy::window::{Window, WindowMode, PrimaryWindow, CursorGrabMode};
|
||||
use bevy::diagnostic::FrameTimeDiagnosticsPlugin;
|
||||
use bevy::prelude::*;
|
||||
use bevy::window::{CursorGrabMode, PrimaryWindow, Window, WindowMode};
|
||||
use std::env;
|
||||
|
||||
const HELP: &str = "./outfly [options]
|
||||
|
@ -68,8 +69,7 @@ fn main() {
|
|||
if arg == "--help" || arg == "-h" {
|
||||
println!("{}", HELP);
|
||||
return;
|
||||
}
|
||||
else if arg == "--version" || arg == "-v" {
|
||||
} else if arg == "--version" || arg == "-v" {
|
||||
let version = option_env!("CARGO_PKG_VERSION").unwrap();
|
||||
let name = option_env!("CARGO_PKG_NAME").unwrap();
|
||||
let homepage = option_env!("CARGO_PKG_HOMEPAGE").unwrap();
|
||||
|
@ -77,28 +77,23 @@ fn main() {
|
|||
println!("License: GNU GPL version 3: https://gnu.org/licenses/gpl.html");
|
||||
println!("{homepage}");
|
||||
return;
|
||||
}
|
||||
else if arg == "--gl" {
|
||||
} else if arg == "--gl" {
|
||||
opt.use_gl = true;
|
||||
}
|
||||
else if arg == "--windowed" {
|
||||
} else if arg == "--windowed" {
|
||||
opt.window_mode_initial = WindowMode::Windowed;
|
||||
}
|
||||
else if arg == "--fs-legacy" {
|
||||
} else if arg == "--fs-legacy" {
|
||||
let mode = WindowMode::Fullscreen;
|
||||
if opt.window_mode_initial == opt.window_mode_fullscreen {
|
||||
opt.window_mode_initial = mode;
|
||||
}
|
||||
opt.window_mode_fullscreen = mode;
|
||||
}
|
||||
else if arg == "--fs-sized" {
|
||||
} else if arg == "--fs-sized" {
|
||||
let mode = WindowMode::SizedFullscreen;
|
||||
if opt.window_mode_initial == opt.window_mode_fullscreen {
|
||||
opt.window_mode_initial = mode;
|
||||
}
|
||||
opt.window_mode_fullscreen = mode;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
println!("{}", HELP);
|
||||
println!("\nERROR: no such option: `{}`", arg);
|
||||
return;
|
||||
|
@ -114,7 +109,7 @@ fn main() {
|
|||
|
||||
#[cfg(feature = "embed_assets")]
|
||||
app.add_plugins(bevy_embedded_assets::EmbeddedAssetPlugin {
|
||||
mode: bevy_embedded_assets::PluginMode::ReplaceDefault
|
||||
mode: bevy_embedded_assets::PluginMode::ReplaceDefault,
|
||||
});
|
||||
|
||||
app.add_plugins(OutFlyPlugin);
|
||||
|
@ -130,9 +125,8 @@ impl Plugin for OutFlyPlugin {
|
|||
app.insert_resource(var::Settings::default());
|
||||
app.insert_resource(var::GameVars::default());
|
||||
app.add_plugins((
|
||||
DefaultPlugins,//.set(ImagePlugin::default_nearest()),
|
||||
DefaultPlugins, //.set(ImagePlugin::default_nearest()),
|
||||
FrameTimeDiagnosticsPlugin,
|
||||
|
||||
actor::ActorPlugin,
|
||||
audio::AudioPlugin,
|
||||
camera::CameraPlugin,
|
||||
|
@ -148,10 +142,7 @@ impl Plugin for OutFlyPlugin {
|
|||
}
|
||||
}
|
||||
|
||||
fn setup(
|
||||
mut windows: Query<&mut Window, With<PrimaryWindow>>,
|
||||
opt: Res<var::CommandLineOptions>,
|
||||
) {
|
||||
fn setup(mut windows: Query<&mut Window, With<PrimaryWindow>>, opt: Res<var::CommandLineOptions>) {
|
||||
for mut window in &mut windows {
|
||||
window.cursor.grab_mode = CursorGrabMode::Locked;
|
||||
window.cursor.visible = false;
|
||||
|
|
340
src/menu.rs
340
src/menu.rs
|
@ -20,28 +20,47 @@ pub struct MenuPlugin;
|
|||
impl Plugin for MenuPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(Startup, setup.after(hud::setup));
|
||||
app.add_systems(PreUpdate, show_deathscreen.run_if(on_event::<DeathScreenEvent>()));
|
||||
app.add_systems(
|
||||
PreUpdate,
|
||||
show_deathscreen.run_if(on_event::<DeathScreenEvent>()),
|
||||
);
|
||||
app.add_systems(Update, handle_deathscreen_input);
|
||||
app.add_systems(PostUpdate, update_menu
|
||||
.after(game::handle_game_event)
|
||||
.run_if(on_event::<UpdateMenuEvent>()));
|
||||
app.add_systems(
|
||||
PostUpdate,
|
||||
update_menu
|
||||
.after(game::handle_game_event)
|
||||
.run_if(on_event::<UpdateMenuEvent>()),
|
||||
);
|
||||
app.add_systems(Update, handle_input.run_if(alive));
|
||||
app.insert_resource(DeathScreenInputDelayTimer(
|
||||
Timer::from_seconds(1.0, TimerMode::Once)));
|
||||
app.insert_resource(DeathScreenInputDelayTimer(Timer::from_seconds(
|
||||
1.0,
|
||||
TimerMode::Once,
|
||||
)));
|
||||
app.insert_resource(MenuState::default());
|
||||
app.add_event::<DeathScreenEvent>();
|
||||
app.add_event::<UpdateMenuEvent>();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource)] pub struct DeathScreenInputDelayTimer(pub Timer);
|
||||
#[derive(Component)] pub struct MenuElement;
|
||||
#[derive(Component)] pub struct MenuTopLevel;
|
||||
#[derive(Component)] pub struct MenuAchievements;
|
||||
#[derive(Component)] pub struct DeathScreenElement;
|
||||
#[derive(Component)] pub struct DeathText;
|
||||
#[derive(Event)] pub struct UpdateMenuEvent;
|
||||
#[derive(Event, PartialEq)] pub enum DeathScreenEvent { Show, Hide }
|
||||
#[derive(Resource)]
|
||||
pub struct DeathScreenInputDelayTimer(pub Timer);
|
||||
#[derive(Component)]
|
||||
pub struct MenuElement;
|
||||
#[derive(Component)]
|
||||
pub struct MenuTopLevel;
|
||||
#[derive(Component)]
|
||||
pub struct MenuAchievements;
|
||||
#[derive(Component)]
|
||||
pub struct DeathScreenElement;
|
||||
#[derive(Component)]
|
||||
pub struct DeathText;
|
||||
#[derive(Event)]
|
||||
pub struct UpdateMenuEvent;
|
||||
#[derive(Event, PartialEq)]
|
||||
pub enum DeathScreenEvent {
|
||||
Show,
|
||||
Hide,
|
||||
}
|
||||
|
||||
pub const MENUDEF: &[(&str, MenuAction)] = &[
|
||||
("", MenuAction::ToggleMap),
|
||||
|
@ -115,33 +134,38 @@ pub fn setup(
|
|||
color: settings.hud_color_death_achievements,
|
||||
..default()
|
||||
};
|
||||
commands.spawn((
|
||||
DeathScreenElement,
|
||||
NodeBundle {
|
||||
style: style_centered(),
|
||||
visibility: Visibility::Hidden,
|
||||
..default()
|
||||
},
|
||||
)).with_children(|builder| {
|
||||
builder.spawn((
|
||||
DeathText,
|
||||
TextBundle {
|
||||
text: Text {
|
||||
sections: vec![
|
||||
TextSection::new("", style_death_poem),
|
||||
TextSection::new("You are dead.\n", style_death),
|
||||
TextSection::new("Cause: ", style_death_subtext.clone()),
|
||||
TextSection::new("Unknown", style_death_subtext),
|
||||
TextSection::new("", style_death_achievements),
|
||||
TextSection::new("\n\n\n\nPress E to begin anew.", style_death_subsubtext),
|
||||
],
|
||||
justify: JustifyText::Center,
|
||||
..default()
|
||||
},
|
||||
commands
|
||||
.spawn((
|
||||
DeathScreenElement,
|
||||
NodeBundle {
|
||||
style: style_centered(),
|
||||
visibility: Visibility::Hidden,
|
||||
..default()
|
||||
},
|
||||
));
|
||||
});
|
||||
))
|
||||
.with_children(|builder| {
|
||||
builder.spawn((
|
||||
DeathText,
|
||||
TextBundle {
|
||||
text: Text {
|
||||
sections: vec![
|
||||
TextSection::new("", style_death_poem),
|
||||
TextSection::new("You are dead.\n", style_death),
|
||||
TextSection::new("Cause: ", style_death_subtext.clone()),
|
||||
TextSection::new("Unknown", style_death_subtext),
|
||||
TextSection::new("", style_death_achievements),
|
||||
TextSection::new(
|
||||
"\n\n\n\nPress E to begin anew.",
|
||||
style_death_subsubtext,
|
||||
),
|
||||
],
|
||||
justify: JustifyText::Center,
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
},
|
||||
));
|
||||
});
|
||||
|
||||
let style_menu = TextStyle {
|
||||
font: font_handle.clone(),
|
||||
|
@ -150,9 +174,11 @@ pub fn setup(
|
|||
..default()
|
||||
};
|
||||
|
||||
let sections: Vec<TextSection> = Vec::from_iter(MENUDEF.iter().map(|(label, _)|
|
||||
TextSection::new(label.to_string() + "\n", style_menu.clone())
|
||||
));
|
||||
let sections: Vec<TextSection> = Vec::from_iter(
|
||||
MENUDEF
|
||||
.iter()
|
||||
.map(|(label, _)| TextSection::new(label.to_string() + "\n", style_menu.clone())),
|
||||
);
|
||||
|
||||
commands.spawn((
|
||||
MenuElement,
|
||||
|
@ -164,32 +190,34 @@ pub fn setup(
|
|||
},
|
||||
));
|
||||
|
||||
commands.spawn((
|
||||
MenuElement,
|
||||
NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Percent(100.0),
|
||||
height: Val::Percent(100.0),
|
||||
align_items: AlignItems::Center,
|
||||
justify_content: JustifyContent::SpaceAround,
|
||||
..default()
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
..default()
|
||||
},
|
||||
)).with_children(|builder| {
|
||||
builder.spawn((
|
||||
MenuTopLevel,
|
||||
TextBundle {
|
||||
text: Text {
|
||||
sections,
|
||||
justify: JustifyText::Center,
|
||||
commands
|
||||
.spawn((
|
||||
MenuElement,
|
||||
NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Percent(100.0),
|
||||
height: Val::Percent(100.0),
|
||||
align_items: AlignItems::Center,
|
||||
justify_content: JustifyContent::SpaceAround,
|
||||
..default()
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
..default()
|
||||
},
|
||||
));
|
||||
});
|
||||
))
|
||||
.with_children(|builder| {
|
||||
builder.spawn((
|
||||
MenuTopLevel,
|
||||
TextBundle {
|
||||
text: Text {
|
||||
sections,
|
||||
justify: JustifyText::Center,
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
},
|
||||
));
|
||||
});
|
||||
|
||||
let style_achievement_header = TextStyle {
|
||||
font: font_handle.clone(),
|
||||
|
@ -205,40 +233,43 @@ pub fn setup(
|
|||
};
|
||||
let achievement_count = achievement_tracker.to_bool_vec().len();
|
||||
|
||||
commands.spawn((
|
||||
MenuElement,
|
||||
NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Percent(100.0),
|
||||
height: Val::Percent(100.0),
|
||||
left: Val::Percent(2.0),
|
||||
top: Val::Percent(2.0),
|
||||
align_items: AlignItems::Start,
|
||||
justify_content: JustifyContent::Start,
|
||||
..default()
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
..default()
|
||||
},
|
||||
)).with_children(|builder| {
|
||||
let mut sections = vec![
|
||||
TextSection::new("Achievements\n", style_achievement_header.clone())
|
||||
];
|
||||
sections.extend(Vec::from_iter((0..achievement_count).map(|_|
|
||||
TextSection::new("", style_achievement.clone())
|
||||
)));
|
||||
builder.spawn((
|
||||
MenuAchievements,
|
||||
TextBundle {
|
||||
text: Text {
|
||||
sections,
|
||||
justify: JustifyText::Left,
|
||||
commands
|
||||
.spawn((
|
||||
MenuElement,
|
||||
NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Percent(100.0),
|
||||
height: Val::Percent(100.0),
|
||||
left: Val::Percent(2.0),
|
||||
top: Val::Percent(2.0),
|
||||
align_items: AlignItems::Start,
|
||||
justify_content: JustifyContent::Start,
|
||||
..default()
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
..default()
|
||||
},
|
||||
));
|
||||
});
|
||||
))
|
||||
.with_children(|builder| {
|
||||
let mut sections = vec![TextSection::new(
|
||||
"Achievements\n",
|
||||
style_achievement_header.clone(),
|
||||
)];
|
||||
sections.extend(Vec::from_iter(
|
||||
(0..achievement_count).map(|_| TextSection::new("", style_achievement.clone())),
|
||||
));
|
||||
builder.spawn((
|
||||
MenuAchievements,
|
||||
TextBundle {
|
||||
text: Text {
|
||||
sections,
|
||||
justify: JustifyText::Left,
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
},
|
||||
));
|
||||
});
|
||||
|
||||
let keybindings = include_str!("data/keybindings.in");
|
||||
let style_keybindings = TextStyle {
|
||||
|
@ -248,36 +279,36 @@ pub fn setup(
|
|||
..default()
|
||||
};
|
||||
|
||||
commands.spawn((
|
||||
MenuElement,
|
||||
NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Percent(96.0),
|
||||
height: Val::Percent(96.0),
|
||||
left: Val::Percent(2.0),
|
||||
top: Val::Percent(2.0),
|
||||
align_items: AlignItems::Start,
|
||||
justify_content: JustifyContent::End,
|
||||
commands
|
||||
.spawn((
|
||||
MenuElement,
|
||||
NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Percent(96.0),
|
||||
height: Val::Percent(96.0),
|
||||
left: Val::Percent(2.0),
|
||||
top: Val::Percent(2.0),
|
||||
align_items: AlignItems::Start,
|
||||
justify_content: JustifyContent::End,
|
||||
..default()
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
..default()
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
..default()
|
||||
},
|
||||
)).with_children(|builder| {
|
||||
builder.spawn((
|
||||
TextBundle {
|
||||
))
|
||||
.with_children(|builder| {
|
||||
builder.spawn((TextBundle {
|
||||
text: Text {
|
||||
sections: vec![
|
||||
TextSection::new("Controls\n", style_achievement_header),
|
||||
TextSection::new(keybindings, style_keybindings)
|
||||
TextSection::new(keybindings, style_keybindings),
|
||||
],
|
||||
justify: JustifyText::Right,
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
},
|
||||
));
|
||||
});
|
||||
},));
|
||||
});
|
||||
|
||||
let style_version = TextStyle {
|
||||
font: font_handle.clone(),
|
||||
|
@ -286,36 +317,36 @@ pub fn setup(
|
|||
..default()
|
||||
};
|
||||
|
||||
commands.spawn((
|
||||
MenuElement,
|
||||
NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Percent(96.0),
|
||||
height: Val::Percent(96.0),
|
||||
left: Val::Percent(2.0),
|
||||
top: Val::Percent(2.0),
|
||||
align_items: AlignItems::End,
|
||||
justify_content: JustifyContent::End,
|
||||
commands
|
||||
.spawn((
|
||||
MenuElement,
|
||||
NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Percent(96.0),
|
||||
height: Val::Percent(96.0),
|
||||
left: Val::Percent(2.0),
|
||||
top: Val::Percent(2.0),
|
||||
align_items: AlignItems::End,
|
||||
justify_content: JustifyContent::End,
|
||||
..default()
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
..default()
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
..default()
|
||||
},
|
||||
)).with_children(|builder| {
|
||||
builder.spawn((
|
||||
TextBundle {
|
||||
))
|
||||
.with_children(|builder| {
|
||||
builder.spawn((TextBundle {
|
||||
text: Text {
|
||||
sections: vec![
|
||||
TextSection::new(format!("{} {}", GAME_NAME,
|
||||
settings.version.as_str()), style_version),
|
||||
],
|
||||
sections: vec![TextSection::new(
|
||||
format!("{} {}", GAME_NAME, settings.version.as_str()),
|
||||
style_version,
|
||||
)],
|
||||
justify: JustifyText::Right,
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
},
|
||||
));
|
||||
});
|
||||
},));
|
||||
});
|
||||
}
|
||||
|
||||
pub fn show_deathscreen(
|
||||
|
@ -359,7 +390,10 @@ pub fn show_deathscreen(
|
|||
} else {
|
||||
ew_respawnaudiosinks.send(audio::RespawnSinksEvent);
|
||||
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::WakeUp));
|
||||
ew_effect.send(visual::SpawnEffectEvent { class: visual::Effects::FadeIn(Color::BLACK), duration: 0.3 });
|
||||
ew_effect.send(visual::SpawnEffectEvent {
|
||||
class: visual::Effects::FadeIn(Color::BLACK),
|
||||
duration: 0.3,
|
||||
});
|
||||
ew_respawn.send(world::RespawnEvent);
|
||||
}
|
||||
}
|
||||
|
@ -472,7 +506,7 @@ pub fn handle_input(
|
|||
let last_menu_entry = MENUDEF.len() - 1;
|
||||
|
||||
if keyboard_input.just_pressed(settings.key_menu)
|
||||
|| keyboard_input.just_pressed(settings.key_vehicle) && settings.menu_active
|
||||
|| keyboard_input.just_pressed(settings.key_vehicle) && settings.menu_active
|
||||
{
|
||||
ew_game.send(GameEvent::SetMenu(Toggle));
|
||||
}
|
||||
|
@ -480,8 +514,8 @@ pub fn handle_input(
|
|||
return;
|
||||
}
|
||||
if keyboard_input.just_pressed(settings.key_forward)
|
||||
|| keyboard_input.just_pressed(settings.key_mouseup)
|
||||
|| keyboard_input.just_pressed(KeyCode::ArrowUp)
|
||||
|| keyboard_input.just_pressed(settings.key_mouseup)
|
||||
|| keyboard_input.just_pressed(KeyCode::ArrowUp)
|
||||
{
|
||||
menustate.cursor = if menustate.cursor == 0 {
|
||||
last_menu_entry
|
||||
|
@ -492,8 +526,8 @@ pub fn handle_input(
|
|||
ew_updatemenu.send(UpdateMenuEvent);
|
||||
}
|
||||
if keyboard_input.just_pressed(settings.key_back)
|
||||
|| keyboard_input.just_pressed(settings.key_mousedown)
|
||||
|| keyboard_input.just_pressed(KeyCode::ArrowDown)
|
||||
|| keyboard_input.just_pressed(settings.key_mousedown)
|
||||
|| keyboard_input.just_pressed(KeyCode::ArrowDown)
|
||||
{
|
||||
menustate.cursor = if menustate.cursor == last_menu_entry {
|
||||
0
|
||||
|
@ -504,7 +538,7 @@ pub fn handle_input(
|
|||
ew_updatemenu.send(UpdateMenuEvent);
|
||||
}
|
||||
if keyboard_input.just_pressed(settings.key_interact)
|
||||
|| keyboard_input.just_pressed(KeyCode::Enter)
|
||||
|| keyboard_input.just_pressed(KeyCode::Enter)
|
||||
{
|
||||
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Click));
|
||||
match MENUDEF[menustate.cursor].1 {
|
||||
|
@ -512,37 +546,37 @@ pub fn handle_input(
|
|||
ew_game.send(GameEvent::SetMap(Toggle));
|
||||
ew_game.send(GameEvent::SetMenu(Turn::Off));
|
||||
ew_updatemenu.send(UpdateMenuEvent);
|
||||
},
|
||||
}
|
||||
MenuAction::ToggleAR => {
|
||||
ew_game.send(GameEvent::SetAR(Toggle));
|
||||
ew_updatemenu.send(UpdateMenuEvent);
|
||||
},
|
||||
}
|
||||
MenuAction::ToggleMusic => {
|
||||
ew_game.send(GameEvent::SetMusic(Toggle));
|
||||
ew_updatemenu.send(UpdateMenuEvent);
|
||||
},
|
||||
}
|
||||
MenuAction::ToggleSound => {
|
||||
ew_game.send(GameEvent::SetSound(Toggle));
|
||||
ew_updatemenu.send(UpdateMenuEvent);
|
||||
},
|
||||
}
|
||||
MenuAction::ToggleCamera => {
|
||||
ew_game.send(GameEvent::SetThirdPerson(Toggle));
|
||||
ew_updatemenu.send(UpdateMenuEvent);
|
||||
},
|
||||
}
|
||||
MenuAction::ToggleFullscreen => {
|
||||
ew_game.send(GameEvent::SetFullscreen(Toggle));
|
||||
},
|
||||
}
|
||||
MenuAction::ToggleShadows => {
|
||||
ew_game.send(GameEvent::SetShadows(Toggle));
|
||||
ew_updatemenu.send(UpdateMenuEvent);
|
||||
},
|
||||
}
|
||||
MenuAction::Restart => {
|
||||
settings.god_mode = false;
|
||||
ew_playerdies.send(game::PlayerDiesEvent(actor::DamageType::Depressurization));
|
||||
},
|
||||
}
|
||||
MenuAction::Quit => {
|
||||
app_exit_events.send(bevy::app::AppExit);
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,8 +24,8 @@ pub const EARTH_GRAVITY: f32 = 9.81;
|
|||
pub const C: f64 = 299792458.0; // m/s
|
||||
pub const G: f64 = 6.6743015e-11; // Gravitational constant in Nm²/kg²
|
||||
|
||||
pub const SOL_RADIUS: f64 = 696_300_000.0;
|
||||
pub const JUPITER_RADIUS: f64 = 71_492_000.0;
|
||||
pub const SOL_RADIUS: f64 = 696_300_000.0;
|
||||
pub const JUPITER_RADIUS: f64 = 71_492_000.0;
|
||||
pub const JUPITER_RING_RADIUS: f64 = 229_000_000.0;
|
||||
|
||||
pub const SOL_MASS: f64 = 1.9885e30;
|
||||
|
@ -35,7 +35,8 @@ pub const JUPITER_MASS: f64 = 1.8982e27;
|
|||
pub const STARS: &[(f32, f32, f32, f32, f32, f32, &str)] = &include!("data/stars.in");
|
||||
|
||||
pub fn star_color_index_to_rgb(color_index: f32) -> (f32, f32, f32) {
|
||||
let temperature = 4600.0 * ((1.0 / (0.92 * color_index + 1.7)) + (1.0 / (0.92 * color_index + 0.62)));
|
||||
let temperature =
|
||||
4600.0 * ((1.0 / (0.92 * color_index + 1.7)) + (1.0 / (0.92 * color_index + 0.62)));
|
||||
|
||||
let (red, green, blue) = if temperature <= 6600.0 {
|
||||
let red = 255.0;
|
||||
|
@ -55,7 +56,7 @@ pub fn star_color_index_to_rgb(color_index: f32) -> (f32, f32, f32) {
|
|||
|
||||
let clamp = |x: f32| -> f32 { (x / 255.0).max(0.0).min(1.0) };
|
||||
|
||||
return (clamp(red), clamp(green), clamp(blue))
|
||||
return (clamp(red), clamp(green), clamp(blue));
|
||||
}
|
||||
|
||||
fn smooth_edge(start: f32, end: f32, value: f32) -> f32 {
|
||||
|
@ -89,10 +90,19 @@ pub fn ring_density(radius: f32) -> f32 {
|
|||
density = halo_brightness * smooth_edge(halo_inner, halo_outer, radius);
|
||||
} else if radius >= main_inner && radius <= main_outer {
|
||||
let mut metis_notch_effect: f32 = 1.0;
|
||||
if radius > metis_notch_center - metis_notch_width * 0.5 && radius < metis_notch_center + metis_notch_width * 0.5 {
|
||||
metis_notch_effect = 0.8 * (1.0 - smooth_edge(metis_notch_center - metis_notch_width * 0.5, metis_notch_center + metis_notch_width * 0.5, radius));
|
||||
if radius > metis_notch_center - metis_notch_width * 0.5
|
||||
&& radius < metis_notch_center + metis_notch_width * 0.5
|
||||
{
|
||||
metis_notch_effect = 0.8
|
||||
* (1.0
|
||||
- smooth_edge(
|
||||
metis_notch_center - metis_notch_width * 0.5,
|
||||
metis_notch_center + metis_notch_width * 0.5,
|
||||
radius,
|
||||
));
|
||||
}
|
||||
density = main_brightness * metis_notch_effect * smooth_edge(main_inner, main_outer, radius);
|
||||
density =
|
||||
main_brightness * metis_notch_effect * smooth_edge(main_inner, main_outer, radius);
|
||||
} else {
|
||||
if radius >= amalthea_inner && radius <= amalthea_outer {
|
||||
density = almathea_brightness * smooth_edge(amalthea_inner, amalthea_outer, radius);
|
||||
|
@ -131,8 +141,7 @@ pub fn readable_speed(speed: f64) -> String {
|
|||
if abs > C * 0.0005 {
|
||||
let lightyears = abs / C;
|
||||
return format!("{lightyears:.4} c");
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
let kmh = abs * 1.0e-3 * 3600.0;
|
||||
return format!("{kmh:.0} km/h");
|
||||
}
|
||||
|
|
64
src/var.rs
64
src/var.rs
|
@ -12,13 +12,13 @@
|
|||
// "if"-conditions in chats.
|
||||
|
||||
use crate::prelude::*;
|
||||
use bevy::window::WindowMode;
|
||||
use bevy::prelude::*;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use bevy::window::WindowMode;
|
||||
use serde::Deserialize;
|
||||
use toml_edit::DocumentMut;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use toml_edit::DocumentMut;
|
||||
|
||||
pub const SCOPE_SEPARATOR: &str = "$";
|
||||
|
||||
|
@ -379,28 +379,29 @@ impl AchievementTracker {
|
|||
(self.ride_every_vehicle, "ride every vehicle".into()),
|
||||
(self.talk_to_everyone, "talk to everyone".into()),
|
||||
(self.find_earth, "find Earth".into()),
|
||||
(self.in_jupiters_shadow, "eclipse the Sun with Jupiter".into()),
|
||||
(
|
||||
self.in_jupiters_shadow,
|
||||
"eclipse the Sun with Jupiter".into(),
|
||||
),
|
||||
]
|
||||
}
|
||||
pub fn to_summary(&self) -> String {
|
||||
let list = self.to_overview();
|
||||
let count = list.iter().filter(|(achieved, _)| *achieved).count();
|
||||
if count == 0 {
|
||||
return "".to_string()
|
||||
return "".to_string();
|
||||
}
|
||||
let mut summary = "\n\n\nYou managed to ".to_string();
|
||||
for (i, (_, text)) in list.iter().filter(|(achieved, _)| *achieved).enumerate() {
|
||||
summary += text.as_str();
|
||||
if i + 2 == count {
|
||||
summary += ", and ";
|
||||
}
|
||||
else if i + 1 == count {
|
||||
} else if i + 1 == count {
|
||||
summary += " before you perished.";
|
||||
if count == list.len() {
|
||||
summary += "\nA truly astounding achievement, a glimmer in the void, before it all fades, into nothingness.";
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
summary += ", ";
|
||||
}
|
||||
}
|
||||
|
@ -439,7 +440,9 @@ impl Preferences {
|
|||
}
|
||||
|
||||
fn file_is_readable(file_path: &str) -> bool {
|
||||
fs::metadata(file_path).map(|metadata| metadata.is_file()).unwrap_or(false)
|
||||
fs::metadata(file_path)
|
||||
.map(|metadata| metadata.is_file())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn get_prefs_path() -> Option<String> {
|
||||
|
@ -479,24 +482,22 @@ pub fn load_prefs() -> Preferences {
|
|||
}
|
||||
};
|
||||
match toml.parse::<DocumentMut>() {
|
||||
Ok(doc) => {
|
||||
match toml_edit::de::from_document::<Preferences>(doc) {
|
||||
Ok(mut pref) => {
|
||||
if let Some(path) = &path {
|
||||
info!("Loaded preference file from {path}");
|
||||
} else {
|
||||
info!("Loaded preferences from internal defaults");
|
||||
}
|
||||
pref.source_file = path;
|
||||
dbg!(&pref);
|
||||
return pref;
|
||||
}
|
||||
Err(error) => {
|
||||
error!("Failed to read preference line: {error}");
|
||||
return Preferences::default();
|
||||
Ok(doc) => match toml_edit::de::from_document::<Preferences>(doc) {
|
||||
Ok(mut pref) => {
|
||||
if let Some(path) = &path {
|
||||
info!("Loaded preference file from {path}");
|
||||
} else {
|
||||
info!("Loaded preferences from internal defaults");
|
||||
}
|
||||
pref.source_file = path;
|
||||
dbg!(&pref);
|
||||
return pref;
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
error!("Failed to read preference line: {error}");
|
||||
return Preferences::default();
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
error!("Failed to open preferences: {error}");
|
||||
return Preferences::default();
|
||||
|
@ -511,9 +512,7 @@ pub struct GameVars {
|
|||
|
||||
impl Default for GameVars {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
db: HashMap::new(),
|
||||
}
|
||||
Self { db: HashMap::new() }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -573,8 +572,7 @@ impl GameVars {
|
|||
if scope_part.is_empty() {
|
||||
// we got a key like "$foo", just prefix the fallback scope
|
||||
fallback_scope.to_string() + key
|
||||
}
|
||||
else {
|
||||
} 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()
|
||||
|
@ -609,11 +607,9 @@ impl GameVars {
|
|||
let part = Self::normalize_varname(scope, part);
|
||||
let value_bool = self.getb(part.as_str());
|
||||
return value_bool ^ negate;
|
||||
}
|
||||
else {
|
||||
} 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.
|
||||
|
|
|
@ -10,15 +10,18 @@
|
|||
//
|
||||
// This module manages visual effects.
|
||||
|
||||
use bevy::prelude::*;
|
||||
use crate::prelude::*;
|
||||
use bevy::prelude::*;
|
||||
|
||||
pub struct VisualPlugin;
|
||||
|
||||
impl Plugin for VisualPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(Startup, setup.after(menu::setup).after(hud::setup));
|
||||
app.add_systems(Startup, spawn_effects.after(setup).after(camera::setup_camera));
|
||||
app.add_systems(
|
||||
Startup,
|
||||
spawn_effects.after(setup).after(camera::setup_camera),
|
||||
);
|
||||
app.add_systems(Update, spawn_effects);
|
||||
app.add_systems(Update, update_fadein);
|
||||
app.add_systems(Update, update_fadeout);
|
||||
|
@ -38,8 +41,10 @@ pub enum Effects {
|
|||
// Blackout disabled for now
|
||||
//#[derive(Component)] pub struct BlackOutOverlay;
|
||||
|
||||
#[derive(Component)] pub struct FadeIn;
|
||||
#[derive(Component)] pub struct FadeOut;
|
||||
#[derive(Component)]
|
||||
pub struct FadeIn;
|
||||
#[derive(Component)]
|
||||
pub struct FadeOut;
|
||||
#[derive(Component)]
|
||||
pub struct Effect {
|
||||
pub class: Effects,
|
||||
|
@ -52,29 +57,29 @@ pub struct SpawnEffectEvent {
|
|||
pub duration: f64,
|
||||
}
|
||||
|
||||
pub fn setup(
|
||||
settings: Res<var::Settings>,
|
||||
mut ew_effect: EventWriter<SpawnEffectEvent>,
|
||||
) {
|
||||
pub fn setup(settings: Res<var::Settings>, mut ew_effect: EventWriter<SpawnEffectEvent>) {
|
||||
if !settings.dev_mode {
|
||||
ew_effect.send(SpawnEffectEvent { class: Effects::FadeIn(Color::BLACK), duration: 4.0 });
|
||||
ew_effect.send(SpawnEffectEvent {
|
||||
class: Effects::FadeIn(Color::BLACK),
|
||||
duration: 4.0,
|
||||
});
|
||||
}
|
||||
// Blackout disabled for now
|
||||
// commands.spawn((
|
||||
// BlackOutOverlay,
|
||||
// NodeBundle {
|
||||
// style: Style {
|
||||
// width: Val::Vw(100.0),
|
||||
// height: Val::Vh(100.0),
|
||||
// position_type: PositionType::Absolute,
|
||||
// top: Val::Px(0.0),
|
||||
// left: Val::Px(0.0),
|
||||
// ..default()
|
||||
// },
|
||||
// background_color: Color::BLACK.into(),
|
||||
// ..default()
|
||||
// },
|
||||
// ));
|
||||
// Blackout disabled for now
|
||||
// commands.spawn((
|
||||
// BlackOutOverlay,
|
||||
// NodeBundle {
|
||||
// style: Style {
|
||||
// width: Val::Vw(100.0),
|
||||
// height: Val::Vh(100.0),
|
||||
// position_type: PositionType::Absolute,
|
||||
// top: Val::Px(0.0),
|
||||
// left: Val::Px(0.0),
|
||||
// ..default()
|
||||
// },
|
||||
// background_color: Color::BLACK.into(),
|
||||
// ..default()
|
||||
// },
|
||||
// ));
|
||||
}
|
||||
|
||||
pub fn spawn_effects(
|
||||
|
@ -99,7 +104,7 @@ pub fn spawn_effects(
|
|||
..default()
|
||||
},
|
||||
));
|
||||
},
|
||||
}
|
||||
Effects::FadeOut(color) => {
|
||||
commands.spawn((
|
||||
Effect {
|
||||
|
@ -114,8 +119,7 @@ pub fn spawn_effects(
|
|||
..default()
|
||||
},
|
||||
));
|
||||
},
|
||||
//_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
79
src/world.rs
79
src/world.rs
|
@ -11,13 +11,13 @@
|
|||
// This module populates the world with stars and asteroids.
|
||||
|
||||
use crate::prelude::*;
|
||||
use bevy::prelude::*;
|
||||
use bevy::math::I64Vec3;
|
||||
use bevy::scene::{InstanceId, SceneInstance};
|
||||
use bevy::prelude::*;
|
||||
use bevy::render::mesh::Indices;
|
||||
use bevy::scene::{InstanceId, SceneInstance};
|
||||
use bevy_xpbd_3d::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
use fastrand;
|
||||
use std::collections::HashMap;
|
||||
|
||||
const ASTEROID_UPDATE_INTERVAL: f32 = 0.1; // seconds
|
||||
const ASTEROID_SIZE_FACTOR: f32 = 10.0;
|
||||
|
@ -42,20 +42,28 @@ impl Plugin for WorldPlugin {
|
|||
app.add_plugins(PhysicsPlugins::default());
|
||||
//app.add_plugins(PhysicsDebugPlugin::default());
|
||||
app.insert_resource(Gravity(DVec3::splat(0.0)));
|
||||
app.insert_resource(AsteroidUpdateTimer(
|
||||
Timer::from_seconds(ASTEROID_UPDATE_INTERVAL, TimerMode::Repeating)));
|
||||
app.insert_resource(AsteroidUpdateTimer(Timer::from_seconds(
|
||||
ASTEROID_UPDATE_INTERVAL,
|
||||
TimerMode::Repeating,
|
||||
)));
|
||||
app.insert_resource(ActiveAsteroids(HashMap::new()));
|
||||
app.add_event::<DespawnAsteroidEvent>();
|
||||
app.add_event::<RespawnEvent>();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource)] struct AsteroidUpdateTimer(Timer);
|
||||
#[derive(Resource)] pub struct ActiveAsteroids(pub HashMap<I64Vec3, AsteroidData>);
|
||||
#[derive(Component)] struct Asteroid;
|
||||
#[derive(Component)] pub struct Star;
|
||||
#[derive(Component)] pub struct DespawnOnPlayerDeath;
|
||||
#[derive(Event)] pub struct RespawnEvent;
|
||||
#[derive(Resource)]
|
||||
struct AsteroidUpdateTimer(Timer);
|
||||
#[derive(Resource)]
|
||||
pub struct ActiveAsteroids(pub HashMap<I64Vec3, AsteroidData>);
|
||||
#[derive(Component)]
|
||||
struct Asteroid;
|
||||
#[derive(Component)]
|
||||
pub struct Star;
|
||||
#[derive(Component)]
|
||||
pub struct DespawnOnPlayerDeath;
|
||||
#[derive(Event)]
|
||||
pub struct RespawnEvent;
|
||||
|
||||
pub struct AsteroidData {
|
||||
entity: Entity,
|
||||
|
@ -93,17 +101,15 @@ pub fn setup(
|
|||
let scale_factor = 1e-4 * pos_render.length() as f32; // from experimentation
|
||||
|
||||
let mag = mag.min(6.0);
|
||||
let scale_size = {|mag: f32|
|
||||
scale_factor * (0.230299 * mag * mag - 3.09013 * mag + 15.1782)
|
||||
};
|
||||
let scale_size =
|
||||
{ |mag: f32| scale_factor * (0.230299 * mag * mag - 3.09013 * mag + 15.1782) };
|
||||
let scale = scale_size(mag);
|
||||
|
||||
let scale_color = {|color: f32|
|
||||
1.2 * color * (0.0659663 * mag * mag - 1.09862 * mag + 4.3)
|
||||
};
|
||||
let scale_color =
|
||||
{ |color: f32| 1.2 * color * (0.0659663 * mag * mag - 1.09862 * mag + 4.3) };
|
||||
//let scale = translation.length().powf(0.84);
|
||||
//pos_render.length().powf(0.64)
|
||||
//(radius as f64 * nature::SOL_RADIUS).powf(0.02) as f32 *
|
||||
//pos_render.length().powf(0.64)
|
||||
//(radius as f64 * nature::SOL_RADIUS).powf(0.02) as f32 *
|
||||
|
||||
let star_color_handle = materials.add(StandardMaterial {
|
||||
base_color: Color::rgb(scale_color(r), scale_color(g), scale_color(b)),
|
||||
|
@ -176,8 +182,8 @@ fn spawn_despawn_asteroids(
|
|||
id2pos: Res<game::Id2Pos>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
if !timer.0.tick(time.delta()).just_finished() || q_player.is_empty() {
|
||||
//if q_player.is_empty() {
|
||||
if !timer.0.tick(time.delta()).just_finished() || q_player.is_empty() {
|
||||
//if q_player.is_empty() {
|
||||
return;
|
||||
}
|
||||
let jupiter_pos = if let Some(jupiter_pos) = id2pos.0.get(&"jupiter".to_string()) {
|
||||
|
@ -211,9 +217,12 @@ fn spawn_despawn_asteroids(
|
|||
let z_min = player_cell.z - stepmax;
|
||||
let z_max = player_cell.z + stepmax;
|
||||
for (origin, asteroid) in db.0.iter() {
|
||||
if origin.x < x_min || origin.x > x_max
|
||||
|| origin.y < y_min || origin.y > y_max
|
||||
|| origin.z < z_min || origin.z > z_max
|
||||
if origin.x < x_min
|
||||
|| origin.x > x_max
|
||||
|| origin.y < y_min
|
||||
|| origin.y > y_max
|
||||
|| origin.z < z_min
|
||||
|| origin.z > z_max
|
||||
{
|
||||
if let Ok((pos, sceneinstance)) = q_asteroid.get(asteroid.entity) {
|
||||
if pos.0.distance(player.0) > 1000.0 {
|
||||
|
@ -288,11 +297,12 @@ fn spawn_despawn_asteroids(
|
|||
|
||||
//let max_viewdist = ASTEROID_VIEW_RADIUS / ASTEROID_SPAWN_STEP;
|
||||
let wobble = ASTEROID_SPAWN_STEP * 0.5;
|
||||
let pos = jupiter_pos + DVec3::new(
|
||||
origin.x as f64 * ASTEROID_SPAWN_STEP + wobble * rand_x * 2.0 - 1.0,
|
||||
origin.y as f64 * ASTEROID_SPAWN_STEP + wobble * rand_y * 2.0 - 1.0,
|
||||
origin.z as f64 * ASTEROID_SPAWN_STEP + wobble * rand_z * 2.0 - 1.0,
|
||||
);
|
||||
let pos = jupiter_pos
|
||||
+ DVec3::new(
|
||||
origin.x as f64 * ASTEROID_SPAWN_STEP + wobble * rand_x * 2.0 - 1.0,
|
||||
origin.y as f64 * ASTEROID_SPAWN_STEP + wobble * rand_y * 2.0 - 1.0,
|
||||
origin.z as f64 * ASTEROID_SPAWN_STEP + wobble * rand_z * 2.0 - 1.0,
|
||||
);
|
||||
|
||||
// Spawn
|
||||
let mut entity_commands = commands.spawn((
|
||||
|
@ -322,10 +332,13 @@ fn spawn_despawn_asteroids(
|
|||
..default()
|
||||
});
|
||||
load_asset(model, &mut entity_commands, &*asset_server);
|
||||
db.0.insert(origin, AsteroidData {
|
||||
entity: entity_commands.id(),
|
||||
//viewdistance: 99999999.0,
|
||||
});
|
||||
db.0.insert(
|
||||
origin,
|
||||
AsteroidData {
|
||||
entity: entity_commands.id(),
|
||||
//viewdistance: 99999999.0,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue