Compare commits
43 commits
83313c1a5f
...
4fecab5428
Author | SHA1 | Date | |
---|---|---|---|
yuni | 4fecab5428 | ||
yuni | 3429ca5ab7 | ||
yuni | 9f936989f3 | ||
yuni | 1ce864c746 | ||
yuni | 63beec86a2 | ||
yuni | 3f1dc27684 | ||
yuni | 0bbca303cb | ||
yuni | f7002fd064 | ||
yuni | 76bfdf0bfb | ||
yuni | 23c86e9c2f | ||
yuni | 28cf269907 | ||
yuni | cc3213788e | ||
yuni | 192d2e0fcb | ||
yuni | efbb44a9fc | ||
yuni | 099e935e3e | ||
yuni | 2a6e14aa90 | ||
yuni | 8d4ad64330 | ||
yuni | e2046380ea | ||
yuni | 60be58b0fa | ||
yuni | 224e0ce2c9 | ||
yuni | bfad39613e | ||
yuni | e9afeefb7d | ||
yuni | c9e38c7b29 | ||
yuni | 62a0387867 | ||
yuni | 91d19e94a0 | ||
yuni | 8a07e9cfb7 | ||
yuni | c56b5d6d74 | ||
yuni | bcba3d0945 | ||
yuni | 9b48112ee6 | ||
yuni | 8acbd4f33b | ||
yuni | 93e5ee26e4 | ||
yuni | 8621002931 | ||
yuni | 220ab340fb | ||
yuni | 87199f41db | ||
yuni | b0ac508d91 | ||
yuni | 14a22699bc | ||
yuni | 2bc86227a9 | ||
yuni | 61b63aefbe | ||
yuni | c7e3a9396e | ||
yuni | b62c5a6287 | ||
yuni | 42c1d3e191 | ||
yuni | 77b682a7c1 | ||
yuni | 6043d4a1b0 |
10
CHANGELOG.md
|
@ -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
|
@ -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",
|
||||
|
|
12
Cargo.toml
|
@ -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"]
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
63
README.md
|
@ -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"
|
||||
|
||||
|
|
BIN
assets/models/suit_v2/ar_wings.glb
Normal file
BIN
assets/music/JupiterRecording.ogg
Normal 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]
|
||||
|
|
|
@ -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"
|
||||
|
|
Before Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 22 KiB |
26
doc/branding/README.md
Normal 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
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
BIN
doc/branding/logo.png
Normal file
After Width: | Height: | Size: 17 KiB |
|
@ -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
After Width: | Height: | Size: 654 KiB |
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 8.9 KiB |
278
src/actor.rs
|
@ -14,9 +14,9 @@
|
|||
//
|
||||
// This module should never handle any visual aspects directly.
|
||||
|
||||
use crate::prelude::*;
|
||||
use bevy::prelude::*;
|
||||
use bevy_xpbd_3d::prelude::*;
|
||||
use crate::prelude::*;
|
||||
|
||||
pub const ENGINE_SPEED_FACTOR: f32 = 30.0;
|
||||
const MAX_TRANSMISSION_DISTANCE: f32 = 100.0;
|
||||
|
@ -25,23 +25,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, (
|
||||
update_physics_lifeforms,
|
||||
update_power,
|
||||
handle_wants_maxrotation,
|
||||
handle_wants_maxvelocity,
|
||||
));
|
||||
app.add_systems(PostUpdate, handle_gforce
|
||||
.after(PhysicsSet::Sync)
|
||||
.after(sync::position_to_transform));
|
||||
app.add_systems(Update, (
|
||||
handle_input.run_if(in_control),
|
||||
handle_collisions,
|
||||
handle_damage,
|
||||
));
|
||||
app.add_systems(PostUpdate, (
|
||||
handle_vehicle_enter_exit,
|
||||
));
|
||||
app.add_systems(
|
||||
FixedUpdate,
|
||||
(
|
||||
update_physics_lifeforms,
|
||||
update_power,
|
||||
handle_wants_maxrotation,
|
||||
handle_wants_maxvelocity,
|
||||
handle_wants_lookat.run_if(alive),
|
||||
),
|
||||
);
|
||||
app.add_systems(
|
||||
PostUpdate,
|
||||
handle_gforce
|
||||
.after(PhysicsSet::Sync)
|
||||
.after(sync::position_to_transform),
|
||||
);
|
||||
app.add_systems(
|
||||
Update,
|
||||
(
|
||||
handle_input.run_if(in_control),
|
||||
handle_collisions,
|
||||
handle_damage,
|
||||
),
|
||||
);
|
||||
app.add_systems(PostUpdate, (handle_vehicle_enter_exit,));
|
||||
app.add_event::<VehicleEnterExitEvent>();
|
||||
}
|
||||
}
|
||||
|
@ -66,7 +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 {
|
||||
gforce: 0.0,
|
||||
damage_threshold: 100.0,
|
||||
visual_effect_threshold: 20.0,
|
||||
visual_effect: 0.0,
|
||||
last_linear_velocity: DVec3::splat(0.0),
|
||||
ignore_gforce_seconds: 0.0,
|
||||
}}}
|
||||
impl Default for ExperiencesGForce {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
gforce: 0.0,
|
||||
damage_threshold: 100.0,
|
||||
visual_effect_threshold: 20.0,
|
||||
visual_effect: 0.0,
|
||||
last_linear_velocity: DVec3::splat(0.0),
|
||||
ignore_gforce_seconds: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)] pub struct Player; // Attached to the suit of the player
|
||||
#[derive(Component)] pub struct PlayerCollider; // Attached to the collider of the suit of the player
|
||||
#[derive(Component)] pub struct PlayerDrivesThis; // Attached to the entered vehicle
|
||||
#[derive(Component)] pub struct PlayerCamera; // Attached to the actor to use as point of view
|
||||
#[derive(Component)] pub struct JustNowEnteredVehicle;
|
||||
#[derive(Component)] pub struct ActorEnteringVehicle;
|
||||
#[derive(Component)] pub struct ActorVehicleBeingEntered;
|
||||
#[derive(Component)] pub struct PlayersFlashLight;
|
||||
#[derive(Component)] pub struct WantsMaxRotation(pub f64);
|
||||
#[derive(Component)] pub struct WantsMaxVelocity(pub f64);
|
||||
#[derive(Component)] pub struct Identifier(pub String);
|
||||
#[derive(Component)]
|
||||
pub struct Player; // Attached to the suit of the player
|
||||
#[derive(Component)]
|
||||
pub struct PlayerCollider; // Attached to the collider of the suit of the player
|
||||
#[derive(Component)]
|
||||
pub struct PlayerDrivesThis; // Attached to the entered vehicle
|
||||
#[derive(Component)]
|
||||
pub struct PlayerCamera; // Attached to the actor to use as point of view
|
||||
#[derive(Component)]
|
||||
pub struct JustNowEnteredVehicle;
|
||||
#[derive(Component)]
|
||||
pub struct ActorEnteringVehicle;
|
||||
#[derive(Component)]
|
||||
pub struct ActorVehicleBeingEntered;
|
||||
#[derive(Component)]
|
||||
pub struct PlayersFlashLight;
|
||||
#[derive(Component)]
|
||||
pub struct WantsMaxRotation(pub f64);
|
||||
#[derive(Component)]
|
||||
pub struct WantsMaxVelocity(pub f64);
|
||||
#[derive(Component)]
|
||||
pub struct 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 {
|
||||
is_alive: true,
|
||||
adrenaline: 0.3,
|
||||
adrenaline_baseline: 0.3,
|
||||
adrenaline_jolt: 0.0,
|
||||
}}}
|
||||
impl Default for LifeForm {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
is_alive: true,
|
||||
adrenaline: 0.3,
|
||||
adrenaline_baseline: 0.3,
|
||||
adrenaline_jolt: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Vehicle {
|
||||
stored_drivers_collider: Option<Collider>,
|
||||
}
|
||||
impl Default for Vehicle { fn default() -> Self { Self {
|
||||
stored_drivers_collider: None,
|
||||
}}}
|
||||
impl Default for Vehicle {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
stored_drivers_collider: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
pub enum EngineType {
|
||||
|
@ -192,7 +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,
|
||||
|
@ -202,9 +239,9 @@ const SUIT_SIMPLE: Suit = Suit {
|
|||
|
||||
#[derive(Component)]
|
||||
pub struct Battery {
|
||||
pub power: f32, // Watt-seconds
|
||||
pub power: f32, // Watt-seconds
|
||||
pub capacity: f32, // Watt-seconds
|
||||
pub reactor: f32, // Watt (production)
|
||||
pub reactor: f32, // Watt (production)
|
||||
}
|
||||
|
||||
impl Default for Battery {
|
||||
|
@ -212,7 +249,7 @@ impl Default for Battery {
|
|||
Self {
|
||||
power: 10e3 * 3600.0,
|
||||
capacity: 10e3 * 3600.0, // 10kWh
|
||||
reactor: 2000e3, // 2MW
|
||||
reactor: 2000e3, // 2MW
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -325,12 +371,12 @@ pub fn handle_input(
|
|||
.map(|(entity, actor, transform)| ((entity, actor), transform))
|
||||
.collect();
|
||||
if let (Some((entity, actor)), dist) =
|
||||
camera::find_closest_target::<(Entity, &Actor)>(objects, camtrans)
|
||||
camera::find_closest_target::<(Entity, &Actor)>(objects, camtrans)
|
||||
{
|
||||
if dist <= MAX_INTERACT_DISTANCE {
|
||||
commands.entity(entity).insert(ActorVehicleBeingEntered);
|
||||
commands.entity(player_entity).insert(ActorEnteringVehicle);
|
||||
ew_vehicle.send(VehicleEnterExitEvent{
|
||||
ew_vehicle.send(VehicleEnterExitEvent {
|
||||
vehicle: entity,
|
||||
driver: player_entity,
|
||||
name: actor.name.clone(),
|
||||
|
@ -340,13 +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,17 +546,40 @@ 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 acceleration = (avg_thrust * dt) as f64 * -v.0;
|
||||
v.0 += acceleration;
|
||||
if v.0.length() + EPSILON < acceleration.length() {
|
||||
v.0 = DVec3::splat(0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 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());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
137
src/audio.rs
|
@ -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,23 +234,19 @@ 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();
|
||||
for (bgm_sink, sfx) in &q_audiosinks {
|
||||
if *sfx != Sfx::BGM {
|
||||
pub fn toggle_music(q_audiosinks: Query<(&AudioSink, &Sfx)>, settings: Res<var::Settings>) {
|
||||
for (bgm_sink, sfx) in &q_audiosinks {
|
||||
let play = match *sfx {
|
||||
Sfx::BGM => settings.hud_active,
|
||||
Sfx::BGMActualJupiterRecording => !settings.hud_active,
|
||||
_ => {
|
||||
continue;
|
||||
}
|
||||
if settings.mute_music {
|
||||
bgm_sink.pause();
|
||||
}
|
||||
else {
|
||||
bgm_sink.play();
|
||||
}
|
||||
};
|
||||
if settings.mute_music || !play {
|
||||
bgm_sink.pause();
|
||||
} else {
|
||||
bgm_sink.play();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
|
418
src/audio/JupiterRecording.kdenlive
Normal 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 & 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 & 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 & 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>
|
BIN
src/blender/wings.blend
Normal file
296
src/camera.rs
|
@ -12,16 +12,16 @@
|
|||
// movement-related keyboard input, and provides some camera-
|
||||
// related computation functions.
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy::input::mouse::{MouseMotion, MouseWheel};
|
||||
use bevy::window::PrimaryWindow;
|
||||
use crate::prelude::*;
|
||||
use bevy::core_pipeline::bloom::{BloomCompositeMode, BloomSettings};
|
||||
use bevy::core_pipeline::tonemapping::Tonemapping;
|
||||
use bevy::input::mouse::{MouseMotion, MouseWheel};
|
||||
use bevy::pbr::{CascadeShadowConfigBuilder, DirectionalLightShadowMap};
|
||||
use bevy::prelude::*;
|
||||
use bevy::transform::TransformSystem;
|
||||
use bevy_xpbd_3d::prelude::*;
|
||||
use bevy::window::PrimaryWindow;
|
||||
use bevy_xpbd_3d::plugins::sync;
|
||||
use crate::prelude::*;
|
||||
use bevy_xpbd_3d::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub const INITIAL_ZOOM_LEVEL: f64 = 10.0;
|
||||
|
@ -34,13 +34,19 @@ impl Plugin for CameraPlugin {
|
|||
app.add_systems(Update, handle_input.run_if(in_control));
|
||||
app.add_systems(Update, update_map_only_object_visibility.run_if(alive));
|
||||
app.add_systems(Update, manage_player_actor.after(handle_input));
|
||||
app.add_systems(PostUpdate, sync_camera_to_player
|
||||
.after(PhysicsSet::Sync)
|
||||
.after(apply_input_to_player)
|
||||
.before(TransformSystem::TransformPropagate));
|
||||
app.add_systems(PostUpdate, update_mapcam_center
|
||||
.before(sync::position_to_transform)
|
||||
.in_set(sync::SyncSet::PositionToTransform));
|
||||
app.add_systems(
|
||||
PostUpdate,
|
||||
sync_camera_to_player
|
||||
.after(PhysicsSet::Sync)
|
||||
.after(apply_input_to_player)
|
||||
.before(TransformSystem::TransformPropagate),
|
||||
);
|
||||
app.add_systems(
|
||||
PostUpdate,
|
||||
update_mapcam_center
|
||||
.before(sync::position_to_transform)
|
||||
.in_set(sync::SyncSet::PositionToTransform),
|
||||
);
|
||||
app.add_systems(Update, update_map_camera.run_if(in_control));
|
||||
app.add_systems(Update, update_fov.run_if(alive));
|
||||
app.add_systems(PreUpdate, apply_input_to_player);
|
||||
|
@ -53,9 +59,12 @@ impl Plugin for CameraPlugin {
|
|||
transform_to_position: false,
|
||||
});
|
||||
// 2. Add own position->transform sync function
|
||||
app.add_systems(PostUpdate, position_to_transform
|
||||
.after(sync::position_to_transform)
|
||||
.in_set(sync::SyncSet::PositionToTransform));
|
||||
app.add_systems(
|
||||
PostUpdate,
|
||||
position_to_transform
|
||||
.after(sync::position_to_transform)
|
||||
.in_set(sync::SyncSet::PositionToTransform),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -93,10 +102,7 @@ impl Default for MapCam {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn setup_camera(
|
||||
mut commands: Commands,
|
||||
settings: Res<var::Settings>,
|
||||
) {
|
||||
pub fn setup_camera(mut commands: Commands, settings: Res<var::Settings>) {
|
||||
// Add player
|
||||
commands.spawn((
|
||||
Camera3dBundle {
|
||||
|
@ -122,13 +128,14 @@ pub fn setup_camera(
|
|||
shadows_enabled: settings.shadows_sun,
|
||||
..default()
|
||||
},
|
||||
transform: Transform::from_rotation(Quat::from_rotation_y(PI32/2.0)),
|
||||
transform: Transform::from_rotation(Quat::from_rotation_y(PI32 / 2.0)),
|
||||
cascade_shadow_config: CascadeShadowConfigBuilder {
|
||||
num_cascades: 4,
|
||||
minimum_distance: 0.1,
|
||||
maximum_distance: 5000.0,
|
||||
..default()
|
||||
}.into(),
|
||||
}
|
||||
.into(),
|
||||
..default()
|
||||
});
|
||||
|
||||
|
@ -153,10 +160,10 @@ pub fn sync_camera_to_player(
|
|||
|
||||
// Translation
|
||||
if settings.third_person {
|
||||
camera_transform.translation = player_transform.translation + rotation * (actor.camdistance * Vec3::new(0.0, 0.2, 1.0));
|
||||
camera_transform.translation = player_transform.translation
|
||||
+ rotation * (actor.camdistance * Vec3::new(0.0, 0.2, 1.0));
|
||||
camera_transform.rotation = rotation * Quat::from_euler(EulerRot::XYZ, -0.02, 0.0, 0.0);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
camera_transform.translation = player_transform.translation;
|
||||
camera_transform.rotation = rotation;
|
||||
}
|
||||
|
@ -167,7 +174,14 @@ pub fn update_map_camera(
|
|||
mut mapcam: ResMut<MapCam>,
|
||||
mut q_camera: Query<&mut Transform, (With<Camera>, Without<actor::PlayerCamera>)>,
|
||||
q_playercam: Query<(Entity, &Transform), (With<actor::PlayerCamera>, Without<Camera>)>,
|
||||
q_target: Query<(Entity, &Transform), (With<hud::IsTargeted>, Without<Camera>, Without<actor::PlayerCamera>)>,
|
||||
q_target: Query<
|
||||
(Entity, &Transform),
|
||||
(
|
||||
With<hud::IsTargeted>,
|
||||
Without<Camera>,
|
||||
Without<actor::PlayerCamera>,
|
||||
),
|
||||
>,
|
||||
q_target_changed: Query<(), Changed<hud::IsTargeted>>,
|
||||
mut mouse_events: EventReader<MouseMotion>,
|
||||
mut er_mousewheel: EventReader<MouseWheel>,
|
||||
|
@ -195,7 +209,9 @@ pub fn update_map_camera(
|
|||
// at the extreme values and the orientation will flicker back/forth.
|
||||
let min_zoom: f64 = target_trans.scale.x as f64 * 2.0;
|
||||
let max_zoom: f64 = 17e18; // at this point, camera starts glitching
|
||||
mapcam.pitch = (mapcam.pitch + mouse_delta.y as f64 / 180.0 * settings.mouse_sensitivity as f64).clamp(-PI / 2.0 + EPSILON, PI / 2.0 - EPSILON);
|
||||
mapcam.pitch = (mapcam.pitch
|
||||
+ mouse_delta.y as f64 / 180.0 * settings.mouse_sensitivity as f64)
|
||||
.clamp(-PI / 2.0 + EPSILON, PI / 2.0 - EPSILON);
|
||||
mapcam.yaw += mouse_delta.x as f64 / 180.0 * settings.mouse_sensitivity as f64;
|
||||
|
||||
// Reset movement offset if target changes
|
||||
|
@ -226,7 +242,11 @@ pub fn update_map_camera(
|
|||
|
||||
// Update zoom level
|
||||
if !mapcam.initialized {
|
||||
let factor: f64 = if target_trans == player_trans { 7.0 } else { 1.0 };
|
||||
let factor: f64 = if target_trans == player_trans {
|
||||
7.0
|
||||
} else {
|
||||
1.0
|
||||
};
|
||||
mapcam.target_zoom_level *= target_trans.scale.x as f64 * factor;
|
||||
mapcam.zoom_level *= target_trans.scale.x as f64 * factor;
|
||||
mapcam.initialized = true;
|
||||
|
@ -241,12 +261,16 @@ pub fn update_map_camera(
|
|||
for wheel_event in er_mousewheel.read() {
|
||||
change_zoom -= wheel_event.y as f64 * 3.0;
|
||||
}
|
||||
mapcam.target_zoom_level = (mapcam.target_zoom_level * 1.1f64.powf(change_zoom)).clamp(min_zoom, max_zoom);
|
||||
mapcam.target_zoom_level =
|
||||
(mapcam.target_zoom_level * 1.1f64.powf(change_zoom)).clamp(min_zoom, max_zoom);
|
||||
let zoom_speed = 0.05; // should be between 0.0001 (slow) and 1.0 (instant)
|
||||
mapcam.zoom_level = (zoom_speed * mapcam.target_zoom_level + (1.0 - zoom_speed) * mapcam.zoom_level).clamp(min_zoom, max_zoom);
|
||||
mapcam.zoom_level = (zoom_speed * mapcam.target_zoom_level
|
||||
+ (1.0 - zoom_speed) * mapcam.zoom_level)
|
||||
.clamp(min_zoom, max_zoom);
|
||||
|
||||
// Update point of view
|
||||
let pov_rotation = DQuat::from_euler(EulerRot::XYZ, 0.0, mapcam.yaw as f64, mapcam.pitch as f64);
|
||||
let pov_rotation =
|
||||
DQuat::from_euler(EulerRot::XYZ, 0.0, mapcam.yaw as f64, mapcam.pitch as f64);
|
||||
let point_of_view = pov_rotation * (mapcam.zoom_level as f64 * DVec3::new(1.0, 0.0, 0.0));
|
||||
|
||||
// Update movement offset
|
||||
|
@ -285,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,18 +502,21 @@ pub fn apply_input_to_player(
|
|||
axis_input = axis_input.clamp(DVec3::splat(-1.0), DVec3::splat(1.0));
|
||||
|
||||
// Apply movement update
|
||||
let forward_factor = engine.current_warmup * (if axis_input.z > 0.0 {
|
||||
engine.thrust_forward
|
||||
} else {
|
||||
engine.thrust_back
|
||||
});
|
||||
let forward_factor = engine.current_warmup
|
||||
* (if axis_input.z > 0.0 {
|
||||
engine.thrust_forward
|
||||
} else {
|
||||
engine.thrust_back
|
||||
});
|
||||
let right_factor = engine.thrust_sideways * engine.current_warmup;
|
||||
let up_factor = engine.thrust_sideways * engine.current_warmup;
|
||||
let factor = DVec3::new(right_factor as f64, up_factor as f64, forward_factor as f64);
|
||||
|
||||
if axis_input.length_squared() > 0.003 {
|
||||
let acceleration_global: DVec3 = DVec3::from(player_transform.rotation * (axis_input * factor).as_vec3());
|
||||
let mut acceleration_total: DVec3 = (actor::ENGINE_SPEED_FACTOR * dt) as f64 * acceleration_global;
|
||||
let acceleration_global: DVec3 =
|
||||
DVec3::from(player_transform.rotation * (axis_input * factor).as_vec3());
|
||||
let mut acceleration_total: DVec3 =
|
||||
(actor::ENGINE_SPEED_FACTOR * dt) as f64 * acceleration_global;
|
||||
let threshold = 1e-5;
|
||||
if key_input.pressed(settings.key_stop) {
|
||||
// Decelerate (or match velocity to target_v)
|
||||
|
@ -461,8 +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;
|
||||
player_transform.rotation
|
||||
* Vec3::new(pitch_yaw_rot[0], pitch_yaw_rot[1], pitch_yaw_rot[2]),
|
||||
));
|
||||
}
|
||||
else {
|
||||
if angularvelocity.length_squared() > 1.0e-18 {
|
||||
angularvelocity.0 *= angular_slowdown;
|
||||
}
|
||||
else {
|
||||
angularvelocity.0 = DVec3::splat(0.0);
|
||||
}
|
||||
|
||||
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,21 +715,22 @@ pub fn find_closest_target<TargetSpecifier>(
|
|||
objects: Vec<(TargetSpecifier, &Transform)>,
|
||||
camera_transform: &Transform,
|
||||
) -> (Option<TargetSpecifier>, f32)
|
||||
where TargetSpecifier: Clone
|
||||
where
|
||||
TargetSpecifier: Clone,
|
||||
{
|
||||
let mut closest_entity: Option<TargetSpecifier> = None;
|
||||
let mut closest_distance: f32 = f32::MAX;
|
||||
let target_vector: Vec3 = (camera_transform.rotation * Vec3::new(0.0, 0.0, -1.0))
|
||||
.normalize_or_zero();
|
||||
let target_vector: Vec3 =
|
||||
(camera_transform.rotation * Vec3::new(0.0, 0.0, -1.0)).normalize_or_zero();
|
||||
for (entity, trans) in objects {
|
||||
// Use Transform instead of Position because we're basing this
|
||||
// not on the player mesh but on the camera, which doesn't have a position.
|
||||
let (angular_diameter, angle, distance) = calc_angular_diameter_known_target_vector(
|
||||
trans, camera_transform, &target_vector);
|
||||
let (angular_diameter, angle, distance) =
|
||||
calc_angular_diameter_known_target_vector(trans, camera_transform, &target_vector);
|
||||
if angle <= angular_diameter.clamp(0.01, PI32) {
|
||||
// It's in the field of view!
|
||||
//commands.entity(entity).insert(IsTargeted);
|
||||
let distance_to_surface = distance - trans.scale.x;
|
||||
let distance_to_surface = distance - trans.scale.x;
|
||||
if distance_to_surface < closest_distance {
|
||||
closest_distance = distance_to_surface;
|
||||
closest_entity = Some(entity);
|
||||
|
@ -669,8 +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
|
||||
|
|
257
src/chat.rs
|
@ -14,8 +14,8 @@
|
|||
use crate::prelude::*;
|
||||
use bevy::prelude::*;
|
||||
use bevy_xpbd_3d::prelude::*;
|
||||
use serde_yaml::Value;
|
||||
use serde::Deserialize;
|
||||
use serde_yaml::Value;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub const CHATS: &[&str] = &[
|
||||
|
@ -40,7 +40,7 @@ pub const TOKEN_NOWAIT: &str = "nowait";
|
|||
|
||||
pub const TOKEN_INCLUDE: &str = "include";
|
||||
pub const TOKEN_GOTO_EXIT: &str = "EXIT";
|
||||
pub const TOKEN_IF_INLINE: &str = "if "; // for lines like `- if foo:`
|
||||
pub const TOKEN_IF_INLINE: &str = "if "; // for lines like `- if foo:`
|
||||
|
||||
pub const DEFAULT_SOUND: &str = "chat";
|
||||
pub const MAX_BRANCH_DEPTH: usize = 64;
|
||||
|
@ -65,24 +65,22 @@ pub const NON_CHOICE_TOKENS: &[&str] = &[
|
|||
TOKEN_SOUND,
|
||||
TOKEN_NOWAIT,
|
||||
];
|
||||
pub const SKIPPABLE_TOKENS: &[&str] = &[
|
||||
TOKEN_CHAT,
|
||||
TOKEN_LABEL,
|
||||
TOKEN_GOTO,
|
||||
TOKEN_NOWAIT,
|
||||
];
|
||||
pub const SKIPPABLE_TOKENS: &[&str] = &[TOKEN_CHAT, TOKEN_LABEL, TOKEN_GOTO, TOKEN_NOWAIT];
|
||||
|
||||
pub struct ChatPlugin;
|
||||
impl Plugin for ChatPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(Startup, load_chats);
|
||||
app.add_systems(Update, (
|
||||
handle_reply_keys.before(handle_chat_timer),
|
||||
handle_chat_timer.before(handle_chat_events),
|
||||
handle_new_conversations.before(handle_chat_events),
|
||||
handle_chat_events.before(handle_chat_scripts),
|
||||
handle_chat_scripts,
|
||||
));
|
||||
app.add_systems(
|
||||
Update,
|
||||
(
|
||||
handle_reply_keys.before(handle_chat_timer),
|
||||
handle_chat_timer.before(handle_chat_events),
|
||||
handle_new_conversations.before(handle_chat_events),
|
||||
handle_chat_events.before(handle_chat_scripts),
|
||||
handle_chat_scripts,
|
||||
),
|
||||
);
|
||||
app.add_event::<StartConversationEvent>();
|
||||
app.add_event::<ChatEvent>();
|
||||
app.add_event::<ChatScriptEvent>();
|
||||
|
@ -107,8 +105,7 @@ pub struct Choice {
|
|||
pub goto: ChatPos,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
#[derive(Clone)]
|
||||
#[derive(Component, Clone)]
|
||||
pub struct Talker {
|
||||
pub chat_name: String,
|
||||
pub actor_id: String,
|
||||
|
@ -152,7 +149,6 @@ impl Default for Extracted {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// This is the only place where any YAML interaction should be happening.
|
||||
#[derive(Resource)]
|
||||
pub struct ChatDB(Vec<Value>);
|
||||
|
@ -165,8 +161,7 @@ impl ChatDB {
|
|||
if let Value::Sequence(yaml_sequence) = yaml_data {
|
||||
self.0.push(Value::Sequence(yaml_sequence));
|
||||
count += 1;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
error!("Could not load YAML: {:?}", yaml_data);
|
||||
}
|
||||
}
|
||||
|
@ -202,7 +197,10 @@ impl ChatDB {
|
|||
}
|
||||
}
|
||||
|
||||
fn preprocess_includes_recursively(sequence: &mut Value, include_db: &HashMap<String, Vec<Value>>) {
|
||||
fn preprocess_includes_recursively(
|
||||
sequence: &mut Value,
|
||||
include_db: &HashMap<String, Vec<Value>>,
|
||||
) {
|
||||
let mut changes: Vec<(usize, String)> = Vec::new();
|
||||
if let Some(vector) = sequence.as_sequence_mut() {
|
||||
for (index, item) in vector.iter_mut().enumerate() {
|
||||
|
@ -213,8 +211,7 @@ impl ChatDB {
|
|||
if key == TOKEN_INCLUDE {
|
||||
changes.push((index, value.to_string()));
|
||||
}
|
||||
}
|
||||
else if value.is_sequence() {
|
||||
} else if value.is_sequence() {
|
||||
ChatDB::preprocess_includes_recursively(value, include_db);
|
||||
}
|
||||
}
|
||||
|
@ -248,7 +245,9 @@ impl ChatDB {
|
|||
if let Some(result) = found {
|
||||
return Ok(result);
|
||||
}
|
||||
return Err(format!("No chat with the conversation ID `{id}` was found."));
|
||||
return Err(format!(
|
||||
"No chat with the conversation ID `{id}` was found."
|
||||
));
|
||||
}
|
||||
|
||||
// For a given Value, check whether it's a Value::Mapping and whether it
|
||||
|
@ -273,19 +272,15 @@ impl ChatDB {
|
|||
if let Value::String(key) = key {
|
||||
if key == TOKEN_NOWAIT && value.as_bool() == Some(true) {
|
||||
result.nowait = true;
|
||||
}
|
||||
else if key == TOKEN_IF {
|
||||
} else if key == TOKEN_IF {
|
||||
if let Some(condition) = value.as_str() {
|
||||
result.condition = Some(condition.to_string());
|
||||
}
|
||||
}
|
||||
else if non_choice_tokens.contains(&key.as_str()) {
|
||||
} else if non_choice_tokens.contains(&key.as_str()) {
|
||||
// skip over the other non-choice tokens
|
||||
}
|
||||
else if key.as_str().starts_with(TOKEN_IF_INLINE) {
|
||||
} else if key.as_str().starts_with(TOKEN_IF_INLINE) {
|
||||
// skip over inlined if-statements
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
result.choice_text = Some(key.to_string());
|
||||
}
|
||||
}
|
||||
|
@ -295,7 +290,12 @@ impl ChatDB {
|
|||
return None;
|
||||
}
|
||||
|
||||
fn search_label_recursively(&self, sequence: &Value, label: &String, mut pos: ChatPos) -> Option<ChatPos> {
|
||||
fn search_label_recursively(
|
||||
&self,
|
||||
sequence: &Value,
|
||||
label: &String,
|
||||
mut pos: ChatPos,
|
||||
) -> Option<ChatPos> {
|
||||
if pos.len() > MAX_BRANCH_DEPTH {
|
||||
return None;
|
||||
}
|
||||
|
@ -315,9 +315,10 @@ impl ChatDB {
|
|||
}
|
||||
if value.is_sequence() {
|
||||
pos.push(index);
|
||||
if let Some(result) = self.search_label_recursively(
|
||||
value, label, pos.clone()) {
|
||||
return Some(result)
|
||||
if let Some(result) =
|
||||
self.search_label_recursively(value, label, pos.clone())
|
||||
{
|
||||
return Some(result);
|
||||
}
|
||||
pos.pop();
|
||||
}
|
||||
|
@ -357,7 +358,7 @@ impl ChatDB {
|
|||
let index = chat.position.len() - 1;
|
||||
chat.position[index] += 1;
|
||||
}
|
||||
},
|
||||
}
|
||||
Some(Value::Mapping(map)) => {
|
||||
if seek_past_dialog_choices && self.is_choice(Some(&Value::Mapping(map))) {
|
||||
// we just dropped out of a branch and ended up in a dialog
|
||||
|
@ -367,8 +368,7 @@ impl ChatDB {
|
|||
let index = chat.position.len() - 1;
|
||||
chat.position[index] += 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -441,8 +441,7 @@ impl ChatDB {
|
|||
if !SKIPPABLE_TOKENS.contains(&key) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -452,17 +451,16 @@ impl ChatDB {
|
|||
return false;
|
||||
}
|
||||
|
||||
fn process_yaml_entry(
|
||||
&self,
|
||||
chat: &mut Chat,
|
||||
event: &mut EventWriter<ChatEvent>,
|
||||
) -> bool {
|
||||
fn process_yaml_entry(&self, chat: &mut Chat, event: &mut EventWriter<ChatEvent>) -> bool {
|
||||
let current_item = self.at(chat.internal_id, &chat.position);
|
||||
let mut processed_a_choice = false;
|
||||
match current_item {
|
||||
Some(Value::String(message)) => {
|
||||
event.send(ChatEvent::SpawnMessage(message.to_string(),
|
||||
hud::LogLevel::Chat, DEFAULT_SOUND.to_string()));
|
||||
event.send(ChatEvent::SpawnMessage(
|
||||
message.to_string(),
|
||||
hud::LogLevel::Chat,
|
||||
DEFAULT_SOUND.to_string(),
|
||||
));
|
||||
}
|
||||
Some(Value::Mapping(map)) => {
|
||||
let mut sound = DEFAULT_SOUND.to_string();
|
||||
|
@ -506,15 +504,24 @@ impl ChatDB {
|
|||
match (key, value) {
|
||||
(Some(TOKEN_MSG), Value::String(message)) => {
|
||||
event.send(ChatEvent::SpawnMessage(
|
||||
message.to_string(), hud::LogLevel::Chat, sound.clone()));
|
||||
message.to_string(),
|
||||
hud::LogLevel::Chat,
|
||||
sound.clone(),
|
||||
));
|
||||
}
|
||||
(Some(TOKEN_SYSTEM), Value::String(message)) => {
|
||||
event.send(ChatEvent::SpawnMessage(
|
||||
message.to_string(), hud::LogLevel::Info, sound.clone()));
|
||||
message.to_string(),
|
||||
hud::LogLevel::Info,
|
||||
sound.clone(),
|
||||
));
|
||||
}
|
||||
(Some(TOKEN_WARN), Value::String(message)) => {
|
||||
event.send(ChatEvent::SpawnMessage(
|
||||
message.to_string(), hud::LogLevel::Warning, sound.clone()));
|
||||
message.to_string(),
|
||||
hud::LogLevel::Warning,
|
||||
sound.clone(),
|
||||
));
|
||||
}
|
||||
(Some(TOKEN_SET), Value::String(instructions)) => {
|
||||
event.send(ChatEvent::SetVariable(instructions.to_string()));
|
||||
|
@ -551,8 +558,11 @@ impl ChatDB {
|
|||
}
|
||||
None => {
|
||||
if chat.position.len() == 0 {
|
||||
event.send(ChatEvent::SpawnMessage("Disconnected.".to_string(),
|
||||
hud::LogLevel::Info, DEFAULT_SOUND.to_string()));
|
||||
event.send(ChatEvent::SpawnMessage(
|
||||
"Disconnected.".to_string(),
|
||||
hud::LogLevel::Info,
|
||||
DEFAULT_SOUND.to_string(),
|
||||
));
|
||||
event.send(ChatEvent::DespawnAllChats);
|
||||
}
|
||||
}
|
||||
|
@ -584,19 +594,25 @@ impl ChatDB {
|
|||
// Spawn choices until we reach a non-choice item or the end of the branch
|
||||
let mut key: usize = 0;
|
||||
let mut reached_end_of_branch = false;
|
||||
while let Some(data) = self.extract_choice(self.at(chat.internal_id, &chat.position).as_ref()) {
|
||||
while let Some(data) =
|
||||
self.extract_choice(self.at(chat.internal_id, &chat.position).as_ref())
|
||||
{
|
||||
if let Some(choice_text) = data.choice_text {
|
||||
if reached_end_of_branch {
|
||||
break;
|
||||
}
|
||||
let mut goto: Vec<usize> = chat.position.clone();
|
||||
goto.push(0);
|
||||
event.send(ChatEvent::SpawnChoice(choice_text,
|
||||
key, goto, data.nowait, data.condition));
|
||||
event.send(ChatEvent::SpawnChoice(
|
||||
choice_text,
|
||||
key,
|
||||
goto,
|
||||
data.nowait,
|
||||
data.condition,
|
||||
));
|
||||
key += 1;
|
||||
reached_end_of_branch = self.advance_pointer(chat);
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -640,10 +656,7 @@ pub fn handle_new_conversations(
|
|||
talker: event.talker.clone(),
|
||||
};
|
||||
chatdb.advance_chat(&mut chat, &mut ew_chatevent);
|
||||
commands.spawn((
|
||||
chat,
|
||||
world::DespawnOnPlayerDeath,
|
||||
));
|
||||
commands.spawn((chat, world::DespawnOnPlayerDeath));
|
||||
}
|
||||
Err(error) => {
|
||||
error!("Error while looking for chat ID: {error}");
|
||||
|
@ -700,7 +713,10 @@ pub fn handle_chat_events(
|
|||
ChatEvent::SpawnMessage(message, level, sound) => {
|
||||
match level {
|
||||
hud::LogLevel::Chat => {
|
||||
log.chat(message.into(), chat.talker.name.clone().unwrap_or("".to_string()));
|
||||
log.chat(
|
||||
message.into(),
|
||||
chat.talker.name.clone().unwrap_or("".to_string()),
|
||||
);
|
||||
}
|
||||
hud::LogLevel::Info => {
|
||||
log.info(message.into());
|
||||
|
@ -712,36 +728,40 @@ pub fn handle_chat_events(
|
|||
log.warning(message.into());
|
||||
}
|
||||
hud::LogLevel::Always => {
|
||||
log.add(message.into(),
|
||||
log.add(
|
||||
message.into(),
|
||||
chat.talker.name.clone().unwrap_or("".to_string()),
|
||||
hud::LogLevel::Always);
|
||||
hud::LogLevel::Always,
|
||||
);
|
||||
}
|
||||
}
|
||||
chat.timer = now + ((message.len() as f32).max(CHAT_SPEED_MIN_LEN) * TALKER_SPEED_FACTOR * chat.talker.talking_speed / settings.chat_speed) as f64;
|
||||
chat.timer = now
|
||||
+ ((message.len() as f32).max(CHAT_SPEED_MIN_LEN)
|
||||
* TALKER_SPEED_FACTOR
|
||||
* chat.talker.talking_speed
|
||||
/ settings.chat_speed) as f64;
|
||||
|
||||
let sfx = audio::str2sfx(sound);
|
||||
ew_sfx.send(audio::PlaySfxEvent(sfx));
|
||||
}
|
||||
ChatEvent::SpawnChoice(replytext, _key, goto, nowait, condition) => {
|
||||
'out: {
|
||||
if let Some(condition) = condition {
|
||||
if !vars.evaluate_condition(condition, &chat.talker.actor_id) {
|
||||
break 'out;
|
||||
}
|
||||
}
|
||||
commands.spawn((
|
||||
world::DespawnOnPlayerDeath,
|
||||
Choice {
|
||||
text: replytext.into(),
|
||||
key: choice_key,
|
||||
goto: goto.clone(),
|
||||
}
|
||||
));
|
||||
choice_key += 1;
|
||||
if !nowait {
|
||||
chat.timer = now + CHOICE_TIMER / settings.chat_speed as f64;
|
||||
ChatEvent::SpawnChoice(replytext, _key, goto, nowait, condition) => 'out: {
|
||||
if let Some(condition) = condition {
|
||||
if !vars.evaluate_condition(condition, &chat.talker.actor_id) {
|
||||
break 'out;
|
||||
}
|
||||
}
|
||||
commands.spawn((
|
||||
world::DespawnOnPlayerDeath,
|
||||
Choice {
|
||||
text: replytext.into(),
|
||||
key: choice_key,
|
||||
goto: goto.clone(),
|
||||
},
|
||||
));
|
||||
choice_key += 1;
|
||||
if !nowait {
|
||||
chat.timer = now + CHOICE_TIMER / settings.chat_speed as f64;
|
||||
}
|
||||
}
|
||||
ChatEvent::RunScript(script) => {
|
||||
ew_chatscript.send(ChatScriptEvent(script.clone()));
|
||||
|
@ -794,7 +814,14 @@ fn handle_reply_keys(
|
|||
pub fn handle_chat_scripts(
|
||||
mut er_chatscript: EventReader<ChatScriptEvent>,
|
||||
mut q_actor: Query<(&mut actor::Actor, &mut actor::Suit), Without<actor::Player>>,
|
||||
mut q_player: Query<(&mut actor::Actor, &mut actor::Suit, &mut actor::ExperiencesGForce), With<actor::Player>>,
|
||||
mut q_player: Query<
|
||||
(
|
||||
&mut actor::Actor,
|
||||
&mut actor::Suit,
|
||||
&mut actor::ExperiencesGForce,
|
||||
),
|
||||
With<actor::Player>,
|
||||
>,
|
||||
mut q_playercam: Query<(&mut Position, &mut LinearVelocity), With<actor::PlayerCamera>>,
|
||||
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
|
||||
mut ew_effect: EventWriter<visual::SpawnEffectEvent>,
|
||||
|
@ -815,35 +842,37 @@ pub fn handle_chat_scripts(
|
|||
|
||||
// Process the script
|
||||
match name {
|
||||
"refilloxygen" => if let Ok(mut amount) = param1.to_string().parse::<f32>() {
|
||||
for (_, mut suit, _) in q_player.iter_mut() {
|
||||
if param2.is_empty() {
|
||||
suit.oxygen = (suit.oxygen + amount).clamp(0.0, suit.oxygen_max);
|
||||
}
|
||||
else {
|
||||
let mut found_other = false;
|
||||
info!("param2={}", param2);
|
||||
for (other_actor, mut other_suit) in q_actor.iter_mut() {
|
||||
if !other_actor.id.is_empty() {
|
||||
info!("ID={}", other_actor.id);
|
||||
"refilloxygen" => {
|
||||
if let Ok(mut amount) = param1.to_string().parse::<f32>() {
|
||||
for (_, mut suit, _) in q_player.iter_mut() {
|
||||
if param2.is_empty() {
|
||||
suit.oxygen = (suit.oxygen + amount).clamp(0.0, suit.oxygen_max);
|
||||
} else {
|
||||
let mut found_other = false;
|
||||
info!("param2={}", param2);
|
||||
for (other_actor, mut other_suit) in q_actor.iter_mut() {
|
||||
if !other_actor.id.is_empty() {
|
||||
info!("ID={}", other_actor.id);
|
||||
}
|
||||
if other_actor.id == param2 {
|
||||
found_other = true;
|
||||
amount = amount
|
||||
.clamp(0.0, other_suit.oxygen)
|
||||
.clamp(0.0, suit.oxygen_max - suit.oxygen);
|
||||
other_suit.oxygen = other_suit.oxygen - amount;
|
||||
suit.oxygen =
|
||||
(suit.oxygen + amount).clamp(0.0, suit.oxygen_max);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if other_actor.id == param2 {
|
||||
found_other = true;
|
||||
amount = amount
|
||||
.clamp(0.0, other_suit.oxygen)
|
||||
.clamp(0.0, suit.oxygen_max - suit.oxygen);
|
||||
other_suit.oxygen = other_suit.oxygen - amount;
|
||||
suit.oxygen = (suit.oxygen + amount).clamp(0.0, suit.oxygen_max);
|
||||
break;
|
||||
if !found_other {
|
||||
error!("Script error: could not find actor with ID `{}`", param2);
|
||||
}
|
||||
}
|
||||
if !found_other {
|
||||
error!("Script error: could not find actor with ID `{}`", param2);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error!("Invalid parameter for command `{}`: `{}`", name, param1);
|
||||
}
|
||||
} else {
|
||||
error!("Invalid parameter for command `{}`: `{}`", name, param1);
|
||||
}
|
||||
"repairsuit" => {
|
||||
ew_achievement.send(game::AchievementEvent::RepairSuit);
|
||||
|
@ -854,21 +883,23 @@ pub fn handle_chat_scripts(
|
|||
"cryotrip" => {
|
||||
if param1.is_empty() {
|
||||
error!("Chat script cryotrip needs a parameter");
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
if let Ok((mut pos, mut v)) = q_playercam.get_single_mut() {
|
||||
let busstop = match param1 {
|
||||
"serenity" => Some("busstopclippy"),
|
||||
"farview" => Some("busstopclippy2"),
|
||||
"metisprime" => Some("busstopclippy3"),
|
||||
_ => None
|
||||
_ => None,
|
||||
};
|
||||
if let Some(station) = busstop {
|
||||
if let Some(target) = id2pos.0.get(&station.to_string()) {
|
||||
pos.0 = *target + DVec3::new(0.0, -1000.0, 0.0);
|
||||
v.0 = DVec3::ZERO;
|
||||
} else {
|
||||
error!("Could not determine position of actor with ID: '{}'", station);
|
||||
error!(
|
||||
"Could not determine position of actor with ID: '{}'",
|
||||
station
|
||||
);
|
||||
}
|
||||
} else {
|
||||
error!("Invalid destination for cryotrip chat script: '{}'", param1);
|
||||
|
|
652
src/cmd.rs
|
@ -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,229 +659,235 @@ fn spawn_entities(
|
|||
}
|
||||
absolute_pos += nature::phase_dist_to_coords(-phase_radians, r);
|
||||
}
|
||||
let scale = Vec3::splat(if state.is_sun {
|
||||
5.0
|
||||
} else if state.is_moon && settings.large_moons {
|
||||
3.0
|
||||
} else {
|
||||
1.0
|
||||
} * state.model_scale);
|
||||
let scale = Vec3::splat(
|
||||
if state.is_sun {
|
||||
5.0
|
||||
} else if state.is_moon && settings.large_moons {
|
||||
3.0
|
||||
} else {
|
||||
1.0
|
||||
} * state.model_scale,
|
||||
);
|
||||
|
||||
// Spawn the actor
|
||||
let actor_entity;
|
||||
{
|
||||
let mut actor = commands.spawn_empty();
|
||||
actor.insert(actor::Actor {
|
||||
id: state.id.clone(),
|
||||
name: state.name.clone(),
|
||||
camdistance: state.camdistance,
|
||||
..default()
|
||||
});
|
||||
actor.insert(SleepingDisabled);
|
||||
actor.insert(world::DespawnOnPlayerDeath);
|
||||
actor.insert(actor::HitPoints::default());
|
||||
actor.insert(Position::from(absolute_pos));
|
||||
if state.is_sphere {
|
||||
let sphere_texture_handle = if let Some(model) = &state.model {
|
||||
Some(asset_server.load(format!("textures/{}.jpg", model)))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
rotation = Quat::from_rotation_x(-90f32.to_radians()) * rotation;
|
||||
let sphere_handle = meshes.add(Sphere::new(1.0).mesh().uv(128, 128));
|
||||
let sphere_material_handle = materials.add(StandardMaterial {
|
||||
base_color_texture: sphere_texture_handle,
|
||||
perceptual_roughness: 1.0,
|
||||
metallic: 0.0,
|
||||
..default()
|
||||
});
|
||||
actor.insert(PbrBundle {
|
||||
mesh: sphere_handle,
|
||||
material: sphere_material_handle,
|
||||
transform: Transform::from_scale(scale),
|
||||
..default()
|
||||
});
|
||||
} else if let Some(model) = &state.model {
|
||||
actor.insert(SpatialBundle {
|
||||
transform: Transform::from_scale(scale),
|
||||
..default()
|
||||
});
|
||||
load_asset(model.as_str(), &mut actor, &*asset_server);
|
||||
}
|
||||
actor.insert(Rotation::from(rotation));
|
||||
|
||||
// Physics Parameters
|
||||
if state.has_physics {
|
||||
actor.insert(RigidBody::Dynamic);
|
||||
actor.insert(LinearVelocity(state.velocity));
|
||||
actor.insert(AngularVelocity(state.angular_momentum));
|
||||
actor.insert(ColliderDensity(state.density));
|
||||
if state.collider_is_mesh {
|
||||
actor.insert(MassPropertiesBundle::new_computed(
|
||||
&Collider::sphere(0.5 * state.model_scale as f64), state.density));
|
||||
actor.insert(AsyncSceneCollider::new(Some(
|
||||
ComputedCollider::TriMesh
|
||||
//ComputedCollider::ConvexDecomposition(VHACDParameters::default())
|
||||
)));
|
||||
}
|
||||
else if state.collider_is_one_mesh_of_scene {
|
||||
actor.insert(MassPropertiesBundle::new_computed(
|
||||
&Collider::sphere(0.5 * state.model_scale as f64), state.density));
|
||||
actor.insert(AsyncSceneCollider::new(None)
|
||||
.with_shape_for_name("Collider", ComputedCollider::TriMesh)
|
||||
.with_layers_for_name("Collider", CollisionLayers::ALL)
|
||||
//.with_density_for_name("Collider", state.density)
|
||||
);
|
||||
actor.insert(NeedsSceneColliderRemoved);
|
||||
}
|
||||
else {
|
||||
actor.insert(state.collider.clone());
|
||||
}
|
||||
}
|
||||
// TODO: angular velocity for objects without collisions, static objects
|
||||
|
||||
// Optional Components
|
||||
if state.is_player {
|
||||
actor.insert(actor::Player);
|
||||
actor.insert(actor::PlayerCamera);
|
||||
}
|
||||
if state.is_sun {
|
||||
let (r, g, b) = nature::star_color_index_to_rgb(0.656);
|
||||
actor.insert(materials.add(StandardMaterial {
|
||||
base_color: Color::rgb(r, g, b) * 13.0,
|
||||
unlit: true,
|
||||
..default()
|
||||
}));
|
||||
actor.insert((
|
||||
NotShadowCaster,
|
||||
NotShadowReceiver,
|
||||
));
|
||||
}
|
||||
if state.is_targeted_on_startup {
|
||||
actor.insert(hud::IsTargeted);
|
||||
}
|
||||
if let Some((mindist, id)) = &state.show_only_in_map_at_distance {
|
||||
actor.insert(camera::ShowOnlyInMap {
|
||||
min_distance: *mindist,
|
||||
distance_to_id: id.clone()
|
||||
});
|
||||
}
|
||||
if state.is_player || state.is_vehicle {
|
||||
// used to apply mouse movement to actor rotation
|
||||
actor.insert(ExternalTorque::ZERO.with_persistence(false));
|
||||
}
|
||||
if state.is_lifeform {
|
||||
actor.insert(actor::LifeForm::default());
|
||||
actor.insert(actor::ExperiencesGForce::default());
|
||||
actor.insert(actor::Suit {
|
||||
oxygen: state.oxygen,
|
||||
oxygen_max: nature::OXY_D,
|
||||
integrity: state.suit_integrity,
|
||||
..default()
|
||||
});
|
||||
actor.insert(actor::Battery::default());
|
||||
}
|
||||
if state.is_clickable {
|
||||
actor.insert(hud::IsClickable {
|
||||
let mut actor = commands.spawn_empty();
|
||||
actor.insert(actor::Actor {
|
||||
id: state.id.clone(),
|
||||
name: state.name.clone(),
|
||||
pronoun: state.pronoun.clone(),
|
||||
camdistance: state.camdistance,
|
||||
..default()
|
||||
});
|
||||
}
|
||||
if let Some(value) = state.wants_maxrotation {
|
||||
actor.insert(actor::WantsMaxRotation(value));
|
||||
}
|
||||
if let Some(value) = state.wants_maxvelocity {
|
||||
actor.insert(actor::WantsMaxVelocity(value));
|
||||
}
|
||||
if let Some(color) = state.light_color {
|
||||
actor.insert((
|
||||
PointLight {
|
||||
intensity: state.light_brightness,
|
||||
color,
|
||||
range: 2000.0,
|
||||
shadows_enabled: settings.shadows_pointlights,
|
||||
actor.insert(SleepingDisabled);
|
||||
actor.insert(world::DespawnOnPlayerDeath);
|
||||
actor.insert(actor::HitPoints::default());
|
||||
actor.insert(Position::from(absolute_pos));
|
||||
if state.is_sphere {
|
||||
let sphere_texture_handle = if let Some(model) = &state.model {
|
||||
Some(asset_server.load(format!("textures/{}.jpg", model)))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
rotation = Quat::from_rotation_x(-90f32.to_radians()) * rotation;
|
||||
let sphere_handle = meshes.add(Sphere::new(1.0).mesh().uv(128, 128));
|
||||
let sphere_material_handle = materials.add(StandardMaterial {
|
||||
base_color_texture: sphere_texture_handle,
|
||||
perceptual_roughness: 1.0,
|
||||
metallic: 0.0,
|
||||
..default()
|
||||
},
|
||||
bevy::pbr::CubemapVisibleEntities::default(),
|
||||
bevy::render::primitives::CubemapFrusta::default(),
|
||||
));
|
||||
}
|
||||
if !state.id.is_empty() {
|
||||
actor.insert(actor::Identifier(state.id.clone()));
|
||||
id2pos.0.insert(state.id.clone(), absolute_pos);
|
||||
}
|
||||
if !state.chat.is_empty() {
|
||||
actor.insert(chat::Talker {
|
||||
actor_id: state.id.clone(),
|
||||
chat_name: state.chat.clone(),
|
||||
name: state.name.clone(),
|
||||
pronoun: state.pronoun.clone(),
|
||||
talking_speed: 1.0,
|
||||
});
|
||||
if let Some(name) = &state.name {
|
||||
achievement_tracker.all_people.insert(name.clone());
|
||||
});
|
||||
actor.insert(PbrBundle {
|
||||
mesh: sphere_handle,
|
||||
material: sphere_material_handle,
|
||||
transform: Transform::from_scale(scale),
|
||||
..default()
|
||||
});
|
||||
} else if let Some(model) = &state.model {
|
||||
actor.insert(SpatialBundle {
|
||||
transform: Transform::from_scale(scale),
|
||||
..default()
|
||||
});
|
||||
load_asset(model.as_str(), &mut actor, &*asset_server);
|
||||
}
|
||||
}
|
||||
if state.is_vehicle {
|
||||
actor.insert(actor::Vehicle::default());
|
||||
if let Some(name) = &state.name {
|
||||
achievement_tracker.all_vehicles.insert(name.clone());
|
||||
actor.insert(Rotation::from(rotation));
|
||||
|
||||
// Physics Parameters
|
||||
if state.has_physics {
|
||||
actor.insert(RigidBody::Dynamic);
|
||||
actor.insert(LinearVelocity(state.velocity));
|
||||
actor.insert(AngularVelocity(state.angular_momentum));
|
||||
actor.insert(ColliderDensity(state.density));
|
||||
if state.collider_is_mesh {
|
||||
actor.insert(MassPropertiesBundle::new_computed(
|
||||
&Collider::sphere(0.5 * state.model_scale as f64),
|
||||
state.density,
|
||||
));
|
||||
actor.insert(AsyncSceneCollider::new(Some(
|
||||
ComputedCollider::TriMesh, //ComputedCollider::ConvexDecomposition(VHACDParameters::default())
|
||||
)));
|
||||
} else if state.collider_is_one_mesh_of_scene {
|
||||
actor.insert(MassPropertiesBundle::new_computed(
|
||||
&Collider::sphere(0.5 * state.model_scale as f64),
|
||||
state.density,
|
||||
));
|
||||
actor.insert(
|
||||
AsyncSceneCollider::new(None)
|
||||
.with_shape_for_name("Collider", ComputedCollider::TriMesh)
|
||||
.with_layers_for_name("Collider", CollisionLayers::ALL), //.with_density_for_name("Collider", state.density)
|
||||
);
|
||||
actor.insert(NeedsSceneColliderRemoved);
|
||||
} else {
|
||||
actor.insert(state.collider.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
if state.is_vehicle
|
||||
|| state.is_suited
|
||||
|| state.thrust_forward > 0.0
|
||||
|| state.thrust_sideways > 0.0
|
||||
|| state.thrust_back > 0.0
|
||||
|| state.reaction_wheels > 0.0
|
||||
{
|
||||
actor.insert(actor::Engine {
|
||||
thrust_forward: state.thrust_forward,
|
||||
thrust_back: state.thrust_back,
|
||||
thrust_sideways: state.thrust_sideways,
|
||||
reaction_wheels: state.reaction_wheels,
|
||||
warmup_seconds: state.warmup_seconds,
|
||||
engine_type: state.engine_type,
|
||||
..default()
|
||||
});
|
||||
}
|
||||
if let Some(_) = state.ar_model {
|
||||
actor.insert(hud::AugmentedRealityOverlayBroadcaster);
|
||||
}
|
||||
if state.is_player {
|
||||
actor.with_children(|builder| {
|
||||
builder.spawn((
|
||||
world::DespawnOnPlayerDeath,
|
||||
actor::PlayersFlashLight,
|
||||
SpotLightBundle {
|
||||
transform: Transform {
|
||||
translation: Vec3::new(0.0, 0.0, 1.0),
|
||||
rotation: Quat::from_rotation_y(180f32.to_radians()),
|
||||
..default()
|
||||
},
|
||||
spot_light: SpotLight {
|
||||
intensity: 40_000_000.0, // lumens
|
||||
color: Color::WHITE,
|
||||
shadows_enabled: true,
|
||||
inner_angle: PI32 / 8.0 * 0.85,
|
||||
outer_angle: PI32 / 4.0,
|
||||
range: 2000.0,
|
||||
..default()
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
// TODO: angular velocity for objects without collisions, static objects
|
||||
|
||||
// Optional Components
|
||||
if state.is_player {
|
||||
actor.insert(actor::Player);
|
||||
actor.insert(actor::PlayerCamera);
|
||||
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);
|
||||
actor.insert(materials.add(StandardMaterial {
|
||||
base_color: Color::rgb(r, g, b) * 13.0,
|
||||
unlit: true,
|
||||
..default()
|
||||
}));
|
||||
actor.insert((NotShadowCaster, NotShadowReceiver));
|
||||
}
|
||||
if state.is_targeted_on_startup {
|
||||
actor.insert(hud::IsTargeted);
|
||||
}
|
||||
if let Some((mindist, id)) = &state.show_only_in_map_at_distance {
|
||||
actor.insert(camera::ShowOnlyInMap {
|
||||
min_distance: *mindist,
|
||||
distance_to_id: id.clone(),
|
||||
});
|
||||
}
|
||||
if state.is_player || state.is_vehicle {
|
||||
// used to apply mouse movement to actor rotation
|
||||
actor.insert(ExternalTorque::ZERO.with_persistence(false));
|
||||
}
|
||||
if state.is_lifeform {
|
||||
actor.insert(actor::LifeForm::default());
|
||||
actor.insert(actor::ExperiencesGForce::default());
|
||||
actor.insert(actor::Suit {
|
||||
oxygen: state.oxygen,
|
||||
oxygen_max: nature::OXY_D,
|
||||
integrity: state.suit_integrity,
|
||||
..default()
|
||||
});
|
||||
actor.insert(actor::Battery::default());
|
||||
}
|
||||
if state.is_clickable {
|
||||
actor.insert(hud::IsClickable {
|
||||
name: state.name.clone(),
|
||||
pronoun: state.pronoun.clone(),
|
||||
..default()
|
||||
});
|
||||
}
|
||||
if let Some(value) = state.wants_maxrotation {
|
||||
actor.insert(actor::WantsMaxRotation(value));
|
||||
}
|
||||
if let Some(value) = state.wants_maxvelocity {
|
||||
actor.insert(actor::WantsMaxVelocity(value));
|
||||
}
|
||||
if let Some(value) = &state.wants_tolookat_id {
|
||||
actor.insert(actor::WantsToLookAt(value.clone()));
|
||||
}
|
||||
if let Some(color) = state.light_color {
|
||||
actor.insert((
|
||||
PointLight {
|
||||
intensity: state.light_brightness,
|
||||
color,
|
||||
range: 2000.0,
|
||||
shadows_enabled: settings.shadows_pointlights,
|
||||
..default()
|
||||
}
|
||||
},
|
||||
bevy::pbr::CubemapVisibleEntities::default(),
|
||||
bevy::render::primitives::CubemapFrusta::default(),
|
||||
));
|
||||
});
|
||||
}
|
||||
actor_entity = actor.id();
|
||||
}
|
||||
if !state.id.is_empty() {
|
||||
actor.insert(actor::Identifier(state.id.clone()));
|
||||
id2pos.0.insert(state.id.clone(), absolute_pos);
|
||||
}
|
||||
if !state.chat.is_empty() {
|
||||
actor.insert(chat::Talker {
|
||||
actor_id: state.id.clone(),
|
||||
chat_name: state.chat.clone(),
|
||||
name: state.name.clone(),
|
||||
pronoun: state.pronoun.clone(),
|
||||
talking_speed: 1.0,
|
||||
});
|
||||
if let Some(name) = &state.name {
|
||||
achievement_tracker.all_people.insert(name.clone());
|
||||
}
|
||||
}
|
||||
if state.is_vehicle {
|
||||
actor.insert(actor::Vehicle::default());
|
||||
if let Some(name) = &state.name {
|
||||
achievement_tracker.all_vehicles.insert(name.clone());
|
||||
}
|
||||
}
|
||||
if state.is_vehicle
|
||||
|| state.is_suited
|
||||
|| state.thrust_forward > 0.0
|
||||
|| state.thrust_sideways > 0.0
|
||||
|| state.thrust_back > 0.0
|
||||
|| state.reaction_wheels > 0.0
|
||||
{
|
||||
actor.insert(actor::Engine {
|
||||
thrust_forward: state.thrust_forward,
|
||||
thrust_back: state.thrust_back,
|
||||
thrust_sideways: state.thrust_sideways,
|
||||
reaction_wheels: state.reaction_wheels,
|
||||
warmup_seconds: state.warmup_seconds,
|
||||
engine_type: state.engine_type,
|
||||
..default()
|
||||
});
|
||||
}
|
||||
if let Some(_) = state.ar_model {
|
||||
actor.insert(hud::AugmentedRealityOverlayBroadcaster);
|
||||
}
|
||||
if state.is_player {
|
||||
actor.with_children(|builder| {
|
||||
builder.spawn((
|
||||
world::DespawnOnPlayerDeath,
|
||||
actor::PlayersFlashLight,
|
||||
SpotLightBundle {
|
||||
transform: Transform {
|
||||
translation: Vec3::new(0.0, 0.0, 1.0),
|
||||
rotation: Quat::from_rotation_y(180f32.to_radians()),
|
||||
..default()
|
||||
},
|
||||
spot_light: SpotLight {
|
||||
intensity: 40_000_000.0, // lumens
|
||||
color: Color::WHITE,
|
||||
shadows_enabled: true,
|
||||
inner_angle: PI32 / 8.0 * 0.85,
|
||||
outer_angle: PI32 / 4.0,
|
||||
range: 2000.0,
|
||||
..default()
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
..default()
|
||||
},
|
||||
));
|
||||
});
|
||||
}
|
||||
actor_entity = actor.id();
|
||||
}
|
||||
|
||||
if let Some(ar_asset_name) = &state.ar_model {
|
||||
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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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"
|
||||
|
|
108
src/game.rs
|
@ -11,10 +11,10 @@
|
|||
// This module handles player input, and coordinates interplay between other modules
|
||||
|
||||
use crate::prelude::*;
|
||||
use bevy::prelude::*;
|
||||
use bevy::pbr::ExtendedMaterial;
|
||||
use bevy::prelude::*;
|
||||
use bevy::scene::SceneInstance;
|
||||
use bevy::window::{Window, WindowMode, PrimaryWindow};
|
||||
use bevy::window::{PrimaryWindow, Window, WindowMode};
|
||||
use bevy_xpbd_3d::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
|
@ -26,21 +26,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);
|
||||
|
|
828
src/hud.rs
24
src/load.rs
|
@ -11,16 +11,18 @@
|
|||
// This module manages asset loading.
|
||||
|
||||
use bevy::ecs::system::EntityCommands;
|
||||
use bevy::render::render_resource::{AsBindGroup, ShaderRef};
|
||||
use bevy::pbr::{ExtendedMaterial, MaterialExtension, OpaqueRendererMethod};
|
||||
use bevy::prelude::*;
|
||||
use bevy::render::render_resource::{AsBindGroup, ShaderRef};
|
||||
|
||||
pub struct LoadPlugin;
|
||||
impl Plugin for LoadPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins(MaterialPlugin::<JupitersRing>::default());
|
||||
app.add_plugins(MaterialPlugin::<SkyBox>::default());
|
||||
app.add_plugins(MaterialPlugin::<ExtendedMaterial<StandardMaterial, AsteroidSurface, >>::default());
|
||||
app.add_plugins(MaterialPlugin::<
|
||||
ExtendedMaterial<StandardMaterial, AsteroidSurface>,
|
||||
>::default());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
|
41
src/main.rs
|
@ -28,18 +28,19 @@ pub mod visual;
|
|||
pub mod world;
|
||||
|
||||
pub mod prelude {
|
||||
pub use crate::{actor, audio, camera, chat, cmd, common, game, hud,
|
||||
load, menu, nature, var, visual, world};
|
||||
pub use crate::common::*;
|
||||
pub use crate::var::Settings;
|
||||
pub use crate::load::load_asset;
|
||||
pub use game::{GameEvent, Turn};
|
||||
pub use crate::var::Settings;
|
||||
pub use crate::{
|
||||
actor, audio, camera, chat, cmd, common, game, hud, load, menu, nature, var, visual, world,
|
||||
};
|
||||
pub use game::Turn::Toggle;
|
||||
pub use game::{GameEvent, Turn};
|
||||
}
|
||||
|
||||
use bevy::window::{Window, WindowMode, PrimaryWindow, CursorGrabMode};
|
||||
use bevy::diagnostic::FrameTimeDiagnosticsPlugin;
|
||||
use bevy::prelude::*;
|
||||
use bevy::window::{CursorGrabMode, PrimaryWindow, Window, WindowMode};
|
||||
use std::env;
|
||||
|
||||
const HELP: &str = "./outfly [options]
|
||||
|
@ -68,8 +69,7 @@ fn main() {
|
|||
if arg == "--help" || arg == "-h" {
|
||||
println!("{}", HELP);
|
||||
return;
|
||||
}
|
||||
else if arg == "--version" || arg == "-v" {
|
||||
} else if arg == "--version" || arg == "-v" {
|
||||
let version = option_env!("CARGO_PKG_VERSION").unwrap();
|
||||
let name = option_env!("CARGO_PKG_NAME").unwrap();
|
||||
let homepage = option_env!("CARGO_PKG_HOMEPAGE").unwrap();
|
||||
|
@ -77,28 +77,23 @@ fn main() {
|
|||
println!("License: GNU GPL version 3: https://gnu.org/licenses/gpl.html");
|
||||
println!("{homepage}");
|
||||
return;
|
||||
}
|
||||
else if arg == "--gl" {
|
||||
} else if arg == "--gl" {
|
||||
opt.use_gl = true;
|
||||
}
|
||||
else if arg == "--windowed" {
|
||||
} else if arg == "--windowed" {
|
||||
opt.window_mode_initial = WindowMode::Windowed;
|
||||
}
|
||||
else if arg == "--fs-legacy" {
|
||||
} else if arg == "--fs-legacy" {
|
||||
let mode = WindowMode::Fullscreen;
|
||||
if opt.window_mode_initial == opt.window_mode_fullscreen {
|
||||
opt.window_mode_initial = mode;
|
||||
}
|
||||
opt.window_mode_fullscreen = mode;
|
||||
}
|
||||
else if arg == "--fs-sized" {
|
||||
} else if arg == "--fs-sized" {
|
||||
let mode = WindowMode::SizedFullscreen;
|
||||
if opt.window_mode_initial == opt.window_mode_fullscreen {
|
||||
opt.window_mode_initial = mode;
|
||||
}
|
||||
opt.window_mode_fullscreen = mode;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
println!("{}", HELP);
|
||||
println!("\nERROR: no such option: `{}`", arg);
|
||||
return;
|
||||
|
@ -114,7 +109,7 @@ fn main() {
|
|||
|
||||
#[cfg(feature = "embed_assets")]
|
||||
app.add_plugins(bevy_embedded_assets::EmbeddedAssetPlugin {
|
||||
mode: bevy_embedded_assets::PluginMode::ReplaceDefault
|
||||
mode: bevy_embedded_assets::PluginMode::ReplaceDefault,
|
||||
});
|
||||
|
||||
app.add_plugins(OutFlyPlugin);
|
||||
|
@ -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;
|
||||
|
|
354
src/menu.rs
|
@ -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
|
||||
.after(game::handle_game_event)
|
||||
.run_if(on_event::<UpdateMenuEvent>()));
|
||||
app.add_systems(
|
||||
PostUpdate,
|
||||
update_menu
|
||||
.after(game::handle_game_event)
|
||||
.run_if(on_event::<UpdateMenuEvent>()),
|
||||
);
|
||||
app.add_systems(Update, handle_input.run_if(alive));
|
||||
app.insert_resource(DeathScreenInputDelayTimer(
|
||||
Timer::from_seconds(1.0, TimerMode::Once)));
|
||||
app.insert_resource(DeathScreenInputDelayTimer(Timer::from_seconds(
|
||||
1.0,
|
||||
TimerMode::Once,
|
||||
)));
|
||||
app.insert_resource(MenuState::default());
|
||||
app.add_event::<DeathScreenEvent>();
|
||||
app.add_event::<UpdateMenuEvent>();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource)] pub struct DeathScreenInputDelayTimer(pub Timer);
|
||||
#[derive(Component)] pub struct MenuElement;
|
||||
#[derive(Component)] pub struct MenuTopLevel;
|
||||
#[derive(Component)] pub struct MenuAchievements;
|
||||
#[derive(Component)] pub struct DeathScreenElement;
|
||||
#[derive(Component)] pub struct DeathText;
|
||||
#[derive(Event)] pub struct UpdateMenuEvent;
|
||||
#[derive(Event, PartialEq)] pub enum DeathScreenEvent { Show, Hide }
|
||||
#[derive(Resource)]
|
||||
pub struct DeathScreenInputDelayTimer(pub Timer);
|
||||
#[derive(Component)]
|
||||
pub struct MenuElement;
|
||||
#[derive(Component)]
|
||||
pub struct MenuTopLevel;
|
||||
#[derive(Component)]
|
||||
pub struct MenuAchievements;
|
||||
#[derive(Component)]
|
||||
pub struct DeathScreenElement;
|
||||
#[derive(Component)]
|
||||
pub struct DeathText;
|
||||
#[derive(Event)]
|
||||
pub struct UpdateMenuEvent;
|
||||
#[derive(Event, PartialEq)]
|
||||
pub enum DeathScreenEvent {
|
||||
Show,
|
||||
Hide,
|
||||
}
|
||||
|
||||
pub const MENUDEF: &[(&str, MenuAction)] = &[
|
||||
("", MenuAction::ToggleMap),
|
||||
("", 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,33 +136,38 @@ pub fn setup(
|
|||
color: settings.hud_color_death_achievements,
|
||||
..default()
|
||||
};
|
||||
commands.spawn((
|
||||
DeathScreenElement,
|
||||
NodeBundle {
|
||||
style: style_centered(),
|
||||
visibility: Visibility::Hidden,
|
||||
..default()
|
||||
},
|
||||
)).with_children(|builder| {
|
||||
builder.spawn((
|
||||
DeathText,
|
||||
TextBundle {
|
||||
text: Text {
|
||||
sections: vec![
|
||||
TextSection::new("", style_death_poem),
|
||||
TextSection::new("You are dead.\n", style_death),
|
||||
TextSection::new("Cause: ", style_death_subtext.clone()),
|
||||
TextSection::new("Unknown", style_death_subtext),
|
||||
TextSection::new("", style_death_achievements),
|
||||
TextSection::new("\n\n\n\nPress E to begin anew.", style_death_subsubtext),
|
||||
],
|
||||
justify: JustifyText::Center,
|
||||
..default()
|
||||
},
|
||||
commands
|
||||
.spawn((
|
||||
DeathScreenElement,
|
||||
NodeBundle {
|
||||
style: style_centered(),
|
||||
visibility: Visibility::Hidden,
|
||||
..default()
|
||||
},
|
||||
));
|
||||
});
|
||||
))
|
||||
.with_children(|builder| {
|
||||
builder.spawn((
|
||||
DeathText,
|
||||
TextBundle {
|
||||
text: Text {
|
||||
sections: vec![
|
||||
TextSection::new("", style_death_poem),
|
||||
TextSection::new("You are dead.\n", style_death),
|
||||
TextSection::new("Cause: ", style_death_subtext.clone()),
|
||||
TextSection::new("Unknown", style_death_subtext),
|
||||
TextSection::new("", style_death_achievements),
|
||||
TextSection::new(
|
||||
"\n\n\n\nPress E to begin anew.",
|
||||
style_death_subsubtext,
|
||||
),
|
||||
],
|
||||
justify: JustifyText::Center,
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
},
|
||||
));
|
||||
});
|
||||
|
||||
let style_menu = TextStyle {
|
||||
font: font_handle.clone(),
|
||||
|
@ -150,9 +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,32 +192,34 @@ pub fn setup(
|
|||
},
|
||||
));
|
||||
|
||||
commands.spawn((
|
||||
MenuElement,
|
||||
NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Percent(100.0),
|
||||
height: Val::Percent(100.0),
|
||||
align_items: AlignItems::Center,
|
||||
justify_content: JustifyContent::SpaceAround,
|
||||
..default()
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
..default()
|
||||
},
|
||||
)).with_children(|builder| {
|
||||
builder.spawn((
|
||||
MenuTopLevel,
|
||||
TextBundle {
|
||||
text: Text {
|
||||
sections,
|
||||
justify: JustifyText::Center,
|
||||
commands
|
||||
.spawn((
|
||||
MenuElement,
|
||||
NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Percent(100.0),
|
||||
height: Val::Percent(100.0),
|
||||
align_items: AlignItems::Center,
|
||||
justify_content: JustifyContent::SpaceAround,
|
||||
..default()
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
..default()
|
||||
},
|
||||
));
|
||||
});
|
||||
))
|
||||
.with_children(|builder| {
|
||||
builder.spawn((
|
||||
MenuTopLevel,
|
||||
TextBundle {
|
||||
text: Text {
|
||||
sections,
|
||||
justify: JustifyText::Center,
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
},
|
||||
));
|
||||
});
|
||||
|
||||
let style_achievement_header = TextStyle {
|
||||
font: font_handle.clone(),
|
||||
|
@ -205,40 +235,43 @@ pub fn setup(
|
|||
};
|
||||
let achievement_count = achievement_tracker.to_bool_vec().len();
|
||||
|
||||
commands.spawn((
|
||||
MenuElement,
|
||||
NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Percent(100.0),
|
||||
height: Val::Percent(100.0),
|
||||
left: Val::Percent(2.0),
|
||||
top: Val::Percent(2.0),
|
||||
align_items: AlignItems::Start,
|
||||
justify_content: JustifyContent::Start,
|
||||
..default()
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
..default()
|
||||
},
|
||||
)).with_children(|builder| {
|
||||
let mut sections = vec![
|
||||
TextSection::new("Achievements\n", style_achievement_header.clone())
|
||||
];
|
||||
sections.extend(Vec::from_iter((0..achievement_count).map(|_|
|
||||
TextSection::new("", style_achievement.clone())
|
||||
)));
|
||||
builder.spawn((
|
||||
MenuAchievements,
|
||||
TextBundle {
|
||||
text: Text {
|
||||
sections,
|
||||
justify: JustifyText::Left,
|
||||
commands
|
||||
.spawn((
|
||||
MenuElement,
|
||||
NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Percent(100.0),
|
||||
height: Val::Percent(100.0),
|
||||
left: Val::Percent(2.0),
|
||||
top: Val::Percent(2.0),
|
||||
align_items: AlignItems::Start,
|
||||
justify_content: JustifyContent::Start,
|
||||
..default()
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
..default()
|
||||
},
|
||||
));
|
||||
});
|
||||
))
|
||||
.with_children(|builder| {
|
||||
let mut sections = vec![TextSection::new(
|
||||
"Achievements\n",
|
||||
style_achievement_header.clone(),
|
||||
)];
|
||||
sections.extend(Vec::from_iter(
|
||||
(0..achievement_count).map(|_| TextSection::new("", style_achievement.clone())),
|
||||
));
|
||||
builder.spawn((
|
||||
MenuAchievements,
|
||||
TextBundle {
|
||||
text: Text {
|
||||
sections,
|
||||
justify: JustifyText::Left,
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
},
|
||||
));
|
||||
});
|
||||
|
||||
let keybindings = include_str!("data/keybindings.in");
|
||||
let style_keybindings = TextStyle {
|
||||
|
@ -248,36 +281,36 @@ pub fn setup(
|
|||
..default()
|
||||
};
|
||||
|
||||
commands.spawn((
|
||||
MenuElement,
|
||||
NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Percent(96.0),
|
||||
height: Val::Percent(96.0),
|
||||
left: Val::Percent(2.0),
|
||||
top: Val::Percent(2.0),
|
||||
align_items: AlignItems::Start,
|
||||
justify_content: JustifyContent::End,
|
||||
commands
|
||||
.spawn((
|
||||
MenuElement,
|
||||
NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Percent(96.0),
|
||||
height: Val::Percent(96.0),
|
||||
left: Val::Percent(2.0),
|
||||
top: Val::Percent(2.0),
|
||||
align_items: AlignItems::Start,
|
||||
justify_content: JustifyContent::End,
|
||||
..default()
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
..default()
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
..default()
|
||||
},
|
||||
)).with_children(|builder| {
|
||||
builder.spawn((
|
||||
TextBundle {
|
||||
))
|
||||
.with_children(|builder| {
|
||||
builder.spawn((TextBundle {
|
||||
text: Text {
|
||||
sections: vec![
|
||||
TextSection::new("Controls\n", style_achievement_header),
|
||||
TextSection::new(keybindings, style_keybindings)
|
||||
TextSection::new(keybindings, style_keybindings),
|
||||
],
|
||||
justify: JustifyText::Right,
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
},
|
||||
));
|
||||
});
|
||||
},));
|
||||
});
|
||||
|
||||
let style_version = TextStyle {
|
||||
font: font_handle.clone(),
|
||||
|
@ -286,36 +319,36 @@ pub fn setup(
|
|||
..default()
|
||||
};
|
||||
|
||||
commands.spawn((
|
||||
MenuElement,
|
||||
NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Percent(96.0),
|
||||
height: Val::Percent(96.0),
|
||||
left: Val::Percent(2.0),
|
||||
top: Val::Percent(2.0),
|
||||
align_items: AlignItems::End,
|
||||
justify_content: JustifyContent::End,
|
||||
commands
|
||||
.spawn((
|
||||
MenuElement,
|
||||
NodeBundle {
|
||||
style: Style {
|
||||
width: Val::Percent(96.0),
|
||||
height: Val::Percent(96.0),
|
||||
left: Val::Percent(2.0),
|
||||
top: Val::Percent(2.0),
|
||||
align_items: AlignItems::End,
|
||||
justify_content: JustifyContent::End,
|
||||
..default()
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
..default()
|
||||
},
|
||||
visibility: Visibility::Hidden,
|
||||
..default()
|
||||
},
|
||||
)).with_children(|builder| {
|
||||
builder.spawn((
|
||||
TextBundle {
|
||||
))
|
||||
.with_children(|builder| {
|
||||
builder.spawn((TextBundle {
|
||||
text: Text {
|
||||
sections: vec![
|
||||
TextSection::new(format!("{} {}", GAME_NAME,
|
||||
settings.version.as_str()), style_version),
|
||||
],
|
||||
sections: vec![TextSection::new(
|
||||
format!("{} {}", GAME_NAME, settings.version.as_str()),
|
||||
style_version,
|
||||
)],
|
||||
justify: JustifyText::Right,
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
},
|
||||
));
|
||||
});
|
||||
},));
|
||||
});
|
||||
}
|
||||
|
||||
pub fn show_deathscreen(
|
||||
|
@ -359,7 +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,11 +510,12 @@ 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;
|
||||
|
||||
if keyboard_input.just_pressed(settings.key_menu)
|
||||
|| keyboard_input.just_pressed(settings.key_vehicle) && settings.menu_active
|
||||
|| keyboard_input.just_pressed(settings.key_vehicle) && settings.menu_active
|
||||
{
|
||||
ew_game.send(GameEvent::SetMenu(Toggle));
|
||||
}
|
||||
|
@ -480,8 +523,8 @@ pub fn handle_input(
|
|||
return;
|
||||
}
|
||||
if keyboard_input.just_pressed(settings.key_forward)
|
||||
|| keyboard_input.just_pressed(settings.key_mouseup)
|
||||
|| keyboard_input.just_pressed(KeyCode::ArrowUp)
|
||||
|| keyboard_input.just_pressed(settings.key_mouseup)
|
||||
|| keyboard_input.just_pressed(KeyCode::ArrowUp)
|
||||
{
|
||||
menustate.cursor = if menustate.cursor == 0 {
|
||||
last_menu_entry
|
||||
|
@ -492,8 +535,8 @@ pub fn handle_input(
|
|||
ew_updatemenu.send(UpdateMenuEvent);
|
||||
}
|
||||
if keyboard_input.just_pressed(settings.key_back)
|
||||
|| keyboard_input.just_pressed(settings.key_mousedown)
|
||||
|| keyboard_input.just_pressed(KeyCode::ArrowDown)
|
||||
|| keyboard_input.just_pressed(settings.key_mousedown)
|
||||
|| keyboard_input.just_pressed(KeyCode::ArrowDown)
|
||||
{
|
||||
menustate.cursor = if menustate.cursor == last_menu_entry {
|
||||
0
|
||||
|
@ -504,7 +547,7 @@ pub fn handle_input(
|
|||
ew_updatemenu.send(UpdateMenuEvent);
|
||||
}
|
||||
if keyboard_input.just_pressed(settings.key_interact)
|
||||
|| keyboard_input.just_pressed(KeyCode::Enter)
|
||||
|| keyboard_input.just_pressed(KeyCode::Enter)
|
||||
{
|
||||
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Click));
|
||||
match MENUDEF[menustate.cursor].1 {
|
||||
|
@ -512,37 +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);
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,8 +24,8 @@ pub const EARTH_GRAVITY: f32 = 9.81;
|
|||
pub const C: f64 = 299792458.0; // m/s
|
||||
pub const G: f64 = 6.6743015e-11; // Gravitational constant in Nm²/kg²
|
||||
|
||||
pub const SOL_RADIUS: f64 = 696_300_000.0;
|
||||
pub const JUPITER_RADIUS: f64 = 71_492_000.0;
|
||||
pub const SOL_RADIUS: f64 = 696_300_000.0;
|
||||
pub const JUPITER_RADIUS: f64 = 71_492_000.0;
|
||||
pub const JUPITER_RING_RADIUS: f64 = 229_000_000.0;
|
||||
|
||||
pub const SOL_MASS: f64 = 1.9885e30;
|
||||
|
@ -35,7 +35,8 @@ pub const JUPITER_MASS: f64 = 1.8982e27;
|
|||
pub const STARS: &[(f32, f32, f32, f32, f32, f32, &str)] = &include!("data/stars.in");
|
||||
|
||||
pub fn star_color_index_to_rgb(color_index: f32) -> (f32, f32, f32) {
|
||||
let temperature = 4600.0 * ((1.0 / (0.92 * color_index + 1.7)) + (1.0 / (0.92 * color_index + 0.62)));
|
||||
let temperature =
|
||||
4600.0 * ((1.0 / (0.92 * color_index + 1.7)) + (1.0 / (0.92 * color_index + 0.62)));
|
||||
|
||||
let (red, green, blue) = if temperature <= 6600.0 {
|
||||
let red = 255.0;
|
||||
|
@ -55,7 +56,7 @@ pub fn star_color_index_to_rgb(color_index: f32) -> (f32, f32, f32) {
|
|||
|
||||
let clamp = |x: f32| -> f32 { (x / 255.0).max(0.0).min(1.0) };
|
||||
|
||||
return (clamp(red), clamp(green), clamp(blue))
|
||||
return (clamp(red), clamp(green), clamp(blue));
|
||||
}
|
||||
|
||||
fn smooth_edge(start: f32, end: f32, value: f32) -> f32 {
|
||||
|
@ -89,10 +90,19 @@ pub fn ring_density(radius: f32) -> f32 {
|
|||
density = halo_brightness * smooth_edge(halo_inner, halo_outer, radius);
|
||||
} else if radius >= main_inner && radius <= main_outer {
|
||||
let mut metis_notch_effect: f32 = 1.0;
|
||||
if radius > metis_notch_center - metis_notch_width * 0.5 && radius < metis_notch_center + metis_notch_width * 0.5 {
|
||||
metis_notch_effect = 0.8 * (1.0 - smooth_edge(metis_notch_center - metis_notch_width * 0.5, metis_notch_center + metis_notch_width * 0.5, radius));
|
||||
if radius > metis_notch_center - metis_notch_width * 0.5
|
||||
&& radius < metis_notch_center + metis_notch_width * 0.5
|
||||
{
|
||||
metis_notch_effect = 0.8
|
||||
* (1.0
|
||||
- smooth_edge(
|
||||
metis_notch_center - metis_notch_width * 0.5,
|
||||
metis_notch_center + metis_notch_width * 0.5,
|
||||
radius,
|
||||
));
|
||||
}
|
||||
density = main_brightness * metis_notch_effect * smooth_edge(main_inner, main_outer, radius);
|
||||
density =
|
||||
main_brightness * metis_notch_effect * smooth_edge(main_inner, main_outer, radius);
|
||||
} else {
|
||||
if radius >= amalthea_inner && radius <= amalthea_outer {
|
||||
density = almathea_brightness * smooth_edge(amalthea_inner, amalthea_outer, radius);
|
||||
|
@ -131,8 +141,7 @@ pub fn readable_speed(speed: f64) -> String {
|
|||
if abs > C * 0.0005 {
|
||||
let lightyears = abs / C;
|
||||
return format!("{lightyears:.4} c");
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
let kmh = abs * 1.0e-3 * 3600.0;
|
||||
return format!("{kmh:.0} km/h");
|
||||
}
|
||||
|
|
70
src/var.rs
|
@ -12,13 +12,13 @@
|
|||
// "if"-conditions in chats.
|
||||
|
||||
use crate::prelude::*;
|
||||
use bevy::window::WindowMode;
|
||||
use bevy::prelude::*;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use bevy::window::WindowMode;
|
||||
use serde::Deserialize;
|
||||
use toml_edit::DocumentMut;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use toml_edit::DocumentMut;
|
||||
|
||||
pub const SCOPE_SEPARATOR: &str = "$";
|
||||
|
||||
|
@ -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,24 +484,22 @@ pub fn load_prefs() -> Preferences {
|
|||
}
|
||||
};
|
||||
match toml.parse::<DocumentMut>() {
|
||||
Ok(doc) => {
|
||||
match toml_edit::de::from_document::<Preferences>(doc) {
|
||||
Ok(mut pref) => {
|
||||
if let Some(path) = &path {
|
||||
info!("Loaded preference file from {path}");
|
||||
} else {
|
||||
info!("Loaded preferences from internal defaults");
|
||||
}
|
||||
pref.source_file = path;
|
||||
dbg!(&pref);
|
||||
return pref;
|
||||
}
|
||||
Err(error) => {
|
||||
error!("Failed to read preference line: {error}");
|
||||
return Preferences::default();
|
||||
Ok(doc) => match toml_edit::de::from_document::<Preferences>(doc) {
|
||||
Ok(mut pref) => {
|
||||
if let Some(path) = &path {
|
||||
info!("Loaded preference file from {path}");
|
||||
} else {
|
||||
info!("Loaded preferences from internal defaults");
|
||||
}
|
||||
pref.source_file = path;
|
||||
dbg!(&pref);
|
||||
return pref;
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
error!("Failed to read preference line: {error}");
|
||||
return Preferences::default();
|
||||
}
|
||||
},
|
||||
Err(error) => {
|
||||
error!("Failed to open preferences: {error}");
|
||||
return Preferences::default();
|
||||
|
@ -511,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.
|
||||
|
|
|
@ -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()
|
||||
},
|
||||
));
|
||||
},
|
||||
//_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
81
src/world.rs
|
@ -11,13 +11,13 @@
|
|||
// This module populates the world with stars and asteroids.
|
||||
|
||||
use crate::prelude::*;
|
||||
use bevy::prelude::*;
|
||||
use bevy::math::I64Vec3;
|
||||
use bevy::scene::{InstanceId, SceneInstance};
|
||||
use bevy::prelude::*;
|
||||
use bevy::render::mesh::Indices;
|
||||
use bevy::scene::{InstanceId, SceneInstance};
|
||||
use bevy_xpbd_3d::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
use fastrand;
|
||||
use std::collections::HashMap;
|
||||
|
||||
const ASTEROID_UPDATE_INTERVAL: f32 = 0.1; // seconds
|
||||
const ASTEROID_SIZE_FACTOR: f32 = 10.0;
|
||||
|
@ -42,20 +42,28 @@ impl Plugin for WorldPlugin {
|
|||
app.add_plugins(PhysicsPlugins::default());
|
||||
//app.add_plugins(PhysicsDebugPlugin::default());
|
||||
app.insert_resource(Gravity(DVec3::splat(0.0)));
|
||||
app.insert_resource(AsteroidUpdateTimer(
|
||||
Timer::from_seconds(ASTEROID_UPDATE_INTERVAL, TimerMode::Repeating)));
|
||||
app.insert_resource(AsteroidUpdateTimer(Timer::from_seconds(
|
||||
ASTEROID_UPDATE_INTERVAL,
|
||||
TimerMode::Repeating,
|
||||
)));
|
||||
app.insert_resource(ActiveAsteroids(HashMap::new()));
|
||||
app.add_event::<DespawnAsteroidEvent>();
|
||||
app.add_event::<RespawnEvent>();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource)] struct AsteroidUpdateTimer(Timer);
|
||||
#[derive(Resource)] pub struct ActiveAsteroids(pub HashMap<I64Vec3, AsteroidData>);
|
||||
#[derive(Component)] struct Asteroid;
|
||||
#[derive(Component)] pub struct Star;
|
||||
#[derive(Component)] pub struct DespawnOnPlayerDeath;
|
||||
#[derive(Event)] pub struct RespawnEvent;
|
||||
#[derive(Resource)]
|
||||
struct AsteroidUpdateTimer(Timer);
|
||||
#[derive(Resource)]
|
||||
pub struct ActiveAsteroids(pub HashMap<I64Vec3, AsteroidData>);
|
||||
#[derive(Component)]
|
||||
struct Asteroid;
|
||||
#[derive(Component)]
|
||||
pub struct Star;
|
||||
#[derive(Component)]
|
||||
pub struct DespawnOnPlayerDeath;
|
||||
#[derive(Event)]
|
||||
pub struct RespawnEvent;
|
||||
|
||||
pub struct AsteroidData {
|
||||
entity: Entity,
|
||||
|
@ -93,17 +101,15 @@ pub fn setup(
|
|||
let scale_factor = 1e-4 * pos_render.length() as f32; // from experimentation
|
||||
|
||||
let mag = mag.min(6.0);
|
||||
let scale_size = {|mag: f32|
|
||||
scale_factor * (0.230299 * mag * mag - 3.09013 * mag + 15.1782)
|
||||
};
|
||||
let scale_size =
|
||||
{ |mag: f32| scale_factor * (0.230299 * mag * mag - 3.09013 * mag + 15.1782) };
|
||||
let scale = scale_size(mag);
|
||||
|
||||
let scale_color = {|color: f32|
|
||||
1.2 * color * (0.0659663 * mag * mag - 1.09862 * mag + 4.3)
|
||||
};
|
||||
let scale_color =
|
||||
{ |color: f32| 1.2 * color * (0.0659663 * mag * mag - 1.09862 * mag + 4.3) };
|
||||
//let scale = translation.length().powf(0.84);
|
||||
//pos_render.length().powf(0.64)
|
||||
//(radius as f64 * nature::SOL_RADIUS).powf(0.02) as f32 *
|
||||
//pos_render.length().powf(0.64)
|
||||
//(radius as f64 * nature::SOL_RADIUS).powf(0.02) as f32 *
|
||||
|
||||
let star_color_handle = materials.add(StandardMaterial {
|
||||
base_color: Color::rgb(scale_color(r), scale_color(g), scale_color(b)),
|
||||
|
@ -176,8 +182,8 @@ fn spawn_despawn_asteroids(
|
|||
id2pos: Res<game::Id2Pos>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
if !timer.0.tick(time.delta()).just_finished() || q_player.is_empty() {
|
||||
//if q_player.is_empty() {
|
||||
if !timer.0.tick(time.delta()).just_finished() || q_player.is_empty() {
|
||||
//if q_player.is_empty() {
|
||||
return;
|
||||
}
|
||||
let jupiter_pos = if let Some(jupiter_pos) = id2pos.0.get(&"jupiter".to_string()) {
|
||||
|
@ -211,9 +217,12 @@ fn spawn_despawn_asteroids(
|
|||
let z_min = player_cell.z - stepmax;
|
||||
let z_max = player_cell.z + stepmax;
|
||||
for (origin, asteroid) in db.0.iter() {
|
||||
if origin.x < x_min || origin.x > x_max
|
||||
|| origin.y < y_min || origin.y > y_max
|
||||
|| origin.z < z_min || origin.z > z_max
|
||||
if origin.x < x_min
|
||||
|| origin.x > x_max
|
||||
|| origin.y < y_min
|
||||
|| origin.y > y_max
|
||||
|| origin.z < z_min
|
||||
|| origin.z > z_max
|
||||
{
|
||||
if let Ok((pos, sceneinstance)) = q_asteroid.get(asteroid.entity) {
|
||||
if pos.0.distance(player.0) > 1000.0 {
|
||||
|
@ -288,11 +297,12 @@ fn spawn_despawn_asteroids(
|
|||
|
||||
//let max_viewdist = ASTEROID_VIEW_RADIUS / ASTEROID_SPAWN_STEP;
|
||||
let wobble = ASTEROID_SPAWN_STEP * 0.5;
|
||||
let pos = jupiter_pos + DVec3::new(
|
||||
origin.x as f64 * ASTEROID_SPAWN_STEP + wobble * rand_x * 2.0 - 1.0,
|
||||
origin.y as f64 * ASTEROID_SPAWN_STEP + wobble * rand_y * 2.0 - 1.0,
|
||||
origin.z as f64 * ASTEROID_SPAWN_STEP + wobble * rand_z * 2.0 - 1.0,
|
||||
);
|
||||
let pos = jupiter_pos
|
||||
+ DVec3::new(
|
||||
origin.x as f64 * ASTEROID_SPAWN_STEP + wobble * rand_x * 2.0 - 1.0,
|
||||
origin.y as f64 * ASTEROID_SPAWN_STEP + wobble * rand_y * 2.0 - 1.0,
|
||||
origin.z as f64 * ASTEROID_SPAWN_STEP + wobble * rand_z * 2.0 - 1.0,
|
||||
);
|
||||
|
||||
// Spawn
|
||||
let mut entity_commands = commands.spawn((
|
||||
|
@ -322,10 +332,13 @@ fn spawn_despawn_asteroids(
|
|||
..default()
|
||||
});
|
||||
load_asset(model, &mut entity_commands, &*asset_server);
|
||||
db.0.insert(origin, AsteroidData {
|
||||
entity: entity_commands.id(),
|
||||
//viewdistance: 99999999.0,
|
||||
});
|
||||
db.0.insert(
|
||||
origin,
|
||||
AsteroidData {
|
||||
entity: entity_commands.id(),
|
||||
//viewdistance: 99999999.0,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>,
|
||||
) {
|
||||
|
|