Compare commits

...

44 Commits

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

View File

@ -1,3 +1,13 @@
# ▄████████▄ + ███ + ▄█████████ ███ +
# ███▀ ▀███ + + ███ ███▀ + ███ + +
# ███ + ███ ███ ███ █████████ ███ ███ ███ ███
# ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
# ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
# ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
# ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
# + + + ███
# + ▀████████████████████████████████████████████████████▀
[package]
name = "outfly"
version = "0.7.3"
@ -14,7 +24,7 @@ build = "build/build.rs"
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_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"
serde = "1.0"
serde_yaml = "0.9"
@ -28,6 +38,7 @@ dev = ["bevy/dynamic_linking", "bevy/file_watcher"]
wasm = ["bevy/webgl2"]
x11 = ["bevy/x11"]
wayland = ["bevy/wayland"]
embed_assets = ["dep:bevy_embedded_assets"]
[profile.dev]
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.
@ -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.
Key features:
![screenshot](doc/images/screenshot3.jpg)
# Features
- Open source forever
- 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)
- Status: Early access, not much content
Links:
- [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
# Controls
- F1: Show key bindings
- Space: Slow down (or match velocity)
@ -34,19 +41,19 @@ Links:
- R: Rotate (hold & move mouse)
- E: Interact: Talk to people, enter vehicles
- Q: Exit vehicle
- F7: Restart game
- JKULIO: Mouseless camera rotation
- Augmented Reality: (toggle with Tab)
- Left click: Target objects
- Right click: Zoom
- Settings
- Tab: Toggle HUD/AR
- F11: Toggle fullscreen
- F: Toggle 3rd person view
- M: Toggle map
- F: Toggle 3rd person view
- Y: Toggle rotation stabilizer
- F4: Toggle music
- 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
@ -54,35 +61,45 @@ Links:
- C: Impossibly instant stopping
- X: Teleport to target
# System Requirements
# Running OutFly
## System Requirements
- Screen, keyboard
- Operating System: Linux, Windows, MacOS
- Ideally, a graphics card with Vulkan support
- Screen/keyboard/mouse
- Operating System: Linux, Windows, Mac
- 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
or
WGPU_BACKEND=gl cargo run
```
However, this may result in poor performance and visual glitches.
# 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:
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:
```
cd [path-to-extracted-outfly-directory]
./outfly
yay -S outfly-git
```
## 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
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.0: First release with basic controls, HUD, sounds, skybox, sun
# Credits and License
# Credits
- Source code: GPL Version 3.0
- 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::{
mesh_view_bindings::globals,
forward_io::VertexOutput,

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +1,22 @@
#!/bin/sh
# ▄████████▄ + ███ + ▄█████████ ███ +
# ███▀ ▀███ + + ███ ███▀ + ███ + +
# ███ + ███ ███ ███ █████████ ███ ███ ███ ███
# ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
# ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
# ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
# ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
# + + + ███
# + ▀████████████████████████████████████████████████████▀
#
# 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
if [ "$1" == "-b" ]; then
cargo build --release --target=x86_64-unknown-linux-gnu --features "x11 wayland"
cargo build --release --target=x86_64-pc-windows-gnu --no-default-features
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 --features "embed_assets"
fi
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_xpbd_3d::prelude::*;
use bevy_xpbd_3d::plugins::sync;
use bevy::scene::SceneInstance;
use bevy::math::DVec3;
use crate::{actor, audio, camera, chat, commands, effects, hud, nature, var, world};
use std::collections::HashMap;
const CENTER_WORLD_ON_PLAYER: bool = true;
pub const ENGINE_SPEED_FACTOR: f32 = 30.0;
const MAX_TRANSMISSION_DISTANCE: f32 = 100.0;
const MAX_INTERACT_DISTANCE: f32 = 50.0;
@ -25,6 +43,7 @@ impl Plugin for ActorPlugin {
handle_input,
handle_collisions,
handle_damage,
handle_cheats,
));
app.add_systems(PostUpdate, (
handle_vehicle_enter_exit,
@ -33,6 +52,18 @@ impl Plugin for ActorPlugin {
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));
}
}
}
@ -538,3 +569,109 @@ fn update_id2pos(
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::audio::{PlaybackMode, Volume};
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::input::mouse::{MouseMotion, MouseWheel};
use bevy::window::PrimaryWindow;
@ -183,6 +197,10 @@ pub fn update_map_camera(
if keyboard_input.pressed(settings.key_left) {
offset_z += 1.0;
}
if keyboard_input.pressed(settings.key_stop) {
mapcam.offset_x = 0.0;
mapcam.offset_z = 0.0;
}
// Update zoom level
if !mapcam.initialized {
@ -254,6 +272,7 @@ pub fn handle_input(
mut settings: ResMut<var::Settings>,
mut mapcam: ResMut<MapCam>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_updateoverlays: EventWriter<hud::UpdateOverlayVisibility>,
) {
if keyboard_input.just_pressed(settings.key_camera) {
settings.third_person ^= true;
@ -261,6 +280,7 @@ pub fn handle_input(
if keyboard_input.just_pressed(settings.key_map) {
settings.map_active ^= true;
*mapcam = MapCam::default();
ew_updateoverlays.send(hud::UpdateOverlayVisibility);
}
if keyboard_input.just_pressed(settings.key_rotation_stabilizer) {
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 bevy::prelude::*;
use bevy::math::DVec3;
@ -9,6 +22,7 @@ use std::collections::HashMap;
pub const CHATS: &[&str] = &[
include_str!("chats/serenity.yaml"),
include_str!("chats/startrans.yaml"),
include_str!("chats/thebe.yaml"),
];
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
- 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
- if $eat:
- 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.
- goto: generic_questions
- 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
- system: "Error: No response"
- 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;
use bevy::prelude::*;
use bevy_xpbd_3d::prelude::*;
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 std::f32::consts::PI;
use std::f64::consts::PI as PI64;
@ -54,6 +65,7 @@ struct ParserState {
is_clickable: bool,
is_targeted_on_startup: bool,
is_sun: bool,
is_point_of_interest: bool,
has_physics: bool,
has_ring: bool,
wants_maxrotation: Option<f64>,
@ -103,6 +115,7 @@ impl Default for ParserState {
is_clickable: true,
is_targeted_on_startup: false,
is_sun: false,
is_point_of_interest: false,
has_physics: true,
has_ring: false,
wants_maxrotation: None,
@ -133,7 +146,7 @@ pub fn load_defs(
) {
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 defs_string = include_str!("defs.txt");
let defs_string = include_str!("data/defs.txt");
let mut lines = defs_string.lines();
let mut state = ParserState::default();
let mut command;
@ -252,6 +265,9 @@ pub fn load_defs(
["ring", "yes"] => {
state.has_ring = true;
}
["pointofinterest", "yes"] => {
state.is_point_of_interest = true;
}
["oxygen", amount] => {
if let Ok(amount) = amount.parse::<f32>() {
state.is_lifeform = true;
@ -523,14 +539,14 @@ fn spawn_entities(
..default()
});
} else if let Some(model) = &state.model {
actor.insert(SceneBundle {
actor.insert(SpatialBundle {
transform: Transform {
scale: Vec3::splat(state.model_scale),
..default()
},
scene: asset_server.load(world::asset_name_to_path(model.as_str())),
..default()
});
skeleton::load(model.as_str(), &mut actor, &*asset_server);
}
// Physics Parameters
@ -663,17 +679,30 @@ fn spawn_entities(
}
if let Some(ar_asset_name) = &state.ar_model {
commands.spawn((
let mut entitycmd = commands.spawn((
hud::AugmentedRealityOverlay {
owner: actor_entity,
},
world::DespawnOnPlayerDeath,
SceneBundle {
scene: asset_server.load(world::asset_name_to_path(ar_asset_name)),
SpatialBundle {
visibility: Visibility::Hidden,
..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 {
@ -698,19 +727,10 @@ fn spawn_entities(
}
}
pub fn hide_colliders(
//mut commands: Commands,
mut q_mesh: Query<(&mut Visibility, &Name), With<Handle<Mesh>>>,
//q_flag: Query<Entity, With<NeedsSceneColliderRemoved>>,
) {
pub fn hide_colliders(mut q_mesh: Query<(&mut Visibility, &Name), (Added<Visibility>, With<Handle<Mesh>>)>) {
for (mut visibility, name) in &mut q_mesh {
if name.as_str() == "Collider" {
*visibility = Visibility::Hidden;
}
}
// 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
physics off
actor 0 593051 0 suit
actor 0 593051 0 suitv1
relativeto jupiter
orbit 224000e3 0.66
player yes
@ -146,6 +146,7 @@ actor 10 -30 20 MeteorAceGT
camdistance 50
density 500
angularmomentum 0.1 0.1 0.3
pointofinterest yes
actor 0 0 0 io
name Io
@ -206,6 +207,20 @@ actor 0 0 0 moonlet
orbit 221900e3 0.66
scale 50e3
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
name Metis
@ -301,6 +316,7 @@ actor -3300 10 0 pizzeria
relativeto player
id pizzeria
scale 40
pointofinterest yes
collider mesh
rotationy 0.30
angularmomentum 0 0 0
@ -315,6 +331,7 @@ actor -3300 10 0 pizzeria
camdistance 50
density 500
angularmomentum 0 0 0.2
pointofinterest yes
actor -100 63 -13 pizzasign
name "Pizzeria Sign"
relativeto pizzeria
@ -345,7 +362,7 @@ actor -3300 10 0 pizzeria
chatid SubduedClippy
pronoun it
actor -45 -4 -4 suit
actor -45 -4 -4 suitv1
relativeto pizzeria
name "Nox"
chatid PizzaChef
@ -360,7 +377,7 @@ actor -3300 10 0 pizzeria
angularmomentum 0 0 0
pronoun he
actor 60 -15 -40 suit
actor 60 -15 -40 suitv1
relativeto player
name Icarus
id Icarus
@ -371,17 +388,19 @@ actor 60 -15 -40 suit
angularmomentum 0.4 0.2 0.1
rotationy 0.6
rotationx 1
pointofinterest yes
thrust 1.2 1 1 10 1.5
wants maxrotation 0.5
wants maxvelocity 0
pronoun it
actor -300 0 40 suit
actor -300 0 40 suitv1
relativeto player
id Drifter
name "梓涵"
chatid Drifter
oxygen 0.08
pointofinterest yes
scale 2
collider handcrafted
pronoun she
@ -391,6 +410,7 @@ actor 100 -18000 2000 "orb_busstop"
id "busstop"
name "StarTrans Bus Stop: Serenity Station"
scale 100
pointofinterest yes
wants maxrotation 0
wants maxvelocity 0
actor 120 864 150 clippy
@ -422,7 +442,7 @@ actor 100 -18000 2000 "orb_busstop"
name "Light Orb"
relativeto busstopclippy
light "47FF00" 1000000
actor 8 2 0 suit
actor 8 2 0 suitv1
relativeto "busstopclippy"
name "Rudy"
wants maxrotation 0.2
@ -438,6 +458,7 @@ actor -184971e3 149410e3 -134273e3 "orb_busstop"
id "busstop2"
name "StarTrans Bus Station 'Oscillation Station'"
scale 100
pointofinterest yes
wants maxrotation 0
wants maxvelocity 0
actor 120 864 150 clippy
@ -475,6 +496,7 @@ actor 27643e3 -44e3 -124434e3 "orb_busstop"
id "busstop3"
name "StarTrans Bus Station 'Metis Prime'"
scale 100
pointofinterest yes
wants maxrotation 0
wants maxvelocity 0
actor 120 864 150 clippy
@ -515,6 +537,7 @@ actor 110 -2000 0 whale
density 100000
camdistance 4000
scale 300
pointofinterest yes
angularmomentum 0 0.015 0
thrust 2.45 0.48 0.33 1000000000000000 3
engine ion

View File

@ -1,3 +1,15 @@
// ▄████████▄ + ███ + ▄█████████ ███ +
// ███▀ ▀███ + + ███ ███▀ + ███ + +
// ███ + ███ ███ ███ █████████ ███ ███ ███ ███
// ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
// ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
// ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
// ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
// + + + ███
// + ▀████████████████████████████████████████████████████▀
//
// This module manages visual effects.
use bevy::prelude::*;
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::diagnostic::{DiagnosticsStore, FrameTimeDiagnosticsPlugin};
use bevy::transform::TransformSystem;
@ -29,8 +41,12 @@ impl Plugin for HudPlugin {
handle_target_event,
));
app.add_systems(PostUpdate, (
update_overlay_visibility,
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),
update_target_selectagon
.after(PhysicsSet::Sync)
@ -47,19 +63,23 @@ impl Plugin for HudPlugin {
app.insert_resource(FPSUpdateTimer(
Timer::from_seconds(HUD_REFRESH_TIME, TimerMode::Repeating)));
app.add_event::<TargetEvent>();
app.add_event::<UpdateOverlayVisibility>();
}
}
#[derive(Event)] pub struct TargetEvent(pub Option<Entity>);
#[derive(Event)] pub struct UpdateOverlayVisibility;
#[derive(Component)] struct NodeHud;
#[derive(Component)] struct NodeConsole;
#[derive(Component)] struct NodeChoiceText;
#[derive(Component)] struct NodeCurrentChatLine;
#[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 Selectagon;
#[derive(Component)] pub struct IsTargeted;
#[derive(Component)] pub struct PointOfInterestMarker(pub Entity);
#[derive(Resource)]
pub struct AugmentedRealityState {
@ -338,16 +358,16 @@ fn setup(
});
// Selectagon
commands.spawn((
let mut entitycmd = commands.spawn((
Selectagon,
ToggleableHudElement,
OnlyHideWhenTogglingHud,
SceneBundle {
scene: asset_server.load(world::asset_name_to_path("selectagon")),
SpatialBundle {
visibility: Visibility::Hidden,
..default()
},
));
skeleton::load("selectagon", &mut entitycmd, &*asset_server);
// AR-related things
ambient_light.brightness = if settings.hud_active {
@ -626,10 +646,10 @@ fn handle_input(
mouse_input: Res<ButtonInput<MouseButton>>,
mut settings: ResMut<var::Settings>,
mut log: ResMut<Log>,
mut q_hud: Query<(&mut Visibility, Option<&OnlyHideWhenTogglingHud>), With<ToggleableHudElement>>,
mut ew_sfx: EventWriter<audio::PlaySfxEvent>,
mut ew_togglemusic: EventWriter<audio::ToggleMusicEvent>,
mut ew_target: EventWriter<TargetEvent>,
mut ew_updateoverlays: EventWriter<UpdateOverlayVisibility>,
mut ambient_light: ResMut<AmbientLight>,
q_objects: Query<(Entity, &Transform), (With<IsClickable>, Without<IsTargeted>, Without<actor::PlayerDrivesThis>, Without<actor::Player>)>,
q_camera: Query<&Transform, With<Camera>>,
@ -640,21 +660,12 @@ fn handle_input(
}
}
if keyboard_input.just_pressed(settings.key_togglehud) {
ew_updateoverlays.send(UpdateOverlayVisibility);
settings.hud_active ^= true;
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;
} else {
ambient_light.brightness = AMBIENT_LIGHT;
}
ew_sfx.send(audio::PlaySfxEvent(audio::Sfx::Switch));
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 audio;
mod camera;
@ -6,6 +19,7 @@ mod commands;
mod effects;
mod hud;
mod shading;
mod skeleton;
mod var;
mod world;
@ -15,7 +29,6 @@ mod nature;
use bevy::window::{Window, WindowMode, PrimaryWindow, CursorGrabMode};
use bevy::diagnostic::FrameTimeDiagnosticsPlugin;
use bevy::prelude::*;
use bevy_embedded_assets::{EmbeddedAssetPlugin, PluginMode};
use bevy::pbr::ExtendedMaterial;
use std::env;
@ -32,15 +45,12 @@ fn main() {
return;
}
}
if cfg!(debug_assertions) {
App::new().add_plugins(OutFlyPlugin).run();
} else {
// In release builds, embed assets into the binary
App::new().add_plugins((
EmbeddedAssetPlugin { mode: PluginMode::ReplaceDefault },
OutFlyPlugin,
)).run();
}
let mut app = App::new();
#[cfg(feature = "embed_assets")]
app.add_plugins(bevy_embedded_assets::EmbeddedAssetPlugin {
mode: bevy_embedded_assets::PluginMode::ReplaceDefault
});
app.add_plugins(OutFlyPlugin).run();
}
pub struct OutFlyPlugin;
@ -63,6 +73,7 @@ impl Plugin for OutFlyPlugin {
effects::EffectsPlugin,
hud::HudPlugin,
shading::ShadingPlugin,
skeleton::SkeletonPlugin,
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 OXY_S: f32 = OXYGEN_USE_KG_PER_S;

View File

@ -1,3 +1,15 @@
// ▄████████▄ + ███ + ▄█████████ ███ +
// ███▀ ▀███ + + ███ ███▀ + ███ + +
// ███ + ███ ███ ███ █████████ ███ ███ ███ ███
// ███ +███ ███ ███ ███ ███▐██████ ███ ███ ███
// ███ + ███ ███+ ███ +███ ███ + ███ ███ + ███
// ███▄ ▄███ ███▄ ███ ███ + ███ + ███ ███▄ ███
// ▀████████▀ + ▀███████ ███▄ ███▄ ▀████ ▀███████
// + + + ███
// + ▀████████████████████████████████████████████████████▀
//
// This module manages graphics shaders.
use bevy::prelude::*;
use bevy::render::render_resource::{AsBindGroup, ShaderRef};
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 std::collections::HashMap;
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::math::{DVec3, I64Vec3};
use bevy::scene::{InstanceId, SceneInstance};
use bevy::render::mesh::Indices;
use bevy_xpbd_3d::prelude::*;
use bevy_xpbd_3d::plugins::sync;
use std::collections::HashMap;
use std::f32::consts::PI;
use fastrand;
@ -14,44 +25,18 @@ const ASTEROID_SIZE_FACTOR: f32 = 10.0;
const RING_THICKNESS: f64 = 8.0e6;
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 ASTEROID_SPAWN_STEP: f64 = 500.0;
const ASTEROID_VIEW_RADIUS: f64 = 3000.0;
const ASSET_ASTEROID1: &str = "models/asteroid.glb#Scene0";
const ASSET_ASTEROID2: &str = "models/asteroid2.glb#Scene0";
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",
}
}
const ASSET_NAME_ASTEROID1: &str = "asteroid1";
const ASSET_NAME_ASTEROID2: &str = "asteroid2";
pub struct WorldPlugin;
impl Plugin for WorldPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, setup);
app.add_systems(Update, handle_cheats);
app.add_systems(PostUpdate, handle_despawn);
app.add_systems(Update, spawn_despawn_asteroids);
app.add_plugins(PhysicsPlugins::default());
@ -61,18 +46,6 @@ impl Plugin for WorldPlugin {
Timer::from_seconds(ASTEROID_UPDATE_INTERVAL, TimerMode::Repeating)));
app.insert_resource(ActiveAsteroids(HashMap::new()));
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 materials: ResMut<Assets<StandardMaterial>>,
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
let sphere_handle = meshes.add(Sphere::new(1.0).mesh().uv(16, 16));
let mut starcount = 0;
@ -204,11 +172,10 @@ fn spawn_despawn_asteroids(
q_player: Query<&Position, With<actor::PlayerCamera>>,
mut ew_despawn: EventWriter<DespawnEvent>,
mut db: ResMut<ActiveAsteroids>,
asteroid1_handle: Res<AsteroidModel1>,
asteroid2_handle: Res<AsteroidModel2>,
mut q_asteroid: Query<(Entity, &SceneInstance), With<Asteroid>>,
mut last_player_cell: Local<I64Vec3>,
id2pos: Res<actor::Id2Pos>,
asset_server: Res<AssetServer>,
) {
if !timer.0.tick(time.delta()).just_finished() || q_player.is_empty() {
//if q_player.is_empty() {
@ -345,17 +312,17 @@ fn spawn_despawn_asteroids(
DespawnOnPlayerDeath,
));
let model = match class {
0 => asteroid1_handle.0.clone(),
_ => asteroid2_handle.0.clone(),
0 => ASSET_NAME_ASTEROID1,
_ => ASSET_NAME_ASTEROID2,
};
entity_commands.insert(SceneBundle {
scene: model,
entity_commands.insert(SpatialBundle {
transform: Transform {
scale: Vec3::splat(size),
..default()
},
..default()
});
skeleton::load(model, &mut entity_commands, &*asset_server);
db.0.insert(origin, AsteroidData {
entity: entity_commands.id(),
//viewdistance: 99999999.0,
@ -377,109 +344,3 @@ fn handle_despawn(
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();
}
}
}