Compare commits

..

43 commits

Author SHA1 Message Date
yuni 4fecab5428 bump version to v0.9.2 2024-06-07 00:48:02 +02:00
yuni 3429ca5ab7 update changelog 2024-06-07 00:47:03 +02:00
yuni 9f936989f3 fix player erroneously slowing down automatically 2024-06-07 00:43:27 +02:00
yuni 1ce864c746 tweak suit_v2 curves 2024-06-07 00:33:21 +02:00
yuni 63beec86a2 fix icarus' rotation 2024-06-02 21:37:29 +02:00
yuni 3f1dc27684 add command "template clippy" 2024-06-02 21:35:40 +02:00
yuni 0bbca303cb add "template person" command 2024-06-02 21:29:08 +02:00
yuni f7002fd064 reset chat variables (GameVars) on death 2024-06-02 21:04:03 +02:00
yuni 76bfdf0bfb (fix in bevy repo) release keys when moving window to other workspace with hotkey 2024-06-02 20:58:58 +02:00
yuni 23c86e9c2f update changelog 2024-05-23 06:11:25 +02:00
yuni 28cf269907 keep avatar on death/respawn 2024-05-23 05:42:25 +02:00
yuni cc3213788e implement wing avatar, give it to icarus (and player, optionally) 2024-05-23 05:42:18 +02:00
yuni 192d2e0fcb update changelog 2024-05-23 05:04:17 +02:00
yuni efbb44a9fc implement player avatars 2024-05-23 05:02:59 +02:00
yuni 099e935e3e avoid errors during player death 2024-05-23 05:01:53 +02:00
yuni 2a6e14aa90 add JupiterRecording.ogg, toggle BGM with TAB 2024-05-23 03:56:13 +02:00
yuni 8d4ad64330 cut out a different part from the jupiter recording 2024-05-23 03:53:08 +02:00
yuni e2046380ea WIP jupiter recording 2024-05-23 03:50:09 +02:00
yuni 60be58b0fa delete unused monolith.glb 2024-05-23 02:07:37 +02:00
yuni 224e0ce2c9 cleanup, add TODO 2024-05-23 01:26:24 +02:00
yuni bfad39613e better value for "up" vector 2024-05-23 00:59:33 +02:00
yuni e9afeefb7d fix "want to look at player" for when player rides a vehicle 2024-05-23 00:56:12 +02:00
yuni c9e38c7b29 avoid potential panic 2024-05-23 00:40:47 +02:00
yuni 62a0387867 fix monolith model, make it look at cult asteroid 2024-05-23 00:40:14 +02:00
yuni 91d19e94a0 implement preferred looking direction for NPCs 2024-05-23 00:31:31 +02:00
yuni 8a07e9cfb7 cleanup 2024-05-22 23:54:11 +02:00
yuni c56b5d6d74 dynamic camera movement speed limit based on mouse speed 2024-05-22 23:50:28 +02:00
yuni bcba3d0945 cleanup 2024-05-22 23:33:37 +02:00
yuni 9b48112ee6 remove unnecessary check 2024-05-22 23:31:57 +02:00
yuni 8acbd4f33b unify rotation stabilizer of players and npcs 2024-05-22 23:29:54 +02:00
yuni 93e5ee26e4 bump version to v0.9.1 2024-05-22 22:33:54 +02:00
yuni 8621002931 fix build instruction link 2024-05-22 22:33:48 +02:00
yuni 220ab340fb fix banner in README 2024-05-22 05:01:49 +02:00
yuni 87199f41db apply cargo fmt 2024-05-22 05:00:45 +02:00
yuni b0ac508d91 enable zoom outside of augmented reality (why confuse the player?) 2024-05-22 04:11:48 +02:00
yuni 14a22699bc brevity 2024-05-21 23:32:23 +02:00
yuni 2bc86227a9 highlight keys 2024-05-21 23:29:21 +02:00
yuni 61b63aefbe wording 2024-05-21 23:28:17 +02:00
yuni c7e3a9396e add sticker parameters 2024-05-21 19:19:42 +02:00
yuni b62c5a6287 preview of branding colors 2024-05-21 19:07:40 +02:00
yuni 42c1d3e191 add branding directory 2024-05-21 19:05:49 +02:00
yuni 77b682a7c1 update repository URL 2024-05-20 21:56:25 +02:00
yuni 6043d4a1b0 Revert "brighter logo"
This reverts commit 83313c1a5f.
2024-05-17 17:17:18 +02:00
41 changed files with 2534 additions and 1491 deletions

View file

@ -1,8 +1,16 @@
# git
# v0.9.2
- Implement customizable player avatars
- Implement NPC AI that dynamically changes their facing direction
- Implement "template" shortcuts for defining world objects
- Add actual Jupiter sound recording, playing when Augmented Reality is off
# v0.9.1
- Implement cruise control
- New logo: a white O, diagonal ring around it, and neon pink glow
- New dashboard icons: rotation stabiliser, radioactivity warning
- Allow zooming (right click) when Augmented Reality is disabled
# v0.9.0

126
Cargo.lock generated
View file

