Compare commits

...

199 commits

Author SHA1 Message Date
yuni f75da62ef9 polished logo, now symmetrical 2024-05-16 03:21:18 +02:00
yuni 3325ac2213 write changelog 2024-05-16 02:59:11 +02:00
yuni b074c88d9f new logo 2024-05-16 02:55:11 +02:00
yuni a99ce596d7 keep cruise control going when the game is not focused 2024-05-15 15:07:29 +02:00
yuni e801f3ac02 better cruise control dashboard icon 2024-05-15 14:52:19 +02:00
yuni 3eb7ec45e7 document cruise control key 2024-05-15 05:53:39 +02:00
yuni a4f5f0ea42 fix export path in dashboard_radioactivity.svg 2024-05-15 05:50:31 +02:00
yuni fa8c21203f simplify dashboard definition 2024-05-15 05:34:10 +02:00
yuni b012a2d51e longer HUD gauges 2024-05-15 05:28:53 +02:00
yuni e76043ca97 reset cruise control on death 2024-05-15 05:28:41 +02:00
yuni 7ccc09c7a0 radioactivity icon on dashboard when near jupiter (with no meaning yet) 2024-05-15 05:22:21 +02:00
yuni a07955d635 force-enable rotation stabiliser while holding space key 2024-05-15 05:02:38 +02:00
yuni f94c46cce2 implement cruise control 2024-05-15 05:00:59 +02:00
yuni e07ad4d236 add dashboard indicator light for rotation stabiliser 2024-05-15 04:43:33 +02:00
yuni d1a906b487 remove more redundancy (add "color scheme") 2024-05-14 23:00:55 +02:00
yuni ff610d4000 remove redundancy 2024-05-14 22:41:02 +02:00
yuni 68d218100c rename Key Bindings to Controls 2024-05-14 19:40:53 +02:00
yuni 367c5a82f3 README: mention game menu in Controls section 2024-05-14 19:33:42 +02:00
yuni 1abb5e91e6 typo 2024-05-14 19:25:25 +02:00
yuni f458ebe16b bump version to v0.9.0 2024-05-14 19:17:49 +02:00
yuni 6f565e9725 typos 2024-05-14 19:17:40 +02:00
yuni de327ec748 make the StarTrans Clippys individuals w.r.t. achievements 2024-05-14 19:17:26 +02:00
yuni 9f64bd65a7 rename "Restart Game" to "Take Off Helmet" 2024-05-14 19:12:58 +02:00
yuni 8f76a31cfa show version in menu 2024-05-14 19:08:18 +02:00
yuni a0dd6d45a0 wrap cursor in menu 2024-05-14 18:58:47 +02:00
yuni 8fb4ab4a18 yet another tweak of key binding lists 2024-05-14 18:50:07 +02:00
yuni ac416411a8 tweak wording of achievements 2024-05-14 18:45:35 +02:00
yuni 122a1e49ec make key bindings look more like the achievement list 2024-05-14 18:43:38 +02:00
yuni 49314bc6bb shift around key bindings 2024-05-14 18:43:25 +02:00
yuni ac6011540f don't list menu key; player obviously already pressed it 2024-05-14 18:39:30 +02:00
yuni fdfc479587 more concise key binding descriptions in menu 2024-05-14 18:38:52 +02:00
yuni 31705586cd show key bindings in menu instead of with F1 key 2024-05-14 18:32:23 +02:00
yuni 5a3f3bd96f fix version extraction 2024-05-14 18:25:07 +02:00
yuni 59fefad6d3 draw visual effects on top of menu/hud 2024-05-14 18:11:18 +02:00
yuni f1725fcab5 show achievements in golden color in the log 2024-05-14 07:27:43 +02:00
yuni ac38a248fa add achievement sound effect 2024-05-14 06:37:00 +02:00
yuni d20dc5c60d summarize achievements on death screen 2024-05-14 06:28:14 +02:00
yuni 22d7a8cc4c less clutter in achievement display 2024-05-14 05:41:26 +02:00
yuni fce2cfdce1 add "Find Earth" achievement 2024-05-14 05:33:35 +02:00
yuni c00a47fe7f padding 2024-05-14 05:22:10 +02:00
yuni cb90846b6f show achievements in the menu 2024-05-14 05:17:32 +02:00
yuni 8f796c92fa implement InJupitersShadow achievement 2024-05-14 04:35:45 +02:00
yuni 2a4759abb2 extend changelog 2024-05-14 01:37:49 +02:00
yuni aa7734947c implement achievements 2024-05-14 01:24:57 +02:00
yuni e7c533d728 format 2024-05-14 00:57:56 +02:00
yuni 60d3198e41 add Camera to menu 2024-05-13 23:51:18 +02:00
yuni 9c148a5a7c fix menu labels not updating 2024-05-13 23:50:53 +02:00
yuni 1b7f422791 change camera key to C 2024-05-13 23:07:47 +02:00
yuni b51e1683b6 typo 2024-05-13 22:09:01 +02:00
yuni 86734fdc72 fix menu not getting updated after toggling settings 2024-05-13 21:49:44 +02:00
yuni f57a1220d6 write changelog 2024-05-13 21:46:09 +02:00
yuni 47ca299abb document menu key 2024-05-13 21:43:11 +02:00
yuni c861df0ab4 disable map controls inside menu 2024-05-13 21:43:02 +02:00
yuni 6b6732bdec change schedule 2024-05-13 21:39:55 +02:00
yuni f476f351c9 change menu labels to reflect settings 2024-05-13 21:39:51 +02:00
yuni 83fe739e91 remove keys for restart and toggle shadows/music/sfx 2024-05-13 21:18:37 +02:00
yuni b9791fcdaa add key binding hints in menu 2024-05-13 21:12:23 +02:00
yuni b505312f6e add Toggle Shadows menu entry 2024-05-13 21:11:27 +02:00
yuni 556e98deec add Restart Game to menu 2024-05-13 20:53:08 +02:00
yuni 1c10a0c561 block closing death screen for the first 1 second 2024-05-13 20:42:34 +02:00
yuni ac9451ea4a send GameEvents instead of running redundand code 2024-05-13 20:26:00 +02:00
yuni eb681999f8 implement game menu 2024-05-13 20:21:56 +02:00
yuni 2cf10f2395 cleanup 2024-05-13 17:19:07 +02:00
yuni fcd9fe55d3 Revert "remove light orbs from inconspicuous asteroid - use flashlight now"
This reverts commit f9fcb885fa.
2024-05-13 16:43:18 +02:00
yuni f7412df73d replace special apostrophe character with ' 2024-05-13 06:12:26 +02:00
yuni 3814aa4155 implement special relativity (for speed cheats only, so far) 2024-05-13 06:10:34 +02:00
yuni dd2e596f5c fix lorentz factor 2024-05-13 05:55:34 +02:00
yuni cc67cf961a run input handlers only when alive 2024-05-13 05:01:39 +02:00
yuni 48476e317f reset audio on death/respawn 2024-05-13 04:41:17 +02:00
yuni ea25c7fed3 add audio::PauseAllSfxEvent 2024-05-13 04:33:03 +02:00
yuni 13fbe226e9 simplify audio system 2024-05-13 04:22:41 +02:00
yuni 3bbc57d29f show death poems on death 2024-05-13 03:09:03 +02:00
yuni 585fa7d3f4 move conf file name to common.rs 2024-05-13 01:42:14 +02:00
yuni 6ab89615b7 cleanup 2024-05-13 01:08:16 +02:00
yuni 48f78468c9 cleanup 2024-05-13 01:00:39 +02:00
yuni 2d42edb69c move some of var's constants into common 2024-05-13 00:59:43 +02:00
yuni 7f3770cf49 add bevy::math::{DVec3,DQuat} import to common 2024-05-13 00:55:49 +02:00
yuni c292c66900 add PI/PI32 to common.rs 2024-05-13 00:52:34 +02:00
yuni 3b7e3e94dc add EPSILON and EPSILON32 constants 2024-05-13 00:48:41 +02:00
yuni 78eeef6201 add common.rs 2024-05-13 00:45:16 +02:00
yuni 8515603f2d add help on getting stuck inside objects 2024-05-13 00:43:44 +02:00
yuni 2f0c84c691 remove top-left HUD entirely, apart of a very dim FPS display 2024-05-13 00:31:41 +02:00
yuni db083e0638 cleanup 2024-05-13 00:10:13 +02:00
yuni d02b820f3f fix dashboard icon placement/sizing 2024-05-13 00:08:40 +02:00
yuni 33876bac73 add game.rs 2024-05-12 23:57:21 +02:00
yuni 2f3190eb1a cleanup 2024-05-12 23:44:10 +02:00
yuni 94d732d9f0 simplify crate module imports 2024-05-12 23:42:56 +02:00
yuni d37b90be15 rename commands.rs to cmd.rs (in preparation for the next commit) 2024-05-12 23:37:13 +02:00
yuni 9095a93a82 disable map on death 2024-05-12 23:30:55 +02:00
yuni d03ca40cae move src/build to build 2024-05-12 23:16:37 +02:00
yuni c3ac62780a 2024-05-12 23:12:59 +02:00
yuni d49f331fa5 cleaner dependency specification 2024-05-12 23:11:56 +02:00
yuni 99c8c9c827 fork bevy to fix https://github.com/bevyengine/bevy/issues/13299 2024-05-12 22:54:21 +02:00
yuni 81e700ef66 add death type: GForce 2024-05-12 22:31:16 +02:00
yuni 69381db524 respawn the world on death only after death screen closes 2024-05-12 22:31:15 +02:00
yuni 182659eff0 add death screen 2024-05-12 22:31:14 +02:00
yuni 7aa6885509 add crate prelude 2024-05-12 22:31:13 +02:00
yuni 984830e77b roadmap 2024-05-12 18:48:17 +02:00
yuni 055d5e9b30 add ring to all gas giants 2024-05-10 13:46:12 +02:00
yuni 21fc2a55f5 add axialtilt command 2024-05-10 13:46:11 +02:00
yuni 3463fb3c4c changed venus' texture to an almost white one 2024-05-10 12:25:00 +02:00
yuni e6df2de8b2 high resolution textures for galileian moons 2024-05-10 12:24:59 +02:00
yuni 8664adcec4 enlarge venus to cover clouds, see https://bjj.mmedia.is/data/venus/venus.html
Quote from the linked page:

