Compare commits

...

44 commits

Author SHA1 Message Date
yuni 73410efc09 add comment 2024-04-23 17:45:47 +02:00
yuni e7df73d4fc more efficient(?) collider hiding 2024-04-23 17:44:22 +02:00
yuni 9c4167f6e9 move defs.txt to src/data/ 2024-04-23 17:40:16 +02:00
yuni 2ecb976b14 move around code to better match the module's purposes 2024-04-23 17:39:07 +02:00
yuni 8fa7859568 document module purposes 2024-04-23 17:33:36 +02:00
yuni 29f0850874 embed assets only in release builds 2024-04-23 15:39:46 +02:00
yuni ce65022905 despawn skeleton limbs on player death 2024-04-23 03:49:47 +02:00
yuni 7f55ca7d80 reorganized models 2024-04-22 23:36:19 +02:00
yuni 08f88f7eeb give animated suit to everybody 2024-04-22 23:28:32 +02:00
yuni 6bf2596649 move head further down 2024-04-22 23:28:22 +02:00
yuni 191d918e4f tweak legs 2024-04-22 23:25:32 +02:00
yuni 228380b9f4 refactoring 2024-04-22 23:20:42 +02:00
yuni c9adeeb94f tweak skeleton 2024-04-22 23:11:41 +02:00
yuni 44f0770226 animate suit 2024-04-22 23:09:50 +02:00
yuni bc9ff6b7a6 implement constructing suits from skeleton 2024-04-22 22:21:18 +02:00
yuni f118384661 add suit skeleton collider 2024-04-22 21:11:01 +02:00
yuni 68f274cb90 load the base of the skeleton 2024-04-22 21:11:00 +02:00
yuni a1910c4075 move model loading code into skeleton 2024-04-22 21:10:59 +02:00
yuni a12ffac841 add skeleton.rs and individual body part suit models 2024-04-22 21:10:52 +02:00
yuni dff3652c37 update itch link 2024-04-22 14:18:06 +02:00
yuni a52773f15d shorten links 2024-04-22 14:16:52 +02:00
yuni 8c8b9e0b43 simplify mesh of POI marker 2024-04-22 00:41:50 +02:00
yuni c1e76d09a9 Space now resets the map camera 2024-04-22 00:07:45 +02:00
yuni 267ffc105c Yuni now patches up the player on first meet 2024-04-22 00:03:17 +02:00
yuni 6267be23cd add Yuni, orbiting Thebe 2024-04-21 23:52:29 +02:00
yuni 2d2be6bd7e cleanup 2024-04-21 21:57:59 +02:00
yuni bf87866244 cleanup 2024-04-21 21:48:02 +02:00
yuni e1d48c72a3 smaller point of interest marker 2024-04-21 21:47:04 +02:00
yuni 8e987f6d22 make MeteorAceGTs points of interest 2024-04-21 21:46:54 +02:00
yuni 556f097193 show point of interest marker only if HUD + map are active 2024-04-21 21:38:46 +02:00
yuni 00e4fb4957 add point of interest markers in AR mode 2024-04-21 21:21:34 +02:00
yuni b9bce22ead add link descriptions 2024-04-21 19:37:05 +02:00
yuni 7d2fc6224a move links into a single line at the top 2024-04-21 19:36:14 +02:00
yuni 7b21c2b820 tweak ASCII art 2024-04-21 19:34:00 +02:00
yuni 38a8f5421c add comment 2024-04-21 19:28:29 +02:00
yuni 1da7ad151f add MacOS/Android/iOS instructions 2024-04-21 19:16:16 +02:00
yuni 15592b837e update comment 2024-04-21 19:11:21 +02:00
yuni fad7347cd9 rephrased "Running OutFly" section 2024-04-21 19:11:18 +02:00
yuni 634a13fcf9 add a new screenshot to README 2024-04-21 18:57:14 +02:00
yuni 0687952258 Shortened README 2024-04-21 18:53:26 +02:00
yuni 03a5cd0831 add quick links to README 2024-04-21 18:45:40 +02:00
yuni 9d8d28937f remove screenshots from README.md 2024-04-21 18:34:34 +02:00
yuni 1adb56c0e2 add ASCII art header to most files 2024-04-21 18:25:30 +02:00
yuni 39d8eefa17 update key bindings 2024-04-20 21:28:51 +02:00
39 changed files with 1007 additions and 319 deletions

View file

@ -1,3 +1,15 @@
```
▄████████▄ + ███ + ▄█████████ ███ +
███▀ ▀███ + + ███ ███▀ + ███ + +
███ + ███ ███ ███ █████████ ███ ███ ███ ███
███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
+ + + ███
+ ▀████████████████████████████████████████████████████▀
```
# Developer Commentary # Developer Commentary
## Clippy Convenience Companion ## Clippy Convenience Companion

View file

@ -1,3 +1,13 @@
# ▄████████▄ + ███ + ▄█████████ ███ +
# ███▀ ▀███ + + ███ ███▀ + ███ + +
# ███ + ███ ███ ███ █████████ ███ ███ ███ ███
# ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
# ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
# ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
# ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
# + + + ███
# + ▀████████████████████████████████████████████████████▀
[package] [package]
name = "outfly" name = "outfly"
version = "0.7.3" version = "0.7.3"
@ -14,7 +24,7 @@ build = "build/build.rs"
regex = "1" regex = "1"
bevy = { version = "0.13.2", default-features = false, features = ["jpeg", "bevy_asset", "bevy_audio", "bevy_scene", "bevy_winit", "bevy_core_pipeline", "bevy_pbr", "bevy_gltf", "bevy_render", "bevy_text", "bevy_ui", "multi-threaded", "tonemapping_luts", "vorbis"]} bevy = { version = "0.13.2", default-features = false, features = ["jpeg", "bevy_asset", "bevy_audio", "bevy_scene", "bevy_winit", "bevy_core_pipeline", "bevy_pbr", "bevy_gltf", "bevy_render", "bevy_text", "bevy_ui", "multi-threaded", "tonemapping_luts", "vorbis"]}
bevy_xpbd_3d = { version = "0.4.2", default-features = false, features = ["3d", "f64", "parry-f64", "parallel", "async-collider"] } bevy_xpbd_3d = { version = "0.4.2", default-features = false, features = ["3d", "f64", "parry-f64", "parallel", "async-collider"] }
bevy_embedded_assets = "0.10.2" bevy_embedded_assets = { version = "0.10.2", optional = true }
fastrand = "2.0" fastrand = "2.0"
serde = "1.0" serde = "1.0"
serde_yaml = "0.9" serde_yaml = "0.9"
@ -28,6 +38,7 @@ dev = ["bevy/dynamic_linking", "bevy/file_watcher"]
wasm = ["bevy/webgl2"] wasm = ["bevy/webgl2"]
x11 = ["bevy/x11"] x11 = ["bevy/x11"]
wayland = ["bevy/wayland"] wayland = ["bevy/wayland"]
embed_assets = ["dep:bevy_embedded_assets"]
[profile.dev] [profile.dev]
opt-level = 1 opt-level = 1

View file