@ -291,7 +291,7 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "bevy"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"bevy_dylib",
"bevy_internal",
@ -300,7 +300,7 @@ dependencies = [
[[package]]
name = "bevy_a11y"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"accesskit",
"bevy_app",
@ -311,14 +311,14 @@ dependencies = [
[[package]]
name = "bevy_animation"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"bevy_app",
"bevy_asset",
"bevy_core",
"bevy_ecs",
"bevy_hierarchy",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b)",
"bevy_reflect",
"bevy_render",
"bevy_time",
@ -329,7 +329,7 @@ dependencies = [
[[package]]
name = "bevy_app"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"bevy_derive",
"bevy_ecs",
@ -344,7 +344,7 @@ dependencies = [
[[package]]
name = "bevy_asset"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"async-broadcast",
"async-fs",
@ -376,7 +376,7 @@ dependencies = [
[[package]]
name = "bevy_asset_macros"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"bevy_macro_utils",
"proc-macro2",
@ -387,13 +387,13 @@ dependencies = [
[[package]]
name = "bevy_audio"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"bevy_app",
"bevy_asset",
"bevy_derive",
"bevy_ecs",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b)",
"bevy_reflect",
"bevy_transform",
"bevy_utils",
@ -403,11 +403,11 @@ dependencies = [
[[package]]
name = "bevy_core"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"bevy_app",
"bevy_ecs",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b)",
"bevy_reflect",
"bevy_tasks",
"bevy_utils",
@ -417,7 +417,7 @@ dependencies = [
[[package]]
name = "bevy_core_pipeline"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"bevy_app",
"bevy_asset",
@ -425,7 +425,7 @@ dependencies = [
"bevy_derive",
"bevy_ecs",
"bevy_log",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b)",
"bevy_reflect",
"bevy_render",
"bevy_transform",
@ -438,7 +438,7 @@ dependencies = [
[[package]]
name = "bevy_derive"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"bevy_macro_utils",
"quote",
@ -448,7 +448,7 @@ dependencies = [
[[package]]
name = "bevy_diagnostic"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"bevy_app",
"bevy_core",
@ -463,7 +463,7 @@ dependencies = [
[[package]]
name = "bevy_dylib"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"bevy_internal",
]
@ -471,7 +471,7 @@ dependencies = [
[[package]]
name = "bevy_ecs"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"async-channel",
"bevy_ecs_macros",
@ -490,7 +490,7 @@ dependencies = [
[[package]]
name = "bevy_ecs_macros"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"bevy_macro_utils",
"proc-macro2",
@ -501,7 +501,7 @@ dependencies = [
[[package]]
name = "bevy_embedded_assets"
version = "0.10.2"
source = "git+https://codeberg.org/outfly/bevy_embedded_assets.git?rev=bb925e7e5373c742c01e6e7aff04e92fdc07c095#bb925e7e5373c742c01e6e7aff04e92fdc07c095"
source = "git+https://codeberg.org/outfly/bevy_embedded_assets.git?rev=2696fcc0319e8660c50d4601e7d4e530cf0bb981#2696fcc0319e8660c50d4601e7d4e530cf0bb981"
dependencies = [
"bevy",
"cargo-emit",
@ -512,7 +512,7 @@ dependencies = [
[[package]]
name = "bevy_encase_derive"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"bevy_macro_utils",
"encase_derive_impl",
@ -521,7 +521,7 @@ dependencies = [
[[package]]
name = "bevy_gizmos"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"bevy_app",
"bevy_asset",
@ -530,7 +530,7 @@ dependencies = [
"bevy_ecs",
"bevy_gizmos_macros",
"bevy_log",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b)",
"bevy_pbr",
"bevy_reflect",
"bevy_render",
@ -542,7 +542,7 @@ dependencies = [
[[package]]
name = "bevy_gizmos_macros"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"bevy_macro_utils",
"proc-macro2",
@ -553,7 +553,7 @@ dependencies = [
[[package]]
name = "bevy_gltf"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"base64",
"bevy_animation",
@ -564,7 +564,7 @@ dependencies = [
"bevy_ecs",
"bevy_hierarchy",
"bevy_log",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b)",
"bevy_pbr",
"bevy_reflect",
"bevy_render",
@ -582,7 +582,7 @@ dependencies = [
[[package]]
name = "bevy_hierarchy"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"bevy_app",
"bevy_core",
@ -595,11 +595,11 @@ dependencies = [
[[package]]
name = "bevy_input"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"bevy_app",
"bevy_ecs",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b)",
"bevy_reflect",
"bevy_utils",
"smol_str",
@ -609,7 +609,7 @@ dependencies = [
[[package]]
name = "bevy_internal"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"bevy_a11y",
"bevy_animation",
@ -626,7 +626,7 @@ dependencies = [
"bevy_hierarchy",
"bevy_input",
"bevy_log",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b)",
"bevy_pbr",
"bevy_ptr",
"bevy_reflect",
@ -646,7 +646,7 @@ dependencies = [
[[package]]
name = "bevy_log"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"android_log-sys",
"bevy_app",
@ -661,7 +661,7 @@ dependencies = [
[[package]]
name = "bevy_macro_utils"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"proc-macro2",
"quote",
@ -682,7 +682,7 @@ dependencies = [
[[package]]
name = "bevy_math"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"glam",
"serde",
@ -691,7 +691,7 @@ dependencies = [
[[package]]
name = "bevy_mikktspace"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"glam",
]
@ -699,14 +699,14 @@ dependencies = [
[[package]]
name = "bevy_pbr"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"bevy_app",
"bevy_asset",
"bevy_core_pipeline",
"bevy_derive",
"bevy_ecs",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b)",
"bevy_reflect",
"bevy_render",
"bevy_transform",
@ -723,14 +723,14 @@ dependencies = [
[[package]]
name = "bevy_ptr"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
[[package]]
name = "bevy_reflect"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b)",
"bevy_ptr",
"bevy_reflect_derive",
"bevy_utils",
@ -745,7 +745,7 @@ dependencies = [
[[package]]
name = "bevy_reflect_derive"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"bevy_macro_utils",
"proc-macro2",
@ -757,7 +757,7 @@ dependencies = [
[[package]]
name = "bevy_render"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"async-channel",
"bevy_app",
@ -768,7 +768,7 @@ dependencies = [
"bevy_encase_derive",
"bevy_hierarchy",
"bevy_log",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b)",
"bevy_mikktspace",
"bevy_reflect",
"bevy_render_macros",
@ -801,7 +801,7 @@ dependencies = [
[[package]]
name = "bevy_render_macros"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"bevy_macro_utils",
"proc-macro2",
@ -812,7 +812,7 @@ dependencies = [
[[package]]
name = "bevy_scene"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"bevy_app",
"bevy_asset",
@ -831,7 +831,7 @@ dependencies = [
[[package]]
name = "bevy_sprite"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"bevy_app",
"bevy_asset",
@ -839,7 +839,7 @@ dependencies = [
"bevy_derive",
"bevy_ecs",
"bevy_log",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b)",
"bevy_reflect",
"bevy_render",
"bevy_transform",
@ -856,7 +856,7 @@ dependencies = [
[[package]]
name = "bevy_tasks"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"async-channel",
"async-executor",
@ -869,13 +869,13 @@ dependencies = [
[[package]]
name = "bevy_text"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"ab_glyph",
"bevy_app",
"bevy_asset",
"bevy_ecs",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b)",
"bevy_reflect",
"bevy_render",
"bevy_sprite",
@ -890,7 +890,7 @@ dependencies = [
[[package]]
name = "bevy_time"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"bevy_app",
"bevy_ecs",
@ -903,12 +903,12 @@ dependencies = [
[[package]]
name = "bevy_transform"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"bevy_app",
"bevy_ecs",
"bevy_hierarchy",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b)",
"bevy_reflect",
"thiserror",
]
@ -916,7 +916,7 @@ dependencies = [
[[package]]
name = "bevy_ui"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"bevy_a11y",
"bevy_app",
@ -927,7 +927,7 @@ dependencies = [
"bevy_hierarchy",
"bevy_input",
"bevy_log",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b)",
"bevy_reflect",
"bevy_render",
"bevy_sprite",
@ -943,7 +943,7 @@ dependencies = [
[[package]]
name = "bevy_utils"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"ahash",
"bevy_utils_proc_macros",
@ -961,7 +961,7 @@ dependencies = [
[[package]]
name = "bevy_utils_proc_macros"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"proc-macro2",
"quote",
@ -971,13 +971,13 @@ dependencies = [
[[package]]
name = "bevy_window"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"bevy_a11y",
"bevy_app",
"bevy_ecs",
"bevy_input",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b)",
"bevy_reflect",
"bevy_utils",
"raw-window-handle",
@ -987,7 +987,7 @@ dependencies = [
[[package]]
name = "bevy_winit"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
source = "git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b#e4dc13639106aa86826f6243d58f3209e1e94b1b"
dependencies = [
"accesskit_winit",
"approx",
@ -997,7 +997,7 @@ dependencies = [
"bevy_ecs",
"bevy_hierarchy",
"bevy_input",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=e4dc13639106aa86826f6243d58f3209e1e94b1b)",
"bevy_tasks",
"bevy_utils",
"bevy_window",
@ -1011,7 +1011,7 @@ dependencies = [
[[package]]
name = "bevy_xpbd_3d"
version = "0.4.2"
source = "git+https://codeberg.org/outfly/bevy_xpbd.git?rev=99bca3f6a25b8c4e6ec6509e9e9b0e7bed565912#99bca3f6a25b8c4e6ec6509e9e9b0e7bed565912"
source = "git+https://codeberg.org/outfly/bevy_xpbd.git?rev=b6a03d6ec41e409d56f6b876f654a14d0b33afa7#b6a03d6ec41e409d56f6b876f654a14d0b33afa7"
dependencies = [
"bevy",
"bevy_math 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1028,7 +1028,7 @@ dependencies = [
[[package]]
name = "bevy_xpbd_derive"
version = "0.1.0"
source = "git+https://codeberg.org/outfly/bevy_xpbd.git?rev=99bca3f6a25b8c4e6ec6509e9e9b0e7bed565912#99bca3f6a25b8c4e6ec6509e9e9b0e7bed565912"
source = "git+https://codeberg.org/outfly/bevy_xpbd.git?rev=b6a03d6ec41e409d56f6b876f654a14d0b33afa7#b6a03d6ec41e409d56f6b876f654a14d0b33afa7"
dependencies = [
"quote",
"syn 2.0.52",
@ -2767,7 +2767,7 @@ dependencies = [
[[package]]
name = "outfly"
version = "0.9.0"
version = "0.9.2"
dependencies = [
"bevy",
"bevy_embedded_assets",

View file

@ -10,10 +10,10 @@
[package]
name = "outfly"
version = "0.9.0"
version = "0.9.2"
edition = "2021"
homepage = "https://codeberg.org/hut/outfly"
repository = "https://codeberg.org/hut/outfly"
homepage = "https://codeberg.org/outfly/outfly"
repository = "https://codeberg.org/outfly/outfly"
categories = ["game", "aerospace", "simulation"]
keywords = ["game", "space", "3d"]
license = "GPL-3.0-only"
@ -38,7 +38,7 @@ toml_edit = { version = "0.22", features = ["serde"] }
# We temporarily use a fork with a custom bug fix, see https://codeberg.org/outfly/bevy
version = "0.13.2"
git = "https://codeberg.org/outfly/bevy.git"
rev = "2076f102be15a88a5026e128851a8ee1ae9d8960"
rev = "e4dc13639106aa86826f6243d58f3209e1e94b1b"
default-features = false
features = ["animation", "bevy_asset", "bevy_audio", "bevy_scene", "bevy_winit", "bevy_core_pipeline", "bevy_pbr", "bevy_gltf", "bevy_render", "bevy_text", "bevy_ui", "jpeg", "multi-threaded", "png", "tonemapping_luts", "vorbis"]
@ -47,7 +47,7 @@ features = ["animation", "bevy_asset", "bevy_audio", "bevy_scene", "bevy_winit",
# We temporarily use a fork with a custom bug fix, see https://codeberg.org/outfly/bevy
version = "0.10.2"
git = "https://codeberg.org/outfly/bevy_embedded_assets.git"
rev = "bb925e7e5373c742c01e6e7aff04e92fdc07c095"
rev = "2696fcc0319e8660c50d4601e7d4e530cf0bb981"
optional = true
[dependencies.bevy_xpbd_3d]
@ -55,7 +55,7 @@ optional = true
# We temporarily use a fork with a custom bug fix, see https://codeberg.org/outfly/bevy
version = "0.4.2"
git = "https://codeberg.org/outfly/bevy_xpbd.git"
rev = "99bca3f6a25b8c4e6ec6509e9e9b0e7bed565912"
rev = "b6a03d6ec41e409d56f6b876f654a14d0b33afa7"
default-features = false
features = ["3d", "f64", "parry-f64", "parallel", "async-collider"]

View file

@ -1,7 +1,7 @@
# Licenses of OutFly files
- Source code: GPL Version 3.0
- https://codeberg.org/hut/outfly
- https://codeberg.org/outfly/outfly
- 3D models: Original art, placed under the Creative Commons CC0 License
- Photographs of celestial bodies:
- Mercury: [By Solar System Scope, CC BY 4.0](https://www.solarsystemscope.com/textures/)
@ -41,7 +41,11 @@
- https://pixabay.com/sound-effects/whoosh-blow-flutter-shortwav-14678/
- https://pixabay.com/sound-effects/dslr-camera-sounds-26117/
- https://pixabay.com/sound-effects/beep-6-96243
- Music: [Cinematic Cello](https://pixabay.com/music/build-up-scenes-cinematic-cello-115667) by [Aleksey Chistilin](https://pixabay.com/users/lexin_music-28841948/), [Pixabay Content License](https://pixabay.com/service/license-summary)
- Music:
- JupiterRecording.ogg is an [actual Jupiter recording by NASA](https://archive.org/download/voyager-1-and-2-1990-jupiter-nasa-voyager-space-sounds-electronic), public domain.
- Processed by cutting out min 1:47-3:47 and applying a 10s linear crossfade at the end. Exported as ogg with quality=3, see [.kdenlive file](src/audio/JupiterRecording.kdenlive).
- The source file has been taken down, and generally, I can't find information on how exactly this was produced. Hoping it's not a hoax.
- [Cinematic Cello](https://pixabay.com/music/build-up-scenes-cinematic-cello-115667) by [Aleksey Chistilin](https://pixabay.com/users/lexin_music-28841948/), [Pixabay Content License](https://pixabay.com/service/license-summary)
- Star chart based on the [HYG Stellar database](https://github.com/astronexus/HYG-Database)
- Font Yupiter-Regular.ttf is placed under the SIL OPEN FONT LICENSE Version 1.1 and is based on:
- Noto Sans Symbols 2, Copyright 2022 The Noto Project Authors (https://github.com/notofonts/symbols)

View file

@ -1,4 +1,4 @@
![OutFly Screenshot](doc/images/screenshot3.jpg)
![OutFly Screenshot](doc/branding/banner.jpg)
[Features](#features) • [Controls](#controls) • [Running OutFly](#running-outfly) • [Troubleshooting](#troubleshooting)
@ -16,43 +16,44 @@ Imagine a blend of [Fallout](https://en.wikipedia.org/wiki/Fallout_%28series%29)
This game aims to respect the player as much as possible. It doesn't waste your time: Despite the vastness of space, nothing takes too long. Speed cheats are active by default, allowing you to visit places you normally couldn't, without passing out from the g-forces. There are no anxiety-causing features (apart of, maybe, space itself), no loading screens, nothing to micromanage, not even save games. You can plunge into the game any time you feel like it, and it's up to you whether you just want to soak in the beautiful scenery, engage with the survival mechanics [still in development], or dive into the game story [still in development]. And finally, it's not just DRM-free but completely open source, allowing you to tinker on any part of the game to your liking.
Source code: https://codeberg.org/hut/outfly
Source code: https://codeberg.org/outfly/outfly
# Features
- A beautiful, serene atmosphere with gorgeous views
- Racing vehicles, unique NPCs, cozy places, deadly environment
- Accurate, clickable star chart. Can you spot the constellations?
- Cross platform, [free & open source](https://codeberg.org/hut/outfly) forever!
- Cross platform, [free & open source](https://codeberg.org/outfly/outfly) forever!
- Written in [Rust](https://www.rust-lang.org) with the [Bevy game engine](https://bevyengine.org)
- Status: Early access, not much content
# Controls
You can view these any time in game through the game menu (press Escape.)
Press **ESC** to view these any time from the in-game menu.
- Space: Slow down, match velocity
- E: Interact
- F: Flashlight
- Q: Exit vehicle
- M: Map
- C: Camera
- T: Cruise control
- R: Rotate (hold + move mouse)
- Y: Rotation stabilizer
- AWSD/Shift/Ctrl: Move
- J/K/U/L/I/O: Rotate
- F11: Fullscreen
- Tab: Toggle Augmented Reality
- Augmented Reality only:
- Left click: Target objects
- Right click: Zoom
- Cheats:
- G: Toggle cheats + invulnerability
- V/B: Impossible acceleration
- Shift+V/B: Extreme acceleration
- X: Teleport to target
- Z: Stop
- **Space**: Slow down, match velocity
- **E**: Interact
- **F**: Flashlight
- **Q**: Exit vehicle
- **M**: Map
- **C**: Camera
- **T**: Cruise control
- **R**: Rotate (hold + move mouse)
- **Y**: Rotation stabilizer
- **AWSD/Shift/Ctrl**: Move
- **J/K/U/L/I/O**: Rotate
- **F11**: Fullscreen
- **Tab**: Toggle Augmented Reality
- **Left click**: Target objects (in AR only)
- **Right click**: Zoom
Cheats:
- **G**: Toggle cheats + invulnerability
- **V/B**: Impossible acceleration
- **Shift+V/B**: Extreme acceleration
- **X**: Teleport to target
- **Z**: Stop
# Running OutFly
## System Requirements
@ -63,7 +64,7 @@ You can view these any time in game through the game menu (press Escape.)
## Running on Linux
1. Download and unpack the latest release: https://codeberg.org/hut/outfly/releases
1. Download and unpack the latest release: https://codeberg.org/outfly/outfly/releases
2. Open a terminal and navigate to the directory where you unpacked outfly
3. If you are on ArchLinux, type the following commands. For other distributions, replace "pacman -S" with the distro's command to install packages. Also, the packages may be called slightly differently.
@ -78,14 +79,16 @@ Alternatively, you can also install OutFly as a package, if your distribution ha
yay -S outfly-git
```
See also: [build instructions](https://codeberg.org/outfly/outfly/src/branch/main/build/README.md)
## Running on Windows
1. Download and unpack the latest release: https://codeberg.org/hut/outfly/releases
1. Download and unpack the latest release: https://codeberg.org/outfly/outfly/releases
2. Double-click on `OutFly.exe`
## Running on MacOS / Android / iOS
No releases for these operating systems exist yet. For MacOS, you can build OutFly yourself using the instructions in [HACKING.md](https://codeberg.org/hut/outfly/src/branch/main/HACKING.md). Support for Android/iOS is planned for the future.
No releases for these operating systems exist yet. For MacOS, you can build OutFly yourself using the [build instructions](https://codeberg.org/outfly/outfly/src/branch/main/build/README.md). Support for Android/iOS is planned for the future.
# Troubleshooting
## I'm stuck inside another object
@ -112,7 +115,7 @@ Try changing the full screen mode with the command line option "--fs-legacy":
cargo run -- --fs-legacy
```
If this doesn't work, please open an issue on https://codeberg.org/hut/outfly and provide as many details as you can, including the crash log.
If this doesn't work, please open an issue on https://codeberg.org/outfly/outfly and provide as many details as you can, including the crash log.
## Crash with "error while loading shared libraries"

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -90,7 +90,7 @@ python -m http.server -d wasm
## Building release versions optimized for packaging
To build release versions optimized for final deployment, build with the following features: (see also [pack.sh](https://codeberg.org/hut/outfly/src/branch/main/build/pack.sh))
To build release versions optimized for final deployment, build with the following features: (see also [pack.sh](https://codeberg.org/outfly/outfly/src/branch/main/build/pack.sh))
```
cargo build --release --no-default-features --features release_[linux|windows] [--target=$YOUR_TARGET]

View file

@ -13,5 +13,5 @@
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 "doc/branding/logo.png" "$rootdir/usr/share/pixmaps/outfly.png"
install -Dm644 "build/linux/outfly.desktop" "$rootdir/usr/share/applications/outfly.desktop"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 22 KiB

26
doc/branding/README.md Normal file
View file

@ -0,0 +1,26 @@
# OutFly Branding
## Colors
- Primary color: #BE1251 <span style="color:#BE1251">(pink)</span>
- Secondary color: #CCCCCC <span style="color:#CCCCCC">(white)</span>
- Warning color: #F0D50C <span style="color:#F0D50C">(golden)</span>
## Logo
[![logo](logo.png)](logo.png)
based on the [SVG Version](logo.svg), exported with Inkscape 1.3.2
## Sticker
[![sticker](sticker.jpg)](sticker.jpg)
based on the [SVG Version](sticker.svg), exported with Inkscape 1.3.2
Sticker parameters:
- Printing service provider: https://www.flyeralarm.com/de/c/druckprodukte/aufkleber/
- Type: Outdoor sticker
- Format: Square, 5x5 cm
- Material: 65 micrometer, silver, iridescent
- Colors: 4/0-colored

View file

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

BIN
doc/branding/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View file

@ -27,12 +27,12 @@
inkscape:deskcolor="#000000"
inkscape:document-units="px"
inkscape:zoom="2.3395291"
inkscape:cx="139.34428"
inkscape:cy="142.55005"
inkscape:cx="50.223782"
inkscape:cy="56.635329"
inkscape:window-width="2880"
inkscape:window-height="1627"
inkscape:window-height="1765"
inkscape:window-x="0"
inkscape:window-y="138"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
showgrid="true"
@ -56,10 +56,10 @@
style="color-interpolation-filters:sRGB"
id="filter1"
inkscape:label="bloom"
x="-0.26431716"
y="-0.26431324"
width="1.5286343"
height="1.5286265"><feConvolveMatrix
x="-0.28216198"
y="-0.2827584"
width="1.564324"
height="1.5655168"><feConvolveMatrix
order="1 1"
kernelMatrix="1.0000000 "
id="feConvolveMatrix1"
@ -84,13 +84,13 @@
id="g227"
style="fill:none;fill-opacity:1;stroke:#000000;stroke-width:3.96875;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter1)"
transform="matrix(0.79690616,0,0,0.79690616,6.8658634,6.7579172)"><circle
style="fill:none;fill-opacity:1;stroke:#a90e47;stroke-width:5.97623696;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
style="fill:none;fill-opacity:1;stroke:#870b39;stroke-width:5.97623696;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path1"
cx="33.848091"
cy="33.848091"
inkscape:label="circle"
r="21.166666" /><path
style="color:#000000;fill:#a90e47;stroke:none;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none;fill-opacity:1"
style="color:#000000;fill:#870b39;stroke:none;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none"
d="M 60.443093,7.4564407 C 57.786988,4.8003353 44.50646,10.112546 39.19425,15.424757 h 5.31221 c 2.656106,0 5.976237,-3.320131 8.632343,-0.664026 C 55.794908,17.416837 49.818671,28.705284 39.19425,39.329706 28.569828,49.954128 17.28138,55.930366 14.625274,53.27426 11.969169,50.618155 15.2893,47.298022 15.420264,44.641917 V 39.329706 C 9.9770896,44.641917 4.6648787,57.922444 7.3209841,60.57855 9.9770899,63.234655 25.913723,57.922444 41.850355,41.985812 57.786988,26.049179 63.099199,10.112546 60.443093,7.4564407 Z"
id="path2"
sodipodi:nodetypes="sccsssccsss" /></g></g></svg>

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
doc/branding/sticker.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 654 KiB

View file

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

View file

@ -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,31 @@ const MAX_INTERACT_DISTANCE: f32 = 50.0;
pub struct ActorPlugin;
impl Plugin for ActorPlugin {
fn build(&self, app: &mut App) {
app.add_systems(FixedUpdate, (
app.add_systems(
FixedUpdate,
(
update_physics_lifeforms,
update_power,
handle_wants_maxrotation,
handle_wants_maxvelocity,
));
app.add_systems(PostUpdate, handle_gforce
handle_wants_lookat.run_if(alive),
),
);
app.add_systems(
PostUpdate,
handle_gforce
.after(PhysicsSet::Sync)
.after(sync::position_to_transform));
app.add_systems(Update, (
.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(PostUpdate, (handle_vehicle_enter_exit,));
app.add_event::<VehicleEnterExitEvent>();
}
}
@ -66,7 +74,7 @@ pub struct VehicleEnterExitEvent {
driver: Entity,
name: Option<String>,
is_entering: bool,
is_player: bool
is_player: bool,
}
#[derive(Component)]
@ -112,26 +120,43 @@ pub struct ExperiencesGForce {
pub last_linear_velocity: DVec3,
pub ignore_gforce_seconds: f32,
}
impl Default for ExperiencesGForce { fn default() -> Self { Self {
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 WantsToLookAt(pub String);
#[derive(Component)]
pub struct Identifier(pub String);
#[derive(Component)]
pub struct LifeForm {
@ -140,20 +165,28 @@ pub struct LifeForm {
pub adrenaline_baseline: f32,
pub adrenaline_jolt: f32,
}
impl Default for LifeForm { fn default() -> Self { Self {
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 {
impl Default for Vehicle {
fn default() -> Self {
Self {
stored_drivers_collider: None,
}}}
}
}
}
#[derive(Copy, Clone, PartialEq)]
pub enum EngineType {
@ -192,7 +225,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,
@ -236,8 +273,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 +285,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 +314,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 +330,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 +356,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
@ -330,7 +376,7 @@ pub fn handle_input(
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 +386,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 +402,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 +413,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 +424,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 +474,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 +503,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 +527,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.05).clamp(1.001, 1.1)) as f64;
v_ang.0 *= angular_slowdown;
}
}
@ -480,18 +546,41 @@ fn handle_wants_maxvelocity(
if total > maxv.0 {
v.0 = DVec3::splat(0.0);
}
// already not moving
continue;
}
} else {
// 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() {
v.0 = DVec3::splat(0.0);
}
}
}
}
fn handle_wants_lookat(
mut query: Query<(&Position, &mut Rotation, &Transform, &WantsToLookAt)>,
q_playercam: Query<&Position, With<PlayerCamera>>,
id2pos: Res<game::Id2Pos>,
) {
let player_pos = if let Ok(player_pos) = q_playercam.get_single() {
player_pos
} else {
return;
};
// TODO: use ExternalTorque rather than hard-resetting the rotation
for (pos, mut rot, trans, target_id) in &mut query {
let target_pos = if target_id.0 == cmd::ID_SPECIAL_PLAYERCAM {
player_pos
} else if let Some(target_pos) = id2pos.0.get(&target_id.0) {
target_pos
} else {
continue;
};
rot.0 = look_at_quat(**pos, *target_pos, trans.up().as_dvec3());
}
}
fn handle_damage(
@ -507,8 +596,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 +623,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
}
}
}

View file

@ -10,45 +10,80 @@
//
// 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, play_zoom_sfx);
app.add_systems(Update, pause_all.run_if(on_event::<PauseAllSfxEvent>()));
app.add_systems(PostUpdate, play_sfx);
app.add_systems(PostUpdate, update_music);
app.add_systems(
Update,
(
play_zoom_sfx,
respawn_sinks.run_if(on_event::<RespawnSinksEvent>()),
pause_all.run_if(on_event::<PauseAllSfxEvent>()),
),
);
app.add_systems(
PostUpdate,
(
play_sfx,
toggle_music.run_if(on_event::<ToggleMusicEvent>()),
),
);
app.add_event::<PlaySfxEvent>();
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::BGMNoAR,
Sfx::BGMActualJupiterRecording,
"music/JupiterRecording.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"),
@ -58,6 +93,7 @@ const PATHS: &[(SfxType, Sfx, &str)] = &[
pub enum Sfx {
Achieve,
BGM,
BGMActualJupiterRecording,
Click,
Connect,
Crash,
@ -92,15 +128,21 @@ pub fn str2sfx(sfx_label: &str) -> Sfx {
pub enum SfxType {
BGM,
BGMNoAR,
LoopSfx,
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,
@ -135,12 +177,25 @@ pub fn respawn_sinks(
source,
settings: PlaybackSettings {
mode: PlaybackMode::Loop,
paused: settings.mute_music,
paused: settings.mute_music || !settings.hud_active,
..default()
},
},
));
}
SfxType::BGMNoAR => {
commands.spawn((
*sfx,
AudioBundle {
source,
settings: PlaybackSettings {
mode: PlaybackMode::Loop,
paused: settings.mute_music || settings.hud_active,
..default()
},
},
));
}
SfxType::LoopSfx => {
commands.spawn((
*sfx,
@ -154,8 +209,8 @@ pub fn respawn_sinks(
},
},
));
},
SfxType::OneOff => ()
}
SfxType::OneOff => (),
}
}
}
@ -179,25 +234,21 @@ pub fn play_sfx(
}
}
pub fn update_music(
mut events: EventReader<ToggleMusicEvent>,
q_audiosinks: Query<(&AudioSink, &Sfx)>,
settings: Res<var::Settings>,
) {
if !events.is_empty() {
events.clear();
pub fn toggle_music(q_audiosinks: Query<(&AudioSink, &Sfx)>, settings: Res<var::Settings>) {
for (bgm_sink, sfx) in &q_audiosinks {
if *sfx != Sfx::BGM {
let play = match *sfx {
Sfx::BGM => settings.hud_active,
Sfx::BGMActualJupiterRecording => !settings.hud_active,
_ => {
continue;
}
if settings.mute_music {
};
if settings.mute_music || !play {
bgm_sink.pause();
}
else {
} else {
bgm_sink.play();
}
}
}
}
pub fn play_zoom_sfx(
@ -211,7 +262,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 +272,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();
}

View file

@ -0,0 +1,418 @@
<?xml version='1.0' encoding='utf-8'?>
<mlt LC_NUMERIC="C" producer="main_bin" root="/home/hut/repos/outfly/src/audio" version="7.24.0">
<profile colorspace="709" description="HD 1080p 25 fps" display_aspect_den="9" display_aspect_num="16" frame_rate_den="1" frame_rate_num="25" height="1080" progressive="1" sample_aspect_den="1" sample_aspect_num="1" width="1920"/>
<chain id="chain2" out="00:09:59.600">
<property name="length">14991</property>
<property name="eof">pause</property>
<property name="resource">/home/hut/repos/outfly/additional/music/VOYAGER 1 and 2 - 1990 - Jupiter - [NASA Voyager Space Sounds][Electronic].mp3</property>
<property name="mlt_service">avformat</property>
<property name="meta.media.nb_streams">1</property>
<property name="meta.media.0.stream.type">audio</property>
<property name="meta.media.0.codec.sample_fmt">fltp</property>
<property name="meta.media.0.codec.sample_rate">44100</property>
<property name="meta.media.0.codec.channels">2</property>
<property name="meta.media.0.codec.layout">stereo</property>
<property name="meta.media.0.codec.name">mp3float</property>
<property name="meta.media.0.codec.long_name">MP3 (MPEG audio layer 3)</property>
<property name="meta.media.0.codec.bit_rate">160000</property>
<property name="meta.attr.encoder.markup">Lavf53.21.0</property>
<property name="meta.attr.track.markup">1</property>
<property name="meta.attr.date.markup">1990</property>
<property name="meta.attr.title.markup">Jupiter</property>
<property name="meta.attr.artist.markup">VOYAGER 1 and 2</property>
<property name="meta.attr.album.markup">NASA Voyager Space Sounds</property>
<property name="meta.attr.comment.markup">The sounds used on these recordings were taken from NASA Voyager I &amp; II. ... Jupiter, the fifth planet from the sun is the largest and most massive planet in the Solar System. In mass alone, it is three hundred times the mass of Earth. Jupiter is mostly composed of hydrogen and helium. The entire planet is made of gas, with no solid surface under the atmosphere. The pressures and temperatures deep in Jupiter are so high that gases form a gradual transition into liquids which are gradually compressed into a metallic "plasma" in which the molecules have been stripped of their outer electrons, The winds of Jupiter are a thousand meters per second relative to the rotating interior. Jupiter's magnetic field is four thousand times stronger than Earth's, and is tipped by 11° degrees of axis spin. This causes the magnetic field to wobble, which has a profound effect on trapped electronically charged particles. This plasma of charged particles is accelerated beyond the magnetosphere of Jupiter to speeds of tens of thousands of kilometres per second. It is these magnetic particle vibrations which generate some of the sounds you hear on this recording.</property>
<property name="meta.attr.genre.markup">Electronic</property>
<property name="seekable">1</property>
<property name="audio_index">0</property>
<property name="video_index">-1</property>
<property name="astream">0</property>
<property name="kdenlive:folderid">-1</property>
<property name="kdenlive:id">4</property>
<property name="kdenlive:clip_type">1</property>
<property name="kdenlive:file_size">11995402</property>
<property name="kdenlive:file_hash">abfa2a24311614b54ac4cd440263dd6a</property>
<property name="kdenlive:audio_max0">171</property>
<property name="kdenlive:monitorPosition">0</property>
</chain>
<producer id="producer0" in="00:00:00.000" out="00:22:00.000">
<property name="length">2147483647</property>
<property name="eof">continue</property>
<property name="resource">black</property>
<property name="aspect_ratio">1</property>
<property name="mlt_service">color</property>
<property name="kdenlive:playlistid">black_track</property>
<property name="mlt_image_format">rgba</property>
<property name="set.test_audio">0</property>
</producer>
<chain id="chain0" out="00:09:59.600">
<property name="length">14991</property>
<property name="eof">pause</property>
<property name="resource">/home/hut/repos/outfly/additional/music/VOYAGER 1 and 2 - 1990 - Jupiter - [NASA Voyager Space Sounds][Electronic].mp3</property>
<property name="mlt_service">avformat-novalidate</property>
<property name="seekable">1</property>
<property name="audio_index">0</property>
<property name="video_index">-1</property>
<property name="astream">0</property>
<property name="kdenlive:folderid">-1</property>
<property name="kdenlive:id">4</property>
<property name="kdenlive:clip_type">1</property>
<property name="kdenlive:file_size">11995402</property>
<property name="kdenlive:file_hash">abfa2a24311614b54ac4cd440263dd6a</property>
<property name="kdenlive:audio_max0">171</property>
<property name="meta.media.nb_streams">1</property>
<property name="meta.media.0.stream.type">audio</property>
<property name="meta.media.0.codec.sample_fmt">fltp</property>
<property name="meta.media.0.codec.sample_rate">44100</property>
<property name="meta.media.0.codec.channels">2</property>
<property name="meta.media.0.codec.layout">stereo</property>
<property name="meta.media.0.codec.name">mp3float</property>
<property name="meta.media.0.codec.long_name">MP3 (MPEG audio layer 3)</property>
<property name="meta.media.0.codec.bit_rate">160000</property>
<property name="meta.attr.encoder.markup">Lavf53.21.0</property>
<property name="meta.attr.track.markup">1</property>
<property name="meta.attr.date.markup">1990</property>
<property name="meta.attr.title.markup">Jupiter</property>
<property name="meta.attr.artist.markup">VOYAGER 1 and 2</property>
<property name="meta.attr.album.markup">NASA Voyager Space Sounds</property>
<property name="meta.attr.comment.markup">The sounds used on these recordings were taken from NASA Voyager I &amp; II. ... Jupiter, the fifth planet from the sun is the largest and most massive planet in the Solar System. In mass alone, it is three hundred times the mass of Earth. Jupiter is mostly composed of hydrogen and helium. The entire planet is made of gas, with no solid surface under the atmosphere. The pressures and temperatures deep in Jupiter are so high that gases form a gradual transition into liquids which are gradually compressed into a metallic "plasma" in which the molecules have been stripped of their outer electrons, The winds of Jupiter are a thousand meters per second relative to the rotating interior. Jupiter's magnetic field is four thousand times stronger than Earth's, and is tipped by 11° degrees of axis spin. This causes the magnetic field to wobble, which has a profound effect on trapped electronically charged particles. This plasma of charged particles is accelerated beyond the magnetosphere of Jupiter to speeds of tens of thousands of kilometres per second. It is these magnetic particle vibrations which generate some of the sounds you hear on this recording.</property>
<property name="meta.attr.genre.markup">Electronic</property>
<property name="xml">was here</property>
<property name="mute_on_pause">0</property>
<property name="set.test_audio">0</property>
<property name="set.test_image">1</property>
</chain>
<playlist id="playlist0">
<property name="kdenlive:audio_track">1</property>
<blank length="00:01:50.000"/>
<entry in="00:01:50.000" out="00:01:59.960" producer="chain0">
<property name="kdenlive:id">4</property>
<filter id="filter0" in="00:01:50.000" out="00:02:00.000">
<property name="window">75</property>
<property name="max_gain">20dB</property>
<property name="mlt_service">volume</property>
<property name="kdenlive_id">fadein</property>
<property name="gain">0</property>
<property name="end">1</property>
<property name="kdenlive:collapsed">0</property>
</filter>
<filter id="filter1">
<property name="window">75</property>
<property name="max_gain">20dB</property>
<property name="mlt_service">volume</property>
<property name="kdenlive_id">gain</property>
<property name="gain">1</property>
<property name="kdenlive:collapsed">0</property>
</filter>
</entry>
</playlist>
<playlist id="playlist1">
<property name="kdenlive:audio_track">1</property>
</playlist>
<tractor id="tractor0" in="00:00:00.000" out="00:01:59.960">
<property name="kdenlive:audio_track">1</property>
<property name="kdenlive:trackheight">141</property>
<property name="kdenlive:timeline_active">1</property>
<property name="kdenlive:collapsed">0</property>
<property name="kdenlive:thumbs_format"/>
<property name="kdenlive:audio_rec"/>
<track hide="video" producer="playlist0"/>
<track hide="video" producer="playlist1"/>
<filter id="filter2">
<property name="window">75</property>
<property name="max_gain">20dB</property>
<property name="mlt_service">volume</property>
<property name="internal_added">237</property>
<property name="disable">1</property>
</filter>
<filter id="filter3">
<property name="channel">-1</property>
<property name="mlt_service">panner</property>
<property name="internal_added">237</property>
<property name="start">0.5</property>
<property name="disable">1</property>
</filter>
<filter id="filter4">
<property name="iec_scale">0</property>
<property name="mlt_service">audiolevel</property>
<property name="dbpeak">1</property>
<property name="disable">1</property>
</filter>
</tractor>
<chain id="chain1" out="00:09:59.600">
<property name="length">14991</property>
<property name="eof">pause</property>
<property name="resource">/home/hut/repos/outfly/additional/music/VOYAGER 1 and 2 - 1990 - Jupiter - [NASA Voyager Space Sounds][Electronic].mp3</property>
<property name="mlt_service">avformat-novalidate</property>
<property name="seekable">1</property>
<property name="audio_index">0</property>
<property name="video_index">-1</property>
<property name="astream">0</property>
<property name="kdenlive:folderid">-1</property>
<property name="kdenlive:id">4</property>
<property name="kdenlive:clip_type">1</property>
<property name="kdenlive:file_size">11995402</property>
<property name="kdenlive:file_hash">abfa2a24311614b54ac4cd440263dd6a</property>
<property name="kdenlive:audio_max0">171</property>
<property name="meta.media.nb_streams">1</property>
<property name="meta.media.0.stream.type">audio</property>
<property name="meta.media.0.codec.sample_fmt">fltp</property>
<property name="meta.media.0.codec.sample_rate">44100</property>
<property name="meta.media.0.codec.channels">2</property>
<property name="meta.media.0.codec.layout">stereo</property>
<property name="meta.media.0.codec.name">mp3float</property>
<property name="meta.media.0.codec.long_name">MP3 (MPEG audio layer 3)</property>
<property name="meta.media.0.codec.bit_rate">160000</property>
<property name="meta.attr.encoder.markup">Lavf53.21.0</property>
<property name="meta.attr.track.markup">1</property>
<property name="meta.attr.date.markup">1990</property>
<property name="meta.attr.title.markup">Jupiter</property>
<property name="meta.attr.artist.markup">VOYAGER 1 and 2</property>
<property name="meta.attr.album.markup">NASA Voyager Space Sounds</property>
<property name="meta.attr.comment.markup">The sounds used on these recordings were taken from NASA Voyager I &amp; II. ... Jupiter, the fifth planet from the sun is the largest and most massive planet in the Solar System. In mass alone, it is three hundred times the mass of Earth. Jupiter is mostly composed of hydrogen and helium. The entire planet is made of gas, with no solid surface under the atmosphere. The pressures and temperatures deep in Jupiter are so high that gases form a gradual transition into liquids which are gradually compressed into a metallic "plasma" in which the molecules have been stripped of their outer electrons, The winds of Jupiter are a thousand meters per second relative to the rotating interior. Jupiter's magnetic field is four thousand times stronger than Earth's, and is tipped by 11° degrees of axis spin. This causes the magnetic field to wobble, which has a profound effect on trapped electronically charged particles. This plasma of charged particles is accelerated beyond the magnetosphere of Jupiter to speeds of tens of thousands of kilometres per second. It is these magnetic particle vibrations which generate some of the sounds you hear on this recording.</property>
<property name="meta.attr.genre.markup">Electronic</property>
<property name="xml">was here</property>
<property name="mute_on_pause">0</property>
<property name="set.test_audio">0</property>
<property name="set.test_image">1</property>
</chain>
<playlist id="playlist2">
<property name="kdenlive:audio_track">1</property>
<entry in="00:01:47.280" out="00:03:47.240" producer="chain1">
<property name="kdenlive:id">4</property>
<filter id="filter5" in="00:03:37.240" out="00:03:47.280">
<property name="window">75</property>
<property name="max_gain">20dB</property>
<property name="mlt_service">volume</property>
<property name="kdenlive_id">fadeout</property>
<property name="gain">1</property>
<property name="end">0</property>
<property name="kdenlive:collapsed">0</property>
</filter>
<filter id="filter6">
<property name="window">75</property>
<property name="max_gain">20dB</property>
<property name="mlt_service">volume</property>
<property name="kdenlive_id">gain</property>
<property name="gain">1</property>
<property name="kdenlive:collapsed">0</property>
<property name="disable">0</property>
</filter>
</entry>
</playlist>
<playlist id="playlist3">
<property name="kdenlive:audio_track">1</property>
</playlist>
<tractor id="tractor1" in="00:00:00.000" out="00:01:59.960">
<property name="kdenlive:audio_track">1</property>
<property name="kdenlive:trackheight">131</property>
<property name="kdenlive:timeline_active">1</property>
<property name="kdenlive:collapsed">0</property>
<property name="kdenlive:thumbs_format"/>
<property name="kdenlive:audio_rec"/>
<track hide="video" producer="playlist2"/>
<track hide="video" producer="playlist3"/>
<filter id="filter7">
<property name="window">75</property>
<property name="max_gain">20dB</property>
<property name="mlt_service">volume</property>
<property name="internal_added">237</property>
<property name="disable">1</property>
</filter>
<filter id="filter8">
<property name="channel">-1</property>
<property name="mlt_service">panner</property>
<property name="internal_added">237</property>
<property name="start">0.5</property>
<property name="disable">1</property>
</filter>
<filter id="filter9">
<property name="iec_scale">0</property>
<property name="mlt_service">audiolevel</property>
<property name="dbpeak">1</property>
<property name="disable">1</property>
</filter>
</tractor>
<tractor id="tractor2" in="00:00:00.000" out="00:01:59.960">
<property name="kdenlive:sequenceproperties.hasAudio">1</property>
<property name="kdenlive:sequenceproperties.hasVideo">0</property>
<property name="kdenlive:clip_type">1</property>
<property name="kdenlive:duration">00:02:00.000</property>
<property name="kdenlive:maxduration">3000</property>
<property name="kdenlive:clipname">Sequence 1</property>
<property name="kdenlive:description"/>
<property name="kdenlive:uuid">{700cf7a6-dd54-4622-9e00-6df4c0862a67}</property>
<property name="kdenlive:producer_type">17</property>
<property name="kdenlive:id">3</property>
<property name="kdenlive:file_hash">7e34583bd15531251a720488f71ecb03</property>
<property name="kdenlive:folderid">2</property>
<property name="kdenlive:sequenceproperties.activeTrack">1</property>
<property name="kdenlive:sequenceproperties.audioTarget">1</property>
<property name="kdenlive:sequenceproperties.disablepreview">0</property>
<property name="kdenlive:sequenceproperties.documentuuid">{700cf7a6-dd54-4622-9e00-6df4c0862a67}</property>
<property name="kdenlive:sequenceproperties.position">2999</property>
<property name="kdenlive:sequenceproperties.scrollPos">0</property>
<property name="kdenlive:sequenceproperties.tracks">4</property>
<property name="kdenlive:sequenceproperties.tracksCount">2</property>
<property name="kdenlive:sequenceproperties.verticalzoom">1</property>
<property name="kdenlive:sequenceproperties.videoTarget">2</property>
<property name="kdenlive:sequenceproperties.zonein">0</property>
<property name="kdenlive:sequenceproperties.zoneout">75</property>
<property name="kdenlive:sequenceproperties.zoom">10</property>
<property name="kdenlive:sequenceproperties.groups">[
]
</property>
<property name="kdenlive:sequenceproperties.guides">[
]
</property>
<track producer="producer0"/>
<track producer="tractor0"/>
<track producer="tractor1"/>
<transition id="transition0">
<property name="a_track">0</property>
<property name="b_track">1</property>
<property name="mlt_service">mix</property>
<property name="kdenlive_id">mix</property>
<property name="internal_added">237</property>
<property name="always_active">1</property>
<property name="accepts_blanks">1</property>
<property name="sum">1</property>
</transition>
<transition id="transition1">
<property name="a_track">0</property>
<property name="b_track">2</property>
<property name="mlt_service">mix</property>
<property name="kdenlive_id">mix</property>
<property name="internal_added">237</property>
<property name="always_active">1</property>
<property name="accepts_blanks">1</property>
<property name="sum">1</property>
</transition>
<filter id="filter10">
<property name="window">75</property>
<property name="max_gain">20dB</property>
<property name="mlt_service">volume</property>
<property name="internal_added">237</property>
<property name="disable">1</property>
</filter>
<filter id="filter11">
<property name="channel">-1</property>
<property name="mlt_service">panner</property>
<property name="internal_added">237</property>
<property name="start">0.5</property>
<property name="disable">1</property>
</filter>
</tractor>
<playlist id="main_bin">
<property name="kdenlive:folder.-1.2">Sequences</property>
<property name="kdenlive:sequenceFolder">2</property>
<property name="kdenlive:docproperties.activetimeline">{700cf7a6-dd54-4622-9e00-6df4c0862a67}</property>
<property name="kdenlive:docproperties.audioChannels">2</property>
<property name="kdenlive:docproperties.binsort">0</property>
<property name="kdenlive:docproperties.browserurl">/home/hut/repos/pages/data/videos/cellmade/</property>
<property name="kdenlive:docproperties.compositing">1</property>
<property name="kdenlive:docproperties.documentid">1716424483385</property>
<property name="kdenlive:docproperties.enableTimelineZone">0</property>
<property name="kdenlive:docproperties.enableexternalproxy">0</property>
<property name="kdenlive:docproperties.enableproxy">0</property>
<property name="kdenlive:docproperties.externalproxyparams"/>
<property name="kdenlive:docproperties.generateimageproxy">0</property>
<property name="kdenlive:docproperties.generateproxy">0</property>
<property name="kdenlive:docproperties.guidesCategories">[
{
"color": "#9b59b6",
"comment": "Category 1",
"index": 0
},
{
"color": "#3daee9",
"comment": "Category 2",
"index": 1
},
{
"color": "#1abc9c",
"comment": "Category 3",
"index": 2
},
{
"color": "#1cdc9a",
"comment": "Category 4",
"index": 3
},
{
"color": "#c9ce3b",
"comment": "Category 5",
"index": 4
},
{
"color": "#fdbc4b",
"comment": "Category 6",
"index": 5
},
{
"color": "#f39c1f",
"comment": "Category 7",
"index": 6
},
{
"color": "#f47750",
"comment": "Category 8",
"index": 7
},
{
"color": "#da4453",
"comment": "Category 9",
"index": 8
}
]
</property>
<property name="kdenlive:docproperties.kdenliveversion">24.02.2</property>
<property name="kdenlive:docproperties.opensequences">{700cf7a6-dd54-4622-9e00-6df4c0862a67}</property>
<property name="kdenlive:docproperties.previewextension"/>
<property name="kdenlive:docproperties.previewparameters"/>
<property name="kdenlive:docproperties.profile">atsc_1080p_25</property>
<property name="kdenlive:docproperties.proxyextension"/>
<property name="kdenlive:docproperties.proxyimageminsize">2000</property>
<property name="kdenlive:docproperties.proxyimagesize">800</property>
<property name="kdenlive:docproperties.proxyminsize">1000</property>
<property name="kdenlive:docproperties.proxyparams"/>
<property name="kdenlive:docproperties.proxyresize">640</property>
<property name="kdenlive:docproperties.rendercategory">Audio only</property>
<property name="kdenlive:docproperties.rendercustomquality">30</property>
<property name="kdenlive:docproperties.renderendguide">-1</property>
<property name="kdenlive:docproperties.renderexportaudio">0</property>
<property name="kdenlive:docproperties.renderfullcolorrange">0</property>
<property name="kdenlive:docproperties.rendermode">0</property>
<property name="kdenlive:docproperties.renderplay">0</property>
<property name="kdenlive:docproperties.renderpreview">0</property>
<property name="kdenlive:docproperties.renderprofile">OGG</property>
<property name="kdenlive:docproperties.renderrescale">0</property>
<property name="kdenlive:docproperties.renderrescaleheight">540</property>
<property name="kdenlive:docproperties.renderrescalewidth">960</property>
<property name="kdenlive:docproperties.renderspeed">1</property>
<property name="kdenlive:docproperties.renderstartguide">-1</property>
<property name="kdenlive:docproperties.rendertcoverlay">0</property>
<property name="kdenlive:docproperties.rendertctype">-1</property>
<property name="kdenlive:docproperties.rendertwopass">0</property>
<property name="kdenlive:docproperties.renderurl">/home/hut/repos/outfly/assets/music/JupiterRecording.ogg</property>
<property name="kdenlive:docproperties.seekOffset">30000</property>
<property name="kdenlive:docproperties.uuid">{700cf7a6-dd54-4622-9e00-6df4c0862a67}</property>
<property name="kdenlive:docproperties.version">1.1</property>
<property name="kdenlive:expandedFolders"/>
<property name="kdenlive:binZoom">4</property>
<property name="kdenlive:documentnotes"/>
<property name="kdenlive:docmetadata.meta.attr.Album.markup">NASA Voyager Space Sounds</property>
<property name="kdenlive:docmetadata.meta.attr.Artist.markup">VOYAGER 1 and 2</property>
<property name="kdenlive:docmetadata.meta.attr.Genre.markup">Electronic</property>
<property name="kdenlive:docmetadata.meta.attr.copyright.markup">Public Domain</property>
<property name="kdenlive:docmetadata.meta.attr.title.markup">Jupiter Recording</property>
<property name="kdenlive:docmetadata.meta.attr.year.markup">1990</property>
<property name="xml_retain">1</property>
<entry in="00:00:00.000" out="00:09:59.600" producer="chain2"/>
<entry in="00:00:00.000" out="00:03:29.960" producer="tractor2"/>
</playlist>
<tractor id="tractor3" in="00:00:00.000" out="00:01:59.960">
<property name="kdenlive:projectTractor">1</property>
<track in="00:00:00.000" out="00:01:59.960" producer="tractor2"/>
</tractor>
</mlt>

Binary file not shown.

BIN
src/blender/wings.blend Normal file

Binary file not shown.

View file

@ -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
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(TransformSystem::TransformPropagate),
);
app.add_systems(
PostUpdate,
update_mapcam_center
.before(sync::position_to_transform)
.in_set(sync::SyncSet::PositionToTransform));
.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
app.add_systems(
PostUpdate,
position_to_transform
.after(sync::position_to_transform)
.in_set(sync::SyncSet::PositionToTransform));
.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,21 +309,24 @@ 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 settings.hud_active && mouse_input.pressed(settings.key_zoom) {
if mouse_input.pressed(settings.key_zoom) {
fov = settings.zoom_fov.to_radians();
if !settings.is_zooming {
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>();
}
}
}
@ -362,20 +414,24 @@ fn manage_player_actor(
#[allow(clippy::too_many_arguments)]
pub fn apply_input_to_player(
time: Res<Time>,
mut commands: Commands,
settings: Res<var::Settings>,
windows: Query<&Window, With<PrimaryWindow>>,
mut mouse_events: EventReader<MouseMotion>,
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<
(
Entity,
&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 +446,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 +457,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_entity, player_transform, mut engine, 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 +483,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,7 +502,8 @@ 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 {
let forward_factor = engine.current_warmup
* (if axis_input.z > 0.0 {
engine.thrust_forward
} else {
engine.thrust_back
@ -452,8 +513,10 @@ pub fn apply_input_to_player(
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 +524,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 +533,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 +559,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;
}
@ -514,29 +582,27 @@ pub fn apply_input_to_player(
}
}
let slowrot = settings.rotation_stabilizer_active || key_input.pressed(settings.key_stop);
let angular_slowdown: f64 = if slowrot {
(2.0 - engine.reaction_wheels.powf(0.05).clamp(1.001, 1.1)) as f64
} else {
1.0
};
if pitch_yaw_rot.length_squared() > 1.0e-18 {
let mouse_speed = pitch_yaw_rot.length();
let mouse_moving = mouse_speed > EPSILON32;
if mouse_moving {
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])));
angularvelocity.0 *= angular_slowdown.clamp(0.97, 1.0) as f64;
}
else {
if angularvelocity.length_squared() > 1.0e-18 {
angularvelocity.0 *= angular_slowdown;
}
else {
angularvelocity.0 = DVec3::splat(0.0);
player_transform.rotation
* Vec3::new(pitch_yaw_rot[0], pitch_yaw_rot[1], pitch_yaw_rot[2]),
));
}
if settings.rotation_stabilizer_active || key_input.pressed(settings.key_stop) {
commands
.entity(player_entity)
.try_insert(actor::WantsMaxRotation(mouse_speed as f64 * 0.1));
} else {
commands
.entity(player_entity)
.remove::<actor::WantsMaxRotation>();
}
let mut sinks: HashMap<audio::Sfx, &AudioSink> = HashMap::new();
@ -563,8 +629,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 +647,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 +655,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 +693,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,17 +715,18 @@ 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);
@ -669,8 +746,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 +754,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 +773,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

View file

@ -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] = &[
@ -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, (
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,18 +728,23 @@ 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: {
ChatEvent::SpawnChoice(replytext, _key, goto, nowait, condition) => 'out: {
if let Some(condition) = condition {
if !vars.evaluate_condition(condition, &chat.talker.actor_id) {
break 'out;
@ -735,14 +756,13 @@ pub fn handle_chat_events(
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,12 +842,12 @@ pub fn handle_chat_scripts(
// Process the script
match name {
"refilloxygen" => if let Ok(mut amount) = param1.to_string().parse::<f32>() {
"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 {
} else {
let mut found_other = false;
info!("param2={}", param2);
for (other_actor, mut other_suit) in q_actor.iter_mut() {
@ -833,7 +860,8 @@ pub fn handle_chat_scripts(
.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);
suit.oxygen =
(suit.oxygen + amount).clamp(0.0, suit.oxygen_max);
break;
}
}
@ -845,6 +873,7 @@ pub fn handle_chat_scripts(
} else {
error!("Invalid parameter for command `{}`: `{}`", name, param1);
}
}
"repairsuit" => {
ew_achievement.send(game::AchievementEvent::RepairSuit);
for (_, mut suit, _) in q_player.iter_mut() {
@ -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);

View file

@ -11,13 +11,14 @@
// 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;
pub const ID_SPECIAL_PLAYERCAM: &str = "PLAYERCAMERA";
pub const ID_EARTH: &str = "earth";
pub const ID_SOL: &str = "sol";
pub const ID_JUPITER: &str = "jupiter";
@ -28,14 +29,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,
@ -80,6 +85,7 @@ struct ParserState {
has_ring: bool,
wants_maxrotation: Option<f64>,
wants_maxvelocity: Option<f64>,
wants_tolookat_id: Option<String>,
collider_is_mesh: bool,
collider_is_one_mesh_of_scene: bool,
thrust_forward: f32,
@ -136,6 +142,7 @@ impl Default for ParserState {
has_ring: false,
wants_maxrotation: None,
wants_maxvelocity: None,
wants_tolookat_id: None,
collider_is_mesh: false,
collider_is_one_mesh_of_scene: false,
thrust_forward: default_engine.thrust_forward,
@ -157,11 +164,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 +188,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;
}
@ -206,6 +215,69 @@ pub fn load_defs(
debug!("Registering name: {}", name);
state.name = Some(name.to_string());
}
["template", "person"] => {
// command: collider handcrafted
state.collider_is_one_mesh_of_scene = true;
// command: alive yes
state.is_alive = true;
state.is_lifeform = true;
state.is_suited = true;
// command: oxygen 0.864
state.is_lifeform = true;
state.is_suited = true;
state.oxygen = nature::OXY_D;
// command: engine monopropellant
state.engine_type = actor::EngineType::Monopropellant;
// command: thrust 1.2 1 1 14 1.5
state.thrust_forward = 1.2;
state.thrust_back = 1.0;
state.thrust_sideways = 1.0;
state.reaction_wheels = 14.0;
state.warmup_seconds = 1.5;
// command: wants maxrotation 0
state.wants_maxrotation = Some(0.0);
// command: wants maxvelocity 0
state.wants_maxvelocity = Some(0.0);
// command: pointofinterest yes
state.is_point_of_interest = true;
// command: density 200
state.density = 200.0;
// command: angularmomentum 0 0 0
state.angular_momentum = DVec3::ZERO;
}
["template", "clippy"] => {
// command: angularmomentum 0 0 0
state.angular_momentum = DVec3::ZERO;
// command: wants maxrotation 0
state.wants_maxrotation = Some(0.0);
// command: wants maxvelocity 0
state.wants_maxvelocity = Some(0.0);
// command: thrust 15 6 3 400 0.5
state.thrust_forward = 15.0;
state.thrust_back = 6.0;
state.thrust_sideways = 3.0;
state.reaction_wheels = 400.0;
state.warmup_seconds = 0.5;
// command: scale 3
state.model_scale = 3.0;
// command: pronoun it
state.pronoun = Some("it".to_string());
// command: pointofinterest yes
state.is_point_of_interest = true;
}
// Parsing actors
["actor", x, y, z, model] => {
@ -214,10 +286,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 +300,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 +316,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 +325,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 +335,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;
}
@ -286,6 +354,9 @@ pub fn load_defs(
state.is_lifeform = true;
state.is_suited = true;
}
["alive", "no"] => {
state.is_alive = false;
}
["vehicle", "yes"] => {
state.is_vehicle = true;
}
@ -312,8 +383,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 +397,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 +405,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 +413,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 +421,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 +430,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 +488,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 +496,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 +507,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 +535,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,37 +545,43 @@ 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;
}
}
["wants", "maxrotation", "none"] => {
state.wants_maxrotation = None;
}
["wants", "maxrotation", value] => {
// 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;
}
}
["wants", "maxvelocity", "none"] => {
state.wants_maxvelocity = None;
}
["wants", "maxvelocity", value] => {
// 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;
}
}
["wants", "lookat", id] => {
// NOTE: Will not work if the actor has no engine
state.wants_tolookat_id = Some(id.to_string());
}
["armodel", asset_name] => {
state.ar_model = Some(asset_name.to_string());
}
@ -511,8 +591,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;
}
@ -534,6 +613,7 @@ fn spawn_entities(
mut materials_jupiter: ResMut<Assets<load::JupitersRing>>,
mut id2pos: ResMut<game::Id2Pos>,
mut achievement_tracker: ResMut<var::AchievementTracker>,
mut ew_updateavatar: EventWriter<hud::UpdateAvatarEvent>,
settings: Res<var::Settings>,
) {
for state_wrapper in er_spawn.read() {
@ -543,9 +623,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 +647,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,13 +659,15 @@ fn spawn_entities(
}
absolute_pos += nature::phase_dist_to_coords(-phase_radians, r);
}
let scale = Vec3::splat(if state.is_sun {
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);
} * state.model_scale,
);
// Spawn the actor
let actor_entity;
@ -638,23 +720,24 @@ fn spawn_entities(
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));
&Collider::sphere(0.5 * state.model_scale as f64),
state.density,
));
actor.insert(AsyncSceneCollider::new(Some(
ComputedCollider::TriMesh
//ComputedCollider::ConvexDecomposition(VHACDParameters::default())
ComputedCollider::TriMesh, //ComputedCollider::ConvexDecomposition(VHACDParameters::default())
)));
}
else if state.collider_is_one_mesh_of_scene {
} 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)
&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)
.with_layers_for_name("Collider", CollisionLayers::ALL), //.with_density_for_name("Collider", state.density)
);
actor.insert(NeedsSceneColliderRemoved);
}
else {
} else {
actor.insert(state.collider.clone());
}
}
@ -664,6 +747,8 @@ fn spawn_entities(
if state.is_player {
actor.insert(actor::Player);
actor.insert(actor::PlayerCamera);
actor.insert(hud::AugmentedRealityOverlayBroadcaster);
ew_updateavatar.send(hud::UpdateAvatarEvent);
}
if state.is_sun {
let (r, g, b) = nature::star_color_index_to_rgb(0.656);
@ -672,10 +757,7 @@ fn spawn_entities(
unlit: true,
..default()
}));
actor.insert((
NotShadowCaster,
NotShadowReceiver,
));
actor.insert((NotShadowCaster, NotShadowReceiver));
}
if state.is_targeted_on_startup {
actor.insert(hud::IsTargeted);
@ -683,7 +765,7 @@ fn spawn_entities(
if let Some((mindist, id)) = &state.show_only_in_map_at_distance {
actor.insert(camera::ShowOnlyInMap {
min_distance: *mindist,
distance_to_id: id.clone()
distance_to_id: id.clone(),
});
}
if state.is_player || state.is_vehicle {
@ -714,6 +796,9 @@ fn spawn_entities(
if let Some(value) = state.wants_maxvelocity {
actor.insert(actor::WantsMaxVelocity(value));
}
if let Some(value) = &state.wants_tolookat_id {
actor.insert(actor::WantsToLookAt(value.clone()));
}
if let Some(color) = state.light_color {
actor.insert((
PointLight {
@ -791,7 +876,7 @@ fn spawn_entities(
},
visibility: Visibility::Hidden,
..default()
}
},
));
});
}
@ -802,6 +887,7 @@ fn spawn_entities(
let mut entitycmd = commands.spawn((
hud::AugmentedRealityOverlay {
owner: actor_entity,
scale: 1.0,
},
world::DespawnOnPlayerDeath,
SpatialBundle {
@ -837,7 +923,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 +947,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 +960,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 {

View file

@ -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,19 @@ 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;
}
pub fn look_at_quat(from: DVec3, to: DVec3, up: DVec3) -> DQuat {
let direction = (to - from).normalize();
let right = up.cross(direction).normalize();
let corrected_up = direction.cross(right);
let mat3 = bevy::math::DMat3::from_cols(right, corrected_up, direction);
DQuat::from_mat3(&mat3)
}

View file

@ -257,18 +257,15 @@ actor 0 0 0
actor 0 593051 0 suitv2
template person
relativeto jupiter
orbit 221900e3 0.338
player yes
id player
density 200
collider handcrafted
wants maxvelocity none
oxygen 0.008
health 0.3
angularmomentum 0 0 0
thrust 1.2 1 1 14 1.5
rotationy 135
engine monopropellant
actor 10 -30 20 cruiser
name "Cruiser"
@ -284,18 +281,12 @@ actor 10 -30 20 cruiser
pointofinterest yes
actor -55e3 44e3 0 suitv2
template person
relativeto thebe
id yuni
name "Yuni"
chatid Yuni
density 200
collider handcrafted
thrust 1.2 1 1 20 1.5
rotationx 180
engine monopropellant
wants maxrotation 0
wants maxvelocity 0
pointofinterest yes
actor 5000 0 -3000 moonlet
name Moonlet
@ -315,26 +306,16 @@ actor 13200 300 -3000 hollow_asteroid
pointofinterest yes
angularmomentum 0 0.015 0
actor 0 0 0 suitv2
template person
relativeto cultasteroid
name "Ash"
chatid Ash
alive yes
collider handcrafted
thrust 1.2 1 1 20 1.5
wants maxrotation 0
wants maxvelocity 0
angularmomentum 0 0 0
pronoun they
actor -8 8 0 suitv2
template person
relativeto cultasteroid
name "River"
chatid River
alive yes
collider handcrafted
thrust 1.2 1 1 20 1.5
wants maxrotation 0
wants maxvelocity 0
angularmomentum 0 0 0
rotationy 54
rotationx -90
pronoun she
@ -382,10 +363,11 @@ actor -200 -110 1000 satellite
actor 1000 20 300 monolith
name "Mysterious Monolith 1"
relativeto player
scale 2
scale 4
density 300
rotationx 90
wants maxrotation 0.01
wants lookat cultasteroid
angularmomentum 0.0 0.0 0.01
thrust 0 0 0 30 1
collider mesh
@ -393,10 +375,11 @@ actor 1000 20 300 monolith
actor 10000 2000 -3500 monolith
name "Mysterious Monolith 2"
relativeto player
scale 2
scale 4
density 300
rotationx 90
wants maxrotation 0.01
wants lookat cultasteroid
angularmomentum 0.0 0.0 0.01
thrust 0 0 0 30 1
collider mesh
@ -404,10 +387,11 @@ actor 10000 2000 -3500 monolith
actor -8000 -1000 -100 monolith
name "Mysterious Monolith 3"
relativeto player
scale 2
scale 4
density 300
rotationx 90
wants maxrotation 0.01
wants lookat cultasteroid
angularmomentum 0.0 0.0 0.01
thrust 0 0 0 30 1
collider mesh
@ -453,46 +437,35 @@ actor -3300 10 0 pizzeria
scale 0.25
light FF8F4A 5000000
actor -33 0 4 clippy
template clippy
name "Clippy™ Convenience Companion"
relativeto pizzeria
armodel clippy_ar
angularmomentum 0 0 0
wants maxrotation 0
wants maxvelocity 0
thrust 15 6 3 400 0.5
wants lookat PLAYERCAMERA
rotationy -126
scale 3
chatid SubduedClippy
pronoun it
actor -45 -4 -4 suitv2
template person
relativeto pizzeria
name "Nox"
chatid PizzaChef
armodel suit_ar_chefhat
alive yes
collider handcrafted
thrust 1.2 1 1 20 1.5
wants maxrotation 0
wants maxvelocity 0
wants lookat PLAYERCAMERA
rotationy -90
angularmomentum 0 0 0
pronoun he
actor 30 -12 -40 suitv2
template person
relativeto player
name Icarus
id Icarus
chatid Icarus
alive yes
collider handcrafted
armodel suit_ar_wings
angularmomentum 0.4 0.2 0.1
wants maxrotation 0.5
rotationy 108
rotationx 180
pointofinterest yes
thrust 1.2 1 1 20 1.5
wants maxrotation 0.5
wants maxvelocity 0
pronoun it
actor 12 -35 -27 lightorb
name "Light Orb"
@ -516,29 +489,24 @@ actor 30 -12 -40 suitv2
light FF8F4A 5000000
actor -300 0 40 suitv2
template person
relativeto player
id Drifter
name "梓涵"
chatid Drifter
alive no
oxygen 0.08
pointofinterest yes
collider handcrafted
pronoun she
actor 100 -18000 2000 clippy
template clippy
relativeto "player"
id "busstopclippy"
name "StarTrans Clippy™ Serenity Station"
armodel clippy_ar
angularmomentum 0 0 0
wants maxrotation 0
wants maxvelocity 0
thrust 15 6 3 400 0.5
pointofinterest yes
wants lookat PLAYERCAMERA
rotationy -90
scale 3
chatid ClippyTransSerenity
pronoun it
actor 60 0 0 "orb_busstop"
name "StarTrans Bus Stop: Serenity Station"
@ -667,29 +635,21 @@ actor 100 -18000 2000 clippy
scale 5
actor 8 20 0 suitv2
template person
relativeto "busstopclippy"
name "Rudy"
wants maxrotation 0.2
wants maxvelocity 0
thrust 1.2 1 1 20 1.5
collider capsule 1 0.5
chatid NPCinCryoStasis
pronoun he
actor -184971e3 149410e3 -134273e3 clippy
template clippy
relativeto jupiter
id "busstopclippy2"
name "StarTrans Clippy™ Farview Station"
armodel clippy_ar
angularmomentum 0 0 0
wants maxrotation 0
wants maxvelocity 0
thrust 15 6 3 400 0.5
pointofinterest yes
wants lookat PLAYERCAMERA
rotationy -90
scale 3
chatid ClippyTransFarview
pronoun it
actor 60 0 0 "orb_busstop"
name "StarTrans Bus Stop: Farview Station"
@ -819,21 +779,16 @@ actor -184971e3 149410e3 -134273e3 clippy
actor 0 -44e3 0 clippy
template clippy
relativeto jupiter
id "busstopclippy3"
name "StarTrans Clippy™ Metis Prime Station"
armodel clippy_ar
wants lookat PLAYERCAMERA
orbitaround jupiter 128000e3
orbit_phase_offset -0.002
angularmomentum 0 0 0
wants maxrotation 0
wants maxvelocity 0
thrust 15 6 3 400 0.5
pointofinterest yes
rotationy -90
scale 3
chatid ClippyTransMetis
pronoun it
actor 60 0 0 "orb_busstop"
name "StarTrans Bus Stop: Metis Prime Station"

View file

@ -11,9 +11,7 @@ AWSD/Shift/Ctrl: Move
J/K/U/L/I/O: Rotate
F11: Fullscreen
Tab: Toggle Augmented Reality
Augmented Reality only:
Left click: Target objects
Left click: Target objects (in AR only)
Right click: Zoom
Cheats:

View file

@ -9,7 +9,7 @@
# + ▀████████████████████████████████████████████████████▀
#
# User preferences for the game OutFly.
# See https://codeberg.org/hut/outfly
# See https://codeberg.org/outfly/outfly
# fullscreen_mode may be "borderless", "legacy", or "sized"
fullscreen_mode = "borderless"

View file

@ -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,31 @@ 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(var::Settings::default());
app.insert_resource(var::GameVars::default());
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 +135,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 +147,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 +158,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,
);
}
}
}
@ -165,6 +178,7 @@ fn handle_player_death(
mut ew_effect: EventWriter<visual::SpawnEffectEvent>,
mut ew_deathscreen: EventWriter<menu::DeathScreenEvent>,
mut log: ResMut<hud::Log>,
mut gamevars: ResMut<var::GameVars>,
mut settings: ResMut<Settings>,
) {
for death in er_playerdies.read() {
@ -172,6 +186,7 @@ fn handle_player_death(
return;
}
settings.reset_player_settings();
gamevars.reset();
active_asteroids.0.clear();
for entity in &q_noscenes {
cmd.entity(entity).despawn();
@ -234,9 +249,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 +287,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 +305,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 +351,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 +362,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 +397,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 +444,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);

View file

@ -11,9 +11,10 @@
// This module manages the heads-up display and augmented reality overlays.
use crate::prelude::*;
use bevy::diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin};
use bevy::pbr::{NotShadowCaster, NotShadowReceiver};
use bevy::prelude::*;
use bevy::diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin};
use bevy::scene::SceneInstance;
use bevy::transform::TransformSystem;
use bevy_xpbd_3d::prelude::*;
use std::collections::VecDeque;
@ -41,20 +42,34 @@ pub const DASHBOARD_DEF: &[(Dashboard, &str)] = &[
(Dashboard::Radioactivity, "radioactivity"),
];
// Player avatars: [(Avatar, model name, scale, in-game name)]
pub const PLAYER_AR_AVATARS: &[(Avatar, &str, f32, &str)] = &[
(Avatar::None, "", 1.0, "No Avatar"),
(Avatar::ChefHat, "suit_ar_chefhat", 1.0, "Chef Hat"),
(Avatar::Wings, "suit_ar_wings", 1.0, "Wings"),
(Avatar::Asteroid, "asteroid2", 1.2, "Asteroid"),
];
pub struct HudPlugin;
impl Plugin for HudPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, setup);
app.add_systems(Update, (
app.add_systems(
Update,
(
update_hud,
update_dashboard,
update_speedometer,
update_gauges,
handle_input.run_if(in_control),
handle_target_event,
));
app.add_systems(PostUpdate, (
),
);
app.add_systems(
PostUpdate,
(
update_overlay_visibility,
update_avatar.run_if(on_event::<UpdateAvatarEvent>()),
update_ar_overlays
.after(camera::position_to_transform)
.in_set(sync::SyncSet::PositionToTransform),
@ -65,7 +80,8 @@ impl Plugin for HudPlugin {
.after(PhysicsSet::Sync)
.after(camera::apply_input_to_player)
.before(TransformSystem::TransformPropagate),
));
),
);
app.insert_resource(AugmentedRealityState {
overlays_visible: false,
});
@ -73,29 +89,52 @@ impl Plugin for HudPlugin {
logs: VecDeque::with_capacity(LOG_MAX),
needs_rerendering: true,
});
app.insert_resource(FPSUpdateTimer(
Timer::from_seconds(HUD_REFRESH_TIME, TimerMode::Repeating)));
app.insert_resource(FPSUpdateTimer(Timer::from_seconds(
HUD_REFRESH_TIME,
TimerMode::Repeating,
)));
app.add_event::<TargetEvent>();
app.add_event::<UpdateAvatarEvent>();
app.add_event::<UpdateOverlayVisibility>();
}
}
#[derive(Event)] pub struct TargetEvent(pub Option<Entity>);
#[derive(Event)] pub struct UpdateOverlayVisibility;
#[derive(Component)] struct NodeHud;
#[derive(Component)] struct NodeConsole;
#[derive(Component)] struct NodeChoiceText;
#[derive(Component)] struct NodeSpeedometerText;
#[derive(Component)] struct NodeCurrentChatLine;
#[derive(Component)] struct Reticule;
#[derive(Component)] struct Speedometer;
#[derive(Component)] struct Speedometer2;
#[derive(Component)] struct GaugeLength(f32);
#[derive(Component)] pub struct ToggleableHudElement;
#[derive(Component)] pub struct ToggleableHudMapElement;
#[derive(Component)] struct Selectagon;
#[derive(Component)] pub struct IsTargeted;
#[derive(Component)] pub struct PointOfInterestMarker(pub Entity);
#[derive(Event)]
pub struct TargetEvent(pub Option<Entity>);
#[derive(Event)]
pub struct UpdateOverlayVisibility;
#[derive(Event)]
pub struct UpdateAvatarEvent;
#[derive(Component)]
struct NodeHud;
#[derive(Component)]
struct NodeConsole;
#[derive(Component)]
struct NodeChoiceText;
#[derive(Component)]
struct NodeSpeedometerText;
#[derive(Component)]
struct NodeCurrentChatLine;
#[derive(Component)]
struct Reticule;
#[derive(Component)]
struct Speedometer;
#[derive(Component)]
struct Speedometer2;
#[derive(Component)]
struct GaugeLength(f32);
#[derive(Component)]
pub struct ToggleableHudElement;
#[derive(Component)]
pub struct ToggleableHudMapElement;
#[derive(Component)]
struct Selectagon;
#[derive(Component)]
struct PlayerAvatar;
#[derive(Component)]
pub struct IsTargeted;
#[derive(Component)]
pub struct PointOfInterestMarker(pub Entity);
#[derive(Component, Debug, Copy, Clone)]
pub enum Dashboard {
@ -119,15 +158,24 @@ pub struct AugmentedRealityState {
pub overlays_visible: bool,
}
#[derive(Component)] pub struct AugmentedRealityOverlayBroadcaster;
#[derive(Component)]
pub struct AugmentedRealityOverlayBroadcaster;
#[derive(Component)]
pub struct AugmentedRealityOverlay {
pub owner: Entity,
pub scale: f32,
}
#[derive(Resource)]
struct FPSUpdateTimer(Timer);
pub enum Avatar {
None,
ChefHat,
Wings,
Asteroid,
}
pub enum LogLevel {
Achievement,
Always,
@ -155,8 +203,7 @@ impl Message {
pub fn format(&self) -> String {
if self.sender.is_empty() {
return self.text.clone() + "\n";
}
else {
} else {
return format!("{}: {}\n", self.sender, self.text);
}
}
@ -168,11 +215,15 @@ pub struct IsClickable {
pub pronoun: Option<String>,
pub distance: Option<f64>,
}
impl Default for IsClickable { fn default() -> Self { Self {
impl Default for IsClickable {
fn default() -> Self {
Self {
name: None,
pronoun: None,
distance: None,
}}}
}
}
}
#[derive(Resource)]
pub struct Log {
@ -277,33 +328,33 @@ pub fn setup(
let mut bundle_fps = TextBundle::from_sections([
TextSection::new("", style), // Target
TextSection::new("", style_fps), // Frames per second
]).with_style(Style {
])
.with_style(Style {
position_type: PositionType::Absolute,
top: Val::VMin(2.0),
left: Val::VMin(3.0),
..default()
}).with_text_justify(JustifyText::Left);
})
.with_text_justify(JustifyText::Left);
bundle_fps.visibility = visibility;
commands.spawn((
NodeHud,
ToggleableHudElement,
bundle_fps,
));
commands.spawn((NodeHud, ToggleableHudElement, bundle_fps));
// Add Console
// This one is intentionally NOT a ToggleableHudElement. Instead, console entries
// are filtered based on whether the hud is active or not. LogLevel::Always is
// even shown when hud is inactive.
let bundle_chatbox = TextBundle::from_sections((0..LOG_MAX_ROWS).map(|_|
TextSection::new("", style_console.clone()))
).with_style(Style {
let bundle_chatbox = TextBundle::from_sections(
(0..LOG_MAX_ROWS).map(|_| TextSection::new("", style_console.clone())),
)
.with_style(Style {
position_type: PositionType::Absolute,
top: Val::VMin(0.0),
right: Val::VMin(0.0),
..default()
}).with_text_justify(JustifyText::Right);
commands.spawn((
NodeBundle {
})
.with_text_justify(JustifyText::Right);
commands
.spawn((NodeBundle {
style: Style {
width: Val::Percent(50.0),
align_items: AlignItems::Start,
@ -313,24 +364,23 @@ pub fn setup(
..default()
},
..default()
},
)).with_children(|parent| {
parent.spawn((
bundle_chatbox,
NodeConsole,
));
},))
.with_children(|parent| {
parent.spawn((bundle_chatbox, NodeConsole));
});
// Add Reticule
let reticule_handle: Handle<Image> = asset_server.load("sprites/reticule4.png");
commands.spawn((
commands
.spawn((
NodeBundle {
style: style_centered(),
visibility,
..default()
},
ToggleableHudElement,
)).with_children(|builder| {
))
.with_children(|builder| {
builder.spawn((
ImageBundle {
image: UiImage::new(reticule_handle),
@ -356,10 +406,11 @@ pub fn setup(
let icon_size = 24.0;
let gauge_bar_padding_left = 4.0;
for (i, (sprite, gauge)) in gauges.iter().enumerate() {
let bar_length = if i == 0 { 32.0*8.0 } else { 32.0*5.0 };
let bar_length = if i == 0 { 32.0 * 8.0 } else { 32.0 * 5.0 };
// The bar with variable width
commands.spawn((
commands
.spawn((
NodeBundle {
style: Style {
width: Val::Percent(30.0),
@ -374,7 +425,8 @@ pub fn setup(
..default()
},
ToggleableHudElement,
)).with_children(|builder| {
))
.with_children(|builder| {
builder.spawn((
NodeBundle {
style: Style {
@ -394,7 +446,8 @@ pub fn setup(
});
// The decorator sprites surrounding the bar
commands.spawn((
commands
.spawn((
NodeBundle {
style: Style {
width: Val::Percent(30.0),
@ -409,10 +462,10 @@ pub fn setup(
..default()
},
ToggleableHudElement,
)).with_children(|builder| {
))
.with_children(|builder| {
// The gauge symbol
builder.spawn((
ImageBundle {
builder.spawn((ImageBundle {
image: UiImage::new(asset_server.load(sprite.to_string())),
style: Style {
width: Val::Px(icon_size),
@ -421,11 +474,9 @@ pub fn setup(
},
visibility,
..Default::default()
},
));
},));
// The gauge bar border
builder.spawn((
ImageBundle {
builder.spawn((ImageBundle {
image: UiImage::new(gauges_handle.clone()),
style: Style {
width: Val::Px(bar_length),
@ -436,19 +487,18 @@ pub fn setup(
},
visibility,
..Default::default()
},
));
},));
});
}
// Car-Dashboard-Style icons
let style_dashboard = Style {
width: Val::Px(DASHBOARD_ICON_SIZE),
height: Val::Px(DASHBOARD_ICON_SIZE),
..Default::default()
};
commands.spawn((
commands
.spawn((
NodeBundle {
style: Style {
width: Val::Percent(30.0),
@ -463,13 +513,15 @@ pub fn setup(
..default()
},
ToggleableHudElement,
)).with_children(|builder| {
))
.with_children(|builder| {
for (component, filename) in DASHBOARD_DEF {
builder.spawn((
*component,
ImageBundle {
image: UiImage::new(asset_server.load(
format!("sprites/dashboard_{}.png", filename))),
image: UiImage::new(
asset_server.load(format!("sprites/dashboard_{}.png", filename)),
),
style: style_dashboard.clone(),
visibility: Visibility::Hidden,
..Default::default()
@ -480,7 +532,8 @@ pub fn setup(
// Add Speedometer
let speedometer_handle: Handle<Image> = asset_server.load("sprites/speedometer.png");
commands.spawn((
commands
.spawn((
NodeBundle {
style: Style {
width: Val::VMin(0.0),
@ -495,9 +548,9 @@ pub fn setup(
},
Speedometer,
ToggleableHudElement,
)).with_children(|builder| {
builder.spawn((
ImageBundle {
))
.with_children(|builder| {
builder.spawn((ImageBundle {
image: UiImage::new(speedometer_handle),
style: Style {
width: Val::Vw(SPEEDOMETER_WIDTH),
@ -505,11 +558,11 @@ pub fn setup(
..Default::default()
},
..Default::default()
},
));
},));
});
let speedometer_handle: Handle<Image> = asset_server.load("sprites/speedometer_white.png");
commands.spawn((
commands
.spawn((
NodeBundle {
style: Style {
width: Val::VMin(0.0),
@ -524,9 +577,9 @@ pub fn setup(
},
Speedometer2,
ToggleableHudElement,
)).with_children(|builder| {
builder.spawn((
ImageBundle {
))
.with_children(|builder| {
builder.spawn((ImageBundle {
image: UiImage::new(speedometer_handle),
style: Style {
width: Val::Vw(SPEEDOMETER_WIDTH),
@ -534,19 +587,20 @@ pub fn setup(
..Default::default()
},
..Default::default()
},
));
},));
});
let mut bundle_speedometer_text = TextBundle::from_sections([
TextSection::new("", style_speedometer.clone()), // speed relative to target
TextSection::new("", style_speedometer.clone()), // speed relative to target
TextSection::new("", style_speedometer.clone()), // speed relative to orbit
]).with_style(Style {
])
.with_style(Style {
position_type: PositionType::Absolute,
left: Val::Vw(100.0 - SPEEDOMETER_WIDTH + 2.0),
bottom: Val::VMin(4.0),
..default()
}).with_text_justify(JustifyText::Left);
})
.with_text_justify(JustifyText::Left);
bundle_speedometer_text.visibility = visibility;
commands.spawn((
NodeSpeedometerText,
@ -555,7 +609,8 @@ pub fn setup(
));
// Chat "subtitles" and choices
commands.spawn(NodeBundle {
commands
.spawn(NodeBundle {
style: Style {
width: Val::Vw(100.0),
align_items: AlignItems::Center,
@ -566,13 +621,12 @@ pub fn setup(
..default()
},
..default()
}).with_children(|builder| {
})
.with_children(|builder| {
builder.spawn((
TextBundle {
text: Text {
sections: vec![
TextSection::new("", style_conversations),
],
sections: vec![TextSection::new("", style_conversations)],
justify: JustifyText::Center,
..default()
},
@ -588,8 +642,8 @@ pub fn setup(
},
NodeCurrentChatLine,
));
let choice_sections = (0..MAX_CHOICES).map(|_|
TextSection::new("", style_choices.clone()));
let choice_sections =
(0..MAX_CHOICES).map(|_| TextSection::new("", style_choices.clone()));
builder.spawn((
TextBundle {
text: Text {
@ -628,23 +682,17 @@ fn update_dashboard(
return;
}
let player = q_player.get_single();
if player.is_err() { return; }
if player.is_err() {
return;
}
let (suit, pos) = player.unwrap();
for (mut vis, icon) in &mut q_dashboard {
*vis = bool2vis(match icon {
Dashboard::Flashlight => {
settings.flashlight_active
}
Dashboard::Leak => {
suit.integrity < 0.5
}
Dashboard::RotationStabiliser => {
!settings.rotation_stabilizer_active
}
Dashboard::CruiseControl => {
settings.cruise_control_active
}
Dashboard::Flashlight => settings.flashlight_active,
Dashboard::Leak => suit.integrity < 0.5,
Dashboard::RotationStabiliser => !settings.rotation_stabilizer_active,
Dashboard::CruiseControl => settings.cruise_control_active,
Dashboard::Radioactivity => {
if let Some(pos_jupiter) = id2pos.0.get(cmd::ID_JUPITER) {
pos_jupiter.distance(pos.0) < 140_000_000.0
@ -675,13 +723,21 @@ fn update_speedometer(
let speedometer_split = 5_000.0;
if let Ok(mut speedometer) = q_speedometer.get_single_mut() {
let custom_c = speedometer_split;
let fraction = nature::inverse_lorentz_factor_custom_c((custom_c - speed).clamp(0.0, custom_c), custom_c).clamp(0.0, 1.0) as f32;
let fraction = nature::inverse_lorentz_factor_custom_c(
(custom_c - speed).clamp(0.0, custom_c),
custom_c,
)
.clamp(0.0, 1.0) as f32;
let wid = (fraction * SPEEDOMETER_WIDTH).clamp(0.0, 100.0);
speedometer.width = Val::Vw(wid);
}
if let Ok(mut speedometer2) = q_speedometer2.get_single_mut() {
let custom_c = nature::C - speedometer_split;
let fraction = nature::inverse_lorentz_factor_custom_c((custom_c - speed + speedometer_split).clamp(0.0, custom_c), custom_c).clamp(0.0, 1.0) as f32;
let fraction = nature::inverse_lorentz_factor_custom_c(
(custom_c - speed + speedometer_split).clamp(0.0, custom_c),
custom_c,
)
.clamp(0.0, 1.0) as f32;
let wid = (fraction * SPEEDOMETER_WIDTH).clamp(0.0, 100.0);
speedometer2.width = Val::Vw(wid);
}
@ -729,7 +785,9 @@ fn update_gauges(
return;
}
let player = q_player.get_single();
if player.is_err() { return; }
if player.is_err() {
return;
}
let (hp, suit, battery) = player.unwrap();
for (mut style, mut bg, gauge, len) in &mut q_gauges {
@ -741,8 +799,7 @@ fn update_gauges(
};
if value < 0.5 {
*bg = settings.hud_color_alert.into();
}
else {
} else {
*bg = settings.hud_color.into();
}
style.width = Val::Px(len.0 * value);
@ -758,9 +815,23 @@ fn update_hud(
q_choices: Query<&chat::Choice>,
q_chat: Query<&chat::Chat>,
mut q_node_hud: Query<&mut Text, With<NodeHud>>,
mut q_node_console: Query<&mut Text, (With<NodeConsole>, Without<NodeHud>, Without<NodeChoiceText>)>,
mut q_node_choice: Query<&mut Text, (With<NodeChoiceText>, Without<NodeHud>, Without<NodeConsole>)>,
mut q_node_currentline: Query<&mut Text, (With<NodeCurrentChatLine>, Without<NodeHud>, Without<NodeConsole>, Without<NodeChoiceText>)>,
mut q_node_console: Query<
&mut Text,
(With<NodeConsole>, Without<NodeHud>, Without<NodeChoiceText>),
>,
mut q_node_choice: Query<
&mut Text,
(With<NodeChoiceText>, Without<NodeHud>, Without<NodeConsole>),
>,
mut q_node_currentline: Query<
&mut Text,
(
With<NodeCurrentChatLine>,
Without<NodeHud>,
Without<NodeConsole>,
Without<NodeChoiceText>,
),
>,
settings: Res<Settings>,
q_target: Query<(&IsClickable, Option<&Position>, Option<&LinearVelocity>), With<IsTargeted>>,
) {
@ -781,41 +852,42 @@ fn update_hud(
let dist_scalar: f64;
let mut target_multiple = false;
let mut target_error = false;
if let Ok((IsClickable { distance: Some(dist), .. }, _, _)) = q_target.get_single() {
if let Ok((
IsClickable {
distance: Some(dist),
..
},
_,
_,
)) = q_target.get_single()
{
dist_scalar = *dist;
}
else {
} else {
let target: Option<DVec3>;
if let Ok((_, Some(targetpos), _)) = q_target.get_single() {
target = Some(targetpos.0);
}
else if q_target.is_empty() {
} else if q_target.is_empty() {
target = Some(DVec3::new(0.0, 0.0, 0.0));
}
else if q_target.iter().len() > 1 {
} else if q_target.iter().len() > 1 {
target_multiple = true;
target = None;
}
else {
} else {
target_error = true;
target = None;
}
if let Some(target_pos) = target {
let dist = pos.0 - target_pos;
dist_scalar = dist.length();
}
else {
} else {
dist_scalar = 0.0;
}
}
if target_multiple {
text.sections[0].value = "ERROR: MULTIPLE TARGETS\n\n".to_string();
}
else if target_error {
} else if target_error {
text.sections[0].value = "ERROR: FAILED TO AQUIRE TARGET\n\n".to_string();
}
else if let Ok((clickable, _, _)) = q_target.get_single() {
} else if let Ok((clickable, _, _)) = q_target.get_single() {
let distance = if dist_scalar.is_nan() {
"UNKNOWN".to_string()
} else if dist_scalar != 0.0 {
@ -829,9 +901,9 @@ fn update_hud(
} else {
"".to_string()
};
text.sections[0].value = format!("Target: {target_name}\n{pronoun}Distance: {distance}\n\n");
}
else {
text.sections[0].value =
format!("Target: {target_name}\n{pronoun}Distance: {distance}\n\n");
} else {
text.sections[0].value = "".to_string();
}
}
@ -862,15 +934,17 @@ fn update_hud(
// Chat Log and System Log
let logfilter = if settings.hud_active {
|_msg: &&Message| { true }
|_msg: &&Message| true
} else {
|msg: &&Message| { match msg.level {
|msg: &&Message| match msg.level {
LogLevel::Always => true,
LogLevel::Achievement => true,
_ => false
}}
_ => false,
}
};
let messages: Vec<&Message> = log.logs.iter()
let messages: Vec<&Message> = log
.logs
.iter()
.filter(logfilter)
.rev()
.take(LOG_MAX_ROWS)
@ -893,11 +967,13 @@ fn update_hud(
// Display the last chat line as "subtitles"
if !q_chat.is_empty() {
let messages: Vec<&Message> = log.logs.iter()
.filter(|msg: &&Message| { match msg.level {
let messages: Vec<&Message> = log
.logs
.iter()
.filter(|msg: &&Message| match msg.level {
LogLevel::Chat => true,
_ => false
}})
_ => false,
})
.rev()
.take(1)
.collect();
@ -963,7 +1039,15 @@ fn handle_input(
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_target: EventWriter<TargetEvent>,
mut ew_game: EventWriter<GameEvent>,
q_objects: Query<(Entity, &Transform), (With<IsClickable>, Without<IsTargeted>, Without<actor::PlayerDrivesThis>, Without<actor::Player>)>,
q_objects: Query<
(Entity, &Transform),
(
With<IsClickable>,
Without<IsTargeted>,
Without<actor::PlayerDrivesThis>,
Without<actor::Player>,
),
>,
q_camera: Query<&Transform, With<Camera>>,
) {
if keyboard_input.just_pressed(settings.key_togglehud) {
@ -973,10 +1057,11 @@ fn handle_input(
if settings.hud_active && mouse_input.just_pressed(settings.key_selectobject) {
if let Ok(camtrans) = q_camera.get_single() {
let objects: Vec<(Entity, &Transform)> = q_objects.iter().collect();
if let (Some(new_target), _dist) = camera::find_closest_target::<Entity>(objects, camtrans) {
if let (Some(new_target), _dist) =
camera::find_closest_target::<Entity>(objects, camtrans)
{
ew_target.send(TargetEvent(Some(new_target)));
}
else {
} else {
ew_target.send(TargetEvent(None));
}
}
@ -1018,7 +1103,10 @@ fn handle_target_event(
fn update_target_selectagon(
settings: Res<Settings>,
mut q_selectagon: Query<(&mut Transform, &mut Visibility), (With<Selectagon>, Without<IsTargeted>, Without<Camera>)>,
mut q_selectagon: Query<
(&mut Transform, &mut Visibility),
(With<Selectagon>, Without<IsTargeted>, Without<Camera>),
>,
q_target: Query<&Transform, (With<IsTargeted>, Without<Camera>, Without<Selectagon>)>,
q_camera: Query<&Transform, (With<Camera>, Without<IsTargeted>, Without<Selectagon>)>,
) {
@ -1030,7 +1118,9 @@ fn update_target_selectagon(
if let Ok((mut selectagon_trans, mut selectagon_vis)) = q_selectagon.get_single_mut() {
if let Ok(target_trans) = q_target.get_single() {
match *selectagon_vis {
Visibility::Hidden => { *selectagon_vis = Visibility::Visible; },
Visibility::Hidden => {
*selectagon_vis = Visibility::Visible;
}
_ => {}
}
selectagon_trans.translation = target_trans.translation;
@ -1038,25 +1128,36 @@ fn update_target_selectagon(
selectagon_trans.look_at(camera_trans.translation, camera_trans.up().into());
// Enlarge Selectagon to a minimum angular diameter
let (angular_diameter, _, _) = camera::calc_angular_diameter(
&selectagon_trans, camera_trans);
let (angular_diameter, _, _) =
camera::calc_angular_diameter(&selectagon_trans, camera_trans);
let min_angular_diameter = 2.0f32.to_radians();
if angular_diameter < min_angular_diameter {
selectagon_trans.scale *= min_angular_diameter / angular_diameter;
}
}
else {
} else {
match *selectagon_vis {
Visibility::Hidden => {},
_ => { *selectagon_vis = Visibility::Hidden; }
Visibility::Hidden => {}
_ => {
*selectagon_vis = Visibility::Hidden;
}
}
}
}
}
fn update_ar_overlays (
q_owners: Query<(Entity, &Transform, &Visibility), (With<AugmentedRealityOverlayBroadcaster>, Without<AugmentedRealityOverlay>)>,
mut q_overlays: Query<(&mut Transform, &mut Visibility, &mut AugmentedRealityOverlay)>,
fn update_ar_overlays(
q_owners: Query<
(Entity, &Transform, &Visibility),
(
With<AugmentedRealityOverlayBroadcaster>,
Without<AugmentedRealityOverlay>,
),
>,
mut q_overlays: Query<(
&mut Transform,
&mut Visibility,
&mut AugmentedRealityOverlay,
)>,
settings: ResMut<Settings>,
mut state: ResMut<AugmentedRealityState>,
) {
@ -1064,8 +1165,7 @@ fn update_ar_overlays (
if settings.hud_active {
need_activate = !state.overlays_visible;
need_clean = false;
}
else {
} else {
need_activate = false;
need_clean = state.overlays_visible;
}
@ -1077,10 +1177,12 @@ fn update_ar_overlays (
for (owner_id, owner_trans, owner_vis) in &q_owners {
if owner_id == ar.owner {
*trans = *owner_trans;
if ar.scale != 1.0 {
trans.scale *= ar.scale;
}
if need_clean {
*vis = Visibility::Hidden;
}
else {
} else {
*vis = *owner_vis;
}
break;
@ -1090,7 +1192,7 @@ fn update_ar_overlays (
}
}
fn update_poi_overlays (
fn update_poi_overlays(
mut q_marker: Query<(&mut Transform, &PointOfInterestMarker)>,
q_parent: Query<&Transform, Without<PointOfInterestMarker>>,
q_camera: Query<&Transform, (With<Camera>, Without<PointOfInterestMarker>)>,
@ -1105,8 +1207,7 @@ fn update_poi_overlays (
// Enlarge POI marker to a minimum angular diameter
trans.translation = parent_trans.translation;
trans.scale = Vec3::splat(1.0);
let (angular_diameter, _, _) = camera::calc_angular_diameter(
&trans, camera_trans);
let (angular_diameter, _, _) = camera::calc_angular_diameter(&trans, camera_trans);
let min_angular_diameter = 3.0f32.to_radians();
if angular_diameter < min_angular_diameter {
trans.scale *= min_angular_diameter / angular_diameter;
@ -1118,9 +1219,27 @@ fn update_poi_overlays (
fn update_overlay_visibility(
mut q_marker: Query<&mut Visibility, With<PointOfInterestMarker>>,
mut q_hudelement: Query<&mut Visibility, (With<ToggleableHudElement>, Without<PointOfInterestMarker>)>,
mut q_selectagon: Query<&mut Visibility, (With<Selectagon>, Without<ToggleableHudElement>, Without<PointOfInterestMarker>)>,
q_target: Query<&IsTargeted, (Without<Camera>, Without<Selectagon>, Without<PointOfInterestMarker>, Without<ToggleableHudElement>)>,
mut q_hudelement: Query<
&mut Visibility,
(With<ToggleableHudElement>, Without<PointOfInterestMarker>),
>,
mut q_selectagon: Query<
&mut Visibility,
(
With<Selectagon>,
Without<ToggleableHudElement>,
Without<PointOfInterestMarker>,
),
>,
q_target: Query<
&IsTargeted,
(
Without<Camera>,
Without<Selectagon>,
Without<PointOfInterestMarker>,
Without<ToggleableHudElement>,
),
>,
mut ambient_light: ResMut<AmbientLight>,
er_target: EventReader<UpdateOverlayVisibility>,
settings: Res<Settings>,
@ -1128,8 +1247,14 @@ fn update_overlay_visibility(
if er_target.is_empty() {
return;
}
let check = {|check: bool|
if check { Visibility::Inherited } else { Visibility::Hidden }
let check = {
|check: bool| {
if check {
Visibility::Inherited
} else {
Visibility::Hidden
}
}
};
let show_poi = check(settings.hud_active && settings.map_active);
let show_hud = check(settings.hud_active);
@ -1150,3 +1275,52 @@ fn update_overlay_visibility(
AMBIENT_LIGHT
};
}
fn update_avatar(
mut commands: Commands,
mut settings: ResMut<Settings>,
asset_server: Res<AssetServer>,
q_avatar: Query<(Entity, &SceneInstance), With<PlayerAvatar>>,
q_player: Query<Entity, With<actor::Player>>,
mut scene_spawner: ResMut<SceneSpawner>,
) {
if settings.ar_avatar >= PLAYER_AR_AVATARS.len() {
settings.ar_avatar = settings.ar_avatar % PLAYER_AR_AVATARS.len();
}
let ava = if let Some(ava) = PLAYER_AR_AVATARS.get(settings.ar_avatar) {
ava
} else {
error!("Avatar index out of bounds!");
return;
};
let model_name = ava.1;
let model_scale = ava.2;
for (entity, sceneinstance) in &q_avatar {
commands.entity(entity).despawn();
scene_spawner.despawn_instance(**sceneinstance);
}
if model_name.is_empty() {
// No avatar selected.
return;
}
if let Ok(player_entity) = q_player.get_single() {
let mut entitycmd = commands.spawn((
hud::AugmentedRealityOverlay {
owner: player_entity,
scale: model_scale,
},
world::DespawnOnPlayerDeath,
PlayerAvatar,
SpatialBundle {
visibility: bool2vis(settings.hud_active),
..default()
},
NotShadowCaster,
NotShadowReceiver,
));
load_asset(model_name, &mut entitycmd, &*asset_server);
}
}

View file

@ -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());
}
}
@ -28,6 +30,7 @@ pub fn asset_name_to_path(name: &str) -> &'static str {
match name {
"suitv2" => "models/suit_v2/suit_v2.glb#Scene0",
"suit_ar_chefhat" => "models/suit_v2/ar_chefhat.glb#Scene0",
"suit_ar_wings" => "models/suit_v2/ar_wings.glb#Scene0",
"asteroid1" => "models/asteroid.glb#Scene0",
"asteroid2" => "models/asteroid2.glb#Scene0",
"asteroid_lum" => "models/asteroid_lum.glb#Scene0",
@ -55,19 +58,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 +104,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 +131,6 @@ impl AsteroidSurface {
}
impl Default for AsteroidSurface {
fn default() -> Self {
Self {
quantize_steps: 3,
}
Self { quantize_steps: 3 }
}
}

View file

@ -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);
@ -127,12 +122,9 @@ impl Plugin for OutFlyPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, setup);
app.add_systems(Update, handle_input);
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 +140,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;

View file

@ -20,32 +20,52 @@ 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
app.add_systems(
PostUpdate,
update_menu
.after(game::handle_game_event)
.run_if(on_event::<UpdateMenuEvent>()));
.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),
("", MenuAction::ToggleAR),
("", MenuAction::ChangeARAvatar),
("", MenuAction::ToggleSound),
("", MenuAction::ToggleMusic),
("", MenuAction::ToggleCamera),
@ -59,6 +79,7 @@ pub const MENUDEF: &[(&str, MenuAction)] = &[
pub enum MenuAction {
ToggleMap,
ToggleAR,
ChangeARAvatar,
ToggleSound,
ToggleMusic,
ToggleCamera,
@ -115,14 +136,16 @@ pub fn setup(
color: settings.hud_color_death_achievements,
..default()
};
commands.spawn((
commands
.spawn((
DeathScreenElement,
NodeBundle {
style: style_centered(),
visibility: Visibility::Hidden,
..default()
},
)).with_children(|builder| {
))
.with_children(|builder| {
builder.spawn((
DeathText,
TextBundle {
@ -133,7 +156,10 @@ pub fn setup(
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),
TextSection::new(
"\n\n\n\nPress E to begin anew.",
style_death_subsubtext,
),
],
justify: JustifyText::Center,
..default()
@ -150,9 +176,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,7 +192,8 @@ pub fn setup(
},
));
commands.spawn((
commands
.spawn((
MenuElement,
NodeBundle {
style: Style {
@ -177,7 +206,8 @@ pub fn setup(
visibility: Visibility::Hidden,
..default()
},
)).with_children(|builder| {
))
.with_children(|builder| {
builder.spawn((
MenuTopLevel,
TextBundle {
@ -205,7 +235,8 @@ pub fn setup(
};
let achievement_count = achievement_tracker.to_bool_vec().len();
commands.spawn((
commands
.spawn((
MenuElement,
NodeBundle {
style: Style {
@ -220,13 +251,15 @@ pub fn setup(
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())
)));
))
.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 {
@ -248,7 +281,8 @@ pub fn setup(
..default()
};
commands.spawn((
commands
.spawn((
MenuElement,
NodeBundle {
style: Style {
@ -263,20 +297,19 @@ pub fn setup(
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 {
@ -286,7 +319,8 @@ pub fn setup(
..default()
};
commands.spawn((
commands
.spawn((
MenuElement,
NodeBundle {
style: Style {
@ -301,20 +335,19 @@ pub fn setup(
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()
},
));
},));
});
}
@ -359,7 +392,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);
}
}
@ -433,6 +469,12 @@ pub fn update_menu(
let onoff = bool2string(settings.hud_active);
text.sections[i].value = format!("Augmented Reality: {onoff} [TAB]\n");
}
MenuAction::ChangeARAvatar => {
if let Some(ava) = hud::PLAYER_AR_AVATARS.get(settings.ar_avatar) {
let avatar_title = ava.3;
text.sections[i].value = format!("Avatar: {avatar_title}\n");
}
}
MenuAction::ToggleMap => {
let onoff = bool2string(settings.map_active);
text.sections[i].value = format!("Map: {onoff} [M]\n");
@ -468,6 +510,7 @@ pub fn handle_input(
mut ew_playerdies: EventWriter<game::PlayerDiesEvent>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_updatemenu: EventWriter<UpdateMenuEvent>,
mut ew_updateavatar: EventWriter<hud::UpdateAvatarEvent>,
) {
let last_menu_entry = MENUDEF.len() - 1;
@ -512,37 +555,42 @@ 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::ChangeARAvatar => {
settings.ar_avatar += 1;
ew_updateavatar.send(hud::UpdateAvatarEvent);
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);
},
}
};
}
}

View file

@ -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");
}

View file

@ -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 = "$";
@ -80,6 +80,7 @@ pub struct Settings {
pub hud_color_keybindings: Color,
pub hud_color_version: Color,
pub chat_speed: f32,
pub ar_avatar: usize,
pub flashlight_active: bool,
pub hud_active: bool,
pub map_active: bool,
@ -208,6 +209,7 @@ impl Default for Settings {
hud_color_keybindings: Color::hex(COLOR_DIM).unwrap(),
hud_color_version: Color::hex(COLOR_PRIMARY).unwrap(),
chat_speed: DEFAULT_CHAT_SPEED * if dev_mode { 2.5 } else { 1.0 },
ar_avatar: 0,
flashlight_active: false,
hud_active: true,
map_active: false,
@ -379,28 +381,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 +442,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,8 +484,7 @@ pub fn load_prefs() -> Preferences {
}
};
match toml.parse::<DocumentMut>() {
Ok(doc) => {
match toml_edit::de::from_document::<Preferences>(doc) {
Ok(doc) => match toml_edit::de::from_document::<Preferences>(doc) {
Ok(mut pref) => {
if let Some(path) = &path {
info!("Loaded preference file from {path}");
@ -495,8 +499,7 @@ pub fn load_prefs() -> Preferences {
error!("Failed to read preference line: {error}");
return Preferences::default();
}
}
}
},
Err(error) => {
error!("Failed to open preferences: {error}");
return Preferences::default();
@ -511,13 +514,15 @@ pub struct GameVars {
impl Default for GameVars {
fn default() -> Self {
Self {
db: HashMap::new(),
}
Self { db: HashMap::new() }
}
}
impl GameVars {
pub fn reset(&mut self) {
self.db.clear();
}
#[allow(dead_code)]
pub fn get(&self, key: &str) -> Option<String> {
if let Some(value) = self.db.get(key) {
@ -573,8 +578,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 +613,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.

View file

@ -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()
},
));
},
//_ => {},
}
}
}
}

View file

@ -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,14 +101,12 @@ 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 *
@ -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,7 +297,8 @@ 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 = 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,
@ -322,10 +332,13 @@ fn spawn_despawn_asteroids(
..default()
});
load_asset(model, &mut entity_commands, &*asset_server);
db.0.insert(origin, AsteroidData {
db.0.insert(
origin,
AsteroidData {
entity: entity_commands.id(),
//viewdistance: 99999999.0,
});
},
);
}
}
}
@ -344,7 +357,7 @@ fn handle_despawn(
}
}
fn handle_respawn(
pub fn handle_respawn(
ew_spawn: EventWriter<cmd::SpawnEvent>,
mut achievement_tracker: ResMut<var::AchievementTracker>,
) {