The map should be rendered by projecting it onto a sphere of about 6115 km radius or equivalent unit. Actually Venus' solid body radius is 6051 km but the height of the cloudtops is approximately 65 km so 6115 km seems to be an appropriate value.
2024-05-10 12:24:58 +02:00
yuni 83f43ee06c rename effects.rs to visual.rs 2024-05-10 12:24:57 +02:00
yuni 932a54b460 merge shading.rs into load.rs 2024-05-10 12:24:56 +02:00
yuni 3c52a10a6d rename skeleton.rs to load.rs 2024-05-10 12:24:55 +02:00
yuni 10acb83b83 cleanup 2024-05-10 12:24:53 +02:00
yuni eeffe3cc8c fix credits 2024-05-10 08:42:24 +02:00
yuni c2ee7ee3e1 better aiming in 3rd person mode 2024-05-08 18:40:01 +02:00
yuni 500f5c7953 bump version to v0.8.5 2024-05-08 16:38:30 +02:00
yuni 6f1cdbc931 update in-game key binding list 2024-05-08 16:38:10 +02:00
yuni 249b937f68 hide suit integrity gauge 2024-05-08 06:42:45 +02:00
yuni c2124180f6 change flashlight sound to "switch" 2024-05-08 06:42:35 +02:00
yuni 83966bf452 selectagon rotation -> camera's "up" vector. started flickering a bit 2024-05-08 05:55:53 +02:00
yuni d44d171a1c rename Racer to Cruiser 2024-05-08 05:50:35 +02:00
yuni 77bc975a6a turn off flashlight on death 2024-05-08 05:47:37 +02:00
yuni f78052f5ed better leaky suit dashboard symbol 2024-05-08 05:47:23 +02:00
yuni 626c685003 add dashboard icon for leaky suit 2024-05-08 05:17:47 +02:00
yuni 0ba64421d7 add racing vehicle gtlf file 2024-05-08 04:22:38 +02:00
yuni 0023bb9e24 WIP new, more balanced racing vehicle 2024-05-08 03:22:46 +02:00
yuni 9304c6fd7f tune up the horsepower of MeteorAceGT until it hurts 2024-05-08 02:50:59 +02:00
yuni 35a0d51dfb battery drain on flashlight 2024-05-08 02:35:36 +02:00
yuni 853b976a43 update color of selectagon to match new color scheme 2024-05-08 01:47:16 +02:00
yuni f79b5deb04 don't update HUD if it's turned off 2024-05-08 01:38:29 +02:00
yuni 1140e1eaca move g-force gauge to speedometer 2024-05-08 01:35:28 +02:00
yuni 4b9ed44dd4 less frequent updating of speedometer, dashboard, gauges 2024-05-08 01:20:22 +02:00
yuni 406bd79877 apply square-root scaling to oxygen meter 2024-05-08 01:18:09 +02:00
yuni c1755b87bf remove most written information from top-left part of HUD 2024-05-08 01:18:01 +02:00
yuni 0a4e3c3006 cleanup 2024-05-08 01:00:58 +02:00
yuni a8d824ad16 add symbols to gauges 2024-05-08 01:00:06 +02:00
yuni 99c32ce516 use actual data for gauges 2024-05-08 00:33:49 +02:00
yuni c38fcaa8dd clamp suit integrity at 0%-100% 2024-05-08 00:32:20 +02:00
yuni dc0d4be8ea add update_gauges system (dummy data for now) 2024-05-08 00:24:36 +02:00
yuni 32e5c2258c WIP: add gauges to the HUD on the bottom left 2024-05-08 00:16:30 +02:00
yuni db5fdd5a35 move speedometer to the right 2024-05-08 00:15:34 +02:00
yuni 310f977fb6 add gauge sprites 2024-05-07 23:33:33 +02:00
yuni 60309f84a0 update mapcam.center fast enough to avoid flicker on moving target 2024-05-07 22:05:24 +02:00
yuni 875e27ccf1 more responsive player input (hopefully?) 2024-05-07 21:44:23 +02:00
yuni 0f5c4d1d89 remove CENTER_WORLD_ON_PLAYER feature flag (always enabled now) 2024-05-07 21:33:15 +02:00
yuni ddf197c057 pack.sh: refuse to run if there are uncommited assets 2024-05-07 21:21:43 +02:00
yuni c1b071996a make Rudy more visible 2024-05-07 21:09:07 +02:00
yuni eaddfcd8cc disable shadows on selectagon 2024-05-07 21:05:51 +02:00
yuni 1dadfd5770 desaturate parts of dashboard icon, make it look more bloomy 2024-05-07 21:02:42 +02:00
yuni 7d31a95a7c show dashboard icon when flashlight is active 2024-05-07 20:54:24 +02:00
yuni b4441f6715 add car dashboard icon for high beams 2024-05-07 20:43:14 +02:00
yuni c363aa41b1 fix NoShadowCaster/Receiver with loaded gltf scenes 2024-05-07 19:38:19 +02:00
yuni dd3fa4f284 extend ROADMAP 2024-05-07 19:15:15 +02:00
yuni 6e92e37cdf turn off flashlight when entering vehicle 2024-05-07 19:13:49 +02:00
yuni 0c1393290e fix suit damage on collisions outside of vehicles 2024-05-07 19:07:32 +02:00
yuni 79673e5ec5 fix collision sounds 2024-05-07 19:07:24 +02:00
yuni f9fcb885fa remove light orbs from inconspicuous asteroid - use flashlight now 2024-05-07 18:08:05 +02:00
yuni 8627934993 swap flashlight/camera keys. F=flashlight, H=3rd person mode 2024-05-07 17:51:30 +02:00
yuni e08339ad5e add key to toggle flashlight, fix flashlight in 1st person mode 2024-05-07 17:11:18 +02:00
yuni f88d063beb add spotlight to player 2024-05-07 15:35:47 +02:00
yuni fa54f0e57a add comment 2024-05-07 03:27:31 +02:00
yuni 358938631f much better Jupiter texture 2024-05-04 03:00:56 +02:00
yuni f6155a3914 bump version to v0.8.4 2024-05-04 01:51:24 +02:00
yuni 395ebb8ed9 remove mobile-unfriendly ascii art in README.md 2024-05-03 20:56:24 +02:00
yuni 7cfd4377c7 s/Coffee/Soykaf/ 2024-05-02 19:12:10 +02:00
yuni a4ee4a60ef allow teleporting to other stars, though this kinda breaks the game
but seeing the night sky from so far away is truly magical
2024-05-02 01:43:16 +02:00
yuni d2970d0ffb fix star positions during large-scale map movement 2024-05-02 01:36:06 +02:00
yuni be2420a212 add a point to changelog 2024-05-01 23:16:15 +02:00
yuni f23234d170 this is the real v0.8.3 now, after a last-minute fix 2024-05-01 23:02:26 +02:00
yuni e750ca93ad fix audio not playing on startup 2024-05-01 23:01:41 +02:00
yuni aed356c380 more documentation for generate_starchart.py 2024-05-01 22:52:41 +02:00
yuni 9617358229 bump version to v0.8.3 2024-05-01 22:43:31 +02:00
yuni e8c0f7f6fa less AR ambient light 2024-05-01 22:26:02 +02:00
yuni b695ba7701 fix floating point errors in map mode on far away targets 2024-05-01 22:18:15 +02:00
yuni 591b4a4f46 move actor::position_to_transform to camera module 2024-05-01 21:50:59 +02:00
yuni e361b1f493 cleanup 2024-05-01 21:50:16 +02:00
yuni cd515d0e87 give the planets an accurate axial tilt 2024-05-01 21:42:17 +02:00
yuni fc017dcd43 change angle specification for rotation* commands to degrees 2024-05-01 21:25:33 +02:00
yuni 632d1b02c9 set actual scale of planets, though now they glitch on close zoom-in
this is due to floating point precision error, need to move render
origin to the center of the camera
2024-05-01 21:13:49 +02:00
yuni 13e2eed144 delete unused sprites 2024-05-01 21:03:01 +02:00
yuni 017d399627 write changelog 2024-05-01 21:02:44 +02:00
yuni 9ba8b42b2c remove Pluto's status as a planet 2024-05-01 21:01:32 +02:00
yuni 70cf0920e9 add textures for remaining 7 planets 2024-05-01 21:00:46 +02:00
yuni 756fe91f23 WIP adding new textures 2024-05-01 20:51:08 +02:00
yuni bca15ad5ff move WIP cultist asteroid further away 2024-05-01 19:11:41 +02:00
yuni fea8b656e0 consolidate planet/moon definitions into single block 2024-05-01 19:07:14 +02:00
yuni 54624d6aac add planet names 2024-05-01 18:59:03 +02:00
yuni d02a6fac90 enable HUD and third person mode by default 2024-05-01 18:50:46 +02:00
yuni 2bfb1efcfd even smaller planet marker 2024-05-01 18:10:16 +02:00
yuni 2672793df4 give planets a different, smaller marker 2024-05-01 18:07:51 +02:00
yuni 2cb9f10f4b mark solar system planets (and pluto) the same way as moons 2024-05-01 17:55:05 +02:00
yuni 90e4f36fff work on roadmap 2024-05-01 17:43:34 +02:00
yuni fd90c1b2f0 add special point-of-interest markers for moons 2024-05-01 17:43:16 +02:00
yuni aa95f894ad move Metis Prime station in dynamic orbit behind Metis 2024-05-01 17:21:05 +02:00
yuni 6741c3f189 disable point lights on bus stations for performance reasons 2024-05-01 17:20:37 +02:00
yuni c9b6e0b0e1 thinner metis notch 2024-05-01 17:17:56 +02:00
yuni 4c369d7a32 add handcrafted collider for the whale 2024-05-01 17:17:43 +02:00
yuni 753aa93127 adjust reaction wheels of vehicles 2024-05-01 05:21:27 +02:00
yuni cfadb5e2bf start dev mode only when run with cargo. remove "mute_music" feature 2024-05-01 05:01:11 +02:00
yuni b00c583d4d faster rotation stabilizer 2024-05-01 04:02:04 +02:00
yuni 66920f44e9 fewer asteroids (for performance reasons, due to shadows) 2024-05-01 03:52:08 +02:00
yuni 95645e4ab1 add hollow asteroid under construction 2024-05-01 03:36:54 +02:00
yuni 35b56c2295 tweak player reaction wheel speed 2024-05-01 03:19:08 +02:00
yuni a9fc27ac27 better g-force calculation 2024-05-01 00:58:58 +02:00
yuni 678b0c39e1 change HUD colors to neon red/pink 2024-05-01 00:51:50 +02:00
92 changed files with 4410 additions and 1648 deletions

