Compare commits

..

No commits in common. "f75da62ef9f2d5c620e352f9a4a842764ac388f5" and "c59f8242c92abd2eb9643185825c6c2520eeb394" have entirely different histories.

92 changed files with 1648 additions and 4410 deletions

View file

@ -1,40 +1,3 @@
# git
- Implement cruise control
- New logo: a white O, diagonal ring around it, and neon pink glow
- New dashboard icons: rotation stabiliser, radioactivity warning
# v0.9.0
- Implement game menu
- Implement achievements
- Implement death screen, showing cause of death and death poem
- Add better textures for Venus, Io, Europa, Ganymede, Callisto
# v0.8.5
- Implement flashlight
- Implement power drain (for flashlight only, for now)
- Redesign HUD with fallout4esque bars and car dashboard warning lights
- Add a more balanced cruising vehicle near starting position
- Add much better Jupiter texture
- Fix collision sounds
# v0.8.4
- Fix star positions in map when zooming far out and moving around
# v0.8.3
- Implement "outfly.toml" configuration file
- Implement dynamic planet/moon locations based on real time
- Change hud color to neon red/pink
- Add textures for the remaining 7 planets
- Add point-of-interest markers for moons/planets
- Add work-in-progress settlement in hollow asteroid
- Fix jitter glitch in map mode when targeting far away objects
- Remove point lights of bus stations for performance reasons
# v0.8.2 # v0.8.2
- Add speedometer and bigger reticule - Add speedometer and bigger reticule

178
Cargo.lock generated
View file