@ -1,6 +1,20 @@
# OutFly ```
▄████████▄ + ███ + ▄█████████ ███ +
███▀ ▀███ + + ███ ███▀ + ███ + +
███ + ███ ███ ███ █████████ ███ ███ ███ ███
███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
+ + + ███
+ ▀████████████████████████████████████████████████████▀
```
![screenshot](doc/images/screenshot1.jpg) Chapters: [Features](#features) • [Controls](#controls) • [Running OutFly](#running-outfly) • [Building](#building) • [Changelog](#changelog) • [Credits](#credits)
Links: [Code](https://codeberg.org/hut/outfly) • [itch.io](https://yunicode.itch.io/outfly) • [Mastodon](https://mastodon.gamedev.place/@outfly) • [Chat](https://matrix.to/#/#outfly:pub.solar)
# OutFly
OutFly is an atmospheric, open world, 100% hard sci-fi 3D game that throws you into the [Rings of Jupiter](https://en.wikipedia.org/wiki/Rings_of_Jupiter) with a self-sufficient sportswear space suit that will take you anywhere. OutFly is an atmospheric, open world, 100% hard sci-fi 3D game that throws you into the [Rings of Jupiter](https://en.wikipedia.org/wiki/Rings_of_Jupiter) with a self-sufficient sportswear space suit that will take you anywhere.
@ -8,7 +22,9 @@ Imagine a blend of [Fallout](https://en.wikipedia.org/wiki/Fallout_%28series%29)
This game aims to respect the player as much as possible. It doesn't waste your time: Despite the vastness of space, nothing takes too long. Speed cheats are active by default, allowing you to visit places you normally couldn't, without passing out from the g-forces. There are no anxiety-causing features (apart of, maybe, space itself), no loading screens, nothing to micromanage, not even save games. You can plunge into the game any time you feel like it, and it's up to you whether you just want to soak in the beautiful scenery, engage with the survival mechanics [still in development], or dive into the game story [still in development]. And finally, it's not just DRM-free but completely open source, allowing you to tinker on any part of the game to your liking. This game aims to respect the player as much as possible. It doesn't waste your time: Despite the vastness of space, nothing takes too long. Speed cheats are active by default, allowing you to visit places you normally couldn't, without passing out from the g-forces. There are no anxiety-causing features (apart of, maybe, space itself), no loading screens, nothing to micromanage, not even save games. You can plunge into the game any time you feel like it, and it's up to you whether you just want to soak in the beautiful scenery, engage with the survival mechanics [still in development], or dive into the game story [still in development]. And finally, it's not just DRM-free but completely open source, allowing you to tinker on any part of the game to your liking.
Key features: ![screenshot](doc/images/screenshot3.jpg)
# Features
- Open source forever - Open source forever
- Open world, realistic hard sci-fi, atmospheric, deadly - Open world, realistic hard sci-fi, atmospheric, deadly
@ -17,16 +33,7 @@ Key features:
- Written in [Rust](https://www.rust-lang.org) with the [Bevy game engine](https://bevyengine.org) - Written in [Rust](https://www.rust-lang.org) with the [Bevy game engine](https://bevyengine.org)
- Status: Early access, not much content - Status: Early access, not much content
Links: # Controls
- [Source code](https://codeberg.org/hut/outfly)
- [itch.io page](https://wholesomevacuum.itch.io/outfly)
- [Game updates on Mastodon](https://mastodon.gamedev.place/@outfly)
- [Chatroom](https://matrix.to/#/#outfly:pub.solar)
![screenshot](doc/images/screenshot2.jpg)
# Key Bindings
- F1: Show key bindings - F1: Show key bindings
- Space: Slow down (or match velocity) - Space: Slow down (or match velocity)
@ -34,19 +41,19 @@ Links:
- R: Rotate (hold & move mouse) - R: Rotate (hold & move mouse)
- E: Interact: Talk to people, enter vehicles - E: Interact: Talk to people, enter vehicles
- Q: Exit vehicle - Q: Exit vehicle
- F7: Restart game
- JKULIO: Mouseless camera rotation - JKULIO: Mouseless camera rotation
- Augmented Reality: (toggle with Tab) - Augmented Reality: (toggle with Tab)
- Left click: Target objects - Left click: Target objects
- Right click: Zoom - Right click: Zoom
- Settings - Settings
- Tab: Toggle HUD/AR - Tab: Toggle HUD/AR
- F11: Toggle fullscreen
- F: Toggle 3rd person view
- M: Toggle map - M: Toggle map
- F: Toggle 3rd person view
- Y: Toggle rotation stabilizer - Y: Toggle rotation stabilizer
- F4: Toggle music
- F3: Toggle sound effects - F3: Toggle sound effects
- F4: Toggle music
- F7: Restart game
- F11: Toggle fullscreen
- Cheats - Cheats
- G: Toggle god mode / cheats - G: Toggle god mode / cheats
- V/B: Impossible acceleration forward/backward - V/B: Impossible acceleration forward/backward
@ -54,35 +61,45 @@ Links:
- C: Impossibly instant stopping - C: Impossibly instant stopping
- X: Teleport to target - X: Teleport to target
# System Requirements # Running OutFly
## System Requirements
- Screen, keyboard - Screen/keyboard/mouse
- Operating System: Linux, Windows, MacOS - Operating System: Linux, Windows, Mac
- Ideally, a graphics card with Vulkan support - A graphics card with vulkan support
If your GPU does not support Vulkan, try rendering with OpenGL by setting the environment variable `WGPU_BACKEND` to `gl`, like: ## Running on Linux
1. Download and unpack the latest release: https://codeberg.org/hut/outfly/releases
2. Open a terminal and navigate to the directory where you unpacked outfly
3. If you are on ArchLinux, type the following commands. For other distributions, replace "pacman -S" with the distro's command to install packages. Also, the packages may be called slightly differently.
```
pacman -S glibc libcap gcc-libs alsa-lib systemd-libs
./outfly
```
If your graphics card does not support vulkan, try setting the environment variable `WGPU_BACKEND=gl`: (will result in poor performance)
``` ```
WGPU_BACKEND=gl ./outfly WGPU_BACKEND=gl ./outfly
or
WGPU_BACKEND=gl cargo run
``` ```
However, this may result in poor performance and visual glitches. Alternatively, you can also install OutFly as a package, if your distribution has one. This will place OutFly in your "start menu". As of writing, only an ArchLinux AUR package exists, which you can install with this command:
# Running OutFly
1. Download a release for your operating system at https://codeberg.org/hut/outfly/releases
2. On Linux, you need the dependency packages: `glibc libcap gcc-libs alsa-lib systemd-libs`. These are the names for ArchLinux, they may differ on your distribution.
3. Unpack and run the outfly/outfly.exe executable.
1. On Windows, just double-click on outfly.exe
2. On Linux, open the console and type this:
``` ```
cd [path-to-extracted-outfly-directory] yay -S outfly-git
./outfly
``` ```
## Running on Windows
1. Download and unpack the latest release: https://codeberg.org/hut/outfly/releases
2. Double-click on `OutFly.exe`
## Running on MacOS / Android / iOS
No releases for these operating systems exist yet. For MacOS, you can build OutFly yourself using the instructions below. Support for Android/iOS is planned for the future.
# Building # Building
If there is no package for the version or operating system that you need, or if you wish to tinker on the game, you can also build outfly yourself. If there is no package for the version or operating system that you need, or if you wish to tinker on the game, you can also build outfly yourself.
@ -221,7 +238,7 @@ python -m http.server -d wasm
- v0.1.1: Better sky box and HUD - v0.1.1: Better sky box and HUD
- v0.1.0: First release with basic controls, HUD, sounds, skybox, sun - v0.1.0: First release with basic controls, HUD, sounds, skybox, sun
# Credits and License # Credits
- Source code: GPL Version 3.0 - Source code: GPL Version 3.0
- 3D models: Original art, placed under the Creative Commons CC0 License - 3D models: Original art, placed under the Creative Commons CC0 License

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,3 +1,4 @@
// NOTE: This is currently not being used
#import bevy_pbr::{ #import bevy_pbr::{
mesh_view_bindings::globals, mesh_view_bindings::globals,
forward_io::VertexOutput, forward_io::VertexOutput,

View file

@ -1,3 +1,13 @@
// ▄████████▄ + ███ + ▄█████████ ███ +
// ███▀ ▀███ + + ███ ███▀ + ███ + +
// ███ + ███ ███ ███ █████████ ███ ███ ███ ███
// ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
// ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
// ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
// ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
// + + + ███
// + ▀████████████████████████████████████████████████████▀
fn main() { fn main() {
let target = std::env::var("TARGET").unwrap(); let target = std::env::var("TARGET").unwrap();
if target.contains("windows") { if target.contains("windows") {

View file

@ -1,4 +1,14 @@
#!/usr/bin/env python #!/usr/bin/env python
# ▄████████▄ + ███ + ▄█████████ ███ +
# ███▀ ▀███ + + ███ ███▀ + ███ + +
# ███ + ███ ███ ███ █████████ ███ ███ ███ ███
# ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
# ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
# ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
# ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
# + + + ███
# + ▀████████████████████████████████████████████████████▀
#
# This script requires the following file in the extra/ directory: # This script requires the following file in the extra/ directory:
# https://github.com/astronexus/HYG-Database/blob/cbd21013d2bb89732b893be357a6f41836dbe614/hyg/CURRENT/hygdata_v41.csv # https://github.com/astronexus/HYG-Database/blob/cbd21013d2bb89732b893be357a6f41836dbe614/hyg/CURRENT/hygdata_v41.csv

View file

@ -1,4 +1,14 @@
#!/bin/sh #!/bin/sh
# ▄████████▄ + ███ + ▄█████████ ███ +
# ███▀ ▀███ + + ███ ███▀ + ███ + +
# ███ + ███ ███ ███ █████████ ███ ███ ███ ███
# ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
# ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
# ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
# ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
# + + + ███
# + ▀████████████████████████████████████████████████████▀
#
# usage: run this script from the project root, like this: build/install.sh [rootdir] # usage: run this script from the project root, like this: build/install.sh [rootdir]
rootdir="${1:-}" rootdir="${1:-}"

View file

@ -1,3 +1,13 @@
# + + +
# + + + + +
# +
# +
# + + + + +
# + +
# +
# + + +
# +
[Desktop Entry] [Desktop Entry]
Type=Application Type=Application
Name=OutFly Name=OutFly

View file

@ -1,12 +1,22 @@
#!/bin/sh #!/bin/sh
# ▄████████▄ + ███ + ▄█████████ ███ +
# ███▀ ▀███ + + ███ ███▀ + ███ + +
# ███ + ███ ███ ███ █████████ ███ ███ ███ ███
# ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
# ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
# ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
# ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
# + + + ███
# + ▀████████████████████████████████████████████████████▀
#
# 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; doc/scripts/pack.sh # Usage: cd outfly; build/pack.sh
set -e set -e
if [ "$1" == "-b" ]; then if [ "$1" == "-b" ]; then
cargo build --release --target=x86_64-unknown-linux-gnu --features "x11 wayland" cargo build --release --target=x86_64-unknown-linux-gnu --no-default-features --features "x11 wayland embed_assets"
cargo build --release --target=x86_64-pc-windows-gnu --no-default-features cargo build --release --target=x86_64-pc-windows-gnu --no-default-features --features "embed_assets"
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)"

BIN
doc/images/screenshot3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

View file

@ -1,10 +1,28 @@
// ▄████████▄ + ███ + ▄█████████ ███ +
// ███▀ ▀███ + + ███ ███▀ + ███ + +
// ███ + ███ ███ ███ █████████ ███ ███ ███ ███
// ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
// ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
// ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
// ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
// + + + ███
// + ▀████████████████████████████████████████████████████▀
//
// This module manages the internal states of individual characters,
// such as their resources, the damage they receive, and interactions
// between characters and with vehicles. It also handles cheats.
//
// 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 bevy::scene::SceneInstance; use bevy::scene::SceneInstance;
use bevy::math::DVec3; use bevy::math::DVec3;
use crate::{actor, audio, camera, chat, commands, effects, hud, nature, var, world}; use crate::{actor, audio, camera, chat, commands, effects, hud, nature, var, world};
use std::collections::HashMap; use std::collections::HashMap;
const CENTER_WORLD_ON_PLAYER: bool = true;
pub const ENGINE_SPEED_FACTOR: f32 = 30.0; pub const ENGINE_SPEED_FACTOR: f32 = 30.0;
const MAX_TRANSMISSION_DISTANCE: f32 = 100.0; const MAX_TRANSMISSION_DISTANCE: f32 = 100.0;
const MAX_INTERACT_DISTANCE: f32 = 50.0; const MAX_INTERACT_DISTANCE: f32 = 50.0;
@ -25,6 +43,7 @@ impl Plugin for ActorPlugin {
handle_input, handle_input,
handle_collisions, handle_collisions,
handle_damage, handle_damage,
handle_cheats,
)); ));
app.add_systems(PostUpdate, ( app.add_systems(PostUpdate, (
handle_vehicle_enter_exit, handle_vehicle_enter_exit,
@ -33,6 +52,18 @@ impl Plugin for ActorPlugin {
app.add_event::<VehicleEnterExitEvent>(); app.add_event::<VehicleEnterExitEvent>();
app.add_event::<PlayerDiesEvent>(); app.add_event::<PlayerDiesEvent>();
app.insert_resource(Id2Pos(HashMap::new())); 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));
}
} }
} }
@ -538,3 +569,109 @@ fn update_id2pos(
id2pos.0.insert(id.0.clone(), pos.0); 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(&"busstop2".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(&"busstop3".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

@ -1,3 +1,15 @@
// ▄████████▄ + ███ + ▄█████████ ███ +
// ███▀ ▀███ + + ███ ███▀ + ███ + +
// ███ + ███ ███ ███ █████████ ███ ███ ███ ███
// ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
// ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
// ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
// ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
// + + + ███
// + ▀████████████████████████████████████████████████████▀
//
// This module manages sound effects and music.
use bevy::prelude::*; use bevy::prelude::*;
use bevy::audio::{PlaybackMode, Volume}; use bevy::audio::{PlaybackMode, Volume};
use crate::var; use crate::var;

View file

@ -1,3 +1,17 @@
// ▄████████▄ + ███ + ▄█████████ ███ +
// ███▀ ▀███ + + ███ ███▀ + ███ + +
// ███ + ███ ███ ███ █████████ ███ ███ ███ ███
// ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
// ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
// ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
// ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
// + + + ███
// + ▀████████████████████████████████████████████████████▀
//
// This module manages the game's viewport, handles camera- and
// movement-related keyboard input, and provides some camera-
// related computation functions.
use bevy::prelude::*; use bevy::prelude::*;
use bevy::input::mouse::{MouseMotion, MouseWheel}; use bevy::input::mouse::{MouseMotion, MouseWheel};
use bevy::window::PrimaryWindow; use bevy::window::PrimaryWindow;
@ -183,6 +197,10 @@ pub fn update_map_camera(
if keyboard_input.pressed(settings.key_left) { if keyboard_input.pressed(settings.key_left) {
offset_z += 1.0; offset_z += 1.0;
} }
if keyboard_input.pressed(settings.key_stop) {
mapcam.offset_x = 0.0;
mapcam.offset_z = 0.0;
}
// Update zoom level // Update zoom level
if !mapcam.initialized { if !mapcam.initialized {
@ -254,6 +272,7 @@ pub fn handle_input(
mut settings: ResMut<var::Settings>, mut settings: ResMut<var::Settings>,
mut mapcam: ResMut<MapCam>, mut mapcam: ResMut<MapCam>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>, mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_updateoverlays: EventWriter<hud::UpdateOverlayVisibility>,
) { ) {
if keyboard_input.just_pressed(settings.key_camera) { if keyboard_input.just_pressed(settings.key_camera) {
settings.third_person ^= true; settings.third_person ^= true;
@ -261,6 +280,7 @@ pub fn handle_input(
if keyboard_input.just_pressed(settings.key_map) { if keyboard_input.just_pressed(settings.key_map) {
settings.map_active ^= true; settings.map_active ^= true;
*mapcam = MapCam::default(); *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_sfx.send(audio::PlaySfxEvent(audio::Sfx::Click)); ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Click));

View file

@ -1,3 +1,16 @@
// ▄████████▄ + ███ + ▄█████████ ███ +
// ███▀ ▀███ + + ███ ███▀ + ███ + +
// ███ + ███ ███ ███ █████████ ███ ███ ███ ███
// ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
// ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
// ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
// ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
// + + + ███
// + ▀████████████████████████████████████████████████████▀
//
// This module loads the chat definitions from the YAML files
// and manages the flow of conversations.
use crate::{actor, audio, effects, hud, var, world}; use crate::{actor, audio, effects, hud, var, world};
use bevy::prelude::*; use bevy::prelude::*;
use bevy::math::DVec3; use bevy::math::DVec3;
@ -9,6 +22,7 @@ use std::collections::HashMap;
pub const CHATS: &[&str] = &[ pub const CHATS: &[&str] = &[
include_str!("chats/serenity.yaml"), include_str!("chats/serenity.yaml"),
include_str!("chats/startrans.yaml"), include_str!("chats/startrans.yaml"),
include_str!("chats/thebe.yaml"),
]; ];
pub const TOKEN_CHAT: &str = "chat"; pub const TOKEN_CHAT: &str = "chat";

View file

@ -1,20 +1,12 @@
- chat: Drifter # ▄████████▄ + ███ + ▄█████████ ███ +
- system: "Error: No response" # ███▀ ▀███ + + ███ ███▀ + ███ + +
- system: No life signs detected # ███ + ███ ███ ███ █████████ ███ ███ ███ ███
- Damn, it's gotta be moldy in that suit. How long has it been drifting?: # ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
- Harvest some oxygen: # ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
- script: refilloxygen 1 Drifter # ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
# ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
# + + + ███
--- # + ▀████████████████████████████████████████████████████▀
- chat: SubduedClippy
- At your service!
---
- chat: Icarus - chat: Icarus
- if $met: - if $met:
@ -134,53 +126,6 @@
--- ---
- chat: SpacePizzaChef
- Welcome to Space Pizza™, best pizza around the rings!
- Great to see a customer, we don't get many lately
- Would you like to order today's special?
- label: offer
- What's the special?:
- Suspicious Spacefunghi
- With free pineapple imitation
- Our pizza smoothies are freshly blended every day
- Wait... pizza smoothie?:
- Huh? Of course, smoothie! How else do you want to get that pizza down your spacesuit feeding tube?
- An emulsion of deliciousness!
- I think I'll pass...:
- Your loss, mate
- goto: EXIT
- Wh... what's a pizzeria doing here?:
- Hah, beautiful, right? I carved it out this asteroid myself!
- You know how much work it was to neutralize the rotation of the asteroid, so my valued customers don't bang against the walls while drinking my pizza?
- Now would you like today's special or not?
- goto: offer
- My head hurts, my suit leaks, I think I'm dying...:
- Seriously? Let me have a look. Just press the 'Grant Access' button please.
- "[GRANT ACCESS TO SPACESUIT WIFI]":
- label: hack
- warn: MALWARE DETECTED
- warn: BITCOIN MINER DETECTED
- nowait: true
Hey, what are you doing with me?:
- Just checking your systems, hang on tight
- Yeah, suit's fucked, I'd look out for a repair shop
- Anyway, wanna order today's special?
- goto: offer
- "[DENY ACCESS TO SPACESUIT WIFI]":
- Oh come on, do you want my help or not?
- "[GRANT ACCESS TO SPACESUIT WIFI]":
- goto: hack
- "[DENY ACCESS TO SPACESUIT WIFI]":
- Great, the first customer in ages, and they're brain damaged...
- Fuck off!:
- Great, the first customer in ages, and they're brain damaged...
- goto: EXIT
- Hey, are you still there? Is this a prank?
---
- chat: PizzaChef - chat: PizzaChef
- if $eat: - if $eat:
- Ah, they always come back. - Ah, they always come back.
@ -351,3 +296,21 @@
- Push the TAB button, your space suit's AR will show you the date and time. - Push the TAB button, your space suit's AR will show you the date and time.
- goto: generic_questions - goto: generic_questions
- I think I'm good for now.: [] - I think I'm good for now.: []
---
- chat: Drifter
- system: "Error: No response"
- system: No life signs detected
- Damn, it's gotta be moldy in that suit. How long has it been drifting?:
- Harvest some oxygen:
- script: refilloxygen 1 Drifter
---
- chat: SubduedClippy
- At your service!

View file

@ -1,3 +1,13 @@
# ▄████████▄ + ███ + ▄█████████ ███ +
# ███▀ ▀███ + + ███ ███▀ + ███ + +
# ███ + ███ ███ ███ █████████ ███ ███ ███ ███
# ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
# ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
# ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
# ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
# + + + ███
# + ▀████████████████████████████████████████████████████▀
- chat: NPCinCryoStasis - chat: NPCinCryoStasis
- system: "Error: No response" - system: "Error: No response"
- system: Lifeform in cryostasis detected - system: Lifeform in cryostasis detected

93
src/chats/thebe.yaml Normal file
View file

@ -0,0 +1,93 @@
# ▄████████▄ + ███ + ▄█████████ ███ +
# ███▀ ▀███ + + ███ ███▀ + ███ + +
# ███ + ███ ███ ███ █████████ ███ ███ ███ ███
# ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
# ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
# ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
# ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
# + + + ███
# + ▀████████████████████████████████████████████████████▀
- chat: Yuni
- if $introduced:
- Hi again!
- goto: questions
- OH!
- Wow, you found me!
- You've come a long way, let me patch you up!
- script: repairsuit
- system: SuitPatch™ SuperGlue™ applied.
- script: refilloxygen 1
- system: Oxygen refilled
- How the 卫生纸 did you even get here?
- I saw the point-of-interest marker and flew all the way here!:
- Holy 螃蟹, that's impressive.
- It's really not easy, with all the asteroids on the way.
- Well, uhm, I might have cheated a little bit.:
- "Hah :)"
- Nothing to be ashamed of, of course.
- Feel free to explore this world on your own terms.
- Hope you like this place so far.
- Took me quite a long time to get all the details just right.
- nowait: true
Ah, you're the game developer, aren't you?:
- "Well, in a sense, yes, I am :)"
- Tell me, what do you like the most?
- set: $introduced
- The stars are so pretty!:
- "*_* Yes!! I love them too!"
- Have you tried opening the map and zooming all the way out?
- That really blows my mind and brings tears to my eyes.
- Really shows how small we are, and how much more there is out there.
- Jupiter! It's beautiful and terrifying at the same time!:
- Jupiter really creeps me out!
- I visited Metis Prime earlier, and how Jupiter was eating up half the night sky shook me to the core.
- This planet is so gigantic, it's even bigger than some red dwarf stars!
- Some abysmal cosmic horror, if you ask me.
- Thebe, this moon here, on the other hand, is small and cozy in comparison.
- I can get up close without too much fear of dying.
- The pizza sure is awesome here:
- Ah come on, don't lie to me.
- Nobody likes pizza through their feeding tube.
- Even if it's the legendary Old Earth Pizza smoothies, it's still smoothies.
- I love the freedom I have in this world:
- Right, nobody tells you what to do or not to do. :)
- I love crashing things into one another and watching things fly off into the distance.
- Have you been to Metis Prime and tried out driving The Whale?
- That ship is enormous and can swallow pretty much anything.
- But did you know that you can also take it with you on a bus ride?
- Give that a try if you like, it's hilarious.
- I wish I could tell you, but the thing I want to say is not listed here.:
- Oh, what a pity!
- Come on, let's change that.
- If you write me a message, out there in "reality", I can add your choice here.
- Check out the README for ways of contacting me! :)
- Anyway.
- label: questions
- What are you doing here?:
- Thinking.
- I want to add a space station orbiting Thebe.
- But I don't have any inspiration yet.
- I thought that if I come over here myself, maybe some inspiration will come to me.
- Visit again later, it'll be cool! :)
- How are you doing?:
- Great. Creating this world is quite the blast! :)
- I have so many ideas though, and so little time.
- You should check for updates in a couple months.
- If my "reality" persona is still alive by then, I'm sure there'll be cool new things to explore.
- Are you... God?:
- I'm the avatar of the game creator, if that's what you mean.
- But I'm not omnipotent or something.
- In here, I'm still constrained to the same laws of physics as you are.
- And out there, in "reality", I am a mere mortal of flesh and bone.
- "What do *you* like the most?":
- Watching this place grow!
- It started out with just a black window. Then came a skybox.
- But now, look at this! It's really starting to get cozy here.
- I keep discovering new, beautiful things here that just blow my mind.
- Can't wait to see what I'm going to add next.
- See you around!:
- Don't be a stranger!
- goto: EXIT
- goto: questions

View file

@ -1,9 +1,20 @@
// This plugin loads "defs.txt" and applies the therein contained commands // ▄████████▄ + ███ + ▄█████████ ███ +
// ███▀ ▀███ + + ███ ███▀ + ███ + +
// ███ + ███ ███ ███ █████████ ███ ███ ███ ███
// ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
// ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
// ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
// ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
// + + + ███
// + ▀████████████████████████████████████████████████████▀
//
// This module populates the world with actors as defined in "defs.txt"
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::math::DVec3;
use crate::{actor, camera, chat, hud, nature, shading, world}; use crate::{actor, camera, chat, hud, nature, shading, skeleton, world};
use regex::Regex; use regex::Regex;
use std::f32::consts::PI; use std::f32::consts::PI;
use std::f64::consts::PI as PI64; use std::f64::consts::PI as PI64;
@ -54,6 +65,7 @@ struct ParserState {
is_clickable: bool, is_clickable: bool,
is_targeted_on_startup: bool, is_targeted_on_startup: bool,
is_sun: bool, is_sun: bool,
is_point_of_interest: bool,
has_physics: bool, has_physics: bool,
has_ring: bool, has_ring: bool,
wants_maxrotation: Option<f64>, wants_maxrotation: Option<f64>,
@ -103,6 +115,7 @@ impl Default for ParserState {
is_clickable: true, is_clickable: true,
is_targeted_on_startup: false, is_targeted_on_startup: false,
is_sun: false, is_sun: false,
is_point_of_interest: false,
has_physics: true, has_physics: true,
has_ring: false, has_ring: false,
wants_maxrotation: None, wants_maxrotation: None,
@ -133,7 +146,7 @@ pub fn load_defs(
) { ) {
let re1 = Regex::new(r"^\s*([a-z_-]+)\s+(.*)$").unwrap(); let re1 = Regex::new(r"^\s*([a-z_-]+)\s+(.*)$").unwrap();
let re2 = Regex::new("\"([^\"]*)\"|(-?[0-9]+[0-9e-]*(?:\\.[0-9e-]+)?)|([a-zA-Z_-][a-zA-Z0-9_-]*)").unwrap(); let re2 = Regex::new("\"([^\"]*)\"|(-?[0-9]+[0-9e-]*(?:\\.[0-9e-]+)?)|([a-zA-Z_-][a-zA-Z0-9_-]*)").unwrap();
let defs_string = include_str!("defs.txt"); let defs_string = include_str!("data/defs.txt");
let mut lines = defs_string.lines(); let mut lines = defs_string.lines();
let mut state = ParserState::default(); let mut state = ParserState::default();
let mut command; let mut command;
@ -252,6 +265,9 @@ pub fn load_defs(
["ring", "yes"] => { ["ring", "yes"] => {
state.has_ring = true; state.has_ring = true;
} }
["pointofinterest", "yes"] => {
state.is_point_of_interest = true;
}
["oxygen", amount] => { ["oxygen", amount] => {
if let Ok(amount) = amount.parse::<f32>() { if let Ok(amount) = amount.parse::<f32>() {
state.is_lifeform = true; state.is_lifeform = true;
@ -523,14 +539,14 @@ fn spawn_entities(
..default() ..default()
}); });
} else if let Some(model) = &state.model { } else if let Some(model) = &state.model {
actor.insert(SceneBundle { actor.insert(SpatialBundle {
transform: Transform { transform: Transform {
scale: Vec3::splat(state.model_scale), scale: Vec3::splat(state.model_scale),
..default() ..default()
}, },
scene: asset_server.load(world::asset_name_to_path(model.as_str())),
..default() ..default()
}); });
skeleton::load(model.as_str(), &mut actor, &*asset_server);
} }
// Physics Parameters // Physics Parameters
@ -663,17 +679,30 @@ fn spawn_entities(
} }
if let Some(ar_asset_name) = &state.ar_model { if let Some(ar_asset_name) = &state.ar_model {
commands.spawn(( let mut entitycmd = commands.spawn((
hud::AugmentedRealityOverlay { hud::AugmentedRealityOverlay {
owner: actor_entity, owner: actor_entity,
}, },
world::DespawnOnPlayerDeath, world::DespawnOnPlayerDeath,
SceneBundle { SpatialBundle {
scene: asset_server.load(world::asset_name_to_path(ar_asset_name)),
visibility: Visibility::Hidden, visibility: Visibility::Hidden,
..default() ..default()
}, },
)); ));
skeleton::load(ar_asset_name, &mut entitycmd, &*asset_server);
}
if state.is_point_of_interest {
let mut entitycmd = commands.spawn((
hud::PointOfInterestMarker(actor_entity),
world::DespawnOnPlayerDeath,
hud::ToggleableHudElement,
SpatialBundle {
visibility: Visibility::Hidden,
..default()
},
));
skeleton::load("point_of_interest", &mut entitycmd, &*asset_server);
} }
if state.has_ring { if state.has_ring {
@ -698,19 +727,10 @@ fn spawn_entities(
} }
} }
pub fn hide_colliders(mut q_mesh: Query<(&mut Visibility, &Name), (Added<Visibility>, With<Handle<Mesh>>)>) {
pub fn hide_colliders(
//mut commands: Commands,
mut q_mesh: Query<(&mut Visibility, &Name), With<Handle<Mesh>>>,
//q_flag: Query<Entity, With<NeedsSceneColliderRemoved>>,
) {
for (mut visibility, name) in &mut q_mesh { for (mut visibility, name) in &mut q_mesh {
if name.as_str() == "Collider" { if name.as_str() == "Collider" {
*visibility = Visibility::Hidden; *visibility = Visibility::Hidden;
} }
} }
// TODO: not quite done here yet...
// for entity in &q_flag {
// commands.entity(entity).remove::<NeedsSceneColliderRemoved>();
// }
} }

View file

@ -120,7 +120,7 @@ actor 0 0 0 jupiter
clickable no clickable no
physics off physics off
actor 0 593051 0 suit actor 0 593051 0 suitv1
relativeto jupiter relativeto jupiter
orbit 224000e3 0.66 orbit 224000e3 0.66
player yes player yes
@ -146,6 +146,7 @@ actor 10 -30 20 MeteorAceGT
camdistance 50 camdistance 50
density 500 density 500
angularmomentum 0.1 0.1 0.3 angularmomentum 0.1 0.1 0.3
pointofinterest yes
actor 0 0 0 io actor 0 0 0 io
name Io name Io
@ -206,6 +207,20 @@ actor 0 0 0 moonlet
orbit 221900e3 0.66 orbit 221900e3 0.66
scale 50e3 scale 50e3
angularmomentum 0 0.025 0 angularmomentum 0 0.025 0
actor -48e3 20e3 0 suitv1
relativeto thebe
id yuni
name "Yuni"
chatid Yuni
scale 2
density 200
collider handcrafted
thrust 1.2 1 1 400 1.5
rotationx 1
engine monopropellant
wants maxrotation 0
wants maxvelocity 0
pointofinterest yes
actor 0 0 0 moonlet actor 0 0 0 moonlet
name Metis name Metis
@ -301,6 +316,7 @@ actor -3300 10 0 pizzeria
relativeto player relativeto player
id pizzeria id pizzeria
scale 40 scale 40
pointofinterest yes
collider mesh collider mesh
rotationy 0.30 rotationy 0.30
angularmomentum 0 0 0 angularmomentum 0 0 0
@ -315,6 +331,7 @@ actor -3300 10 0 pizzeria
camdistance 50 camdistance 50
density 500 density 500
angularmomentum 0 0 0.2 angularmomentum 0 0 0.2
pointofinterest yes
actor -100 63 -13 pizzasign actor -100 63 -13 pizzasign
name "Pizzeria Sign" name "Pizzeria Sign"
relativeto pizzeria relativeto pizzeria
@ -345,7 +362,7 @@ actor -3300 10 0 pizzeria
chatid SubduedClippy chatid SubduedClippy
pronoun it pronoun it
actor -45 -4 -4 suit actor -45 -4 -4 suitv1
relativeto pizzeria relativeto pizzeria
name "Nox" name "Nox"
chatid PizzaChef chatid PizzaChef
@ -360,7 +377,7 @@ actor -3300 10 0 pizzeria
angularmomentum 0 0 0 angularmomentum 0 0 0
pronoun he pronoun he
actor 60 -15 -40 suit actor 60 -15 -40 suitv1
relativeto player relativeto player
name Icarus name Icarus
id Icarus id Icarus
@ -371,17 +388,19 @@ actor 60 -15 -40 suit
angularmomentum 0.4 0.2 0.1 angularmomentum 0.4 0.2 0.1
rotationy 0.6 rotationy 0.6
rotationx 1 rotationx 1
pointofinterest yes
thrust 1.2 1 1 10 1.5 thrust 1.2 1 1 10 1.5
wants maxrotation 0.5 wants maxrotation 0.5
wants maxvelocity 0 wants maxvelocity 0
pronoun it pronoun it
actor -300 0 40 suit actor -300 0 40 suitv1
relativeto player relativeto player
id Drifter id Drifter
name "梓涵" name "梓涵"
chatid Drifter chatid Drifter
oxygen 0.08 oxygen 0.08
pointofinterest yes
scale 2 scale 2
collider handcrafted collider handcrafted
pronoun she pronoun she
@ -391,6 +410,7 @@ actor 100 -18000 2000 "orb_busstop"
id "busstop" id "busstop"
name "StarTrans Bus Stop: Serenity Station" name "StarTrans Bus Stop: Serenity Station"
scale 100 scale 100
pointofinterest yes
wants maxrotation 0 wants maxrotation 0
wants maxvelocity 0 wants maxvelocity 0
actor 120 864 150 clippy actor 120 864 150 clippy
@ -422,7 +442,7 @@ actor 100 -18000 2000 "orb_busstop"
name "Light Orb" name "Light Orb"
relativeto busstopclippy relativeto busstopclippy
light "47FF00" 1000000 light "47FF00" 1000000
actor 8 2 0 suit actor 8 2 0 suitv1
relativeto "busstopclippy" relativeto "busstopclippy"
name "Rudy" name "Rudy"
wants maxrotation 0.2 wants maxrotation 0.2
@ -438,6 +458,7 @@ actor -184971e3 149410e3 -134273e3 "orb_busstop"
id "busstop2" id "busstop2"
name "StarTrans Bus Station 'Oscillation Station'" name "StarTrans Bus Station 'Oscillation Station'"
scale 100 scale 100
pointofinterest yes
wants maxrotation 0 wants maxrotation 0
wants maxvelocity 0 wants maxvelocity 0
actor 120 864 150 clippy actor 120 864 150 clippy
@ -475,6 +496,7 @@ actor 27643e3 -44e3 -124434e3 "orb_busstop"
id "busstop3" id "busstop3"
name "StarTrans Bus Station 'Metis Prime'" name "StarTrans Bus Station 'Metis Prime'"
scale 100 scale 100
pointofinterest yes
wants maxrotation 0 wants maxrotation 0
wants maxvelocity 0 wants maxvelocity 0
actor 120 864 150 clippy actor 120 864 150 clippy
@ -515,6 +537,7 @@ actor 110 -2000 0 whale
density 100000 density 100000
camdistance 4000 camdistance 4000
scale 300 scale 300
pointofinterest yes
angularmomentum 0 0.015 0 angularmomentum 0 0.015 0
thrust 2.45 0.48 0.33 1000000000000000 3 thrust 2.45 0.48 0.33 1000000000000000 3
engine ion engine ion

View file

@ -1,3 +1,15 @@
// ▄████████▄ + ███ + ▄█████████ ███ +
// ███▀ ▀███ + + ███ ███▀ + ███ + +
// ███ + ███ ███ ███ █████████ ███ ███ ███ ███
// ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
// ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
// ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
// ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
// + + + ███
// + ▀████████████████████████████████████████████████████▀
//
// This module manages visual effects.
use bevy::prelude::*; use bevy::prelude::*;
use crate::{camera, var}; use crate::{camera, var};

View file

@ -1,4 +1,16 @@
use crate::{actor, audio, camera, chat, nature, var, world}; // ▄████████▄ + ███ + ▄█████████ ███ +
// ███▀ ▀███ + + ███ ███▀ + ███ + +
// ███ + ███ ███ ███ █████████ ███ ███ ███ ███
// ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
// ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
// ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
// ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
// + + + ███
// + ▀████████████████████████████████████████████████████▀
//
// This module manages the heads-up display and augmented reality overlays.
use crate::{actor, audio, camera, chat, nature, skeleton, var};
use bevy::prelude::*; use bevy::prelude::*;
use bevy::diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin}; use bevy::diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin};
use bevy::transform::TransformSystem; use bevy::transform::TransformSystem;
@ -29,8 +41,12 @@ impl Plugin for HudPlugin {
handle_target_event, handle_target_event,
)); ));
app.add_systems(PostUpdate, ( app.add_systems(PostUpdate, (
update_overlay_visibility,
update_ar_overlays update_ar_overlays
.after(world::position_to_transform) .after(actor::position_to_transform)
.in_set(sync::SyncSet::PositionToTransform),
update_poi_overlays
.after(actor::position_to_transform)
.in_set(sync::SyncSet::PositionToTransform), .in_set(sync::SyncSet::PositionToTransform),
update_target_selectagon update_target_selectagon
.after(PhysicsSet::Sync) .after(PhysicsSet::Sync)
@ -47,19 +63,23 @@ impl Plugin for HudPlugin {
app.insert_resource(FPSUpdateTimer( app.insert_resource(FPSUpdateTimer(
Timer::from_seconds(HUD_REFRESH_TIME, TimerMode::Repeating))); Timer::from_seconds(HUD_REFRESH_TIME, TimerMode::Repeating)));
app.add_event::<TargetEvent>(); app.add_event::<TargetEvent>();
app.add_event::<UpdateOverlayVisibility>();
} }
} }
#[derive(Event)] pub struct TargetEvent(pub Option<Entity>); #[derive(Event)] pub struct TargetEvent(pub Option<Entity>);
#[derive(Event)] pub struct UpdateOverlayVisibility;
#[derive(Component)] struct NodeHud; #[derive(Component)] struct NodeHud;
#[derive(Component)] struct NodeConsole; #[derive(Component)] struct NodeConsole;
#[derive(Component)] struct NodeChoiceText; #[derive(Component)] struct NodeChoiceText;
#[derive(Component)] struct NodeCurrentChatLine; #[derive(Component)] struct NodeCurrentChatLine;
#[derive(Component)] struct Reticule; #[derive(Component)] struct Reticule;
#[derive(Component)] struct ToggleableHudElement; #[derive(Component)] pub struct ToggleableHudElement;
#[derive(Component)] pub struct ToggleableHudMapElement;
#[derive(Component)] struct OnlyHideWhenTogglingHud; #[derive(Component)] struct OnlyHideWhenTogglingHud;
#[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(Resource)] #[derive(Resource)]
pub struct AugmentedRealityState { pub struct AugmentedRealityState {
@ -338,16 +358,16 @@ fn setup(
}); });
// Selectagon // Selectagon
commands.spawn(( let mut entitycmd = commands.spawn((
Selectagon, Selectagon,
ToggleableHudElement, ToggleableHudElement,
OnlyHideWhenTogglingHud, OnlyHideWhenTogglingHud,
SceneBundle { SpatialBundle {
scene: asset_server.load(world::asset_name_to_path("selectagon")),
visibility: Visibility::Hidden, visibility: Visibility::Hidden,
..default() ..default()
}, },
)); ));
skeleton::load("selectagon", &mut entitycmd, &*asset_server);
// AR-related things // AR-related things
ambient_light.brightness = if settings.hud_active { ambient_light.brightness = if settings.hud_active {
@ -626,10 +646,10 @@ fn handle_input(
mouse_input: Res<ButtonInput<MouseButton>>, mouse_input: Res<ButtonInput<MouseButton>>,
mut settings: ResMut<var::Settings>, mut settings: ResMut<var::Settings>,
mut log: ResMut<Log>, mut log: ResMut<Log>,
mut q_hud: Query<(&mut Visibility, Option<&OnlyHideWhenTogglingHud>), With<ToggleableHudElement>>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>, mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_togglemusic: EventWriter<audio::ToggleMusicEvent>, mut ew_togglemusic: EventWriter<audio::ToggleMusicEvent>,
mut ew_target: EventWriter<TargetEvent>, mut ew_target: EventWriter<TargetEvent>,
mut ew_updateoverlays: EventWriter<UpdateOverlayVisibility>,
mut ambient_light: ResMut<AmbientLight>, mut ambient_light: ResMut<AmbientLight>,
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>>,
@ -640,21 +660,12 @@ fn handle_input(
} }
} }
if keyboard_input.just_pressed(settings.key_togglehud) { if keyboard_input.just_pressed(settings.key_togglehud) {
ew_updateoverlays.send(UpdateOverlayVisibility);
settings.hud_active ^= true;
if settings.hud_active { if settings.hud_active {
for (mut hudelement_visibility, _) in q_hud.iter_mut() {
*hudelement_visibility = Visibility::Hidden;
}
settings.hud_active = false;
ambient_light.brightness = AMBIENT_LIGHT;
}
else {
for (mut hudelement_visibility, only_hide) in q_hud.iter_mut() {
if only_hide.is_none() {
*hudelement_visibility = Visibility::Inherited;
}
}
settings.hud_active = true;
ambient_light.brightness = AMBIENT_LIGHT_AR; ambient_light.brightness = AMBIENT_LIGHT_AR;
} else {
ambient_light.brightness = AMBIENT_LIGHT;
} }
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Switch)); ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Switch));
ew_togglemusic.send(audio::ToggleMusicEvent()); ew_togglemusic.send(audio::ToggleMusicEvent());
@ -770,3 +781,51 @@ fn update_ar_overlays (
} }
} }
} }
fn update_poi_overlays (
mut q_marker: Query<(&mut Transform, &PointOfInterestMarker)>,
q_parent: Query<&Transform, Without<PointOfInterestMarker>>,
q_camera: Query<&Transform, (With<Camera>, Without<PointOfInterestMarker>)>,
settings: ResMut<var::Settings>,
) {
if !settings.hud_active || !settings.map_active || q_camera.is_empty() {
return;
}
let camera_trans = q_camera.get_single().unwrap();
for (mut trans, marker) in &mut q_marker {
if let Ok(parent_trans) = q_parent.get(marker.0) {
// Enlarge POI marker to a minimum angular diameter
trans.translation = parent_trans.translation;
trans.scale = Vec3::splat(1.0);
let (angular_diameter, _, _) = camera::calc_angular_diameter(
&trans, camera_trans);
let min_angular_diameter = 3.0f32.to_radians();
if angular_diameter < min_angular_diameter {
trans.scale *= min_angular_diameter / angular_diameter;
}
trans.look_at(camera_trans.translation, camera_trans.up().into());
}
}
}
fn update_overlay_visibility(
mut q_marker: Query<&mut Visibility, With<PointOfInterestMarker>>,
mut q_hudelement: Query<&mut Visibility, (With<ToggleableHudElement>, Without<PointOfInterestMarker>)>,
er_target: EventReader<UpdateOverlayVisibility>,
settings: Res<var::Settings>,
) {
if er_target.is_empty() {
return;
}
let check = {|check: bool|
if check { Visibility::Inherited } else { Visibility::Hidden }
};
let show_poi = check(settings.hud_active && settings.map_active);
let show_hud = check(settings.hud_active);
for mut vis in &mut q_marker {
*vis = show_poi;
}
for mut vis in &mut q_hudelement {
*vis = show_hud;
}
}

View file

@ -1,3 +1,16 @@
// ▄████████▄ + ███ + ▄█████████ ███ +
// ███▀ ▀███ + + ███ ███▀ + ███ + +
// ███ + ███ ███ ███ █████████ ███ ███ ███ ███
// ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
// ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
// ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
// ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
// + + + ███
// + ▀████████████████████████████████████████████████████▀
//
// This module initializes the game, handles command-line arguments,
// and manages window-related key bindings.
mod actor; mod actor;
mod audio; mod audio;
mod camera; mod camera;
@ -6,6 +19,7 @@ mod commands;
mod effects; mod effects;
mod hud; mod hud;
mod shading; mod shading;
mod skeleton;
mod var; mod var;
mod world; mod world;
@ -15,7 +29,6 @@ mod nature;
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_embedded_assets::{EmbeddedAssetPlugin, PluginMode};
use bevy::pbr::ExtendedMaterial; use bevy::pbr::ExtendedMaterial;
use std::env; use std::env;
@ -32,15 +45,12 @@ fn main() {
return; return;
} }
} }
if cfg!(debug_assertions) { let mut app = App::new();
App::new().add_plugins(OutFlyPlugin).run(); #[cfg(feature = "embed_assets")]
} else { app.add_plugins(bevy_embedded_assets::EmbeddedAssetPlugin {
// In release builds, embed assets into the binary mode: bevy_embedded_assets::PluginMode::ReplaceDefault
App::new().add_plugins(( });
EmbeddedAssetPlugin { mode: PluginMode::ReplaceDefault }, app.add_plugins(OutFlyPlugin).run();
OutFlyPlugin,
)).run();
}
} }
pub struct OutFlyPlugin; pub struct OutFlyPlugin;
@ -63,6 +73,7 @@ impl Plugin for OutFlyPlugin {
effects::EffectsPlugin, effects::EffectsPlugin,
hud::HudPlugin, hud::HudPlugin,
shading::ShadingPlugin, shading::ShadingPlugin,
skeleton::SkeletonPlugin,
world::WorldPlugin, world::WorldPlugin,
)); ));
} }

View file

@ -1,6 +1,14 @@
// This stuff here, this stuff is messy. Nobody wants to deal with this, // ▄████████▄ + ███ + ▄█████████ ███ +
// nobody cares how it works, but I guess we need it as an ingredient for // ███▀ ▀███ + + ███ ███▀ + ███ + +
// the universe *sigh* so here we go. // ███ + ███ ███ ███ █████████ ███ ███ ███ ███
// ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
// ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
// ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
// ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
// + + + ███
// + ▀████████████████████████████████████████████████████▀
//
// This module manages the messy, impure parts of our universe.
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;

View file

@ -1,3 +1,15 @@
// ▄████████▄ + ███ + ▄█████████ ███ +
// ███▀ ▀███ + + ███ ███▀ + ███ + +
// ███ + ███ ███ ███ █████████ ███ ███ ███ ███
// ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
// ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
// ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
// ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
// + + + ███
// + ▀████████████████████████████████████████████████████▀
//
// This module manages graphics shaders.
use bevy::prelude::*; use bevy::prelude::*;
use bevy::render::render_resource::{AsBindGroup, ShaderRef}; use bevy::render::render_resource::{AsBindGroup, ShaderRef};
use bevy::pbr::{ExtendedMaterial, MaterialExtension, OpaqueRendererMethod}; use bevy::pbr::{ExtendedMaterial, MaterialExtension, OpaqueRendererMethod};

329
src/skeleton.rs Normal file
View file

@ -0,0 +1,329 @@
// ▄████████▄ + ███ + ▄█████████ ███ +
// ███▀ ▀███ + + ███ ███▀ + ███ + +
// ███ + ███ ███ ███ █████████ ███ ███ ███ ███
// ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
// ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
// ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
// ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
// + + + ███
// + ▀████████████████████████████████████████████████████▀
//
// 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);
}
}
pub fn asset_name_to_path(name: &str) -> &'static str {
match name {
"suit_ar_chefhat" => "models/suit_v1/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));
}
}
//pub fn load_scene(
// path: &str,
// asset_server: &AssetServer
//) -> Handle<Scene> {
// load_scene_by_path(asset_name_to_path(path), 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,
),
_ => {},
}
}

View file

@ -1,3 +1,16 @@
// ▄████████▄ + ███ + ▄█████████ ███ +
// ███▀ ▀███ + + ███ ███▀ + ███ + +
// ███ + ███ ███ ███ █████████ ███ ███ ███ ███
// ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
// ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
// ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
// ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
// + + + ███
// + ▀████████████████████████████████████████████████████▀
//
// This module manages variables, settings, as well as evaluating
// "if"-conditions in chats.
use bevy::prelude::*; use bevy::prelude::*;
use std::collections::HashMap; use std::collections::HashMap;
use std::env; use std::env;

View file

@ -1,10 +1,21 @@
use crate::{actor, audio, hud, nature, shading, var}; // ▄████████▄ + ███ + ▄█████████ ███ +
// ███▀ ▀███ + + ███ ███▀ + ███ + +
// ███ + ███ ███ ███ █████████ ███ ███ ███ ███
// ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
// ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
// ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
// ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
// + + + ███
// + ▀████████████████████████████████████████████████████▀
//
// This module populates the world with stars and asteroids.
use crate::{actor, hud, nature, shading, skeleton};
use bevy::prelude::*; use bevy::prelude::*;
use bevy::math::{DVec3, I64Vec3}; use bevy::math::{DVec3, I64Vec3};
use bevy::scene::{InstanceId, SceneInstance}; use bevy::scene::{InstanceId, SceneInstance};
use bevy::render::mesh::Indices; use bevy::render::mesh::Indices;
use bevy_xpbd_3d::prelude::*; use bevy_xpbd_3d::prelude::*;
use bevy_xpbd_3d::plugins::sync;
use std::collections::HashMap; use std::collections::HashMap;
use std::f32::consts::PI; use std::f32::consts::PI;
use fastrand; use fastrand;
@ -14,44 +25,18 @@ const ASTEROID_SIZE_FACTOR: f32 = 10.0;
const RING_THICKNESS: f64 = 8.0e6; const RING_THICKNESS: f64 = 8.0e6;
const STARS_MAX_MAGNITUDE: f32 = 5.5; // max 7.0, see generate_starchart.py const STARS_MAX_MAGNITUDE: f32 = 5.5; // max 7.0, see generate_starchart.py
const CENTER_WORLD_ON_PLAYER: bool = true;
const SKYBOX: bool = false; const SKYBOX: bool = false;
const ASTEROID_SPAWN_STEP: f64 = 500.0; const ASTEROID_SPAWN_STEP: f64 = 500.0;
const ASTEROID_VIEW_RADIUS: f64 = 3000.0; const ASTEROID_VIEW_RADIUS: f64 = 3000.0;
const ASSET_ASTEROID1: &str = "models/asteroid.glb#Scene0"; const ASSET_NAME_ASTEROID1: &str = "asteroid1";
const ASSET_ASTEROID2: &str = "models/asteroid2.glb#Scene0"; const ASSET_NAME_ASTEROID2: &str = "asteroid2";
pub fn asset_name_to_path(name: &str) -> &'static str {
match name {
"suit" => "models/suit_with_collider.glb#Scene0",
"suit_ar_chefhat" => "models/suit_ar_chefhat.glb#Scene0",
"asteroid1" => ASSET_ASTEROID1,
"asteroid2" => ASSET_ASTEROID2,
"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.glb#Scene0",
"clippy_ar" => "models/clippy_ar.glb#Scene0",
"whale" => "models/whale.glb#Scene0",
_ => "models/error.glb#Scene0",
}
}
pub struct WorldPlugin; pub struct WorldPlugin;
impl Plugin for WorldPlugin { impl Plugin for WorldPlugin {
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_cheats);
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_plugins(PhysicsPlugins::default()); app.add_plugins(PhysicsPlugins::default());
@ -61,18 +46,6 @@ impl Plugin for WorldPlugin {
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::<DespawnEvent>();
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));
}
} }
} }
@ -104,12 +77,7 @@ pub fn setup(
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<shading::SkyBox>>,
asset_server: Res<AssetServer>,
) { ) {
// Load assets
commands.insert_resource(AsteroidModel1(asset_server.load(ASSET_ASTEROID1)));
commands.insert_resource(AsteroidModel2(asset_server.load(ASSET_ASTEROID2)));
// 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));
let mut starcount = 0; let mut starcount = 0;
@ -204,11 +172,10 @@ fn spawn_despawn_asteroids(
q_player: Query<&Position, With<actor::PlayerCamera>>, q_player: Query<&Position, With<actor::PlayerCamera>>,
mut ew_despawn: EventWriter<DespawnEvent>, mut ew_despawn: EventWriter<DespawnEvent>,
mut db: ResMut<ActiveAsteroids>, mut db: ResMut<ActiveAsteroids>,
asteroid1_handle: Res<AsteroidModel1>,
asteroid2_handle: Res<AsteroidModel2>,
mut q_asteroid: Query<(Entity, &SceneInstance), With<Asteroid>>, mut q_asteroid: Query<(Entity, &SceneInstance), With<Asteroid>>,
mut last_player_cell: Local<I64Vec3>, mut last_player_cell: Local<I64Vec3>,
id2pos: Res<actor::Id2Pos>, id2pos: Res<actor::Id2Pos>,
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() {
//if q_player.is_empty() { //if q_player.is_empty() {
@ -345,17 +312,17 @@ fn spawn_despawn_asteroids(
DespawnOnPlayerDeath, DespawnOnPlayerDeath,
)); ));
let model = match class { let model = match class {
0 => asteroid1_handle.0.clone(), 0 => ASSET_NAME_ASTEROID1,
_ => asteroid2_handle.0.clone(), _ => ASSET_NAME_ASTEROID2,
}; };
entity_commands.insert(SceneBundle { entity_commands.insert(SpatialBundle {
scene: model,
transform: Transform { transform: Transform {
scale: Vec3::splat(size), scale: Vec3::splat(size),
..default() ..default()
}, },
..default() ..default()
}); });
skeleton::load(model, &mut entity_commands, &*asset_server);
db.0.insert(origin, AsteroidData { db.0.insert(origin, AsteroidData {
entity: entity_commands.id(), entity: entity_commands.id(),
//viewdistance: 99999999.0, //viewdistance: 99999999.0,
@ -377,109 +344,3 @@ fn handle_despawn(
db.0.remove(&despawn.origin); db.0.remove(&despawn.origin);
} }
} }
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(&"busstop2".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(&"busstop3".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();
}
}
}