View file

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

178
Cargo.lock generated
View file

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

View file

@ -10,7 +10,7 @@
[package] [package]
name = "outfly" name = "outfly"
version = "0.8.2" version = "0.9.0"
edition = "2021" edition = "2021"
homepage = "https://codeberg.org/hut/outfly" homepage = "https://codeberg.org/hut/outfly"
repository = "https://codeberg.org/hut/outfly" repository = "https://codeberg.org/hut/outfly"
@ -23,15 +23,6 @@ rust-version = "1.76.0"
# For parsing the game definition file, src/data/defs.txt # For parsing the game definition file, src/data/defs.txt
regex = "1" regex = "1"
# The bevy game engine, the basis for this game
bevy = { version = "0.13.2", default-features = false, features = ["animation", "bevy_asset", "bevy_audio", "bevy_scene", "bevy_winit", "bevy_core_pipeline", "bevy_pbr", "bevy_gltf", "bevy_render", "bevy_text", "bevy_ui", "jpeg", "multi-threaded", "png", "tonemapping_luts", "vorbis"]}
# For physics and collision handling
bevy_xpbd_3d = { version = "0.4.2", default-features = false, features = ["3d", "f64", "parry-f64", "parallel", "async-collider"] }
# For embedding assets into the binary, creating a self-sufficient executable
bevy_embedded_assets = { version = "0.10.2", optional = true }
# For seeded pseudo-random procedural generation of asteroids # For seeded pseudo-random procedural generation of asteroids
fastrand = "2.0" fastrand = "2.0"
@ -42,14 +33,42 @@ serde_yaml = "0.9"
# For reading/writing the player's configuration file. # For reading/writing the player's configuration file.
toml_edit = { version = "0.22", features = ["serde"] } toml_edit = { version = "0.22", features = ["serde"] }
[dependencies.bevy]
# The bevy game engine, the basis for this game
# We temporarily use a fork with a custom bug fix, see https://codeberg.org/outfly/bevy
version = "0.13.2"
git = "https://codeberg.org/outfly/bevy.git"
rev = "2076f102be15a88a5026e128851a8ee1ae9d8960"
default-features = false
features = ["animation", "bevy_asset", "bevy_audio", "bevy_scene", "bevy_winit", "bevy_core_pipeline", "bevy_pbr", "bevy_gltf", "bevy_render", "bevy_text", "bevy_ui", "jpeg", "multi-threaded", "png", "tonemapping_luts", "vorbis"]
[dependencies.bevy_embedded_assets]
# For embedding assets into the binary, creating a self-sufficient executable
# We temporarily use a fork with a custom bug fix, see https://codeberg.org/outfly/bevy
version = "0.10.2"
git = "https://codeberg.org/outfly/bevy_embedded_assets.git"
rev = "bb925e7e5373c742c01e6e7aff04e92fdc07c095"
optional = true
[dependencies.bevy_xpbd_3d]
# For physics and collision handling
# We temporarily use a fork with a custom bug fix, see https://codeberg.org/outfly/bevy
version = "0.4.2"
git = "https://codeberg.org/outfly/bevy_xpbd.git"
rev = "99bca3f6a25b8c4e6ec6509e9e9b0e7bed565912"
default-features = false
features = ["3d", "f64", "parry-f64", "parallel", "async-collider"]
[build-dependencies] [build-dependencies]
# NOTE: even though we use embed-resource for windows only, we can't move it into
# a [target[...]build-dependencies] block because in case of cross-compiling, the
# build.rs will be compiled for a different, non-windows target than the main executable.
embed-resource = "1.6.3" # embedding of .exe metadata embed-resource = "1.6.3" # embedding of .exe metadata
[features] [features]
default = ["x11", "embed_assets"] default = ["x11", "embed_assets"]
mute_music = []
dev_mode = [] dev_mode = []
dev = ["dev_mode", "mute_music", "bevy/dynamic_linking", "bevy/file_watcher"] dev = ["dev_mode", "bevy/dynamic_linking", "bevy/file_watcher"]
release_linux = ["x11", "wayland", "embed_assets"] release_linux = ["x11", "wayland", "embed_assets"]
release_windows = ["embed_assets"] release_windows = ["embed_assets"]
wasm = ["bevy/webgl2"] wasm = ["bevy/webgl2"]

View file

