Compare commits

..

No commits in common. "838b8b7fd6908e7198d667f1233bc7f851801662" and "6d12033e2342a4d04c18e4c1558cd0efaadf7d66" have entirely different histories.

25 changed files with 15782 additions and 16301 deletions

2
.gitignore vendored
View file

@ -3,4 +3,4 @@ assets/tmp
assets/external
*.blend1
extra
outfly_v*
outfly_*.zip

118
Cargo.lock generated
View file

@ -72,6 +72,12 @@ dependencies = [
"winit",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
version = "0.8.11"
@ -1482,6 +1488,15 @@ dependencies = [
"windows 0.54.0",
]
[[package]]
name = "crc32fast"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.12"
@ -1585,19 +1600,6 @@ version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
[[package]]
name = "embed-resource"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e62abb876c07e4754fae5c14cafa77937841f01740637e17d78dc04352f32a5e"
dependencies = [
"cc",
"rustc_version",
"toml",
"vswhom",
"winreg",
]
[[package]]
name = "encase"
version = "0.7.0"
@ -1718,6 +1720,15 @@ version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984"
[[package]]
name = "fdeflate"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645"
dependencies = [
"simd-adler32",
]
[[package]]
name = "file-id"
version = "0.2.1"
@ -1745,6 +1756,16 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flate2"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e"
dependencies = [
"crc32fast",
"miniz_oxide",
]
[[package]]
name = "foreign-types"
version = "0.5.0"
@ -2065,6 +2086,7 @@ dependencies = [
"color_quant",
"jpeg-decoder",
"num-traits",
"png",
]
[[package]]
@ -2371,6 +2393,16 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
dependencies = [
"adler",
"simd-adler32",
]
[[package]]
name = "mio"
version = "0.8.11"
@ -2737,12 +2769,11 @@ dependencies = [
[[package]]
name = "outfly"
version = "0.7.3"
version = "0.7.1"
dependencies = [
"bevy",
"bevy_embedded_assets",
"bevy_xpbd_3d",
"embed-resource",
"fastrand",
"regex",
"serde",
@ -2882,6 +2913,19 @@ version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
[[package]]
name = "png"
version = "0.17.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1"
dependencies = [
"bitflags 1.3.2",
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide",
]
[[package]]
name = "polling"
version = "3.5.0"
@ -3265,6 +3309,12 @@ dependencies = [
"wide",
]
[[package]]
name = "simd-adler32"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "slab"
version = "0.4.9"
@ -3492,15 +3542,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "toml"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
dependencies = [
"serde",
]
[[package]]
name = "toml_datetime"
version = "0.6.5"
@ -3675,26 +3716,6 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "vswhom"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b"
dependencies = [
"libc",
"vswhom-sys",
]
[[package]]
name = "vswhom-sys"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3b17ae1f6c8a2b28506cd96d412eebf83b4a0ff2cbefeeb952f2f9dfa44ba18"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "walkdir"
version = "2.5.0"
@ -4395,15 +4416,6 @@ dependencies = [
"memchr",
]
[[package]]
name = "winreg"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
dependencies = [
"winapi",
]
[[package]]
name = "x11-dl"
version = "2.21.0"

View file

@ -1,6 +1,6 @@
[package]
name = "outfly"
version = "0.7.3"
version = "0.7.1"
edition = "2021"
homepage = "https://codeberg.org/hut/outfly"
repository = "https://codeberg.org/hut/outfly"
@ -8,20 +8,16 @@ categories = ["game", "aerospace", "simulation"]
keywords = ["game", "space", "3d"]
license = "GPL-3.0-only"
rust-version = "1.76.0"
build = "build/build.rs"
[dependencies]
regex = "1"
bevy = { version = "0.13.2", default-features = false, features = ["jpeg", "bevy_asset", "bevy_audio", "bevy_scene", "bevy_winit", "bevy_core_pipeline", "bevy_pbr", "bevy_gltf", "bevy_render", "bevy_text", "bevy_ui", "multi-threaded", "tonemapping_luts", "vorbis"]}
bevy = { version = "0.13.2", default-features = false, features = ["jpeg", "bevy_asset", "bevy_audio", "bevy_scene", "bevy_winit", "bevy_core_pipeline", "bevy_pbr", "bevy_gltf", "bevy_render", "bevy_text", "bevy_ui", "multi-threaded", "png", "tonemapping_luts", "vorbis"]}
bevy_xpbd_3d = { version = "0.4.2", default-features = false, features = ["3d", "f64", "parry-f64", "parallel", "async-collider"] }
bevy_embedded_assets = "0.10.2"
fastrand = "2.0"
serde = "1.0"
serde_yaml = "0.9"
[build-dependencies]
embed-resource = "1.6.3" # embedding of .exe metadata
[features]
default = ["x11"]
dev = ["bevy/dynamic_linking", "bevy/file_watcher"]

View file

@ -21,7 +21,6 @@ Links:
- [Source code](https://codeberg.org/hut/outfly)
- [itch.io page](https://wholesomevacuum.itch.io/outfly)
- [Game updates on Mastodon](https://mastodon.gamedev.place/@outfly)
- [Chatroom](https://matrix.to/#/#outfly:pub.solar)
![screenshot](doc/images/screenshot2.jpg)
@ -43,10 +42,9 @@ Links:
- Tab: Toggle HUD/AR
- F11: Toggle fullscreen
- F: Toggle 3rd person view
- M: Toggle map
- Y: Toggle rotation stabilizer
- F4: Toggle music
- F3: Toggle sound effects
- T: Toggle music
- M: Toggle sound effects
- Cheats
- G: Toggle god mode / cheats
- V/B: Impossible acceleration forward/backward
@ -157,10 +155,6 @@ python -m http.server -d wasm
# Changelog
- v0.7.3: Implement map. You can now zoom out ALL THE WAY
- v0.7.2:
- Implement colliders based on object shape
- Add "The Whale" vehicle around bus station Metis Prime
- v0.7.1: Much nicer HUD
- v0.7.0:
- Overhaul conversation system, now defined in YAML files

Binary file not shown.

View file

@ -1,7 +0,0 @@
fn main() {
let target = std::env::var("TARGET").unwrap();
if target.contains("windows") {
println!("cargo:warning=Embedding Windows Icon");
embed_resource::compile("build/windows/icon.rc");
}
}

View file

@ -1,7 +0,0 @@
#!/bin/sh
# usage: run this script from the project root, like this: build/install.sh [rootdir]
rootdir="${1:-}"
install -Dm755 "target/release/outfly" "$rootdir/usr/bin/outfly"
install -Dm644 "build/linux/outfly.png" "$rootdir/usr/share/pixmaps/outfly.png"
install -Dm644 "build/linux/outfly.desktop" "$rootdir/usr/share/applications/outfly.desktop"

View file

@ -1,9 +0,0 @@
[Desktop Entry]
Type=Application
Name=OutFly
GenericName=Space Game
Comment=A breathtaking 3D space game in the rings of Jupiter
Icon=outfly
Categories=Game;Simulation;
Exec=outfly
Terminal=false

View file

@ -1 +0,0 @@
app_icon ICON "outfly.ico"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

View file

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -31,85 +31,55 @@ PATH = "extra/hygdata_v41.csv"
# Meissa (orion's head, 8th brightest star in orion) = 3.33
MAX_APPARENT_MAGNITUDE = 7.0
SOL_ABSMAG = 4.83
SOL_RADIUS = 696.3e6
SOL_LUMINOSITY = 3.828e26
STEFAN_BOLTZMANN_CONSTANT = 5.670374419e-8
print("// This file was autogenerated by \"genrate_starchart.py\" using data from the")
print("// HYG database: https://github.com/astronexus/HYG-Database/tree/main/hyg")
print("// License: CC BY-SA 4.0: https://creativecommons.org/licenses/by-sa/4.0/")
print("// Each star's values: (x, y, z, magnitude, absolute magnitude, color index, name)")
print("// Each star's values: (x, y, z, magnitude, color index, distance, name)")
print("[")
def render(i, x, y, z, ra, dec, mag, absmag, ci, dist, name):
def render(ra, dec, mag, ci, dist, name):
# Takes ra/deg in degrees
ra = float(ra)
dec = float(dec)
mag = float(mag)
x, y, z = float(x), float(y), float(z)
name = re.sub(r'\s+', ' ', name)
if name == 'Sol':
return
#radius = star_radius(float(ci), float(absmag))
distance = 1.0
ra_radians = math.radians(ra * 15) # ra is in [0, 24], multiplying by 15 gives degrees in [0, 360]
dec_radians = math.radians(dec)
#print(f"ra_radians={ra_radians}, dec_radians={dec_radians}, dec={dec}, ra={ra}", file=sys.stderr)
x = distance * math.cos(dec_radians) * math.cos(-ra_radians)
y = distance * math.cos(dec_radians) * math.sin(-ra_radians)
z = distance * math.sin(dec_radians)
# distance = 1.0
# ra_radians = math.radians(ra * 15) # ra is in [0, 24], multiplying by 15 gives degrees in [0, 360]
# dec_radians = math.radians(dec)
# #print(f"ra_radians={ra_radians}, dec_radians={dec_radians}, dec={dec}, ra={ra}", file=sys.stderr)
# x = distance * math.cos(dec_radians) * math.cos(-ra_radians)
# y = distance * math.cos(dec_radians) * math.sin(-ra_radians)
# z = distance * math.sin(dec_radians)
#
# # Correct for differences in coordinate system axes
# x, y, z = x, z, y
# Correct for differences in coordinate system axes
x, y, z = x, z, y
#brightness = 2.512 ** (0 - mag)
print(f'({x:.04},{y:.04},{z:.04},{mag:.04},{absmag:.04},{ci},"{name}"),')
print(f'({x:.04},{y:.04},{z:.04},{mag:.04},{ci},{dist},"{name}"),')
def clean_name(string):
return "".join([c for c in string if c.isalnum() or c in ' -_'])
def star_radius(color_index, absolute_magnitude):
# Convert color index to temperature (using Ballesteros' formula approximation for B-V)
temp = 4600 * ((1 / (0.92 * color_index + 1.7)) + (1 / (0.92 * color_index + 0.62)))
# Convert absolute magnitude to luminosity (in solar luminosities)
lum = 10 ** (0.4 * (SOL_ABSMAG - absolute_magnitude))
# Calculate the radius using Stefan-Boltzmann law (output should be in meters)
radius = math.sqrt(lum * SOL_LUMINOSITY / (4 * math.pi * STEFAN_BOLTZMANN_CONSTANT * temp ** 4)) # Luminosity in watts for the Sun ~ 3.828e26
# Convert radius from meters to solar radii (1 solar radius ≈ 6.96e8 meters)
radius_solar = radius / SOL_RADIUS
return radius_solar
total = 0
count = 0
count_extra = 0
entries = []
with open(PATH, "r", encoding="utf-8") as f:
for index, entry in enumerate(csv.DictReader(f)):
entries.append((index, entry))
for entry in csv.DictReader(f):
entries.append(entry)
entries.sort(key=lambda entry: float(entry[1]['mag']))
entries.sort(key=lambda entry: float(entry['mag']))
for index, entry in entries:
for entry in entries:
total += 1
ra = entry['ra']
dec = entry['dec']
x = entry['x']
y = entry['y']
z = entry['z']
mag = entry['mag']
absmag = entry['absmag']
ci = entry['ci']
dist = entry['dist']
name = clean_name(entry['proper'])
@ -122,7 +92,7 @@ for index, entry in entries:
continue
if float(mag) > MAX_APPARENT_MAGNITUDE:
continue
render(index, x, y, z, ra, dec, mag, absmag, ci, dist, name)
render(ra, dec, mag, ci, dist, name)
count += 1
#for entry in CUSTOM_ENTRIES:
# render(entry[0], entry[1], entry[2], entry[3], entry[4])

View file

@ -21,10 +21,10 @@ SRCPATH="outfly_v$VERSION"
mkdir "$SRCPATH"
cp ../README.md "$SRCPATH"
cp ../target/x86_64-pc-windows-gnu/release/outfly.exe "$SRCPATH"/OutFly.exe
cp ../target/x86_64-pc-windows-gnu/release/outfly.exe "$SRCPATH"
zip -v -r -9 ../"outfly_v${VERSION}_windows.zip" "$SRCPATH"
rm "$SRCPATH"/OutFly.exe
rm "$SRCPATH"/outfly.exe
cp ../target/x86_64-unknown-linux-gnu/release/outfly "$SRCPATH"
zip -v -r -9 ../"outfly_v${VERSION}_linux.zip" "$SRCPATH"

View file

@ -3,11 +3,10 @@ use bevy_xpbd_3d::prelude::*;
use bevy::scene::SceneInstance;
use bevy::math::DVec3;
use crate::{actor, audio, camera, chat, commands, effects, hud, nature, var, world};
use std::collections::HashMap;
pub const ENGINE_SPEED_FACTOR: f32 = 30.0;
const MAX_TRANSMISSION_DISTANCE: f32 = 100.0;
const MAX_INTERACT_DISTANCE: f32 = 50.0;
const MAX_INTERACT_DISTANCE: f32 = 40.0;
pub struct ActorPlugin;
impl Plugin for ActorPlugin {
@ -28,11 +27,9 @@ impl Plugin for ActorPlugin {
));
app.add_systems(PostUpdate, (
handle_vehicle_enter_exit,
update_id2pos,
));
app.add_event::<VehicleEnterExitEvent>();
app.add_event::<PlayerDiesEvent>();
app.insert_resource(Id2Pos(HashMap::new()));
}
}
@ -113,13 +110,10 @@ impl Default for ExperiencesGForce { fn default() -> Self { Self {
#[derive(Component)] pub struct Player; // Attached to 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 WantsMaxRotation(pub f64);
#[derive(Component)] pub struct WantsMaxVelocity(pub f64);
#[derive(Component)] pub struct Identifier(pub String);
#[derive(Resource)] pub struct Id2Pos(pub HashMap<String, DVec3>);
#[derive(Component)]
pub struct LifeForm {
@ -332,7 +326,6 @@ pub fn handle_vehicle_enter_exit(
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::EnterVehicle));
commands.entity(driver).remove::<PlayerCamera>();
commands.entity(driver).remove::<Collider>();
commands.entity(driver).insert(JustNowEnteredVehicle);
commands.entity(vehicle).insert(PlayerCamera);
commands.entity(vehicle).insert(PlayerDrivesThis);
}
@ -428,7 +421,6 @@ fn handle_player_death(
q_noscenes: Query<Entity, (With<world::DespawnOnPlayerDeath>, Without<SceneInstance>)>,
ew_spawn: EventWriter<commands::SpawnEvent>,
mut scene_spawner: ResMut<SceneSpawner>,
mut active_asteroids: ResMut<world::ActiveAsteroids>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_effect: EventWriter<effects::SpawnEffectEvent>,
mut log: ResMut<hud::Log>,
@ -439,7 +431,6 @@ fn handle_player_death(
return;
}
settings.reset_player_settings();
active_asteroids.0.clear();
for entity in &q_noscenes {
cmd.entity(entity).despawn();
}
@ -528,13 +519,3 @@ fn handle_gforce(
}
}
}
fn update_id2pos(
mut id2pos: ResMut<Id2Pos>,
q_id: Query<(&Position, &Identifier)>,
) {
id2pos.0.clear();
for (pos, id) in &q_id {
id2pos.0.insert(id.0.clone(), pos.0);
}
}

View file

@ -137,12 +137,12 @@ pub fn toggle_bgm(
mut evwriter_sfx: EventWriter<PlaySfxEvent>,
mut settings: ResMut<var::Settings>,
) {
if keyboard_input.just_pressed(settings.key_toggle_music) {
if keyboard_input.just_pressed(KeyCode::KeyT) {
settings.mute_music ^= true;
evwriter_sfx.send(PlaySfxEvent(Sfx::Click));
evwriter_toggle.send(ToggleMusicEvent());
}
if keyboard_input.just_pressed(settings.key_toggle_sfx) {
if keyboard_input.just_pressed(KeyCode::KeyM) {
settings.mute_sfx ^= true;
evwriter_sfx.send(PlaySfxEvent(Sfx::Click));
evwriter_toggle.send(ToggleMusicEvent());

View file

@ -1,5 +1,5 @@
use bevy::prelude::*;
use bevy::input::mouse::{MouseMotion, MouseWheel};
use bevy::input::mouse::MouseMotion;
use bevy::window::PrimaryWindow;
use bevy::core_pipeline::bloom::{BloomCompositeMode, BloomSettings};
use bevy::core_pipeline::tonemapping::Tonemapping;
@ -8,7 +8,6 @@ use bevy::transform::TransformSystem;
use bevy::math::{DVec3, DQuat};
use bevy_xpbd_3d::prelude::*;
use std::f32::consts::PI;
use std::f64::consts::PI as PI64;
use crate::{actor, audio, hud, var};
pub struct CameraPlugin;
@ -17,50 +16,15 @@ impl Plugin for CameraPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, setup_camera);
app.add_systems(Update, handle_input);
app.add_systems(Update, update_map_only_object_visibility);
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(Update, update_map_camera);
app.add_systems(Update, update_fov);
app.add_systems(PostUpdate, apply_input_to_player
.after(PhysicsSet::Sync)
.before(TransformSystem::TransformPropagate));
app.insert_resource(MapCam::default());
}
}
#[derive(Component)]
pub struct ShowOnlyInMap {
pub min_distance: f64,
pub distance_to_id: String,
}
#[derive(Resource)]
pub struct MapCam {
pub initialized: bool,
pub zoom_level: f64,
pub target_zoom_level: f64,
pub pitch: f64,
pub yaw: f64,
pub offset_x: f64,
pub offset_z: f64,
pub center: DVec3,
}
impl Default for MapCam {
fn default() -> Self {
Self {
initialized: false,
zoom_level: 2.0,
target_zoom_level: 10.0,
pitch: PI64 * 0.3,
yaw: 0.0,
offset_x: 0.0,
offset_z: 0.0,
center: DVec3::new(0.0, 0.0, 0.0),
}
}
}
@ -108,7 +72,7 @@ pub fn sync_camera_to_player(
mut q_camera: Query<&mut Transform, (With<Camera>, Without<actor::PlayerCamera>)>,
q_playercam: Query<(&actor::Actor, &Transform), (With<actor::PlayerCamera>, Without<Camera>)>,
) {
if settings.map_active || q_camera.is_empty() || q_playercam.is_empty() {
if q_camera.is_empty() || q_playercam.is_empty() {
return;
}
let mut camera_transform = q_camera.get_single_mut().unwrap();
@ -126,105 +90,6 @@ pub fn sync_camera_to_player(
}
}
pub fn update_map_camera(
settings: Res<var::Settings>,
mut mapcam: ResMut<MapCam>,
mut q_camera: Query<&mut Transform, (With<Camera>, Without<actor::PlayerCamera>)>,
q_playercam: Query<&Transform, (With<actor::PlayerCamera>, Without<Camera>)>,
q_target: Query<&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>,
keyboard_input: Res<ButtonInput<KeyCode>>,
) {
if !settings.map_active || q_camera.is_empty() || q_playercam.is_empty() {
return;
}
let mut camera_transform = q_camera.get_single_mut().unwrap();
let player_transform = q_playercam.get_single().unwrap();
let target = if let Ok(target) = q_target.get_single() {
target
} else {
player_transform
};
// Get mouse movement
let mut mouse_delta = Vec2::ZERO;
for mouse_event in mouse_events.read() {
mouse_delta += mouse_event.delta;
}
// NOTE: we need to subtract a bit from PI/2, otherwise the "up"
// direction parameter for the Transform.look_at function is ambiguous
// at the extreme values and the orientation will flicker back/forth.
let epsilon = 0.001;
let min_zoom: f64 = target.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(-PI64 / 2.0 + epsilon, PI64 / 2.0 - epsilon);
mapcam.yaw += mouse_delta.x as f64 / 180.0 * settings.mouse_sensitivity as f64;
// Reset movement offset if target changes
if !q_target_changed.is_empty() {
mapcam.offset_x = 0.0;
mapcam.offset_z = 0.0;
}
// Get keyboard movement
let mut offset_x: f64 = 0.0;
let mut offset_z: f64 = 0.0;
if keyboard_input.pressed(settings.key_forward) {
offset_x -= 1.0;
}
if keyboard_input.pressed(settings.key_back) {
offset_x += 1.0;
}
if keyboard_input.pressed(settings.key_right) {
offset_z -= 1.0;
}
if keyboard_input.pressed(settings.key_left) {
offset_z += 1.0;
}
// Update zoom level
if !mapcam.initialized {
let factor: f64 = if target == player_transform { 7.0 } else { 1.0 };
mapcam.target_zoom_level *= target.scale.x as f64 * factor;
mapcam.zoom_level *= target.scale.x as f64 * factor;
mapcam.initialized = true;
}
let mut change_zoom: f64 = 0.0;
if keyboard_input.pressed(settings.key_map_zoom_out) {
change_zoom += 0.5;
}
if keyboard_input.pressed(settings.key_map_zoom_in) {
change_zoom -= 0.5;
}
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);
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);
// Update point of view
let pov_rotation = DQuat::from_euler(EulerRot::XYZ, 0.0, mapcam.yaw as f64, mapcam.pitch as f64);
let offset = DVec3::new(mapcam.offset_x, 0.0, mapcam.offset_z);
let point_of_view = offset + pov_rotation * (mapcam.zoom_level as f64 * DVec3::new(1.0, 0.0, 0.0));
// Update movement offset
let mut direction = pov_rotation * DVec3::new(offset_x, 0.0, offset_z);
let speed = direction.length();
direction.y = 0.0;
let direction = speed * direction.normalize_or_zero();
mapcam.offset_x += 0.01 * (direction.x * mapcam.zoom_level);
mapcam.offset_z += 0.01 * (direction.z * mapcam.zoom_level);
// Apply updates to camera
mapcam.center = target.translation.as_dvec3() + offset;
camera_transform.translation = target.translation + point_of_view.as_vec3();
camera_transform.look_at(mapcam.center.as_vec3(), Vec3::Y);
}
pub fn update_fov(
q_player: Query<&actor::ExperiencesGForce, With<actor::Player>>,
mouse_input: Res<ButtonInput<MouseButton>>,
@ -252,16 +117,11 @@ pub fn update_fov(
pub fn handle_input(
keyboard_input: Res<ButtonInput<KeyCode>>,
mut settings: ResMut<var::Settings>,
mut mapcam: ResMut<MapCam>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
) {
if keyboard_input.just_pressed(settings.key_camera) {
settings.third_person ^= true;
}
if keyboard_input.just_pressed(settings.key_map) {
settings.map_active ^= true;
*mapcam = MapCam::default();
}
if keyboard_input.just_pressed(settings.key_rotation_stabilizer) {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Click));
settings.rotation_stabilizer_active ^= true;
@ -269,21 +129,20 @@ pub fn handle_input(
}
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>)>,
mut q_hiddenplayer: Query<(&mut Visibility, &mut Position, &mut Rotation, &mut LinearVelocity, &mut AngularVelocity), (With<actor::Player>, Without<actor::PlayerCamera>)>,
q_ride: Query<(&Transform, &Position, &Rotation, &LinearVelocity, &AngularVelocity), (With<actor::PlayerDrivesThis>, Without<actor::Player>)>,
) {
for mut vis in &mut q_playercam {
if settings.third_person || settings.map_active {
if settings.third_person {
*vis = Visibility::Inherited;
}
else {
*vis = Visibility::Hidden;
}
}
for (entity, mut vis, mut pos, mut rot, mut v, mut angv, mut gforce, entering) in &mut q_hiddenplayer {
for (mut vis, mut pos, mut rot, mut v, mut angv) 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
@ -295,13 +154,6 @@ fn manage_player_actor(
rot.0 = ride_rot.0 * DQuat::from_array([-1.0, 0.0, 0.0, 0.0]);
*v = ride_v.clone();
*angv = ride_angv.clone();
// I really don't want people to die from the g-forces of entering
// 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>();
}
}
}
}
@ -327,9 +179,6 @@ pub fn apply_input_to_player(
Option<&actor::PlayerDrivesThis>,
), (With<actor::PlayerCamera>, Without<Camera>)>,
) {
if settings.map_active {
return;
}
let dt = time.delta_seconds();
let mut play_thruster_sound = false;
let mut axis_input: DVec3 = DVec3::ZERO;
@ -540,39 +389,6 @@ pub fn apply_input_to_player(
}
}
pub fn update_map_only_object_visibility(
settings: Res<var::Settings>,
q_camera: Query<&Transform, With<Camera>>,
q_player: Query<&Position, With<actor::PlayerCamera>>,
mut q_onlyinmap: Query<(&mut Visibility, &ShowOnlyInMap), Without<Camera>>,
id2pos: Res<actor::Id2Pos>,
) {
if q_camera.is_empty() || q_player.is_empty() {
return;
}
let cam: &Transform = q_camera.get_single().unwrap();
let player_pos: &Position = q_player.get_single().unwrap();
let cam_pos: Vec3 = cam.translation + player_pos.as_vec3();
for (mut vis, onlyinmap) in &mut q_onlyinmap {
if settings.map_active && settings.hud_active {
if let Some(pos) = id2pos.0.get(&onlyinmap.distance_to_id) {
let dist = cam_pos.distance(pos.as_vec3());
if dist >= onlyinmap.min_distance as f32 {
*vis = Visibility::Inherited;
}
else {
*vis = Visibility::Hidden;
}
} else {
error!("Failed get position of actor ID '{}'", &onlyinmap.distance_to_id);
*vis = Visibility::Hidden;
}
} else {
*vis = Visibility::Hidden;
}
}
}
// Find the closest world object that the player is looking at
#[inline]
pub fn find_closest_target<TargetSpecifier>(
@ -590,10 +406,10 @@ pub fn find_closest_target<TargetSpecifier>(
// 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 distance_to_surface = distance - trans.scale.x;
if angle <= angular_diameter.clamp(0.001, PI) {
// It's in the field of view!
//commands.entity(entity).insert(IsTargeted);
let distance_to_surface = distance - trans.scale.x;
if distance_to_surface < closest_distance {
closest_distance = distance_to_surface;
closest_entity = Some(entity);

View file

@ -778,7 +778,6 @@ pub fn handle_chat_scripts(
mut q_playercam: Query<(&mut Position, &mut LinearVelocity), With<actor::PlayerCamera>>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_effect: EventWriter<effects::SpawnEffectEvent>,
id2pos: Res<actor::Id2Pos>,
) {
for script in er_chatscript.read() {
// Parse the script string
@ -835,20 +834,19 @@ pub fn handle_chat_scripts(
}
else {
if let Ok((mut pos, mut v)) = q_playercam.get_single_mut() {
let busstop = match param1 {
"serenity" => Some("busstop"),
"oscillation" => Some("busstop2"),
"metisprime" => Some("busstop3"),
_ => 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);
}
} else {
if param1 == "oscillation".to_string() {
*pos = Position(DVec3::new(-184968e3, 149410e3, -134273e3));
v.0 = DVec3::ZERO;
}
else if param1 == "metisprime".to_string() {
*pos = Position(DVec3::new(27643e3, -47e3, -124434e3));
v.0 = DVec3::ZERO;
}
else if param1 == "serenity".to_string() {
*pos = Position(DVec3::new(-121095e3, 582e3, -190816e3));
v.0 = DVec3::ZERO;
}
else {
error!("Invalid destination for cryotrip chat script: '{}'", param1);
}
}

View file

@ -3,10 +3,11 @@ extern crate regex;
use bevy::prelude::*;
use bevy_xpbd_3d::prelude::*;
use bevy::math::DVec3;
use crate::{actor, camera, chat, hud, nature, shading, world};
use crate::{actor, chat, hud, nature, world};
use regex::Regex;
use std::f32::consts::PI;
use std::f64::consts::PI as PI64;
use std::collections::HashMap;
pub struct CommandsPlugin;
impl Plugin for CommandsPlugin {
@ -38,13 +39,12 @@ struct ParserState {
// Actor fields
id: String,
pos: DVec3,
relative_to: Option<String>,
model: Option<String>,
model: String,
model_scale: f32,
rotation: Quat,
velocity: DVec3,
angular_momentum: DVec3,
pronoun: Option<String>,
pronoun: String,
is_sphere: bool,
is_player: bool,
is_lifeform: bool,
@ -53,9 +53,7 @@ struct ParserState {
is_vehicle: bool,
is_clickable: bool,
is_targeted_on_startup: bool,
is_sun: bool,
has_physics: bool,
has_ring: bool,
wants_maxrotation: Option<f64>,
wants_maxvelocity: Option<f64>,
collider_is_mesh: bool,
@ -74,7 +72,6 @@ struct ParserState {
light_brightness: f32,
light_color: Option<Color>,
ar_model: Option<String>,
show_only_in_map_at_distance: Option<(f64, String)>,
}
impl Default for ParserState {
fn default() -> Self {
@ -87,13 +84,12 @@ impl Default for ParserState {
id: "".to_string(),
pos: DVec3::new(0.0, 0.0, 0.0),
relative_to: None,
model: None,
model: "".to_string(),
model_scale: 1.0,
rotation: Quat::IDENTITY,
velocity: DVec3::splat(0.0),
angular_momentum: DVec3::new(0.03, 0.3, 0.09),
pronoun: None,
pronoun: "they/them".to_string(),
is_sphere: false,
is_player: false,
is_lifeform: false,
@ -102,9 +98,7 @@ impl Default for ParserState {
is_vehicle: false,
is_clickable: true,
is_targeted_on_startup: false,
is_sun: false,
has_physics: true,
has_ring: false,
wants_maxrotation: None,
wants_maxvelocity: None,
collider_is_mesh: false,
@ -123,7 +117,6 @@ impl Default for ParserState {
light_brightness: 0.0,
light_color: None,
ar_model: None,
show_only_in_map_at_distance: None,
}
}
}
@ -138,6 +131,7 @@ pub fn load_defs(
let mut state = ParserState::default();
let mut command;
let mut parameters;
let mut id2pos: HashMap<String, DVec3> = HashMap::new();
let mut line_nr = -1;
while let Some(line) = lines.next() {
@ -183,21 +177,7 @@ pub fn load_defs(
ew_spawn.send(SpawnEvent(state));
state = ParserState::default();
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>()) {
state.pos = DVec3::new(x_float, y_float, z_float);
}
else {
error!("Can't parse coordinates as floats in def: {line}");
state = ParserState::default();
continue;
}
}
["actor", x, y, z] => {
ew_spawn.send(SpawnEvent(state));
state = ParserState::default();
state.class = DefClass::Actor;
state.model = model.to_string();
if let (Ok(x_float), Ok(y_float), Ok(z_float)) =
(x.parse::<f64>(), y.parse::<f64>(), z.parse::<f64>()) {
state.pos = DVec3::new(x_float, y_float, z_float);
@ -209,7 +189,18 @@ pub fn load_defs(
}
}
["relativeto", id] => {
state.relative_to = Some(id.to_string());
// NOTE: call this command before "id", otherwise actors that
// set their position relative to this actor will get the wrong offset
// TODO: fix the above
match id2pos.get(&id.to_string()) {
Some(pos) => {
state.pos += *pos;
}
None => {
error!("Specified `relativeto` command but could not find id `{id}`");
continue;
}
}
}
["orbit", radius_str, phase_str] => {
if let (Ok(r), Ok(phase)) = (radius_str.parse::<f64>(), phase_str.parse::<f64>()) {
@ -230,6 +221,7 @@ pub fn load_defs(
}
["id", id] => {
state.id = id.to_string();
id2pos.insert(state.id.clone(), state.pos.clone());
}
["alive", "yes"] => {
state.is_alive = true;
@ -245,13 +237,6 @@ pub fn load_defs(
["moon", "yes"] => {
state.model_scale *= 3.0;
}
["sun", "yes"] => {
state.is_sun = true;
state.model_scale *= 5.0;
}
["ring", "yes"] => {
state.has_ring = true;
}
["oxygen", amount] => {
if let Ok(amount) = amount.parse::<f32>() {
state.is_lifeform = true;
@ -264,7 +249,7 @@ pub fn load_defs(
}
}
["pronoun", pronoun] => {
state.pronoun = Some(pronoun.to_string());
state.pronoun = pronoun.to_string();
}
["chatid", chat] => {
state.chat = chat.to_string();
@ -443,15 +428,6 @@ pub fn load_defs(
["targeted", "yes"] => {
state.is_targeted_on_startup = true;
}
["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 {
error!("Can't parse float: {line}");
continue;
}
}
_ => {
error!("No match for [{}]", parts.join(","));
}
@ -466,26 +442,10 @@ fn spawn_entities(
asset_server: Res<AssetServer>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut materials_jupiter: ResMut<Assets<shading::JupitersRing>>,
mut id2pos: ResMut<actor::Id2Pos>,
) {
for state_wrapper in er_spawn.read() {
let state = &state_wrapper.0;
if state.class == DefClass::Actor {
let relative_pos = if let Some(id) = &state.relative_to {
match id2pos.0.get(&id.to_string()) {
Some(pos) => {
state.pos + *pos
}
None => {
error!("Specified `relativeto` command but could not find id `{id}`");
continue;
}
}
} else {
state.pos
};
let actor_entity;
{
let mut actor = commands.spawn_empty();
@ -498,17 +458,13 @@ fn spawn_entities(
actor.insert(SleepingDisabled);
actor.insert(world::DespawnOnPlayerDeath);
actor.insert(actor::HitPoints::default());
actor.insert(Position::from(relative_pos));
actor.insert(Position::from(state.pos));
actor.insert(Rotation::from(state.rotation));
if state.is_sphere {
let sphere_texture_handle = if let Some(model) = &state.model {
Some(asset_server.load(format!("textures/{}.jpg", model)))
} else {
None
};
let sphere_texture_handle: Handle<Image> = asset_server.load(format!("textures/{}.jpg", state.model));
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,
base_color_texture: Some(sphere_texture_handle.clone()),
perceptual_roughness: 1.0,
metallic: 0.0,
..default()
@ -522,13 +478,13 @@ fn spawn_entities(
},
..default()
});
} else if let Some(model) = &state.model {
} else {
actor.insert(SceneBundle {
transform: Transform {
scale: Vec3::splat(state.model_scale),
..default()
},
scene: asset_server.load(world::asset_name_to_path(model.as_str())),
scene: asset_server.load(world::asset_name_to_path(state.model.as_str())),
..default()
});
}
@ -568,23 +524,9 @@ fn spawn_entities(
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()
}));
}
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));
@ -602,7 +544,6 @@ fn spawn_entities(
if state.is_clickable {
actor.insert(hud::IsClickable {
name: state.name.clone(),
pronoun: state.pronoun.clone(),
..default()
});
}
@ -624,16 +565,12 @@ fn spawn_entities(
..default()
});
}
if !state.id.is_empty() {
actor.insert(actor::Identifier(state.id.clone()));
id2pos.0.insert(state.id.clone(), relative_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(),
pronoun: Some(state.pronoun.clone()),
talking_speed: 1.0,
});
}
@ -675,25 +612,6 @@ fn spawn_entities(
},
));
}
if state.has_ring {
commands.spawn((
world::DespawnOnPlayerDeath,
MaterialMeshBundle {
mesh: meshes.add(Mesh::from(Cylinder::new(nature::JUPITER_RING_RADIUS as f32, 1.0))),
material: materials_jupiter.add(shading::JupitersRing {
alpha_mode: AlphaMode::Blend,
ring_radius: nature::JUPITER_RING_RADIUS as f32,
jupiter_radius: nature::JUPITER_RADIUS as f32,
}),
transform: Transform::from_translation(relative_pos.as_vec3()),
..default()
},
Position::new(relative_pos),
Rotation::from(Quat::IDENTITY),
//Rotation::from(Quat::from_rotation_x(-0.3f32.to_radians())),
));
}
}
}
}

View file

@ -3,11 +3,10 @@ C: Impossibly instant stopping [CHEAT]
Shift+V/B: Same as V/B, but a thousand times faster [CHEAT]
V/B: Impossible acceleration forward/backward [CHEAT]
G: Toggle god mode / cheats [CHEAT]
M: Toggle map
M: Toggle sound effects
T: Toggle music
Y: Toggle rotation stabilizer
F: Toggle 3rd person view
F3: Toggle sound effects
F4: Toggle music
F11: Toggle fullscreen
Tab: Toggle HUD + Augmented Reality
Right click: Zoom [AUGMENTED REALITY ONLY]

File diff suppressed because it is too large Load diff

View file

@ -1,128 +1,16 @@
actor 0 0 0
id sol
name Sol
scale 696300e3
sphere yes
sun yes
physics off
actor 0 0 0 orbitring
scale 57.91e9
rotationz 0.0353
only_in_map_at_dist 1e10 jupiter
clickable no
physics off
actor 0 0 0 orbitring
scale 108.21e9
rotationz 0.0119
only_in_map_at_dist 1e10 jupiter
clickable no
physics off
actor 0 0 0 orbitring
scale 149.598023e9
rotationz 0.0088
only_in_map_at_dist 1e10 jupiter
clickable no
physics off
actor 0 0 0 orbitring
scale 227.939366e9
rotationz 0.0091
only_in_map_at_dist 1e10 jupiter
clickable no
physics off
actor 0 0 0 orbitring
scale 778.479e9
only_in_map_at_dist 1e10 jupiter
clickable no
physics off
actor 0 0 0 orbitring
scale 1433.53e9
rotationz 0.0052
only_in_map_at_dist 1e10 jupiter
clickable no
physics off
actor 0 0 0 orbitring
scale 2870.972e9
rotationz 0.0055
only_in_map_at_dist 1e10 jupiter
clickable no
physics off
actor 0 0 0 orbitring
scale 4500e9
rotationz 0.0041
only_in_map_at_dist 1e10 jupiter
clickable no
physics off
actor 0 0 0 orbitring
scale 5906.38e9
rotationz 0.0953
only_in_map_at_dist 1e10 jupiter
clickable no
physics off
actor 0 0 0 jupiter
relativeto sol
orbit 778479000e3 0.5
id jupiter
name Jupiter
scale 71492e3
sphere yes
ring yes
physics off
rotationx -0.50
rotationz -0.28
angularmomentum 30 30 30
actor 0 0 0 orbitring
relativeto jupiter
scale 128000e3
only_in_map_at_dist 1e7 metis
clickable no
physics off
actor 0 0 0 orbitring
relativeto jupiter
scale 129000e3
only_in_map_at_dist 1e7 adrastea
clickable no
physics off
actor 0 0 0 orbitring
relativeto jupiter
scale 181365.84e3
only_in_map_at_dist 1e7 amalthea
clickable no
physics off
actor 0 0 0 orbitring
relativeto jupiter
scale 221900e3
only_in_map_at_dist 1e7 thebe
clickable no
physics off
actor 0 0 0 orbitring
relativeto jupiter
scale 421700e3
only_in_map_at_dist 1e8 io
clickable no
physics off
actor 0 0 0 orbitring
relativeto jupiter
scale 670900e3
only_in_map_at_dist 1e8 europa
clickable no
physics off
actor 0 0 0 orbitring
relativeto jupiter
scale 1070400e3
only_in_map_at_dist 1e8 ganymede
clickable no
physics off
actor 0 0 0 orbitring
relativeto jupiter
scale 1882700e3
only_in_map_at_dist 1e8 callisto
clickable no
physics off
actor 0 593051 0 suit
relativeto jupiter
orbit 224000e3 0.66
orbit 226000e3 0.66
player yes
id player
scale 2
@ -149,12 +37,10 @@ actor 10 -30 20 MeteorAceGT
actor 0 0 0 io
name Io
id io
relativeto jupiter
orbit 421700e3 0.65
scale 1822e3
rotationy -0.40
rotationx -0.50
angularmomentum 0 0.0001 0
sphere yes
moon yes
@ -162,12 +48,10 @@ actor 0 0 0 io
actor 0 0 0 europa
name Europa
id europa
relativeto jupiter
orbit 670900e3 0.35
scale 1561e3
rotationy 0.20
rotationx -0.50
angularmomentum 0 0.0001 0
sphere yes
moon yes
@ -175,12 +59,10 @@ actor 0 0 0 europa
actor 0 0 0 ganymede
name Ganymede
id ganymede
relativeto jupiter
orbit 1070400e3 0.93
scale 2634e3
rotationy -0.40
rotationx -0.50
angularmomentum 0 0.0001 0
sphere yes
moon yes
@ -188,12 +70,10 @@ actor 0 0 0 ganymede
actor 0 0 0 callisto
name Callisto
id callisto
relativeto jupiter
orbit 1882700e3 0.45
scale 2410e3
rotationy -0.40
rotationx -0.50
angularmomentum 0 0.0001 0
sphere yes
moon yes
@ -207,30 +87,6 @@ actor 0 0 0 moonlet
scale 50e3
angularmomentum 0 0.025 0
actor 0 0 0 moonlet
name Metis
relativeto jupiter
id metis
orbit 128000e3 0.8
scale 21.5e3
angularmomentum 0 0.025 0
actor 0 0 0 moonlet
name Adrastea
relativeto jupiter
id adrastea
orbit 129000e3 0.5
scale 8.2e3
angularmomentum 0 0.025 0
actor 0 0 0 moonlet
name Amalthea
relativeto jupiter
id amalthea
orbit 181365.84e3 0.2
scale 83.5e3
angularmomentum 0 0.025 0
actor 3000 0 0 moonlet
name Moonlet
collider mesh
@ -239,16 +95,15 @@ actor 3000 0 0 moonlet
scale 500
angularmomentum 0 0.015 0
actor -8200 -4400 -8100 asteroid_lum
actor 220 -2400 410 asteroid_lum
relativeto player
name Lum
id Lum
collider mesh
density 10000000000
scale 300
rotationy 0.82
angularmomentum 0 0.015 0
actor 70 30 30 lightorb
actor -80 0 0 lightorb
relativeto Lum
name "Light Orb"
scale 0.3
@ -343,7 +198,6 @@ actor -3300 10 0 pizzeria
rotationy -0.7
scale 3
chatid SubduedClippy
pronoun it
actor -45 -4 -4 suit
relativeto pizzeria
@ -379,12 +233,11 @@ actor 60 -15 -40 suit
actor -300 0 40 suit
relativeto player
id Drifter
name "梓涵"
name "Drifter"
chatid Drifter
oxygen 0.08
scale 2
collider handcrafted
pronoun she
actor 100 -18000 2000 "orb_busstop"
relativeto player
@ -405,7 +258,6 @@ actor 100 -18000 2000 "orb_busstop"
rotationy -0.5
scale 3
chatid ClippyTransSerenity
pronoun it
actor 40 10 40 "orb_busstop"
name "Light Orb"
relativeto busstopclippy
@ -431,7 +283,6 @@ actor 100 -18000 2000 "orb_busstop"
scale 2
collider capsule 1 0.5
chatid NPCinCryoStasis
pronoun he
actor -184971e3 149410e3 -134273e3 "orb_busstop"
relativeto jupiter
@ -452,7 +303,6 @@ actor -184971e3 149410e3 -134273e3 "orb_busstop"
rotationy -0.5
scale 3
chatid ClippyTransOscillation
pronoun it
actor 40 10 40 "orb_busstop"
name "Light Orb"
relativeto busstopclippy2
@ -489,7 +339,6 @@ actor 27643e3 -44e3 -124434e3 "orb_busstop"
rotationy -0.5
scale 3
chatid ClippyTransMetis
pronoun it
actor 40 10 40 "orb_busstop"
name "Light Orb"
relativeto busstopclippy3

View file

@ -25,13 +25,11 @@ impl Plugin for HudPlugin {
app.add_systems(Startup, setup);
app.add_systems(Update, (
update_hud,
update_ar_overlays,
handle_input,
handle_target_event,
));
app.add_systems(PostUpdate, (
update_ar_overlays
.after(world::position_to_transform)
.in_set(sync::SyncSet::PositionToTransform),
update_target_selectagon
.after(PhysicsSet::Sync)
.after(camera::apply_input_to_player)
@ -111,12 +109,10 @@ impl Message {
#[derive(Component)]
pub struct IsClickable {
pub name: Option<String>,
pub pronoun: Option<String>,
pub distance: Option<f64>,
}
impl Default for IsClickable { fn default() -> Self { Self {
name: None,
pronoun: None,
distance: None,
}}}
@ -208,10 +204,9 @@ fn setup(
};
// Add Statistics HUD
let version = &settings.version;
let mut bundle_fps = TextBundle::from_sections([
TextSection::new("", style.clone()),
TextSection::new(format!(" OutFlyOS v{version} "), style.clone()),
TextSection::new(" ", style.clone()),
TextSection::new("", style.clone()),
TextSection::new("", style.clone()),
TextSection::new("", style.clone()),
@ -491,12 +486,7 @@ fn update_hud(
};
let speed_readable = nature::readable_distance(speed);
let target_name = clickable.name.clone().unwrap_or("Unnamed".to_string());
let pronoun = if let Some(pronoun) = &clickable.pronoun {
format!("Pronoun: {pronoun}\n")
} else {
"".to_string()
};
text.sections[15].value = format!("\n\nTarget: {target_name}\n{pronoun}Distance: {distance}\nΔv {speed_readable}/s");
text.sections[15].value = format!("\n\nTarget: {target_name}\nDistance: {distance}\nΔv {speed_readable}/s");
}
else {
text.sections[15].value = "".to_string();
@ -716,7 +706,7 @@ fn update_target_selectagon(
}
selectagon_trans.translation = target_trans.translation;
selectagon_trans.scale = target_trans.scale;
selectagon_trans.look_at(camera_trans.translation, Vec3::X);
selectagon_trans.rotation = Quat::from_rotation_arc(Vec3::Z, (-selectagon_trans.translation).normalize());
// Enlarge Selectagon to a minimum angular diameter
let (angular_diameter, _, _) = camera::calc_angular_diameter(

View file

@ -12,10 +12,6 @@ pub const PARSEC2METER: f64 = 3.0857e16;
pub const DIST_JUPTER_SUN: f64 = 778479.0e6;
pub const EARTH_GRAVITY: f32 = 9.81;
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;
// Each star's values: (x, y, z, magnitude, color index, distance, name)
pub const STARS: &[(f32, f32, f32, f32, f32, f32, &str)] = &include!("data/stars.in");

View file

@ -18,7 +18,6 @@ pub const DEFAULT_CHAT_SPEED: f32 = 10.0;
pub struct Settings {
pub dev_mode: bool,
pub god_mode: bool,
pub version: String,
pub mute_sfx: bool,
pub mute_music: bool,
pub volume_sfx: u8,
@ -41,17 +40,11 @@ pub struct Settings {
pub hud_color_choices: Color,
pub chat_speed: f32,
pub hud_active: bool,
pub map_active: bool,
pub is_zooming: bool,
pub third_person: bool,
pub rotation_stabilizer_active: bool,
pub key_selectobject: MouseButton,
pub key_zoom: MouseButton,
pub key_map: KeyCode,
pub key_map_zoom_out: KeyCode,
pub key_map_zoom_in: KeyCode,
//pub key_map_zoom_out_wheel: MouseButton,
//pub key_map_zoom_in_wheel: MouseButton,
pub key_togglehud: KeyCode,
pub key_exit: KeyCode,
pub key_restart: KeyCode,
@ -76,8 +69,6 @@ pub struct Settings {
pub key_mouseright: KeyCode,
pub key_rotateleft: KeyCode,
pub key_rotateright: KeyCode,
pub key_toggle_sfx: KeyCode,
pub key_toggle_music: KeyCode,
pub key_reply1: KeyCode,
pub key_reply2: KeyCode,
pub key_reply3: KeyCode,
@ -119,21 +110,15 @@ impl Default for Settings {
default_mute_music = false;
dev_mode = false;
}
let version = if let Some(version) = option_env!("CARGO_PKG_VERSION") {
version.to_string()
} else {
"13.37".to_string()
};
Settings {
dev_mode,
god_mode: false,
version,
mute_sfx: default_mute_sfx,
mute_music: default_mute_music,
volume_sfx: 100,
volume_music: 100,
mouse_sensitivity: 0.4,
mouse_sensitivity: 0.7,
fov: 50.0,
fov_highspeed: 25.0,
zoom_fov: 15.0,
@ -151,17 +136,11 @@ impl Default for Settings {
hud_color_choices: Color::rgb(0.45, 0.45, 0.45),
chat_speed: DEFAULT_CHAT_SPEED * if dev_mode { 2.5 } else { 1.0 },
hud_active: false,
map_active: false,
is_zooming: false,
third_person: false,
rotation_stabilizer_active: true,
key_selectobject: MouseButton::Left,
key_zoom: MouseButton::Right,
key_map: KeyCode::KeyM,
key_map_zoom_out: KeyCode::ShiftLeft,
key_map_zoom_in: KeyCode::ControlLeft,
//key_map_zoom_out_wheel: KeyCode::Shift,
//key_map_zoom_in_wheel: KeyCode::Shift,
key_togglehud: KeyCode::Tab,
key_exit: KeyCode::Escape,
key_restart: KeyCode::F7,
@ -186,8 +165,6 @@ impl Default for Settings {
key_mouseright: KeyCode::KeyL,
key_rotateleft: KeyCode::KeyU,
key_rotateright: KeyCode::KeyO,
key_toggle_sfx: KeyCode::F3,
key_toggle_music: KeyCode::F4,
key_reply1: KeyCode::Digit1,
key_reply2: KeyCode::Digit2,
key_reply3: KeyCode::Digit3,

View file

@ -15,7 +15,6 @@ const RING_THICKNESS: f64 = 8.0e6;
const STARS_MAX_MAGNITUDE: f32 = 5.5; // max 7.0, see generate_starchart.py
const CENTER_WORLD_ON_PLAYER: bool = true;
const SKYBOX: bool = false;
const ASTEROID_SPAWN_STEP: f64 = 500.0;
const ASTEROID_VIEW_RADIUS: f64 = 3000.0;
@ -39,7 +38,6 @@ pub fn asset_name_to_path(name: &str) -> &'static str {
"pizzeria" => "models/pizzeria2.glb#Scene0",
"pizzasign" => "models/pizzasign.glb#Scene0",
"selectagon" => "models/selectagon.glb#Scene0",
"orbitring" => "models/orbitring.glb#Scene0",
"clippy" => "models/clippy.glb#Scene0",
"clippy_ar" => "models/clippy_ar.glb#Scene0",
"whale" => "models/whale.glb#Scene0",
@ -59,6 +57,7 @@ impl Plugin for WorldPlugin {
app.insert_resource(Gravity(DVec3::splat(0.0)));
app.insert_resource(AsteroidUpdateTimer(
Timer::from_seconds(ASTEROID_UPDATE_INTERVAL, TimerMode::Repeating)));
app.insert_resource(AsteroidDatabase(Vec::new()));
app.insert_resource(ActiveAsteroids(HashMap::new()));
app.add_event::<DespawnEvent>();
@ -77,14 +76,15 @@ impl Plugin for WorldPlugin {
}
#[derive(Resource)] struct AsteroidUpdateTimer(Timer);
#[derive(Resource)] pub struct ActiveAsteroids(pub HashMap<I64Vec3, AsteroidData>);
#[derive(Resource)] struct AsteroidDatabase(Vec<AsteroidData>);
#[derive(Resource)] struct ActiveAsteroids(HashMap<I64Vec3, AsteroidData>);
#[derive(Resource)] struct AsteroidModel1(Handle<Scene>);
#[derive(Resource)] struct AsteroidModel2(Handle<Scene>);
#[derive(Component)] struct Asteroid;
#[derive(Component)] pub struct DespawnOnPlayerDeath;
pub struct AsteroidData {
struct AsteroidData {
entity: Entity,
//viewdistance: f64,
}
@ -103,6 +103,7 @@ pub fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut materials_jupiter: ResMut<Assets<shading::JupitersRing>>,
mut materials_skybox: ResMut<Assets<shading::SkyBox>>,
asset_server: Res<AssetServer>,
) {
@ -111,90 +112,113 @@ pub fn setup(
commands.insert_resource(AsteroidModel2(asset_server.load(ASSET_ASTEROID2)));
// Generate starmap
let sphere_handle = meshes.add(Sphere::new(1.0).mesh().uv(16, 16));
let sphere_handle = meshes.add(Circle::new(1.0));
let mut starcount = 0;
for (index, star) in nature::STARS.iter().enumerate() {
let (x, y, z, mag, _absmag, color_index, name) = *star;
for star in nature::STARS {
let mag = star.3;
if mag > STARS_MAX_MAGNITUDE {
continue;
}
let (r, g, b) = nature::star_color_index_to_rgb(color_index);
let mut pos = DVec3::new(x as f64, z as f64, -y as f64) * nature::PARSEC2METER;
if pos.length() > 1e21 {
pos *= 0.002;
}
let pos_render = pos * 1.0;
let scale_factor = 1e-4 * pos_render.length() as f32; // from experimentation
let is_sun = mag < -20.0;
let mag = mag.min(6.0);
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)
if is_sun {
color * 13.0f32
} else {
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 *
let scale_size = {|mag: f32|
if is_sun {
40000.0f32
} else {
1000.0 * (0.230299 * mag * mag - 3.09013 * mag + 15.1782)
} * 100.0
};
let (r, g, b) = nature::star_color_index_to_rgb(star.4);
let star_color_handle = materials.add(StandardMaterial {
base_color: Color::rgb(scale_color(r), scale_color(g), scale_color(b)),
unlit: true,
..default()
});
let name = if name.is_empty() {
format!("Uncharted Star #{index}")
} else {
name.to_string()
};
let distance = if pos.length() > 1e21 {
let mesh_distance = 1e9;
let starchart_distance = if is_sun {
Some(nature::DIST_JUPTER_SUN)
} else if star.5 >= 100000.0 {
None
} else {
Some(pos.length())
Some(nature::PARSEC2METER * star.5 as f64)
};
let name = if star.6.is_empty() {
"Uncharted Star".to_string()
} else {
star.6.to_string()
};
let translation = Vec3::new(
mesh_distance * star.0,
mesh_distance * star.1,
mesh_distance * star.2,
);
let rotation = Quat::from_rotation_arc(Vec3::Z, (-translation).normalize());
commands.spawn((
Star,
hud::IsClickable {
name: Some(name),
distance,
..default()
distance: starchart_distance,
},
PbrBundle {
mesh: sphere_handle.clone(),
material: star_color_handle,
transform: Transform {
translation: pos_render.as_vec3(),
scale: Vec3::splat(scale as f32),
..default()
translation,
rotation,
scale: Vec3::splat(scale_size(mag)),
},
..default()
},
}
));
starcount += 1;
}
info!("Generated {starcount} stars");
// Add shaded skybox
if SKYBOX {
let mut mesh = Mesh::from(Sphere::new(1e10).mesh().uv(5, 5));
//let mut mesh = Mesh::from(Cuboid::from_size(Vec3::splat(2e10)));
if let Some(Indices::U32(indices)) = mesh.indices_mut() {
// Reverse the order of each triangle to avoid backface culling
for slice in indices.chunks_mut(3) {
slice.reverse();
}
//let mut mesh = Mesh::from(Sphere::new(1e9).mesh().uv(50, 50));
let mut mesh = Mesh::from(Sphere::new(1e10).mesh().uv(5, 5));
//let mut mesh = Mesh::from(Cuboid::from_size(Vec3::splat(2e10)));
if let Some(Indices::U32(indices)) = mesh.indices_mut() {
// Reverse the order of each triangle to avoid backface culling
for slice in indices.chunks_mut(3) {
slice.reverse();
}
commands.spawn(MaterialMeshBundle {
}
commands.spawn((
MaterialMeshBundle {
mesh: meshes.add(mesh),
material: materials_skybox.add(shading::SkyBox {}),
transform: Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)),
..default()
});
}
},
Position::from_xyz(0.0, 0.0, 0.0),
Rotation::from(Quat::IDENTITY),
));
// Add shaded ring
let ring_radius = 229_000_000.0;
let jupiter_radius = 71_492_000.0;
commands.spawn((
MaterialMeshBundle {
mesh: meshes.add(Mesh::from(Cylinder::new(ring_radius, 1.0))),
material: materials_jupiter.add(shading::JupitersRing {
alpha_mode: AlphaMode::Blend,
ring_radius: ring_radius,
jupiter_radius: jupiter_radius,
}),
..default()
},
Position::from_xyz(0.0, 0.0, 0.0),
Rotation::from(Quat::IDENTITY),
//Rotation::from(Quat::from_rotation_x(-0.3f32.to_radians())),
));
}
fn spawn_despawn_asteroids(
@ -208,24 +232,16 @@ fn spawn_despawn_asteroids(
asteroid2_handle: Res<AsteroidModel2>,
mut q_asteroid: Query<(Entity, &SceneInstance), With<Asteroid>>,
mut last_player_cell: Local<I64Vec3>,
id2pos: Res<actor::Id2Pos>,
) {
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()) {
*jupiter_pos
} else {
error!("Can't spawn asteroids because Jupiter's position can not be determined");
return;
};
let player = q_player.get_single().unwrap();
let fromjupiter = player.0 - jupiter_pos;
let player_cell = I64Vec3::new(
(fromjupiter.x / ASTEROID_SPAWN_STEP).round() as i64,
(fromjupiter.y / ASTEROID_SPAWN_STEP).round() as i64,
(fromjupiter.z / ASTEROID_SPAWN_STEP).round() as i64,
(player.x / ASTEROID_SPAWN_STEP).round() as i64,
(player.y / ASTEROID_SPAWN_STEP).round() as i64,
(player.z / ASTEROID_SPAWN_STEP).round() as i64,
);
if *last_player_cell == player_cell {
return;
@ -269,13 +285,13 @@ fn spawn_despawn_asteroids(
}
// Density based on the radius alone
let radius_plane = (fromjupiter.x * fromjupiter.x + fromjupiter.z * fromjupiter.z).sqrt();
let radius_plane = (player.x * player.x + player.z * player.z).sqrt();
let density_r = nature::ring_density((radius_plane / 1e6) as f32);
if density_r < 0.001 {
return;
}
// Density based on radius and the vertical distance to the ring
let normalized_distance = fromjupiter.y / (RING_THICKNESS / 2.0);
let normalized_distance = player.y / (RING_THICKNESS / 2.0);
let density = density_r * (-4.0 * normalized_distance.powf(2.0)).exp() as f32;
if density < 0.001 {
return;
@ -326,7 +342,7 @@ 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(
let 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,
@ -342,7 +358,6 @@ fn spawn_despawn_asteroids(
Rotation::from(Quat::from_rotation_y(-PI / 3.)),
Position::new(pos),
Asteroid,
DespawnOnPlayerDeath,
));
let model = match class {
0 => asteroid1_handle.0.clone(),
@ -385,7 +400,6 @@ fn handle_cheats(
q_target: Query<(&Transform, &Position, &LinearVelocity), (With<hud::IsTargeted>, Without<actor::PlayerCamera>)>,
mut ew_playerdies: EventWriter<actor::PlayerDiesEvent>,
mut settings: ResMut<var::Settings>,
id2pos: Res<actor::Id2Pos>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
) {
if q_player.is_empty() || q_life.is_empty() {
@ -433,22 +447,16 @@ fn handle_cheats(
}
if key_input.just_pressed(settings.key_cheat_pizza) {
if let Some(target) = id2pos.0.get(&"pizzeria".to_string()) {
pos.0 = *target + DVec3::new(-60.0, 0.0, 0.0);
gforce.ignore_gforce_seconds = 1.0;
}
pos.0 = DVec3::new(-121100218.0, 593057.0, -190818113.0);
gforce.ignore_gforce_seconds = 1.0;
}
if key_input.just_pressed(settings.key_cheat_farview1) {
if let Some(target) = id2pos.0.get(&"busstop2".to_string()) {
pos.0 = *target + DVec3::new(0.0, -1000.0, 0.0);
gforce.ignore_gforce_seconds = 1.0;
}
pos.0 = DVec3::new(27643e3, -47e3, -124434e3);
gforce.ignore_gforce_seconds = 1.0;
}
if key_input.just_pressed(settings.key_cheat_farview2) {
if let Some(target) = id2pos.0.get(&"busstop3".to_string()) {
pos.0 = *target + DVec3::new(0.0, -1000.0, 0.0);
gforce.ignore_gforce_seconds = 1.0;
}
pos.0 = DVec3::new(-184968e3, 149410e3, -134273e3);
gforce.ignore_gforce_seconds = 1.0;
}
if key_input.pressed(settings.key_cheat_adrenaline_zero) {
lifeform.adrenaline = 0.0;