@ -291,7 +291,8 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]] [[package]]
name = "bevy" name = "bevy"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65b9eadaacf8fe971331bc3f250f35c18bc9dace3f96b483062f38ac07e3a1b4"
dependencies = [ dependencies = [
"bevy_dylib", "bevy_dylib",
"bevy_internal", "bevy_internal",
@ -300,7 +301,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_a11y" name = "bevy_a11y"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd8ef2795f7f5c816a4eda04834083eb5a92e8fef603bc21d2091c6e3b63621a"
dependencies = [ dependencies = [
"accesskit", "accesskit",
"bevy_app", "bevy_app",
@ -311,14 +313,15 @@ dependencies = [
[[package]] [[package]]
name = "bevy_animation" name = "bevy_animation"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e553d68bc937586010ed2194ac66b751bc6238cf622b3ed5a86f4e1581e94509"
dependencies = [ dependencies = [
"bevy_app", "bevy_app",
"bevy_asset", "bevy_asset",
"bevy_core", "bevy_core",
"bevy_ecs", "bevy_ecs",
"bevy_hierarchy", "bevy_hierarchy",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)", "bevy_math",
"bevy_reflect", "bevy_reflect",
"bevy_render", "bevy_render",
"bevy_time", "bevy_time",
@ -329,7 +332,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_app" name = "bevy_app"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab348a32e46d21c5d61794294a92d415a770d26c7ba8951830b127b40b53ccc4"
dependencies = [ dependencies = [
"bevy_derive", "bevy_derive",
"bevy_ecs", "bevy_ecs",
@ -344,7 +348,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_asset" name = "bevy_asset"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50028e0d4f28a9f6aab48f61b688ba2793141188f88cdc9aa6c2bca2cc02ad35"
dependencies = [ dependencies = [
"async-broadcast", "async-broadcast",
"async-fs", "async-fs",
@ -376,7 +381,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_asset_macros" name = "bevy_asset_macros"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6617475908368418d815360148fdbb82f879dc255a70d2d7baa3766f0cd4bfd7"
dependencies = [ dependencies = [
"bevy_macro_utils", "bevy_macro_utils",
"proc-macro2", "proc-macro2",
@ -387,13 +393,14 @@ dependencies = [
[[package]] [[package]]
name = "bevy_audio" name = "bevy_audio"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0f12495e230cd5cf59c6051cdd820c97d7fe4f0597d4d9c3240c62e9c65b485"
dependencies = [ dependencies = [
"bevy_app", "bevy_app",
"bevy_asset", "bevy_asset",
"bevy_derive", "bevy_derive",
"bevy_ecs", "bevy_ecs",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)", "bevy_math",
"bevy_reflect", "bevy_reflect",
"bevy_transform", "bevy_transform",
"bevy_utils", "bevy_utils",
@ -403,11 +410,12 @@ dependencies = [
[[package]] [[package]]
name = "bevy_core" name = "bevy_core"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12b0042f241ba7cd61487aadd8addfb56f7eeb662d713ac1577026704508fc6c"
dependencies = [ dependencies = [
"bevy_app", "bevy_app",
"bevy_ecs", "bevy_ecs",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)", "bevy_math",
"bevy_reflect", "bevy_reflect",
"bevy_tasks", "bevy_tasks",
"bevy_utils", "bevy_utils",
@ -417,7 +425,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_core_pipeline" name = "bevy_core_pipeline"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48b7a471cb8ba665f12f7a167faa5566c11386f5bfc77d2e10bfde22b179f7b3"
dependencies = [ dependencies = [
"bevy_app", "bevy_app",
"bevy_asset", "bevy_asset",
@ -425,7 +434,7 @@ dependencies = [
"bevy_derive", "bevy_derive",
"bevy_ecs", "bevy_ecs",
"bevy_log", "bevy_log",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)", "bevy_math",
"bevy_reflect", "bevy_reflect",
"bevy_render", "bevy_render",
"bevy_transform", "bevy_transform",
@ -438,7 +447,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_derive" name = "bevy_derive"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0e01f8343f391e2d6a63b368b82fb5b252ed43c8713fc87f9a8f2d59407dd00"
dependencies = [ dependencies = [
"bevy_macro_utils", "bevy_macro_utils",
"quote", "quote",
@ -448,7 +458,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_diagnostic" name = "bevy_diagnostic"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1401cdccec7e49378d013dfb0ff62c251f85b3be19dcdf04cfd827f793d1ee9"
dependencies = [ dependencies = [
"bevy_app", "bevy_app",
"bevy_core", "bevy_core",
@ -463,7 +474,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_dylib" name = "bevy_dylib"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "922826e3b8f37c19836b49e18ceca662260cce87ab8faa4db6df8433903660cc"
dependencies = [ dependencies = [
"bevy_internal", "bevy_internal",
] ]
@ -471,7 +483,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_ecs" name = "bevy_ecs"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98e612a8e7962ead849e370f3a7e972b88df879ced05cd9dad6a0286d14650cf"
dependencies = [ dependencies = [
"async-channel", "async-channel",
"bevy_ecs_macros", "bevy_ecs_macros",
@ -490,7 +503,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_ecs_macros" name = "bevy_ecs_macros"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "807b5106c3410e58f4f523b55ea3c071e2a09e31e9510f3c22021c6a04732b5b"
dependencies = [ dependencies = [
"bevy_macro_utils", "bevy_macro_utils",
"proc-macro2", "proc-macro2",
@ -501,7 +515,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_embedded_assets" name = "bevy_embedded_assets"
version = "0.10.2" version = "0.10.2"
source = "git+https://codeberg.org/outfly/bevy_embedded_assets.git?rev=bb925e7e5373c742c01e6e7aff04e92fdc07c095#bb925e7e5373c742c01e6e7aff04e92fdc07c095" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a4b0bfcdcbd0c59829415ae0756757d50dfdb0c8f324087b4a2daabb3971fbd"
dependencies = [ dependencies = [
"bevy", "bevy",
"cargo-emit", "cargo-emit",
@ -512,7 +527,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_encase_derive" name = "bevy_encase_derive"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "887087a5e522d9f20733a84dd7e6e9ca04cd8fdfac659220ed87d675eebc83a7"
dependencies = [ dependencies = [
"bevy_macro_utils", "bevy_macro_utils",
"encase_derive_impl", "encase_derive_impl",
@ -521,7 +537,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_gizmos" name = "bevy_gizmos"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "054df3550a9d423a961de65b459946ff23304f97f25af8a62c23f4259db8506d"
dependencies = [ dependencies = [
"bevy_app", "bevy_app",
"bevy_asset", "bevy_asset",
@ -530,7 +547,7 @@ dependencies = [
"bevy_ecs", "bevy_ecs",
"bevy_gizmos_macros", "bevy_gizmos_macros",
"bevy_log", "bevy_log",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)", "bevy_math",
"bevy_pbr", "bevy_pbr",
"bevy_reflect", "bevy_reflect",
"bevy_render", "bevy_render",
@ -542,7 +559,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_gizmos_macros" name = "bevy_gizmos_macros"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abdcaf74d8cd34aa5c3293527e7a012826840886ad3496c1b963ed8b66b1619f"
dependencies = [ dependencies = [
"bevy_macro_utils", "bevy_macro_utils",
"proc-macro2", "proc-macro2",
@ -553,7 +571,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_gltf" name = "bevy_gltf"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21ecf404295055deb7fe037495891bc135ca10d46bc5b6c55f9ab7b7ebc61d31"
dependencies = [ dependencies = [
"base64", "base64",
"bevy_animation", "bevy_animation",
@ -564,7 +583,7 @@ dependencies = [
"bevy_ecs", "bevy_ecs",
"bevy_hierarchy", "bevy_hierarchy",
"bevy_log", "bevy_log",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)", "bevy_math",
"bevy_pbr", "bevy_pbr",
"bevy_reflect", "bevy_reflect",
"bevy_render", "bevy_render",
@ -582,7 +601,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_hierarchy" name = "bevy_hierarchy"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbb3dfad24866a6713dafa3065a91c5cf5e355f6e1b191c25d704ae54185246c"
dependencies = [ dependencies = [
"bevy_app", "bevy_app",
"bevy_core", "bevy_core",
@ -595,11 +615,12 @@ dependencies = [
[[package]] [[package]]
name = "bevy_input" name = "bevy_input"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47f2b2b3df168c6ef661d25e09abf5bd4fecaacd400f27e5db650df1c3fa3a3b"
dependencies = [ dependencies = [
"bevy_app", "bevy_app",
"bevy_ecs", "bevy_ecs",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)", "bevy_math",
"bevy_reflect", "bevy_reflect",
"bevy_utils", "bevy_utils",
"smol_str", "smol_str",
@ -609,7 +630,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_internal" name = "bevy_internal"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f58ec0ce77603df9474cde61f429126bfe06eb79094440e9141afb4217751c79"
dependencies = [ dependencies = [
"bevy_a11y", "bevy_a11y",
"bevy_animation", "bevy_animation",
@ -626,7 +648,7 @@ dependencies = [
"bevy_hierarchy", "bevy_hierarchy",
"bevy_input", "bevy_input",
"bevy_log", "bevy_log",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)", "bevy_math",
"bevy_pbr", "bevy_pbr",
"bevy_ptr", "bevy_ptr",
"bevy_reflect", "bevy_reflect",
@ -646,7 +668,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_log" name = "bevy_log"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5eea6c527fd828b7fef8d0f518167f27f405b904a16f227b644687d3f46a809"
dependencies = [ dependencies = [
"android_log-sys", "android_log-sys",
"bevy_app", "bevy_app",
@ -661,7 +684,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_macro_utils" name = "bevy_macro_utils"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb270c98a96243b29465139ed10bda2f675d00a11904f6588a5f7fc4774119c7"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -675,14 +699,6 @@ name = "bevy_math"
version = "0.13.2" version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f06daa26ffb82d90ba772256c0ba286f6c305c392f6976c9822717974805837c" checksum = "f06daa26ffb82d90ba772256c0ba286f6c305c392f6976c9822717974805837c"
dependencies = [
"glam",
]
[[package]]
name = "bevy_math"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
dependencies = [ dependencies = [
"glam", "glam",
"serde", "serde",
@ -691,7 +707,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_mikktspace" name = "bevy_mikktspace"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0d7ef7f2a826d0b19f059035831ce00a5e930435cc53c61e045773d0483f67a"
dependencies = [ dependencies = [
"glam", "glam",
] ]
@ -699,14 +716,15 @@ dependencies = [
[[package]] [[package]]
name = "bevy_pbr" name = "bevy_pbr"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b29c80269fa6db55c9e33701edd3ecb73d8866ca8cb814d49a9d3fb72531b6"
dependencies = [ dependencies = [
"bevy_app", "bevy_app",
"bevy_asset", "bevy_asset",
"bevy_core_pipeline", "bevy_core_pipeline",
"bevy_derive", "bevy_derive",
"bevy_ecs", "bevy_ecs",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)", "bevy_math",
"bevy_reflect", "bevy_reflect",
"bevy_render", "bevy_render",
"bevy_transform", "bevy_transform",
@ -723,14 +741,16 @@ dependencies = [
[[package]] [[package]]
name = "bevy_ptr" name = "bevy_ptr"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8050e2869fe341db6874203b5a01ff12673807a2c7c80cb829f6c7bea6997268"
[[package]] [[package]]
name = "bevy_reflect" name = "bevy_reflect"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccbd7de21d586457a340a0962ad0747dc5098ff925eb6b27a918c4bdd8252f7b"
dependencies = [ dependencies = [
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)", "bevy_math",
"bevy_ptr", "bevy_ptr",
"bevy_reflect_derive", "bevy_reflect_derive",
"bevy_utils", "bevy_utils",
@ -745,7 +765,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_reflect_derive" name = "bevy_reflect_derive"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ce33051bd49036d4a5a62aa3f2068672ec55f3ebe92aa0d003a341f15cc37ac"
dependencies = [ dependencies = [
"bevy_macro_utils", "bevy_macro_utils",
"proc-macro2", "proc-macro2",
@ -757,7 +778,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_render" name = "bevy_render"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88b2c4b644c739c0b474b6f8f7b0bc68ac13d83b59688781e9a7753c52780177"
dependencies = [ dependencies = [
"async-channel", "async-channel",
"bevy_app", "bevy_app",
@ -768,7 +790,7 @@ dependencies = [
"bevy_encase_derive", "bevy_encase_derive",
"bevy_hierarchy", "bevy_hierarchy",
"bevy_log", "bevy_log",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)", "bevy_math",
"bevy_mikktspace", "bevy_mikktspace",
"bevy_reflect", "bevy_reflect",
"bevy_render_macros", "bevy_render_macros",
@ -801,7 +823,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_render_macros" name = "bevy_render_macros"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "720b88406e786e378829b7d43c1ffb5300186912b99904d0d4d8ec6698a4f210"
dependencies = [ dependencies = [
"bevy_macro_utils", "bevy_macro_utils",
"proc-macro2", "proc-macro2",
@ -812,7 +835,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_scene" name = "bevy_scene"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3d2caa1bfe7542dbe2c62e1bcc10791ba181fb744d2fe6711d1d373354da7c"
dependencies = [ dependencies = [
"bevy_app", "bevy_app",
"bevy_asset", "bevy_asset",
@ -831,7 +855,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_sprite" name = "bevy_sprite"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cad1b555161f50e5d62b7fdf7ebeef1b24338aae7a88e51985da9553cd60ddf"
dependencies = [ dependencies = [
"bevy_app", "bevy_app",
"bevy_asset", "bevy_asset",
@ -839,7 +864,7 @@ dependencies = [
"bevy_derive", "bevy_derive",
"bevy_ecs", "bevy_ecs",
"bevy_log", "bevy_log",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)", "bevy_math",
"bevy_reflect", "bevy_reflect",
"bevy_render", "bevy_render",
"bevy_transform", "bevy_transform",
@ -856,7 +881,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_tasks" name = "bevy_tasks"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f07fcc4969b357de143509925b39c9a2c56eaa8750828d97f319ca9ed41897cb"
dependencies = [ dependencies = [
"async-channel", "async-channel",
"async-executor", "async-executor",
@ -869,13 +895,14 @@ dependencies = [
[[package]] [[package]]
name = "bevy_text" name = "bevy_text"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4e8456ae0bea7d6b7621e42c1c12bf66c0891381e62c948ab23920673ce611c"
dependencies = [ dependencies = [
"ab_glyph", "ab_glyph",
"bevy_app", "bevy_app",
"bevy_asset", "bevy_asset",
"bevy_ecs", "bevy_ecs",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)", "bevy_math",
"bevy_reflect", "bevy_reflect",
"bevy_render", "bevy_render",
"bevy_sprite", "bevy_sprite",
@ -890,7 +917,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_time" name = "bevy_time"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38ea5ae9fe7f56f555dbb05a88d34931907873e3f0c7dc426591839eef72fe3e"
dependencies = [ dependencies = [
"bevy_app", "bevy_app",
"bevy_ecs", "bevy_ecs",
@ -903,12 +931,13 @@ dependencies = [
[[package]] [[package]]
name = "bevy_transform" name = "bevy_transform"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0d51a1f332cc00939d2f19ed6b909e5ed7037e39c7e25cc86930d79d432163e"
dependencies = [ dependencies = [
"bevy_app", "bevy_app",
"bevy_ecs", "bevy_ecs",
"bevy_hierarchy", "bevy_hierarchy",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)", "bevy_math",
"bevy_reflect", "bevy_reflect",
"thiserror", "thiserror",
] ]
@ -916,7 +945,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_ui" name = "bevy_ui"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6bbc30be39cfbfa3a073b541d22aea43ab14452dea12d7411ce201df17ff7b1"
dependencies = [ dependencies = [
"bevy_a11y", "bevy_a11y",
"bevy_app", "bevy_app",
@ -927,7 +957,7 @@ dependencies = [
"bevy_hierarchy", "bevy_hierarchy",
"bevy_input", "bevy_input",
"bevy_log", "bevy_log",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)", "bevy_math",
"bevy_reflect", "bevy_reflect",
"bevy_render", "bevy_render",
"bevy_sprite", "bevy_sprite",
@ -943,7 +973,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_utils" name = "bevy_utils"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9f845a985c00e0ee8dc2d8af3f417be925fb52aad4bda5b96e2e58a2b4d2eb"
dependencies = [ dependencies = [
"ahash", "ahash",
"bevy_utils_proc_macros", "bevy_utils_proc_macros",
@ -961,7 +992,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_utils_proc_macros" name = "bevy_utils_proc_macros"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef158627f30503d5c18c20c60b444829f698d343516eeaf6eeee078c9a45163"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -971,13 +1003,14 @@ dependencies = [
[[package]] [[package]]
name = "bevy_window" name = "bevy_window"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "976202d2ed838176595b550ac654b15ae236e0178a6f19a94ca6d58f2a96ca60"
dependencies = [ dependencies = [
"bevy_a11y", "bevy_a11y",
"bevy_app", "bevy_app",
"bevy_ecs", "bevy_ecs",
"bevy_input", "bevy_input",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)", "bevy_math",
"bevy_reflect", "bevy_reflect",
"bevy_utils", "bevy_utils",
"raw-window-handle", "raw-window-handle",
@ -987,7 +1020,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_winit" name = "bevy_winit"
version = "0.13.2" version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa66539aa93d8522b146bf82de429714ea6370a6061fc1f1ff7bcacd4e64c6c4"
dependencies = [ dependencies = [
"accesskit_winit", "accesskit_winit",
"approx", "approx",
@ -997,7 +1031,7 @@ dependencies = [
"bevy_ecs", "bevy_ecs",
"bevy_hierarchy", "bevy_hierarchy",
"bevy_input", "bevy_input",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)", "bevy_math",
"bevy_tasks", "bevy_tasks",
"bevy_utils", "bevy_utils",
"bevy_window", "bevy_window",
@ -1011,10 +1045,11 @@ dependencies = [
[[package]] [[package]]
name = "bevy_xpbd_3d" name = "bevy_xpbd_3d"
version = "0.4.2" version = "0.4.2"
source = "git+https://codeberg.org/outfly/bevy_xpbd.git?rev=99bca3f6a25b8c4e6ec6509e9e9b0e7bed565912#99bca3f6a25b8c4e6ec6509e9e9b0e7bed565912" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0425ea7361b9b27c2a382e0663deb42f41147eee60fb2b3d5fa7e42d363ea848"
dependencies = [ dependencies = [
"bevy", "bevy",
"bevy_math 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)", "bevy_math",
"bevy_xpbd_derive", "bevy_xpbd_derive",
"derive_more", "derive_more",
"fxhash", "fxhash",
@ -1028,7 +1063,8 @@ dependencies = [
[[package]] [[package]]
name = "bevy_xpbd_derive" name = "bevy_xpbd_derive"
version = "0.1.0" version = "0.1.0"
source = "git+https://codeberg.org/outfly/bevy_xpbd.git?rev=99bca3f6a25b8c4e6ec6509e9e9b0e7bed565912#99bca3f6a25b8c4e6ec6509e9e9b0e7bed565912" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e1ef1d5e328abe1b76df974245f78e17fd17867583883d5e77444c6a8223a64"
dependencies = [ dependencies = [
"quote", "quote",
"syn 2.0.52", "syn 2.0.52",
@ -2767,7 +2803,7 @@ dependencies = [
[[package]] [[package]]
name = "outfly" name = "outfly"
version = "0.9.0" version = "0.8.2"
dependencies = [ dependencies = [
"bevy", "bevy",
"bevy_embedded_assets", "bevy_embedded_assets",

View file

@ -10,7 +10,7 @@
[package] [package]
name = "outfly" name = "outfly"
version = "0.9.0" version = "0.8.2"
edition = "2021" edition = "2021"
homepage = "https://codeberg.org/hut/outfly" homepage = "https://codeberg.org/hut/outfly"
repository = "https://codeberg.org/hut/outfly" repository = "https://codeberg.org/hut/outfly"
@ -23,6 +23,15 @@ rust-version = "1.76.0"
# For parsing the game definition file, src/data/defs.txt # For parsing the game definition file, src/data/defs.txt
regex = "1" regex = "1"
# The bevy game engine, the basis for this game
bevy = { version = "0.13.2", 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"]}
# For physics and collision handling
bevy_xpbd_3d = { version = "0.4.2", default-features = false, features = ["3d", "f64", "parry-f64", "parallel", "async-collider"] }
# For embedding assets into the binary, creating a self-sufficient executable
bevy_embedded_assets = { version = "0.10.2", optional = true }
# For seeded pseudo-random procedural generation of asteroids # For seeded pseudo-random procedural generation of asteroids
fastrand = "2.0" fastrand = "2.0"
@ -33,42 +42,14 @@ serde_yaml = "0.9"
# For reading/writing the player's configuration file. # For reading/writing the player's configuration file.
toml_edit = { version = "0.22", features = ["serde"] } toml_edit = { version = "0.22", features = ["serde"] }
[dependencies.bevy]
# The bevy game engine, the basis for this game
# 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"
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"]
[dependencies.bevy_embedded_assets]
# For embedding assets into the binary, creating a self-sufficient executable
# 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"
optional = true
[dependencies.bevy_xpbd_3d]
# For physics and collision handling
# 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"
default-features = false
features = ["3d", "f64", "parry-f64", "parallel", "async-collider"]
[build-dependencies] [build-dependencies]
# NOTE: even though we use embed-resource for windows only, we can't move it into
# a [target[...]build-dependencies] block because in case of cross-compiling, the
# build.rs will be compiled for a different, non-windows target than the main executable.
embed-resource = "1.6.3" # embedding of .exe metadata embed-resource = "1.6.3" # embedding of .exe metadata
[features] [features]
default = ["x11", "embed_assets"] default = ["x11", "embed_assets"]
mute_music = []
dev_mode = [] dev_mode = []
dev = ["dev_mode", "bevy/dynamic_linking", "bevy/file_watcher"] dev = ["dev_mode", "mute_music", "bevy/dynamic_linking", "bevy/file_watcher"]
release_linux = ["x11", "wayland", "embed_assets"] release_linux = ["x11", "wayland", "embed_assets"]
release_windows = ["embed_assets"] release_windows = ["embed_assets"]
wasm = ["bevy/webgl2"] wasm = ["bevy/webgl2"]

View file

@ -1,5 +1,3 @@
This directory contains scripts and data files for building outfly for various operating systems.
# Dev Features # Dev Features
For development, it's recommended to use `--features dev`: For development, it's recommended to use `--features dev`:
@ -8,7 +6,7 @@ For development, it's recommended to use `--features dev`:
cargo [run|build] --features dev cargo [run|build] --features dev
``` ```
This enables the following, but ONLY if you run it with `cargo run`: This enables the following:
- Mutes music by default (you can still unmute it) - Mutes music by default (you can still unmute it)
- Enables "dev mode", which changes the game slightly: - Enables "dev mode", which changes the game slightly:
@ -20,7 +18,7 @@ This enables the following, but ONLY if you run it with `cargo run`:
# pack.sh # pack.sh
The [pack.sh](build/pack.sh) script is used by the developer team to compile and pack release binaries into official packages. The [pack.sh](src/build/pack.sh) script is used by the developer team to compile and pack release binaries into official packages.
It could serve as a starting point for package maintainers or tinkerers. It could serve as a starting point for package maintainers or tinkerers.
@ -90,7 +88,7 @@ python -m http.server -d wasm
## Building release versions optimized for packaging ## 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/hut/outfly/src/branch/main/src/build/pack.sh))
``` ```
cargo build --release --no-default-features --features release_[linux|windows] [--target=$YOUR_TARGET] cargo build --release --no-default-features --features release_[linux|windows] [--target=$YOUR_TARGET]

View file

@ -3,26 +3,7 @@
- Source code: GPL Version 3.0 - Source code: GPL Version 3.0
- https://codeberg.org/hut/outfly - https://codeberg.org/hut/outfly
- 3D models: Original art, placed under the Creative Commons CC0 License - 3D models: Original art, placed under the Creative Commons CC0 License
- Photographs of celestial bodies: - Photographs of celestial bodies: By NASA, public domain
- Mercury: [By Solar System Scope, CC BY 4.0](https://www.solarsystemscope.com/textures/)
- Venus: [By Björn Jónsson, free to use, with attribution](https://bjj.mmedia.is/data/venus/venus.html)
- To reduce the visibility of the ultraviolet features in Björn's original image, I applied GIMP's "Colors"→"Hue-Saturation" with Lightness=100 and Saturation=-20.
- Exported as 80% quality JPEG
- Earth: A simple addition of
- [base image, by NASA, public domain](https://visibleearth.nasa.gov/images/74318/april-blue-marble-next-generation-w-topography)
- [clouds, by NASA, public domain](https://visibleearth.nasa.gov/images/57747/blue-marble-clouds)
- Mars: [By Solar System Scope, CC BY 4.0](https://www.solarsystemscope.com/textures/)
- Jupiter: [By Björn Jónsson, CC BY 3.0](https://www.planetary.org/space-images/merged-cassini-and-juno)
- Downscaled by 2x with LoHalo interpolation
- Adjusted contrast with GIMP's "Colors"→"Curves", by pulling x=128/y=128 down to y=96.
- Exported as 80% quality JPEG
- Io: [By Björn Jónsson, free to use, with attribution](https://bjj.mmedia.is/data/io/io.html)
- Europa: [By Björn Jónsson, free to use, with attribution](https://www.planetary.org/articles/0218-mapping-europa)
- Ganymede: [By Björn Jónsson, free to use, with attribution](https://bjj.mmedia.is/data/ganymede/index.html)
- Callisto: [By Björn Jónsson, free to use, with attribution](https://bjj.mmedia.is/data/callisto/index.html)
- Saturn: [By Solar System Scope, CC BY 4.0](https://www.solarsystemscope.com/textures/)
- Uranus: [By Askaniy, CC BY-SA 3.0](https://www.deviantart.com/askaniy/art/Uranus-Texture-Map-763551816)
- Neptune: [By Solar System Scope, CC BY 4.0](https://www.solarsystemscope.com/textures/)
- Icon: Creative Commons CC0 License - Icon: Creative Commons CC0 License
- Original sound files: - Original sound files:
- wakeup.ogg: Creative Commons CC0 License - wakeup.ogg: Creative Commons CC0 License
@ -40,7 +21,6 @@
- https://pixabay.com/sound-effects/electric-fan-motor-blades-removed-13169 - https://pixabay.com/sound-effects/electric-fan-motor-blades-removed-13169
- https://pixabay.com/sound-effects/whoosh-blow-flutter-shortwav-14678/ - https://pixabay.com/sound-effects/whoosh-blow-flutter-shortwav-14678/
- https://pixabay.com/sound-effects/dslr-camera-sounds-26117/ - 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: [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) - 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: - Font Yupiter-Regular.ttf is placed under the SIL OPEN FONT LICENSE Version 1.1 and is based on:

View file

@ -1,4 +1,14 @@
![OutFly Screenshot](doc/images/screenshot3.jpg) ```
▄████████▄ + ███ + ▄█████████ ███ +
███▀ ▀███ + + ███ ███▀ + ███ + +
███ + ███ ███ ███ █████████ ███ ███ ███ ███
███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
+ + + ███
+ ▀████████████████████████████████████████████████████▀
```
[Features](#features) • [Controls](#controls) • [Running OutFly](#running-outfly) • [Troubleshooting](#troubleshooting) [Features](#features) • [Controls](#controls) • [Running OutFly](#running-outfly) • [Troubleshooting](#troubleshooting)
@ -18,6 +28,8 @@ This game aims to respect the player as much as possible. It doesn't waste your
Source code: https://codeberg.org/hut/outfly Source code: https://codeberg.org/hut/outfly
![screenshot](doc/images/screenshot3.jpg)
# Features # Features
- A beautiful, serene atmosphere with gorgeous views - A beautiful, serene atmosphere with gorgeous views
@ -29,30 +41,32 @@ Source code: https://codeberg.org/hut/outfly
# Controls # Controls
You can view these any time in game through the game menu (press Escape.) - F1: Show key bindings
- Space: Slow down (or match velocity)
- Space: Slow down, match velocity - AWSD/Shift/Ctrl: Accelerate
- E: Interact - R: Rotate (hold & move mouse)
- F: Flashlight - E: Interact: Talk to people, enter vehicles
- Q: Exit vehicle - Q: Exit vehicle
- M: Map - JKULIO: Mouseless camera rotation
- C: Camera - Augmented Reality: (toggle with Tab)
- 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 - Left click: Target objects
- Right click: Zoom - Right click: Zoom
- Cheats: - Settings
- G: Toggle cheats + invulnerability - Tab: Toggle HUD/AR
- V/B: Impossible acceleration - M: Toggle map
- Shift+V/B: Extreme acceleration - F: Toggle 3rd person view
- Y: Toggle rotation stabilizer
- F2: Toggle shadows
- F3: Toggle sound effects
- F4: Toggle music
- F7: Restart game
- F11: Toggle fullscreen
- Cheats
- G: Toggle god mode / cheats
- V/B: Impossible acceleration forward/backward
- Shift+V/B: Same as V/B, but a thousand times faster
- C: Impossibly instant stopping
- X: Teleport to target - X: Teleport to target
- Z: Stop
# Running OutFly # Running OutFly
## System Requirements ## System Requirements
@ -88,10 +102,6 @@ yay -S outfly-git
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 instructions in [HACKING.md](https://codeberg.org/hut/outfly/src/branch/main/HACKING.md). Support for Android/iOS is planned for the future.
# Troubleshooting # Troubleshooting
## I'm stuck inside another object
Try turning on God Mode and using one of the speed cheats, that should get you out.
## My GPU doesn't support Vulkan! ## My GPU doesn't support Vulkan!
Try running outfly with the command-line option "--gl", with one of these commands: Try running outfly with the command-line option "--gl", with one of these commands:

View file

@ -91,14 +91,12 @@ We are not quite there yet, but this is what I'm aiming for:
- The player can survive higher g-forces than a contemporary human, though this can be explained through better equipment, drugs, and/or genetic engineering. - The player can survive higher g-forces than a contemporary human, though this can be explained through better equipment, drugs, and/or genetic engineering.
- The position of the planets/moons is the one of the present real time of the player, even though the game takes place hundreds of years in the future. I could simulate the future positions, but I think it's an extra layer of awesomeness if the positions of the celestial objects in the game mirroring the ones of reality. And nobody would ever notice the discrepancy. - The position of the planets/moons is the one of the present real time of the player, even though the game takes place hundreds of years in the future. I could simulate the future positions, but I think it's an extra layer of awesomeness if the positions of the celestial objects in the game mirroring the ones of reality. And nobody would ever notice the discrepancy.
- P.S. First I have to find a way to actually calculate the current real positions of the moons...
# Game systems # Game systems
A variety of relatively simple game systems should interact with each other to create emergent gameplay and interesting game mechanics. A variety of relatively simple game systems should interact with each other to create emergent gameplay and interesting game mechanics.
- Free movement in space - Free movement in space
- Everything orbits around Planets/Sun
- Collision with other actors - Collision with other actors
- Augmented Reality overlay - Augmented Reality overlay
- Targeting objects - Targeting objects
@ -109,13 +107,6 @@ A variety of relatively simple game systems should interact with each other to c
- G-forces and equipment/organ damage - G-forces and equipment/organ damage
- Death, and survival - Death, and survival
# Quest Ideas
- Pizza Delivery
- Charting the major moonlets
- Killing somebody (perhaps one of the cultists)
- Some overarching quest for which you need the support/friendship of everybody else
# Challenges # Challenges
- How to tell a deep story with permadeath without getting repetitive? - How to tell a deep story with permadeath without getting repetitive?
@ -190,11 +181,6 @@ Items:
- Pizzeria Clippy - Pizzeria Clippy
- Bus Station Clippys - Bus Station Clippys
## Other life forms
- Space dogs + cats. Definitely space dogs + cats.
- Life forms that have adapted to the vacuum of space, perhaps some gigantic space-radiation-eating monsters with light-eating leaf-like appendages that move slowly
## Places ## Places
![Game Map](doc/images/map.svg) ![Game Map](doc/images/map.svg)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -27,7 +27,7 @@ fn ring_density(radius: f32) -> f32 {
let thebe_inner: f32 = 129.0; let thebe_inner: f32 = 129.0;
let thebe_outer: f32 = 229.0; let thebe_outer: f32 = 229.0;
let metis_notch_center: f32 = 128.0; let metis_notch_center: f32 = 128.0;
let metis_notch_width: f32 = 0.1; let metis_notch_width: f32 = 0.6;
let halo_brightness: f32 = 0.75; let halo_brightness: f32 = 0.75;
let main_brightness: f32 = 1.0; let main_brightness: f32 = 1.0;
@ -41,7 +41,7 @@ fn ring_density(radius: f32) -> f32 {
} else if (radius >= main_inner && radius <= main_outer) { } else if (radius >= main_inner && radius <= main_outer) {
var metis_notch_effect: f32 = 1.0; var 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) { 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)); metis_notch_effect = 0.5 * (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 { } else {

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 B

BIN
assets/sprites/reticule.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 430 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 770 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 MiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 360 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 928 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 MiB

After

Width:  |  Height:  |  Size: 403 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

View file

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View file

@ -10,14 +10,19 @@
// //
// This module manages the internal states of individual characters, // This module manages the internal states of individual characters,
// such as their resources, the damage they receive, and interactions // such as their resources, the damage they receive, and interactions
// between characters and with vehicles. // between characters and with vehicles. It also handles cheats.
// //
// This module should never handle any visual aspects directly. // This module should never handle any visual aspects directly.
use bevy::prelude::*; use bevy::prelude::*;
use bevy_xpbd_3d::prelude::*; use bevy_xpbd_3d::prelude::*;
use crate::prelude::*; use bevy_xpbd_3d::plugins::sync;
use bevy::scene::SceneInstance;
use bevy::math::DVec3;
use crate::{actor, audio, camera, chat, commands, effects, hud, nature, var, world};
use std::collections::HashMap;
const CENTER_WORLD_ON_PLAYER: bool = true;
pub const ENGINE_SPEED_FACTOR: f32 = 30.0; pub const ENGINE_SPEED_FACTOR: f32 = 30.0;
const MAX_TRANSMISSION_DISTANCE: f32 = 100.0; const MAX_TRANSMISSION_DISTANCE: f32 = 100.0;
const MAX_INTERACT_DISTANCE: f32 = 50.0; const MAX_INTERACT_DISTANCE: f32 = 50.0;
@ -25,24 +30,40 @@ const MAX_INTERACT_DISTANCE: f32 = 50.0;
pub struct ActorPlugin; pub struct ActorPlugin;
impl Plugin for ActorPlugin { impl Plugin for ActorPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(PreUpdate, (
handle_player_death,
));
app.add_systems(FixedUpdate, ( app.add_systems(FixedUpdate, (
update_physics_lifeforms, update_physics_lifeforms,
update_power,
handle_wants_maxrotation, handle_wants_maxrotation,
handle_wants_maxvelocity, handle_wants_maxvelocity,
handle_gforce,
)); ));
app.add_systems(PostUpdate, handle_gforce
.after(PhysicsSet::Sync)
.after(sync::position_to_transform));
app.add_systems(Update, ( app.add_systems(Update, (
handle_input.run_if(in_control), handle_input,
handle_collisions, handle_collisions,
handle_damage, handle_damage,
handle_cheats,
)); ));
app.add_systems(PostUpdate, ( app.add_systems(PostUpdate, (
handle_vehicle_enter_exit, handle_vehicle_enter_exit,
update_id2pos,
)); ));
app.add_event::<VehicleEnterExitEvent>(); app.add_event::<VehicleEnterExitEvent>();
app.add_event::<PlayerDiesEvent>();
app.insert_resource(Id2Pos(HashMap::new()));
if CENTER_WORLD_ON_PLAYER {
// Disable bevy_xpbd's position->transform sync function
app.insert_resource(sync::SyncConfig {
position_to_transform: true,
transform_to_position: false,
});
// Add own position->transform sync function
app.add_systems(PostUpdate, position_to_transform
.after(sync::position_to_transform)
.in_set(sync::SyncSet::PositionToTransform));
}
} }
} }
@ -51,20 +72,19 @@ pub enum DamageType {
Unknown, Unknown,
Mental, Mental,
Trauma, Trauma,
GForce,
Asphyxiation, Asphyxiation,
Depressurization,
//Poison, //Poison,
//Radiation, //Radiation,
//Freeze, //Freeze,
//Burn, //Burn,
} }
#[derive(Event)] pub struct PlayerDiesEvent(pub DamageType);
#[derive(Event)] #[derive(Event)]
pub struct VehicleEnterExitEvent { pub struct VehicleEnterExitEvent {
vehicle: Entity, vehicle: Entity,
driver: Entity, driver: Entity,
name: Option<String>,
is_entering: bool, is_entering: bool,
is_player: bool is_player: bool
} }
@ -122,16 +142,15 @@ impl Default for ExperiencesGForce { fn default() -> Self { Self {
}}} }}}
#[derive(Component)] pub struct Player; // Attached to the suit of the player #[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 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 PlayerCamera; // Attached to the actor to use as point of view
#[derive(Component)] pub struct JustNowEnteredVehicle; #[derive(Component)] pub struct JustNowEnteredVehicle;
#[derive(Component)] pub struct ActorEnteringVehicle; #[derive(Component)] pub struct ActorEnteringVehicle;
#[derive(Component)] pub struct ActorVehicleBeingEntered; #[derive(Component)] pub struct ActorVehicleBeingEntered;
#[derive(Component)] pub struct PlayersFlashLight;
#[derive(Component)] pub struct WantsMaxRotation(pub f64); #[derive(Component)] pub struct WantsMaxRotation(pub f64);
#[derive(Component)] pub struct WantsMaxVelocity(pub f64); #[derive(Component)] pub struct WantsMaxVelocity(pub f64);
#[derive(Component)] pub struct Identifier(pub String); #[derive(Component)] pub struct Identifier(pub String);
#[derive(Resource)] pub struct Id2Pos(pub HashMap<String, DVec3>);
#[derive(Component)] #[derive(Component)]
pub struct LifeForm { pub struct LifeForm {
@ -189,58 +208,21 @@ impl Default for Engine {
#[derive(Component)] #[derive(Component)]
pub struct Suit { pub struct Suit {
pub oxygen: f32, pub oxygen: f32,
pub power: f32,
pub oxygen_max: f32, pub oxygen_max: f32,
pub power_max: f32,
pub integrity: f32, // [0.0 - 1.0] 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 { const SUIT_SIMPLE: Suit = Suit {
power: 1e5,
power_max: 1e5,
oxygen: nature::OXY_D, oxygen: nature::OXY_D,
oxygen_max: nature::OXY_D, oxygen_max: nature::OXY_D,
integrity: 1.0, integrity: 1.0,
}; };
#[derive(Component)]
pub struct Battery {
pub power: f32, // Watt-seconds
pub capacity: f32, // Watt-seconds
pub reactor: f32, // Watt (production)
}
impl Default for Battery {
fn default() -> Self {
Self {
power: 10e3 * 3600.0,
capacity: 10e3 * 3600.0, // 10kWh
reactor: 2000e3, // 2MW
}
}
}
pub fn update_power(
time: Res<Time>,
mut settings: ResMut<Settings>,
mut q_battery: Query<(&mut Battery, Option<&Player>)>,
mut q_flashlight: Query<&mut Visibility, With<PlayersFlashLight>>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
) {
let d = time.delta_seconds();
for (mut battery, player) in &mut q_battery {
if player.is_some() && settings.flashlight_active {
battery.power -= 4000000.0 * d;
if battery.power <= 0.0 {
settings.flashlight_active = false;
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Switch));
for mut flashlight_vis in &mut q_flashlight {
*flashlight_vis = Visibility::Hidden;
}
}
}
battery.power = (battery.power + battery.reactor * d)
.clamp(0.0, battery.capacity);
}
}
pub fn update_physics_lifeforms( pub fn update_physics_lifeforms(
time: Res<Time>, time: Res<Time>,
mut query: Query<(&mut LifeForm, &mut HitPoints, &mut Suit, &LinearVelocity)>, mut query: Query<(&mut LifeForm, &mut HitPoints, &mut Suit, &LinearVelocity)>,
@ -289,15 +271,14 @@ pub fn update_physics_lifeforms(
pub fn handle_input( pub fn handle_input(
mut commands: Commands, mut commands: Commands,
keyboard_input: Res<ButtonInput<KeyCode>>, keyboard_input: Res<ButtonInput<KeyCode>>,
mut settings: ResMut<Settings>, mut settings: ResMut<var::Settings>,
q_talker: Query<(&chat::Talker, &Transform), (Without<actor::Player>, Without<Camera>)>, q_talker: Query<(&chat::Talker, &Transform), (Without<actor::Player>, Without<Camera>)>,
player: Query<Entity, With<actor::Player>>, player: Query<Entity, With<actor::Player>>,
q_camera: Query<&Transform, With<Camera>>, q_camera: Query<&Transform, With<Camera>>,
mut q_flashlight: Query<&mut Visibility, With<PlayersFlashLight>>, q_vehicles: Query<(Entity, &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_conv: EventWriter<chat::StartConversationEvent>,
mut ew_vehicle: EventWriter<VehicleEnterExitEvent>, mut ew_vehicle: EventWriter<VehicleEnterExitEvent>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>, mut ew_playerdies: EventWriter<PlayerDiesEvent>,
q_player_drives: Query<Entity, With<PlayerDrivesThis>>, q_player_drives: Query<Entity, With<PlayerDrivesThis>>,
) { ) {
if q_camera.is_empty() || player.is_empty() { if q_camera.is_empty() || player.is_empty() {
@ -320,20 +301,16 @@ pub fn handle_input(
} }
// Entering Vehicles // Entering Vehicles
if q_player_drives.is_empty() { if q_player_drives.is_empty() {
let objects: Vec<((Entity, &Actor), &Transform)> = q_vehicles let objects: Vec<(Entity, &Transform)> = q_vehicles
.iter() .iter()
.map(|(entity, actor, transform)| ((entity, actor), transform))
.collect(); .collect();
if let (Some((entity, actor)), dist) = if let (Some(entity), dist) = camera::find_closest_target::<Entity>(objects, camtrans) {
camera::find_closest_target::<(Entity, &Actor)>(objects, camtrans)
{
if dist <= MAX_INTERACT_DISTANCE { if dist <= MAX_INTERACT_DISTANCE {
commands.entity(entity).insert(ActorVehicleBeingEntered); commands.entity(entity).insert(ActorVehicleBeingEntered);
commands.entity(player_entity).insert(ActorEnteringVehicle); commands.entity(player_entity).insert(ActorEnteringVehicle);
ew_vehicle.send(VehicleEnterExitEvent{ ew_vehicle.send(VehicleEnterExitEvent{
vehicle: entity, vehicle: entity,
driver: player_entity, driver: player_entity,
name: actor.name.clone(),
is_entering: q_player_drives.is_empty(), is_entering: q_player_drives.is_empty(),
is_player: true, is_player: true,
}); });
@ -349,37 +326,21 @@ pub fn handle_input(
ew_vehicle.send(VehicleEnterExitEvent{ ew_vehicle.send(VehicleEnterExitEvent{
vehicle: vehicle_entity, vehicle: vehicle_entity,
driver: player_entity, driver: player_entity,
name: None,
is_entering: false, is_entering: false,
is_player: true, is_player: true,
}); });
break; break;
} }
} }
else if keyboard_input.just_pressed(settings.key_flashlight) { else if keyboard_input.just_pressed(settings.key_restart) {
for mut flashlight_vis in &mut q_flashlight { settings.god_mode = false;
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Switch)); ew_playerdies.send(PlayerDiesEvent(DamageType::Mental));
if *flashlight_vis == Visibility::Hidden {
*flashlight_vis = Visibility::Visible;
settings.flashlight_active = true;
} else {
*flashlight_vis = Visibility::Hidden;
settings.flashlight_active = false;
}
}
}
else if keyboard_input.just_pressed(settings.key_cruise_control) {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Switch));
settings.cruise_control_active ^= true;
} }
} }
pub fn handle_vehicle_enter_exit( pub fn handle_vehicle_enter_exit(
mut commands: Commands, mut commands: Commands,
mut settings: ResMut<Settings>,
mut er_vehicle: EventReader<VehicleEnterExitEvent>, 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_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_vehicles: Query<(Entity, &mut Vehicle, &mut Visibility), (With<ActorVehicleBeingEntered>, Without<ActorEnteringVehicle>)>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>, mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
@ -405,14 +366,6 @@ pub fn handle_vehicle_enter_exit(
commands.entity(driver).insert(JustNowEnteredVehicle); commands.entity(driver).insert(JustNowEnteredVehicle);
commands.entity(vehicle).insert(PlayerCamera); commands.entity(vehicle).insert(PlayerCamera);
commands.entity(vehicle).insert(PlayerDrivesThis); commands.entity(vehicle).insert(PlayerDrivesThis);
if let Ok(mut flashlight_vis) = q_playerflashlight.get_single_mut() {
*flashlight_vis = Visibility::Hidden;
settings.flashlight_active = false;
}
if let Some(vehicle_name) = &event.name {
ew_achievement.send(game::AchievementEvent
::RideVehicle(vehicle_name.clone()));
}
} }
else { else {
// Exiting Vehicle // Exiting Vehicle
@ -436,15 +389,18 @@ pub fn handle_vehicle_enter_exit(
fn handle_collisions( fn handle_collisions(
mut collision_event_reader: EventReader<CollisionStarted>, mut collision_event_reader: EventReader<CollisionStarted>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>, mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
q_player: Query<Entity, With<PlayerCollider>>, q_player: Query<(Entity, Option<&Player>), With<PlayerCamera>>,
mut q_player_lifeform: Query<(&mut LifeForm, &mut Suit), With<Player>>, 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, player_maybe)), Ok((mut lifeform, mut suit))) = (q_player.get_single(), q_player_lifeform.get_single_mut()) {
for CollisionStarted(entity1, entity2) in collision_event_reader.read() { for CollisionStarted(entity1, entity2) in collision_event_reader.read() {
if *entity1 == player || *entity2 == player { if *entity1 == player || *entity2 == player {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Crash)); ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Crash));
lifeform.adrenaline_jolt += 0.1; lifeform.adrenaline_jolt += 0.1;
suit.integrity = (suit.integrity - 0.03).max(0.0);
if player_maybe.is_some() {
suit.integrity -= 0.03;
}
} }
} }
} }
@ -454,10 +410,11 @@ fn handle_wants_maxrotation(
//time: Res<Time>, //time: Res<Time>,
mut query: Query<(&mut AngularVelocity, &Engine, &WantsMaxRotation)>, mut query: Query<(&mut AngularVelocity, &Engine, &WantsMaxRotation)>,
) { ) {
let epsilon = 0.0001;
//let d = time.delta_seconds(); //let d = time.delta_seconds();
for (mut v_ang, engine, maxrot) in &mut query { for (mut v_ang, engine, maxrot) in &mut query {
let total = v_ang.0.length(); let total = v_ang.0.length();
if total <= maxrot.0 + EPSILON { if total <= maxrot.0 + epsilon {
if total > maxrot.0 { if total > maxrot.0 {
v_ang.0 = DVec3::splat(0.0); v_ang.0 = DVec3::splat(0.0);
} }
@ -474,9 +431,10 @@ fn handle_wants_maxvelocity(
mut query: Query<(&mut LinearVelocity, &Engine, &WantsMaxVelocity)>, mut query: Query<(&mut LinearVelocity, &Engine, &WantsMaxVelocity)>,
) { ) {
let dt = time.delta_seconds(); let dt = time.delta_seconds();
let epsilon = 0.0001;
for (mut v, engine, maxv) in &mut query { for (mut v, engine, maxv) in &mut query {
let total = v.0.length(); let total = v.0.length();
if total <= maxv.0 + EPSILON { if total <= maxv.0 + epsilon {
if total > maxv.0 { if total > maxv.0 {
v.0 = DVec3::splat(0.0); v.0 = DVec3::splat(0.0);
} }
@ -488,16 +446,73 @@ fn handle_wants_maxvelocity(
let avg_thrust = (engine.thrust_forward + engine.thrust_back + engine.thrust_sideways) / 3.0; let avg_thrust = (engine.thrust_forward + engine.thrust_back + engine.thrust_sideways) / 3.0;
let acceleration = (avg_thrust * dt) as f64 * -v.0; let acceleration = (avg_thrust * dt) as f64 * -v.0;
v.0 += acceleration; v.0 += acceleration;
if v.0.length() + EPSILON < acceleration.length() { if v.0.length() + epsilon < acceleration.length() {
v.0 = DVec3::splat(0.0); v.0 = DVec3::splat(0.0);
} }
} }
} }
fn handle_player_death(
mut cmd: Commands,
mut er_playerdies: EventReader<PlayerDiesEvent>,
q_scenes: Query<(Entity, &SceneInstance), With<world::DespawnOnPlayerDeath>>,
q_noscenes: Query<Entity, (With<world::DespawnOnPlayerDeath>, Without<SceneInstance>)>,
ew_spawn: EventWriter<commands::SpawnEvent>,
mut scene_spawner: ResMut<SceneSpawner>,
mut active_asteroids: ResMut<world::ActiveAsteroids>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_effect: EventWriter<effects::SpawnEffectEvent>,
mut log: ResMut<hud::Log>,
mut settings: ResMut<var::Settings>,
) {
for death in er_playerdies.read() {
if settings.god_mode {
return;
}
settings.reset_player_settings();
active_asteroids.0.clear();
for entity in &q_noscenes {
cmd.entity(entity).despawn();
}
for (entity, sceneinstance) in &q_scenes {
cmd.entity(entity).despawn();
scene_spawner.despawn_instance(**sceneinstance);
}
log.clear();
//cmd.run_system(commands::load_defs); // why is it so complicated to get SystemId?
match death.0 {
DamageType::Mental => {
ew_effect.send(effects::SpawnEffectEvent {
class: effects::Effects::FadeIn(Color::BLACK),
duration: 4.0,
});
}
DamageType::Asphyxiation => {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::WakeUp));
ew_effect.send(effects::SpawnEffectEvent {
class: effects::Effects::FadeIn(Color::BLACK),
duration: 1.0,
});
}
DamageType::Trauma | _ => {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::WakeUp));
ew_effect.send(effects::SpawnEffectEvent {
class: effects::Effects::FadeIn(Color::MAROON),
duration: 1.0,
});
}
}
commands::load_defs(ew_spawn);
return;
}
}
fn handle_damage( fn handle_damage(
mut ew_playerdies: EventWriter<game::PlayerDiesEvent>, mut ew_playerdies: EventWriter<PlayerDiesEvent>,
mut q_hp: Query<(&mut HitPoints, Option<&Player>), Changed<HitPoints>>, mut q_hp: Query<(&mut HitPoints, Option<&Player>), Changed<HitPoints>>,
settings: Res<Settings>, settings: Res<var::Settings>,
) { ) {
for (mut hp, player_maybe) in &mut q_hp { for (mut hp, player_maybe) in &mut q_hp {
if player_maybe.is_some() { if player_maybe.is_some() {
@ -505,7 +520,7 @@ fn handle_damage(
hp.current -= hp.damage; hp.current -= hp.damage;
} }
if hp.current <= 0.0 { if hp.current <= 0.0 {
ew_playerdies.send(game::PlayerDiesEvent(hp.damagetype)); ew_playerdies.send(PlayerDiesEvent(hp.damagetype));
} }
} }
else { else {
@ -530,7 +545,7 @@ fn handle_gforce(
} }
if gforce.gforce > gforce.damage_threshold { if gforce.gforce > gforce.damage_threshold {
hp.damage += (gforce.gforce - gforce.damage_threshold).powf(2.0) / 3000.0; hp.damage += (gforce.gforce - gforce.damage_threshold).powf(2.0) / 3000.0;
hp.damagetype = DamageType::GForce; hp.damagetype = DamageType::Trauma;
} }
if gforce.visual_effect > 0.0001 { if gforce.visual_effect > 0.0001 {
@ -544,3 +559,119 @@ fn handle_gforce(
} }
} }
} }
fn update_id2pos(
mut id2pos: ResMut<Id2Pos>,
q_id: Query<(&Position, &Identifier)>,
) {
id2pos.0.clear();
for (pos, id) in &q_id {
id2pos.0.insert(id.0.clone(), pos.0);
}
}
fn handle_cheats(
key_input: Res<ButtonInput<KeyCode>>,
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, &LinearVelocity), (With<hud::IsTargeted>, Without<actor::PlayerCamera>)>,
mut ew_playerdies: EventWriter<actor::PlayerDiesEvent>,
mut settings: ResMut<var::Settings>,
id2pos: Res<actor::Id2Pos>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
) {
if q_player.is_empty() || q_life.is_empty() {
return;
}
let (trans, mut pos, mut v) = q_player.get_single_mut().unwrap();
let (mut lifeform, mut gforce) = q_life.get_single_mut().unwrap();
let boost = if key_input.pressed(KeyCode::ShiftLeft) {
1e6
} else {
1e3
};
if key_input.just_pressed(settings.key_cheat_god_mode) {
settings.god_mode ^= true;
if settings.god_mode {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::EnterVehicle));
}
}
if !settings.god_mode && !settings.dev_mode {
return;
}
if key_input.just_pressed(settings.key_cheat_stop) {
gforce.ignore_gforce_seconds = 1.0;
v.0 = DVec3::ZERO;
}
if key_input.pressed(settings.key_cheat_speed) {
gforce.ignore_gforce_seconds = 1.0;
v.0 += DVec3::from(trans.rotation * Vec3::new(0.0, 0.0, boost));
}
if key_input.pressed(settings.key_cheat_speed_backward) {
gforce.ignore_gforce_seconds = 1.0;
v.0 += DVec3::from(trans.rotation * Vec3::new(0.0, 0.0, -boost));
}
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();
pos.0 = **target_pos + offset;
*v = target_v.clone();
}
}
if !settings.dev_mode {
return;
}
if key_input.just_pressed(settings.key_cheat_pizza) {
if let Some(target) = id2pos.0.get(&"pizzeria".to_string()) {
pos.0 = *target + DVec3::new(-60.0, 0.0, 0.0);
gforce.ignore_gforce_seconds = 1.0;
}
}
if key_input.just_pressed(settings.key_cheat_farview1) {
if let Some(target) = id2pos.0.get(&"busstopclippy2".to_string()) {
pos.0 = *target + DVec3::new(0.0, -1000.0, 0.0);
gforce.ignore_gforce_seconds = 1.0;
}
}
if key_input.just_pressed(settings.key_cheat_farview2) {
if let Some(target) = id2pos.0.get(&"busstopclippy3".to_string()) {
pos.0 = *target + DVec3::new(0.0, -1000.0, 0.0);
gforce.ignore_gforce_seconds = 1.0;
}
}
if key_input.pressed(settings.key_cheat_adrenaline_zero) {
lifeform.adrenaline = 0.0;
}
if key_input.pressed(settings.key_cheat_adrenaline_mid) {
lifeform.adrenaline = 0.5;
}
if key_input.pressed(settings.key_cheat_adrenaline_max) {
lifeform.adrenaline = 1.0;
}
if key_input.just_pressed(settings.key_cheat_die) {
settings.god_mode = false;
ew_playerdies.send(actor::PlayerDiesEvent(actor::DamageType::Trauma));
}
}
// An extension of bevy_xpbd_3d::plugins::position_to_transform that adjusts
// the rendering position to center entities at the player camera.
// This avoids rendering glitches when very far away from the origin.
pub fn position_to_transform(
q_player: Query<&Position, With<actor::PlayerCamera>>,
mut q_trans: Query<(&'static mut Transform, &'static Position, &'static Rotation), Without<Parent>>,
) {
if let Ok(player_pos) = q_player.get_single() {
for (mut transform, pos, rot) in &mut q_trans {
transform.translation = Vec3::new(
(pos.x - player_pos.x) as f32,
(pos.y - player_pos.y) as f32,
(pos.z - player_pos.z) as f32,
);
transform.rotation = rot.as_quat();
}
}
}

View file

@ -12,22 +12,34 @@
use bevy::prelude::*; use bevy::prelude::*;
use bevy::audio::{PlaybackMode, Volume}; use bevy::audio::{PlaybackMode, Volume};
use crate::prelude::*; use crate::{camera, var};
use std::collections::HashMap;
const ASSET_CLICK: &str = "sounds/click-button-140881-crop.ogg";
const ASSET_SWITCH: &str = "sounds/typosonic-typing-192811-crop.ogg";
const ASSET_WOOSH: &str = "sounds/woosh.ogg";
const ASSET_ZOOM: &str = "sounds/zoom.ogg";
const ASSET_INCOMING_MESSAGE: &str = "sounds/connect.ogg";
const ASSET_PING: &str = "sounds/connect.ogg";
const ASSET_CONNECT: &str = "sounds/connect.ogg";
const ASSET_BGM: &str = "music/Aleksey Chistilin - Cinematic Cello.ogg";
const ASSET_THRUSTER: &str = "sounds/thruster.ogg";
const ASSET_ROCKET: &str = "sounds/rocket.ogg";
const ASSET_ION: &str = "sounds/ion.ogg";
const ASSET_WAKEUP: &str = "sounds/wakeup.ogg";
const ASSET_BIKESTART: &str = "sounds/bikestart.ogg";
const ASSET_CRASH: &str = "sounds/crash.ogg";
const ASSET_ELECTRICMOTOR: &str = "sounds/electricmotor.ogg";
pub struct AudioPlugin; pub struct AudioPlugin;
impl Plugin for AudioPlugin { impl Plugin for AudioPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Startup, setup); app.add_systems(Startup, setup);
app.add_systems(Update, respawn_sinks.run_if(on_event::<RespawnSinksEvent>())); app.add_systems(Update, toggle_bgm);
app.add_systems(Update, play_zoom_sfx); 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, play_sfx);
app.add_systems(PostUpdate, update_music); app.add_systems(PostUpdate, update_music);
app.add_event::<PlaySfxEvent>(); app.add_event::<PlaySfxEvent>();
app.add_event::<PauseAllSfxEvent>();
app.add_event::<ToggleMusicEvent>(); app.add_event::<ToggleMusicEvent>();
app.add_event::<RespawnSinksEvent>();
app.insert_resource(ZoomTimer( app.insert_resource(ZoomTimer(
Timer::from_seconds(0.09, TimerMode::Repeating))); Timer::from_seconds(0.09, TimerMode::Repeating)));
} }
@ -35,48 +47,177 @@ impl Plugin for AudioPlugin {
#[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::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::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::Ping, "sounds/connect.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"),
];
#[derive(Component, PartialEq, Hash, Eq, Copy, Clone)]
pub enum Sfx { pub enum Sfx {
Achieve,
BGM,
Click,
Connect,
Crash,
ElectricMotor,
EnterVehicle,
IncomingChatMessage, IncomingChatMessage,
Ion, Click,
Ping,
Rocket,
Switch, Switch,
Thruster,
WakeUp,
Woosh, Woosh,
Zoom, Zoom,
Ping,
Connect,
EnterVehicle,
Crash,
WakeUp,
None,
}
#[derive(Event)] pub struct PlaySfxEvent(pub Sfx);
#[derive(Event)] pub struct ToggleMusicEvent();
#[derive(Component)] pub struct ComponentBGM;
#[derive(Component)] pub struct ComponentThrusterSound;
#[derive(Component)] pub struct ComponentRocketSound;
#[derive(Component)] pub struct ComponentIonSound;
#[derive(Component)] pub struct ComponentElectricMotorSound;
#[derive(Component)] struct SoundBGM(Handle<AudioSource>);
#[derive(Resource)] pub struct SoundClick(Handle<AudioSource>);
#[derive(Resource)] pub struct SoundSwitch(Handle<AudioSource>);
#[derive(Resource)] pub struct SoundWoosh(Handle<AudioSource>);
#[derive(Resource)] pub struct SoundZoom(Handle<AudioSource>);
#[derive(Resource)] pub struct SoundIncomingMessage(Handle<AudioSource>);
#[derive(Resource)] pub struct SoundPing(Handle<AudioSource>);
#[derive(Resource)] pub struct SoundConnect(Handle<AudioSource>);
#[derive(Resource)] pub struct SoundBikeStart(Handle<AudioSource>);
#[derive(Resource)] pub struct SoundCrash(Handle<AudioSource>);
#[derive(Resource)] pub struct SoundWakeUp(Handle<AudioSource>);
pub fn setup(
mut commands: Commands,
settings: Res<var::Settings>,
asset_server: Res<AssetServer>,
) {
commands.spawn((
ComponentBGM,
AudioBundle {
source: SoundBGM(asset_server.load(ASSET_BGM)).0.clone(),
settings: PlaybackSettings {
mode: PlaybackMode::Loop,
paused: settings.hud_active || settings.mute_music,
..default()
},
},
));
commands.spawn((
ComponentThrusterSound,
AudioBundle {
source: asset_server.load(ASSET_THRUSTER),
settings: PlaybackSettings {
mode: PlaybackMode::Loop,
volume: Volume::new(0.0),
paused: true,
..default()
},
},
));
commands.spawn((
ComponentRocketSound,
AudioBundle {
source: asset_server.load(ASSET_ROCKET),
settings: PlaybackSettings {
mode: PlaybackMode::Loop,
volume: Volume::new(0.0),
paused: true,
..default()
},
},
));
commands.spawn((
ComponentIonSound,
AudioBundle {
source: asset_server.load(ASSET_ION),
settings: PlaybackSettings {
mode: PlaybackMode::Loop,
volume: Volume::new(0.0),
paused: true,
..default()
},
},
));
commands.spawn((
ComponentElectricMotorSound,
AudioBundle {
source: asset_server.load(ASSET_ELECTRICMOTOR),
settings: PlaybackSettings {
mode: PlaybackMode::Loop,
volume: Volume::new(0.5),
paused: true,
..default()
},
},
));
commands.insert_resource(SoundClick(asset_server.load(ASSET_CLICK)));
commands.insert_resource(SoundSwitch(asset_server.load(ASSET_SWITCH)));
commands.insert_resource(SoundWoosh(asset_server.load(ASSET_WOOSH)));
commands.insert_resource(SoundZoom(asset_server.load(ASSET_ZOOM)));
commands.insert_resource(SoundIncomingMessage(asset_server.load(ASSET_INCOMING_MESSAGE)));
commands.insert_resource(SoundPing(asset_server.load(ASSET_PING)));
commands.insert_resource(SoundConnect(asset_server.load(ASSET_CONNECT)));
commands.insert_resource(SoundBikeStart(asset_server.load(ASSET_BIKESTART)));
commands.insert_resource(SoundCrash(asset_server.load(ASSET_CRASH)));
commands.insert_resource(SoundWakeUp(asset_server.load(ASSET_WAKEUP)));
}
pub fn toggle_bgm(
keyboard_input: Res<ButtonInput<KeyCode>>,
mut evwriter_toggle: EventWriter<ToggleMusicEvent>,
mut evwriter_sfx: EventWriter<PlaySfxEvent>,
mut settings: ResMut<var::Settings>,
) {
if keyboard_input.just_pressed(settings.key_toggle_music) {
settings.mute_music ^= true;
evwriter_sfx.send(PlaySfxEvent(Sfx::Click));
evwriter_toggle.send(ToggleMusicEvent());
}
if keyboard_input.just_pressed(settings.key_toggle_sfx) {
settings.mute_sfx ^= true;
evwriter_sfx.send(PlaySfxEvent(Sfx::Click));
evwriter_toggle.send(ToggleMusicEvent());
}
}
pub fn play_sfx(
mut commands: Commands,
settings: Res<var::Settings>,
mut events_sfx: EventReader<PlaySfxEvent>,
sound_click: Res<SoundClick>,
sound_switch: Res<SoundSwitch>,
sound_woosh: Res<SoundWoosh>,
sound_zoom: Res<SoundZoom>,
sound_incoming_message: Res<SoundIncomingMessage>,
sound_ping: Res<SoundPing>,
sound_connect: Res<SoundConnect>,
sound_bikestart: Res<SoundBikeStart>,
sound_crash: Res<SoundCrash>,
sound_wakeup: Res<SoundWakeUp>,
) {
if settings.mute_sfx && !events_sfx.is_empty() {
events_sfx.clear();
}
for sfx in events_sfx.read() {
match sfx.0 {
Sfx::None => { continue; }
_ => {}
}
commands.spawn(AudioBundle {
source: match sfx.0 {
Sfx::Switch => sound_switch.0.clone(),
Sfx::Click => sound_click.0.clone(),
Sfx::Woosh => sound_woosh.0.clone(),
Sfx::Zoom => sound_zoom.0.clone(),
Sfx::IncomingChatMessage => sound_incoming_message.0.clone(),
Sfx::Ping => sound_ping.0.clone(),
Sfx::Connect => sound_connect.0.clone(),
Sfx::EnterVehicle => sound_bikestart.0.clone(),
Sfx::Crash => sound_crash.0.clone(),
Sfx::WakeUp => sound_wakeup.0.clone(),
Sfx::None => sound_ping.0.clone(),
},
settings: PlaybackSettings::DESPAWN,
});
}
} }
pub fn str2sfx(sfx_label: &str) -> Sfx { pub fn str2sfx(sfx_label: &str) -> Sfx {
return match sfx_label { return match sfx_label {
"achieve" => Sfx::Achieve,
"switch" => Sfx::Switch, "switch" => Sfx::Switch,
"click" => Sfx::Click, "click" => Sfx::Click,
"woosh" => Sfx::Woosh, "woosh" => Sfx::Woosh,
@ -85,111 +226,19 @@ pub fn str2sfx(sfx_label: &str) -> Sfx {
"ping" => Sfx::Ping, "ping" => Sfx::Ping,
"connect" => Sfx::Connect, "connect" => Sfx::Connect,
"entervehicle" => Sfx::EnterVehicle, "entervehicle" => Sfx::EnterVehicle,
"crash" => Sfx::Ping, "crash" => Sfx::Crash,
_ => Sfx::Click, _ => Sfx::None,
}; };
} }
pub enum SfxType {
BGM,
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>>);
pub fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut ew_respawnsinks: EventWriter<RespawnSinksEvent>,
) {
let mut map = HashMap::new();
for (_, sfx, path) in PATHS {
let source = asset_server.load(*path);
map.insert(*sfx, source.clone());
}
commands.insert_resource(Sounds(map));
ew_respawnsinks.send(RespawnSinksEvent);
}
pub fn respawn_sinks(
mut commands: Commands,
asset_server: Res<AssetServer>,
settings: Res<var::Settings>,
q_audiosinks: Query<Entity, (With<AudioSink>, With<Sfx>)>,
) {
for sink in &q_audiosinks {
commands.entity(sink).despawn();
}
for (sfxtype, sfx, path) in PATHS {
let source = asset_server.load(*path);
match sfxtype {
SfxType::BGM => {
commands.spawn((
*sfx,
AudioBundle {
source,
settings: PlaybackSettings {
mode: PlaybackMode::Loop,
paused: settings.mute_music,
..default()
},
},
));
},
SfxType::LoopSfx => {
commands.spawn((
*sfx,
AudioBundle {
source,
settings: PlaybackSettings {
mode: PlaybackMode::Loop,
volume: Volume::new(0.0),
paused: true,
..default()
},
},
));
},
SfxType::OneOff => ()
}
}
}
pub fn play_sfx(
mut commands: Commands,
settings: Res<var::Settings>,
mut events_sfx: EventReader<PlaySfxEvent>,
sounds: Res<Sounds>,
) {
if settings.mute_sfx && !events_sfx.is_empty() {
events_sfx.clear();
}
for sfx in events_sfx.read() {
if let Some(source) = sounds.0.get(&sfx.0) {
commands.spawn(AudioBundle {
source: source.clone(),
settings: PlaybackSettings::DESPAWN,
});
}
}
}
pub fn update_music( pub fn update_music(
mut events: EventReader<ToggleMusicEvent>, mut events: EventReader<ToggleMusicEvent>,
q_audiosinks: Query<(&AudioSink, &Sfx)>, bgm_controller: Query<&AudioSink, With<ComponentBGM>>,
settings: Res<var::Settings>, settings: Res<var::Settings>,
) { ) {
if !events.is_empty() { if !events.is_empty() {
events.clear(); events.clear();
for (bgm_sink, sfx) in &q_audiosinks { if let Ok(bgm_sink) = bgm_controller.get_single() {
if *sfx != Sfx::BGM {
continue;
}
if settings.mute_music { if settings.mute_music {
bgm_sink.pause(); bgm_sink.pause();
} }
@ -218,11 +267,3 @@ pub fn play_zoom_sfx(
*last_zoom_level = mapcam.target_zoom_level; *last_zoom_level = mapcam.target_zoom_level;
} }
} }
pub fn pause_all(
q_audiosinks: Query<&AudioSink, With<Sfx>>,
) {
for sink in &q_audiosinks {
sink.pause();
}
}

Binary file not shown.

Binary file not shown.

View file

@ -9,11 +9,7 @@
# + + + ███ # + + + ███
# + ▀████████████████████████████████████████████████████▀ # + ▀████████████████████████████████████████████████████▀
# #
# This script generates the file /src/data/stars.in. # This script requires the following file in the extra/ directory:
#
# You do not have to run it unless you wish to change the stars.in file.
#
# It requires the following file in the extra/ directory:
# https://github.com/astronexus/HYG-Database/blob/cbd21013d2bb89732b893be357a6f41836dbe614/hyg/CURRENT/hygdata_v41.csv # https://github.com/astronexus/HYG-Database/blob/cbd21013d2bb89732b893be357a6f41836dbe614/hyg/CURRENT/hygdata_v41.csv
import csv import csv

View file

@ -13,5 +13,5 @@
rootdir="${1:-}" rootdir="${1:-}"
install -Dm755 "target/release/outfly" "$rootdir/usr/bin/outfly" install -Dm755 "target/release/outfly" "$rootdir/usr/bin/outfly"
install -Dm644 "build/linux/outfly.png" "$rootdir/usr/share/pixmaps/outfly.png" install -Dm644 "src/build/linux/outfly.png" "$rootdir/usr/share/pixmaps/outfly.png"
install -Dm644 "build/linux/outfly.desktop" "$rootdir/usr/share/applications/outfly.desktop" install -Dm644 "src/build/linux/outfly.desktop" "$rootdir/usr/share/applications/outfly.desktop"

BIN
src/build/linux/outfly.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View file

@ -10,26 +10,17 @@
# + ▀████████████████████████████████████████████████████▀ # + ▀████████████████████████████████████████████████████▀
# #
# A script to package release binaries + README.md into zip files. # A script to package release binaries + README.md into zip files.
# Usage: cd outfly; build/pack.sh [-b] # Usage: cd outfly; src/build/pack.sh [-b]
# Options: -b: cross-compile targets before packing # Options: -b: cross-compile targets before packing
set -e set -e
function check_uncommitted_assets() {
( cd assets; [ $(git status --porcelain . | wc -l) -gt 0 ] )
}
if check_uncommitted_assets; then
echo "Please make sure there are no uncommited files in the 'asset' directory before packing"
exit 1
fi
if [ "$1" == "-b" ]; then if [ "$1" == "-b" ]; then
cargo build --release --target=x86_64-unknown-linux-gnu --no-default-features --features release_linux cargo build --release --target=x86_64-unknown-linux-gnu --no-default-features --features release_linux
cargo build --release --target=x86_64-pc-windows-gnu --no-default-features --features release_windows cargo build --release --target=x86_64-pc-windows-gnu --no-default-features --features release_windows
fi fi
VERSION="$(sed -nr 's/^\s*version\s*=\s*"(.*)"\s*$/\1/p' Cargo.toml | head -n1)" VERSION="$(sed -nr 's/^\s*version\s*=\s*"(.*)"\s*$/\1/p' Cargo.toml)"
test -z "$VERSION" && echo 'Error: Could not extract version from Cargo.toml' && exit test -z "$VERSION" && echo 'Error: Could not extract version from Cargo.toml' && exit
echo "Extracted version from Cargo.toml: $VERSION" echo "Extracted version from Cargo.toml: $VERSION"

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View file

@ -19,10 +19,11 @@ use bevy::core_pipeline::bloom::{BloomCompositeMode, BloomSettings};
use bevy::core_pipeline::tonemapping::Tonemapping; use bevy::core_pipeline::tonemapping::Tonemapping;
use bevy::pbr::{CascadeShadowConfigBuilder, DirectionalLightShadowMap}; use bevy::pbr::{CascadeShadowConfigBuilder, DirectionalLightShadowMap};
use bevy::transform::TransformSystem; use bevy::transform::TransformSystem;
use bevy::math::{DVec3, DQuat};
use bevy_xpbd_3d::prelude::*; use bevy_xpbd_3d::prelude::*;
use bevy_xpbd_3d::plugins::sync; use std::f32::consts::PI;
use crate::prelude::*; use std::f64::consts::PI as PI64;
use std::collections::HashMap; use crate::{actor, audio, hud, var};
pub const INITIAL_ZOOM_LEVEL: f64 = 10.0; pub const INITIAL_ZOOM_LEVEL: f64 = 10.0;
@ -31,31 +32,19 @@ pub struct CameraPlugin;
impl Plugin for CameraPlugin { impl Plugin for CameraPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Startup, setup_camera); app.add_systems(Startup, setup_camera);
app.add_systems(Update, handle_input.run_if(in_control)); app.add_systems(Update, handle_input);
app.add_systems(Update, update_map_only_object_visibility.run_if(alive)); app.add_systems(Update, update_map_only_object_visibility);
app.add_systems(Update, manage_player_actor.after(handle_input)); app.add_systems(Update, manage_player_actor.after(handle_input));
app.add_systems(PostUpdate, sync_camera_to_player app.add_systems(PostUpdate, sync_camera_to_player
.after(PhysicsSet::Sync) .after(PhysicsSet::Sync)
.after(apply_input_to_player) .after(apply_input_to_player)
.before(TransformSystem::TransformPropagate)); .before(TransformSystem::TransformPropagate));
app.add_systems(PostUpdate, update_mapcam_center app.add_systems(Update, update_map_camera);
.before(sync::position_to_transform) app.add_systems(Update, update_fov);
.in_set(sync::SyncSet::PositionToTransform)); app.add_systems(PostUpdate, apply_input_to_player
app.add_systems(Update, update_map_camera.run_if(in_control)); .after(PhysicsSet::Sync)
app.add_systems(Update, update_fov.run_if(alive)); .before(TransformSystem::TransformPropagate));
app.add_systems(PreUpdate, apply_input_to_player);
app.insert_resource(MapCam::default()); app.insert_resource(MapCam::default());
// To center the renderer origin on the player camera,
// 1. Disable bevy_xpbd's position->transform sync function
app.insert_resource(sync::SyncConfig {
position_to_transform: true,
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));
} }
} }
@ -75,7 +64,6 @@ pub struct MapCam {
pub offset_x: f64, pub offset_x: f64,
pub offset_z: f64, pub offset_z: f64,
pub center: DVec3, pub center: DVec3,
pub center_on_entity: Option<Entity>,
} }
impl Default for MapCam { impl Default for MapCam {
fn default() -> Self { fn default() -> Self {
@ -83,12 +71,11 @@ impl Default for MapCam {
initialized: false, initialized: false,
zoom_level: 2.0, zoom_level: 2.0,
target_zoom_level: INITIAL_ZOOM_LEVEL, target_zoom_level: INITIAL_ZOOM_LEVEL,
pitch: PI * 0.3, pitch: PI64 * 0.3,
yaw: 0.0, yaw: 0.0,
offset_x: 0.0, offset_x: 0.0,
offset_z: 0.0, offset_z: 0.0,
center: DVec3::new(0.0, 0.0, 0.0), center: DVec3::new(0.0, 0.0, 0.0),
center_on_entity: None,
} }
} }
} }
@ -122,7 +109,7 @@ pub fn setup_camera(
shadows_enabled: settings.shadows_sun, shadows_enabled: settings.shadows_sun,
..default() ..default()
}, },
transform: Transform::from_rotation(Quat::from_rotation_y(PI32/2.0)), transform: Transform::from_rotation(Quat::from_rotation_y(PI/2.0)),
cascade_shadow_config: CascadeShadowConfigBuilder { cascade_shadow_config: CascadeShadowConfigBuilder {
num_cascades: 4, num_cascades: 4,
minimum_distance: 0.1, minimum_distance: 0.1,
@ -149,16 +136,14 @@ pub fn sync_camera_to_player(
let (actor, player_transform) = q_playercam.get_single().unwrap(); let (actor, player_transform) = q_playercam.get_single().unwrap();
// Rotation // Rotation
let rotation = player_transform.rotation * Quat::from_array([0.0, -1.0, 0.0, 0.0]); camera_transform.rotation = player_transform.rotation * Quat::from_array([0.0, -1.0, 0.0, 0.0]);
// Translation // Translation
if settings.third_person { 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 + camera_transform.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.translation = player_transform.translation;
camera_transform.rotation = rotation;
} }
} }
@ -166,8 +151,8 @@ pub fn update_map_camera(
settings: Res<var::Settings>, settings: Res<var::Settings>,
mut mapcam: ResMut<MapCam>, mut mapcam: ResMut<MapCam>,
mut q_camera: Query<&mut Transform, (With<Camera>, Without<actor::PlayerCamera>)>, mut q_camera: Query<&mut Transform, (With<Camera>, Without<actor::PlayerCamera>)>,
q_playercam: Query<(Entity, &Transform), (With<actor::PlayerCamera>, Without<Camera>)>, q_playercam: Query<&Transform, (With<actor::PlayerCamera>, Without<Camera>)>,
q_target: Query<(Entity, &Transform), (With<hud::IsTargeted>, Without<Camera>, Without<actor::PlayerCamera>)>, q_target: Query<&Transform, (With<hud::IsTargeted>, Without<Camera>, Without<actor::PlayerCamera>)>,
q_target_changed: Query<(), Changed<hud::IsTargeted>>, q_target_changed: Query<(), Changed<hud::IsTargeted>>,
mut mouse_events: EventReader<MouseMotion>, mut mouse_events: EventReader<MouseMotion>,
mut er_mousewheel: EventReader<MouseWheel>, mut er_mousewheel: EventReader<MouseWheel>,
@ -177,13 +162,12 @@ pub fn update_map_camera(
return; return;
} }
let mut camera_transform = q_camera.get_single_mut().unwrap(); let mut camera_transform = q_camera.get_single_mut().unwrap();
let (player_entity, player_trans) = q_playercam.get_single().unwrap(); let player_transform = q_playercam.get_single().unwrap();
let (target_entity, target_trans) = if let Ok(target) = q_target.get_single() { let target = if let Ok(target) = q_target.get_single() {
target target
} else { } else {
(player_entity, player_trans) player_transform
}; };
mapcam.center_on_entity = Some(target_entity);
// Get mouse movement // Get mouse movement
let mut mouse_delta = Vec2::ZERO; let mut mouse_delta = Vec2::ZERO;
@ -193,9 +177,10 @@ pub fn update_map_camera(
// NOTE: we need to subtract a bit from PI/2, otherwise the "up" // NOTE: we need to subtract a bit from PI/2, otherwise the "up"
// direction parameter for the Transform.look_at function is ambiguous // direction parameter for the Transform.look_at function is ambiguous
// at the extreme values and the orientation will flicker back/forth. // at the extreme values and the orientation will flicker back/forth.
let min_zoom: f64 = target_trans.scale.x as f64 * 2.0; let epsilon = 0.001;
let min_zoom: f64 = target.scale.x as f64 * 2.0;
let max_zoom: f64 = 17e18; // at this point, camera starts glitching 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(-PI64 / 2.0 + epsilon, PI64 / 2.0 - epsilon);
mapcam.yaw += mouse_delta.x as f64 / 180.0 * settings.mouse_sensitivity as f64; mapcam.yaw += mouse_delta.x as f64 / 180.0 * settings.mouse_sensitivity as f64;
// Reset movement offset if target changes // Reset movement offset if target changes
@ -226,9 +211,9 @@ pub fn update_map_camera(
// Update zoom level // Update zoom level
if !mapcam.initialized { if !mapcam.initialized {
let factor: f64 = if target_trans == player_trans { 7.0 } else { 1.0 }; let factor: f64 = if target == player_transform { 7.0 } else { 1.0 };
mapcam.target_zoom_level *= target_trans.scale.x as f64 * factor; mapcam.target_zoom_level *= target.scale.x as f64 * factor;
mapcam.zoom_level *= target_trans.scale.x as f64 * factor; mapcam.zoom_level *= target.scale.x as f64 * factor;
mapcam.initialized = true; mapcam.initialized = true;
} }
let mut change_zoom: f64 = 0.0; let mut change_zoom: f64 = 0.0;
@ -247,7 +232,8 @@ pub fn update_map_camera(
// Update point of view // 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)); let offset = DVec3::new(mapcam.offset_x, 0.0, mapcam.offset_z);
let point_of_view = offset + pov_rotation * (mapcam.zoom_level as f64 * DVec3::new(1.0, 0.0, 0.0));
// Update movement offset // Update movement offset
let mut direction = pov_rotation * DVec3::new(offset_x, 0.0, offset_z); let mut direction = pov_rotation * DVec3::new(offset_x, 0.0, offset_z);
@ -259,24 +245,9 @@ pub fn update_map_camera(
mapcam.offset_z += 0.01 * (direction.z * mapcam.zoom_level); mapcam.offset_z += 0.01 * (direction.z * mapcam.zoom_level);
// Apply updates to camera // Apply updates to camera
camera_transform.translation = point_of_view.as_vec3(); mapcam.center = target.translation.as_dvec3() + offset;
camera_transform.look_at(Vec3::ZERO, Vec3::Y); camera_transform.translation = target.translation + point_of_view.as_vec3();
} camera_transform.look_at(mapcam.center.as_vec3(), Vec3::Y);
pub fn update_mapcam_center(
mut mapcam: ResMut<MapCam>,
settings: Res<var::Settings>,
q_pos: Query<&Position>,
) {
if !settings.map_active {
return;
}
if let Some(entity) = mapcam.center_on_entity {
if let Ok(pos) = q_pos.get(entity) {
let offset = DVec3::new(mapcam.offset_x, 0.0, mapcam.offset_z);
mapcam.center = **pos + offset;
}
}
} }
pub fn update_fov( pub fn update_fov(
@ -305,19 +276,33 @@ pub fn update_fov(
pub fn handle_input( pub fn handle_input(
keyboard_input: Res<ButtonInput<KeyCode>>, keyboard_input: Res<ButtonInput<KeyCode>>,
settings: Res<var::Settings>, mut q_light: Query<&mut DirectionalLight>,
mut settings: ResMut<var::Settings>,
mut mapcam: ResMut<MapCam>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>, mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_game: EventWriter<GameEvent>, mut ew_updateoverlays: EventWriter<hud::UpdateOverlayVisibility>,
) { ) {
if keyboard_input.just_pressed(settings.key_camera) { if keyboard_input.just_pressed(settings.key_camera) {
ew_game.send(GameEvent::SetThirdPerson(Toggle)); settings.third_person ^= true;
}
if keyboard_input.just_pressed(settings.key_shadows) {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Click));
settings.shadows_sun ^= true;
for mut light in &mut q_light {
light.shadows_enabled = settings.shadows_sun;
}
} }
if keyboard_input.just_pressed(settings.key_map) { if keyboard_input.just_pressed(settings.key_map) {
ew_game.send(GameEvent::SetMap(Toggle)); settings.map_active ^= true;
if settings.map_active {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Woosh));
}
*mapcam = MapCam::default();
ew_updateoverlays.send(hud::UpdateOverlayVisibility);
} }
if keyboard_input.just_pressed(settings.key_rotation_stabilizer) { if keyboard_input.just_pressed(settings.key_rotation_stabilizer) {
ew_game.send(GameEvent::SetRotationStabilizer(Toggle));
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Switch)); ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Switch));
settings.rotation_stabilizer_active ^= true;
} }
} }
@ -366,7 +351,10 @@ pub fn apply_input_to_player(
windows: Query<&Window, With<PrimaryWindow>>, windows: Query<&Window, With<PrimaryWindow>>,
mut mouse_events: EventReader<MouseMotion>, mut mouse_events: EventReader<MouseMotion>,
key_input: Res<ButtonInput<KeyCode>>, key_input: Res<ButtonInput<KeyCode>>,
q_audiosinks: Query<(&audio::Sfx, &AudioSink)>, thruster_sound_controller: Query<&AudioSink, With<audio::ComponentThrusterSound>>,
rocket_sound_controller: Query<&AudioSink, With<audio::ComponentRocketSound>>,
ion_sound_controller: Query<&AudioSink, With<audio::ComponentIonSound>>,
electricmotor_sound_controller: Query<&AudioSink, With<audio::ComponentElectricMotorSound>>,
q_target: Query<&LinearVelocity, (With<hud::IsTargeted>, Without<actor::PlayerCamera>)>, q_target: Query<&LinearVelocity, (With<hud::IsTargeted>, Without<actor::PlayerCamera>)>,
mut q_playercam: Query<( mut q_playercam: Query<(
&Transform, &Transform,
@ -377,7 +365,7 @@ pub fn apply_input_to_player(
Option<&actor::PlayerDrivesThis>, Option<&actor::PlayerDrivesThis>,
), (With<actor::PlayerCamera>, Without<Camera>)>, ), (With<actor::PlayerCamera>, Without<Camera>)>,
) { ) {
if settings.map_active || !settings.in_control() { if settings.map_active {
return; return;
} }
let dt = time.delta_seconds(); let dt = time.delta_seconds();
@ -405,7 +393,7 @@ pub fn apply_input_to_player(
if let Ok((player_transform, mut engine, mut angularvelocity, mut v, mut torque, bike)) = q_playercam.get_single_mut() { if let Ok((player_transform, mut engine, mut angularvelocity, mut v, mut torque, bike)) = q_playercam.get_single_mut() {
// Handle key input // Handle key input
if focused { if focused {
if key_input.pressed(settings.key_forward) || settings.cruise_control_active { if key_input.pressed(settings.key_forward) {
axis_input.z += 1.2; axis_input.z += 1.2;
} }
if key_input.pressed(settings.key_back) { if key_input.pressed(settings.key_back) {
@ -429,10 +417,6 @@ pub fn apply_input_to_player(
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 {
if settings.cruise_control_active {
axis_input.z += 1.2;
}
} }
// In typical games we would normalize the input vector so that diagonal movement is as // In typical games we would normalize the input vector so that diagonal movement is as
// fast as forward or sideways movement. But here, we merely clamp each direction to an // fast as forward or sideways movement. But here, we merely clamp each direction to an
@ -514,9 +498,8 @@ pub fn apply_input_to_player(
} }
} }
let slowrot = settings.rotation_stabilizer_active || key_input.pressed(settings.key_stop); let angular_slowdown: f64 = if settings.rotation_stabilizer_active {
let angular_slowdown: f64 = if slowrot { (2.0 - engine.reaction_wheels.powf(0.01).clamp(1.001, 1.1)) as f64
(2.0 - engine.reaction_wheels.powf(0.05).clamp(1.001, 1.1)) as f64
} else { } else {
1.0 1.0
}; };
@ -539,13 +522,8 @@ pub fn apply_input_to_player(
} }
} }
let mut sinks: HashMap<audio::Sfx, &AudioSink> = HashMap::new();
for (sfx, sink) in &q_audiosinks {
sinks.insert(*sfx, sink);
}
// Play sound effects // Play sound effects
if let Some(sink) = sinks.get(&audio::Sfx::ElectricMotor) { if let Ok(sink) = electricmotor_sound_controller.get_single() {
let volume = sink.volume(); let volume = sink.volume();
let speed = sink.speed(); let speed = sink.speed();
let action = pitch_yaw_rot.length_squared().powf(0.2) * 0.0005; let action = pitch_yaw_rot.length_squared().powf(0.2) * 0.0005;
@ -563,14 +541,14 @@ pub fn apply_input_to_player(
} }
} }
let sinks = vec![ let sinks = vec![
(1.2, actor::EngineType::Monopropellant, sinks.get(&audio::Sfx::Thruster)), (1.2, actor::EngineType::Monopropellant, thruster_sound_controller.get_single()),
(1.0, actor::EngineType::Rocket, sinks.get(&audio::Sfx::Rocket)), (1.0, actor::EngineType::Rocket, rocket_sound_controller.get_single()),
(1.4, actor::EngineType::Ion, sinks.get(&audio::Sfx::Ion)), (1.4, actor::EngineType::Ion, ion_sound_controller.get_single()),
]; ];
let seconds_to_max_vol = 0.05; let seconds_to_max_vol = 0.05;
let seconds_to_min_vol = 0.05; let seconds_to_min_vol = 0.05;
for sink_data in sinks { for sink_data in sinks {
if let (vol_boost, engine_type, Some(sink)) = sink_data { if let (vol_boost, engine_type, Ok(sink)) = sink_data {
if settings.mute_sfx { if settings.mute_sfx {
sink.pause(); sink.pause();
} }
@ -605,7 +583,7 @@ pub fn update_map_only_object_visibility(
q_camera: Query<&Transform, With<Camera>>, q_camera: Query<&Transform, With<Camera>>,
q_player: Query<&Position, With<actor::PlayerCamera>>, q_player: Query<&Position, With<actor::PlayerCamera>>,
mut q_onlyinmap: Query<(&mut Visibility, &ShowOnlyInMap), Without<Camera>>, mut q_onlyinmap: Query<(&mut Visibility, &ShowOnlyInMap), Without<Camera>>,
id2pos: Res<game::Id2Pos>, id2pos: Res<actor::Id2Pos>,
) { ) {
if q_camera.is_empty() || q_player.is_empty() { if q_camera.is_empty() || q_player.is_empty() {
return; return;
@ -650,7 +628,7 @@ pub fn find_closest_target<TargetSpecifier>(
// not on the player mesh but on the camera, which doesn't have a position. // 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( let (angular_diameter, angle, distance) = calc_angular_diameter_known_target_vector(
trans, camera_transform, &target_vector); trans, camera_transform, &target_vector);
if angle <= angular_diameter.clamp(0.01, PI32) { if angle <= angular_diameter.clamp(0.01, PI) {
// It's in the field of view! // It's in the field of view!
//commands.entity(entity).insert(IsTargeted); //commands.entity(entity).insert(IsTargeted);
let distance_to_surface = distance - trans.scale.x; let distance_to_surface = distance - trans.scale.x;
@ -694,30 +672,3 @@ pub fn calc_angular_diameter(
.normalize_or_zero(); .normalize_or_zero();
return calc_angular_diameter_known_target_vector(target, camera, &target_vector); return calc_angular_diameter_known_target_vector(target, camera, &target_vector);
} }
// An extension of bevy_xpbd_3d::plugins::position_to_transform that adjusts
// the rendering position to center entities at the player camera.
// This avoids rendering glitches when very far away from the origin.
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>>,
) {
let center: DVec3 = if settings.map_active {
mapcam.center
} else if let Ok(player_pos) = q_player.get_single() {
**player_pos
} else {
return;
};
for (mut transform, pos, rot) in &mut q_trans {
transform.translation = Vec3::new(
(pos.x - center.x) as f32,
(pos.y - center.y) as f32,
(pos.z - center.z) as f32,
);
transform.rotation = rot.as_quat();
}
}

View file

@ -11,8 +11,9 @@
// This module loads the chat definitions from the YAML files // This module loads the chat definitions from the YAML files
// and manages the flow of conversations. // and manages the flow of conversations.
use crate::prelude::*; use crate::{actor, audio, effects, hud, var, world};
use bevy::prelude::*; use bevy::prelude::*;
use bevy::math::DVec3;
use bevy_xpbd_3d::prelude::*; use bevy_xpbd_3d::prelude::*;
use serde_yaml::Value; use serde_yaml::Value;
use serde::Deserialize; use serde::Deserialize;
@ -618,7 +619,6 @@ pub fn handle_new_conversations(
mut er_conv: EventReader<StartConversationEvent>, mut er_conv: EventReader<StartConversationEvent>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>, mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_chatevent: EventWriter<ChatEvent>, mut ew_chatevent: EventWriter<ChatEvent>,
mut ew_achievement: EventWriter<game::AchievementEvent>,
chatdb: Res<ChatDB>, chatdb: Res<ChatDB>,
q_chats: Query<&Chat>, q_chats: Query<&Chat>,
time: Res<Time>, time: Res<Time>,
@ -630,9 +630,6 @@ pub fn handle_new_conversations(
} }
match (*chatdb).get_chat_by_id(&event.talker.chat_name) { match (*chatdb).get_chat_by_id(&event.talker.chat_name) {
Ok(chat_id) => { Ok(chat_id) => {
if let Some(name) = &event.talker.name {
ew_achievement.send(game::AchievementEvent::TalkTo(name.clone()));
}
let mut chat = Chat { let mut chat = Chat {
internal_id: chat_id, internal_id: chat_id,
position: vec![0], position: vec![0],
@ -705,9 +702,6 @@ pub fn handle_chat_events(
hud::LogLevel::Info => { hud::LogLevel::Info => {
log.info(message.into()); log.info(message.into());
} }
hud::LogLevel::Achievement => {
log.add(message.into(), "".into(), hud::LogLevel::Achievement);
}
hud::LogLevel::Warning => { hud::LogLevel::Warning => {
log.warning(message.into()); log.warning(message.into());
} }
@ -797,9 +791,8 @@ pub fn handle_chat_scripts(
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 q_playercam: Query<(&mut Position, &mut LinearVelocity), With<actor::PlayerCamera>>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>, mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_effect: EventWriter<visual::SpawnEffectEvent>, mut ew_effect: EventWriter<effects::SpawnEffectEvent>,
mut ew_achievement: EventWriter<game::AchievementEvent>, id2pos: Res<actor::Id2Pos>,
id2pos: Res<game::Id2Pos>,
) { ) {
for script in er_chatscript.read() { for script in er_chatscript.read() {
// Parse the script string // Parse the script string
@ -846,7 +839,6 @@ pub fn handle_chat_scripts(
error!("Invalid parameter for command `{}`: `{}`", name, param1); error!("Invalid parameter for command `{}`: `{}`", name, param1);
} }
"repairsuit" => { "repairsuit" => {
ew_achievement.send(game::AchievementEvent::RepairSuit);
for (_, mut suit, _) in q_player.iter_mut() { for (_, mut suit, _) in q_player.iter_mut() {
suit.integrity = 1.0; suit.integrity = 1.0;
} }
@ -879,21 +871,18 @@ pub fn handle_chat_scripts(
gforce.ignore_gforce_seconds = 1.0; gforce.ignore_gforce_seconds = 1.0;
} }
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::WakeUp)); ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::WakeUp));
ew_effect.send(visual::SpawnEffectEvent { ew_effect.send(effects::SpawnEffectEvent {
class: visual::Effects::FadeIn(Color::CYAN), class: effects::Effects::FadeIn(Color::CYAN),
duration: 1.0, duration: 1.0,
}); });
} }
} }
"cryofadeout" => { "cryofadeout" => {
ew_effect.send(visual::SpawnEffectEvent { ew_effect.send(effects::SpawnEffectEvent {
class: visual::Effects::FadeOut(Color::CYAN), class: effects::Effects::FadeOut(Color::CYAN),
duration: 5.1, duration: 5.1,
}); });
} }
"drinkpizza" => {
ew_achievement.send(game::AchievementEvent::DrinkPizza);
}
_ => { _ => {
error!("Error, undefined chat script {name}"); error!("Error, undefined chat script {name}");
} }

View file

@ -74,6 +74,10 @@
- Micros? What's that?: - Micros? What's that?:
- Micrometeorites. Those tiny 混蛋 that fly right through you, leaving holes in your suit. And your body. - Micrometeorites. Those tiny 混蛋 that fly right through you, leaving holes in your suit. And your body.
- goto: help - goto: help
- What year is this?:
- Oh, is your Augmented Reality deactivated?
- Push the TAB button, your space suit's AR will show you the date and time.
- goto: help
- Why am I here?: - Why am I here?:
- That's a very philosophical question. - That's a very philosophical question.
- I don't know. - I don't know.
@ -86,7 +90,7 @@
- I'm here mostly for the view and the peace. - I'm here mostly for the view and the peace.
- Just look at Jupiter, it's mesmerizing, isn't it? - Just look at Jupiter, it's mesmerizing, isn't it?
- So far away from everything, nobody expects anything from you. - So far away from everything, nobody expects anything from you.
- If you want, you can take my sports cruiser for a ride. It's right over there. - If you want, you can take my sports racing capsule MeteorAceGT™ for a ride. It's right over there.
- It rides like a punch in the face, don't hurt yourself, ok? - It rides like a punch in the face, don't hurt yourself, ok?
- You're too kind!: - You're too kind!:
- Ah, don't mention it! - Ah, don't mention it!
@ -206,31 +210,27 @@
- if: $knows-menu - if: $knows-menu
I'd like a Suspicious Spacefunghi: I'd like a Suspicious Spacefunghi:
- Coming right up your feeding tube! - Coming right up your feeding tube!
- script: drinkpizza
- system: Received Suspicious Spacefunghi pizza smoothie - system: Received Suspicious Spacefunghi pizza smoothie
- goto: served - goto: served
- if: $knows-menu - if: $knows-menu
I'd like a Daring Durian: I'd like a Daring Durian:
- Coming right up your feeding tube! - Coming right up your feeding tube!
- script: drinkpizza
- system: Received Daring Durian pizza smoothie - system: Received Daring Durian pizza smoothie
- goto: served - goto: served
- if: $knows-menu - if: $knows-menu
I'd like an Artichoke Apple Pie pizza: I'd like an Artichoke Apple Pie pizza:
- Coming right up your feeding tube! - Coming right up your feeding tube!
- script: drinkpizza
- system: Received Artichoke Apple Pie pizza smoothie - system: Received Artichoke Apple Pie pizza smoothie
- goto: served - goto: served
- if: $knows-pineapple - if: $knows-pineapple
I'd like a pineapple pizza: I'd like a pineapple pizza:
- Coming right up your feeding tube! - Coming right up your feeding tube!
- script: drinkpizza
- system: Received pineapple pizza smoothie - system: Received pineapple pizza smoothie
- goto: served - goto: served
- if: $knows-coffee - if: $knows-coffee
I'd like a cup of that legendary Old Earth Soykaf, please: I'd like a cup of that legendary Old Earth Coffee, please:
- Coming right up your feeding tube! - Coming right up your feeding tube!
- system: Received Old Earth Soykaf - system: Received Old Earth Coffee
- goto: served - goto: served
- Surprise me.: - Surprise me.:
- Hmm... - Hmm...
@ -257,7 +257,7 @@
- goto: served - goto: served
- Got any coffee?: - Got any coffee?:
- Your suit should have a coffee dispenser built right into it. - Your suit should have a coffee dispenser built right into it.
- Naturally, it's not as good as my legendary Old Earth Soykaf! - Naturally, it's not as good as my legendary Old Earth Coffee!
- set: knows-coffee - set: knows-coffee
- goto: non-pizza - goto: non-pizza
- Can't think of anything right now.: - Can't think of anything right now.:
@ -285,30 +285,16 @@
--- ---
- chat: Ash
- Oh, hello!
- Look, I'm very busy right now constructing this place.
- We can talk more once this is done.
- See you around.
---
- chat: River
- Welcome to our little oasis!
- This will be great once it's finished.
---
- chat: generic_questions_serenity - chat: generic_questions_serenity
- Where are we?: - Where are we?:
- Inside Jupiter's rings, obviously. - Inside Jupiter's rings, obviously.
- We're about 150,000km away from the gas giant. - We're about 150,000km away from the gas giant.
- This region is called Serenity by its inhabitants, due to the relative safety from Jupiter's magnetic field and the micros. - This region is called Serenity by its inhabitants, due to the relative safety from Jupiter's magnetic field and the micros.
- goto: generic_questions - goto: generic_questions
- What time is it?:
- Oh, is your Augmented Reality deactivated?
- Push the TAB button, your space suit's AR will show you the date and time.
- goto: generic_questions
- I think I'm good for now.: [] - I think I'm good for now.: []

View file

@ -13,21 +13,19 @@
extern crate regex; extern crate regex;
use bevy::prelude::*; use bevy::prelude::*;
use bevy_xpbd_3d::prelude::*; use bevy_xpbd_3d::prelude::*;
use bevy::math::DVec3;
use bevy::pbr::{NotShadowCaster, NotShadowReceiver}; use bevy::pbr::{NotShadowCaster, NotShadowReceiver};
use crate::prelude::*; use crate::{actor, camera, chat, hud, nature, shading, skeleton, var, world};
use regex::Regex; use regex::Regex;
use std::f32::consts::PI;
use std::f64::consts::PI as PI64;
use std::time::SystemTime; use std::time::SystemTime;
pub const ID_EARTH: &str = "earth"; pub struct CommandsPlugin;
pub const ID_SOL: &str = "sol"; impl Plugin for CommandsPlugin {
pub const ID_JUPITER: &str = "jupiter";
pub struct CmdPlugin;
impl Plugin for CmdPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Startup, load_defs); app.add_systems(Startup, load_defs);
app.add_systems(Update, spawn_entities); app.add_systems(Update, spawn_entities);
app.add_systems(Update, process_mesh);
app.add_systems(PreUpdate, hide_colliders app.add_systems(PreUpdate, hide_colliders
.run_if(any_with_component::<NeedsSceneColliderRemoved>)); .run_if(any_with_component::<NeedsSceneColliderRemoved>));
app.add_event::<SpawnEvent>(); app.add_event::<SpawnEvent>();
@ -57,7 +55,6 @@ struct ParserState {
model: Option<String>, model: Option<String>,
model_scale: f32, model_scale: f32,
rotation: Quat, rotation: Quat,
axialtilt: f32,
velocity: DVec3, velocity: DVec3,
angular_momentum: DVec3, angular_momentum: DVec3,
pronoun: Option<String>, pronoun: Option<String>,
@ -71,7 +68,6 @@ struct ParserState {
is_targeted_on_startup: bool, is_targeted_on_startup: bool,
is_sun: bool, is_sun: bool,
is_moon: bool, is_moon: bool,
is_planet: bool,
is_point_of_interest: bool, is_point_of_interest: bool,
orbit_distance: Option<f64>, orbit_distance: Option<f64>,
orbit_object_id: Option<String>, orbit_object_id: Option<String>,
@ -113,7 +109,6 @@ impl Default for ParserState {
model: None, model: None,
model_scale: 1.0, model_scale: 1.0,
rotation: Quat::IDENTITY, rotation: Quat::IDENTITY,
axialtilt: 0.0,
velocity: DVec3::splat(0.0), velocity: DVec3::splat(0.0),
angular_momentum: DVec3::new(0.03, 0.3, 0.09), angular_momentum: DVec3::new(0.03, 0.3, 0.09),
pronoun: None, pronoun: None,
@ -127,7 +122,6 @@ impl Default for ParserState {
is_targeted_on_startup: false, is_targeted_on_startup: false,
is_sun: false, is_sun: false,
is_moon: false, is_moon: false,
is_planet: false,
is_point_of_interest: false, is_point_of_interest: false,
orbit_distance: None, orbit_distance: None,
orbit_object_id: None, orbit_object_id: None,
@ -253,7 +247,7 @@ pub fn load_defs(
["orbit", radius_str, phase_str] => { ["orbit", radius_str, phase_str] => {
if let (Ok(r), Ok(phase)) = (radius_str.parse::<f64>(), phase_str.parse::<f64>()) { if let (Ok(r), Ok(phase)) = (radius_str.parse::<f64>(), phase_str.parse::<f64>()) {
state.orbit_distance = Some(r); state.orbit_distance = Some(r);
state.orbit_phase = Some(phase * PI * 2.0); state.orbit_phase = Some(phase * PI64 * 2.0);
} }
else { else {
error!("Can't parse float: {line}"); error!("Can't parse float: {line}");
@ -262,7 +256,7 @@ pub fn load_defs(
} }
["orbit_phase_offset", value] => { ["orbit_phase_offset", value] => {
if let Ok(value_float) = value.parse::<f64>() { if let Ok(value_float) = value.parse::<f64>() {
let offset_radians = 2.0 * PI * value_float; let offset_radians = 2.0 * PI64 * value_float;
if let Some(phase_radians) = state.orbit_phase { if let Some(phase_radians) = state.orbit_phase {
state.orbit_phase = Some(phase_radians + offset_radians); state.orbit_phase = Some(phase_radians + offset_radians);
} }
@ -295,9 +289,6 @@ pub fn load_defs(
["moon", "yes"] => { ["moon", "yes"] => {
state.is_moon = true; state.is_moon = true;
} }
["planet", "yes"] => {
state.is_planet = true;
}
["sun", "yes"] => { ["sun", "yes"] => {
state.is_sun = true; state.is_sun = true;
} }
@ -335,7 +326,7 @@ pub fn load_defs(
} }
["rotationx", rotation_x] => { ["rotationx", rotation_x] => {
if let Ok(rotation_x_float) = rotation_x.parse::<f32>() { if let Ok(rotation_x_float) = rotation_x.parse::<f32>() {
state.rotation *= Quat::from_rotation_x(rotation_x_float.to_radians()); state.rotation *= Quat::from_rotation_x(PI * rotation_x_float);
} }
else { else {
error!("Can't parse float: {line}"); error!("Can't parse float: {line}");
@ -344,7 +335,7 @@ pub fn load_defs(
} }
["rotationy", rotation_y] => { ["rotationy", rotation_y] => {
if let Ok(rotation_y_float) = rotation_y.parse::<f32>() { if let Ok(rotation_y_float) = rotation_y.parse::<f32>() {
state.rotation *= Quat::from_rotation_y(rotation_y_float.to_radians()); state.rotation *= Quat::from_rotation_y(PI * rotation_y_float);
} }
else { else {
error!("Can't parse float: {line}"); error!("Can't parse float: {line}");
@ -353,17 +344,7 @@ pub fn load_defs(
} }
["rotationz", rotation_z] => { ["rotationz", rotation_z] => {
if let Ok(rotation_z_float) = rotation_z.parse::<f32>() { if let Ok(rotation_z_float) = rotation_z.parse::<f32>() {
state.rotation *= Quat::from_rotation_z(rotation_z_float.to_radians()); state.rotation *= Quat::from_rotation_z(PI * rotation_z_float);
}
else {
error!("Can't parse float: {line}");
continue;
}
}
["axialtilt", rotation_y] => {
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}"); error!("Can't parse float: {line}");
@ -531,14 +512,12 @@ fn spawn_entities(
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
mut meshes: ResMut<Assets<Mesh>>, mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>, mut materials: ResMut<Assets<StandardMaterial>>,
mut materials_jupiter: ResMut<Assets<load::JupitersRing>>, mut materials_jupiter: ResMut<Assets<shading::JupitersRing>>,
mut id2pos: ResMut<game::Id2Pos>, mut id2pos: ResMut<actor::Id2Pos>,
mut achievement_tracker: ResMut<var::AchievementTracker>,
settings: Res<var::Settings>, settings: Res<var::Settings>,
) { ) {
for state_wrapper in er_spawn.read() { for state_wrapper in er_spawn.read() {
let state = &state_wrapper.0; let state = &state_wrapper.0;
let mut rotation = state.rotation;
if state.class == DefClass::Actor { if state.class == DefClass::Actor {
// Preprocessing // Preprocessing
let mut absolute_pos = if let Some(id) = &state.relative_to { let mut absolute_pos = if let Some(id) = &state.relative_to {
@ -562,7 +541,6 @@ fn spawn_entities(
if let Some(id) = &state.orbit_object_id { if let Some(id) = &state.orbit_object_id {
let mass = match id.as_str() { let mass = match id.as_str() {
"jupiter" => nature::JUPITER_MASS, "jupiter" => nature::JUPITER_MASS,
"sol" => nature::JUPITER_MASS,
_ => { _ => {
error!("Found no mass for object `{id}`"); error!("Found no mass for object `{id}`");
continue; continue;
@ -570,8 +548,7 @@ fn spawn_entities(
}; };
let orbital_period = nature::simple_orbital_period(mass, r); 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 PI64 * 2.0 * (epoch.as_secs_f64() % orbital_period) / orbital_period
PI * 2.0 * (now % orbital_period) / orbital_period
} else { } else {
error!("Can't determine current time `{id}`"); error!("Can't determine current time `{id}`");
0.0 0.0
@ -601,13 +578,13 @@ fn spawn_entities(
actor.insert(world::DespawnOnPlayerDeath); actor.insert(world::DespawnOnPlayerDeath);
actor.insert(actor::HitPoints::default()); actor.insert(actor::HitPoints::default());
actor.insert(Position::from(absolute_pos)); actor.insert(Position::from(absolute_pos));
actor.insert(Rotation::from(state.rotation));
if state.is_sphere { if state.is_sphere {
let sphere_texture_handle = if let Some(model) = &state.model { let sphere_texture_handle = if let Some(model) = &state.model {
Some(asset_server.load(format!("textures/{}.jpg", model))) Some(asset_server.load(format!("textures/{}.jpg", model)))
} else { } else {
None 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_handle = meshes.add(Sphere::new(1.0).mesh().uv(128, 128));
let sphere_material_handle = materials.add(StandardMaterial { let sphere_material_handle = materials.add(StandardMaterial {
base_color_texture: sphere_texture_handle, base_color_texture: sphere_texture_handle,
@ -626,9 +603,8 @@ fn spawn_entities(
transform: Transform::from_scale(scale), transform: Transform::from_scale(scale),
..default() ..default()
}); });
load_asset(model.as_str(), &mut actor, &*asset_server); skeleton::load(model.as_str(), &mut actor, &*asset_server);
} }
actor.insert(Rotation::from(rotation));
// Physics Parameters // Physics Parameters
if state.has_physics { if state.has_physics {
@ -699,7 +675,6 @@ fn spawn_entities(
integrity: state.suit_integrity, integrity: state.suit_integrity,
..default() ..default()
}); });
actor.insert(actor::Battery::default());
} }
if state.is_clickable { if state.is_clickable {
actor.insert(hud::IsClickable { actor.insert(hud::IsClickable {
@ -739,22 +714,15 @@ fn spawn_entities(
pronoun: state.pronoun.clone(), pronoun: state.pronoun.clone(),
talking_speed: 1.0, talking_speed: 1.0,
}); });
if let Some(name) = &state.name {
achievement_tracker.all_people.insert(name.clone());
}
} }
if state.is_vehicle { if state.is_vehicle {
actor.insert(actor::Vehicle::default()); actor.insert(actor::Vehicle::default());
if let Some(name) = &state.name {
achievement_tracker.all_vehicles.insert(name.clone());
}
} }
if state.is_vehicle if state.is_vehicle || state.is_suited
|| state.is_suited || state.thrust_forward > 0.0
|| state.thrust_forward > 0.0 || state.thrust_sideways > 0.0
|| state.thrust_sideways > 0.0 || state.thrust_back > 0.0
|| state.thrust_back > 0.0 || state.reaction_wheels > 0.0
|| state.reaction_wheels > 0.0
{ {
actor.insert(actor::Engine { actor.insert(actor::Engine {
thrust_forward: state.thrust_forward, thrust_forward: state.thrust_forward,
@ -769,32 +737,6 @@ fn spawn_entities(
if let Some(_) = state.ar_model { if let Some(_) = state.ar_model {
actor.insert(hud::AugmentedRealityOverlayBroadcaster); 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(); actor_entity = actor.id();
} }
@ -811,10 +753,10 @@ fn spawn_entities(
NotShadowCaster, NotShadowCaster,
NotShadowReceiver, NotShadowReceiver,
)); ));
load_asset(ar_asset_name, &mut entitycmd, &*asset_server); skeleton::load(ar_asset_name, &mut entitycmd, &*asset_server);
} }
if state.is_point_of_interest || state.is_moon || state.is_planet { if state.is_point_of_interest {
let mut entitycmd = commands.spawn(( let mut entitycmd = commands.spawn((
hud::PointOfInterestMarker(actor_entity), hud::PointOfInterestMarker(actor_entity),
world::DespawnOnPlayerDeath, world::DespawnOnPlayerDeath,
@ -826,23 +768,15 @@ fn spawn_entities(
NotShadowCaster, NotShadowCaster,
NotShadowReceiver, NotShadowReceiver,
)); ));
let model = if state.is_point_of_interest { skeleton::load("point_of_interest", &mut entitycmd, &*asset_server);
"point_of_interest"
} else if state.is_planet {
"marker_planets"
} else {
"marker_satellites"
};
load_asset(model, &mut entitycmd, &*asset_server);
} }
if state.has_ring { if state.has_ring {
let ring_radius = state.model_scale * (nature::JUPITER_RING_RADIUS / nature::JUPITER_RADIUS) as f32;
commands.spawn(( commands.spawn((
world::DespawnOnPlayerDeath, world::DespawnOnPlayerDeath,
MaterialMeshBundle { MaterialMeshBundle {
mesh: meshes.add(Mesh::from(Cylinder::new(ring_radius, 1.0))), mesh: meshes.add(Mesh::from(Cylinder::new(nature::JUPITER_RING_RADIUS as f32, 1.0))),
material: materials_jupiter.add(load::JupitersRing { material: materials_jupiter.add(shading::JupitersRing {
alpha_mode: AlphaMode::Blend, alpha_mode: AlphaMode::Blend,
ring_radius: nature::JUPITER_RING_RADIUS as f32, ring_radius: nature::JUPITER_RING_RADIUS as f32,
jupiter_radius: nature::JUPITER_RADIUS as f32, jupiter_radius: nature::JUPITER_RADIUS as f32,
@ -851,7 +785,8 @@ fn spawn_entities(
..default() ..default()
}, },
Position::new(absolute_pos), Position::new(absolute_pos),
Rotation::from(Quat::from_rotation_z(-state.axialtilt.to_radians())), Rotation::from(Quat::IDENTITY),
//Rotation::from(Quat::from_rotation_x(-0.3f32.to_radians())),
NotShadowCaster, NotShadowCaster,
NotShadowReceiver, NotShadowReceiver,
)); ));
@ -867,38 +802,3 @@ 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>)>,
) {
// Add "PlayerCollider" component to the player's collider mesh entity
for (child_entity, child_name, child_parent) in &mut q_mesh {
// get the root parent
let mut get_parent = q_parents.get(child_parent.get());
while let Ok((parent_maybe, _, _, _)) = get_parent {
if let Some(parent) = parent_maybe {
get_parent = q_parents.get(parent.get());
} else {
break;
}
}
if let Ok((_, player, noshadowcast, noshadowrecv)) = get_parent {
let childcmd = &mut commands.entity(child_entity);
// If the root parent is the player, add PlayerCollider to the collider mesh
if player.is_some() && child_name.as_str() == "Collider" {
childcmd.insert(actor::PlayerCollider);
}
if noshadowcast.is_some() {
childcmd.insert(NotShadowCaster);
}
if noshadowrecv.is_some() {
childcmd.insert(NotShadowReceiver);
}
}
}
}

View file

@ -1,107 +0,0 @@
// ▄████████▄ + ███ + ▄█████████ ███ +
// ███▀ ▀███ + + ███ ███▀ + ███ + +
// ███ + ███ ███ ███ █████████ ███ ███ ███ ███
// ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
// ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
// ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
// ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
// + + + ███
// + ▀████████████████████████████████████████████████████▀
//
// Various common functions and constants
use bevy::prelude::*;
use crate::prelude::*;
pub use bevy::math::{DVec3, DQuat};
pub use std::f32::consts::PI as PI32;
pub use std::f64::consts::PI;
pub const GAME_NAME: &str = "OutFly";
pub const CONF_FILE: &str = "outfly.toml";
pub const FONT: &str = "fonts/Yupiter-Regular.ttf";
pub const EPSILON32: f32 = 1e-9;
pub const EPSILON: f64 = 1e-9;
pub const COLOR_BODY: &str = "#888888"; // For simple text
pub const COLOR_DIM: &str = "#666666"; // For darker, less important text
pub const COLOR_PRIMARY: &str = "#BE1251"; // The "branding" color
pub const COLOR_SECONDARY: &str = "#CCCCCC"; // For accents
pub const COLOR_SUCCESS: &str = "#F0D50C"; // For positive outcomes
pub const COLOR_DANGER: &str = "#CCCCCC"; // For critical situations
pub const COLOR_WARNING: &str = "#F0D50C"; // For warnings
#[inline]
pub fn bool2vis(boolean: bool) -> Visibility {
if boolean {
Visibility::Inherited
} else {
Visibility::Hidden
}
}
#[inline]
pub fn style_fullscreen() -> 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()
}
}
#[inline]
pub fn style_centered() -> Style {
Style {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
align_items: AlignItems::Center,
justify_content: JustifyContent::SpaceAround,
..default()
}
}
pub fn alive(settings: Res<Settings>) -> bool {
return settings.alive;
}
pub fn in_control(settings: Res<Settings>) -> bool {
return settings.in_control();
}
pub fn in_shadow(
light_source_pos: DVec3,
light_source_r: f64,
shadow_caster_pos: DVec3,
shadow_caster_r: f64,
camera_pos: DVec3
) -> bool {
if light_source_r < shadow_caster_r {
error!("common::in_shadow only works if light_source_r > shadow_caster_r");
return false;
}
let direction = (shadow_caster_pos - light_source_pos).normalize();
let shadow_caster_to_player = camera_pos - shadow_caster_pos;
// Calculate the distance to the shadow caster if the player was projected
// onto the line of the shadow, with positive numbers = in the shadow,
// negative numbers = in between light source and shadow caster.
let projection_length = shadow_caster_to_player.dot(direction);
if projection_length < 0.0 {
return false;
}
let projection = projection_length * direction;
// Calculate the vector to the closest point along the shadow line
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;
return closest_vector.length() < max_distance;
}

View file

@ -1,204 +0,0 @@
Though I heard
Everyone goes this road
Eventually
I didn't expect that
I'd be on it yesterday or today
- Ariwara no Narihira, 880
A flower of Sponge cucumber blooms
Phlegm gets caught
In the dead's throat
- Masaoka Shiki, 1902
There is no death;
there is no life.
Indeed, the skies are cloudless
And the river waters clear.
- Toshimoto, Taiheiki
I wish to die
in spring, beneath
the cherry blossoms,
while the springtime moon
is full.
- Saigyo, 1190
Illusion appears, illusion ceases
The biggest illusion among all is our body
Once a pacified heart finds its place
There's no such body to look for
- Zheng Ting, 621
Empty-handed I entered the world
Barefoot I leave it.
My coming, my going —
Two simple happenings
That got entangled.
- Kozan Ichikyo, 1360
Bury me when I die
beneath a wine barrel
in a tavern.
With luck
the cask will leak.
- Moriya Sen'an 1838
I wish I could enjoy,
the rest of Spring,
as the cherry blossoms are yet in bloom,
in spite of the spring breeze
which is attempting to blow off all their petals.
Asano Naganori, 1701
To be saved from the chasm
why cling to the cliff?
Clouds floating low never know where the breezes will blow them.
- Sengai Gibon, 1837
Flash of steel stills me;
calmness mirrors the ocean;
I await the waves.
*
The death of blossoms;
is not something to grieve on;
but the way of things.
- Asakura Sōteki, 1555
The glory and prosperity of my life was as good as a single cup of sake.
My life of forty-nine years is passed like a dream.
I know not what life is; nor death.
Both Heaven and Hell are left behind.
I stand in the moonlit dawn; free from clouds of attachment.
- Uesugi Kenshin, 1578
My whole life long I've sharpened my sword
And now, face to face with death
I unsheathe it, and lo-
The blade is broken-
Alas!
Dairin Soto, 1568
I borrow moonlight
for this journey of a
million miles
- Saikaku, 1730
Not knowing
that my body lies
upon Mount Kamo's rocks,
my love
awaits me.
- Kakinomoto no Hitomaro, 707
Overtaken by darkness
I will lodge under
the boughs of a tree.
Flowers alone
host me tonight.
- Taira no Tadanori, 1184
Like a rotten log
half-buried in the ground-
my life, which
has not flowered, comes
to this sad end.
- Minamoto no Yorimasa, 1180
Had I not known
that I was dead
already
I would have mourned
my loss of life.
- Ota Dokan, 1486
Since I was born
I have to die,
and so ...
- Kisei, 1764
Till now I thought
that death befell
the untalented alone.
If those with talent, too, must die
surely they make a better manure?
- Morikawa Kyoriku, 1715
One leaf lets go, and
then another takes
the wind.
- Ransetsu, 1707
I cleansed the mirror
of my heart - now it reflects
the moon.
- Renseki, 1789
Let them bloom or
let them die-it's all the same:
cherry trees on Mount Yoshino.
- Rekisen, 1834
Depths of cold
unfathomable
ocean roar.
- Kasenjo, 1776
A tune of non-being
Filling the void:
Spring sun
Snow whiteness
Bright clouds
Clear wind.
- Daido Ichi'i, 1370
I raise the mirror of my life
Up to my face: sixty years.
With a swing I smash the reflection-
The world as usual
All in its place.
- Taigen Sofu, 1555
I cast the brush aside-
from here on I'll speak to the moon
face to face.
- Koha, 1897
The joy of dewdrops
in the grass as they
turn back to vapor.
- Koraku, 1837
The foam on the last water
has dissolved
my mind is clear.
- Mitoku, 1669
Still tied to the world,
I cool off and lose
my form.
- Ozui, 1783
Festival of Souls:
yesterday I hosted them
today I am a guest ...
- Sofu, 1891
Spitting blood
clears up reality
and dream alike.
- Sunao, 1926
I go back
to the void where frost and snow
won't bother me.
- Tojaku, 1799
My life was
lunacy until
this moonlit night.
- Tokugen, 1647
The owner of the cherry blossoms
turns to compost
for the trees.
- Utsu, 1863

File diff suppressed because it is too large Load diff

View file

@ -1,24 +1,22 @@
Space: Slow down, match velocity Space: Slow down (or match velocity)
E: Interact AWSD/Shift/Ctrl: Movement
F: Flashlight R: Rotate (hold + move mouse)
E: Interact: Talk to people, enter vehicles
Q: Exit vehicle Q: Exit vehicle
M: Map M: Map
C: Camera Tab: Toggle HUD + Augmented Reality
T: Cruise control Left click: Target objects [AUGMENTED REALITY ONLY]
R: Rotate (hold + move mouse) Right click: Zoom [AUGMENTED REALITY ONLY]
F: 3rd person view
Y: Rotation stabilizer Y: Rotation stabilizer
AWSD/Shift/Ctrl: Move F2: Toggle shadows
J/K/U/L/I/O: Rotate F3: Toggle sound effects
F11: Fullscreen F4: Toggle music
Tab: Toggle Augmented Reality F7: Restart game
F11: Toggle fullscreen
Augmented Reality only: JKULIO: Mouseless camera rotation
Left click: Target objects G: Toggle god mode + cheats [CHEAT]
Right click: Zoom V/B: Impossible acceleration forward/backward [CHEAT]
Shift+V/B: Same as V/B, but a thousand times faster [CHEAT]
Cheats: C: Impossibly instant stopping [CHEAT]
G: Toggle cheats + invulnerability X: Teleport to target [CHEAT]
V/B: Impossible acceleration
Shift+V/B: Extreme acceleration
X: Teleport to target
Z: Stop

View file

@ -11,18 +11,17 @@
// This module manages visual effects. // This module manages visual effects.
use bevy::prelude::*; use bevy::prelude::*;
use crate::prelude::*; use crate::{camera, var};
pub struct VisualPlugin; pub struct EffectsPlugin;
impl Plugin for VisualPlugin { impl Plugin for EffectsPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Startup, setup.after(menu::setup).after(hud::setup)); app.add_systems(Startup, 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, spawn_effects);
app.add_systems(Update, update_fadein); app.add_systems(Update, update_fadein);
app.add_systems(Update, update_fadeout); app.add_systems(Update, update_fadeout);
app.add_systems(Update, play_animations);
// Blackout disabled for now // Blackout disabled for now
//app.add_systems(Update, update_blackout); //app.add_systems(Update, update_blackout);
app.add_event::<SpawnEffectEvent>(); app.add_event::<SpawnEffectEvent>();
@ -94,7 +93,14 @@ pub fn spawn_effects(
}, },
FadeIn, FadeIn,
NodeBundle { NodeBundle {
style: style_fullscreen(), 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.into(), background_color: color.into(),
..default() ..default()
}, },
@ -109,7 +115,14 @@ pub fn spawn_effects(
}, },
FadeOut, FadeOut,
NodeBundle { NodeBundle {
style: style_fullscreen(), 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.with_a(0.0).into(), background_color: color.with_a(0.0).into(),
..default() ..default()
}, },
@ -152,16 +165,6 @@ pub fn update_fadeout(
} }
} }
fn play_animations(
mut players: Query<&mut AnimationPlayer, Added<AnimationPlayer>>,
asset_server: Res<AssetServer>,
) {
for mut player in &mut players {
let animation = asset_server.load("models/suit_v2/suit_v2.glb#Animation0");
player.play(animation.clone()).repeat();
}
}
// Blackout disabled for now // Blackout disabled for now
//pub fn update_blackout( //pub fn update_blackout(
// mut q_effect: Query<&mut BackgroundColor, With<BlackOutOverlay>>, // mut q_effect: Query<&mut BackgroundColor, With<BlackOutOverlay>>,

View file

@ -1,429 +0,0 @@
// ▄████████▄ + ███ + ▄█████████ ███ +
// ███▀ ▀███ + + ███ ███▀ + ███ + +
// ███ + ███ ███ ███ █████████ ███ ███ ███ ███
// ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
// ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
// ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
// ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
// + + + ███
// + ▀████████████████████████████████████████████████████▀
//
// This module handles player input, and coordinates interplay between other modules
use crate::prelude::*;
use bevy::prelude::*;
use bevy::pbr::ExtendedMaterial;
use bevy::scene::SceneInstance;
use bevy::window::{Window, WindowMode, PrimaryWindow};
use bevy_xpbd_3d::prelude::*;
use std::collections::HashMap;
pub struct GamePlugin;
impl Plugin for GamePlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, handle_cheats.run_if(in_control));
app.add_systems(Update, debug);
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, 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.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 enum AchievementEvent {
RepairSuit,
TalkTo(String),
RideVehicle(String),
DrinkPizza,
InJupitersShadow,
FindEarth,
}
#[derive(Event)]
pub enum GameEvent {
SetAR(Turn),
SetMusic(Turn),
SetSound(Turn),
SetMap(Turn),
SetFullscreen(Turn),
SetMenu(Turn),
SetThirdPerson(Turn),
SetRotationStabilizer(Turn),
SetShadows(Turn),
Achievement(String),
}
pub enum Turn {
On,
Off,
Toggle,
}
impl Turn {
pub fn to_bool(&self, current_state: bool) -> bool {
match self {
Turn::On => true,
Turn::Off => false,
Turn::Toggle => !current_state,
}
}
}
pub fn handle_game_event(
mut settings: ResMut<Settings>,
mut er_game: EventReader<GameEvent>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_updateoverlays: EventWriter<hud::UpdateOverlayVisibility>,
mut ew_updatemenu: EventWriter<menu::UpdateMenuEvent>,
mut ew_togglemusic: EventWriter<audio::ToggleMusicEvent>,
mut q_window: Query<&mut Window, With<PrimaryWindow>>,
mut q_light: Query<&mut DirectionalLight>,
mut mapcam: ResMut<camera::MapCam>,
mut log: ResMut<hud::Log>,
opt: Res<var::CommandLineOptions>,
) {
for event in er_game.read() {
match event {
GameEvent::SetAR(turn) => {
settings.hud_active = turn.to_bool(settings.hud_active);
ew_togglemusic.send(audio::ToggleMusicEvent());
ew_updateoverlays.send(hud::UpdateOverlayVisibility);
}
GameEvent::SetMusic(turn) => {
// TODO invert "mute_music" to "music_active"
settings.mute_music = turn.to_bool(settings.mute_music);
ew_togglemusic.send(audio::ToggleMusicEvent());
}
GameEvent::SetSound(turn) => {
// TODO invert "mute_sfx" to "sfx_active"
settings.mute_sfx = turn.to_bool(settings.mute_sfx);
ew_togglemusic.send(audio::ToggleMusicEvent());
}
GameEvent::SetMap(turn) => {
settings.map_active = turn.to_bool(settings.map_active);
if settings.map_active {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Woosh));
}
*mapcam = camera::MapCam::default();
ew_updateoverlays.send(hud::UpdateOverlayVisibility);
}
GameEvent::SetFullscreen(turn) => {
for mut window in &mut q_window {
let current_state = window.mode != WindowMode::Windowed;
window.mode = match turn.to_bool(current_state) {
true => opt.window_mode_fullscreen,
false => WindowMode::Windowed
};
}
}
GameEvent::SetMenu(turn) => {
settings.menu_active = turn.to_bool(settings.menu_active);
ew_updatemenu.send(menu::UpdateMenuEvent);
}
GameEvent::SetThirdPerson(turn) => {
settings.third_person = turn.to_bool(settings.third_person);
}
GameEvent::SetRotationStabilizer(turn) => {
settings.rotation_stabilizer_active
= turn.to_bool(settings.rotation_stabilizer_active);
}
GameEvent::SetShadows(turn) => {
settings.shadows_sun = turn.to_bool(settings.shadows_sun);
for mut light in &mut q_light {
light.shadows_enabled = settings.shadows_sun;
}
}
GameEvent::Achievement(name) => {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Achieve));
log.add(format!("Achievement accomplished: {name}!"),
"".to_string(), hud::LogLevel::Achievement);
}
}
}
}
fn handle_player_death(
mut cmd: Commands,
mut er_playerdies: EventReader<PlayerDiesEvent>,
q_scenes: Query<(Entity, &SceneInstance), With<world::DespawnOnPlayerDeath>>,
q_noscenes: Query<Entity, (With<world::DespawnOnPlayerDeath>, Without<SceneInstance>)>,
mut scene_spawner: ResMut<SceneSpawner>,
mut active_asteroids: ResMut<world::ActiveAsteroids>,
mut ew_effect: EventWriter<visual::SpawnEffectEvent>,
mut ew_deathscreen: EventWriter<menu::DeathScreenEvent>,
mut log: ResMut<hud::Log>,
mut settings: ResMut<Settings>,
) {
for death in er_playerdies.read() {
if settings.god_mode {
return;
}
settings.reset_player_settings();
active_asteroids.0.clear();
for entity in &q_noscenes {
cmd.entity(entity).despawn();
}
for (entity, sceneinstance) in &q_scenes {
cmd.entity(entity).despawn();
scene_spawner.despawn_instance(**sceneinstance);
}
log.clear();
match death.0 {
actor::DamageType::Depressurization => {
settings.death_cause = "Depressurization".to_string();
ew_effect.send(visual::SpawnEffectEvent {
class: visual::Effects::FadeIn(Color::BLACK),
duration: 4.0,
});
}
actor::DamageType::Mental => {
settings.death_cause = "Brain Damage".to_string();
ew_effect.send(visual::SpawnEffectEvent {
class: visual::Effects::FadeIn(Color::BLACK),
duration: 4.0,
});
}
actor::DamageType::Asphyxiation => {
settings.death_cause = "Suffocation".to_string();
ew_effect.send(visual::SpawnEffectEvent {
class: visual::Effects::FadeIn(Color::BLACK),
duration: 1.0,
});
}
actor::DamageType::Trauma => {
settings.death_cause = "Trauma".to_string();
ew_effect.send(visual::SpawnEffectEvent {
class: visual::Effects::FadeIn(Color::MAROON),
duration: 1.0,
});
}
actor::DamageType::GForce => {
settings.death_cause = "Trauma from excessive g forces".to_string();
ew_effect.send(visual::SpawnEffectEvent {
class: visual::Effects::FadeIn(Color::MAROON),
duration: 1.0,
});
}
_ => {
settings.death_cause = "Unknown".to_string();
ew_effect.send(visual::SpawnEffectEvent {
class: visual::Effects::FadeIn(Color::MAROON),
duration: 1.0,
});
}
}
ew_deathscreen.send(menu::DeathScreenEvent::Show);
return;
}
}
fn handle_cheats(
key_input: Res<ButtonInput<KeyCode>>,
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>)>,
mut ew_playerdies: EventWriter<PlayerDiesEvent>,
mut settings: ResMut<Settings>,
id2pos: Res<Id2Pos>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
) {
if q_player.is_empty() || q_life.is_empty() {
return;
}
let (trans, mut pos, mut v) = q_player.get_single_mut().unwrap();
let (mut lifeform, mut gforce) = q_life.get_single_mut().unwrap();
let boost = if key_input.pressed(KeyCode::ShiftLeft) {
1e6
} else {
1e3
};
if key_input.just_pressed(settings.key_cheat_god_mode) {
settings.god_mode ^= true;
if settings.god_mode {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::EnterVehicle));
}
}
if !settings.god_mode && !settings.dev_mode {
return;
}
if key_input.just_pressed(settings.key_cheat_stop) {
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) {
gforce.ignore_gforce_seconds = 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();
let avg_speed = (current_speed + next_speed) / 2.0;
let inv_lorentz = nature::inverse_lorentz_factor(avg_speed.clamp(0.0, nature::C));
v.0 = v.0 + inv_lorentz * dv;
}
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();
pos.0 = **target_pos + offset;
if let Some(target_v) = target_v {
*v = target_v.clone();
}
}
}
if !settings.dev_mode {
return;
}
if key_input.just_pressed(settings.key_cheat_pizza) {
if let Some(target) = id2pos.0.get(&"pizzeria".to_string()) {
pos.0 = *target + DVec3::new(-60.0, 0.0, 0.0);
gforce.ignore_gforce_seconds = 1.0;
}
}
if key_input.just_pressed(settings.key_cheat_farview1) {
if let Some(target) = id2pos.0.get(&"busstopclippy2".to_string()) {
pos.0 = *target + DVec3::new(0.0, -1000.0, 0.0);
gforce.ignore_gforce_seconds = 1.0;
}
}
if key_input.just_pressed(settings.key_cheat_farview2) {
if let Some(target) = id2pos.0.get(&"busstopclippy3".to_string()) {
pos.0 = *target + DVec3::new(0.0, -1000.0, 0.0);
gforce.ignore_gforce_seconds = 1.0;
}
}
if key_input.pressed(settings.key_cheat_adrenaline_zero) {
lifeform.adrenaline = 0.0;
}
if key_input.pressed(settings.key_cheat_adrenaline_mid) {
lifeform.adrenaline = 0.5;
}
if key_input.pressed(settings.key_cheat_adrenaline_max) {
lifeform.adrenaline = 1.0;
}
if key_input.just_pressed(settings.key_cheat_die) {
settings.god_mode = false;
ew_playerdies.send(PlayerDiesEvent(actor::DamageType::Trauma));
}
}
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);
}
}
fn debug(
settings: Res<var::Settings>,
keyboard_input: Res<ButtonInput<KeyCode>>,
mut commands: Commands,
mut extended_materials: ResMut<Assets<ExtendedMaterial<StandardMaterial, load::AsteroidSurface>>>,
mut achievement_tracker: ResMut<var::AchievementTracker>,
materials: Query<(Entity, Option<&Name>, &Handle<Mesh>)>,
) {
if settings.dev_mode && keyboard_input.just_pressed(KeyCode::KeyP) {
for (entity, _name, mesh) in &materials {
dbg!(mesh);
let mut entity = commands.entity(entity);
entity.remove::<Handle<StandardMaterial>>();
let material = extended_materials.add(load::AsteroidSurface::material());
entity.insert(material);
}
}
if settings.dev_mode && keyboard_input.just_pressed(KeyCode::KeyN) {
achievement_tracker.achieve_all();
}
}
fn handle_achievement_event(
mut er_achievement: EventReader<AchievementEvent>,
mut ew_game: EventWriter<GameEvent>,
mut tracker: ResMut<var::AchievementTracker>,
) {
for event in er_achievement.read() {
match event {
AchievementEvent::RepairSuit => {
if !tracker.repair_suit {
ew_game.send(GameEvent::Achievement("Repair Your Suit".into()));
}
tracker.repair_suit = true;
}
AchievementEvent::InJupitersShadow => {
if !tracker.in_jupiters_shadow {
ew_game.send(GameEvent::Achievement("Eclipse the sun with Jupiter".into()));
}
tracker.in_jupiters_shadow = true;
}
AchievementEvent::DrinkPizza => {
if !tracker.drink_a_pizza {
ew_game.send(GameEvent::Achievement("Enjoy A Pizza".into()));
}
tracker.drink_a_pizza = true;
}
AchievementEvent::FindEarth => {
if !tracker.find_earth {
ew_game.send(GameEvent::Achievement("Find Earth".into()));
}
tracker.find_earth = true;
}
AchievementEvent::RideVehicle(name) => {
tracker.vehicles_ridden.insert(name.clone());
let len = tracker.vehicles_ridden.len();
let total = tracker.all_vehicles.len();
if !tracker.ride_every_vehicle && len == total {
tracker.ride_every_vehicle = true;
ew_game.send(GameEvent::Achievement("Ride Every Vehicle".into()));
}
}
AchievementEvent::TalkTo(name) => {
tracker.people_talked_to.insert(name.clone());
let len = tracker.people_talked_to.len();
let total = tracker.all_people.len();
if !tracker.talk_to_everyone && len == total {
tracker.talk_to_everyone = true;
ew_game.send(GameEvent::Achievement("Talk To Everyone".into()));
}
}
}
}
}
fn check_achievements(
time: Res<Time>,
q_player: Query<&Position, With<actor::PlayerCamera>>,
id2pos: Res<Id2Pos>,
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; };
let shadowed = in_shadow(*pos_sun, nature::SOL_RADIUS,
*pos_jupiter, nature::JUPITER_RADIUS, **pos_player);
if shadowed {
ew_achievement.send(AchievementEvent::InJupitersShadow);
}
}

View file

@ -10,56 +10,46 @@
// //
// This module manages the heads-up display and augmented reality overlays. // This module manages the heads-up display and augmented reality overlays.
use crate::prelude::*; use crate::{actor, audio, camera, chat, nature, skeleton, var};
use bevy::pbr::{NotShadowCaster, NotShadowReceiver};
use bevy::prelude::*; use bevy::prelude::*;
use bevy::diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin}; use bevy::diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin};
use bevy::transform::TransformSystem; use bevy::transform::TransformSystem;
use bevy_xpbd_3d::prelude::*; use bevy_xpbd_3d::prelude::*;
use bevy::math::DVec3;
use std::collections::VecDeque; use std::collections::VecDeque;
use std::time::SystemTime; use std::time::SystemTime;
pub const DASHBOARD_ICON_SIZE: f32 = 64.0;
pub const HUD_REFRESH_TIME: f32 = 0.1; pub const HUD_REFRESH_TIME: f32 = 0.1;
pub const FONT: &str = "fonts/Yupiter-Regular.ttf";
pub const LOG_MAX_TIME_S: f64 = 30.0; pub const LOG_MAX_TIME_S: f64 = 30.0;
pub const LOG_MAX_ROWS: usize = 30; pub const LOG_MAX_ROWS: usize = 30;
pub const LOG_MAX: usize = LOG_MAX_ROWS; pub const LOG_MAX: usize = LOG_MAX_ROWS;
pub const MAX_CHOICES: usize = 10; pub const MAX_CHOICES: usize = 10;
pub const SPEEDOMETER_WIDTH: f32 = 20.0; pub const SPEEDOMETER_WIDTH: f32 = 40.0;
pub const SPEEDOMETER_HEIGHT: f32 = 10.0; pub const SPEEDOMETER_HEIGHT: f32 = 10.0;
pub const AMBIENT_LIGHT: f32 = 0.0; // Space is DARK pub const AMBIENT_LIGHT: f32 = 0.0; // Space is DARK
pub const AMBIENT_LIGHT_AR: f32 = 20.0; pub const AMBIENT_LIGHT_AR: f32 = 30.0;
//pub const REPLY_NUMBERS: [char; 10] = ['❶', '❷', '❸', '❹', '❺', '❻', '❼', '❽', '❾', '⓿']; //pub const REPLY_NUMBERS: [char; 10] = ['❶', '❷', '❸', '❹', '❺', '❻', '❼', '❽', '❾', '⓿'];
//pub const REPLY_NUMBERS: [char; 10] = ['①', '②', '③', '④', '⑤', '⑥', '⑦', '⑧', '⑨', '⑩']; //pub const REPLY_NUMBERS: [char; 10] = ['①', '②', '③', '④', '⑤', '⑥', '⑦', '⑧', '⑨', '⑩'];
pub const REPLY_NUMBERS: [char; 10] = ['➀', '➁', '➂', '➃', '➄', '➅', '➆', '➇', '➈', '➉']; pub const REPLY_NUMBERS: [char; 10] = ['➀', '➁', '➂', '➃', '➄', '➅', '➆', '➇', '➈', '➉'];
pub const DASHBOARD_DEF: &[(Dashboard, &str)] = &[
(Dashboard::Flashlight, "highbeams"),
(Dashboard::Leak, "leak"),
(Dashboard::RotationStabiliser, "rotation_stabiliser"),
(Dashboard::CruiseControl, "cruise_control"),
(Dashboard::Radioactivity, "radioactivity"),
];
pub struct HudPlugin; pub struct HudPlugin;
impl Plugin for HudPlugin { impl Plugin for HudPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Startup, setup); app.add_systems(Startup, setup);
app.add_systems(Update, ( app.add_systems(Update, (
update_hud, update_hud,
update_dashboard,
update_speedometer, update_speedometer,
update_gauges, handle_input,
handle_input.run_if(in_control),
handle_target_event, handle_target_event,
)); ));
app.add_systems(PostUpdate, ( app.add_systems(PostUpdate, (
update_overlay_visibility, update_overlay_visibility,
update_ar_overlays update_ar_overlays
.after(camera::position_to_transform) .after(actor::position_to_transform)
.in_set(sync::SyncSet::PositionToTransform), .in_set(sync::SyncSet::PositionToTransform),
update_poi_overlays update_poi_overlays
.after(camera::position_to_transform) .after(actor::position_to_transform)
.in_set(sync::SyncSet::PositionToTransform), .in_set(sync::SyncSet::PositionToTransform),
update_target_selectagon update_target_selectagon
.after(PhysicsSet::Sync) .after(PhysicsSet::Sync)
@ -90,30 +80,12 @@ impl Plugin for HudPlugin {
#[derive(Component)] struct Reticule; #[derive(Component)] struct Reticule;
#[derive(Component)] struct Speedometer; #[derive(Component)] struct Speedometer;
#[derive(Component)] struct Speedometer2; #[derive(Component)] struct Speedometer2;
#[derive(Component)] struct GaugeLength(f32);
#[derive(Component)] pub struct ToggleableHudElement; #[derive(Component)] pub struct ToggleableHudElement;
#[derive(Component)] pub struct ToggleableHudMapElement; #[derive(Component)] pub struct ToggleableHudMapElement;
#[derive(Component)] struct Selectagon; #[derive(Component)] struct Selectagon;
#[derive(Component)] pub struct IsTargeted; #[derive(Component)] pub struct IsTargeted;
#[derive(Component)] pub struct PointOfInterestMarker(pub Entity); #[derive(Component)] pub struct PointOfInterestMarker(pub Entity);
#[derive(Component, Debug, Copy, Clone)]
pub enum Dashboard {
Leak,
Flashlight,
RotationStabiliser,
CruiseControl,
Radioactivity,
}
#[derive(Component, Debug, Copy, Clone)]
enum Gauge {
Health,
Power,
Oxygen,
//Integrity,
}
#[derive(Resource)] #[derive(Resource)]
pub struct AugmentedRealityState { pub struct AugmentedRealityState {
pub overlays_visible: bool, pub overlays_visible: bool,
@ -129,7 +101,6 @@ pub struct AugmentedRealityOverlay {
struct FPSUpdateTimer(Timer); struct FPSUpdateTimer(Timer);
pub enum LogLevel { pub enum LogLevel {
Achievement,
Always, Always,
Warning, Warning,
//Error, //Error,
@ -224,9 +195,9 @@ impl Log {
} }
} }
pub fn setup( fn setup(
mut commands: Commands, mut commands: Commands,
settings: Res<Settings>, settings: Res<var::Settings>,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
mut ew_updateoverlays: EventWriter<UpdateOverlayVisibility>, mut ew_updateoverlays: EventWriter<UpdateOverlayVisibility>,
) { ) {
@ -242,12 +213,6 @@ pub fn setup(
color: settings.hud_color_subtitles, color: settings.hud_color_subtitles,
..default() ..default()
}; };
let style_fps = TextStyle {
font: font_handle.clone(),
font_size: settings.font_size_fps,
color: settings.hud_color_fps,
..default()
};
let style_console = TextStyle { let style_console = TextStyle {
font: font_handle.clone(), font: font_handle.clone(),
font_size: settings.font_size_console, font_size: settings.font_size_console,
@ -274,9 +239,24 @@ pub fn setup(
}; };
// Add Statistics HUD // Add Statistics HUD
let version = &settings.version;
let mut bundle_fps = TextBundle::from_sections([ let mut bundle_fps = TextBundle::from_sections([
TextSection::new("", style), // Target TextSection::new("", style.clone()),
TextSection::new("", style_fps), // Frames per second TextSection::new(format!(" OutFlyOS v{version}"), style.clone()),
TextSection::new("", style.clone()),
TextSection::new("", style.clone()),
TextSection::new("", style.clone()),
TextSection::new("", style.clone()),
TextSection::new("\n氧 OXYGEN ", style.clone()),
TextSection::new("", style.clone()),
TextSection::new("\nProximity 警告 ", style.clone()),
TextSection::new("", style.clone()),
TextSection::new("\nSuit Integrity ", style.clone()),
TextSection::new("", style.clone()),
TextSection::new("\nVitals ", style.clone()),
TextSection::new("", style.clone()),
TextSection::new("", style.clone()), // Speed
TextSection::new("", style.clone()), // Target
]).with_style(Style { ]).with_style(Style {
position_type: PositionType::Absolute, position_type: PositionType::Absolute,
top: Val::VMin(2.0), top: Val::VMin(2.0),
@ -325,7 +305,13 @@ pub fn setup(
let reticule_handle: Handle<Image> = asset_server.load("sprites/reticule4.png"); let reticule_handle: Handle<Image> = asset_server.load("sprites/reticule4.png");
commands.spawn(( commands.spawn((
NodeBundle { NodeBundle {
style: style_centered(), style: Style {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
align_items: AlignItems::Center,
justify_content: JustifyContent::SpaceAround,
..default()
},
visibility, visibility,
..default() ..default()
}, },
@ -345,139 +331,6 @@ pub fn setup(
)); ));
}); });
// HP/O2/Suit Integrity/Power Gauges
let gauges_handle: Handle<Image> = asset_server.load("sprites/gauge_horizontal.png");
let gauges = [
("sprites/gauge_heart.png", Gauge::Health),
("sprites/gauge_battery.png", Gauge::Power),
("sprites/gauge_o2.png", Gauge::Oxygen),
//("sprites/gauge_suit.png", Gauge::Integrity),
];
let icon_size = 24.0;
let gauge_bar_padding_left = 4.0;
for (i, (sprite, gauge)) in gauges.iter().enumerate() {
let bar_length = if i == 0 { 32.0*8.0 } else { 32.0*5.0 };
// The bar with variable width
commands.spawn((
NodeBundle {
style: Style {
width: Val::Percent(30.0),
height: Val::Percent(100.0),
bottom: Val::Px(20.0 + 24.0 * i as f32),
left: Val::VMin(2.0),
align_items: AlignItems::End,
overflow: Overflow::clip(),
..default()
},
visibility,
..default()
},
ToggleableHudElement,
)).with_children(|builder| {
builder.spawn((
NodeBundle {
style: Style {
width: Val::Px(118.0),
height: Val::Px(10.0),
bottom: Val::Px(8.0),
left: Val::Px(gauge_bar_padding_left + icon_size),
..Default::default()
},
visibility,
background_color: settings.hud_color.into(),
..Default::default()
},
gauge.clone(),
GaugeLength(bar_length),
));
});
// The decorator sprites surrounding the bar
commands.spawn((
NodeBundle {
style: Style {
width: Val::Percent(30.0),
height: Val::Percent(100.0),
bottom: Val::Px(20.0 + 24.0 * i as f32),
left: Val::VMin(2.0),
align_items: AlignItems::End,
overflow: Overflow::clip(),
..default()
},
visibility,
..default()
},
ToggleableHudElement,
)).with_children(|builder| {
// The gauge symbol
builder.spawn((
ImageBundle {
image: UiImage::new(asset_server.load(sprite.to_string())),
style: Style {
width: Val::Px(icon_size),
height: Val::Px(icon_size),
..Default::default()
},
visibility,
..Default::default()
},
));
// The gauge bar border
builder.spawn((
ImageBundle {
image: UiImage::new(gauges_handle.clone()),
style: Style {
width: Val::Px(bar_length),
height: Val::Px(10.0),
bottom: Val::Px(8.0),
left: Val::Px(gauge_bar_padding_left),
..Default::default()
},
visibility,
..Default::default()
},
));
});
}
// Car-Dashboard-Style icons
let style_dashboard = Style {
width: Val::Px(DASHBOARD_ICON_SIZE),
height: Val::Px(DASHBOARD_ICON_SIZE),
..Default::default()
};
commands.spawn((
NodeBundle {
style: Style {
width: Val::Percent(30.0),
height: Val::Percent(100.0),
bottom: Val::Px(40.0 + icon_size * gauges.len() as f32),
left: Val::VMin(4.0),
align_items: AlignItems::End,
overflow: Overflow::clip(),
..default()
},
visibility,
..default()
},
ToggleableHudElement,
)).with_children(|builder| {
for (component, filename) in DASHBOARD_DEF {
builder.spawn((
*component,
ImageBundle {
image: UiImage::new(asset_server.load(
format!("sprites/dashboard_{}.png", filename))),
style: style_dashboard.clone(),
visibility: Visibility::Hidden,
..Default::default()
},
));
}
});
// Add Speedometer // Add Speedometer
let speedometer_handle: Handle<Image> = asset_server.load("sprites/speedometer.png"); let speedometer_handle: Handle<Image> = asset_server.load("sprites/speedometer.png");
commands.spawn(( commands.spawn((
@ -485,7 +338,6 @@ pub fn setup(
style: Style { style: Style {
width: Val::VMin(0.0), width: Val::VMin(0.0),
height: Val::Percent(100.0), height: Val::Percent(100.0),
left: Val::Vw(100.0 - SPEEDOMETER_WIDTH),
align_items: AlignItems::End, align_items: AlignItems::End,
overflow: Overflow::clip(), overflow: Overflow::clip(),
..default() ..default()
@ -500,7 +352,7 @@ pub fn setup(
ImageBundle { ImageBundle {
image: UiImage::new(speedometer_handle), image: UiImage::new(speedometer_handle),
style: Style { style: Style {
width: Val::Vw(SPEEDOMETER_WIDTH), width: Val::VMin(SPEEDOMETER_WIDTH),
height: Val::VMin(SPEEDOMETER_HEIGHT), height: Val::VMin(SPEEDOMETER_HEIGHT),
..Default::default() ..Default::default()
}, },
@ -514,7 +366,6 @@ pub fn setup(
style: Style { style: Style {
width: Val::VMin(0.0), width: Val::VMin(0.0),
height: Val::Percent(100.0), height: Val::Percent(100.0),
left: Val::Vw(100.0 - SPEEDOMETER_WIDTH),
align_items: AlignItems::End, align_items: AlignItems::End,
overflow: Overflow::clip(), overflow: Overflow::clip(),
..default() ..default()
@ -529,7 +380,7 @@ pub fn setup(
ImageBundle { ImageBundle {
image: UiImage::new(speedometer_handle), image: UiImage::new(speedometer_handle),
style: Style { style: Style {
width: Val::Vw(SPEEDOMETER_WIDTH), width: Val::VMin(SPEEDOMETER_WIDTH),
height: Val::VMin(SPEEDOMETER_HEIGHT), height: Val::VMin(SPEEDOMETER_HEIGHT),
..Default::default() ..Default::default()
}, },
@ -538,12 +389,11 @@ pub fn setup(
)); ));
}); });
let mut bundle_speedometer_text = TextBundle::from_sections([ let mut bundle_speedometer_text = TextBundle::from_sections([
TextSection::new("", style_speedometer.clone()), // speed relative to target
TextSection::new("", style_speedometer.clone()), // speed relative to target TextSection::new("", style_speedometer.clone()), // speed relative to target
TextSection::new("", style_speedometer.clone()), // speed relative to orbit TextSection::new("", style_speedometer.clone()), // speed relative to orbit
]).with_style(Style { ]).with_style(Style {
position_type: PositionType::Absolute, position_type: PositionType::Absolute,
left: Val::Vw(100.0 - SPEEDOMETER_WIDTH + 2.0), left: Val::VMin(2.0),
bottom: Val::VMin(4.0), bottom: Val::VMin(4.0),
..default() ..default()
}).with_text_justify(JustifyText::Left); }).with_text_justify(JustifyText::Left);
@ -605,100 +455,41 @@ pub fn setup(
// Selectagon // Selectagon
let mut entitycmd = commands.spawn(( let mut entitycmd = commands.spawn((
Selectagon, Selectagon,
NotShadowCaster,
NotShadowReceiver,
SpatialBundle { SpatialBundle {
visibility: Visibility::Hidden, visibility: Visibility::Hidden,
..default() ..default()
}, },
)); ));
load_asset("selectagon", &mut entitycmd, &*asset_server); skeleton::load("selectagon", &mut entitycmd, &*asset_server);
ew_updateoverlays.send(UpdateOverlayVisibility); ew_updateoverlays.send(UpdateOverlayVisibility);
} }
fn update_dashboard(
timer: ResMut<FPSUpdateTimer>,
mut q_dashboard: Query<(&mut Visibility, &Dashboard)>,
id2pos: Res<game::Id2Pos>,
q_player: Query<(&actor::Suit, &Position), With<actor::Player>>,
settings: Res<Settings>,
) {
if !settings.hud_active || !timer.0.just_finished() {
return;
}
let player = q_player.get_single();
if player.is_err() { return; }
let (suit, pos) = player.unwrap();
for (mut vis, icon) in &mut q_dashboard {
*vis = bool2vis(match icon {
Dashboard::Flashlight => {
settings.flashlight_active
}
Dashboard::Leak => {
suit.integrity < 0.5
}
Dashboard::RotationStabiliser => {
!settings.rotation_stabilizer_active
}
Dashboard::CruiseControl => {
settings.cruise_control_active
}
Dashboard::Radioactivity => {
if let Some(pos_jupiter) = id2pos.0.get(cmd::ID_JUPITER) {
pos_jupiter.distance(pos.0) < 140_000_000.0
} else {
false
}
}
});
}
}
fn update_speedometer( fn update_speedometer(
timer: ResMut<FPSUpdateTimer>,
settings: Res<Settings>,
q_camera: Query<&LinearVelocity, With<actor::PlayerCamera>>, q_camera: Query<&LinearVelocity, With<actor::PlayerCamera>>,
q_player: Query<&actor::ExperiencesGForce, With<actor::Player>>,
q_target: Query<&LinearVelocity, With<IsTargeted>>, q_target: Query<&LinearVelocity, With<IsTargeted>>,
mut q_speedometer: Query<&mut Style, (With<Speedometer>, Without<Speedometer2>)>, mut q_speedometer: Query<&mut Style, (With<Speedometer>, Without<Speedometer2>)>,
mut q_speedometer2: Query<&mut Style, (With<Speedometer2>, Without<Speedometer>)>, mut q_speedometer2: Query<&mut Style, (With<Speedometer2>, Without<Speedometer>)>,
mut q_node_speed: Query<&mut Text, With<NodeSpeedometerText>>, mut q_node_speed: Query<&mut Text, With<NodeSpeedometerText>>,
) { ) {
if !settings.hud_active || !timer.0.just_finished() {
return;
}
if let Ok(cam_v) = q_camera.get_single() { if let Ok(cam_v) = q_camera.get_single() {
let speed = cam_v.length(); let speed = cam_v.length();
let speedometer_split = 5_000.0; let speedometer_split = 5_000.0;
if let Ok(mut speedometer) = q_speedometer.get_single_mut() { if let Ok(mut speedometer) = q_speedometer.get_single_mut() {
let custom_c = speedometer_split; let custom_c = speedometer_split;
let fraction = nature::inverse_lorentz_factor_custom_c((custom_c - speed).clamp(0.0, custom_c), custom_c).clamp(0.0, 1.0) as f32; let fraction = nature::lorenz_factor_custom_c((custom_c - speed).clamp(0.0, custom_c), custom_c).clamp(0.0, 1.0) as f32;
let wid = (fraction * SPEEDOMETER_WIDTH).clamp(0.0, 100.0); let wid = (fraction * SPEEDOMETER_WIDTH).clamp(0.0, 100.0);
speedometer.width = Val::Vw(wid); speedometer.width = Val::VMin(wid);
} }
if let Ok(mut speedometer2) = q_speedometer2.get_single_mut() { if let Ok(mut speedometer2) = q_speedometer2.get_single_mut() {
let custom_c = nature::C - speedometer_split; let custom_c = nature::C - speedometer_split;
let fraction = nature::inverse_lorentz_factor_custom_c((custom_c - speed + speedometer_split).clamp(0.0, custom_c), custom_c).clamp(0.0, 1.0) as f32; let fraction = nature::lorenz_factor_custom_c((custom_c - speed + speedometer_split).clamp(0.0, custom_c), custom_c).clamp(0.0, 1.0) as f32;
let wid = (fraction * SPEEDOMETER_WIDTH).clamp(0.0, 100.0); let wid = (fraction * SPEEDOMETER_WIDTH).clamp(0.0, 100.0);
speedometer2.width = Val::Vw(wid); speedometer2.width = Val::VMin(wid);
} }
if let Ok(mut speed_text) = q_node_speed.get_single_mut() { if let Ok(mut speed_text) = q_node_speed.get_single_mut() {
// G forces speed_text.sections[0].value = if let Ok(target_v) = q_target.get_single() {
speed_text.sections[0].value = if let Ok(gforce) = q_player.get_single() {
if gforce.gforce > 0.0001 {
format!("{:.1}g\n", gforce.gforce)
} else {
"".to_string()
}
} else {
"".to_string()
};
// Velocity relative to target
speed_text.sections[1].value = if let Ok(target_v) = q_target.get_single() {
let delta_v = (target_v.0 - cam_v.0).length(); let delta_v = (target_v.0 - cam_v.0).length();
if delta_v > 0.0001 { if delta_v > 0.0001 {
format!("Δv {}\n", nature::readable_speed(delta_v)) format!("Δv {}\n", nature::readable_speed(delta_v))
@ -708,9 +499,7 @@ fn update_speedometer(
} else { } else {
"".to_string() "".to_string()
}; };
speed_text.sections[1].value = if speed > 0.0001 {
// "Absolute velocity", or velocity relative to orbit
speed_text.sections[2].value = if speed > 0.0001 {
nature::readable_speed(speed) nature::readable_speed(speed)
} else { } else {
"".to_string() "".to_string()
@ -719,40 +508,11 @@ fn update_speedometer(
} }
} }
fn update_gauges(
timer: ResMut<FPSUpdateTimer>,
q_player: Query<(&actor::HitPoints, &actor::Suit, &actor::Battery), With<actor::Player>>,
mut q_gauges: Query<(&mut Style, &mut BackgroundColor, &Gauge, &GaugeLength)>,
settings: Res<Settings>,
) {
if !settings.hud_active || !timer.0.just_finished() {
return;
}
let player = q_player.get_single();
if player.is_err() { return; }
let (hp, suit, battery) = player.unwrap();
for (mut style, mut bg, gauge, len) in &mut q_gauges {
let value: f32 = match gauge {
Gauge::Health => hp.current / hp.max,
Gauge::Oxygen => (suit.oxygen / suit.oxygen_max).powf(0.5),
//Gauge::Integrity => suit.integrity,
Gauge::Power => battery.power / battery.capacity,
};
if value < 0.5 {
*bg = settings.hud_color_alert.into();
}
else {
*bg = settings.hud_color.into();
}
style.width = Val::Px(len.0 * value);
}
}
fn update_hud( fn update_hud(
diagnostics: Res<DiagnosticsStore>, diagnostics: Res<DiagnosticsStore>,
time: Res<Time>, time: Res<Time>,
mut log: ResMut<Log>, mut log: ResMut<Log>,
player: Query<(&actor::HitPoints, &actor::Suit, &actor::ExperiencesGForce), With<actor::Player>>,
q_camera: Query<(&Position, &LinearVelocity), With<actor::PlayerCamera>>, q_camera: Query<(&Position, &LinearVelocity), With<actor::PlayerCamera>>,
mut timer: ResMut<FPSUpdateTimer>, mut timer: ResMut<FPSUpdateTimer>,
q_choices: Query<&chat::Choice>, q_choices: Query<&chat::Choice>,
@ -761,21 +521,60 @@ fn update_hud(
mut q_node_console: Query<&mut Text, (With<NodeConsole>, Without<NodeHud>, Without<NodeChoiceText>)>, mut q_node_console: Query<&mut Text, (With<NodeConsole>, Without<NodeHud>, Without<NodeChoiceText>)>,
mut q_node_choice: Query<&mut Text, (With<NodeChoiceText>, Without<NodeHud>, Without<NodeConsole>)>, mut q_node_choice: Query<&mut Text, (With<NodeChoiceText>, Without<NodeHud>, Without<NodeConsole>)>,
mut q_node_currentline: Query<&mut Text, (With<NodeCurrentChatLine>, Without<NodeHud>, Without<NodeConsole>, Without<NodeChoiceText>)>, mut q_node_currentline: Query<&mut Text, (With<NodeCurrentChatLine>, Without<NodeHud>, Without<NodeConsole>, Without<NodeChoiceText>)>,
settings: Res<Settings>, query_all_actors: Query<&actor::Actor>,
settings: Res<var::Settings>,
q_target: Query<(&IsClickable, Option<&Position>, Option<&LinearVelocity>), With<IsTargeted>>, q_target: Query<(&IsClickable, Option<&Position>, Option<&LinearVelocity>), With<IsTargeted>>,
) { ) {
// TODO only when hud is actually on
if timer.0.tick(time.delta()).just_finished() || log.needs_rerendering { if timer.0.tick(time.delta()).just_finished() || log.needs_rerendering {
let q_camera_result = q_camera.get_single(); let q_camera_result = q_camera.get_single();
let player = player.get_single();
let mut freshest_line: f64 = 0.0; let mut freshest_line: f64 = 0.0;
if settings.hud_active && q_camera_result.is_ok() { if player.is_ok() && q_camera_result.is_ok() {
let (hp, suit, gforce) = player.unwrap();
let (pos, _) = q_camera_result.unwrap(); let (pos, _) = q_camera_result.unwrap();
for mut text in &mut q_node_hud { for mut text in &mut q_node_hud {
text.sections[0].value = format!("2524-03-12 03:02");
if let Some(fps) = diagnostics.get(&FrameTimeDiagnosticsPlugin::FPS) { if let Some(fps) = diagnostics.get(&FrameTimeDiagnosticsPlugin::FPS) {
if let Some(value) = fps.smoothed() { if let Some(value) = fps.smoothed() {
// Update the value of the second section // Update the value of the second section
text.sections[1].value = format!("{value:.0}"); text.sections[4].value = format!("{value:.0}");
} }
} }
let power = suit.power / suit.power_max * 100.0;
text.sections[2].value = format!("{power:}%");
let oxy_percent = suit.oxygen / suit.oxygen_max * 100.0;
// the remaining oxygen hud info ignores leaking suits from low integrity
if suit.oxygen > nature::OXY_H {
let oxy_hour = suit.oxygen / nature::OXY_H;
text.sections[7].value = format!("{oxy_percent:.1}% [lasts {oxy_hour:.1} hours]");
text.sections[7].style.color = settings.hud_color;
} else {
let oxy_min = suit.oxygen / nature::OXY_M;
text.sections[7].value = format!("{oxy_percent:.1}% [lasts {oxy_min:.1} min]");
text.sections[7].style.color = settings.hud_color_alert;
}
//let adrenaline = lifeform.adrenaline * 990.0 + 10.0;
//text.sections[11].value = format!("{adrenaline:.0}pg/mL");
let vitals = 100.0 * hp.current / hp.max;
text.sections[13].value = format!("{vitals:.0}%");
if vitals < 50.0 {
text.sections[13].style.color = settings.hud_color_alert;
} else {
text.sections[13].style.color = settings.hud_color;
}
let all_actors = query_all_actors.iter().len();
text.sections[9].value = format!("{all_actors:.0}");
let integrity = suit.integrity * 100.0;
if integrity < 50.0 {
text.sections[11].style.color = settings.hud_color_alert;
text.sections[11].value = format!("{integrity:.0}% [LEAKING]");
} else {
text.sections[11].style.color = settings.hud_color;
text.sections[11].value = format!("{integrity:.0}%");
}
//text.sections[17].value = format!("{speed_readable}/s / {kmh:.0}km/h / {gforce:.1}g");
// Target display // Target display
let dist_scalar: f64; let dist_scalar: f64;
@ -809,11 +608,23 @@ fn update_hud(
} }
} }
// let dev_speed = if settings.dev_mode {
// let x = pos.x;
// let y = pos.y;
// let z = pos.z;
// format!("\n{x:.0}\n{y:.0}\n{z:.0}")
// } else {
// "".to_string()
// };
let dev_speed = "";
let gforce = gforce.gforce;
text.sections[14].value = format!("\n{gforce:.1}g{dev_speed}");
if target_multiple { if target_multiple {
text.sections[0].value = "ERROR: MULTIPLE TARGETS\n\n".to_string(); text.sections[15].value = "\n\nERROR: MULTIPLE TARGETS".to_string();
} }
else if target_error { else if target_error {
text.sections[0].value = "ERROR: FAILED TO AQUIRE TARGET\n\n".to_string(); text.sections[15].value = "\n\nERROR: FAILED TO AQUIRE TARGET".to_string();
} }
else if let Ok((clickable, _, _)) = q_target.get_single() { else if let Ok((clickable, _, _)) = q_target.get_single() {
let distance = if dist_scalar.is_nan() { let distance = if dist_scalar.is_nan() {
@ -829,10 +640,10 @@ fn update_hud(
} else { } else {
"".to_string() "".to_string()
}; };
text.sections[0].value = format!("Target: {target_name}\n{pronoun}Distance: {distance}\n\n"); text.sections[15].value = format!("\n\nTarget: {target_name}\n{pronoun}Distance: {distance}");
} }
else { else {
text.sections[0].value = "".to_string(); text.sections[15].value = "".to_string();
} }
} }
} }
@ -866,7 +677,6 @@ fn update_hud(
} else { } else {
|msg: &&Message| { match msg.level { |msg: &&Message| { match msg.level {
LogLevel::Always => true, LogLevel::Always => true,
LogLevel::Achievement => true,
_ => false _ => false
}} }}
}; };
@ -882,7 +692,6 @@ fn update_hud(
let opacity: f32 = (freshness.powf(1.5) as f32).clamp(0.0, 1.0); let opacity: f32 = (freshness.powf(1.5) as f32).clamp(0.0, 1.0);
freshest_line = freshest_line.max(freshness); freshest_line = freshest_line.max(freshness);
chat.sections[row].style.color = match msg.level { chat.sections[row].style.color = match msg.level {
LogLevel::Achievement => settings.hud_color_console_achievement,
LogLevel::Warning => settings.hud_color_console_warn, LogLevel::Warning => settings.hud_color_console_warn,
LogLevel::Info => settings.hud_color_console_system, LogLevel::Info => settings.hud_color_console_system,
_ => settings.hud_color_console, _ => settings.hud_color_console,
@ -959,16 +768,26 @@ fn update_hud(
fn handle_input( fn handle_input(
keyboard_input: Res<ButtonInput<KeyCode>>, keyboard_input: Res<ButtonInput<KeyCode>>,
mouse_input: Res<ButtonInput<MouseButton>>, mouse_input: Res<ButtonInput<MouseButton>>,
settings: Res<Settings>, mut settings: ResMut<var::Settings>,
mut log: ResMut<Log>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>, mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_togglemusic: EventWriter<audio::ToggleMusicEvent>,
mut ew_target: EventWriter<TargetEvent>, mut ew_target: EventWriter<TargetEvent>,
mut ew_game: EventWriter<GameEvent>, mut ew_updateoverlays: EventWriter<UpdateOverlayVisibility>,
q_objects: Query<(Entity, &Transform), (With<IsClickable>, Without<IsTargeted>, Without<actor::PlayerDrivesThis>, Without<actor::Player>)>, q_objects: Query<(Entity, &Transform), (With<IsClickable>, Without<IsTargeted>, Without<actor::PlayerDrivesThis>, Without<actor::Player>)>,
q_camera: Query<&Transform, With<Camera>>, q_camera: Query<&Transform, With<Camera>>,
) { ) {
if keyboard_input.just_pressed(settings.key_help) {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Click));
for line in include_str!("data/keybindings.in").trim().lines().rev() {
log.add(line.to_string(), "".to_string(), LogLevel::Always);
}
}
if keyboard_input.just_pressed(settings.key_togglehud) { if keyboard_input.just_pressed(settings.key_togglehud) {
ew_game.send(GameEvent::SetAR(Turn::Toggle)); settings.hud_active ^= true;
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Switch)); ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Switch));
ew_togglemusic.send(audio::ToggleMusicEvent());
ew_updateoverlays.send(UpdateOverlayVisibility);
} }
if settings.hud_active && mouse_input.just_pressed(settings.key_selectobject) { if settings.hud_active && mouse_input.just_pressed(settings.key_selectobject) {
if let Ok(camtrans) = q_camera.get_single() { if let Ok(camtrans) = q_camera.get_single() {
@ -985,12 +804,10 @@ fn handle_input(
fn handle_target_event( fn handle_target_event(
mut commands: Commands, mut commands: Commands,
settings: Res<Settings>, settings: Res<var::Settings>,
mut er_target: EventReader<TargetEvent>, mut er_target: EventReader<TargetEvent>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>, mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_achievement: EventWriter<game::AchievementEvent>,
q_target: Query<Entity, With<IsTargeted>>, q_target: Query<Entity, With<IsTargeted>>,
q_ids: Query<&actor::Identifier>,
) { ) {
let mut play_sfx = false; let mut play_sfx = false;
@ -1002,12 +819,6 @@ fn handle_target_event(
if let Some(entity) = target { if let Some(entity) = target {
commands.entity(*entity).insert(IsTargeted); commands.entity(*entity).insert(IsTargeted);
play_sfx = true; play_sfx = true;
if let Ok(id) = q_ids.get(*entity) {
if id.0 == cmd::ID_EARTH {
ew_achievement.send(game::AchievementEvent::FindEarth);
}
}
} }
if play_sfx && !settings.mute_sfx { if play_sfx && !settings.mute_sfx {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Click)); ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Click));
@ -1017,7 +828,7 @@ fn handle_target_event(
} }
fn update_target_selectagon( fn update_target_selectagon(
settings: Res<Settings>, settings: Res<var::Settings>,
mut q_selectagon: Query<(&mut Transform, &mut Visibility), (With<Selectagon>, Without<IsTargeted>, Without<Camera>)>, mut q_selectagon: Query<(&mut Transform, &mut Visibility), (With<Selectagon>, Without<IsTargeted>, Without<Camera>)>,
q_target: Query<&Transform, (With<IsTargeted>, Without<Camera>, Without<Selectagon>)>, q_target: Query<&Transform, (With<IsTargeted>, Without<Camera>, Without<Selectagon>)>,
q_camera: Query<&Transform, (With<Camera>, Without<IsTargeted>, Without<Selectagon>)>, q_camera: Query<&Transform, (With<Camera>, Without<IsTargeted>, Without<Selectagon>)>,
@ -1035,7 +846,7 @@ fn update_target_selectagon(
} }
selectagon_trans.translation = target_trans.translation; selectagon_trans.translation = target_trans.translation;
selectagon_trans.scale = target_trans.scale; selectagon_trans.scale = target_trans.scale;
selectagon_trans.look_at(camera_trans.translation, camera_trans.up().into()); selectagon_trans.look_at(camera_trans.translation, target_trans.up().into());
// Enlarge Selectagon to a minimum angular diameter // Enlarge Selectagon to a minimum angular diameter
let (angular_diameter, _, _) = camera::calc_angular_diameter( let (angular_diameter, _, _) = camera::calc_angular_diameter(
@ -1057,7 +868,7 @@ fn update_target_selectagon(
fn update_ar_overlays ( fn update_ar_overlays (
q_owners: Query<(Entity, &Transform, &Visibility), (With<AugmentedRealityOverlayBroadcaster>, Without<AugmentedRealityOverlay>)>, q_owners: Query<(Entity, &Transform, &Visibility), (With<AugmentedRealityOverlayBroadcaster>, Without<AugmentedRealityOverlay>)>,
mut q_overlays: Query<(&mut Transform, &mut Visibility, &mut AugmentedRealityOverlay)>, mut q_overlays: Query<(&mut Transform, &mut Visibility, &mut AugmentedRealityOverlay)>,
settings: ResMut<Settings>, settings: ResMut<var::Settings>,
mut state: ResMut<AugmentedRealityState>, mut state: ResMut<AugmentedRealityState>,
) { ) {
let (need_activate, need_clean, need_update); let (need_activate, need_clean, need_update);
@ -1094,7 +905,7 @@ fn update_poi_overlays (
mut q_marker: Query<(&mut Transform, &PointOfInterestMarker)>, mut q_marker: Query<(&mut Transform, &PointOfInterestMarker)>,
q_parent: Query<&Transform, Without<PointOfInterestMarker>>, q_parent: Query<&Transform, Without<PointOfInterestMarker>>,
q_camera: Query<&Transform, (With<Camera>, Without<PointOfInterestMarker>)>, q_camera: Query<&Transform, (With<Camera>, Without<PointOfInterestMarker>)>,
settings: ResMut<Settings>, settings: ResMut<var::Settings>,
) { ) {
if !settings.hud_active || !settings.map_active || q_camera.is_empty() { if !settings.hud_active || !settings.map_active || q_camera.is_empty() {
return; return;
@ -1123,7 +934,7 @@ fn update_overlay_visibility(
q_target: Query<&IsTargeted, (Without<Camera>, Without<Selectagon>, Without<PointOfInterestMarker>, Without<ToggleableHudElement>)>, q_target: Query<&IsTargeted, (Without<Camera>, Without<Selectagon>, Without<PointOfInterestMarker>, Without<ToggleableHudElement>)>,
mut ambient_light: ResMut<AmbientLight>, mut ambient_light: ResMut<AmbientLight>,
er_target: EventReader<UpdateOverlayVisibility>, er_target: EventReader<UpdateOverlayVisibility>,
settings: Res<Settings>, settings: Res<var::Settings>,
) { ) {
if er_target.is_empty() { if er_target.is_empty() {
return; return;

View file

@ -11,35 +11,25 @@
// This module initializes the game, handles command-line arguments, // This module initializes the game, handles command-line arguments,
// and manages window-related key bindings. // and manages window-related key bindings.
pub mod actor; mod actor;
pub mod audio; mod audio;
pub mod camera; mod camera;
pub mod chat; mod chat;
pub mod cmd; mod commands;
pub mod common; mod effects;
pub mod game; mod hud;
pub mod hud; mod shading;
pub mod load; mod skeleton;
pub mod menu; mod var;
#[allow(dead_code)] mod world;
pub mod nature;
pub mod var;
pub mod visual;
pub mod world;
pub mod prelude { #[allow(dead_code)]
pub use crate::{actor, audio, camera, chat, cmd, common, game, hud, mod nature;
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 game::Turn::Toggle;
}
use bevy::window::{Window, WindowMode, PrimaryWindow, CursorGrabMode}; use bevy::window::{Window, WindowMode, PrimaryWindow, CursorGrabMode};
use bevy::diagnostic::FrameTimeDiagnosticsPlugin; use bevy::diagnostic::FrameTimeDiagnosticsPlugin;
use bevy::prelude::*; use bevy::prelude::*;
use bevy::pbr::ExtendedMaterial;
use std::env; use std::env;
const HELP: &str = "./outfly [options] const HELP: &str = "./outfly [options]
@ -56,7 +46,7 @@ Note: borderless fullscreen is the default, but it crashes on some systems.";
fn main() { fn main() {
let prefs = var::load_prefs(); let prefs = var::load_prefs();
let mut opt = var::CommandLineOptions { let mut opt = CommandLineOptions {
window_mode_fullscreen: prefs.get_fullscreen_mode(), window_mode_fullscreen: prefs.get_fullscreen_mode(),
window_mode_initial: prefs.get_window_mode(), window_mode_initial: prefs.get_window_mode(),
use_gl: prefs.render_mode_is_gl(), use_gl: prefs.render_mode_is_gl(),
@ -127,6 +117,7 @@ impl Plugin for OutFlyPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Startup, setup); app.add_systems(Startup, setup);
app.add_systems(Update, handle_input); app.add_systems(Update, handle_input);
app.add_systems(Update, debug);
app.insert_resource(var::Settings::default()); app.insert_resource(var::Settings::default());
app.insert_resource(var::GameVars::default()); app.insert_resource(var::GameVars::default());
app.add_plugins(( app.add_plugins((
@ -137,37 +128,72 @@ impl Plugin for OutFlyPlugin {
audio::AudioPlugin, audio::AudioPlugin,
camera::CameraPlugin, camera::CameraPlugin,
chat::ChatPlugin, chat::ChatPlugin,
cmd::CmdPlugin, commands::CommandsPlugin,
game::GamePlugin, effects::EffectsPlugin,
menu::MenuPlugin,
visual::VisualPlugin,
hud::HudPlugin, hud::HudPlugin,
load::LoadPlugin, shading::ShadingPlugin,
skeleton::SkeletonPlugin,
world::WorldPlugin, world::WorldPlugin,
)); ));
} }
} }
#[derive(Resource, Default)]
pub struct CommandLineOptions {
window_mode_fullscreen: WindowMode,
window_mode_initial: WindowMode,
use_gl: bool,
}
fn setup( fn setup(
mut windows: Query<&mut Window, With<PrimaryWindow>>, mut windows: Query<&mut Window, With<PrimaryWindow>>,
opt: Res<var::CommandLineOptions>, opt: Res<CommandLineOptions>,
) { ) {
for mut window in &mut windows { for mut window in &mut windows {
window.cursor.grab_mode = CursorGrabMode::Locked; window.cursor.grab_mode = CursorGrabMode::Locked;
window.cursor.visible = false; window.cursor.visible = false;
window.mode = opt.window_mode_initial; window.mode = opt.window_mode_initial;
window.title = common::GAME_NAME.to_string(); window.title = "OutFly".to_string();
} }
} }
fn handle_input( fn handle_input(
keyboard_input: Res<ButtonInput<KeyCode>>, keyboard_input: Res<ButtonInput<KeyCode>>,
settings: Res<var::Settings>, settings: Res<var::Settings>,
opt: Res<CommandLineOptions>,
mut app_exit_events: ResMut<Events<bevy::app::AppExit>>,
mut windows: Query<&mut Window, With<PrimaryWindow>>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>, mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_game: EventWriter<game::GameEvent>,
) { ) {
if keyboard_input.pressed(settings.key_exit) {
app_exit_events.send(bevy::app::AppExit);
}
if keyboard_input.just_pressed(settings.key_fullscreen) { if keyboard_input.just_pressed(settings.key_fullscreen) {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Click)); ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Click));
ew_game.send(game::GameEvent::SetFullscreen(game::Turn::Toggle)); for mut window in &mut windows {
window.mode = if window.mode == WindowMode::Windowed {
opt.window_mode_fullscreen
} else {
WindowMode::Windowed
}
}
}
}
fn debug(
settings: Res<var::Settings>,
keyboard_input: Res<ButtonInput<KeyCode>>,
mut commands: Commands,
mut extended_materials: ResMut<Assets<ExtendedMaterial<StandardMaterial, shading::AsteroidSurface>>>,
materials: Query<(Entity, Option<&Name>, &Handle<Mesh>)>,
) {
if settings.dev_mode && keyboard_input.pressed(KeyCode::KeyP) {
for (entity, _name, mesh) in &materials {
dbg!(mesh);
let mut entity = commands.entity(entity);
entity.remove::<Handle<StandardMaterial>>();
let material = extended_materials.add(shading::AsteroidSurface::material());
entity.insert(material);
}
} }
} }

View file

@ -1,548 +0,0 @@
// ▄████████▄ + ███ + ▄█████████ ███ +
// ███▀ ▀███ + + ███ ███▀ + ███ + +
// ███ + ███ ███ ███ █████████ ███ ███ ███ ███
// ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
// ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
// ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
// ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
// + + + ███
// + ▀████████████████████████████████████████████████████▀
//
// This plugin manages game menus and the player death screen
use crate::prelude::*;
use bevy::prelude::*;
use fastrand;
pub const POEMS: &str = &include_str!("data/deathpoems.in");
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(Update, handle_deathscreen_input);
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(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 }
pub const MENUDEF: &[(&str, MenuAction)] = &[
("", MenuAction::ToggleMap),
("", MenuAction::ToggleAR),
("", MenuAction::ToggleSound),
("", MenuAction::ToggleMusic),
("", MenuAction::ToggleCamera),
("Toggle Fullscreen [F11]", MenuAction::ToggleFullscreen),
("", MenuAction::ToggleShadows),
("Take Off Helmet", MenuAction::Restart),
("Quit", MenuAction::Quit),
];
#[derive(Component)]
pub enum MenuAction {
ToggleMap,
ToggleAR,
ToggleSound,
ToggleMusic,
ToggleCamera,
ToggleFullscreen,
ToggleShadows,
Restart,
Quit,
}
pub fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
achievement_tracker: Res<var::AchievementTracker>,
settings: Res<Settings>,
) {
commands.spawn((
DeathScreenElement,
NodeBundle {
style: style_fullscreen(),
background_color: Color::BLACK.into(),
visibility: Visibility::Hidden,
..default()
},
));
let font_handle = asset_server.load(FONT);
let style_death = TextStyle {
font: font_handle.clone(),
font_size: settings.font_size_deathtext,
color: settings.hud_color_death,
..default()
};
let style_death_poem = TextStyle {
font: font_handle.clone(),
font_size: settings.font_size_deathpoem,
color: settings.hud_color_deathpoem,
..default()
};
let style_death_subtext = TextStyle {
font: font_handle.clone(),
font_size: settings.font_size_deathsubtext,
color: settings.hud_color_death,
..default()
};
let style_death_subsubtext = TextStyle {
font: font_handle.clone(),
font_size: settings.font_size_deathsubtext * 0.8,
color: settings.hud_color_death,
..default()
};
let style_death_achievements = TextStyle {
font: font_handle.clone(),
font_size: settings.font_size_death_achievements,
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()
},
..default()
},
));
});
let style_menu = TextStyle {
font: font_handle.clone(),
font_size: settings.font_size_hud,
color: settings.hud_color,
..default()
};
let sections: Vec<TextSection> = Vec::from_iter(MENUDEF.iter().map(|(label, _)|
TextSection::new(label.to_string() + "\n", style_menu.clone())
));
commands.spawn((
MenuElement,
NodeBundle {
style: style_fullscreen(),
background_color: Color::rgba(0.0, 0.0, 0.0, 0.8).into(),
visibility: Visibility::Hidden,
..default()
},
));
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(),
font_size: settings.font_size_achievement_header,
color: settings.hud_color_achievement_header,
..default()
};
let style_achievement = TextStyle {
font: font_handle.clone(),
font_size: settings.font_size_achievement,
color: settings.hud_color_achievement,
..default()
};
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,
..default()
},
..default()
},
));
});
let keybindings = include_str!("data/keybindings.in");
let style_keybindings = TextStyle {
font: font_handle.clone(),
font_size: settings.font_size_keybindings,
color: settings.hud_color_keybindings,
..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,
..default()
},
visibility: Visibility::Hidden,
..default()
},
)).with_children(|builder| {
builder.spawn((
TextBundle {
text: Text {
sections: vec![
TextSection::new("Controls\n", style_achievement_header),
TextSection::new(keybindings, style_keybindings)
],
justify: JustifyText::Right,
..default()
},
..default()
},
));
});
let style_version = TextStyle {
font: font_handle.clone(),
font_size: settings.font_size_version,
color: settings.hud_color_version,
..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,
..default()
},
visibility: Visibility::Hidden,
..default()
},
)).with_children(|builder| {
builder.spawn((
TextBundle {
text: Text {
sections: vec![
TextSection::new(format!("{} {}", GAME_NAME,
settings.version.as_str()), style_version),
],
justify: JustifyText::Right,
..default()
},
..default()
},
));
});
}
pub fn show_deathscreen(
mut er_deathscreen: EventReader<DeathScreenEvent>,
mut q_vis: Query<&mut Visibility, With<DeathScreenElement>>,
mut q_text: Query<&mut Text, With<DeathText>>,
mut ew_pausesfx: EventWriter<audio::PauseAllSfxEvent>,
mut ew_game: EventWriter<GameEvent>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_effect: EventWriter<visual::SpawnEffectEvent>,
mut ew_respawn: EventWriter<world::RespawnEvent>,
mut ew_respawnaudiosinks: EventWriter<audio::RespawnSinksEvent>,
mut timer: ResMut<DeathScreenInputDelayTimer>,
mut menustate: ResMut<MenuState>,
mut settings: ResMut<Settings>,
achievement_tracker: Res<var::AchievementTracker>,
) {
for event in er_deathscreen.read() {
let show = *event == DeathScreenEvent::Show;
for mut vis in &mut q_vis {
*vis = bool2vis(show);
}
settings.deathscreen_active = show;
settings.alive = !show;
if show {
timer.0.reset();
*menustate = MenuState::default();
ew_game.send(GameEvent::SetMenu(Turn::Off));
ew_pausesfx.send(audio::PauseAllSfxEvent);
if let Ok(mut text) = q_text.get_single_mut() {
let poems: Vec<&str> = POEMS.split("\n\n").collect();
if poems.len() > 0 {
let poem_index = fastrand::usize(..poems.len());
let poem = poems[poem_index].to_string();
text.sections[0].value = poem + "\n\n\n\n";
}
text.sections[3].value = settings.death_cause.clone();
text.sections[4].value = achievement_tracker.to_summary();
}
} 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_respawn.send(world::RespawnEvent);
}
}
}
pub fn handle_deathscreen_input(
time: Res<Time>,
keyboard_input: Res<ButtonInput<KeyCode>>,
mut timer: ResMut<DeathScreenInputDelayTimer>,
mut ew_deathscreen: EventWriter<DeathScreenEvent>,
settings: Res<Settings>,
) {
if !settings.deathscreen_active || !timer.0.tick(time.delta()).finished() {
return;
}
if keyboard_input.pressed(settings.key_interact) {
ew_deathscreen.send(DeathScreenEvent::Hide);
}
}
#[derive(Resource, Debug, Default)]
pub struct MenuState {
cursor: usize,
}
pub fn update_menu(
mut q_text: Query<&mut Text, With<MenuTopLevel>>,
mut q_achievement_text: Query<&mut Text, (With<MenuAchievements>, Without<MenuTopLevel>)>,
mut q_vis: Query<&mut Visibility, With<menu::MenuElement>>,
achievement_tracker: Res<var::AchievementTracker>,
menustate: Res<MenuState>,
settings: Res<Settings>,
) {
for mut vis in &mut q_vis {
*vis = bool2vis(settings.menu_active);
}
fn bool2string(boolean: bool) -> String {
if boolean { "On" } else { "Off" }.to_string()
}
let bools = achievement_tracker.to_bool_vec();
let rendered = achievement_tracker.to_textsections();
if let Ok(mut text) = q_achievement_text.get_single_mut() {
for i in 0..text.sections.len() - 1 {
text.sections[i + 1].style.color = if bools[i] {
settings.hud_color_achievement_accomplished
} else {
settings.hud_color_achievement
};
text.sections[i + 1].value = rendered[i].clone();
}
}
if let Ok(mut text) = q_text.get_single_mut() {
for i in 0..text.sections.len() {
if menustate.cursor == i {
text.sections[i].style.color = settings.hud_color_subtitles;
} else {
text.sections[i].style.color = settings.hud_color;
}
match MENUDEF[i].1 {
MenuAction::ToggleSound => {
let onoff = bool2string(!settings.mute_sfx);
text.sections[i].value = format!("Sound: {onoff}\n");
}
MenuAction::ToggleMusic => {
let onoff = bool2string(!settings.mute_music);
text.sections[i].value = format!("Music: {onoff}\n");
}
MenuAction::ToggleAR => {
let onoff = bool2string(settings.hud_active);
text.sections[i].value = format!("Augmented Reality: {onoff} [TAB]\n");
}
MenuAction::ToggleMap => {
let onoff = bool2string(settings.map_active);
text.sections[i].value = format!("Map: {onoff} [M]\n");
}
MenuAction::ToggleCamera => {
let onoff = if settings.third_person {
"3rd Person"
} else {
"1st Person"
};
text.sections[i].value = format!("Camera: {onoff} [C]\n");
}
MenuAction::ToggleShadows => {
let onoff = if settings.shadows_sun {
"Flashlight + Sun"
} else {
"Flashlight Only"
};
text.sections[i].value = format!("Shadows: {onoff}\n");
}
_ => {}
}
}
}
}
pub fn handle_input(
keyboard_input: Res<ButtonInput<KeyCode>>,
mut settings: ResMut<Settings>,
mut menustate: ResMut<MenuState>,
mut app_exit_events: ResMut<Events<bevy::app::AppExit>>,
mut ew_game: EventWriter<game::GameEvent>,
mut ew_playerdies: EventWriter<game::PlayerDiesEvent>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_updatemenu: EventWriter<UpdateMenuEvent>,
) {
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
{
ew_game.send(GameEvent::SetMenu(Toggle));
}
if !settings.menu_active {
return;
}
if keyboard_input.just_pressed(settings.key_forward)
|| keyboard_input.just_pressed(settings.key_mouseup)
|| keyboard_input.just_pressed(KeyCode::ArrowUp)
{
menustate.cursor = if menustate.cursor == 0 {
last_menu_entry
} else {
menustate.cursor.saturating_sub(1)
};
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Click));
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)
{
menustate.cursor = if menustate.cursor == last_menu_entry {
0
} else {
menustate.cursor.saturating_add(1)
};
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Click));
ew_updatemenu.send(UpdateMenuEvent);
}
if keyboard_input.just_pressed(settings.key_interact)
|| keyboard_input.just_pressed(KeyCode::Enter)
{
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Click));
match MENUDEF[menustate.cursor].1 {
MenuAction::ToggleMap => {
ew_game.send(GameEvent::SetMap(Toggle));
ew_game.send(GameEvent::SetMenu(Turn::Off));
ew_updatemenu.send(UpdateMenuEvent);
},
MenuAction::ToggleAR => {
ew_game.send(GameEvent::SetAR(Toggle));
ew_updatemenu.send(UpdateMenuEvent);
},
MenuAction::ToggleMusic => {
ew_game.send(GameEvent::SetMusic(Toggle));
ew_updatemenu.send(UpdateMenuEvent);
},
MenuAction::ToggleSound => {
ew_game.send(GameEvent::SetSound(Toggle));
ew_updatemenu.send(UpdateMenuEvent);
},
MenuAction::ToggleCamera => {
ew_game.send(GameEvent::SetThirdPerson(Toggle));
ew_updatemenu.send(UpdateMenuEvent);
},
MenuAction::ToggleFullscreen => {
ew_game.send(GameEvent::SetFullscreen(Toggle));
},
MenuAction::ToggleShadows => {
ew_game.send(GameEvent::SetShadows(Toggle));
ew_updatemenu.send(UpdateMenuEvent);
},
MenuAction::Restart => {
settings.god_mode = false;
ew_playerdies.send(game::PlayerDiesEvent(actor::DamageType::Depressurization));
},
MenuAction::Quit => {
app_exit_events.send(bevy::app::AppExit);
},
};
}
}

View file

@ -10,7 +10,8 @@
// //
// This module manages the messy, impure parts of our universe. // This module manages the messy, impure parts of our universe.
use crate::prelude::*; use bevy::math::DVec3;
use std::f64::consts::PI as PI64;
pub const OXYGEN_USE_KG_PER_S: f32 = 1e-5; pub const OXYGEN_USE_KG_PER_S: f32 = 1e-5;
pub const OXY_S: f32 = OXYGEN_USE_KG_PER_S; pub const OXY_S: f32 = OXYGEN_USE_KG_PER_S;
@ -27,8 +28,6 @@ pub const G: f64 = 6.6743015e-11; // Gravitational constant in Nm²/kg²
pub const SOL_RADIUS: f64 = 696_300_000.0; pub const SOL_RADIUS: f64 = 696_300_000.0;
pub const JUPITER_RADIUS: f64 = 71_492_000.0; pub const JUPITER_RADIUS: f64 = 71_492_000.0;
pub const JUPITER_RING_RADIUS: f64 = 229_000_000.0; pub const JUPITER_RING_RADIUS: f64 = 229_000_000.0;
pub const SOL_MASS: f64 = 1.9885e30;
pub const JUPITER_MASS: f64 = 1.8982e27; pub const JUPITER_MASS: f64 = 1.8982e27;
// Each star's values: (x, y, z, magnitude, color index, distance, name) // Each star's values: (x, y, z, magnitude, color index, distance, name)
@ -76,7 +75,7 @@ pub fn ring_density(radius: f32) -> f32 {
let thebe_inner: f32 = 129.0; let thebe_inner: f32 = 129.0;
let thebe_outer: f32 = 229.0; let thebe_outer: f32 = 229.0;
let metis_notch_center: f32 = 128.0; let metis_notch_center: f32 = 128.0;
let metis_notch_width: f32 = 0.1; let metis_notch_width: f32 = 0.6;
let halo_brightness: f32 = 0.75; let halo_brightness: f32 = 0.75;
let main_brightness: f32 = 1.0; let main_brightness: f32 = 1.0;
@ -90,7 +89,7 @@ pub fn ring_density(radius: f32) -> f32 {
} else if radius >= main_inner && radius <= main_outer { } else if radius >= main_inner && radius <= main_outer {
let mut metis_notch_effect: f32 = 1.0; 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 { 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)); metis_notch_effect = 0.5 * (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 { } else {
@ -138,24 +137,16 @@ pub fn readable_speed(speed: f64) -> String {
} }
} }
pub fn lorentz_factor(speed: f64) -> f64 { pub fn lorenz_factor(speed: f64) -> f64 {
(1.0 - (speed.powf(2.0) / C.powf(2.0))).powf(-0.5)
}
pub fn lorentz_factor_custom_c(speed: f64, c: f64) -> f64 {
(1.0 - (speed.powf(2.0) / c.powf(2.0))).powf(-0.5)
}
pub fn inverse_lorentz_factor(speed: f64) -> f64 {
(1.0 - (speed.powf(2.0) / C.powf(2.0))).sqrt() (1.0 - (speed.powf(2.0) / C.powf(2.0))).sqrt()
} }
pub fn inverse_lorentz_factor_custom_c(speed: f64, c: f64) -> f64 { pub fn lorenz_factor_custom_c(speed: f64, c: f64) -> f64 {
(1.0 - (speed.powf(2.0) / c.powf(2.0))).sqrt() (1.0 - (speed.powf(2.0) / c.powf(2.0))).sqrt()
} }
pub fn simple_orbital_period(mass: f64, distance: f64) -> f64 { pub fn simple_orbital_period(mass: f64, distance: f64) -> f64 {
return 2.0 * PI * (distance.powf(3.0) / (G * mass)).sqrt(); return 2.0 * PI64 * (distance.powf(3.0) / (G * mass)).sqrt();
} }
pub fn phase_dist_to_coords(phase_radians: f64, distance: f64) -> DVec3 { pub fn phase_dist_to_coords(phase_radians: f64, distance: f64) -> DVec3 {

View file

@ -8,15 +8,14 @@
// + + + ███ // + + + ███
// + ▀████████████████████████████████████████████████████▀ // + ▀████████████████████████████████████████████████████▀
// //
// This module manages asset loading. // This module manages graphics shaders.
use bevy::ecs::system::EntityCommands; use bevy::prelude::*;
use bevy::render::render_resource::{AsBindGroup, ShaderRef}; use bevy::render::render_resource::{AsBindGroup, ShaderRef};
use bevy::pbr::{ExtendedMaterial, MaterialExtension, OpaqueRendererMethod}; use bevy::pbr::{ExtendedMaterial, MaterialExtension, OpaqueRendererMethod};
use bevy::prelude::*;
pub struct LoadPlugin; pub struct ShadingPlugin;
impl Plugin for LoadPlugin { impl Plugin for ShadingPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_plugins(MaterialPlugin::<JupitersRing>::default()); app.add_plugins(MaterialPlugin::<JupitersRing>::default());
app.add_plugins(MaterialPlugin::<SkyBox>::default()); app.add_plugins(MaterialPlugin::<SkyBox>::default());
@ -24,58 +23,6 @@ impl Plugin for LoadPlugin {
} }
} }
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",
"asteroid1" => "models/asteroid.glb#Scene0",
"asteroid2" => "models/asteroid2.glb#Scene0",
"asteroid_lum" => "models/asteroid_lum.glb#Scene0",
"hollow_asteroid" => "models/hollow_asteroid.glb#Scene0",
"moonlet" => "models/moonlet.glb#Scene0",
"monolith" => "models/monolith_neon.glb#Scene0",
"lightorb" => "models/lightorb.glb#Scene0",
"orb_busstop" => "models/orb_busstop.glb#Scene0",
"orb_busstop_dim" => "models/orb_busstop_dim.glb#Scene0",
"crate" => "models/crate.glb#Scene0",
"MeteorAceGT" => "models/MeteorAceGT.glb#Scene0",
"cruiser" => "models/cruiser.glb#Scene0",
"satellite" => "models/satellite.glb#Scene0",
"pizzeria" => "models/pizzeria2.glb#Scene0",
"pizzasign" => "models/pizzasign.glb#Scene0",
"selectagon" => "models/selectagon.glb#Scene0",
"orbitring" => "models/orbitring.glb#Scene0",
"clippy" => "models/clippy/clippy.glb#Scene0",
"clippy_ar" => "models/clippy/ar_happy.glb#Scene0",
"whale" => "models/whale.glb#Scene0",
"marker_satellites" => "models/marker_satellites.glb#Scene0",
"marker_planets" => "models/marker_planets.glb#Scene0",
"point_of_interest" => "models/point_of_interest.glb#Scene0",
_ => "models/error.glb#Scene0",
}
}
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> {
let path_string = path.to_string();
if let Some(handle) = asset_server.get_handle(&path_string) {
handle
} else {
asset_server.load(&path_string)
}
}
// Jupiter's Ring // Jupiter's Ring
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)] #[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
pub struct JupitersRing { pub struct JupitersRing {

355
src/skeleton.rs Normal file
View file

@ -0,0 +1,355 @@
// ▄████████▄ + ███ + ▄█████████ ███ +
// ███▀ ▀███ + + ███ ███▀ + ███ + +
// ███ + ███ ███ ███ █████████ ███ ███ ███ ███
// ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
// ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
// ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
// ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
// + + + ███
// + ▀████████████████████████████████████████████████████▀
//
// This module manages model loading and animation.
use crate::world;
use bevy::ecs::system::EntityCommands;
use bevy::prelude::*;
pub struct SkeletonPlugin;
impl Plugin for SkeletonPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, animate_skeleton_parts);
app.add_systems(Update, play_animations);
}
}
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",
"asteroid1" => "models/asteroid.glb#Scene0",
"asteroid2" => "models/asteroid2.glb#Scene0",
"asteroid_lum" => "models/asteroid_lum.glb#Scene0",
"moonlet" => "models/moonlet.glb#Scene0",
"monolith" => "models/monolith_neon.glb#Scene0",
"lightorb" => "models/lightorb.glb#Scene0",
"orb_busstop" => "models/orb_busstop.glb#Scene0",
"orb_busstop_dim" => "models/orb_busstop_dim.glb#Scene0",
"MeteorAceGT" => "models/MeteorAceGT.glb#Scene0",
"satellite" => "models/satellite.glb#Scene0",
"pizzeria" => "models/pizzeria2.glb#Scene0",
"pizzasign" => "models/pizzasign.glb#Scene0",
"selectagon" => "models/selectagon.glb#Scene0",
"orbitring" => "models/orbitring.glb#Scene0",
"clippy" => "models/clippy/clippy.glb#Scene0",
"clippy_ar" => "models/clippy/ar_happy.glb#Scene0",
"whale" => "models/whale.glb#Scene0",
"point_of_interest" => "models/point_of_interest.glb#Scene0",
_ => "models/error.glb#Scene0",
}
}
pub fn skeleton_name_to_skeletondef(name: &str) -> Option<SkeletonDef> {
// x: positive: left, negative: right
// y: positive: upward, negative: downward
// z: positive: forward, negative: backward
match name {
"suitv1" => Some(SkeletonDef::Human(HumanDef {
collider: "models/suit_v1/collider.glb#Scene0".into(),
base: "models/suit_v1/base.glb#Scene0".into(),
limbs: vec![
LimbDef {
class: Limb::Head,
path: "models/suit_v1/head.glb#Scene0".into(),
pos: Vec3::new(0.0, 0.46, 0.0),
..default()
},
LimbDef {
class: Limb::UpperArmLeft,
path: "models/suit_v1/upper_arm.glb#Scene0".into(),
pos: Vec3::new(0.22, 0.3, 0.0),
mirror: true,
children: vec![LimbDef {
class: Limb::LowerArmLeft,
path: "models/suit_v1/lower_arm.glb#Scene0".into(),
pos: Vec3::new(-0.33, 0.0, 0.0),
..default()
}],
..default()
},
LimbDef {
class: Limb::UpperArmRight,
path: "models/suit_v1/upper_arm.glb#Scene0".into(),
pos: Vec3::new(-0.22, 0.3, 0.0),
children: vec![LimbDef {
class: Limb::LowerArmRight,
path: "models/suit_v1/lower_arm.glb#Scene0".into(),
pos: Vec3::new(-0.33, 0.0, 0.0),
..default()
}],
..default()
},
LimbDef {
class: Limb::UpperLegLeft,
path: "models/suit_v1/upper_leg.glb#Scene0".into(),
pos: Vec3::new(0.15, -0.25, 0.1),
mirror: true,
children: vec![LimbDef {
class: Limb::LowerLegLeft,
path: "models/suit_v1/lower_leg.glb#Scene0".into(),
pos: Vec3::new(0.0, -0.3, 0.0),
..default()
}],
..default()
},
LimbDef {
class: Limb::UpperLegRight,
path: "models/suit_v1/upper_leg.glb#Scene0".into(),
pos: Vec3::new(-0.15, -0.25, 0.1),
children: vec![LimbDef {
class: Limb::LowerLegRight,
path: "models/suit_v1/lower_leg.glb#Scene0".into(),
pos: Vec3::new(0.0, -0.3, 0.0),
..default()
}],
..default()
},
],
})),
_ => None,
}
}
#[derive(Component)] pub struct SkeletonLimb;
#[derive(Component)] pub struct MirroredLimb;
pub enum SkeletonDef {
Human(HumanDef)
}
pub struct HumanDef {
collider: String,
base: String,
limbs: Vec<LimbDef>,
}
#[derive(Default)]
pub struct LimbDef {
path: String,
pos: Vec3,
class: Limb,
mirror: bool,
children: Vec<LimbDef>,
}
#[derive(Component, Default)]
pub enum Limb {
#[default]
Base,
Head,
UpperArmRight,
UpperArmLeft,
LowerArmRight,
LowerArmLeft,
UpperLegRight,
UpperLegLeft,
LowerLegRight,
LowerLegLeft,
}
#[derive(Component)]
pub enum Animation {
HumanFloat,
}
pub fn load(
name: &str,
entity_commands: &mut EntityCommands,
asset_server: &AssetServer,
) {
if let Some(skel) = skeleton_name_to_skeletondef(name) {
match skel {
SkeletonDef::Human(human) => {
entity_commands.insert(load_scene_by_path(human.collider.as_str(), asset_server));
entity_commands.with_children(|parent| {
parent.spawn((
Limb::Base,
Animation::HumanFloat,
world::DespawnOnPlayerDeath,
SceneBundle {
scene: load_scene_by_path(human.base.as_str(), asset_server),
..default()
}
));
for limb in human.limbs {
let rot = if limb.mirror {
Quat::from_rotation_y(180.0f32.to_radians())
} else {
Quat::IDENTITY
};
let mut parent_limb = parent.spawn((
limb.class,
Animation::HumanFloat,
world::DespawnOnPlayerDeath,
SceneBundle {
scene: load_scene_by_path(limb.path.as_str(), asset_server),
transform: Transform::from_translation(limb.pos).with_rotation(rot),
..default()
}
));
if limb.mirror {
parent_limb.insert(MirroredLimb);
}
if !limb.children.is_empty() {
parent_limb.with_children(|parent| {
for child_limb in limb.children {
let rot = if child_limb.mirror {
Quat::from_rotation_y(180.0f32.to_radians())
} else {
Quat::IDENTITY
};
let mut entity_commands = parent.spawn((
child_limb.class,
Animation::HumanFloat,
world::DespawnOnPlayerDeath,
SceneBundle {
scene: load_scene_by_path(child_limb.path.as_str(), asset_server),
transform: Transform::from_translation(child_limb.pos).with_rotation(rot),
..default()
}
));
if child_limb.mirror {
entity_commands.insert(MirroredLimb);
}
}
});
}
}
});
}
}
} else {
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> {
let path_string = path.to_string();
if let Some(handle) = asset_server.get_handle(&path_string) {
handle
} else {
asset_server.load(&path_string)
}
}
pub fn _build_body(
_name: String,
mut _entity_commands: EntityCommands,
) {
}
pub fn animate_skeleton_parts(
time: Res<Time>,
mut q_limb: Query<(&mut Transform, &Limb, &Animation, Option<&MirroredLimb>)>,
) {
let t = time.elapsed_seconds();
for (mut trans, limb, animation, mirror) in &mut q_limb {
let mirror = mirror.is_some();
match animation {
Animation::HumanFloat =>
animate_human_float(&mut trans, &limb, mirror, t),
}
}
}
fn rot(trans: &mut Transform, x: f32, y: f32, z: f32) {
trans.rotation = Quat::from_euler(
EulerRot::XYZ,
x.to_radians(),
y.to_radians(),
z.to_radians()
);
}
pub fn animate_human_float(mut trans: &mut Transform, limb: &Limb, mirror: bool, t: f32) {
// x: lean head forward/backward
// y: close/open arms together in front of the torso
// z: spread legs sidewards
let m = {|angle| if mirror { 180f32 + angle } else { angle }};
match limb {
Limb::UpperArmRight => rot(&mut trans,
0.0,
m(40.0 + 5.0 * (t * 0.5).sin()),
20.0 + 10.0 * (t * 0.5).sin(),
),
Limb::UpperArmLeft => rot(&mut trans,
0.0,
m(-(40.0 + 5.0 * (t * 0.5).sin())),
20.0 + 10.0 * (t * 0.5).sin(),
),
Limb::LowerArmRight => rot(&mut trans,
0.0,
m(20.0),
-20.0,
),
Limb::LowerArmLeft => rot(&mut trans,
0.0,
m(-20.0),
-20.0,
),
Limb::UpperLegRight => rot(&mut trans,
-30.0 + 10.0 * (t * 0.5).sin(),
0.0,
-20.0 + 2.5 * (t * 0.5).cos(),
),
Limb::UpperLegLeft => rot(&mut trans,
-30.0 + 10.0 * (t * 0.5).sin(),
0.0,
20.0 - 2.5 * (t * 0.5).cos(),
),
Limb::LowerLegRight => rot(&mut trans,
35.0 + 5.0 * (t * 0.5).sin(),
0.0,
0.0,
),
Limb::LowerLegLeft => rot(&mut trans,
35.0 + 5.0 * (t * 0.5).sin(),
0.0,
0.0,
),
_ => {},
}
}
fn play_animations(
mut players: Query<&mut AnimationPlayer, Added<AnimationPlayer>>,
asset_server: Res<AssetServer>,
) {
for mut player in &mut players {
let animation = asset_server.load("models/suit_v2/suit_v2.glb#Animation0");
player.play(animation.clone()).repeat();
}
}
//fn play_animations(
// players: Query<(Entity, &Parent, &AnimationPlayer), Added<AnimationPlayer>>,
// q_parents: Query<(Entity, &Parent)>,
// world: &World,
//) {
// for (entity, parent, player) in &players {
// info!("Got player!");
//// dbg!(world.inspect_entity(entity));
//// let parent_entity = q_parents.get(parent.get());
//// if let Ok((parent_entity, parent_parents)) = parent_entity {
//// //dbg!(world.inspect_entity(parent_entity));
//// let parent_entity = q_parents.get(parent_parents.get());
//// if let Ok((parent_entity, parent_parents)) = parent_entity {
//// dbg!(world.inspect_entity(parent_entity));
//// }
//// }
// //dbg!(player);
// //player.play(animations.0[0].clone_weak()).repeat();
// }
//}

View file

@ -1,158 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="256"
height="256"
viewBox="0 0 67.733334 67.733334"
version="1.1"
id="svg1"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
sodipodi:docname="dashboard_cruise_control.svg"
xml:space="preserve"
inkscape:export-filename="../../assets/sprites/dashboard_cruise_control.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#000000"
bordercolor="#252525"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#000000"
inkscape:document-units="px"
inkscape:zoom="1.1697646"
inkscape:cx="-291.08422"
inkscape:cy="368.87765"
inkscape:window-width="2880"
inkscape:window-height="1765"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="g227"
showgrid="true"
inkscape:export-bgcolor="#00000000"><inkscape:grid
id="grid1"
units="px"
originx="0"
originy="0"
spacingx="0.26458334"
spacingy="0.26458334"
empcolor="#0099e5"
empopacity="0.30196078"
color="#0099e5"
opacity="0.14901961"
empspacing="8"
dotted="false"
gridanglex="30"
gridanglez="30"
visible="true" /></sodipodi:namedview><defs
id="defs1"><marker
style="overflow:visible"
id="marker3"
refX="0"
refY="0"
orient="-22.00"
inkscape:stockid="Concave triangle arrow"
markerWidth="0.5"
markerHeight="0.5"
viewBox="0 0 1 1"
inkscape:isstock="true"
inkscape:collect="always"
preserveAspectRatio="xMidYMid"><path
d="M -2,-4 9,0 -2,4 c 2,-2.33 2,-5.66 0,-8 z"
style="fill:context-stroke;fill-rule:evenodd;stroke:none"
id="path3"
transform="scale(0.7)" /></marker><filter
style="color-interpolation-filters:sRGB"
id="filter1"
inkscape:label="bloom"
x="-0.14372881"
y="-0.26839938"
width="1.2874576"
height="1.4443655"><feConvolveMatrix
order="1 1"
kernelMatrix="1.0000000 "
id="feConvolveMatrix1"
result="result1"
bias="0.090000000000000024"
preserveAlpha="true"
edgeMode="none"
divisor="0" /><feGaussianBlur
stdDeviation="2"
id="feGaussianBlur1"
in="SourceGraphic" /><feComposite
id="feComposite1"
operator="arithmetic"
k1="1"
k2="0.99999999999999922"
k3="1"
k4="0"
in2="result1" /></filter></defs><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"><g
id="g227"
style="filter:url(#filter1)"><path
style="fill:none;stroke:#007e43;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path1"
inkscape:label="circle"
sodipodi:type="arc"
sodipodi:cx="35.964756"
sodipodi:cy="38.081425"
sodipodi:rx="19.068579"
sodipodi:ry="19.068579"
sodipodi:start="2.3561945"
sodipodi:end="0.78539816"
sodipodi:arc-type="arc"
d="m 22.481235,51.564946 a 19.068579,19.068579 0 0 1 0,-26.967043 19.068579,19.068579 0 0 1 26.967042,0 19.068579,19.068579 0 0 1 0,26.967043"
sodipodi:open="true" /><circle
style="fill:#007e43;fill-opacity:1;stroke:none;stroke-width:0.318295;stroke-linecap:round;stroke-linejoin:round"
id="path2"
cx="35.983334"
cy="38.100002"
r="4.0741858" /><path
style="fill:none;fill-opacity:1;stroke:#007e43;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 22.489583,51.593753 2.910416,-2.910418"
id="path5"
sodipodi:nodetypes="cc" /><path
style="fill:none;fill-opacity:1;stroke:#007e43;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 49.448277,51.564944 46.566668,48.683335"
id="path6"
sodipodi:nodetypes="cc" /><path
style="fill:none;fill-opacity:1;stroke:#007e43;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 22.754167,24.870834 25.4,27.516667"
id="path7" /><path
style="fill:none;fill-opacity:1;stroke:#007e43;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 49.477086,24.60625 -2.910417,2.910417"
id="path8" /><path
style="fill:none;fill-opacity:1;stroke:#007e43;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 16.933333,38.100002 h 4.233334"
id="path9" /><path
style="fill:none;fill-opacity:1;stroke:#007e43;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 55.033336,38.100002 H 50.800002"
id="path10" /><path
style="fill:none;fill-opacity:1;stroke:#007e43;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 35.983335,19.05 v 4.233334"
id="path11" /><path
style="fill:none;fill-opacity:1;stroke:#007e43;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 35.983335,38.100002 -6.35,-6.35"
id="path12" /><path
style="fill:none;stroke:#007e43;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#marker3)"
id="circle2"
inkscape:label="circle"
sodipodi:type="arc"
sodipodi:cx="35.983334"
sodipodi:cy="38.100002"
sodipodi:rx="25.4"
sodipodi:ry="25.4"
sodipodi:start="3.5255651"
sodipodi:end="4.1364303"
sodipodi:arc-type="arc"
d="M 12.432864,28.584995 A 25.4,25.4 0 0 1 22.149502,16.797771"
sodipodi:open="true" /></g></g></svg>

Before

Width:  |  Height:  |  Size: 6.5 KiB

View file

@ -1,115 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="256"
height="256"
viewBox="0 0 67.733334 67.733334"
version="1.1"
id="svg1"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
sodipodi:docname="dashboard_highbeams.svg"
xml:space="preserve"
inkscape:export-filename="../../assets/sprites/dashboard_highbeams.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#000000"
bordercolor="#252525"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#000000"
inkscape:document-units="px"
inkscape:zoom="0.90142722"
inkscape:cx="-154.19991"
inkscape:cy="8.3201393"
inkscape:window-width="2880"
inkscape:window-height="1673"
inkscape:window-x="0"
inkscape:window-y="92"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:export-bgcolor="#00000000"><inkscape:grid
id="grid1"
units="px"
originx="0"
originy="0"
spacingx="0.26458334"
spacingy="0.26458334"
empcolor="#0099e5"
empopacity="0.30196078"
color="#0099e5"
opacity="0.14901961"
empspacing="4"
dotted="false"
gridanglex="30"
gridanglez="30"
visible="false" /></sodipodi:namedview><defs
id="defs1"><filter
style="color-interpolation-filters:sRGB"
id="filter1"
inkscape:label="bloom"
x="-0.18727821"
y="-0.30100527"
width="1.3832762"
height="1.6020105"><feConvolveMatrix
order="1 1"
kernelMatrix="1.0000000 "
id="feConvolveMatrix1"
result="result1"
bias="0.30000000000000004"
preserveAlpha="true"
edgeMode="none"
divisor="0" /><feGaussianBlur
stdDeviation="3"
id="feGaussianBlur1"
in="SourceGraphic" /><feComposite
id="feComposite1"
operator="arithmetic"
k1="1"
k2="1.5"
k3="1"
k4="0"
in2="result1" /></filter></defs><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"><g
id="g227"
style="filter:url(#filter1)"><path
style="font-variation-settings:'wght' 700;fill:none;fill-opacity:1;stroke:#0000ff;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 22.225001,44.450003 H 11.641667"
id="path2-0-1-7"
sodipodi:nodetypes="cc"
inkscape:label="ray5" /><path
style="font-variation-settings:'wght' 700;fill:none;fill-opacity:1;stroke:#0000ff;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 22.225001,39.158336 H 11.641667"
id="path2-0-1"
sodipodi:nodetypes="cc"
inkscape:label="ray4" /><path
style="font-variation-settings:'wght' 700;fill:none;fill-opacity:1;stroke:#0000ff;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 22.225001,33.866669 H 11.641667"
id="path2"
sodipodi:nodetypes="cc"
inkscape:label="ray3" /><path
style="font-variation-settings:'wght' 700;fill:none;fill-opacity:1;stroke:#0000ff;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 22.225001,28.575002 H 11.641667"
id="path2-0"
sodipodi:nodetypes="cc"
inkscape:label="ray2" /><path
style="font-variation-settings:'wght' 700;fill:none;fill-opacity:1;stroke:#0000ff;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 22.225001,23.283335 H 11.641667"
id="path2-0-9"
sodipodi:nodetypes="cc"
inkscape:label="ray1" /><path
style="font-variation-settings:'wght' 700;fill:none;fill-opacity:1;stroke:#0000ff;stroke-width:3.43958;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 30.691669,19.050001 c -4.233334,11.641668 -4.233334,17.991668 0,29.633335 10.583333,0 26.608074,-4.283783 26.458334,-14.816667 C 57.002975,23.524523 41.275002,19.050001 30.691669,19.050001 Z"
id="path1"
sodipodi:nodetypes="ccsc"
inkscape:label="lamp" /></g></g></svg>

Before

Width:  |  Height:  |  Size: 4.7 KiB

View file

@ -1,121 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="256"
height="256"
viewBox="0 0 67.733334 67.733334"
version="1.1"
id="svg1"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
sodipodi:docname="dashboard_leak.svg"
xml:space="preserve"
inkscape:export-filename="../../assets/sprites/dashboard_leak.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#000000"
bordercolor="#252525"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#000000"
inkscape:document-units="px"
inkscape:zoom="4.6790582"
inkscape:cx="149.17532"
inkscape:cy="102.69161"
inkscape:window-width="2880"
inkscape:window-height="1765"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="g227"
showgrid="true"
inkscape:export-bgcolor="#00000000"><inkscape:grid
id="grid1"
units="px"
originx="0"
originy="0"
spacingx="0.26458334"
spacingy="0.26458334"
empcolor="#0099e5"
empopacity="0.30196078"
color="#0099e5"
opacity="0.14901961"
empspacing="8"
dotted="false"
gridanglex="30"
gridanglez="30"
visible="true" /></sodipodi:namedview><defs
id="defs1"><filter
style="color-interpolation-filters:sRGB"
id="filter1"
inkscape:label="bloom"
x="-0.1510532"
y="-0.12537775"
width="1.2709421"
height="1.2236664"><feConvolveMatrix
order="1 1"
kernelMatrix="1.0000000 "
id="feConvolveMatrix1"
result="result1"
bias="0.090000000000000024"
preserveAlpha="true"
edgeMode="none"
divisor="0" /><feGaussianBlur
stdDeviation="2"
id="feGaussianBlur1"
in="SourceGraphic" /><feComposite
id="feComposite1"
operator="arithmetic"
k1="1"
k2="0.99999999999999922"
k3="1"
k4="0"
in2="result1" /></filter></defs><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"><g
id="g227"
style="filter:url(#filter1)"><path
style="font-variation-settings:'wght' 400;display:inline;fill:none;stroke:#a6720b;stroke-width:2.64583;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 50.800004,44.450002 c 0,0 12.7,-31.750001 -16.933335,-31.750001 -29.6333354,0 -16.933334,31.749995 -16.933334,31.749995 z"
id="path3"
sodipodi:nodetypes="cscc"
inkscape:label="Helmet" /><path
style="font-variation-settings:'wght' 400;display:none;fill:#be1251;fill-opacity:1;stroke:#be1251;stroke-width:1.32292;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:normal"
d="m 45.73128,27.516669 -4.233333,8.466668 -7.455338,-7.65886 5.338672,9.775527 -8.466669,0.748606 6.350002,1.368061 -4.233335,7.809454 6.350002,-5.692788 0.843071,7.166799 1.273595,-7.166799 5.068722,6.349999 -2.952055,-6.349999 7.383041,3.63801 -7.383042,-5.754678 8.466668,-2.799589 -7.623804,-0.678689 z"
id="path4"
sodipodi:nodetypes="ccccccccccccccccc" /><path
id="path6"
style="font-variation-settings:'wght' 400;display:inline;fill:#a6720b;fill-opacity:1;stroke:none;stroke-width:1.32292;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:normal"
d="m 45.905214,23.415628 2.248956,4.89479 0.264583,4.497917 -0.661455,0.396877 -2.513542,0.926044 -0.264583,-4.45091 -0.529167,4.45091 -4.762504,-0.264588 -1.322916,-3.96875 1.645173,-7.282649 -2.17434,7.282649 -6.792366,-4.10104 6.527783,4.630206 1.322916,3.439584 -3.175,2.38125 -3.107304,-0.59893 -0.332279,-3.898987 0.08113,3.832324 -3.256132,0.930176 3.082499,-0.599447 3.280937,0.906922 -0.542603,2.867525 -6.35,6.056995 22.754167,-0.765328 3.439586,-14.287499 -5.55625,1.852083 -0.529167,-4.7625 z m 4.233334,9.789584 4.101039,-0.926043 -2.381251,6.614583 -3.96875,-0.264584 -2.645831,-3.968748 2.778126,-0.926041 z m -11.244796,1.190622 1.5875,4.7625 -3.96875,-0.529166 v -1.852084 z m 0.529167,0 5.291669,0.264586 2.38125,4.233334 -5.820834,0.529166 z m 11.641667,6.350002 -1.058148,1.984376 h -8.731436 v -2.778128 l 6.350001,-0.529165 z m -15.08125,-1.058335 4.233333,0.529168 -0.264581,3.175002 -7.143752,-3e-6 z"
sodipodi:nodetypes="cccccccccccccccccccccccccccccccccccccccccccccccccccccccc"
inkscape:label="split" /><path
style="font-variation-settings:'wght' 400;display:none;fill:none;fill-opacity:1;stroke:#be1251;stroke-width:1.32292;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"
d="m 40.21667,57.150004 c 6.349999,-4.233334 14.816667,4.233333 21.166668,0"
id="path13"
sodipodi:nodetypes="cc" /><path
style="font-variation-settings:'wght' 400;display:none;fill:none;fill-opacity:1;stroke:#be1251;stroke-width:1.32292;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="m 40.21667,60.227636 c 6.349999,-4.665132 14.816667,3.801535 21.166668,0"
id="path14"
sodipodi:nodetypes="cc" /><path
style="font-variation-settings:'wght' 400;display:none;fill:none;fill-opacity:1;stroke:#be1251;stroke-width:1.32292;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
d="m 40.21667,63.305268 c 6.349999,-5.096931 14.816667,3.369737 21.166668,0"
id="path15"
sodipodi:nodetypes="cc" /><text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:16.9333px;font-family:Yupiter;-inkscape-font-specification:'Yupiter, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;fill:#a6720b;fill-opacity:1;stroke:none;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:normal"
x="16.933334"
y="61.383339"
id="text19"><tspan
sodipodi:role="line"
id="tspan19"
style="stroke-width:2.64583;stroke:none;fill:#a6720b;fill-opacity:1"
x="16.933334"
y="61.383339">LEAK</tspan></text></g></g></svg>

Before

Width:  |  Height:  |  Size: 6.8 KiB

View file

@ -1,108 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="256"
height="256"
viewBox="0 0 67.733334 67.733334"
version="1.1"
id="svg1"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
sodipodi:docname="dashboard_radioactivity.svg"
xml:space="preserve"
inkscape:export-filename="../../assets/sprites/dashboard_radioactivity.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#000000"
bordercolor="#252525"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#000000"
inkscape:document-units="px"
inkscape:zoom="2.3395291"
inkscape:cx="58.131356"
inkscape:cy="123.31541"
inkscape:window-width="2880"
inkscape:window-height="1765"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="g227"
showgrid="true"
inkscape:export-bgcolor="#00000000"><inkscape:grid
id="grid1"
units="px"
originx="0"
originy="0"
spacingx="0.26458334"
spacingy="0.26458334"
empcolor="#0099e5"
empopacity="0.30196078"
color="#0099e5"
opacity="0.14901961"
empspacing="8"
dotted="false"
gridanglex="30"
gridanglez="30"
visible="true" /></sodipodi:namedview><defs
id="defs1"><filter
style="color-interpolation-filters:sRGB"
id="filter1"
inkscape:label="bloom"
x="-0.20615604"
y="-0.23572759"
width="1.4123121"
height="1.4714552"><feConvolveMatrix
order="1 1"
kernelMatrix="1.0000000 "
id="feConvolveMatrix1"
result="result1"
bias="0.090000000000000024"
preserveAlpha="true"
edgeMode="none"
divisor="0" /><feGaussianBlur
stdDeviation="2"
id="feGaussianBlur1"
in="SourceGraphic" /><feComposite
id="feComposite1"
operator="arithmetic"
k1="1"
k2="0.99999999999999922"
k3="1"
k4="0"
in2="result1" /></filter></defs><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"><g
id="g227"
style="filter:url(#filter1)"><path
id="path1"
style="fill:#a6720b;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round"
d="m 57.150003,33.866669 c 0,8.462523 -4.59159,16.258741 -11.992506,20.362487 l -9.229858,-16.645624 2.172363,-3.716863 z"
sodipodi:nodetypes="ccccc" /><path
id="path2"
clip-path="none"
style="fill:#a6720b;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round"
d="M 22.225001,54.030628 C 15.021112,49.871461 10.583334,42.185003 10.583334,33.866669 l 19.050001,0 2.007676,3.854952 z"
sodipodi:nodetypes="ccccc" /><circle
style="fill:none;fill-opacity:1;stroke:#a6720b;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="circle7"
cx="33.866669"
cy="33.866669"
r="4.2333336" /><path
id="path3"
style="fill:#a6720b;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round"
d="m 22.225001,13.702709 c 7.203889,-4.1591673 16.079447,-4.1591669 23.283336,10e-7 l -9.525001,16.497784 -4.240635,-0.01264 z"
sodipodi:nodetypes="ccccc" /><circle
style="fill:none;fill-opacity:1;stroke:#a6720b;stroke-width:2.64583336;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path4"
cx="33.866669"
cy="33.866669"
r="22.408022" /></g></g></svg>

Before

Width:  |  Height:  |  Size: 4 KiB

View file

@ -1,133 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="256"
height="256"
viewBox="0 0 67.733334 67.733334"
version="1.1"
id="svg1"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
sodipodi:docname="dashboard_rotation_stabiliser.svg"
xml:space="preserve"
inkscape:export-filename="../../assets/sprites/dashboard_rotation_stabiliser.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#000000"
bordercolor="#252525"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#000000"
inkscape:document-units="px"
inkscape:zoom="1.6542969"
inkscape:cx="211.87249"
inkscape:cy="197.9693"
inkscape:window-width="2880"
inkscape:window-height="1673"
inkscape:window-x="0"
inkscape:window-y="92"
inkscape:window-maximized="1"
inkscape:current-layer="g227"
showgrid="true"
inkscape:export-bgcolor="#00000000"><inkscape:grid
id="grid1"
units="px"
originx="0"
originy="0"
spacingx="0.26458334"
spacingy="0.26458334"
empcolor="#0099e5"
empopacity="0.30196078"
color="#0099e5"
opacity="0.14901961"
empspacing="8"
dotted="false"
gridanglex="30"
gridanglez="30"
visible="true" /></sodipodi:namedview><defs
id="defs1"><marker
style="overflow:visible"
id="ConcaveTriangle"
refX="0"
refY="0"
orient="auto-start-reverse"
inkscape:stockid="Concave triangle arrow"
markerWidth="0.5"
markerHeight="0.5"
viewBox="0 0 1 1"
inkscape:isstock="true"
inkscape:collect="always"
preserveAspectRatio="xMidYMid"><path
transform="scale(0.7)"
d="M -2,-4 9,0 -2,4 c 2,-2.33 2,-5.66 0,-8 z"
style="fill:context-stroke;fill-rule:evenodd;stroke:none"
id="path7" /></marker><filter
style="color-interpolation-filters:sRGB"
id="filter1"
inkscape:label="bloom"
x="-0.1605499"
y="-0.1605499"
width="1.3210998"
height="1.3210998"><feConvolveMatrix
order="1 1"
kernelMatrix="1.0000000 "
id="feConvolveMatrix1"
result="result1"
bias="0.090000000000000024"
preserveAlpha="true"
edgeMode="none"
divisor="0" /><feGaussianBlur
stdDeviation="2"
id="feGaussianBlur1"
in="SourceGraphic" /><feComposite
id="feComposite1"
operator="arithmetic"
k1="1"
k2="0.99999999999999922"
k3="1"
k4="0"
in2="result1" /></filter></defs><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"><g
id="g227"
style="filter:url(#filter1)"><circle
style="fill:none;stroke:#007e43;stroke-width:2.64583336;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path1"
cx="33.848091"
cy="33.848091"
r="19.068579"
inkscape:label="circle" /><path
style="fill:none;stroke:#007e43;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ConcaveTriangle)"
id="circle2"
inkscape:label="circle"
sodipodi:type="arc"
sodipodi:cx="33.848091"
sodipodi:cy="33.848091"
sodipodi:rx="25.4"
sodipodi:ry="25.4"
sodipodi:start="2.6179939"
sodipodi:end="3.8397244"
sodipodi:open="true"
sodipodi:arc-type="arc"
d="M 11.851046,46.54809 A 25.4,25.4 0 0 1 14.390563,17.521285" /><path
style="fill:none;stroke:#007e43;stroke-width:2.64583;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;marker-start:url(#ConcaveTriangle)"
id="path2"
inkscape:label="circle"
sodipodi:type="arc"
sodipodi:cx="33.848091"
sodipodi:cy="33.848091"
sodipodi:rx="25.4"
sodipodi:ry="25.4"
sodipodi:start="5.7595865"
sodipodi:end="0.6981317"
sodipodi:open="true"
sodipodi:arc-type="arc"
d="M 55.845136,21.148091 A 25.4,25.4 0 0 1 53.30562,50.174896" /></g></g></svg>

Before

Width:  |  Height:  |  Size: 4.6 KiB

View file

@ -1,99 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="256"
height="256"
viewBox="0 0 67.733334 67.733334"
version="1.1"
id="svg1"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
sodipodi:docname="gauge_battery.svg"
xml:space="preserve"
inkscape:export-filename="../../assets/sprites/gauge_o2.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#000000"
bordercolor="#252525"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#000000"
inkscape:document-units="px"
inkscape:zoom="3.6057089"
inkscape:cx="65.590431"
inkscape:cy="83.340062"
inkscape:window-width="2880"
inkscape:window-height="1581"
inkscape:window-x="0"
inkscape:window-y="184"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:export-bgcolor="#00000000"><inkscape:grid
id="grid1"
units="px"
originx="0"
originy="0"
spacingx="0.26458334"
spacingy="0.26458334"
empcolor="#0099e5"
empopacity="0.30196078"
color="#0099e5"
opacity="0.14901961"
empspacing="4"
dotted="false"
gridanglex="30"
gridanglez="30"
visible="true" /></sodipodi:namedview><defs
id="defs1"><filter
style="color-interpolation-filters:sRGB"
id="filter1"
inkscape:label="bloom"
x="-0.18727821"
y="-0.30100527"
width="1.3832762"
height="1.6020105"><feConvolveMatrix
order="1 1"
kernelMatrix="1.0000000 "
id="feConvolveMatrix1"
result="result1"
bias="0.30000000000000004"
preserveAlpha="true"
edgeMode="none"
divisor="0" /><feGaussianBlur
stdDeviation="3"
id="feGaussianBlur1"
in="SourceGraphic" /><feComposite
id="feComposite1"
operator="arithmetic"
k1="1"
k2="1.5"
k3="1"
k4="0"
in2="result1" /></filter></defs><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"><path
style="font-variation-settings:'wght' 400;fill:none;fill-opacity:1;stroke:#be1251;stroke-width:5.29166672;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 6.7381087,16.059295 4.5e-6,40.702599 54.2571138,-2.5e-5 -3e-6,-40.702598 h -7.398697 l -14.797395,2.4e-5 14.797397,-1.271963 -2e-6,-3.815891 H 41.265364 v 5.08783 l -14.797395,2.4e-5 -14.180835,2.4e-5 14.180837,-1.271963 -2e-6,-3.815891 H 14.136807 v 5.08783 z"
id="path9"
sodipodi:nodetypes="ccccccccccccccccc" /><path
style="font-variation-settings:'wght' 400;fill:none;fill-opacity:1;stroke:#be1251;stroke-width:5.29167;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 13.758334,30.691669 13.758334,0"
id="path10"
sodipodi:nodetypes="cc" /><path
style="font-variation-settings:'wght' 400;fill:none;fill-opacity:1;stroke:#be1251;stroke-width:5.29167;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 20.637501,23.283335 0,14.816667"
id="path11"
sodipodi:nodetypes="cc" /><path
style="font-variation-settings:'wght' 400;fill:none;fill-opacity:1;stroke:#be1251;stroke-width:5.29167;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 40.216669,30.691669 13.758334,0"
id="path12"
sodipodi:nodetypes="cc" /></g></svg>

Before

Width:  |  Height:  |  Size: 3.8 KiB

View file

@ -1,90 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="256"
height="256"
viewBox="0 0 67.733334 67.733334"
version="1.1"
id="svg1"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
sodipodi:docname="gauge_fuel.svg"
xml:space="preserve"
inkscape:export-filename="../../assets/sprites/gauge_fuel.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#000000"
bordercolor="#252525"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#000000"
inkscape:document-units="px"
inkscape:zoom="1.2748106"
inkscape:cx="174.53573"
inkscape:cy="209.05066"
inkscape:window-width="2880"
inkscape:window-height="1581"
inkscape:window-x="0"
inkscape:window-y="184"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:export-bgcolor="#00000000"><inkscape:grid
id="grid1"
units="px"
originx="0"
originy="0"
spacingx="0.26458334"
spacingy="0.26458334"
empcolor="#0099e5"
empopacity="0.30196078"
color="#0099e5"
opacity="0.14901961"
empspacing="4"
dotted="false"
gridanglex="30"
gridanglez="30"
visible="true" /></sodipodi:namedview><defs
id="defs1"><filter
style="color-interpolation-filters:sRGB"
id="filter1"
inkscape:label="bloom"
x="-0.18727821"
y="-0.30100527"
width="1.3832762"
height="1.6020105"><feConvolveMatrix
order="1 1"
kernelMatrix="1.0000000 "
id="feConvolveMatrix1"
result="result1"
bias="0.30000000000000004"
preserveAlpha="true"
edgeMode="none"
divisor="0" /><feGaussianBlur
stdDeviation="3"
id="feGaussianBlur1"
in="SourceGraphic" /><feComposite
id="feComposite1"
operator="arithmetic"
k1="1"
k2="1.5"
k3="1"
k4="0"
in2="result1" /></filter></defs><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"><path
id="path7"
style="font-variation-settings:'wght' 400;fill:#be1251;fill-opacity:1;stroke:#be1251;stroke-width:3.15574;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="M 15.238993,4.762497 12.381998,7.6195116 V 56.188732 H 9.5250001 v 5.71403 H 43.808972 v -5.71403 H 40.951957 V 7.6195116 L 38.094967,4.762497 Z m 2.856996,2.8570146 h 17.141982 l 2.856996,2.8570144 V 21.904584 H 15.238993 V 10.476526 Z" /><path
style="font-variation-settings:'wght' 400;fill:#be1251;fill-opacity:1;stroke:#be1251;stroke-width:3.15574;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 40.951957,33.332643 h 4.285496 c 0,0 0.454708,18.221438 7.142509,22.856089 0.782728,0.542444 2.124714,0.608879 2.856987,0 5.958093,-4.954194 4.654007,-11.242735 3.600464,-18.919535 -0.865387,-6.306034 -4.427679,-11.345209 -6.086401,-17.49038 -1.542319,-5.714029 2.485937,-0.731247 2.485937,-0.731247 L 47.65952,8.0073253 c 2.889759,10.9514577 9.321511,25.8668957 10.434417,31.0393197 1.201936,5.58618 2.595107,15.215356 -4.210071,15.730237 -8.138417,0.615734 -7.527568,-24.30128 -7.527568,-24.30128 h -5.404341 z"
id="path8"
sodipodi:nodetypes="ccssssccssccc" /></g></svg>

Before

Width:  |  Height:  |  Size: 3.7 KiB

View file

@ -1,92 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="256"
height="256"
viewBox="0 0 67.733334 67.733334"
version="1.1"
id="svg1"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
sodipodi:docname="gauge_heart.svg"
xml:space="preserve"
inkscape:export-filename="../../assets/sprites/gauge_heart.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#000000"
bordercolor="#252525"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#000000"
inkscape:document-units="px"
inkscape:zoom="2.5496212"
inkscape:cx="86.483435"
inkscape:cy="137.07919"
inkscape:window-width="2880"
inkscape:window-height="1581"
inkscape:window-x="0"
inkscape:window-y="184"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:export-bgcolor="#00000000"><inkscape:grid
id="grid1"
units="px"
originx="0"
originy="0"
spacingx="0.26458334"
spacingy="0.26458334"
empcolor="#0099e5"
empopacity="0.30196078"
color="#0099e5"
opacity="0.14901961"
empspacing="4"
dotted="false"
gridanglex="30"
gridanglez="30"
visible="false" /></sodipodi:namedview><defs
id="defs1"><filter
style="color-interpolation-filters:sRGB"
id="filter1"
inkscape:label="bloom"
x="-0.18727821"
y="-0.30100527"
width="1.3832762"
height="1.6020105"><feConvolveMatrix
order="1 1"
kernelMatrix="1.0000000 "
id="feConvolveMatrix1"
result="result1"
bias="0.30000000000000004"
preserveAlpha="true"
edgeMode="none"
divisor="0" /><feGaussianBlur
stdDeviation="3"
id="feGaussianBlur1"
in="SourceGraphic" /><feComposite
id="feComposite1"
operator="arithmetic"
k1="1"
k2="1.5"
k3="1"
k4="0"
in2="result1" /></filter></defs><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"><g
id="text2"
transform="matrix(2.66899,0,0,2.5086282,-27.690165,-683.1161)"><path
style="color:#000000;-inkscape-font-specification:'Fira Code, @wght=700';fill:#be1251;stroke-linejoin:round;-inkscape-stroke:none"
d="m 27.81583,276.35504 q 1.93286,0 3.004718,1.30028 1.071858,1.28272 1.071858,3.05744 0,2.05585 -1.177287,4.32257 -1.159715,2.24915 -3.162861,4.79701 -1.985573,2.54786 -4.445576,5.46472 -2.460003,-2.91686 -4.463148,-5.44715 -1.985574,-2.54786 -3.162861,-4.79701 -1.159716,-2.26671 -1.159716,-4.34014 0,-1.77472 1.071859,-3.05744 1.089429,-1.30028 3.004717,-1.30028 1.827431,0 2.864147,0.93128 1.054287,0.93129 1.845002,2.65329 0.790715,-1.722 1.845002,-2.65329 1.054287,-0.93128 2.864146,-0.93128 z"
id="path13" /><path
style="color:#000000;-inkscape-font-specification:'Fira Code, @wght=700';fill:#be1251;stroke-linejoin:round;-inkscape-stroke:none"
d="m 18.398437,273.99414 c -1.811015,0 -3.650852,0.75572 -4.814453,2.14453 l -0.002,0.004 c -1.05803,1.26617 -1.621093,2.91197 -1.621093,4.57031 -1e-6,1.82236 0.511901,3.64507 1.417968,5.41602 0.0032,0.007 0.0065,0.013 0.0098,0.0195 0.867874,1.65803 2.000897,3.36656 3.392578,5.15234 0.0039,0.005 0.0078,0.009 0.01172,0.0137 1.353323,1.70947 2.856469,3.54357 4.509765,5.50391 0.943088,1.11697 2.664334,1.11697 3.607422,0 1.654323,-1.96155 3.153682,-3.8025 4.498047,-5.52734 l 0.002,-0.004 c 1.405851,-1.78852 2.542492,-3.50233 3.402344,-5.16992 0.917572,-1.7684 1.439453,-3.58511 1.439453,-5.4043 0,-1.65539 -0.560843,-3.29723 -1.615234,-4.5625 -1.157815,-1.40068 -3.004809,-2.15625 -4.820313,-2.15625 -1.846213,0.18715 -3.522589,1.53018 -4.710937,2.97852 -1.187313,-1.43868 -2.857445,-2.81289 -4.707032,-2.97852 z m 0,4.7207 c 0.817322,0 1.106937,0.16803 1.285157,0.32813 0.0052,0.004 0.0104,0.008 0.01563,0.0117 0.381287,0.3368 0.833252,0.93603 1.261718,1.86914 0.841986,1.83471 3.44903,1.83471 4.291016,0 0.428467,-0.93311 0.880432,-1.53234 1.261719,-1.86914 0.200576,-0.17718 0.50102,-0.33985 1.302734,-0.33985 0.759271,0 0.911972,0.1119 1.183594,0.44141 0.0033,0.004 0.0065,0.008 0.0098,0.0117 0.371113,0.44412 0.521484,0.83697 0.521484,1.54492 0,0.92 -0.259838,1.98227 -0.910156,3.23438 l -0.0039,0.006 c -0.68583,1.3301 -1.656277,2.81266 -2.919921,4.41993 -0.002,0.003 -0.0039,0.005 -0.0059,0.008 -0.768666,0.98634 -1.710763,2.11007 -2.591797,3.18555 -0.881453,-1.06982 -1.819164,-2.18974 -2.59375,-3.16797 -1.252425,-1.6071 -2.224352,-3.08967 -2.925781,-4.42774 -0.636897,-1.2475 -0.898437,-2.31784 -0.898437,-3.25781 0,-0.70714 0.153241,-1.09947 0.523437,-1.54297 0.287152,-0.34215 0.453178,-0.45508 1.193359,-0.45508 z"
id="path14"
sodipodi:nodetypes="sccscccccccccscscssccccccsccscccccccsccc" /></g></g></svg>

Before

Width:  |  Height:  |  Size: 5.2 KiB

View file

@ -1,87 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="1024"
height="48"
viewBox="0 0 270.93334 12.7"
version="1.1"
id="svg1"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
sodipodi:docname="gauge_horizonal.svg"
xml:space="preserve"
inkscape:export-filename="../../assets/sprites/gauge_horizontal.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#000000"
bordercolor="#252525"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#000000"
inkscape:document-units="px"
inkscape:zoom="1.6326"
inkscape:cx="392.93152"
inkscape:cy="-30.319735"
inkscape:window-width="2880"
inkscape:window-height="1627"
inkscape:window-x="0"
inkscape:window-y="138"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:export-bgcolor="#00000000"><inkscape:grid
id="grid1"
units="px"
originx="0"
originy="0"
spacingx="0.26458334"
spacingy="0.26458334"
empcolor="#0099e5"
empopacity="0.30196078"
color="#0099e5"
opacity="0.14901961"
empspacing="4"
dotted="false"
gridanglex="30"
gridanglez="30"
visible="true" /></sodipodi:namedview><defs
id="defs1"><filter
style="color-interpolation-filters:sRGB"
id="filter1"
inkscape:label="bloom"
x="-0.18727821"
y="-0.30100527"
width="1.3832762"
height="1.6020105"><feConvolveMatrix
order="1 1"
kernelMatrix="1.0000000 "
id="feConvolveMatrix1"
result="result1"
bias="0.30000000000000004"
preserveAlpha="true"
edgeMode="none"
divisor="0" /><feGaussianBlur
stdDeviation="3"
id="feGaussianBlur1"
in="SourceGraphic" /><feComposite
id="feComposite1"
operator="arithmetic"
k1="1"
k2="1.5"
k3="1"
k4="0"
in2="result1" /></filter></defs><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"><path
style="font-variation-settings:'wght' 700;fill:none;stroke:#be1251;stroke-width:1.5875;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 0.79375004,0 v 11.906251 l 269.34584996,0 V 0"
id="path2"
sodipodi:nodetypes="cccc" /></g></svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

View file

@ -1,91 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="256"
height="256"
viewBox="0 0 67.733334 67.733334"
version="1.1"
id="svg1"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
sodipodi:docname="gauge_o2.svg"
xml:space="preserve"
inkscape:export-filename="../../assets/sprites/gauge_o2.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#000000"
bordercolor="#252525"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#000000"
inkscape:document-units="px"
inkscape:zoom="2.5496212"
inkscape:cx="84.52236"
inkscape:cy="46.477492"
inkscape:window-width="2880"
inkscape:window-height="1581"
inkscape:window-x="0"
inkscape:window-y="184"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:export-bgcolor="#00000000"><inkscape:grid
id="grid1"
units="px"
originx="0"
originy="0"
spacingx="0.26458334"
spacingy="0.26458334"
empcolor="#0099e5"
empopacity="0.30196078"
color="#0099e5"
opacity="0.14901961"
empspacing="4"
dotted="false"
gridanglex="30"
gridanglez="30"
visible="true" /></sodipodi:namedview><defs
id="defs1"><filter
style="color-interpolation-filters:sRGB"
id="filter1"
inkscape:label="bloom"
x="-0.18727821"
y="-0.30100527"
width="1.3832762"
height="1.6020105"><feConvolveMatrix
order="1 1"
kernelMatrix="1.0000000 "
id="feConvolveMatrix1"
result="result1"
bias="0.30000000000000004"
preserveAlpha="true"
edgeMode="none"
divisor="0" /><feGaussianBlur
stdDeviation="3"
id="feGaussianBlur1"
in="SourceGraphic" /><feComposite
id="feComposite1"
operator="arithmetic"
k1="1"
k2="1.5"
k3="1"
k4="0"
in2="result1" /></filter></defs><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"><path
style="font-size:25.4px;font-family:Yupiter;-inkscape-font-specification:'Yupiter, Normal';fill:#be1251;stroke:#be1251;stroke-width:2.68582;stroke-linejoin:round"
d="m 34.743546,43.194762 q 0,1.692365 -1.167149,2.91787 l -1.867438,1.867438 q -1.167147,1.167148 -2.859513,1.167148 H 17.586467 q -1.692364,0 -2.859513,-1.167148 L 12.801159,46.054275 Q 11.63401,44.887127 11.63401,43.194762 V 13.082338 q 0,-1.692364 1.167149,-2.917868 L 14.668597,8.2970329 Q 15.835745,7.1298846 17.52811,7.1298846 h 11.321336 q 1.692366,0 2.859513,1.225506 l 1.867438,1.8674374 q 1.167149,1.167146 1.167149,2.85951 z m -7.644822,0.641932 q 2.334297,0 2.334297,-2.334297 V 14.774704 q 0,-2.334297 -2.334297,-2.334297 h -7.819893 q -2.334296,0 -2.334296,2.334297 v 26.727693 q 0,2.334297 2.334296,2.334297 z"
id="text3"
aria-label="O" /><path
style="font-size:16.9333px;font-family:Yupiter;-inkscape-font-specification:'Yupiter, Normal';fill:#be1251;stroke:#be1251;stroke-width:2.68582;stroke-linejoin:round"
d="m 40.582604,35.589275 q 0,-1.55619 1.556194,-1.55619 H 52.48749 q 1.128242,0 1.90634,0.816981 l 1.244956,1.244966 q 0.778097,0.778106 0.778097,1.906333 v 7.975506 q 0,1.128228 -0.778097,1.945231 l -1.244956,1.244967 q -0.778098,0.778082 -1.90634,0.778082 h -6.808351 q -1.556194,0 -1.556194,1.556213 v 5.446677 q 0,1.556189 1.556194,1.556189 h 9.181549 q 1.556195,0 1.556195,1.55619 v 0.427963 q 0,1.556189 -1.556195,1.556189 H 44.47309 q -0.583574,0 -1.050431,-0.194601 -0.427955,-0.155543 -0.855907,-0.544654 l -1.128243,-0.972615 q -0.855905,-0.817004 -0.855905,-1.945255 v -8.09222 q 0,-0.583551 0.194532,-1.011514 0.194533,-0.466858 0.622478,-0.89482 l 1.20605,-1.206045 q 0.778098,-0.778107 1.906339,-0.778107 h 6.847256 q 1.556195,0 1.556195,-1.55619 v -5.719003 q 0,-1.55619 -1.556195,-1.55619 h -9.220451 q -1.556196,0 -1.556196,-1.556213 z"
id="text4"
aria-label="2" /></g></svg>

Before

Width:  |  Height:  |  Size: 4.4 KiB

View file

@ -1,95 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="256"
height="256"
viewBox="0 0 67.733334 67.733334"
version="1.1"
id="svg1"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
sodipodi:docname="gauge_suit.svg"
xml:space="preserve"
inkscape:export-filename="../../assets/sprites/gauge_suit.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#000000"
bordercolor="#252525"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#000000"
inkscape:document-units="px"
inkscape:zoom="0.6374053"
inkscape:cx="76.089734"
inkscape:cy="-262.78413"
inkscape:window-width="2880"
inkscape:window-height="1581"
inkscape:window-x="0"
inkscape:window-y="184"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:export-bgcolor="#00000000"><inkscape:grid
id="grid1"
units="px"
originx="0"
originy="0"
spacingx="0.26458334"
spacingy="0.26458334"
empcolor="#0099e5"
empopacity="0.30196078"
color="#0099e5"
opacity="0.14901961"
empspacing="8"
dotted="false"
gridanglex="30"
gridanglez="30"
visible="true" /></sodipodi:namedview><defs
id="defs1"><filter
style="color-interpolation-filters:sRGB"
id="filter1"
inkscape:label="bloom"
x="-0.18727821"
y="-0.30100527"
width="1.3832762"
height="1.6020105"><feConvolveMatrix
order="1 1"
kernelMatrix="1.0000000 "
id="feConvolveMatrix1"
result="result1"
bias="0.30000000000000004"
preserveAlpha="true"
edgeMode="none"
divisor="0" /><feGaussianBlur
stdDeviation="3"
id="feGaussianBlur1"
in="SourceGraphic" /><feComposite
id="feComposite1"
operator="arithmetic"
k1="1"
k2="1.5"
k3="1"
k4="0"
in2="result1" /></filter></defs><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"><path
style="font-variation-settings:'wght' 400;fill:none;stroke:#be1251;stroke-width:5.29167;stroke-linejoin:round;stroke-dasharray:none"
d="m 40.216669,8.4666671 h -12.7 l -2.116667,2.1166669 v 10.583334 l -8.466668,0 -4.233333,4.233334 v 14.816667 h 8.466667 v 21.166668 h 8.466667 V 52.91667 h 8.466667 v 8.466667 h 8.466667 V 40.216669 h 8.466668 V 25.400002 l -4.233334,-4.233334 -8.466667,0 V 10.583334 Z"
id="path1"
sodipodi:nodetypes="ccccccccccccccccccccc" /><path
style="font-variation-settings:'wght' 400;fill:none;stroke:#be1251;stroke-width:5.29167;stroke-linejoin:round;stroke-dasharray:none"
d="M 33.866669,23.283335 V 40.216669"
id="path2"
sodipodi:nodetypes="cc" /><path
style="font-variation-settings:'wght' 400;fill:none;stroke:#be1251;stroke-width:5.29167;stroke-linejoin:round;stroke-dasharray:none"
d="M 25.400002,31.750002 H 42.333336"
id="path3"
sodipodi:nodetypes="cc" /></g></svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

View file

@ -1,87 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="42"
height="512"
viewBox="0 0 11.1125 135.46667"
version="1.1"
id="svg1"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
sodipodi:docname="gauge_vertical.svg"
xml:space="preserve"
inkscape:export-filename="../../assets/sprites/gauge_horizontal.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#000000"
bordercolor="#252525"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#000000"
inkscape:document-units="px"
inkscape:zoom="3.2652"
inkscape:cx="-50.686022"
inkscape:cy="52.523582"
inkscape:window-width="2880"
inkscape:window-height="1627"
inkscape:window-x="0"
inkscape:window-y="138"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:export-bgcolor="#00000000"><inkscape:grid
id="grid1"
units="px"
originx="0"
originy="0"
spacingx="0.26458334"
spacingy="0.26458334"
empcolor="#0099e5"
empopacity="0.30196078"
color="#0099e5"
opacity="0.14901961"
empspacing="4"
dotted="false"
gridanglex="30"
gridanglez="30"
visible="true" /></sodipodi:namedview><defs
id="defs1"><filter
style="color-interpolation-filters:sRGB"
id="filter1"
inkscape:label="bloom"
x="-0.18727821"
y="-0.30100527"
width="1.3832762"
height="1.6020105"><feConvolveMatrix
order="1 1"
kernelMatrix="1.0000000 "
id="feConvolveMatrix1"
result="result1"
bias="0.30000000000000004"
preserveAlpha="true"
edgeMode="none"
divisor="0" /><feGaussianBlur
stdDeviation="3"
id="feGaussianBlur1"
in="SourceGraphic" /><feComposite
id="feComposite1"
operator="arithmetic"
k1="1"
k2="1.5"
k3="1"
k4="0"
in2="result1" /></filter></defs><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"><path
style="font-variation-settings:'wght' 700;fill:none;stroke:#be1251;stroke-width:1.5875;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
d="m 0,134.67292 h 10.318751 l 0,-133.87916996 H 0"
id="path2"
sodipodi:nodetypes="cccc" /></g></svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

View file

@ -1,96 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="256"
height="256"
viewBox="0 0 67.733334 67.733334"
version="1.1"
id="svg1"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
sodipodi:docname="outfly.svg"
xml:space="preserve"
inkscape:export-filename="../../build/linux/outfly.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview1"
pagecolor="#000000"
bordercolor="#252525"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#000000"
inkscape:document-units="px"
inkscape:zoom="2.3395291"
inkscape:cx="50.223782"
inkscape:cy="56.635329"
inkscape:window-width="2880"
inkscape:window-height="1765"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:export-bgcolor="#000000ff"><inkscape:grid
id="grid1"
units="px"
originx="0"
originy="0"
spacingx="0.26458334"
spacingy="0.26458334"
empcolor="#0099e5"
empopacity="0.30196078"
color="#0099e5"
opacity="0.14901961"
empspacing="8"
dotted="false"
gridanglex="30"
gridanglez="30"
visible="true" /></sodipodi:namedview><defs
id="defs1"><filter
style="color-interpolation-filters:sRGB"
id="filter1"
inkscape:label="bloom"
x="-0.28216198"
y="-0.2827584"
width="1.564324"
height="1.5655168"><feConvolveMatrix
order="1 1"
kernelMatrix="1.0000000 "
id="feConvolveMatrix1"
result="result1"
bias="0.090000000000000024"
preserveAlpha="true"
edgeMode="none"
divisor="0" /><feGaussianBlur
stdDeviation="6"
id="feGaussianBlur1"
in="SourceGraphic" /><feComposite
id="feComposite1"
operator="arithmetic"
k1="0"
k2="4.4249689690420553"
k3="5.8365581191588785"
k4="0"
in2="result1" /></filter></defs><g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"><g
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:#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:#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

View file

@ -11,10 +11,9 @@
// This module manages variables, settings, as well as evaluating // This module manages variables, settings, as well as evaluating
// "if"-conditions in chats. // "if"-conditions in chats.
use crate::prelude::*;
use bevy::window::WindowMode; use bevy::window::WindowMode;
use bevy::prelude::*; use bevy::prelude::*;
use std::collections::{HashMap, HashSet}; use std::collections::HashMap;
use serde::Deserialize; use serde::Deserialize;
use toml_edit::DocumentMut; use toml_edit::DocumentMut;
use std::env; use std::env;
@ -37,7 +36,6 @@ pub struct Settings {
pub dev_mode: bool, pub dev_mode: bool,
pub god_mode: bool, pub god_mode: bool,
pub version: String, pub version: String,
pub alive: bool,
pub mute_sfx: bool, pub mute_sfx: bool,
pub mute_music: bool, pub mute_music: bool,
pub volume_sfx: u8, pub volume_sfx: u8,
@ -48,48 +46,24 @@ pub struct Settings {
pub zoom_fov: f32, pub zoom_fov: f32,
pub zoom_sensitivity_factor: f32, pub zoom_sensitivity_factor: f32,
pub font_size_hud: f32, pub font_size_hud: f32,
pub font_size_fps: f32,
pub font_size_conversations: f32, pub font_size_conversations: f32,
pub font_size_choices: f32, pub font_size_choices: f32,
pub font_size_console: f32, pub font_size_console: f32,
pub font_size_speedometer: f32, pub font_size_speedometer: f32,
pub font_size_deathtext: f32,
pub font_size_deathsubtext: f32,
pub font_size_deathpoem: f32,
pub font_size_death_achievements: f32,
pub font_size_achievement: f32,
pub font_size_achievement_header: f32,
pub font_size_keybindings: f32,
pub font_size_version: f32,
pub hud_color: Color, pub hud_color: Color,
pub hud_color_fps: Color,
pub hud_color_console: Color, pub hud_color_console: Color,
pub hud_color_console_warn: Color, pub hud_color_console_warn: Color,
pub hud_color_console_system: Color, pub hud_color_console_system: Color,
pub hud_color_console_achievement: Color,
pub hud_color_alert: Color, pub hud_color_alert: Color,
pub hud_color_subtitles: Color, pub hud_color_subtitles: Color,
pub hud_color_choices: Color, pub hud_color_choices: Color,
pub hud_color_speedometer: Color, pub hud_color_speedometer: Color,
pub hud_color_deathpoem: Color,
pub hud_color_achievement: Color,
pub hud_color_achievement_header: Color,
pub hud_color_achievement_accomplished: Color,
pub hud_color_death: Color,
pub hud_color_death_achievements: Color,
pub hud_color_keybindings: Color,
pub hud_color_version: Color,
pub chat_speed: f32, pub chat_speed: f32,
pub flashlight_active: bool,
pub hud_active: bool, pub hud_active: bool,
pub map_active: bool, pub map_active: bool,
pub deathscreen_active: bool,
pub menu_active: bool,
pub death_cause: String,
pub is_zooming: bool, pub is_zooming: bool,
pub third_person: bool, pub third_person: bool,
pub rotation_stabilizer_active: bool, pub rotation_stabilizer_active: bool,
pub cruise_control_active: bool,
pub shadows_sun: bool, pub shadows_sun: bool,
pub shadows_pointlights: bool, pub shadows_pointlights: bool,
pub shadowmap_resolution: usize, pub shadowmap_resolution: usize,
@ -102,7 +76,8 @@ pub struct Settings {
//pub key_map_zoom_out_wheel: MouseButton, //pub key_map_zoom_out_wheel: MouseButton,
//pub key_map_zoom_in_wheel: MouseButton, //pub key_map_zoom_in_wheel: MouseButton,
pub key_togglehud: KeyCode, pub key_togglehud: KeyCode,
pub key_menu: KeyCode, pub key_exit: KeyCode,
pub key_restart: KeyCode,
pub key_fullscreen: KeyCode, pub key_fullscreen: KeyCode,
pub key_help: KeyCode, pub key_help: KeyCode,
pub key_forward: KeyCode, pub key_forward: KeyCode,
@ -116,8 +91,7 @@ pub struct Settings {
pub key_interact: KeyCode, pub key_interact: KeyCode,
pub key_vehicle: KeyCode, pub key_vehicle: KeyCode,
pub key_camera: KeyCode, pub key_camera: KeyCode,
pub key_flashlight: KeyCode, pub key_shadows: KeyCode,
pub key_cruise_control: KeyCode,
pub key_rotate: KeyCode, pub key_rotate: KeyCode,
pub key_rotation_stabilizer: KeyCode, pub key_rotation_stabilizer: KeyCode,
pub key_mouseup: KeyCode, pub key_mouseup: KeyCode,
@ -126,6 +100,8 @@ pub struct Settings {
pub key_mouseright: KeyCode, pub key_mouseright: KeyCode,
pub key_rotateleft: KeyCode, pub key_rotateleft: KeyCode,
pub key_rotateright: KeyCode, pub key_rotateright: KeyCode,
pub key_toggle_sfx: KeyCode,
pub key_toggle_music: KeyCode,
pub key_reply1: KeyCode, pub key_reply1: KeyCode,
pub key_reply2: KeyCode, pub key_reply2: KeyCode,
pub key_reply3: KeyCode, pub key_reply3: KeyCode,
@ -152,20 +128,19 @@ pub struct Settings {
impl Default for Settings { impl Default for Settings {
fn default() -> Self { fn default() -> Self {
let dev_mode = cfg!(feature = "dev_mode") && env::var("CARGO").is_ok(); let dev_mode = cfg!(feature = "dev_mode");
let default_mute_sfx = false; let default_mute_sfx = false;
let default_mute_music = dev_mode; let default_mute_music = cfg!(feature = "mute_music");
let version = if let Some(version) = option_env!("CARGO_PKG_VERSION") { let version = if let Some(version) = option_env!("CARGO_PKG_VERSION") {
version.to_string() version.to_string()
} else { } else {
"".to_string() "13.37".to_string()
}; };
Settings { Settings {
dev_mode, dev_mode,
god_mode: false, god_mode: false,
version, version,
alive: true,
mute_sfx: default_mute_sfx, mute_sfx: default_mute_sfx,
mute_music: default_mute_music, mute_music: default_mute_music,
volume_sfx: 100, volume_sfx: 100,
@ -176,48 +151,24 @@ impl Default for Settings {
zoom_fov: 15.0, zoom_fov: 15.0,
zoom_sensitivity_factor: 0.25, zoom_sensitivity_factor: 0.25,
font_size_hud: 24.0, font_size_hud: 24.0,
font_size_fps: 14.0,
font_size_conversations: 32.0, font_size_conversations: 32.0,
font_size_choices: 28.0, font_size_choices: 28.0,
font_size_console: 20.0, font_size_console: 20.0,
font_size_speedometer: 34.0, font_size_speedometer: 34.0,
font_size_deathtext: 64.0, hud_color: Color::hex("#197F19").unwrap(),
font_size_deathsubtext: 32.0, hud_color_console: Color::hex("#197F19").unwrap(),
font_size_deathpoem: 18.0, hud_color_console_warn: Color::hex("#FF4C4C").unwrap(),
font_size_death_achievements: 24.0, hud_color_console_system: Color::hex("#7F7F7F").unwrap(),
font_size_achievement: 24.0, hud_color_alert: Color::hex("#991752").unwrap(),
font_size_achievement_header: 32.0, hud_color_subtitles: Color::hex("#CCCCCC").unwrap(),
font_size_keybindings: 20.0, hud_color_choices: Color::hex("#727272").unwrap(),
font_size_version: 20.0, hud_color_speedometer: Color::hex("#BE1251").unwrap(),
hud_color: Color::hex(COLOR_PRIMARY).unwrap(),
hud_color_fps: Color::hex("#181818").unwrap(),
hud_color_console: Color::hex(COLOR_PRIMARY).unwrap(),
hud_color_console_achievement: Color::hex(COLOR_SUCCESS).unwrap(),
hud_color_console_warn: Color::hex(COLOR_WARNING).unwrap(),
hud_color_console_system: Color::hex(COLOR_SECONDARY).unwrap(),
hud_color_alert: Color::hex(COLOR_SECONDARY).unwrap(),
hud_color_subtitles: Color::hex(COLOR_SECONDARY).unwrap(),
hud_color_choices: Color::hex(COLOR_BODY).unwrap(),
hud_color_speedometer: Color::hex(COLOR_PRIMARY).unwrap(),
hud_color_deathpoem: Color::hex("#CC2200").unwrap(),
hud_color_achievement: Color::hex(COLOR_DIM).unwrap(),
hud_color_achievement_accomplished: Color::hex(COLOR_SUCCESS).unwrap(),
hud_color_achievement_header: Color::hex(COLOR_PRIMARY).unwrap(),
hud_color_death: Color::hex(COLOR_SECONDARY).unwrap(),
hud_color_death_achievements: Color::hex(COLOR_SECONDARY).unwrap(),
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 }, chat_speed: DEFAULT_CHAT_SPEED * if dev_mode { 2.5 } else { 1.0 },
flashlight_active: false, hud_active: false,
hud_active: true,
map_active: false, map_active: false,
deathscreen_active: false,
menu_active: false,
death_cause: "Unknown".to_string(),
is_zooming: false, is_zooming: false,
third_person: true, third_person: false,
rotation_stabilizer_active: true, rotation_stabilizer_active: true,
cruise_control_active: false,
shadows_sun: true, shadows_sun: true,
shadows_pointlights: false, shadows_pointlights: false,
shadowmap_resolution: 2048, shadowmap_resolution: 2048,
@ -230,7 +181,8 @@ impl Default for Settings {
//key_map_zoom_out_wheel: KeyCode::Shift, //key_map_zoom_out_wheel: KeyCode::Shift,
//key_map_zoom_in_wheel: KeyCode::Shift, //key_map_zoom_in_wheel: KeyCode::Shift,
key_togglehud: KeyCode::Tab, key_togglehud: KeyCode::Tab,
key_menu: KeyCode::Escape, key_exit: KeyCode::Escape,
key_restart: KeyCode::F7,
key_fullscreen: KeyCode::F11, key_fullscreen: KeyCode::F11,
key_help: KeyCode::F1, key_help: KeyCode::F1,
key_forward: KeyCode::KeyW, key_forward: KeyCode::KeyW,
@ -243,9 +195,8 @@ impl Default for Settings {
key_stop: KeyCode::Space, key_stop: KeyCode::Space,
key_interact: KeyCode::KeyE, key_interact: KeyCode::KeyE,
key_vehicle: KeyCode::KeyQ, key_vehicle: KeyCode::KeyQ,
key_camera: KeyCode::KeyC, key_camera: KeyCode::KeyF,
key_flashlight: KeyCode::KeyF, key_shadows: KeyCode::F2,
key_cruise_control: KeyCode::KeyT,
key_rotate: KeyCode::KeyR, key_rotate: KeyCode::KeyR,
key_rotation_stabilizer: KeyCode::KeyY, key_rotation_stabilizer: KeyCode::KeyY,
key_mouseup: KeyCode::KeyI, key_mouseup: KeyCode::KeyI,
@ -254,6 +205,8 @@ impl Default for Settings {
key_mouseright: KeyCode::KeyL, key_mouseright: KeyCode::KeyL,
key_rotateleft: KeyCode::KeyU, key_rotateleft: KeyCode::KeyU,
key_rotateright: KeyCode::KeyO, key_rotateright: KeyCode::KeyO,
key_toggle_sfx: KeyCode::F3,
key_toggle_music: KeyCode::F4,
key_reply1: KeyCode::Digit1, key_reply1: KeyCode::Digit1,
key_reply2: KeyCode::Digit2, key_reply2: KeyCode::Digit2,
key_reply3: KeyCode::Digit3, key_reply3: KeyCode::Digit3,
@ -265,7 +218,7 @@ impl Default for Settings {
key_reply9: KeyCode::Digit9, key_reply9: KeyCode::Digit9,
key_reply10: KeyCode::Digit0, key_reply10: KeyCode::Digit0,
key_cheat_god_mode: KeyCode::KeyG, key_cheat_god_mode: KeyCode::KeyG,
key_cheat_stop: KeyCode::KeyZ, key_cheat_stop: KeyCode::KeyC,
key_cheat_speed: KeyCode::KeyV, key_cheat_speed: KeyCode::KeyV,
key_cheat_speed_backward: KeyCode::KeyB, key_cheat_speed_backward: KeyCode::KeyB,
key_cheat_teleport: KeyCode::KeyX, key_cheat_teleport: KeyCode::KeyX,
@ -275,7 +228,7 @@ impl Default for Settings {
key_cheat_adrenaline_zero: KeyCode::F5, key_cheat_adrenaline_zero: KeyCode::F5,
key_cheat_adrenaline_mid: KeyCode::F6, key_cheat_adrenaline_mid: KeyCode::F6,
key_cheat_adrenaline_max: KeyCode::F8, key_cheat_adrenaline_max: KeyCode::F8,
key_cheat_die: KeyCode::F4, key_cheat_die: KeyCode::KeyZ,
} }
} }
} }
@ -293,9 +246,6 @@ impl Settings {
self.rotation_stabilizer_active = default.rotation_stabilizer_active; self.rotation_stabilizer_active = default.rotation_stabilizer_active;
self.third_person = default.third_person; self.third_person = default.third_person;
self.is_zooming = default.is_zooming; self.is_zooming = default.is_zooming;
self.flashlight_active = default.flashlight_active;
self.cruise_control_active = default.cruise_control_active;
self.map_active = default.map_active;
} }
pub fn get_reply_keys(&self) -> [KeyCode; 10] { pub fn get_reply_keys(&self) -> [KeyCode; 10] {
@ -312,100 +262,6 @@ impl Settings {
self.key_reply10, self.key_reply10,
]; ];
} }
pub fn in_control(&self) -> bool {
return self.alive && !self.menu_active;
}
}
#[derive(Resource, Default, Debug)]
pub struct AchievementTracker {
pub repair_suit: bool,
pub drink_a_pizza: bool,
pub in_jupiters_shadow: bool,
pub find_earth: bool,
pub ride_every_vehicle: bool,
pub vehicles_ridden: HashSet<String>,
pub all_vehicles: HashSet<String>,
pub talk_to_everyone: bool,
pub people_talked_to: HashSet<String>,
pub all_people: HashSet<String>,
}
impl AchievementTracker {
pub fn to_bool_vec(&self) -> Vec<bool> {
vec![
self.repair_suit,
self.drink_a_pizza,
self.ride_every_vehicle,
self.talk_to_everyone,
self.find_earth,
self.in_jupiters_shadow,
]
}
pub fn achieve_all(&mut self) {
self.repair_suit = true;
self.drink_a_pizza = true;
self.ride_every_vehicle = true;
self.talk_to_everyone = true;
self.find_earth = true;
self.in_jupiters_shadow = true;
}
pub fn to_textsections(&self) -> Vec<String> {
fn collectible(current: usize, total: usize) -> String {
if current < total {
format!(" ({}/{})", current, total)
} else {
"".to_string()
}
}
let ride = collectible(self.vehicles_ridden.len(), self.all_vehicles.len());
let talk = collectible(self.people_talked_to.len(), self.all_people.len());
vec![
"Repair Your Suit\n".to_string(),
"Enjoy A Pizza\n".to_string(),
format!("Ride Every Vehicle{ride}\n"),
format!("Talk To Everyone{talk}\n"),
"Find Earth\n".to_string(),
"Eclipse The Sun With Jupiter\n".to_string(),
]
}
pub fn to_overview(&self) -> Vec<(bool, String)> {
vec![
(self.repair_suit, "repair your suit".into()),
(self.drink_a_pizza, "enjoy a pizza".into()),
(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()),
]
}
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()
}
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 {
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 {
summary += ", ";
}
}
summary
}
} }
#[derive(Resource, Deserialize, Debug, Default)] #[derive(Resource, Deserialize, Debug, Default)]
@ -443,17 +299,17 @@ fn file_is_readable(file_path: &str) -> bool {
} }
fn get_prefs_path() -> Option<String> { fn get_prefs_path() -> Option<String> {
let test = CONF_FILE; let test = "outfly.toml";
if file_is_readable(test) { if file_is_readable(test) {
return Some(test.to_string()); return Some(test.to_string());
} }
if let Ok(basedir) = env::var("XDG_CONFIG_HOME") { if let Ok(basedir) = env::var("XDG_CONFIG_HOME") {
let test = basedir.to_string() + "/outfly/" + CONF_FILE; let test = basedir.to_string() + "/outfly/outfly.toml";
if file_is_readable(test.as_str()) { if file_is_readable(test.as_str()) {
return Some(test); return Some(test);
} }
} else if let Ok(basedir) = env::var("HOME") { } else if let Ok(basedir) = env::var("HOME") {
let test = basedir.to_string() + ".config/outfly/" + CONF_FILE; let test = basedir.to_string() + ".config/outfly/outfly.toml";
if file_is_readable(test.as_str()) { if file_is_readable(test.as_str()) {
return Some(test); return Some(test);
} }
@ -693,10 +549,3 @@ impl GameVars {
} }
} }
} }
#[derive(Resource, Default)]
pub struct CommandLineOptions {
pub window_mode_fullscreen: WindowMode,
pub window_mode_initial: WindowMode,
pub use_gl: bool,
}

View file

@ -10,13 +10,14 @@
// //
// This module populates the world with stars and asteroids. // This module populates the world with stars and asteroids.
use crate::prelude::*; use crate::{actor, hud, nature, shading, skeleton};
use bevy::prelude::*; use bevy::prelude::*;
use bevy::math::I64Vec3; use bevy::math::{DVec3, I64Vec3};
use bevy::scene::{InstanceId, SceneInstance}; use bevy::scene::{InstanceId, SceneInstance};
use bevy::render::mesh::Indices; use bevy::render::mesh::Indices;
use bevy_xpbd_3d::prelude::*; use bevy_xpbd_3d::prelude::*;
use std::collections::HashMap; use std::collections::HashMap;
use std::f32::consts::PI;
use fastrand; use fastrand;
const ASTEROID_UPDATE_INTERVAL: f32 = 0.1; // seconds const ASTEROID_UPDATE_INTERVAL: f32 = 0.1; // seconds
@ -26,7 +27,7 @@ const STARS_MAX_MAGNITUDE: f32 = 5.5; // max 7.0, see generate_starchart.py
const SKYBOX: bool = false; const SKYBOX: bool = false;
const ASTEROID_SPAWN_STEP: f64 = 1000.0; const ASTEROID_SPAWN_STEP: f64 = 500.0;
const ASTEROID_VIEW_RADIUS: f64 = 3000.0; const ASTEROID_VIEW_RADIUS: f64 = 3000.0;
const ASSET_NAME_ASTEROID1: &str = "asteroid1"; const ASSET_NAME_ASTEROID1: &str = "asteroid1";
@ -38,24 +39,21 @@ impl Plugin for WorldPlugin {
app.add_systems(Startup, setup); app.add_systems(Startup, setup);
app.add_systems(PostUpdate, handle_despawn); app.add_systems(PostUpdate, handle_despawn);
app.add_systems(Update, spawn_despawn_asteroids); app.add_systems(Update, spawn_despawn_asteroids);
app.add_systems(Update, handle_respawn.run_if(on_event::<RespawnEvent>()));
app.add_plugins(PhysicsPlugins::default()); app.add_plugins(PhysicsPlugins::default());
//app.add_plugins(PhysicsDebugPlugin::default()); //app.add_plugins(PhysicsDebugPlugin::default());
app.insert_resource(Gravity(DVec3::splat(0.0))); app.insert_resource(Gravity(DVec3::splat(0.0)));
app.insert_resource(AsteroidUpdateTimer( app.insert_resource(AsteroidUpdateTimer(
Timer::from_seconds(ASTEROID_UPDATE_INTERVAL, TimerMode::Repeating))); Timer::from_seconds(ASTEROID_UPDATE_INTERVAL, TimerMode::Repeating)));
app.insert_resource(ActiveAsteroids(HashMap::new())); app.insert_resource(ActiveAsteroids(HashMap::new()));
app.add_event::<DespawnAsteroidEvent>(); app.add_event::<DespawnEvent>();
app.add_event::<RespawnEvent>();
} }
} }
#[derive(Resource)] struct AsteroidUpdateTimer(Timer); #[derive(Resource)] struct AsteroidUpdateTimer(Timer);
#[derive(Resource)] pub struct ActiveAsteroids(pub HashMap<I64Vec3, AsteroidData>); #[derive(Resource)] pub struct ActiveAsteroids(pub HashMap<I64Vec3, AsteroidData>);
#[derive(Component)] struct Asteroid; #[derive(Component)] struct Asteroid;
#[derive(Component)] pub struct Star;
#[derive(Component)] pub struct DespawnOnPlayerDeath; #[derive(Component)] pub struct DespawnOnPlayerDeath;
#[derive(Event)] pub struct RespawnEvent;
pub struct AsteroidData { pub struct AsteroidData {
entity: Entity, entity: Entity,
@ -63,17 +61,20 @@ pub struct AsteroidData {
} }
#[derive(Event)] #[derive(Event)]
pub struct DespawnAsteroidEvent { pub struct DespawnEvent {
entity: Entity, entity: Entity,
sceneinstance: InstanceId, sceneinstance: InstanceId,
origin: I64Vec3, origin: I64Vec3,
} }
#[derive(Component)]
pub struct Star;
pub fn setup( pub fn setup(
mut commands: Commands, mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>, mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>, mut materials: ResMut<Assets<StandardMaterial>>,
mut materials_skybox: ResMut<Assets<load::SkyBox>>, mut materials_skybox: ResMut<Assets<shading::SkyBox>>,
) { ) {
// Generate starmap // Generate starmap
let sphere_handle = meshes.add(Sphere::new(1.0).mesh().uv(16, 16)); let sphere_handle = meshes.add(Sphere::new(1.0).mesh().uv(16, 16));
@ -138,8 +139,6 @@ pub fn setup(
}, },
..default() ..default()
}, },
Position::from(pos_render),
Rotation::from(Quat::IDENTITY),
)); ));
starcount += 1; starcount += 1;
} }
@ -157,7 +156,7 @@ pub fn setup(
} }
commands.spawn(MaterialMeshBundle { commands.spawn(MaterialMeshBundle {
mesh: meshes.add(mesh), mesh: meshes.add(mesh),
material: materials_skybox.add(load::SkyBox {}), material: materials_skybox.add(shading::SkyBox {}),
transform: Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)), transform: Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)),
..default() ..default()
}); });
@ -169,11 +168,11 @@ fn spawn_despawn_asteroids(
mut timer: ResMut<AsteroidUpdateTimer>, mut timer: ResMut<AsteroidUpdateTimer>,
mut commands: Commands, mut commands: Commands,
q_player: Query<&Position, With<actor::PlayerCamera>>, q_player: Query<&Position, With<actor::PlayerCamera>>,
mut ew_despawn: EventWriter<DespawnAsteroidEvent>, mut ew_despawn: EventWriter<DespawnEvent>,
mut db: ResMut<ActiveAsteroids>, mut db: ResMut<ActiveAsteroids>,
q_asteroid: Query<(&Position, &SceneInstance), With<Asteroid>>, q_asteroid: Query<(&Position, &SceneInstance), With<Asteroid>>,
mut last_player_cell: Local<I64Vec3>, mut last_player_cell: Local<I64Vec3>,
id2pos: Res<game::Id2Pos>, id2pos: Res<actor::Id2Pos>,
asset_server: Res<AssetServer>, asset_server: Res<AssetServer>,
) { ) {
if !timer.0.tick(time.delta()).just_finished() || q_player.is_empty() { if !timer.0.tick(time.delta()).just_finished() || q_player.is_empty() {
@ -217,7 +216,7 @@ fn spawn_despawn_asteroids(
{ {
if let Ok((pos, sceneinstance)) = q_asteroid.get(asteroid.entity) { if let Ok((pos, sceneinstance)) = q_asteroid.get(asteroid.entity) {
if pos.0.distance(player.0) > 1000.0 { if pos.0.distance(player.0) > 1000.0 {
ew_despawn.send(DespawnAsteroidEvent { ew_despawn.send(DespawnEvent {
entity: asteroid.entity, entity: asteroid.entity,
sceneinstance: **sceneinstance, sceneinstance: **sceneinstance,
origin: origin.clone(), origin: origin.clone(),
@ -301,7 +300,7 @@ fn spawn_despawn_asteroids(
AngularVelocity(DVec3::new(0.1, 0.1, 0.03)), AngularVelocity(DVec3::new(0.1, 0.1, 0.03)),
LinearVelocity(DVec3::new(0.0, 0.0, 0.35)), LinearVelocity(DVec3::new(0.0, 0.0, 0.35)),
Collider::sphere(1.0), Collider::sphere(1.0),
Rotation::from(Quat::from_rotation_y(-PI32 / 3.)), Rotation::from(Quat::from_rotation_y(-PI / 3.)),
Position::new(pos), Position::new(pos),
hud::IsClickable { hud::IsClickable {
name: Some("Uncharted Rock".to_string()), name: Some("Uncharted Rock".to_string()),
@ -321,7 +320,7 @@ fn spawn_despawn_asteroids(
}, },
..default() ..default()
}); });
load_asset(model, &mut entity_commands, &*asset_server); skeleton::load(model, &mut entity_commands, &*asset_server);
db.0.insert(origin, AsteroidData { db.0.insert(origin, AsteroidData {
entity: entity_commands.id(), entity: entity_commands.id(),
//viewdistance: 99999999.0, //viewdistance: 99999999.0,
@ -333,7 +332,7 @@ fn spawn_despawn_asteroids(
fn handle_despawn( fn handle_despawn(
mut commands: Commands, mut commands: Commands,
mut er_despawn: EventReader<DespawnAsteroidEvent>, mut er_despawn: EventReader<DespawnEvent>,
mut db: ResMut<ActiveAsteroids>, mut db: ResMut<ActiveAsteroids>,
mut scene_spawner: ResMut<SceneSpawner>, mut scene_spawner: ResMut<SceneSpawner>,
) { ) {
@ -343,11 +342,3 @@ fn handle_despawn(
db.0.remove(&despawn.origin); db.0.remove(&despawn.origin);
} }
} }
fn handle_respawn(
ew_spawn: EventWriter<cmd::SpawnEvent>,
mut achievement_tracker: ResMut<var::AchievementTracker>,
) {
*achievement_tracker = var::AchievementTracker::default();
cmd::load_defs(ew_spawn);
}