@ -3,7 +3,26 @@
- Source code: GPL Version 3.0 - Source code: GPL Version 3.0
- https://codeberg.org/hut/outfly - https://codeberg.org/hut/outfly
- 3D models: Original art, placed under the Creative Commons CC0 License - 3D models: Original art, placed under the Creative Commons CC0 License
- Photographs of celestial bodies: By NASA, public domain - 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/)
- Icon: Creative Commons CC0 License - Icon: Creative Commons CC0 License
- Original sound files: - Original sound files:
- wakeup.ogg: Creative Commons CC0 License - wakeup.ogg: Creative Commons CC0 License
@ -21,6 +40,7 @@
- https://pixabay.com/sound-effects/electric-fan-motor-blades-removed-13169 - https://pixabay.com/sound-effects/electric-fan-motor-blades-removed-13169
- https://pixabay.com/sound-effects/whoosh-blow-flutter-shortwav-14678/ - https://pixabay.com/sound-effects/whoosh-blow-flutter-shortwav-14678/
- https://pixabay.com/sound-effects/dslr-camera-sounds-26117/ - https://pixabay.com/sound-effects/dslr-camera-sounds-26117/
- https://pixabay.com/sound-effects/beep-6-96243
- Music: [Cinematic Cello](https://pixabay.com/music/build-up-scenes-cinematic-cello-115667) by [Aleksey Chistilin](https://pixabay.com/users/lexin_music-28841948/), [Pixabay Content License](https://pixabay.com/service/license-summary) - Music: [Cinematic Cello](https://pixabay.com/music/build-up-scenes-cinematic-cello-115667) by [Aleksey Chistilin](https://pixabay.com/users/lexin_music-28841948/), [Pixabay Content License](https://pixabay.com/service/license-summary)
- Star chart based on the [HYG Stellar database](https://github.com/astronexus/HYG-Database) - Star chart based on the [HYG Stellar database](https://github.com/astronexus/HYG-Database)
- Font Yupiter-Regular.ttf is placed under the SIL OPEN FONT LICENSE Version 1.1 and is based on: - Font Yupiter-Regular.ttf is placed under the SIL OPEN FONT LICENSE Version 1.1 and is based on:

View file

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

View file

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

BIN
assets/models/crate.glb Normal file

Binary file not shown.

BIN
assets/models/cruiser.glb Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

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

BIN
assets/sounds/achieve.ogg Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 B

BIN
assets/sprites/gauge_o2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 430 KiB

BIN
assets/textures/earth.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 770 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 360 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 928 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 403 KiB

After

Width:  |  Height:  |  Size: 3.3 MiB

BIN
assets/textures/mars.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 MiB

BIN
assets/textures/mercury.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

BIN
assets/textures/neptune.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
assets/textures/saturn.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
assets/textures/uranus.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
assets/textures/venus.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View file

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

View file

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

View file

@ -9,7 +9,11 @@
# + + + ███ # + + + ███
# + ▀████████████████████████████████████████████████████▀ # + ▀████████████████████████████████████████████████████▀
# #
# This script requires the following file in the extra/ directory: # 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:
# https://github.com/astronexus/HYG-Database/blob/cbd21013d2bb89732b893be357a6f41836dbe614/hyg/CURRENT/hygdata_v41.csv # https://github.com/astronexus/HYG-Database/blob/cbd21013d2bb89732b893be357a6f41836dbe614/hyg/CURRENT/hygdata_v41.csv
import csv import csv

View file

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

BIN
build/linux/outfly.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View file

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

BIN
build/windows/outfly.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -10,19 +10,14 @@
// //
// This module manages the internal states of individual characters, // This module manages the internal states of individual characters,
// such as their resources, the damage they receive, and interactions // such as their resources, the damage they receive, and interactions
// between characters and with vehicles. It also handles cheats. // between characters and with vehicles.
// //
// This module should never handle any visual aspects directly. // This module should never handle any visual aspects directly.
use bevy::prelude::*; use bevy::prelude::*;
use bevy_xpbd_3d::prelude::*; use bevy_xpbd_3d::prelude::*;
use bevy_xpbd_3d::plugins::sync; use crate::prelude::*;
use bevy::scene::SceneInstance;
use bevy::math::DVec3;
use crate::{actor, audio, camera, chat, commands, effects, hud, nature, var, world};
use std::collections::HashMap;
const CENTER_WORLD_ON_PLAYER: bool = true;
pub const ENGINE_SPEED_FACTOR: f32 = 30.0; pub const ENGINE_SPEED_FACTOR: f32 = 30.0;
const MAX_TRANSMISSION_DISTANCE: f32 = 100.0; const MAX_TRANSMISSION_DISTANCE: f32 = 100.0;
const MAX_INTERACT_DISTANCE: f32 = 50.0; const MAX_INTERACT_DISTANCE: f32 = 50.0;
@ -30,40 +25,24 @@ const MAX_INTERACT_DISTANCE: f32 = 50.0;
pub struct ActorPlugin; pub struct ActorPlugin;
impl Plugin for ActorPlugin { impl Plugin for ActorPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(PreUpdate, (
handle_player_death,
));
app.add_systems(FixedUpdate, ( app.add_systems(FixedUpdate, (
update_physics_lifeforms, update_physics_lifeforms,
update_power,
handle_wants_maxrotation, handle_wants_maxrotation,
handle_wants_maxvelocity, handle_wants_maxvelocity,
handle_gforce,
)); ));
app.add_systems(PostUpdate, handle_gforce
.after(PhysicsSet::Sync)
.after(sync::position_to_transform));
app.add_systems(Update, ( app.add_systems(Update, (
handle_input, handle_input.run_if(in_control),
handle_collisions, handle_collisions,
handle_damage, handle_damage,
handle_cheats,
)); ));
app.add_systems(PostUpdate, ( app.add_systems(PostUpdate, (
handle_vehicle_enter_exit, handle_vehicle_enter_exit,
update_id2pos,
)); ));
app.add_event::<VehicleEnterExitEvent>(); app.add_event::<VehicleEnterExitEvent>();
app.add_event::<PlayerDiesEvent>();
app.insert_resource(Id2Pos(HashMap::new()));
if CENTER_WORLD_ON_PLAYER {
// Disable bevy_xpbd's position->transform sync function
app.insert_resource(sync::SyncConfig {
position_to_transform: true,
transform_to_position: false,
});
// Add own position->transform sync function
app.add_systems(PostUpdate, position_to_transform
.after(sync::position_to_transform)
.in_set(sync::SyncSet::PositionToTransform));
}
} }
} }
@ -72,19 +51,20 @@ pub enum DamageType {
Unknown, Unknown,
Mental, Mental,
Trauma, Trauma,
GForce,
Asphyxiation, Asphyxiation,
Depressurization,
//Poison, //Poison,
//Radiation, //Radiation,
//Freeze, //Freeze,
//Burn, //Burn,
} }
#[derive(Event)] pub struct PlayerDiesEvent(pub DamageType);
#[derive(Event)] #[derive(Event)]
pub struct VehicleEnterExitEvent { pub struct VehicleEnterExitEvent {
vehicle: Entity, vehicle: Entity,
driver: Entity, driver: Entity,
name: Option<String>,
is_entering: bool, is_entering: bool,
is_player: bool is_player: bool
} }
@ -142,15 +122,16 @@ impl Default for ExperiencesGForce { fn default() -> Self { Self {
}}} }}}
#[derive(Component)] pub struct Player; // Attached to the suit of the player #[derive(Component)] pub struct Player; // Attached to the suit of the player
#[derive(Component)] pub struct PlayerCollider; // Attached to the collider of the suit of the player
#[derive(Component)] pub struct PlayerDrivesThis; // Attached to the entered vehicle #[derive(Component)] pub struct PlayerDrivesThis; // Attached to the entered vehicle
#[derive(Component)] pub struct PlayerCamera; // Attached to the actor to use as point of view #[derive(Component)] pub struct PlayerCamera; // Attached to the actor to use as point of view
#[derive(Component)] pub struct JustNowEnteredVehicle; #[derive(Component)] pub struct JustNowEnteredVehicle;
#[derive(Component)] pub struct ActorEnteringVehicle; #[derive(Component)] pub struct ActorEnteringVehicle;
#[derive(Component)] pub struct ActorVehicleBeingEntered; #[derive(Component)] pub struct ActorVehicleBeingEntered;
#[derive(Component)] pub struct PlayersFlashLight;
#[derive(Component)] pub struct WantsMaxRotation(pub f64); #[derive(Component)] pub struct WantsMaxRotation(pub f64);
#[derive(Component)] pub struct WantsMaxVelocity(pub f64); #[derive(Component)] pub struct WantsMaxVelocity(pub f64);
#[derive(Component)] pub struct Identifier(pub String); #[derive(Component)] pub struct Identifier(pub String);
#[derive(Resource)] pub struct Id2Pos(pub HashMap<String, DVec3>);
#[derive(Component)] #[derive(Component)]
pub struct LifeForm { pub struct LifeForm {
@ -208,21 +189,58 @@ impl Default for Engine {
#[derive(Component)] #[derive(Component)]
pub struct Suit { pub struct Suit {
pub oxygen: f32, pub oxygen: f32,
pub power: f32,
pub oxygen_max: f32, pub oxygen_max: f32,
pub power_max: f32,
pub integrity: f32, // [0.0 - 1.0] pub integrity: f32, // [0.0 - 1.0]
} }
impl Default for Suit { fn default() -> Self { SUIT_SIMPLE } } impl Default for Suit { fn default() -> Self { SUIT_SIMPLE } }
const SUIT_SIMPLE: Suit = Suit { const SUIT_SIMPLE: Suit = Suit {
power: 1e5,
power_max: 1e5,
oxygen: nature::OXY_D, oxygen: nature::OXY_D,
oxygen_max: nature::OXY_D, oxygen_max: nature::OXY_D,
integrity: 1.0, integrity: 1.0,
}; };
#[derive(Component)]
pub struct Battery {
pub power: f32, // Watt-seconds
pub capacity: f32, // Watt-seconds
pub reactor: f32, // Watt (production)
}
impl Default for Battery {
fn default() -> Self {
Self {
power: 10e3 * 3600.0,
capacity: 10e3 * 3600.0, // 10kWh
reactor: 2000e3, // 2MW
}
}
}
pub fn update_power(
time: Res<Time>,
mut settings: ResMut<Settings>,
mut q_battery: Query<(&mut Battery, Option<&Player>)>,
mut q_flashlight: Query<&mut Visibility, With<PlayersFlashLight>>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
) {
let d = time.delta_seconds();
for (mut battery, player) in &mut q_battery {
if player.is_some() && settings.flashlight_active {
battery.power -= 4000000.0 * d;
if battery.power <= 0.0 {
settings.flashlight_active = false;
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Switch));
for mut flashlight_vis in &mut q_flashlight {
*flashlight_vis = Visibility::Hidden;
}
}
}
battery.power = (battery.power + battery.reactor * d)
.clamp(0.0, battery.capacity);
}
}
pub fn update_physics_lifeforms( pub fn update_physics_lifeforms(
time: Res<Time>, time: Res<Time>,
mut query: Query<(&mut LifeForm, &mut HitPoints, &mut Suit, &LinearVelocity)>, mut query: Query<(&mut LifeForm, &mut HitPoints, &mut Suit, &LinearVelocity)>,
@ -271,14 +289,15 @@ pub fn update_physics_lifeforms(
pub fn handle_input( pub fn handle_input(
mut commands: Commands, mut commands: Commands,
keyboard_input: Res<ButtonInput<KeyCode>>, keyboard_input: Res<ButtonInput<KeyCode>>,
mut settings: ResMut<var::Settings>, mut settings: ResMut<Settings>,
q_talker: Query<(&chat::Talker, &Transform), (Without<actor::Player>, Without<Camera>)>, q_talker: Query<(&chat::Talker, &Transform), (Without<actor::Player>, Without<Camera>)>,
player: Query<Entity, With<actor::Player>>, player: Query<Entity, With<actor::Player>>,
q_camera: Query<&Transform, With<Camera>>, q_camera: Query<&Transform, With<Camera>>,
q_vehicles: Query<(Entity, &Transform), (With<actor::Vehicle>, Without<actor::Player>, Without<Camera>)>, mut q_flashlight: Query<&mut Visibility, With<PlayersFlashLight>>,
q_vehicles: Query<(Entity, &Actor, &Transform), (With<actor::Vehicle>, Without<actor::Player>, Without<Camera>)>,
mut ew_conv: EventWriter<chat::StartConversationEvent>, mut ew_conv: EventWriter<chat::StartConversationEvent>,
mut ew_vehicle: EventWriter<VehicleEnterExitEvent>, mut ew_vehicle: EventWriter<VehicleEnterExitEvent>,
mut ew_playerdies: EventWriter<PlayerDiesEvent>, mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
q_player_drives: Query<Entity, With<PlayerDrivesThis>>, q_player_drives: Query<Entity, With<PlayerDrivesThis>>,
) { ) {
if q_camera.is_empty() || player.is_empty() { if q_camera.is_empty() || player.is_empty() {
@ -301,16 +320,20 @@ pub fn handle_input(
} }
// Entering Vehicles // Entering Vehicles
if q_player_drives.is_empty() { if q_player_drives.is_empty() {
let objects: Vec<(Entity, &Transform)> = q_vehicles let objects: Vec<((Entity, &Actor), &Transform)> = q_vehicles
.iter() .iter()
.map(|(entity, actor, transform)| ((entity, actor), transform))
.collect(); .collect();
if let (Some(entity), dist) = camera::find_closest_target::<Entity>(objects, camtrans) { if let (Some((entity, actor)), dist) =
camera::find_closest_target::<(Entity, &Actor)>(objects, camtrans)
{
if dist <= MAX_INTERACT_DISTANCE { if dist <= MAX_INTERACT_DISTANCE {
commands.entity(entity).insert(ActorVehicleBeingEntered); commands.entity(entity).insert(ActorVehicleBeingEntered);
commands.entity(player_entity).insert(ActorEnteringVehicle); commands.entity(player_entity).insert(ActorEnteringVehicle);
ew_vehicle.send(VehicleEnterExitEvent{ ew_vehicle.send(VehicleEnterExitEvent{
vehicle: entity, vehicle: entity,
driver: player_entity, driver: player_entity,
name: actor.name.clone(),
is_entering: q_player_drives.is_empty(), is_entering: q_player_drives.is_empty(),
is_player: true, is_player: true,
}); });
@ -326,21 +349,37 @@ pub fn handle_input(
ew_vehicle.send(VehicleEnterExitEvent{ ew_vehicle.send(VehicleEnterExitEvent{
vehicle: vehicle_entity, vehicle: vehicle_entity,
driver: player_entity, driver: player_entity,
name: None,
is_entering: false, is_entering: false,
is_player: true, is_player: true,
}); });
break; break;
} }
} }
else if keyboard_input.just_pressed(settings.key_restart) { else if keyboard_input.just_pressed(settings.key_flashlight) {
settings.god_mode = false; for mut flashlight_vis in &mut q_flashlight {
ew_playerdies.send(PlayerDiesEvent(DamageType::Mental)); 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;
} }
} }
pub fn handle_vehicle_enter_exit( pub fn handle_vehicle_enter_exit(
mut commands: Commands, mut commands: Commands,
mut settings: ResMut<Settings>,
mut er_vehicle: EventReader<VehicleEnterExitEvent>, mut er_vehicle: EventReader<VehicleEnterExitEvent>,
mut ew_achievement: EventWriter<game::AchievementEvent>,
mut q_playerflashlight: Query<&mut Visibility, (With<PlayersFlashLight>, Without<ActorVehicleBeingEntered>, Without<ActorEnteringVehicle>)>,
mut q_drivers: Query<(Entity, &mut Visibility, Option<&Collider>), (Without<ActorVehicleBeingEntered>, With<ActorEnteringVehicle>)>, mut q_drivers: Query<(Entity, &mut Visibility, Option<&Collider>), (Without<ActorVehicleBeingEntered>, With<ActorEnteringVehicle>)>,
mut q_vehicles: Query<(Entity, &mut Vehicle, &mut Visibility), (With<ActorVehicleBeingEntered>, Without<ActorEnteringVehicle>)>, mut q_vehicles: Query<(Entity, &mut Vehicle, &mut Visibility), (With<ActorVehicleBeingEntered>, Without<ActorEnteringVehicle>)>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>, mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
@ -366,6 +405,14 @@ pub fn handle_vehicle_enter_exit(
commands.entity(driver).insert(JustNowEnteredVehicle); commands.entity(driver).insert(JustNowEnteredVehicle);
commands.entity(vehicle).insert(PlayerCamera); commands.entity(vehicle).insert(PlayerCamera);
commands.entity(vehicle).insert(PlayerDrivesThis); commands.entity(vehicle).insert(PlayerDrivesThis);
if let Ok(mut flashlight_vis) = q_playerflashlight.get_single_mut() {
*flashlight_vis = Visibility::Hidden;
settings.flashlight_active = false;
}
if let Some(vehicle_name) = &event.name {
ew_achievement.send(game::AchievementEvent
::RideVehicle(vehicle_name.clone()));
}
} }
else { else {
// Exiting Vehicle // Exiting Vehicle
@ -389,18 +436,15 @@ pub fn handle_vehicle_enter_exit(
fn handle_collisions( fn handle_collisions(
mut collision_event_reader: EventReader<CollisionStarted>, mut collision_event_reader: EventReader<CollisionStarted>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>, mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
q_player: Query<(Entity, Option<&Player>), With<PlayerCamera>>, q_player: Query<Entity, With<PlayerCollider>>,
mut q_player_lifeform: Query<(&mut LifeForm, &mut Suit), With<Player>>, mut q_player_lifeform: Query<(&mut LifeForm, &mut Suit), With<Player>>,
) { ) {
if let (Ok((player, player_maybe)), Ok((mut lifeform, mut suit))) = (q_player.get_single(), q_player_lifeform.get_single_mut()) { if let (Ok(player), Ok((mut lifeform, mut suit))) = (q_player.get_single(), q_player_lifeform.get_single_mut()) {
for CollisionStarted(entity1, entity2) in collision_event_reader.read() { for CollisionStarted(entity1, entity2) in collision_event_reader.read() {
if *entity1 == player || *entity2 == player { if *entity1 == player || *entity2 == player {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Crash)); ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Crash));
lifeform.adrenaline_jolt += 0.1; lifeform.adrenaline_jolt += 0.1;
suit.integrity = (suit.integrity - 0.03).max(0.0);
if player_maybe.is_some() {
suit.integrity -= 0.03;
}
} }
} }
} }
@ -410,11 +454,10 @@ fn handle_wants_maxrotation(
//time: Res<Time>, //time: Res<Time>,
mut query: Query<(&mut AngularVelocity, &Engine, &WantsMaxRotation)>, mut query: Query<(&mut AngularVelocity, &Engine, &WantsMaxRotation)>,
) { ) {
let epsilon = 0.0001;
//let d = time.delta_seconds(); //let d = time.delta_seconds();
for (mut v_ang, engine, maxrot) in &mut query { for (mut v_ang, engine, maxrot) in &mut query {
let total = v_ang.0.length(); let total = v_ang.0.length();
if total <= maxrot.0 + epsilon { if total <= maxrot.0 + EPSILON {
if total > maxrot.0 { if total > maxrot.0 {
v_ang.0 = DVec3::splat(0.0); v_ang.0 = DVec3::splat(0.0);
} }
@ -431,10 +474,9 @@ fn handle_wants_maxvelocity(
mut query: Query<(&mut LinearVelocity, &Engine, &WantsMaxVelocity)>, mut query: Query<(&mut LinearVelocity, &Engine, &WantsMaxVelocity)>,
) { ) {
let dt = time.delta_seconds(); let dt = time.delta_seconds();
let epsilon = 0.0001;
for (mut v, engine, maxv) in &mut query { for (mut v, engine, maxv) in &mut query {
let total = v.0.length(); let total = v.0.length();
if total <= maxv.0 + epsilon { if total <= maxv.0 + EPSILON {
if total > maxv.0 { if total > maxv.0 {
v.0 = DVec3::splat(0.0); v.0 = DVec3::splat(0.0);
} }
@ -446,73 +488,16 @@ fn handle_wants_maxvelocity(
let avg_thrust = (engine.thrust_forward + engine.thrust_back + engine.thrust_sideways) / 3.0; let avg_thrust = (engine.thrust_forward + engine.thrust_back + engine.thrust_sideways) / 3.0;
let acceleration = (avg_thrust * dt) as f64 * -v.0; let acceleration = (avg_thrust * dt) as f64 * -v.0;
v.0 += acceleration; v.0 += acceleration;
if v.0.length() + epsilon < acceleration.length() { if v.0.length() + EPSILON < acceleration.length() {
v.0 = DVec3::splat(0.0); v.0 = DVec3::splat(0.0);
} }
} }
} }
fn handle_player_death(
mut cmd: Commands,
mut er_playerdies: EventReader<PlayerDiesEvent>,
q_scenes: Query<(Entity, &SceneInstance), With<world::DespawnOnPlayerDeath>>,
q_noscenes: Query<Entity, (With<world::DespawnOnPlayerDeath>, Without<SceneInstance>)>,
ew_spawn: EventWriter<commands::SpawnEvent>,
mut scene_spawner: ResMut<SceneSpawner>,
mut active_asteroids: ResMut<world::ActiveAsteroids>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_effect: EventWriter<effects::SpawnEffectEvent>,
mut log: ResMut<hud::Log>,
mut settings: ResMut<var::Settings>,
) {
for death in er_playerdies.read() {
if settings.god_mode {
return;
}
settings.reset_player_settings();
active_asteroids.0.clear();
for entity in &q_noscenes {
cmd.entity(entity).despawn();
}
for (entity, sceneinstance) in &q_scenes {
cmd.entity(entity).despawn();
scene_spawner.despawn_instance(**sceneinstance);
}
log.clear();
//cmd.run_system(commands::load_defs); // why is it so complicated to get SystemId?
match death.0 {
DamageType::Mental => {
ew_effect.send(effects::SpawnEffectEvent {
class: effects::Effects::FadeIn(Color::BLACK),
duration: 4.0,
});
}
DamageType::Asphyxiation => {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::WakeUp));
ew_effect.send(effects::SpawnEffectEvent {
class: effects::Effects::FadeIn(Color::BLACK),
duration: 1.0,
});
}
DamageType::Trauma | _ => {
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::WakeUp));
ew_effect.send(effects::SpawnEffectEvent {
class: effects::Effects::FadeIn(Color::MAROON),
duration: 1.0,
});
}
}
commands::load_defs(ew_spawn);
return;
}
}
fn handle_damage( fn handle_damage(
mut ew_playerdies: EventWriter<PlayerDiesEvent>, mut ew_playerdies: EventWriter<game::PlayerDiesEvent>,
mut q_hp: Query<(&mut HitPoints, Option<&Player>), Changed<HitPoints>>, mut q_hp: Query<(&mut HitPoints, Option<&Player>), Changed<HitPoints>>,
settings: Res<var::Settings>, settings: Res<Settings>,
) { ) {
for (mut hp, player_maybe) in &mut q_hp { for (mut hp, player_maybe) in &mut q_hp {
if player_maybe.is_some() { if player_maybe.is_some() {
@ -520,7 +505,7 @@ fn handle_damage(
hp.current -= hp.damage; hp.current -= hp.damage;
} }
if hp.current <= 0.0 { if hp.current <= 0.0 {
ew_playerdies.send(PlayerDiesEvent(hp.damagetype)); ew_playerdies.send(game::PlayerDiesEvent(hp.damagetype));
} }
} }
else { else {
@ -545,7 +530,7 @@ fn handle_gforce(
} }
if gforce.gforce > gforce.damage_threshold { if gforce.gforce > gforce.damage_threshold {
hp.damage += (gforce.gforce - gforce.damage_threshold).powf(2.0) / 3000.0; hp.damage += (gforce.gforce - gforce.damage_threshold).powf(2.0) / 3000.0;
hp.damagetype = DamageType::Trauma; hp.damagetype = DamageType::GForce;
} }
if gforce.visual_effect > 0.0001 { if gforce.visual_effect > 0.0001 {
@ -559,119 +544,3 @@ fn handle_gforce(
} }
} }
} }
fn update_id2pos(
mut id2pos: ResMut<Id2Pos>,
q_id: Query<(&Position, &Identifier)>,
) {
id2pos.0.clear();
for (pos, id) in &q_id {
id2pos.0.insert(id.0.clone(), pos.0);
}
}
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,34 +12,22 @@
use bevy::prelude::*; use bevy::prelude::*;
use bevy::audio::{PlaybackMode, Volume}; use bevy::audio::{PlaybackMode, Volume};
use crate::{camera, var}; use crate::prelude::*;
use std::collections::HashMap;
const ASSET_CLICK: &str = "sounds/click-button-140881-crop.ogg";
const ASSET_SWITCH: &str = "sounds/typosonic-typing-192811-crop.ogg";
const ASSET_WOOSH: &str = "sounds/woosh.ogg";
const ASSET_ZOOM: &str = "sounds/zoom.ogg";
const ASSET_INCOMING_MESSAGE: &str = "sounds/connect.ogg";
const ASSET_PING: &str = "sounds/connect.ogg";
const ASSET_CONNECT: &str = "sounds/connect.ogg";
const ASSET_BGM: &str = "music/Aleksey Chistilin - Cinematic Cello.ogg";
const ASSET_THRUSTER: &str = "sounds/thruster.ogg";
const ASSET_ROCKET: &str = "sounds/rocket.ogg";
const ASSET_ION: &str = "sounds/ion.ogg";
const ASSET_WAKEUP: &str = "sounds/wakeup.ogg";
const ASSET_BIKESTART: &str = "sounds/bikestart.ogg";
const ASSET_CRASH: &str = "sounds/crash.ogg";
const ASSET_ELECTRICMOTOR: &str = "sounds/electricmotor.ogg";
pub struct AudioPlugin; pub struct AudioPlugin;
impl Plugin for AudioPlugin { impl Plugin for AudioPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Startup, setup); app.add_systems(Startup, setup);
app.add_systems(Update, toggle_bgm); app.add_systems(Update, respawn_sinks.run_if(on_event::<RespawnSinksEvent>()));
app.add_systems(Update, play_zoom_sfx); app.add_systems(Update, play_zoom_sfx);
app.add_systems(Update, pause_all.run_if(on_event::<PauseAllSfxEvent>()));
app.add_systems(PostUpdate, play_sfx); app.add_systems(PostUpdate, play_sfx);
app.add_systems(PostUpdate, update_music); app.add_systems(PostUpdate, update_music);
app.add_event::<PlaySfxEvent>(); app.add_event::<PlaySfxEvent>();
app.add_event::<PauseAllSfxEvent>();
app.add_event::<ToggleMusicEvent>(); app.add_event::<ToggleMusicEvent>();
app.add_event::<RespawnSinksEvent>();
app.insert_resource(ZoomTimer( app.insert_resource(ZoomTimer(
Timer::from_seconds(0.09, TimerMode::Repeating))); Timer::from_seconds(0.09, TimerMode::Repeating)));
} }
@ -47,177 +35,48 @@ impl Plugin for AudioPlugin {
#[derive(Resource)] pub struct ZoomTimer(Timer); #[derive(Resource)] pub struct ZoomTimer(Timer);
const PATHS: &[(SfxType, Sfx, &str)] = &[
(SfxType::BGM, Sfx::BGM, "music/Aleksey Chistilin - Cinematic Cello.ogg"),
(SfxType::LoopSfx, Sfx::ElectricMotor, "sounds/electricmotor.ogg"),
(SfxType::LoopSfx, Sfx::Ion, "sounds/ion.ogg"),
(SfxType::LoopSfx, Sfx::Rocket, "sounds/rocket.ogg"),
(SfxType::LoopSfx, Sfx::Thruster, "sounds/thruster.ogg"),
(SfxType::OneOff, Sfx::Achieve, "sounds/achieve.ogg"),
(SfxType::OneOff, Sfx::Click, "sounds/click-button-140881-crop.ogg"),
(SfxType::OneOff, Sfx::Connect, "sounds/connect.ogg"),
(SfxType::OneOff, Sfx::Crash, "sounds/crash.ogg"),
(SfxType::OneOff, Sfx::EnterVehicle, "sounds/bikestart.ogg"),
(SfxType::OneOff, Sfx::IncomingChatMessage, "sounds/connect.ogg"),
(SfxType::OneOff, Sfx::Ping, "sounds/connect.ogg"),
(SfxType::OneOff, Sfx::Switch, "sounds/typosonic-typing-192811-crop.ogg"),
(SfxType::OneOff, Sfx::WakeUp, "sounds/wakeup.ogg"),
(SfxType::OneOff, Sfx::Woosh, "sounds/woosh.ogg"),
(SfxType::OneOff, Sfx::Zoom, "sounds/zoom.ogg"),
];
#[derive(Component, PartialEq, Hash, Eq, Copy, Clone)]
pub enum Sfx { pub enum Sfx {
IncomingChatMessage, Achieve,
BGM,
Click, Click,
Connect,
Crash,
ElectricMotor,
EnterVehicle,
IncomingChatMessage,
Ion,
Ping,
Rocket,
Switch, Switch,
Thruster,
WakeUp,
Woosh, Woosh,
Zoom, Zoom,
Ping,
Connect,
EnterVehicle,
Crash,
WakeUp,
None,
}
#[derive(Event)] pub struct PlaySfxEvent(pub Sfx);
#[derive(Event)] pub struct ToggleMusicEvent();
#[derive(Component)] pub struct ComponentBGM;
#[derive(Component)] pub struct ComponentThrusterSound;
#[derive(Component)] pub struct ComponentRocketSound;
#[derive(Component)] pub struct ComponentIonSound;
#[derive(Component)] pub struct ComponentElectricMotorSound;
#[derive(Component)] struct SoundBGM(Handle<AudioSource>);
#[derive(Resource)] pub struct SoundClick(Handle<AudioSource>);
#[derive(Resource)] pub struct SoundSwitch(Handle<AudioSource>);
#[derive(Resource)] pub struct SoundWoosh(Handle<AudioSource>);
#[derive(Resource)] pub struct SoundZoom(Handle<AudioSource>);
#[derive(Resource)] pub struct SoundIncomingMessage(Handle<AudioSource>);
#[derive(Resource)] pub struct SoundPing(Handle<AudioSource>);
#[derive(Resource)] pub struct SoundConnect(Handle<AudioSource>);
#[derive(Resource)] pub struct SoundBikeStart(Handle<AudioSource>);
#[derive(Resource)] pub struct SoundCrash(Handle<AudioSource>);
#[derive(Resource)] pub struct SoundWakeUp(Handle<AudioSource>);
pub fn setup(
mut commands: Commands,
settings: Res<var::Settings>,
asset_server: Res<AssetServer>,
) {
commands.spawn((
ComponentBGM,
AudioBundle {
source: SoundBGM(asset_server.load(ASSET_BGM)).0.clone(),
settings: PlaybackSettings {
mode: PlaybackMode::Loop,
paused: settings.hud_active || settings.mute_music,
..default()
},
},
));
commands.spawn((
ComponentThrusterSound,
AudioBundle {
source: asset_server.load(ASSET_THRUSTER),
settings: PlaybackSettings {
mode: PlaybackMode::Loop,
volume: Volume::new(0.0),
paused: true,
..default()
},
},
));
commands.spawn((
ComponentRocketSound,
AudioBundle {
source: asset_server.load(ASSET_ROCKET),
settings: PlaybackSettings {
mode: PlaybackMode::Loop,
volume: Volume::new(0.0),
paused: true,
..default()
},
},
));
commands.spawn((
ComponentIonSound,
AudioBundle {
source: asset_server.load(ASSET_ION),
settings: PlaybackSettings {
mode: PlaybackMode::Loop,
volume: Volume::new(0.0),
paused: true,
..default()
},
},
));
commands.spawn((
ComponentElectricMotorSound,
AudioBundle {
source: asset_server.load(ASSET_ELECTRICMOTOR),
settings: PlaybackSettings {
mode: PlaybackMode::Loop,
volume: Volume::new(0.5),
paused: true,
..default()
},
},
));
commands.insert_resource(SoundClick(asset_server.load(ASSET_CLICK)));
commands.insert_resource(SoundSwitch(asset_server.load(ASSET_SWITCH)));
commands.insert_resource(SoundWoosh(asset_server.load(ASSET_WOOSH)));
commands.insert_resource(SoundZoom(asset_server.load(ASSET_ZOOM)));
commands.insert_resource(SoundIncomingMessage(asset_server.load(ASSET_INCOMING_MESSAGE)));
commands.insert_resource(SoundPing(asset_server.load(ASSET_PING)));
commands.insert_resource(SoundConnect(asset_server.load(ASSET_CONNECT)));
commands.insert_resource(SoundBikeStart(asset_server.load(ASSET_BIKESTART)));
commands.insert_resource(SoundCrash(asset_server.load(ASSET_CRASH)));
commands.insert_resource(SoundWakeUp(asset_server.load(ASSET_WAKEUP)));
}
pub fn toggle_bgm(
keyboard_input: Res<ButtonInput<KeyCode>>,
mut evwriter_toggle: EventWriter<ToggleMusicEvent>,
mut evwriter_sfx: EventWriter<PlaySfxEvent>,
mut settings: ResMut<var::Settings>,
) {
if keyboard_input.just_pressed(settings.key_toggle_music) {
settings.mute_music ^= true;
evwriter_sfx.send(PlaySfxEvent(Sfx::Click));
evwriter_toggle.send(ToggleMusicEvent());
}
if keyboard_input.just_pressed(settings.key_toggle_sfx) {
settings.mute_sfx ^= true;
evwriter_sfx.send(PlaySfxEvent(Sfx::Click));
evwriter_toggle.send(ToggleMusicEvent());
}
}
pub fn play_sfx(
mut commands: Commands,
settings: Res<var::Settings>,
mut events_sfx: EventReader<PlaySfxEvent>,
sound_click: Res<SoundClick>,
sound_switch: Res<SoundSwitch>,
sound_woosh: Res<SoundWoosh>,
sound_zoom: Res<SoundZoom>,
sound_incoming_message: Res<SoundIncomingMessage>,
sound_ping: Res<SoundPing>,
sound_connect: Res<SoundConnect>,
sound_bikestart: Res<SoundBikeStart>,
sound_crash: Res<SoundCrash>,
sound_wakeup: Res<SoundWakeUp>,
) {
if settings.mute_sfx && !events_sfx.is_empty() {
events_sfx.clear();
}
for sfx in events_sfx.read() {
match sfx.0 {
Sfx::None => { continue; }
_ => {}
}
commands.spawn(AudioBundle {
source: match sfx.0 {
Sfx::Switch => sound_switch.0.clone(),
Sfx::Click => sound_click.0.clone(),
Sfx::Woosh => sound_woosh.0.clone(),
Sfx::Zoom => sound_zoom.0.clone(),
Sfx::IncomingChatMessage => sound_incoming_message.0.clone(),
Sfx::Ping => sound_ping.0.clone(),
Sfx::Connect => sound_connect.0.clone(),
Sfx::EnterVehicle => sound_bikestart.0.clone(),
Sfx::Crash => sound_crash.0.clone(),
Sfx::WakeUp => sound_wakeup.0.clone(),
Sfx::None => sound_ping.0.clone(),
},
settings: PlaybackSettings::DESPAWN,
});
}
} }
pub fn str2sfx(sfx_label: &str) -> Sfx { pub fn str2sfx(sfx_label: &str) -> Sfx {
return match sfx_label { return match sfx_label {
"achieve" => Sfx::Achieve,
"switch" => Sfx::Switch, "switch" => Sfx::Switch,
"click" => Sfx::Click, "click" => Sfx::Click,
"woosh" => Sfx::Woosh, "woosh" => Sfx::Woosh,
@ -226,19 +85,111 @@ pub fn str2sfx(sfx_label: &str) -> Sfx {
"ping" => Sfx::Ping, "ping" => Sfx::Ping,
"connect" => Sfx::Connect, "connect" => Sfx::Connect,
"entervehicle" => Sfx::EnterVehicle, "entervehicle" => Sfx::EnterVehicle,
"crash" => Sfx::Crash, "crash" => Sfx::Ping,
_ => Sfx::None, _ => Sfx::Click,
}; };
} }
pub enum SfxType {
BGM,
LoopSfx,
OneOff,
}
#[derive(Event)] pub struct PlaySfxEvent(pub Sfx);
#[derive(Event)] pub struct PauseAllSfxEvent;
#[derive(Event)] pub struct RespawnSinksEvent;
#[derive(Event)] pub struct ToggleMusicEvent();
#[derive(Resource)] pub struct Sounds(HashMap<Sfx, Handle<AudioSource>>);
pub fn setup(
mut commands: Commands,
asset_server: Res<AssetServer>,
mut ew_respawnsinks: EventWriter<RespawnSinksEvent>,
) {
let mut map = HashMap::new();
for (_, sfx, path) in PATHS {
let source = asset_server.load(*path);
map.insert(*sfx, source.clone());
}
commands.insert_resource(Sounds(map));
ew_respawnsinks.send(RespawnSinksEvent);
}
pub fn respawn_sinks(
mut commands: Commands,
asset_server: Res<AssetServer>,
settings: Res<var::Settings>,
q_audiosinks: Query<Entity, (With<AudioSink>, With<Sfx>)>,
) {
for sink in &q_audiosinks {
commands.entity(sink).despawn();
}
for (sfxtype, sfx, path) in PATHS {
let source = asset_server.load(*path);
match sfxtype {
SfxType::BGM => {
commands.spawn((
*sfx,
AudioBundle {
source,
settings: PlaybackSettings {
mode: PlaybackMode::Loop,
paused: settings.mute_music,
..default()
},
},
));
},
SfxType::LoopSfx => {
commands.spawn((
*sfx,
AudioBundle {
source,
settings: PlaybackSettings {
mode: PlaybackMode::Loop,
volume: Volume::new(0.0),
paused: true,
..default()
},
},
));
},
SfxType::OneOff => ()
}
}
}
pub fn play_sfx(
mut commands: Commands,
settings: Res<var::Settings>,
mut events_sfx: EventReader<PlaySfxEvent>,
sounds: Res<Sounds>,
) {
if settings.mute_sfx && !events_sfx.is_empty() {
events_sfx.clear();
}
for sfx in events_sfx.read() {
if let Some(source) = sounds.0.get(&sfx.0) {
commands.spawn(AudioBundle {
source: source.clone(),
settings: PlaybackSettings::DESPAWN,
});
}
}
}
pub fn update_music( pub fn update_music(
mut events: EventReader<ToggleMusicEvent>, mut events: EventReader<ToggleMusicEvent>,
bgm_controller: Query<&AudioSink, With<ComponentBGM>>, q_audiosinks: Query<(&AudioSink, &Sfx)>,
settings: Res<var::Settings>, settings: Res<var::Settings>,
) { ) {
if !events.is_empty() { if !events.is_empty() {
events.clear(); events.clear();
if let Ok(bgm_sink) = bgm_controller.get_single() { for (bgm_sink, sfx) in &q_audiosinks {
if *sfx != Sfx::BGM {
continue;
}
if settings.mute_music { if settings.mute_music {
bgm_sink.pause(); bgm_sink.pause();
} }
@ -267,3 +218,11 @@ pub fn play_zoom_sfx(
*last_zoom_level = mapcam.target_zoom_level; *last_zoom_level = mapcam.target_zoom_level;
} }
} }
pub fn pause_all(
q_audiosinks: Query<&AudioSink, With<Sfx>>,
) {
for sink in &q_audiosinks {
sink.pause();
}
}

BIN
src/blender/cruiser.blend Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

View file

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

View file

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

View file

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

View file

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

107
src/common.rs Normal file
View file

@ -0,0 +1,107 @@
// ▄████████▄ + ███ + ▄█████████ ███ +
// ███▀ ▀███ + + ███ ███▀ + ███ + +
// ███ + ███ ███ ███ █████████ ███ ███ ███ ███
// ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
// ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
// ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
// ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
// + + + ███
// + ▀████████████████████████████████████████████████████▀
//
// 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;
}

204
src/data/deathpoems.in Normal file
View file

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

429
src/game.rs Normal file
View file

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

View file

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

View file

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

548
src/menu.rs Normal file
View file

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

View file

@ -1,355 +0,0 @@
// ▄████████▄ + ███ + ▄█████████ ███ +
// ███▀ ▀███ + + ███ ███▀ + ███ + +
// ███ + ███ ███ ███ █████████ ███ ███ ███ ███
// ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
// ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
// ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
// ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
// + + + ███
// + ▀████████████████████████████████████████████████████▀
//
// 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

@ -0,0 +1,158 @@
<?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>

After

Width:  |  Height:  |  Size: 6.5 KiB

View file

@ -0,0 +1,115 @@
<?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>

After

Width:  |  Height:  |  Size: 4.7 KiB

121
src/svg/dashboard_leak.svg Normal file
View file

@ -0,0 +1,121 @@
<?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>

After

Width:  |  Height:  |  Size: 6.8 KiB

View file

@ -0,0 +1,108 @@
<?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>

After

Width:  |  Height:  |  Size: 4 KiB

View file

@ -0,0 +1,133 @@
<?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>

After

Width:  |  Height:  |  Size: 4.6 KiB

99
src/svg/gauge_battery.svg Normal file
View file

@ -0,0 +1,99 @@
<?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>

After

Width:  |  Height:  |  Size: 3.8 KiB

90
src/svg/gauge_fuel.svg Normal file
View file

@ -0,0 +1,90 @@
<?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>

After

Width:  |  Height:  |  Size: 3.7 KiB

92
src/svg/gauge_heart.svg Normal file
View file

@ -0,0 +1,92 @@
<?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>

After

Width:  |  Height:  |  Size: 5.2 KiB

View file

@ -0,0 +1,87 @@
<?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>

After

Width:  |  Height:  |  Size: 2.8 KiB

91
src/svg/gauge_o2.svg Normal file
View file

@ -0,0 +1,91 @@
<?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>

After

Width:  |  Height:  |  Size: 4.4 KiB

95
src/svg/gauge_suit.svg Normal file
View file

@ -0,0 +1,95 @@
<?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>

After

Width:  |  Height:  |  Size: 3.4 KiB

View file

@ -0,0 +1,87 @@
<?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>

After

Width:  |  Height:  |  Size: 2.8 KiB

96
src/svg/outfly.svg Normal file
View file

@ -0,0 +1,96 @@
<?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>

After

Width:  |  Height:  |  Size: 3.7 KiB

View file

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

View file

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

View file

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