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
- Add speedometer and bigger reticule

178
Cargo.lock generated
View file

@ -291,7 +291,8 @@ checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]]
name = "bevy"
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 = [
"bevy_dylib",
"bevy_internal",
@ -300,7 +301,8 @@ dependencies = [
[[package]]
name = "bevy_a11y"
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 = [
"accesskit",
"bevy_app",
@ -311,14 +313,15 @@ dependencies = [
[[package]]
name = "bevy_animation"
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 = [
"bevy_app",
"bevy_asset",
"bevy_core",
"bevy_ecs",
"bevy_hierarchy",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math",
"bevy_reflect",
"bevy_render",
"bevy_time",
@ -329,7 +332,8 @@ dependencies = [
[[package]]
name = "bevy_app"
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 = [
"bevy_derive",
"bevy_ecs",
@ -344,7 +348,8 @@ dependencies = [
[[package]]
name = "bevy_asset"
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 = [
"async-broadcast",
"async-fs",
@ -376,7 +381,8 @@ dependencies = [
[[package]]
name = "bevy_asset_macros"
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 = [
"bevy_macro_utils",
"proc-macro2",
@ -387,13 +393,14 @@ dependencies = [
[[package]]
name = "bevy_audio"
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 = [
"bevy_app",
"bevy_asset",
"bevy_derive",
"bevy_ecs",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math",
"bevy_reflect",
"bevy_transform",
"bevy_utils",
@ -403,11 +410,12 @@ dependencies = [
[[package]]
name = "bevy_core"
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 = [
"bevy_app",
"bevy_ecs",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math",
"bevy_reflect",
"bevy_tasks",
"bevy_utils",
@ -417,7 +425,8 @@ dependencies = [
[[package]]
name = "bevy_core_pipeline"
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 = [
"bevy_app",
"bevy_asset",
@ -425,7 +434,7 @@ dependencies = [
"bevy_derive",
"bevy_ecs",
"bevy_log",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math",
"bevy_reflect",
"bevy_render",
"bevy_transform",
@ -438,7 +447,8 @@ dependencies = [
[[package]]
name = "bevy_derive"
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 = [
"bevy_macro_utils",
"quote",
@ -448,7 +458,8 @@ dependencies = [
[[package]]
name = "bevy_diagnostic"
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 = [
"bevy_app",
"bevy_core",
@ -463,7 +474,8 @@ dependencies = [
[[package]]
name = "bevy_dylib"
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 = [
"bevy_internal",
]
@ -471,7 +483,8 @@ dependencies = [
[[package]]
name = "bevy_ecs"
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 = [
"async-channel",
"bevy_ecs_macros",
@ -490,7 +503,8 @@ dependencies = [
[[package]]
name = "bevy_ecs_macros"
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 = [
"bevy_macro_utils",
"proc-macro2",
@ -501,7 +515,8 @@ dependencies = [
[[package]]
name = "bevy_embedded_assets"
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 = [
"bevy",
"cargo-emit",
@ -512,7 +527,8 @@ dependencies = [
[[package]]
name = "bevy_encase_derive"
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 = [
"bevy_macro_utils",
"encase_derive_impl",
@ -521,7 +537,8 @@ dependencies = [
[[package]]
name = "bevy_gizmos"
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 = [
"bevy_app",
"bevy_asset",
@ -530,7 +547,7 @@ dependencies = [
"bevy_ecs",
"bevy_gizmos_macros",
"bevy_log",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math",
"bevy_pbr",
"bevy_reflect",
"bevy_render",
@ -542,7 +559,8 @@ dependencies = [
[[package]]
name = "bevy_gizmos_macros"
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 = [
"bevy_macro_utils",
"proc-macro2",
@ -553,7 +571,8 @@ dependencies = [
[[package]]
name = "bevy_gltf"
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 = [
"base64",
"bevy_animation",
@ -564,7 +583,7 @@ dependencies = [
"bevy_ecs",
"bevy_hierarchy",
"bevy_log",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math",
"bevy_pbr",
"bevy_reflect",
"bevy_render",
@ -582,7 +601,8 @@ dependencies = [
[[package]]
name = "bevy_hierarchy"
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 = [
"bevy_app",
"bevy_core",
@ -595,11 +615,12 @@ dependencies = [
[[package]]
name = "bevy_input"
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 = [
"bevy_app",
"bevy_ecs",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math",
"bevy_reflect",
"bevy_utils",
"smol_str",
@ -609,7 +630,8 @@ dependencies = [
[[package]]
name = "bevy_internal"
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 = [
"bevy_a11y",
"bevy_animation",
@ -626,7 +648,7 @@ dependencies = [
"bevy_hierarchy",
"bevy_input",
"bevy_log",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math",
"bevy_pbr",
"bevy_ptr",
"bevy_reflect",
@ -646,7 +668,8 @@ dependencies = [
[[package]]
name = "bevy_log"
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 = [
"android_log-sys",
"bevy_app",
@ -661,7 +684,8 @@ dependencies = [
[[package]]
name = "bevy_macro_utils"
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 = [
"proc-macro2",
"quote",
@ -675,14 +699,6 @@ name = "bevy_math"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f06daa26ffb82d90ba772256c0ba286f6c305c392f6976c9822717974805837c"
dependencies = [
"glam",
]
[[package]]
name = "bevy_math"
version = "0.13.2"
source = "git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960#2076f102be15a88a5026e128851a8ee1ae9d8960"
dependencies = [
"glam",
"serde",
@ -691,7 +707,8 @@ dependencies = [
[[package]]
name = "bevy_mikktspace"
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 = [
"glam",
]
@ -699,14 +716,15 @@ dependencies = [
[[package]]
name = "bevy_pbr"
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 = [
"bevy_app",
"bevy_asset",
"bevy_core_pipeline",
"bevy_derive",
"bevy_ecs",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math",
"bevy_reflect",
"bevy_render",
"bevy_transform",
@ -723,14 +741,16 @@ dependencies = [
[[package]]
name = "bevy_ptr"
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]]
name = "bevy_reflect"
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 = [
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math",
"bevy_ptr",
"bevy_reflect_derive",
"bevy_utils",
@ -745,7 +765,8 @@ dependencies = [
[[package]]
name = "bevy_reflect_derive"
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 = [
"bevy_macro_utils",
"proc-macro2",
@ -757,7 +778,8 @@ dependencies = [
[[package]]
name = "bevy_render"
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 = [
"async-channel",
"bevy_app",
@ -768,7 +790,7 @@ dependencies = [
"bevy_encase_derive",
"bevy_hierarchy",
"bevy_log",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math",
"bevy_mikktspace",
"bevy_reflect",
"bevy_render_macros",
@ -801,7 +823,8 @@ dependencies = [
[[package]]
name = "bevy_render_macros"
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 = [
"bevy_macro_utils",
"proc-macro2",
@ -812,7 +835,8 @@ dependencies = [
[[package]]
name = "bevy_scene"
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 = [
"bevy_app",
"bevy_asset",
@ -831,7 +855,8 @@ dependencies = [
[[package]]
name = "bevy_sprite"
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 = [
"bevy_app",
"bevy_asset",
@ -839,7 +864,7 @@ dependencies = [
"bevy_derive",
"bevy_ecs",
"bevy_log",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math",
"bevy_reflect",
"bevy_render",
"bevy_transform",
@ -856,7 +881,8 @@ dependencies = [
[[package]]
name = "bevy_tasks"
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 = [
"async-channel",
"async-executor",
@ -869,13 +895,14 @@ dependencies = [
[[package]]
name = "bevy_text"
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 = [
"ab_glyph",
"bevy_app",
"bevy_asset",
"bevy_ecs",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math",
"bevy_reflect",
"bevy_render",
"bevy_sprite",
@ -890,7 +917,8 @@ dependencies = [
[[package]]
name = "bevy_time"
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 = [
"bevy_app",
"bevy_ecs",
@ -903,12 +931,13 @@ dependencies = [
[[package]]
name = "bevy_transform"
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 = [
"bevy_app",
"bevy_ecs",
"bevy_hierarchy",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math",
"bevy_reflect",
"thiserror",
]
@ -916,7 +945,8 @@ dependencies = [
[[package]]
name = "bevy_ui"
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 = [
"bevy_a11y",
"bevy_app",
@ -927,7 +957,7 @@ dependencies = [
"bevy_hierarchy",
"bevy_input",
"bevy_log",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math",
"bevy_reflect",
"bevy_render",
"bevy_sprite",
@ -943,7 +973,8 @@ dependencies = [
[[package]]
name = "bevy_utils"
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 = [
"ahash",
"bevy_utils_proc_macros",
@ -961,7 +992,8 @@ dependencies = [
[[package]]
name = "bevy_utils_proc_macros"
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 = [
"proc-macro2",
"quote",
@ -971,13 +1003,14 @@ dependencies = [
[[package]]
name = "bevy_window"
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 = [
"bevy_a11y",
"bevy_app",
"bevy_ecs",
"bevy_input",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math",
"bevy_reflect",
"bevy_utils",
"raw-window-handle",
@ -987,7 +1020,8 @@ dependencies = [
[[package]]
name = "bevy_winit"
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 = [
"accesskit_winit",
"approx",
@ -997,7 +1031,7 @@ dependencies = [
"bevy_ecs",
"bevy_hierarchy",
"bevy_input",
"bevy_math 0.13.2 (git+https://codeberg.org/outfly/bevy.git?rev=2076f102be15a88a5026e128851a8ee1ae9d8960)",
"bevy_math",
"bevy_tasks",
"bevy_utils",
"bevy_window",
@ -1011,10 +1045,11 @@ dependencies = [
[[package]]
name = "bevy_xpbd_3d"
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 = [
"bevy",
"bevy_math 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)",
"bevy_math",
"bevy_xpbd_derive",
"derive_more",
"fxhash",
@ -1028,7 +1063,8 @@ dependencies = [
[[package]]
name = "bevy_xpbd_derive"
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 = [
"quote",
"syn 2.0.52",
@ -2767,7 +2803,7 @@ dependencies = [
[[package]]
name = "outfly"
version = "0.9.0"
version = "0.8.2"
dependencies = [
"bevy",
"bevy_embedded_assets",

View file

@ -10,7 +10,7 @@
[package]
name = "outfly"
version = "0.9.0"
version = "0.8.2"
edition = "2021"
homepage = "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
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
fastrand = "2.0"
@ -33,42 +42,14 @@ serde_yaml = "0.9"
# For reading/writing the player's configuration file.
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]
# 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
[features]
default = ["x11", "embed_assets"]
mute_music = []
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_windows = ["embed_assets"]
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
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
```
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)
- 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
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.
@ -90,7 +88,7 @@ python -m http.server -d wasm
## Building release versions optimized for packaging
To build release versions optimized for final deployment, build with the following features: (see also [pack.sh](https://codeberg.org/hut/outfly/src/branch/main/build/pack.sh))
To build release versions optimized for final deployment, build with the following features: (see also [pack.sh](https://codeberg.org/hut/outfly/src/branch/main/src/build/pack.sh))
```
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
- https://codeberg.org/hut/outfly
- 3D models: Original art, placed under the Creative Commons CC0 License
- Photographs of celestial bodies:
- Mercury: [By Solar System Scope, CC BY 4.0](https://www.solarsystemscope.com/textures/)
- 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/)
- Photographs of celestial bodies: By NASA, public domain
- Icon: Creative Commons CC0 License
- Original sound files:
- 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/whoosh-blow-flutter-shortwav-14678/
- https://pixabay.com/sound-effects/dslr-camera-sounds-26117/
- https://pixabay.com/sound-effects/beep-6-96243
- Music: [Cinematic Cello](https://pixabay.com/music/build-up-scenes-cinematic-cello-115667) by [Aleksey Chistilin](https://pixabay.com/users/lexin_music-28841948/), [Pixabay Content License](https://pixabay.com/service/license-summary)
- 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:

View file

@ -1,4 +1,14 @@
![OutFly Screenshot](doc/images/screenshot3.jpg)
```
▄████████▄ + ███ + ▄█████████ ███ +
███▀ ▀███ + + ███ ███▀ + ███ + +
███ + ███ ███ ███ █████████ ███ ███ ███ ███
███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
+ + + ███
+ ▀████████████████████████████████████████████████████▀
```
[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
![screenshot](doc/images/screenshot3.jpg)
# Features
- A beautiful, serene atmosphere with gorgeous views
@ -29,30 +41,32 @@ Source code: https://codeberg.org/hut/outfly
# Controls
You can view these any time in game through the game menu (press Escape.)
- Space: Slow down, match velocity
- E: Interact
- F: Flashlight
- F1: Show key bindings
- Space: Slow down (or match velocity)
- AWSD/Shift/Ctrl: Accelerate
- R: Rotate (hold & move mouse)
- E: Interact: Talk to people, enter vehicles
- Q: Exit vehicle
- M: Map
- C: Camera
- T: Cruise control
- R: Rotate (hold + move mouse)
- Y: Rotation stabilizer
- AWSD/Shift/Ctrl: Move
- J/K/U/L/I/O: Rotate
- F11: Fullscreen
- Tab: Toggle Augmented Reality
- Augmented Reality only:
- JKULIO: Mouseless camera rotation
- Augmented Reality: (toggle with Tab)
- Left click: Target objects
- Right click: Zoom
- Cheats:
- G: Toggle cheats + invulnerability
- V/B: Impossible acceleration
- Shift+V/B: Extreme acceleration
- Settings
- Tab: Toggle HUD/AR
- M: Toggle map
- 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
- Z: Stop
# Running OutFly
## 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.
# 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!
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 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
A variety of relatively simple game systems should interact with each other to create emergent gameplay and interesting game mechanics.
- Free movement in space
- Everything orbits around Planets/Sun
- Collision with other actors
- Augmented Reality overlay
- 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
- 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
- How to tell a deep story with permadeath without getting repetitive?
@ -190,11 +181,6 @@ Items:
- Pizzeria Clippy
- 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
![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_outer: f32 = 229.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 main_brightness: f32 = 1.0;
@ -41,7 +41,7 @@ fn ring_density(radius: f32) -> f32 {
} else if (radius >= main_inner && radius <= main_outer) {
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) {
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);
} 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();
if target.contains("windows") {
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,
// 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.
use bevy::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;
const MAX_TRANSMISSION_DISTANCE: f32 = 100.0;
const MAX_INTERACT_DISTANCE: f32 = 50.0;
@ -25,24 +30,40 @@ const MAX_INTERACT_DISTANCE: f32 = 50.0;
pub struct ActorPlugin;
impl Plugin for ActorPlugin {
fn build(&self, app: &mut App) {
app.add_systems(PreUpdate, (
handle_player_death,
));
app.add_systems(FixedUpdate, (
update_physics_lifeforms,
update_power,
handle_wants_maxrotation,
handle_wants_maxvelocity,
handle_gforce,
));
app.add_systems(PostUpdate, handle_gforce
.after(PhysicsSet::Sync)
.after(sync::position_to_transform));
app.add_systems(Update, (
handle_input.run_if(in_control),
handle_input,
handle_collisions,
handle_damage,
handle_cheats,
));
app.add_systems(PostUpdate, (
handle_vehicle_enter_exit,
update_id2pos,
));
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,
Mental,
Trauma,
GForce,
Asphyxiation,
Depressurization,
//Poison,
//Radiation,
//Freeze,
//Burn,
}
#[derive(Event)] pub struct PlayerDiesEvent(pub DamageType);
#[derive(Event)]
pub struct VehicleEnterExitEvent {
vehicle: Entity,
driver: Entity,
name: Option<String>,
is_entering: 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 PlayerCollider; // Attached to the collider of the suit of the player
#[derive(Component)] pub struct PlayerDrivesThis; // Attached to the entered vehicle
#[derive(Component)] pub struct PlayerCamera; // Attached to the actor to use as point of view
#[derive(Component)] pub struct JustNowEnteredVehicle;
#[derive(Component)] pub struct ActorEnteringVehicle;
#[derive(Component)] pub struct ActorVehicleBeingEntered;
#[derive(Component)] pub struct PlayersFlashLight;
#[derive(Component)] pub struct WantsMaxRotation(pub f64);
#[derive(Component)] pub struct WantsMaxVelocity(pub f64);
#[derive(Component)] pub struct Identifier(pub String);
#[derive(Resource)] pub struct Id2Pos(pub HashMap<String, DVec3>);
#[derive(Component)]
pub struct LifeForm {
@ -189,58 +208,21 @@ impl Default for Engine {
#[derive(Component)]
pub struct Suit {
pub oxygen: f32,
pub power: f32,
pub oxygen_max: f32,
pub power_max: f32,
pub integrity: f32, // [0.0 - 1.0]
}
impl Default for Suit { fn default() -> Self { SUIT_SIMPLE } }
const SUIT_SIMPLE: Suit = Suit {
power: 1e5,
power_max: 1e5,
oxygen: nature::OXY_D,
oxygen_max: nature::OXY_D,
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(
time: Res<Time>,
mut query: Query<(&mut LifeForm, &mut HitPoints, &mut Suit, &LinearVelocity)>,
@ -289,15 +271,14 @@ pub fn update_physics_lifeforms(
pub fn handle_input(
mut commands: Commands,
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>)>,
player: Query<Entity, With<actor::Player>>,
q_camera: Query<&Transform, With<Camera>>,
mut q_flashlight: Query<&mut Visibility, With<PlayersFlashLight>>,
q_vehicles: Query<(Entity, &Actor, &Transform), (With<actor::Vehicle>, Without<actor::Player>, Without<Camera>)>,
q_vehicles: Query<(Entity, &Transform), (With<actor::Vehicle>, Without<actor::Player>, Without<Camera>)>,
mut ew_conv: EventWriter<chat::StartConversationEvent>,
mut ew_vehicle: EventWriter<VehicleEnterExitEvent>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_playerdies: EventWriter<PlayerDiesEvent>,
q_player_drives: Query<Entity, With<PlayerDrivesThis>>,
) {
if q_camera.is_empty() || player.is_empty() {
@ -320,20 +301,16 @@ pub fn handle_input(
}
// Entering Vehicles
if q_player_drives.is_empty() {
let objects: Vec<((Entity, &Actor), &Transform)> = q_vehicles
let objects: Vec<(Entity, &Transform)> = q_vehicles
.iter()
.map(|(entity, actor, transform)| ((entity, actor), transform))
.collect();
if let (Some((entity, actor)), dist) =
camera::find_closest_target::<(Entity, &Actor)>(objects, camtrans)
{
if let (Some(entity), dist) = camera::find_closest_target::<Entity>(objects, camtrans) {
if dist <= MAX_INTERACT_DISTANCE {
commands.entity(entity).insert(ActorVehicleBeingEntered);
commands.entity(player_entity).insert(ActorEnteringVehicle);
ew_vehicle.send(VehicleEnterExitEvent{
vehicle: entity,
driver: player_entity,
name: actor.name.clone(),
is_entering: q_player_drives.is_empty(),
is_player: true,
});
@ -349,37 +326,21 @@ pub fn handle_input(
ew_vehicle.send(VehicleEnterExitEvent{
vehicle: vehicle_entity,
driver: player_entity,
name: None,
is_entering: false,
is_player: true,
});
break;
}
}
else if keyboard_input.just_pressed(settings.key_flashlight) {
for mut flashlight_vis in &mut q_flashlight {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Switch));
if *flashlight_vis == Visibility::Hidden {
*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;
else if keyboard_input.just_pressed(settings.key_restart) {
settings.god_mode = false;
ew_playerdies.send(PlayerDiesEvent(DamageType::Mental));
}
}
pub fn handle_vehicle_enter_exit(
mut commands: Commands,
mut settings: ResMut<Settings>,
mut er_vehicle: EventReader<VehicleEnterExitEvent>,
mut ew_achievement: EventWriter<game::AchievementEvent>,
mut q_playerflashlight: Query<&mut Visibility, (With<PlayersFlashLight>, Without<ActorVehicleBeingEntered>, Without<ActorEnteringVehicle>)>,
mut q_drivers: Query<(Entity, &mut Visibility, Option<&Collider>), (Without<ActorVehicleBeingEntered>, With<ActorEnteringVehicle>)>,
mut q_vehicles: Query<(Entity, &mut Vehicle, &mut Visibility), (With<ActorVehicleBeingEntered>, Without<ActorEnteringVehicle>)>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
@ -405,14 +366,6 @@ pub fn handle_vehicle_enter_exit(
commands.entity(driver).insert(JustNowEnteredVehicle);
commands.entity(vehicle).insert(PlayerCamera);
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 {
// Exiting Vehicle
@ -436,15 +389,18 @@ pub fn handle_vehicle_enter_exit(
fn handle_collisions(
mut collision_event_reader: EventReader<CollisionStarted>,
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>>,
) {
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() {
if *entity1 == player || *entity2 == player {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Crash));
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>,
mut query: Query<(&mut AngularVelocity, &Engine, &WantsMaxRotation)>,
) {
let epsilon = 0.0001;
//let d = time.delta_seconds();
for (mut v_ang, engine, maxrot) in &mut query {
let total = v_ang.0.length();
if total <= maxrot.0 + EPSILON {
if total <= maxrot.0 + epsilon {
if total > maxrot.0 {
v_ang.0 = DVec3::splat(0.0);
}
@ -474,9 +431,10 @@ fn handle_wants_maxvelocity(
mut query: Query<(&mut LinearVelocity, &Engine, &WantsMaxVelocity)>,
) {
let dt = time.delta_seconds();
let epsilon = 0.0001;
for (mut v, engine, maxv) in &mut query {
let total = v.0.length();
if total <= maxv.0 + EPSILON {
if total <= maxv.0 + epsilon {
if total > maxv.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 acceleration = (avg_thrust * dt) as f64 * -v.0;
v.0 += acceleration;
if v.0.length() + EPSILON < acceleration.length() {
if v.0.length() + epsilon < acceleration.length() {
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(
mut ew_playerdies: EventWriter<game::PlayerDiesEvent>,
mut ew_playerdies: EventWriter<PlayerDiesEvent>,
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 {
if player_maybe.is_some() {
@ -505,7 +520,7 @@ fn handle_damage(
hp.current -= hp.damage;
}
if hp.current <= 0.0 {
ew_playerdies.send(game::PlayerDiesEvent(hp.damagetype));
ew_playerdies.send(PlayerDiesEvent(hp.damagetype));
}
}
else {
@ -530,7 +545,7 @@ fn handle_gforce(
}
if gforce.gforce > gforce.damage_threshold {
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 {
@ -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::audio::{PlaybackMode, Volume};
use crate::prelude::*;
use std::collections::HashMap;
use crate::{camera, var};
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;
impl Plugin for AudioPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, setup);
app.add_systems(Update, respawn_sinks.run_if(on_event::<RespawnSinksEvent>()));
app.add_systems(Update, toggle_bgm);
app.add_systems(Update, play_zoom_sfx);
app.add_systems(Update, pause_all.run_if(on_event::<PauseAllSfxEvent>()));
app.add_systems(PostUpdate, play_sfx);
app.add_systems(PostUpdate, update_music);
app.add_event::<PlaySfxEvent>();
app.add_event::<PauseAllSfxEvent>();
app.add_event::<ToggleMusicEvent>();
app.add_event::<RespawnSinksEvent>();
app.insert_resource(ZoomTimer(
Timer::from_seconds(0.09, TimerMode::Repeating)));
}
@ -35,48 +47,177 @@ impl Plugin for AudioPlugin {
#[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 {
Achieve,
BGM,
Click,
Connect,
Crash,
ElectricMotor,
EnterVehicle,
IncomingChatMessage,
Ion,
Ping,
Rocket,
Click,
Switch,
Thruster,
WakeUp,
Woosh,
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 {
return match sfx_label {
"achieve" => Sfx::Achieve,
"switch" => Sfx::Switch,
"click" => Sfx::Click,
"woosh" => Sfx::Woosh,
@ -85,111 +226,19 @@ pub fn str2sfx(sfx_label: &str) -> Sfx {
"ping" => Sfx::Ping,
"connect" => Sfx::Connect,
"entervehicle" => Sfx::EnterVehicle,
"crash" => Sfx::Ping,
_ => Sfx::Click,
"crash" => Sfx::Crash,
_ => 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(
mut events: EventReader<ToggleMusicEvent>,
q_audiosinks: Query<(&AudioSink, &Sfx)>,
bgm_controller: Query<&AudioSink, With<ComponentBGM>>,
settings: Res<var::Settings>,
) {
if !events.is_empty() {
events.clear();
for (bgm_sink, sfx) in &q_audiosinks {
if *sfx != Sfx::BGM {
continue;
}
if let Ok(bgm_sink) = bgm_controller.get_single() {
if settings.mute_music {
bgm_sink.pause();
}
@ -218,11 +267,3 @@ pub fn play_zoom_sfx(
*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.
#
# 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:
# This script requires the following file in the extra/ directory:
# https://github.com/astronexus/HYG-Database/blob/cbd21013d2bb89732b893be357a6f41836dbe614/hyg/CURRENT/hygdata_v41.csv
import csv

View file

@ -13,5 +13,5 @@
rootdir="${1:-}"
install -Dm755 "target/release/outfly" "$rootdir/usr/bin/outfly"
install -Dm644 "build/linux/outfly.png" "$rootdir/usr/share/pixmaps/outfly.png"
install -Dm644 "build/linux/outfly.desktop" "$rootdir/usr/share/applications/outfly.desktop"
install -Dm644 "src/build/linux/outfly.png" "$rootdir/usr/share/pixmaps/outfly.png"
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.
# Usage: cd outfly; build/pack.sh [-b]
# Usage: cd outfly; src/build/pack.sh [-b]
# Options: -b: cross-compile targets before packing
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
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
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
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::pbr::{CascadeShadowConfigBuilder, DirectionalLightShadowMap};
use bevy::transform::TransformSystem;
use bevy::math::{DVec3, DQuat};
use bevy_xpbd_3d::prelude::*;
use bevy_xpbd_3d::plugins::sync;
use crate::prelude::*;
use std::collections::HashMap;
use std::f32::consts::PI;
use std::f64::consts::PI as PI64;
use crate::{actor, audio, hud, var};
pub const INITIAL_ZOOM_LEVEL: f64 = 10.0;
@ -31,31 +32,19 @@ pub struct CameraPlugin;
impl Plugin for CameraPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, setup_camera);
app.add_systems(Update, handle_input.run_if(in_control));
app.add_systems(Update, update_map_only_object_visibility.run_if(alive));
app.add_systems(Update, handle_input);
app.add_systems(Update, update_map_only_object_visibility);
app.add_systems(Update, manage_player_actor.after(handle_input));
app.add_systems(PostUpdate, sync_camera_to_player
.after(PhysicsSet::Sync)
.after(apply_input_to_player)
.before(TransformSystem::TransformPropagate));
app.add_systems(PostUpdate, update_mapcam_center
.before(sync::position_to_transform)
.in_set(sync::SyncSet::PositionToTransform));
app.add_systems(Update, update_map_camera.run_if(in_control));
app.add_systems(Update, update_fov.run_if(alive));
app.add_systems(PreUpdate, apply_input_to_player);
app.add_systems(Update, update_map_camera);
app.add_systems(Update, update_fov);
app.add_systems(PostUpdate, apply_input_to_player
.after(PhysicsSet::Sync)
.before(TransformSystem::TransformPropagate));
app.insert_resource(MapCam::default());
// 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_z: f64,
pub center: DVec3,
pub center_on_entity: Option<Entity>,
}
impl Default for MapCam {
fn default() -> Self {
@ -83,12 +71,11 @@ impl Default for MapCam {
initialized: false,
zoom_level: 2.0,
target_zoom_level: INITIAL_ZOOM_LEVEL,
pitch: PI * 0.3,
pitch: PI64 * 0.3,
yaw: 0.0,
offset_x: 0.0,
offset_z: 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,
..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 {
num_cascades: 4,
minimum_distance: 0.1,
@ -149,16 +136,14 @@ pub fn sync_camera_to_player(
let (actor, player_transform) = q_playercam.get_single().unwrap();
// 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
if settings.third_person {
camera_transform.translation = player_transform.translation + rotation * (actor.camdistance * Vec3::new(0.0, 0.2, 1.0));
camera_transform.rotation = rotation * Quat::from_euler(EulerRot::XYZ, -0.02, 0.0, 0.0);
camera_transform.translation = player_transform.translation + camera_transform.rotation * (actor.camdistance * Vec3::new(0.0, 0.2, 1.0));
}
else {
camera_transform.translation = player_transform.translation;
camera_transform.rotation = rotation;
}
}
@ -166,8 +151,8 @@ pub fn update_map_camera(
settings: Res<var::Settings>,
mut mapcam: ResMut<MapCam>,
mut q_camera: Query<&mut Transform, (With<Camera>, Without<actor::PlayerCamera>)>,
q_playercam: Query<(Entity, &Transform), (With<actor::PlayerCamera>, Without<Camera>)>,
q_target: Query<(Entity, &Transform), (With<hud::IsTargeted>, Without<Camera>, Without<actor::PlayerCamera>)>,
q_playercam: Query<&Transform, (With<actor::PlayerCamera>, Without<Camera>)>,
q_target: Query<&Transform, (With<hud::IsTargeted>, Without<Camera>, Without<actor::PlayerCamera>)>,
q_target_changed: Query<(), Changed<hud::IsTargeted>>,
mut mouse_events: EventReader<MouseMotion>,
mut er_mousewheel: EventReader<MouseWheel>,
@ -177,13 +162,12 @@ pub fn update_map_camera(
return;
}
let mut camera_transform = q_camera.get_single_mut().unwrap();
let (player_entity, player_trans) = q_playercam.get_single().unwrap();
let (target_entity, target_trans) = if let Ok(target) = q_target.get_single() {
let player_transform = q_playercam.get_single().unwrap();
let target = if let Ok(target) = q_target.get_single() {
target
} else {
(player_entity, player_trans)
player_transform
};
mapcam.center_on_entity = Some(target_entity);
// Get mouse movement
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"
// direction parameter for the Transform.look_at function is ambiguous
// 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
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;
// Reset movement offset if target changes
@ -226,9 +211,9 @@ pub fn update_map_camera(
// Update zoom level
if !mapcam.initialized {
let factor: f64 = if target_trans == player_trans { 7.0 } else { 1.0 };
mapcam.target_zoom_level *= target_trans.scale.x as f64 * factor;
mapcam.zoom_level *= target_trans.scale.x as f64 * factor;
let factor: f64 = if target == player_transform { 7.0 } else { 1.0 };
mapcam.target_zoom_level *= target.scale.x as f64 * factor;
mapcam.zoom_level *= target.scale.x as f64 * factor;
mapcam.initialized = true;
}
let mut change_zoom: f64 = 0.0;
@ -247,7 +232,8 @@ pub fn update_map_camera(
// Update point of view
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
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);
// Apply updates to camera
camera_transform.translation = point_of_view.as_vec3();
camera_transform.look_at(Vec3::ZERO, 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;
}
}
mapcam.center = target.translation.as_dvec3() + offset;
camera_transform.translation = target.translation + point_of_view.as_vec3();
camera_transform.look_at(mapcam.center.as_vec3(), Vec3::Y);
}
pub fn update_fov(
@ -305,19 +276,33 @@ pub fn update_fov(
pub fn handle_input(
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_game: EventWriter<GameEvent>,
mut ew_updateoverlays: EventWriter<hud::UpdateOverlayVisibility>,
) {
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) {
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) {
ew_game.send(GameEvent::SetRotationStabilizer(Toggle));
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>>,
mut mouse_events: EventReader<MouseMotion>,
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>)>,
mut q_playercam: Query<(
&Transform,
@ -377,7 +365,7 @@ pub fn apply_input_to_player(
Option<&actor::PlayerDrivesThis>,
), (With<actor::PlayerCamera>, Without<Camera>)>,
) {
if settings.map_active || !settings.in_control() {
if settings.map_active {
return;
}
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() {
// Handle key input
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;
}
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());
}
}
} 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
// 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 slowrot {
(2.0 - engine.reaction_wheels.powf(0.05).clamp(1.001, 1.1)) as f64
let angular_slowdown: f64 = if settings.rotation_stabilizer_active {
(2.0 - engine.reaction_wheels.powf(0.01).clamp(1.001, 1.1)) as f64
} else {
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
if let Some(sink) = sinks.get(&audio::Sfx::ElectricMotor) {
if let Ok(sink) = electricmotor_sound_controller.get_single() {
let volume = sink.volume();
let speed = sink.speed();
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![
(1.2, actor::EngineType::Monopropellant, sinks.get(&audio::Sfx::Thruster)),
(1.0, actor::EngineType::Rocket, sinks.get(&audio::Sfx::Rocket)),
(1.4, actor::EngineType::Ion, sinks.get(&audio::Sfx::Ion)),
(1.2, actor::EngineType::Monopropellant, thruster_sound_controller.get_single()),
(1.0, actor::EngineType::Rocket, rocket_sound_controller.get_single()),
(1.4, actor::EngineType::Ion, ion_sound_controller.get_single()),
];
let seconds_to_max_vol = 0.05;
let seconds_to_min_vol = 0.05;
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 {
sink.pause();
}
@ -605,7 +583,7 @@ pub fn update_map_only_object_visibility(
q_camera: Query<&Transform, With<Camera>>,
q_player: Query<&Position, With<actor::PlayerCamera>>,
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() {
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.
let (angular_diameter, angle, distance) = calc_angular_diameter_known_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!
//commands.entity(entity).insert(IsTargeted);
let distance_to_surface = distance - trans.scale.x;
@ -694,30 +672,3 @@ pub fn calc_angular_diameter(
.normalize_or_zero();
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
// and manages the flow of conversations.
use crate::prelude::*;
use crate::{actor, audio, effects, hud, var, world};
use bevy::prelude::*;
use bevy::math::DVec3;
use bevy_xpbd_3d::prelude::*;
use serde_yaml::Value;
use serde::Deserialize;
@ -618,7 +619,6 @@ pub fn handle_new_conversations(
mut er_conv: EventReader<StartConversationEvent>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_chatevent: EventWriter<ChatEvent>,
mut ew_achievement: EventWriter<game::AchievementEvent>,
chatdb: Res<ChatDB>,
q_chats: Query<&Chat>,
time: Res<Time>,
@ -630,9 +630,6 @@ pub fn handle_new_conversations(
}
match (*chatdb).get_chat_by_id(&event.talker.chat_name) {
Ok(chat_id) => {
if let Some(name) = &event.talker.name {
ew_achievement.send(game::AchievementEvent::TalkTo(name.clone()));
}
let mut chat = Chat {
internal_id: chat_id,
position: vec![0],
@ -705,9 +702,6 @@ pub fn handle_chat_events(
hud::LogLevel::Info => {
log.info(message.into());
}
hud::LogLevel::Achievement => {
log.add(message.into(), "".into(), hud::LogLevel::Achievement);
}
hud::LogLevel::Warning => {
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_playercam: Query<(&mut Position, &mut LinearVelocity), With<actor::PlayerCamera>>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_effect: EventWriter<visual::SpawnEffectEvent>,
mut ew_achievement: EventWriter<game::AchievementEvent>,
id2pos: Res<game::Id2Pos>,
mut ew_effect: EventWriter<effects::SpawnEffectEvent>,
id2pos: Res<actor::Id2Pos>,
) {
for script in er_chatscript.read() {
// Parse the script string
@ -846,7 +839,6 @@ pub fn handle_chat_scripts(
error!("Invalid parameter for command `{}`: `{}`", name, param1);
}
"repairsuit" => {
ew_achievement.send(game::AchievementEvent::RepairSuit);
for (_, mut suit, _) in q_player.iter_mut() {
suit.integrity = 1.0;
}
@ -879,21 +871,18 @@ pub fn handle_chat_scripts(
gforce.ignore_gforce_seconds = 1.0;
}
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::WakeUp));
ew_effect.send(visual::SpawnEffectEvent {
class: visual::Effects::FadeIn(Color::CYAN),
ew_effect.send(effects::SpawnEffectEvent {
class: effects::Effects::FadeIn(Color::CYAN),
duration: 1.0,
});
}
}
"cryofadeout" => {
ew_effect.send(visual::SpawnEffectEvent {
class: visual::Effects::FadeOut(Color::CYAN),
ew_effect.send(effects::SpawnEffectEvent {
class: effects::Effects::FadeOut(Color::CYAN),
duration: 5.1,
});
}
"drinkpizza" => {
ew_achievement.send(game::AchievementEvent::DrinkPizza);
}
_ => {
error!("Error, undefined chat script {name}");
}

View file

@ -74,6 +74,10 @@
- Micros? What's that?:
- Micrometeorites. Those tiny 混蛋 that fly right through you, leaving holes in your suit. And your body.
- 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?:
- That's a very philosophical question.
- I don't know.
@ -86,7 +90,7 @@
- I'm here mostly for the view and the peace.
- Just look at Jupiter, it's mesmerizing, isn't it?
- 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?
- You're too kind!:
- Ah, don't mention it!
@ -206,31 +210,27 @@
- if: $knows-menu
I'd like a Suspicious Spacefunghi:
- Coming right up your feeding tube!
- script: drinkpizza
- system: Received Suspicious Spacefunghi pizza smoothie
- goto: served
- if: $knows-menu
I'd like a Daring Durian:
- Coming right up your feeding tube!
- script: drinkpizza
- system: Received Daring Durian pizza smoothie
- goto: served
- if: $knows-menu
I'd like an Artichoke Apple Pie pizza:
- Coming right up your feeding tube!
- script: drinkpizza
- system: Received Artichoke Apple Pie pizza smoothie
- goto: served
- if: $knows-pineapple
I'd like a pineapple pizza:
- Coming right up your feeding tube!
- script: drinkpizza
- system: Received pineapple pizza smoothie
- goto: served
- 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!
- system: Received Old Earth Soykaf
- system: Received Old Earth Coffee
- goto: served
- Surprise me.:
- Hmm...
@ -257,7 +257,7 @@
- goto: served
- Got any coffee?:
- 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
- goto: non-pizza
- 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
- Where are we?:
- Inside Jupiter's rings, obviously.
- 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.
- 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.: []

View file

@ -13,21 +13,19 @@
extern crate regex;
use bevy::prelude::*;
use bevy_xpbd_3d::prelude::*;
use bevy::math::DVec3;
use bevy::pbr::{NotShadowCaster, NotShadowReceiver};
use crate::prelude::*;
use crate::{actor, camera, chat, hud, nature, shading, skeleton, var, world};
use regex::Regex;
use std::f32::consts::PI;
use std::f64::consts::PI as PI64;
use std::time::SystemTime;
pub const ID_EARTH: &str = "earth";
pub const ID_SOL: &str = "sol";
pub const ID_JUPITER: &str = "jupiter";
pub struct CmdPlugin;
impl Plugin for CmdPlugin {
pub struct CommandsPlugin;
impl Plugin for CommandsPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, load_defs);
app.add_systems(Update, spawn_entities);
app.add_systems(Update, process_mesh);
app.add_systems(PreUpdate, hide_colliders
.run_if(any_with_component::<NeedsSceneColliderRemoved>));
app.add_event::<SpawnEvent>();
@ -57,7 +55,6 @@ struct ParserState {
model: Option<String>,
model_scale: f32,
rotation: Quat,
axialtilt: f32,
velocity: DVec3,
angular_momentum: DVec3,
pronoun: Option<String>,
@ -71,7 +68,6 @@ struct ParserState {
is_targeted_on_startup: bool,
is_sun: bool,
is_moon: bool,
is_planet: bool,
is_point_of_interest: bool,
orbit_distance: Option<f64>,
orbit_object_id: Option<String>,
@ -113,7 +109,6 @@ impl Default for ParserState {
model: None,
model_scale: 1.0,
rotation: Quat::IDENTITY,
axialtilt: 0.0,
velocity: DVec3::splat(0.0),
angular_momentum: DVec3::new(0.03, 0.3, 0.09),
pronoun: None,
@ -127,7 +122,6 @@ impl Default for ParserState {
is_targeted_on_startup: false,
is_sun: false,
is_moon: false,
is_planet: false,
is_point_of_interest: false,
orbit_distance: None,
orbit_object_id: None,
@ -253,7 +247,7 @@ pub fn load_defs(
["orbit", radius_str, phase_str] => {
if let (Ok(r), Ok(phase)) = (radius_str.parse::<f64>(), phase_str.parse::<f64>()) {
state.orbit_distance = Some(r);
state.orbit_phase = Some(phase * PI * 2.0);
state.orbit_phase = Some(phase * PI64 * 2.0);
}
else {
error!("Can't parse float: {line}");
@ -262,7 +256,7 @@ pub fn load_defs(
}
["orbit_phase_offset", value] => {
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 {
state.orbit_phase = Some(phase_radians + offset_radians);
}
@ -295,9 +289,6 @@ pub fn load_defs(
["moon", "yes"] => {
state.is_moon = true;
}
["planet", "yes"] => {
state.is_planet = true;
}
["sun", "yes"] => {
state.is_sun = true;
}
@ -335,7 +326,7 @@ pub fn load_defs(
}
["rotationx", rotation_x] => {
if let Ok(rotation_x_float) = rotation_x.parse::<f32>() {
state.rotation *= Quat::from_rotation_x(rotation_x_float.to_radians());
state.rotation *= Quat::from_rotation_x(PI * rotation_x_float);
}
else {
error!("Can't parse float: {line}");
@ -344,7 +335,7 @@ pub fn load_defs(
}
["rotationy", rotation_y] => {
if let Ok(rotation_y_float) = rotation_y.parse::<f32>() {
state.rotation *= Quat::from_rotation_y(rotation_y_float.to_radians());
state.rotation *= Quat::from_rotation_y(PI * rotation_y_float);
}
else {
error!("Can't parse float: {line}");
@ -353,17 +344,7 @@ pub fn load_defs(
}
["rotationz", rotation_z] => {
if let Ok(rotation_z_float) = rotation_z.parse::<f32>() {
state.rotation *= Quat::from_rotation_z(rotation_z_float.to_radians());
}
else {
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;
state.rotation *= Quat::from_rotation_z(PI * rotation_z_float);
}
else {
error!("Can't parse float: {line}");
@ -531,14 +512,12 @@ fn spawn_entities(
asset_server: Res<AssetServer>,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut materials_jupiter: ResMut<Assets<load::JupitersRing>>,
mut id2pos: ResMut<game::Id2Pos>,
mut achievement_tracker: ResMut<var::AchievementTracker>,
mut materials_jupiter: ResMut<Assets<shading::JupitersRing>>,
mut id2pos: ResMut<actor::Id2Pos>,
settings: Res<var::Settings>,
) {
for state_wrapper in er_spawn.read() {
let state = &state_wrapper.0;
let mut rotation = state.rotation;
if state.class == DefClass::Actor {
// Preprocessing
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 {
let mass = match id.as_str() {
"jupiter" => nature::JUPITER_MASS,
"sol" => nature::JUPITER_MASS,
_ => {
error!("Found no mass for object `{id}`");
continue;
@ -570,8 +548,7 @@ fn spawn_entities(
};
let orbital_period = nature::simple_orbital_period(mass, r);
phase_radians += if let Ok(epoch) = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
let now = epoch.as_secs_f64() + 614533234154.0; // random
PI * 2.0 * (now % orbital_period) / orbital_period
PI64 * 2.0 * (epoch.as_secs_f64() % orbital_period) / orbital_period
} else {
error!("Can't determine current time `{id}`");
0.0
@ -601,13 +578,13 @@ fn spawn_entities(
actor.insert(world::DespawnOnPlayerDeath);
actor.insert(actor::HitPoints::default());
actor.insert(Position::from(absolute_pos));
actor.insert(Rotation::from(state.rotation));
if state.is_sphere {
let sphere_texture_handle = if let Some(model) = &state.model {
Some(asset_server.load(format!("textures/{}.jpg", model)))
} else {
None
};
rotation = Quat::from_rotation_x(-90f32.to_radians()) * rotation;
let sphere_handle = meshes.add(Sphere::new(1.0).mesh().uv(128, 128));
let sphere_material_handle = materials.add(StandardMaterial {
base_color_texture: sphere_texture_handle,
@ -626,9 +603,8 @@ fn spawn_entities(
transform: Transform::from_scale(scale),
..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
if state.has_physics {
@ -699,7 +675,6 @@ fn spawn_entities(
integrity: state.suit_integrity,
..default()
});
actor.insert(actor::Battery::default());
}
if state.is_clickable {
actor.insert(hud::IsClickable {
@ -739,22 +714,15 @@ fn spawn_entities(
pronoun: state.pronoun.clone(),
talking_speed: 1.0,
});
if let Some(name) = &state.name {
achievement_tracker.all_people.insert(name.clone());
}
}
if state.is_vehicle {
actor.insert(actor::Vehicle::default());
if let Some(name) = &state.name {
achievement_tracker.all_vehicles.insert(name.clone());
}
}
if state.is_vehicle
|| state.is_suited
|| state.thrust_forward > 0.0
|| state.thrust_sideways > 0.0
|| state.thrust_back > 0.0
|| state.reaction_wheels > 0.0
if state.is_vehicle || state.is_suited
|| state.thrust_forward > 0.0
|| state.thrust_sideways > 0.0
|| state.thrust_back > 0.0
|| state.reaction_wheels > 0.0
{
actor.insert(actor::Engine {
thrust_forward: state.thrust_forward,
@ -769,32 +737,6 @@ fn spawn_entities(
if let Some(_) = state.ar_model {
actor.insert(hud::AugmentedRealityOverlayBroadcaster);
}
if state.is_player {
actor.with_children(|builder| {
builder.spawn((
world::DespawnOnPlayerDeath,
actor::PlayersFlashLight,
SpotLightBundle {
transform: Transform {
translation: Vec3::new(0.0, 0.0, 1.0),
rotation: Quat::from_rotation_y(180f32.to_radians()),
..default()
},
spot_light: SpotLight {
intensity: 40_000_000.0, // lumens
color: Color::WHITE,
shadows_enabled: true,
inner_angle: PI32 / 8.0 * 0.85,
outer_angle: PI32 / 4.0,
range: 2000.0,
..default()
},
visibility: Visibility::Hidden,
..default()
}
));
});
}
actor_entity = actor.id();
}
@ -811,10 +753,10 @@ fn spawn_entities(
NotShadowCaster,
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((
hud::PointOfInterestMarker(actor_entity),
world::DespawnOnPlayerDeath,
@ -826,23 +768,15 @@ fn spawn_entities(
NotShadowCaster,
NotShadowReceiver,
));
let model = if state.is_point_of_interest {
"point_of_interest"
} else if state.is_planet {
"marker_planets"
} else {
"marker_satellites"
};
load_asset(model, &mut entitycmd, &*asset_server);
skeleton::load("point_of_interest", &mut entitycmd, &*asset_server);
}
if state.has_ring {
let ring_radius = state.model_scale * (nature::JUPITER_RING_RADIUS / nature::JUPITER_RADIUS) as f32;
commands.spawn((
world::DespawnOnPlayerDeath,
MaterialMeshBundle {
mesh: meshes.add(Mesh::from(Cylinder::new(ring_radius, 1.0))),
material: materials_jupiter.add(load::JupitersRing {
mesh: meshes.add(Mesh::from(Cylinder::new(nature::JUPITER_RING_RADIUS as f32, 1.0))),
material: materials_jupiter.add(shading::JupitersRing {
alpha_mode: AlphaMode::Blend,
ring_radius: nature::JUPITER_RING_RADIUS as f32,
jupiter_radius: nature::JUPITER_RADIUS as f32,
@ -851,7 +785,8 @@ fn spawn_entities(
..default()
},
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,
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
E: Interact
F: Flashlight
Space: Slow down (or match velocity)
AWSD/Shift/Ctrl: Movement
R: Rotate (hold + move mouse)
E: Interact: Talk to people, enter vehicles
Q: Exit vehicle
M: Map
C: Camera
T: Cruise control
R: Rotate (hold + move mouse)
Tab: Toggle HUD + Augmented Reality
Left click: Target objects [AUGMENTED REALITY ONLY]
Right click: Zoom [AUGMENTED REALITY ONLY]
F: 3rd person view
Y: Rotation stabilizer
AWSD/Shift/Ctrl: Move
J/K/U/L/I/O: Rotate
F11: Fullscreen
Tab: Toggle Augmented Reality
Augmented Reality only:
Left click: Target objects
Right click: Zoom
Cheats:
G: Toggle cheats + invulnerability
V/B: Impossible acceleration
Shift+V/B: Extreme acceleration
X: Teleport to target
Z: Stop
F2: Toggle shadows
F3: Toggle sound effects
F4: Toggle music
F7: Restart game
F11: Toggle fullscreen
JKULIO: Mouseless camera rotation
G: Toggle god mode + cheats [CHEAT]
V/B: Impossible acceleration forward/backward [CHEAT]
Shift+V/B: Same as V/B, but a thousand times faster [CHEAT]
C: Impossibly instant stopping [CHEAT]
X: Teleport to target [CHEAT]

View file

@ -11,18 +11,17 @@
// This module manages visual effects.
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) {
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(Update, spawn_effects);
app.add_systems(Update, update_fadein);
app.add_systems(Update, update_fadeout);
app.add_systems(Update, play_animations);
// Blackout disabled for now
//app.add_systems(Update, update_blackout);
app.add_event::<SpawnEffectEvent>();
@ -94,7 +93,14 @@ pub fn spawn_effects(
},
FadeIn,
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(),
..default()
},
@ -109,7 +115,14 @@ pub fn spawn_effects(
},
FadeOut,
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(),
..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
//pub fn update_blackout(
// 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.
use crate::prelude::*;
use bevy::pbr::{NotShadowCaster, NotShadowReceiver};
use crate::{actor, audio, camera, chat, nature, skeleton, var};
use bevy::prelude::*;
use bevy::diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin};
use bevy::transform::TransformSystem;
use bevy_xpbd_3d::prelude::*;
use bevy::math::DVec3;
use std::collections::VecDeque;
use std::time::SystemTime;
pub const DASHBOARD_ICON_SIZE: f32 = 64.0;
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_ROWS: usize = 30;
pub const LOG_MAX: usize = LOG_MAX_ROWS;
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 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 DASHBOARD_DEF: &[(Dashboard, &str)] = &[
(Dashboard::Flashlight, "highbeams"),
(Dashboard::Leak, "leak"),
(Dashboard::RotationStabiliser, "rotation_stabiliser"),
(Dashboard::CruiseControl, "cruise_control"),
(Dashboard::Radioactivity, "radioactivity"),
];
pub struct HudPlugin;
impl Plugin for HudPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, setup);
app.add_systems(Update, (
update_hud,
update_dashboard,
update_speedometer,
update_gauges,
handle_input.run_if(in_control),
handle_input,
handle_target_event,
));
app.add_systems(PostUpdate, (
update_overlay_visibility,
update_ar_overlays
.after(camera::position_to_transform)
.after(actor::position_to_transform)
.in_set(sync::SyncSet::PositionToTransform),
update_poi_overlays
.after(camera::position_to_transform)
.after(actor::position_to_transform)
.in_set(sync::SyncSet::PositionToTransform),
update_target_selectagon
.after(PhysicsSet::Sync)
@ -90,30 +80,12 @@ impl Plugin for HudPlugin {
#[derive(Component)] struct Reticule;
#[derive(Component)] struct Speedometer;
#[derive(Component)] struct Speedometer2;
#[derive(Component)] struct GaugeLength(f32);
#[derive(Component)] pub struct ToggleableHudElement;
#[derive(Component)] pub struct ToggleableHudMapElement;
#[derive(Component)] struct Selectagon;
#[derive(Component)] pub struct IsTargeted;
#[derive(Component)] pub struct PointOfInterestMarker(pub Entity);
#[derive(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)]
pub struct AugmentedRealityState {
pub overlays_visible: bool,
@ -129,7 +101,6 @@ pub struct AugmentedRealityOverlay {
struct FPSUpdateTimer(Timer);
pub enum LogLevel {
Achievement,
Always,
Warning,
//Error,
@ -224,9 +195,9 @@ impl Log {
}
}
pub fn setup(
fn setup(
mut commands: Commands,
settings: Res<Settings>,
settings: Res<var::Settings>,
asset_server: Res<AssetServer>,
mut ew_updateoverlays: EventWriter<UpdateOverlayVisibility>,
) {
@ -242,12 +213,6 @@ pub fn setup(
color: settings.hud_color_subtitles,
..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 {
font: font_handle.clone(),
font_size: settings.font_size_console,
@ -274,9 +239,24 @@ pub fn setup(
};
// Add Statistics HUD
let version = &settings.version;
let mut bundle_fps = TextBundle::from_sections([
TextSection::new("", style), // Target
TextSection::new("", style_fps), // Frames per second
TextSection::new("", style.clone()),
TextSection::new(format!(" OutFlyOS v{version}"), style.clone()),
TextSection::new("", style.clone()),
TextSection::new("", style.clone()),
TextSection::new("", style.clone()),
TextSection::new("", style.clone()),
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 {
position_type: PositionType::Absolute,
top: Val::VMin(2.0),
@ -325,7 +305,13 @@ pub fn setup(
let reticule_handle: Handle<Image> = asset_server.load("sprites/reticule4.png");
commands.spawn((
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,
..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
let speedometer_handle: Handle<Image> = asset_server.load("sprites/speedometer.png");
commands.spawn((
@ -485,7 +338,6 @@ pub fn setup(
style: Style {
width: Val::VMin(0.0),
height: Val::Percent(100.0),
left: Val::Vw(100.0 - SPEEDOMETER_WIDTH),
align_items: AlignItems::End,
overflow: Overflow::clip(),
..default()
@ -500,7 +352,7 @@ pub fn setup(
ImageBundle {
image: UiImage::new(speedometer_handle),
style: Style {
width: Val::Vw(SPEEDOMETER_WIDTH),
width: Val::VMin(SPEEDOMETER_WIDTH),
height: Val::VMin(SPEEDOMETER_HEIGHT),
..Default::default()
},
@ -514,7 +366,6 @@ pub fn setup(
style: Style {
width: Val::VMin(0.0),
height: Val::Percent(100.0),
left: Val::Vw(100.0 - SPEEDOMETER_WIDTH),
align_items: AlignItems::End,
overflow: Overflow::clip(),
..default()
@ -529,7 +380,7 @@ pub fn setup(
ImageBundle {
image: UiImage::new(speedometer_handle),
style: Style {
width: Val::Vw(SPEEDOMETER_WIDTH),
width: Val::VMin(SPEEDOMETER_WIDTH),
height: Val::VMin(SPEEDOMETER_HEIGHT),
..Default::default()
},
@ -538,12 +389,11 @@ pub fn setup(
));
});
let mut bundle_speedometer_text = TextBundle::from_sections([
TextSection::new("", style_speedometer.clone()), // speed relative to target
TextSection::new("", style_speedometer.clone()), // speed relative to target
TextSection::new("", style_speedometer.clone()), // speed relative to orbit
]).with_style(Style {
position_type: PositionType::Absolute,
left: Val::Vw(100.0 - SPEEDOMETER_WIDTH + 2.0),
left: Val::VMin(2.0),
bottom: Val::VMin(4.0),
..default()
}).with_text_justify(JustifyText::Left);
@ -605,100 +455,41 @@ pub fn setup(
// Selectagon
let mut entitycmd = commands.spawn((
Selectagon,
NotShadowCaster,
NotShadowReceiver,
SpatialBundle {
visibility: Visibility::Hidden,
..default()
},
));
load_asset("selectagon", &mut entitycmd, &*asset_server);
skeleton::load("selectagon", &mut entitycmd, &*asset_server);
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(
timer: ResMut<FPSUpdateTimer>,
settings: Res<Settings>,
q_camera: Query<&LinearVelocity, With<actor::PlayerCamera>>,
q_player: Query<&actor::ExperiencesGForce, With<actor::Player>>,
q_target: Query<&LinearVelocity, With<IsTargeted>>,
mut q_speedometer: Query<&mut Style, (With<Speedometer>, Without<Speedometer2>)>,
mut q_speedometer2: Query<&mut Style, (With<Speedometer2>, Without<Speedometer>)>,
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() {
let speed = cam_v.length();
let speedometer_split = 5_000.0;
if let Ok(mut speedometer) = q_speedometer.get_single_mut() {
let custom_c = speedometer_split;
let fraction = nature::inverse_lorentz_factor_custom_c((custom_c - speed).clamp(0.0, custom_c), custom_c).clamp(0.0, 1.0) as f32;
let fraction = nature::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);
speedometer.width = Val::Vw(wid);
speedometer.width = Val::VMin(wid);
}
if let Ok(mut speedometer2) = q_speedometer2.get_single_mut() {
let custom_c = nature::C - speedometer_split;
let fraction = nature::inverse_lorentz_factor_custom_c((custom_c - speed + speedometer_split).clamp(0.0, custom_c), custom_c).clamp(0.0, 1.0) as f32;
let fraction = nature::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);
speedometer2.width = Val::Vw(wid);
speedometer2.width = Val::VMin(wid);
}
if let Ok(mut speed_text) = q_node_speed.get_single_mut() {
// G forces
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() {
speed_text.sections[0].value = if let Ok(target_v) = q_target.get_single() {
let delta_v = (target_v.0 - cam_v.0).length();
if delta_v > 0.0001 {
format!("Δv {}\n", nature::readable_speed(delta_v))
@ -708,9 +499,7 @@ fn update_speedometer(
} else {
"".to_string()
};
// "Absolute velocity", or velocity relative to orbit
speed_text.sections[2].value = if speed > 0.0001 {
speed_text.sections[1].value = if speed > 0.0001 {
nature::readable_speed(speed)
} else {
"".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(
diagnostics: Res<DiagnosticsStore>,
time: Res<Time>,
mut log: ResMut<Log>,
player: Query<(&actor::HitPoints, &actor::Suit, &actor::ExperiencesGForce), With<actor::Player>>,
q_camera: Query<(&Position, &LinearVelocity), With<actor::PlayerCamera>>,
mut timer: ResMut<FPSUpdateTimer>,
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_choice: Query<&mut Text, (With<NodeChoiceText>, Without<NodeHud>, Without<NodeConsole>)>,
mut q_node_currentline: Query<&mut Text, (With<NodeCurrentChatLine>, Without<NodeHud>, Without<NodeConsole>, Without<NodeChoiceText>)>,
settings: Res<Settings>,
query_all_actors: Query<&actor::Actor>,
settings: Res<var::Settings>,
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 {
let q_camera_result = q_camera.get_single();
let player = player.get_single();
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();
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(value) = fps.smoothed() {
// 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
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 {
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 {
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() {
let distance = if dist_scalar.is_nan() {
@ -829,10 +640,10 @@ fn update_hud(
} else {
"".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 {
text.sections[0].value = "".to_string();
text.sections[15].value = "".to_string();
}
}
}
@ -866,7 +677,6 @@ fn update_hud(
} else {
|msg: &&Message| { match msg.level {
LogLevel::Always => true,
LogLevel::Achievement => true,
_ => false
}}
};
@ -882,7 +692,6 @@ fn update_hud(
let opacity: f32 = (freshness.powf(1.5) as f32).clamp(0.0, 1.0);
freshest_line = freshest_line.max(freshness);
chat.sections[row].style.color = match msg.level {
LogLevel::Achievement => settings.hud_color_console_achievement,
LogLevel::Warning => settings.hud_color_console_warn,
LogLevel::Info => settings.hud_color_console_system,
_ => settings.hud_color_console,
@ -959,16 +768,26 @@ fn update_hud(
fn handle_input(
keyboard_input: Res<ButtonInput<KeyCode>>,
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_togglemusic: EventWriter<audio::ToggleMusicEvent>,
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_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) {
ew_game.send(GameEvent::SetAR(Turn::Toggle));
settings.hud_active ^= true;
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 let Ok(camtrans) = q_camera.get_single() {
@ -985,12 +804,10 @@ fn handle_input(
fn handle_target_event(
mut commands: Commands,
settings: Res<Settings>,
settings: Res<var::Settings>,
mut er_target: EventReader<TargetEvent>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_achievement: EventWriter<game::AchievementEvent>,
q_target: Query<Entity, With<IsTargeted>>,
q_ids: Query<&actor::Identifier>,
) {
let mut play_sfx = false;
@ -1002,12 +819,6 @@ fn handle_target_event(
if let Some(entity) = target {
commands.entity(*entity).insert(IsTargeted);
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 {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Click));
@ -1017,7 +828,7 @@ fn handle_target_event(
}
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>)>,
q_target: Query<&Transform, (With<IsTargeted>, Without<Camera>, 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.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
let (angular_diameter, _, _) = camera::calc_angular_diameter(
@ -1057,7 +868,7 @@ fn update_target_selectagon(
fn update_ar_overlays (
q_owners: Query<(Entity, &Transform, &Visibility), (With<AugmentedRealityOverlayBroadcaster>, Without<AugmentedRealityOverlay>)>,
mut q_overlays: Query<(&mut Transform, &mut Visibility, &mut AugmentedRealityOverlay)>,
settings: ResMut<Settings>,
settings: ResMut<var::Settings>,
mut state: ResMut<AugmentedRealityState>,
) {
let (need_activate, need_clean, need_update);
@ -1094,7 +905,7 @@ fn update_poi_overlays (
mut q_marker: Query<(&mut Transform, &PointOfInterestMarker)>,
q_parent: Query<&Transform, Without<PointOfInterestMarker>>,
q_camera: Query<&Transform, (With<Camera>, Without<PointOfInterestMarker>)>,
settings: ResMut<Settings>,
settings: ResMut<var::Settings>,
) {
if !settings.hud_active || !settings.map_active || q_camera.is_empty() {
return;
@ -1123,7 +934,7 @@ fn update_overlay_visibility(
q_target: Query<&IsTargeted, (Without<Camera>, Without<Selectagon>, Without<PointOfInterestMarker>, Without<ToggleableHudElement>)>,
mut ambient_light: ResMut<AmbientLight>,
er_target: EventReader<UpdateOverlayVisibility>,
settings: Res<Settings>,
settings: Res<var::Settings>,
) {
if er_target.is_empty() {
return;

View file

@ -11,35 +11,25 @@
// This module initializes the game, handles command-line arguments,
// and manages window-related key bindings.
pub mod actor;
pub mod audio;
pub mod camera;
pub mod chat;
pub mod cmd;
pub mod common;
pub mod game;
pub mod hud;
pub mod load;
pub mod menu;
#[allow(dead_code)]
pub mod nature;
pub mod var;
pub mod visual;
pub mod world;
mod actor;
mod audio;
mod camera;
mod chat;
mod commands;
mod effects;
mod hud;
mod shading;
mod skeleton;
mod var;
mod world;
pub mod prelude {
pub use crate::{actor, audio, camera, chat, cmd, common, game, hud,
load, menu, nature, var, visual, world};
pub use crate::common::*;
pub use crate::var::Settings;
pub use crate::load::load_asset;
pub use game::{GameEvent, Turn};
pub use game::Turn::Toggle;
}
#[allow(dead_code)]
mod nature;
use bevy::window::{Window, WindowMode, PrimaryWindow, CursorGrabMode};
use bevy::diagnostic::FrameTimeDiagnosticsPlugin;
use bevy::prelude::*;
use bevy::pbr::ExtendedMaterial;
use std::env;
const HELP: &str = "./outfly [options]
@ -56,7 +46,7 @@ Note: borderless fullscreen is the default, but it crashes on some systems.";
fn main() {
let prefs = var::load_prefs();
let mut opt = var::CommandLineOptions {
let mut opt = CommandLineOptions {
window_mode_fullscreen: prefs.get_fullscreen_mode(),
window_mode_initial: prefs.get_window_mode(),
use_gl: prefs.render_mode_is_gl(),
@ -127,6 +117,7 @@ impl Plugin for OutFlyPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, setup);
app.add_systems(Update, handle_input);
app.add_systems(Update, debug);
app.insert_resource(var::Settings::default());
app.insert_resource(var::GameVars::default());
app.add_plugins((
@ -137,37 +128,72 @@ impl Plugin for OutFlyPlugin {
audio::AudioPlugin,
camera::CameraPlugin,
chat::ChatPlugin,
cmd::CmdPlugin,
game::GamePlugin,
menu::MenuPlugin,
visual::VisualPlugin,
commands::CommandsPlugin,
effects::EffectsPlugin,
hud::HudPlugin,
load::LoadPlugin,
shading::ShadingPlugin,
skeleton::SkeletonPlugin,
world::WorldPlugin,
));
}
}
#[derive(Resource, Default)]
pub struct CommandLineOptions {
window_mode_fullscreen: WindowMode,
window_mode_initial: WindowMode,
use_gl: bool,
}
fn setup(
mut windows: Query<&mut Window, With<PrimaryWindow>>,
opt: Res<var::CommandLineOptions>,
opt: Res<CommandLineOptions>,
) {
for mut window in &mut windows {
window.cursor.grab_mode = CursorGrabMode::Locked;
window.cursor.visible = false;
window.mode = opt.window_mode_initial;
window.title = common::GAME_NAME.to_string();
window.title = "OutFly".to_string();
}
}
fn handle_input(
keyboard_input: Res<ButtonInput<KeyCode>>,
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_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) {
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.
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 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 JUPITER_RADIUS: f64 = 71_492_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;
// 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_outer: f32 = 229.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 main_brightness: f32 = 1.0;
@ -90,7 +89,7 @@ pub fn ring_density(radius: f32) -> f32 {
} else if radius >= main_inner && radius <= main_outer {
let mut metis_notch_effect: f32 = 1.0;
if radius > metis_notch_center - metis_notch_width * 0.5 && radius < metis_notch_center + metis_notch_width * 0.5 {
metis_notch_effect = 0.8 * (1.0 - smooth_edge(metis_notch_center - metis_notch_width * 0.5, metis_notch_center + metis_notch_width * 0.5, radius));
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);
} else {
@ -138,24 +137,16 @@ pub fn readable_speed(speed: f64) -> String {
}
}
pub fn lorentz_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 {
pub fn lorenz_factor(speed: f64) -> f64 {
(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()
}
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 {

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::pbr::{ExtendedMaterial, MaterialExtension, OpaqueRendererMethod};
use bevy::prelude::*;
pub struct LoadPlugin;
impl Plugin for LoadPlugin {
pub struct ShadingPlugin;
impl Plugin for ShadingPlugin {
fn build(&self, app: &mut App) {
app.add_plugins(MaterialPlugin::<JupitersRing>::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
#[derive(Asset, TypePath, AsBindGroup, Debug, Clone)]
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
// "if"-conditions in chats.
use crate::prelude::*;
use bevy::window::WindowMode;
use bevy::prelude::*;
use std::collections::{HashMap, HashSet};
use std::collections::HashMap;
use serde::Deserialize;
use toml_edit::DocumentMut;
use std::env;
@ -37,7 +36,6 @@ pub struct Settings {
pub dev_mode: bool,
pub god_mode: bool,
pub version: String,
pub alive: bool,
pub mute_sfx: bool,
pub mute_music: bool,
pub volume_sfx: u8,
@ -48,48 +46,24 @@ pub struct Settings {
pub zoom_fov: f32,
pub zoom_sensitivity_factor: f32,
pub font_size_hud: f32,
pub font_size_fps: f32,
pub font_size_conversations: f32,
pub font_size_choices: f32,
pub font_size_console: 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_fps: Color,
pub hud_color_console: Color,
pub hud_color_console_warn: Color,
pub hud_color_console_system: Color,
pub hud_color_console_achievement: Color,
pub hud_color_alert: Color,
pub hud_color_subtitles: Color,
pub hud_color_choices: 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 flashlight_active: bool,
pub hud_active: bool,
pub map_active: bool,
pub deathscreen_active: bool,
pub menu_active: bool,
pub death_cause: String,
pub is_zooming: bool,
pub third_person: bool,
pub rotation_stabilizer_active: bool,
pub cruise_control_active: bool,
pub shadows_sun: bool,
pub shadows_pointlights: bool,
pub shadowmap_resolution: usize,
@ -102,7 +76,8 @@ pub struct Settings {
//pub key_map_zoom_out_wheel: MouseButton,
//pub key_map_zoom_in_wheel: MouseButton,
pub key_togglehud: KeyCode,
pub key_menu: KeyCode,
pub key_exit: KeyCode,
pub key_restart: KeyCode,
pub key_fullscreen: KeyCode,
pub key_help: KeyCode,
pub key_forward: KeyCode,
@ -116,8 +91,7 @@ pub struct Settings {
pub key_interact: KeyCode,
pub key_vehicle: KeyCode,
pub key_camera: KeyCode,
pub key_flashlight: KeyCode,
pub key_cruise_control: KeyCode,
pub key_shadows: KeyCode,
pub key_rotate: KeyCode,
pub key_rotation_stabilizer: KeyCode,
pub key_mouseup: KeyCode,
@ -126,6 +100,8 @@ pub struct Settings {
pub key_mouseright: KeyCode,
pub key_rotateleft: KeyCode,
pub key_rotateright: KeyCode,
pub key_toggle_sfx: KeyCode,
pub key_toggle_music: KeyCode,
pub key_reply1: KeyCode,
pub key_reply2: KeyCode,
pub key_reply3: KeyCode,
@ -152,20 +128,19 @@ pub struct Settings {
impl Default for Settings {
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_music = dev_mode;
let default_mute_music = cfg!(feature = "mute_music");
let version = if let Some(version) = option_env!("CARGO_PKG_VERSION") {
version.to_string()
} else {
"".to_string()
"13.37".to_string()
};
Settings {
dev_mode,
god_mode: false,
version,
alive: true,
mute_sfx: default_mute_sfx,
mute_music: default_mute_music,
volume_sfx: 100,
@ -176,48 +151,24 @@ impl Default for Settings {
zoom_fov: 15.0,
zoom_sensitivity_factor: 0.25,
font_size_hud: 24.0,
font_size_fps: 14.0,
font_size_conversations: 32.0,
font_size_choices: 28.0,
font_size_console: 20.0,
font_size_speedometer: 34.0,
font_size_deathtext: 64.0,
font_size_deathsubtext: 32.0,
font_size_deathpoem: 18.0,
font_size_death_achievements: 24.0,
font_size_achievement: 24.0,
font_size_achievement_header: 32.0,
font_size_keybindings: 20.0,
font_size_version: 20.0,
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(),
hud_color: Color::hex("#197F19").unwrap(),
hud_color_console: Color::hex("#197F19").unwrap(),
hud_color_console_warn: Color::hex("#FF4C4C").unwrap(),
hud_color_console_system: Color::hex("#7F7F7F").unwrap(),
hud_color_alert: Color::hex("#991752").unwrap(),
hud_color_subtitles: Color::hex("#CCCCCC").unwrap(),
hud_color_choices: Color::hex("#727272").unwrap(),
hud_color_speedometer: Color::hex("#BE1251").unwrap(),
chat_speed: DEFAULT_CHAT_SPEED * if dev_mode { 2.5 } else { 1.0 },
flashlight_active: false,
hud_active: true,
hud_active: false,
map_active: false,
deathscreen_active: false,
menu_active: false,
death_cause: "Unknown".to_string(),
is_zooming: false,
third_person: true,
third_person: false,
rotation_stabilizer_active: true,
cruise_control_active: false,
shadows_sun: true,
shadows_pointlights: false,
shadowmap_resolution: 2048,
@ -230,7 +181,8 @@ impl Default for Settings {
//key_map_zoom_out_wheel: KeyCode::Shift,
//key_map_zoom_in_wheel: KeyCode::Shift,
key_togglehud: KeyCode::Tab,
key_menu: KeyCode::Escape,
key_exit: KeyCode::Escape,
key_restart: KeyCode::F7,
key_fullscreen: KeyCode::F11,
key_help: KeyCode::F1,
key_forward: KeyCode::KeyW,
@ -243,9 +195,8 @@ impl Default for Settings {
key_stop: KeyCode::Space,
key_interact: KeyCode::KeyE,
key_vehicle: KeyCode::KeyQ,
key_camera: KeyCode::KeyC,
key_flashlight: KeyCode::KeyF,
key_cruise_control: KeyCode::KeyT,
key_camera: KeyCode::KeyF,
key_shadows: KeyCode::F2,
key_rotate: KeyCode::KeyR,
key_rotation_stabilizer: KeyCode::KeyY,
key_mouseup: KeyCode::KeyI,
@ -254,6 +205,8 @@ impl Default for Settings {
key_mouseright: KeyCode::KeyL,
key_rotateleft: KeyCode::KeyU,
key_rotateright: KeyCode::KeyO,
key_toggle_sfx: KeyCode::F3,
key_toggle_music: KeyCode::F4,
key_reply1: KeyCode::Digit1,
key_reply2: KeyCode::Digit2,
key_reply3: KeyCode::Digit3,
@ -265,7 +218,7 @@ impl Default for Settings {
key_reply9: KeyCode::Digit9,
key_reply10: KeyCode::Digit0,
key_cheat_god_mode: KeyCode::KeyG,
key_cheat_stop: KeyCode::KeyZ,
key_cheat_stop: KeyCode::KeyC,
key_cheat_speed: KeyCode::KeyV,
key_cheat_speed_backward: KeyCode::KeyB,
key_cheat_teleport: KeyCode::KeyX,
@ -275,7 +228,7 @@ impl Default for Settings {
key_cheat_adrenaline_zero: KeyCode::F5,
key_cheat_adrenaline_mid: KeyCode::F6,
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.third_person = default.third_person;
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] {
@ -312,100 +262,6 @@ impl Settings {
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)]
@ -443,17 +299,17 @@ fn file_is_readable(file_path: &str) -> bool {
}
fn get_prefs_path() -> Option<String> {
let test = CONF_FILE;
let test = "outfly.toml";
if file_is_readable(test) {
return Some(test.to_string());
}
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()) {
return Some(test);
}
} 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()) {
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.
use crate::prelude::*;
use crate::{actor, hud, nature, shading, skeleton};
use bevy::prelude::*;
use bevy::math::I64Vec3;
use bevy::math::{DVec3, I64Vec3};
use bevy::scene::{InstanceId, SceneInstance};
use bevy::render::mesh::Indices;
use bevy_xpbd_3d::prelude::*;
use std::collections::HashMap;
use std::f32::consts::PI;
use fastrand;
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 ASTEROID_SPAWN_STEP: f64 = 1000.0;
const ASTEROID_SPAWN_STEP: f64 = 500.0;
const ASTEROID_VIEW_RADIUS: f64 = 3000.0;
const ASSET_NAME_ASTEROID1: &str = "asteroid1";
@ -38,24 +39,21 @@ impl Plugin for WorldPlugin {
app.add_systems(Startup, setup);
app.add_systems(PostUpdate, handle_despawn);
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(PhysicsDebugPlugin::default());
app.insert_resource(Gravity(DVec3::splat(0.0)));
app.insert_resource(AsteroidUpdateTimer(
Timer::from_seconds(ASTEROID_UPDATE_INTERVAL, TimerMode::Repeating)));
app.insert_resource(ActiveAsteroids(HashMap::new()));
app.add_event::<DespawnAsteroidEvent>();
app.add_event::<RespawnEvent>();
app.add_event::<DespawnEvent>();
}
}
#[derive(Resource)] struct AsteroidUpdateTimer(Timer);
#[derive(Resource)] pub struct ActiveAsteroids(pub HashMap<I64Vec3, AsteroidData>);
#[derive(Component)] struct Asteroid;
#[derive(Component)] pub struct Star;
#[derive(Component)] pub struct DespawnOnPlayerDeath;
#[derive(Event)] pub struct RespawnEvent;
pub struct AsteroidData {
entity: Entity,
@ -63,17 +61,20 @@ pub struct AsteroidData {
}
#[derive(Event)]
pub struct DespawnAsteroidEvent {
pub struct DespawnEvent {
entity: Entity,
sceneinstance: InstanceId,
origin: I64Vec3,
}
#[derive(Component)]
pub struct Star;
pub fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
mut materials_skybox: ResMut<Assets<load::SkyBox>>,
mut materials_skybox: ResMut<Assets<shading::SkyBox>>,
) {
// Generate starmap
let sphere_handle = meshes.add(Sphere::new(1.0).mesh().uv(16, 16));
@ -138,8 +139,6 @@ pub fn setup(
},
..default()
},
Position::from(pos_render),
Rotation::from(Quat::IDENTITY),
));
starcount += 1;
}
@ -157,7 +156,7 @@ pub fn setup(
}
commands.spawn(MaterialMeshBundle {
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)),
..default()
});
@ -169,11 +168,11 @@ fn spawn_despawn_asteroids(
mut timer: ResMut<AsteroidUpdateTimer>,
mut commands: Commands,
q_player: Query<&Position, With<actor::PlayerCamera>>,
mut ew_despawn: EventWriter<DespawnAsteroidEvent>,
mut ew_despawn: EventWriter<DespawnEvent>,
mut db: ResMut<ActiveAsteroids>,
q_asteroid: Query<(&Position, &SceneInstance), With<Asteroid>>,
mut last_player_cell: Local<I64Vec3>,
id2pos: Res<game::Id2Pos>,
id2pos: Res<actor::Id2Pos>,
asset_server: Res<AssetServer>,
) {
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 pos.0.distance(player.0) > 1000.0 {
ew_despawn.send(DespawnAsteroidEvent {
ew_despawn.send(DespawnEvent {
entity: asteroid.entity,
sceneinstance: **sceneinstance,
origin: origin.clone(),
@ -301,7 +300,7 @@ fn spawn_despawn_asteroids(
AngularVelocity(DVec3::new(0.1, 0.1, 0.03)),
LinearVelocity(DVec3::new(0.0, 0.0, 0.35)),
Collider::sphere(1.0),
Rotation::from(Quat::from_rotation_y(-PI32 / 3.)),
Rotation::from(Quat::from_rotation_y(-PI / 3.)),
Position::new(pos),
hud::IsClickable {
name: Some("Uncharted Rock".to_string()),
@ -321,7 +320,7 @@ fn spawn_despawn_asteroids(
},
..default()
});
load_asset(model, &mut entity_commands, &*asset_server);
skeleton::load(model, &mut entity_commands, &*asset_server);
db.0.insert(origin, AsteroidData {
entity: entity_commands.id(),
//viewdistance: 99999999.0,
@ -333,7 +332,7 @@ fn spawn_despawn_asteroids(
fn handle_despawn(
mut commands: Commands,
mut er_despawn: EventReader<DespawnAsteroidEvent>,
mut er_despawn: EventReader<DespawnEvent>,
mut db: ResMut<ActiveAsteroids>,
mut scene_spawner: ResMut<SceneSpawner>,
) {
@ -343,11 +342,3 @@ fn handle_despawn(
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);
}