Compare commits

...

23 commits

Author SHA1 Message Date
7317f7514c
cargofmt; uncommitted balancing
Some checks failed
Rust / rustfmt (push) Successful in 21s
Rust / clippy (push) Failing after 1m29s
Rust / build (push) Successful in 2m36s
2024-07-08 12:56:27 +02:00
173410ef4e balancing
Some checks failed
Rust / rustfmt (push) Failing after 26s
Rust / clippy (push) Failing after 1m49s
Rust / build (push) Failing after 4m1s
2024-07-07 18:49:26 +02:00
e9dd691627
adjust text
Some checks failed
Rust / build (push) Waiting to run
Rust / rustfmt (push) Has been cancelled
Rust / clippy (push) Has been cancelled
2024-07-07 18:49:13 +02:00
23ba11596e blue tint (#22)
Some checks failed
Rust / clippy (push) Waiting to run
Rust / build (push) Waiting to run
Rust / rustfmt (push) Has been cancelled
Reviewed-on: #22
Co-authored-by: Glaeder <niklas@vousten.dev>
Co-committed-by: Glaeder <niklas@vousten.dev>
2024-07-07 16:49:04 +00:00
24db0b0e0f add gameover (#20)
Some checks failed
Rust / rustfmt (push) Successful in 37s
Rust / clippy (push) Failing after 1m38s
Rust / build (push) Has been cancelled
Reviewed-on: #20
Co-authored-by: luckyturtledev <git@lukas1818.de>
Co-committed-by: luckyturtledev <git@lukas1818.de>
2024-07-07 16:46:28 +00:00
2d0b613df1 attack-human (#21)
Some checks failed
Rust / rustfmt (push) Successful in 28s
Rust / clippy (push) Failing after 1m34s
Rust / build (push) Successful in 3m18s
Reviewed-on: #21
Co-authored-by: Glaeder <niklas@vousten.dev>
Co-committed-by: Glaeder <niklas@vousten.dev>
2024-07-07 16:24:01 +00:00
6115ef4aa2
Play background sound (#19)
All checks were successful
Rust / rustfmt (push) Successful in 25s
Rust / clippy (push) Successful in 2m33s
Rust / build (push) Successful in 3m44s
Reviewed-on: #19
Co-authored-by: Dominic <git@msrd0.de>
Co-committed-by: Dominic <git@msrd0.de>
2024-07-07 16:13:39 +00:00
8e04d74682
fix all warnings
All checks were successful
Rust / rustfmt (push) Successful in 21s
Rust / clippy (push) Successful in 1m28s
Rust / build (push) Successful in 2m16s
2024-07-07 17:55:22 +02:00
e5e2ccfc6b
CI: change folder structure
Some checks failed
Rust / rustfmt (push) Successful in 25s
Rust / clippy (push) Failing after 1m26s
Rust / build (push) Has been cancelled
2024-07-07 17:53:22 +02:00
b68b160d9f
CI: fix missing pipe
Some checks failed
Rust / rustfmt (push) Successful in 25s
Rust / clippy (push) Failing after 1m31s
Rust / build (push) Successful in 3m18s
2024-07-07 17:48:07 +02:00
d346d68aed
redistributable game
Some checks failed
Rust / rustfmt (push) Successful in 19s
Rust / clippy (push) Failing after 1m29s
Rust / build (push) Successful in 2m8s
2024-07-07 17:44:17 +02:00
28655cc819
overworld human icon
Some checks failed
Rust / rustfmt (push) Successful in 26s
Rust / clippy (push) Failing after 1m32s
Rust / build (push) Successful in 3m14s
2024-07-07 17:39:06 +02:00
ac21220dea
Upload human artifacts from @librico
Some checks failed
Rust / rustfmt (push) Successful in 20s
Rust / clippy (push) Failing after 1m28s
Rust / build (push) Successful in 2m14s
2024-07-07 17:29:34 +02:00
0c9624d511
Houses have energy that interact with ghosts (#18)
Some checks failed
Rust / rustfmt (push) Successful in 24s
Rust / clippy (push) Failing after 2m32s
Rust / build (push) Successful in 3m40s
Reviewed-on: #18
Co-authored-by: Dominic <git@msrd0.de>
Co-committed-by: Dominic <git@msrd0.de>
2024-07-07 15:18:32 +00:00
2354e34142 energie lost overworld
Some checks failed
Rust / rustfmt (push) Failing after 24s
Rust / clippy (push) Failing after 1m26s
Rust / build (push) Successful in 3m3s
2024-07-07 16:41:44 +02:00
b9e065058f
fix camera inside houses
Some checks failed
Rust / rustfmt (push) Failing after 20s
Rust / clippy (push) Failing after 1m26s
Rust / build (push) Successful in 2m9s
2024-07-07 16:32:07 +02:00
Fredi
3816dbac77 Merge branch 'room-types'
Some checks failed
Rust / rustfmt (push) Failing after 24s
Rust / clippy (push) Failing after 1m27s
Rust / build (push) Successful in 3m1s
2024-07-07 16:20:20 +02:00
Fredi
84030e5b6c Living room 2024-07-07 16:19:57 +02:00
a6d9e088ef room-creation (#15)
Some checks failed
Rust / rustfmt (push) Successful in 25s
Rust / clippy (push) Failing after 2m29s
Rust / build (push) Successful in 3m29s
Co-authored-by: Dominic <git@msrd0.de>
Co-authored-by: Fredi <fredrik.konrad@rwth-aachen.de>
Reviewed-on: #15
Co-authored-by: Glaeder <niklas@vousten.dev>
Co-committed-by: Glaeder <niklas@vousten.dev>
2024-07-07 14:13:52 +00:00
fa990bfadb improve movement at overworld (#16)
Some checks failed
Rust / rustfmt (push) Successful in 25s
Rust / clippy (push) Failing after 1m30s
Rust / build (push) Successful in 3m8s
Reviewed-on: #16
Co-authored-by: luckyturtledev <git@lukas1818.de>
Co-committed-by: luckyturtledev <git@lukas1818.de>
2024-07-07 14:03:49 +00:00
fe6651d573 add option to turn devtools off (#17)
Some checks failed
Rust / clippy (push) Has been cancelled
Rust / build (push) Has been cancelled
Rust / rustfmt (push) Has been cancelled
Reviewed-on: #17
Co-authored-by: luckyturtledev <git@lukas1818.de>
Co-committed-by: luckyturtledev <git@lukas1818.de>
2024-07-07 14:03:30 +00:00
2416ffd7dc
fix warning in build.rs
Some checks failed
Rust / rustfmt (push) Successful in 24s
Rust / clippy (push) Failing after 2m11s
Rust / build (push) Successful in 2m45s
2024-07-07 15:34:58 +02:00
03e12d6feb
Convert Music to ogg/vorbis and load in build.rs (#14)
Some checks failed
Rust / rustfmt (push) Successful in 20s
Rust / clippy (push) Failing after 1m31s
Rust / build (push) Has been cancelled
Reviewed-on: #14
Co-authored-by: Dominic <git@msrd0.de>
Co-committed-by: Dominic <git@msrd0.de>
2024-07-07 13:29:41 +00:00
29 changed files with 831 additions and 139 deletions

View file

@ -59,10 +59,11 @@ jobs:
target
key: "${{runner.os}} Rust ${{steps.rust-toolchain.outputs.cachekey}}"
- run: mkdir .ci-destdir
- run: cargo install --path . --locked --root .ci-destdir
- run: cargo install --path . --locked --root .
- run: find .ci-destdir
- uses: forgejo/upload-artifact@v3
with:
name: Powercreep
path:
.ci-destdir/*
path: |
bin
assets

37
Cargo.lock generated
View file

@ -242,7 +242,7 @@ dependencies = [
"regex",
"rustc-hash",
"shlex",
"syn 2.0.68",
"syn 2.0.69",
]
[[package]]
@ -326,7 +326,7 @@ checksum = "1ee891b04274a59bd38b412188e24b849617b2e45a0fd8d057deb63e7403761b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.68",
"syn 2.0.69",
]
[[package]]
@ -369,9 +369,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.0.104"
version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74b6a57f98764a267ff415d50a25e6e166f3831a5071af4995296ea97d210490"
checksum = "5208975e568d83b6b05cc0a063c8e7e9acc2b43bee6da15616a5b73e109d7437"
dependencies = [
"jobserver",
"libc",
@ -1111,7 +1111,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.68",
"syn 2.0.69",
]
[[package]]
@ -1897,7 +1897,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.68",
"syn 2.0.69",
]
[[package]]
@ -1927,7 +1927,7 @@ dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 2.0.68",
"syn 2.0.69",
]
[[package]]
@ -2130,6 +2130,7 @@ dependencies = [
"comfy",
"heck",
"indexmap",
"log",
"resvg",
]
@ -2427,22 +2428,22 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"
[[package]]
name = "serde"
version = "1.0.203"
version = "1.0.204"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.203"
version = "1.0.204"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.68",
"syn 2.0.69",
]
[[package]]
@ -2702,9 +2703,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.68"
version = "2.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9"
checksum = "201fcda3845c23e8212cd466bfebf0bd20694490fc0356ae8e428e0824a915a6"
dependencies = [
"proc-macro2",
"quote",
@ -2737,7 +2738,7 @@ checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.68",
"syn 2.0.69",
]
[[package]]
@ -2977,7 +2978,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.68",
"syn 2.0.69",
"wasm-bindgen-shared",
]
@ -3011,7 +3012,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.68",
"syn 2.0.69",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -3695,7 +3696,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.68",
"syn 2.0.69",
]
[[package]]

View file

@ -17,6 +17,7 @@ opt-level = 3
[dependencies]
comfy = { version = "0.4.0", features = ["wayland"] }
indexmap = "2"
log = "0.4.22"
[build-dependencies]
heck = "0.5"

BIN
assets/entities/human.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

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.

BIN
assets/sounds/bzz.ogg Normal file

Binary file not shown.

View file

@ -17,6 +17,9 @@ struct Assets {
/// of the constant storing their png.
assets: BTreeMap<String, String>,
/// Sound assets.
sound_assets: BTreeMap<String, String>,
/// Asset groups contained within this asset group, mapping their name to the assets
/// that group contains.
groups: BTreeMap<String, Box<Assets>>
@ -37,15 +40,19 @@ impl AssetsWriter {
}
}
fn add_png<P: AsRef<Path>>(
fn asset_name<P: AsRef<Path>>(canonical_path: P) -> String {
let mut hasher = DefaultHasher::new();
canonical_path.as_ref().hash(&mut hasher);
let hash = hasher.finish();
format!("ASSET_{hash:X}")
}
fn add_png<P: AsRef<Path> + Copy>(
&mut self,
canonical_path: P,
png: tiny_skia::Pixmap
) -> String {
let mut hasher = DefaultHasher::new();
canonical_path.as_ref().hash(&mut hasher);
let hash = hasher.finish();
let const_name = format!("ASSET_{hash:X}");
let const_name = Self::asset_name(canonical_path);
let out_dir = env::var_os("OUT_DIR").unwrap();
let out_dir: PathBuf = out_dir.into();
@ -62,6 +69,18 @@ impl AssetsWriter {
const_name
}
fn add_ogg<P: AsRef<Path> + Copy>(&mut self, canonical_path: P) -> String {
let const_name = Self::asset_name(canonical_path);
writeln!(self.file, "// {}", canonical_path.as_ref().display()).unwrap();
writeln!(
self.file,
"const {const_name}: &[u8] = include_bytes!(\"{}\");",
canonical_path.as_ref().display()
)
.unwrap();
const_name
}
fn finish(mut self) {
fn write_assets_struct(
file: &mut File,
@ -85,6 +104,13 @@ impl AssetsWriter {
asset_name.to_snake_case()
)?;
}
for asset_name in root.sound_assets.keys() {
writeln!(
file,
"{indent}\tpub {}: comfy::Sound,",
asset_name.to_snake_case()
)?;
}
for group_name in root.groups.keys() {
writeln!(
file,
@ -96,13 +122,20 @@ impl AssetsWriter {
writeln!(file, "{indent}impl Assets {{")?;
writeln!(
file,
"{indent}\tpub fn load(c: &mut comfy::EngineContext<'_>) {{"
"{indent}\tpub fn load(_ctx: &mut comfy::EngineContext<'_>) {{"
)?;
for asset_const_name in root.assets.values() {
writeln!(file, "{indent}\t\tc.load_texture_from_bytes({asset_const_name:?}, {asset_const_name});")?;
writeln!(file, "{indent}\t\t_ctx.load_texture_from_bytes({asset_const_name:?}, {asset_const_name});")?;
}
for asset_const_name in root.sound_assets.values() {
writeln!(file, "{indent}\t\tcomfy::load_sound_from_bytes({asset_const_name:?}, {asset_const_name},")?;
writeln!(
file,
"{indent}\t\t\tcomfy::StaticSoundSettings::new().loop_region(..));"
)?;
}
for group_name in root.groups.keys() {
writeln!(file, "{indent}\t\t{group_name}::Assets::load(c);")?;
writeln!(file, "{indent}\t\t{group_name}::Assets::load(_ctx);")?;
}
writeln!(file, "{indent}\t}}")?;
writeln!(file, "{indent}}}")?;
@ -118,6 +151,13 @@ impl AssetsWriter {
asset_name.to_snake_case()
)?;
}
for (asset_name, asset_const_name) in &root.sound_assets {
writeln!(
file,
"{indent}\t{}: comfy::sound_id({asset_const_name:?}),",
asset_name.to_snake_case()
)?;
}
for group_name in root.groups.keys() {
writeln!(
file,
@ -170,6 +210,8 @@ fn process_dir<P: AsRef<Path> + Copy>(
groups.pop();
} else if path.extension().map(|ext| ext == "svg").unwrap_or(false) {
process_svg(&path, dir, writer, groups);
} else if path.extension().map(|ext| ext == "ogg").unwrap_or(false) {
process_ogg(&path, writer, groups);
}
}
}
@ -196,7 +238,8 @@ fn process_svg<P: AsRef<Path> + Copy, Q: AsRef<Path>>(
resvg::render(&tree, transform, &mut pixmap.as_mut());
let const_name = writer.add_png(
file.as_ref()
&file
.as_ref()
.canonicalize()
.expect("Failed to canonicalize"),
pixmap
@ -218,3 +261,32 @@ fn process_svg<P: AsRef<Path> + Copy, Q: AsRef<Path>>(
const_name
);
}
fn process_ogg<P: AsRef<Path> + Copy>(
file: P,
writer: &mut AssetsWriter,
groups: &[String]
) {
let const_name = writer.add_ogg(
&file
.as_ref()
.canonicalize()
.expect("Failed to canonicalize")
);
let mut group = &mut writer.root;
for group_name in groups {
if !group.groups.contains_key(group_name) {
group.groups.insert(group_name.to_owned(), Box::default());
}
group = group.groups.get_mut(group_name).unwrap();
}
group.sound_assets.insert(
file.as_ref()
.file_stem()
.expect("File doesn't have a stem")
.to_str()
.expect("Non-UTF8 file names aren't allowed")
.into(),
const_name
);
}

View file

@ -1,5 +1,5 @@
use comfy::{error, texture_id, EngineContext, HashSet, Lazy, Mutex, TextureHandle};
use std::{fmt::Debug, fs, io, sync::Arc};
use std::{fmt::Debug, fs, io, path::PathBuf, sync::Arc};
static ASSETS_LOADED: Lazy<Arc<Mutex<HashSet<String>>>> =
Lazy::new(|| Arc::new(Mutex::new(HashSet::new())));
@ -23,7 +23,7 @@ impl FurnitureAsset {
}
fn asset_path_magnet(&self) -> String {
format!("{}/magnet/{}.png", self.folder, self.name)
format!("{}/metal/{}.png", self.folder, self.name)
}
fn asset_path_elec(&self) -> String {
@ -39,10 +39,13 @@ impl FurnitureAsset {
if loaded.contains(&path) {
return Some(texture_id(&path));
}
let bytes = match fs::read(format!(
"{}/assets/furniture/{path}",
env!("CARGO_MANIFEST_DIR")
)) {
let mut asset_path = PathBuf::from(format!("assets/furniture/{path}"));
if !asset_path.exists() {
asset_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(asset_path);
}
let bytes = match fs::read(asset_path) {
Ok(bytes) => bytes,
Err(err) if err.kind() == io::ErrorKind::NotFound => return None,
Err(err) => {
@ -110,4 +113,8 @@ impl Furniture {
pub fn get_magnet_texture_handle(&self) -> Option<TextureHandle> {
self.handles.magnet
}
pub fn is_on(&self) -> bool {
(self.on)()
}
}

View file

@ -3,33 +3,93 @@ mod grid;
mod player;
mod room;
use self::{grid::Grid, player::Player, room::Room};
use crate::State;
use comfy::{error, EngineContext};
use crate::assets::ASSETS;
use comfy::{
delta, main_camera_mut, play_sound_id, random_i32, stop_sound_id, vec2,
EngineContext, RandomRange as _
};
use grid::Grid;
use indexmap::IndexSet;
use log::error;
use player::Player;
use room::Room;
use super::Activity;
const MAX_ROOMS: i32 = 6;
#[derive(Debug)]
pub struct HouseState {
room: Room,
current_room_id: usize,
room_count: usize,
human_count: usize,
rooms: Vec<Room>,
human_rooms: Vec<usize>,
//grid: Grid,
player: Player
player: Player,
human_layer: bool, //Human, magnetic, electric
exit_time: f32,
/// The energy level remaining in the house. Should decrease by itself, and much
/// faster when inhabited by the ghost.
pub charge: f32,
pub max_charge: f32,
pub sound_playing: bool
}
impl HouseState {
pub fn generate_new_house(ctx: &mut EngineContext<'_>) -> Self {
let room = Room::new(ctx);
let player = Player::new(&room);
HouseState { room, player }
let room_count = random_i32(2, MAX_ROOMS) as usize;
let human_count = random_i32(1, room_count as i32) as usize;
let mut possible_rooms: IndexSet<usize> = (0 .. room_count).collect();
let mut human_rooms = Vec::new();
for _ in 0 .. human_count {
if !possible_rooms.is_empty() {
let random_idx = usize::gen_range(0, possible_rooms.len());
human_rooms.push(possible_rooms.swap_remove_index(random_idx).unwrap());
}
}
let mut rooms = Vec::new();
for i in 0 .. room_count {
rooms.push(Room::new(ctx, human_rooms.contains(&i)));
}
let player = Player::new(rooms.first().unwrap());
let max_charge = f32::gen_range(2_000.0, 5_000.0);
HouseState {
current_room_id: 0,
room_count,
human_count,
rooms,
human_rooms,
player,
human_layer: false,
exit_time: 0.0,
// TODO this should be lower depending on the time elapsed
charge: max_charge,
max_charge,
sound_playing: false
}
}
}
pub fn draw(state: &State, _ctx: &comfy::EngineContext<'_>) {
pub fn draw(state: &crate::State, _ctx: &EngineContext<'_>) {
let Some(house) = state.house() else {
error!("How can I render a house when I'm not in one?!?");
return;
};
//Draw House
house.room.draw();
house
.rooms
.get(house.current_room_id)
.unwrap()
.draw(house.human_layer, house.player.can_see_metal(0.1));
//Draw Grid
//state.house.grid.draw();
@ -38,15 +98,59 @@ pub fn draw(state: &State, _ctx: &comfy::EngineContext<'_>) {
house.player.draw();
}
pub fn update(state: &mut State, ctx: &mut comfy::EngineContext<'_>) {
let house = state.house_mut(ctx);
house.player.update(&house.room.grid);
pub fn update(state: &mut crate::State, ctx: &mut EngineContext<'_>) {
if state.overworld.sound_playing {
stop_sound_id(ASSETS.music.galactic_rap_speedup);
state.overworld.sound_playing = false;
}
if house.player.is_moving_to_right_room(&house.room) {
house.room = Room::new(ctx);
house.player.reset_on_room(&house.room, true);
} else if house.player.is_moving_to_left_room(&house.room) {
house.room = Room::new(ctx);
house.player.reset_on_room(&house.room, false);
let mut camera = main_camera_mut();
camera.center = vec2(0.0, 0.0);
drop(camera);
let Some(house) = state.house_mut(ctx) else {
error!("WTF I cannot update a house without a house");
return;
};
let current_room = house.rooms.get(house.current_room_id).unwrap();
house.player.update(&current_room);
if !house.sound_playing {
play_sound_id(ASSETS.music.mesmerizing_galaxy_loop);
house.sound_playing = true;
}
if house.player.is_moving_to_right_room(current_room) {
if house.current_room_id < (house.room_count - 1) {
house.current_room_id += 1;
let current_room = house.rooms.get(house.current_room_id).unwrap();
house.player.reset_on_room(current_room, true);
} else {
house.player.reset_on_room(current_room, false);
}
} else if house.player.is_moving_to_left_room(current_room) {
if house.current_room_id > 0 {
house.current_room_id -= 1;
let current_room = house.rooms.get(house.current_room_id).unwrap();
house.player.reset_on_room(current_room, false);
} else {
house.player.reset_on_room(current_room, true);
}
}
if house.player.successfull_attack {
house.human_layer = true;
}
if house.human_layer {
house.exit_time += delta();
}
if house.exit_time >= 2.0 {
state.score += 500.0;
state.ghost.overworld_pos -= vec2(0.0, 1.0);
state.activity = Activity::Overworld;
}
}

View file

@ -2,15 +2,23 @@ use super::{
room::{Room, SCALE},
Grid
};
use comfy::{delta, draw_circle, is_key_down, vec2, KeyCode, Vec2, RED};
use comfy::{
delta, draw_circle, draw_sprite, is_key_down, is_key_pressed, main_camera_mut,
texture_id, vec2, KeyCode, Vec2, RED, WHITE
};
use log::error;
use std::collections::HashSet;
#[derive(Debug)]
pub struct Player {
position: Vec2,
speed: f32,
last_speed: f32,
current_acceleration: f32,
movement_time: f32,
connection: usize,
next_connections: Vec<usize>
next_connections: Vec<usize>,
pub successfull_attack: bool
}
impl Player {
@ -20,17 +28,29 @@ impl Player {
((0.25) - room.size.0 as f32 / 2.0) * SCALE,
room.grid.nodes.first().unwrap().y
),
speed: 10.0,
speed: 0.0,
last_speed: 0.0,
current_acceleration: 0.0,
movement_time: 0.0,
connection: 0,
next_connections: vec![1]
next_connections: vec![1],
successfull_attack: false
}
}
pub fn draw(&self) {
draw_circle(self.position, 0.5, RED, 0);
//draw_circle(self.position, 0.5, RED, 0);
draw_sprite(
texture_id("ghost_house"),
self.position,
WHITE,
5,
vec2(1.25, 1.25)
);
}
pub fn update(&mut self, grid: &Grid) {
pub fn update(&mut self, room: &Room) {
let grid = &room.grid;
let allowed_movement = get_allowed_movement(self, grid);
move_player(self, allowed_movement);
@ -38,6 +58,8 @@ impl Player {
if !on_current_connection(self, grid) && !update_connections(self, grid) {
snap_to_closest_node(self, grid);
}
attack_human_routine(self, room);
}
pub fn is_moving_to_right_room(&self, room: &Room) -> bool {
@ -57,8 +79,38 @@ impl Player {
};
self.position = vec2(x, room.grid.nodes.first().unwrap().y);
self.connection = 0;
self.next_connections = vec![1];
if place_left {
self.connection = 0;
self.next_connections = vec![1];
} else {
let mut current_index = 0;
for (i, node_pos) in room.grid.nodes.iter().enumerate().rev() {
if in_node_range(
&vec2((room.size.0 as f32 / 2.0 + 0.5) * SCALE, self.position.y),
node_pos,
1.0
) {
current_index = i;
}
}
let connction_index = room
.grid
.connections
.iter()
.enumerate()
.find(|(_i, (_node_1, node_2))| *node_2 == current_index)
.map(|(i, _)| i)
.unwrap();
self.connection = connction_index;
self.next_connections = vec![connction_index - 1];
}
}
pub fn can_see_metal(&self, threshold: f32) -> bool {
self.current_acceleration > threshold
}
}
@ -70,6 +122,22 @@ fn move_player(player: &mut Player, allowed_movement: (bool, bool, bool, bool))
allow_right_movement
) = allowed_movement;
if is_key_down(KeyCode::Up)
|| is_key_down(KeyCode::Down)
|| is_key_down(KeyCode::Left)
|| is_key_down(KeyCode::Right)
{
player.movement_time += delta();
player.speed = calc_player_speed(player.movement_time);
player.speed = player.speed.clamp(0.0, 10.0);
} else {
player.speed = 0.0;
player.last_speed = 0.0;
player.movement_time = 0.0;
}
player.current_acceleration = (player.speed - player.last_speed).abs();
if allow_up_movement && is_key_down(KeyCode::Up) {
player.position += vec2(0.0, player.speed) * delta();
}
@ -85,6 +153,14 @@ fn move_player(player: &mut Player, allowed_movement: (bool, bool, bool, bool))
if allow_left_movement && is_key_down(KeyCode::Left) {
player.position += vec2(-player.speed, 0.0) * delta();
}
player.last_speed = player.speed;
}
fn calc_player_speed(time: f32) -> f32 {
//3x^2-2x^3; x=t
let t = time * 20.0;
3.0 * t * t - 2.0 * t * t
}
//(UP, DOWN, LEFT, RIGHT)
@ -293,3 +369,42 @@ fn snap_to_closest_node(player: &mut Player, grid: &Grid) {
player.position = *closest_node;
}
fn attack_human_routine(player: &mut Player, room: &Room) {
let range = 1.0;
if is_key_pressed(KeyCode::Space) {
//In some node range?
let mut in_range = false;
for node in &room.grid.nodes {
if in_node_range(&player.position, node, range) {
in_range = true;
break;
}
}
if in_range {
let (human_pos, human_nodes) = room.get_human_information();
let mut human_attacked = false;
if human_pos.is_some() {
for node_index in human_nodes {
if in_node_range(
&player.position,
room.grid.nodes.get(*node_index).unwrap(),
range
) {
human_attacked = true;
}
}
}
if human_attacked {
player.successfull_attack = true;
} else {
main_camera_mut().shake(1.0, 1.0);
}
}
}
}

View file

@ -1,8 +1,8 @@
use super::{furniture::Furniture, grid::Grid};
use crate::game;
use crate::game::{self, ZLayer};
use comfy::{
draw_rect, draw_rect_outline, draw_sprite, error, random_i32, vec2, EngineContext,
HashSet, RandomRange as _, Vec2, GREEN, PURPLE, RED, WHITE
draw_rect, draw_rect_outline, draw_sprite, error, random_i32, texture_id, vec2,
EngineContext, HashSet, RandomRange as _, Vec2, BLUE, GREEN, PURPLE, RED, WHITE
};
use indexmap::IndexSet;
@ -30,7 +30,9 @@ pub struct Room {
_room_type: RoomType,
pub size: (u8, u8), //(width, height)
pub grid: Grid,
furnitures: Vec<Tile>
furnitures: Vec<Tile>,
human_pos: Option<Vec2>,
human_nodes: Vec<usize>
}
impl RoomType {
@ -47,20 +49,60 @@ impl RoomType {
}
impl Room {
pub fn new(ctx: &mut EngineContext<'_>) -> Self {
pub fn new(ctx: &mut EngineContext<'_>, with_human: bool) -> Self {
let room_type = RoomType::random();
let size = Self::random_size(&room_type);
let furnitures = Self::random_room_furniture(&room_type, size, ctx);
let grid = Self::create_grid(size.0, size.1);
let (human_pos, human_nodes) = if with_human {
let mut human_pos = vec2(random_i32(1, size.0 as i32) as f32 + 0.5, 1.0f32);
human_pos -= vec2(size.0 as f32 / 2.0, size.1 as f32 / 2.0);
human_pos *= SCALE;
let node_index_left = grid
.nodes
.iter()
.enumerate()
.filter(|(_i, &node)| node.y <= size.1 as f32 / 6.0)
.filter(|(_i, &node)| node.x <= (human_pos.x - 0.5 * SCALE))
.max_by_key(|(_i, &node)| node.x as i32)
.map(|(i, _)| i)
.unwrap();
let node_index_right = grid
.nodes
.iter()
.enumerate()
.filter(|(_i, &node)| node.y <= size.1 as f32 / 6.0)
.filter(|(_i, &node)| node.x >= (human_pos.x + 0.5 * SCALE))
.min_by_key(|(_i, &node)| node.x as i32)
.map(|(i, _)| i)
.unwrap();
(
Some(human_pos),
(node_index_left ..= node_index_right).collect()
)
} else {
(None, Vec::new())
};
Room {
_room_type: room_type,
size,
grid: Self::create_grid(size.0, size.1),
furnitures
grid,
furnitures,
human_pos,
human_nodes
}
}
pub fn get_human_information(&self) -> (Option<Vec2>, &Vec<usize>) {
(self.human_pos, &self.human_nodes)
}
fn random_size(room_type: &RoomType) -> (u8, u8) {
//Kitchen + Living Room 5-8
//Bath + sleepingroom 4-6
@ -88,6 +130,37 @@ impl Room {
let random_idx = usize::gen_range(0, empty_spots.len());
empty_spots.swap_remove_index(random_idx)
}
fn random_empty_spot_size(
empty_spots: &mut IndexSet<u8>,
size: u8
) -> Option<u8> {
let mut empty_spots_size = IndexSet::<u8>::new();
for &index in empty_spots.iter() {
let mut is_valid = true;
for offset in 0 .. size {
if !empty_spots.contains(&(index + offset)) {
is_valid = false;
break;
}
}
if is_valid {
empty_spots_size.insert(index);
}
}
if empty_spots_size.is_empty() {
return None;
}
let random_idx = usize::gen_range(0, empty_spots_size.len());
for offset in (0 .. size).rev() {
empty_spots.swap_remove_index(random_idx + offset as usize);
}
Some(random_idx as u8)
}
fn random_appliance<T>(empty_spots: &mut Vec<T>) -> Option<T> {
if empty_spots.is_empty() {
return None;
@ -254,6 +327,42 @@ impl Room {
}
},
RoomType::LivingRoom => {
let has_couch = match u8::gen_range(0, 2) {
0 => false,
1 => true,
_ => unreachable!()
};
if has_couch {
if let Some(pos) = random_empty_spot_size(&mut empty_spots, 3) {
furnitures.push(Tile {
pos: vec2(pos as f32, 0.0),
size: vec2(3.0, 1.0),
f: Furniture::new("bedroom", "couch", ctx),
z: 0
});
}
}
if let Some(pos) = random_empty_spot(&mut empty_spots) {
furnitures.push(Tile {
pos: vec2(pos as f32, 0.0),
size: vec2(1.0, 2.0),
f: Furniture::new("bedroom", "bookshelf", ctx),
z: 0
});
}
if let Some(pos) = random_empty_spot(&mut empty_spots) {
furnitures.push(Tile {
pos: vec2(pos as f32, 0.0),
size: vec2(0.5, 0.9),
f: Furniture::new("bedroom", "mini_ac", ctx),
z: 0
});
}
},
_ => {}
}
@ -310,14 +419,14 @@ impl Room {
Grid::new(nodes, connections)
}
pub fn draw(&self) {
pub fn draw(&self, human_layer: bool, magnet_layer: bool) {
let (width, height) = self.size;
draw_rect(
vec2(0.0, 0.0),
vec2(width as f32 * SCALE, height as f32 * SCALE),
PURPLE,
game::ZLayer::MapMax as i32 - 2
game::ZLayer::HumanLayer as i32 - 4
);
draw_rect_outline(
vec2(0.0, 0.0),
@ -331,25 +440,69 @@ impl Room {
let mut pos = tile.pos - vec2(width as f32 / 2.0, height as f32 / 2.0);
pos += tile.size * 0.5;
if let Some(texture) = tile.f.get_human_texture_handle() {
if human_layer {
if let Some(texture) = tile.f.get_human_texture_handle() {
draw_sprite(
texture,
pos * SCALE,
WHITE,
game::ZLayer::HumanLayer as i32 + tile.z,
tile.size * SCALE
);
} else {
draw_rect_outline(
pos * SCALE,
tile.size * SCALE,
0.3,
GREEN,
game::ZLayer::HumanLayer as i32 + tile.z
);
}
}
if magnet_layer {
if let Some(texture) = tile.f.get_magnet_texture_handle() {
draw_sprite(
texture,
pos * SCALE,
BLUE,
game::ZLayer::MagneticLayer as i32 + tile.z,
tile.size * SCALE
);
}
}
if tile.f.is_on() {
if let Some(texture) = tile.f.get_elec_texture_handle() {
draw_sprite(
texture,
pos * SCALE,
WHITE,
game::ZLayer::ElectricLayer as i32 + tile.z,
tile.size * SCALE
);
}
}
}
if human_layer {
if let Some(human_pos) = self.human_pos {
//draw_circle(human_pos, 0.5, RED, 20);
draw_sprite(
texture,
pos * SCALE,
texture_id("human_house"),
human_pos,
WHITE,
game::ZLayer::MapMax as i32 + tile.z,
tile.size * SCALE
);
} else {
draw_rect_outline(
pos * SCALE,
tile.size * SCALE,
0.3,
GREEN,
game::ZLayer::MapMax as i32 + tile.z
ZLayer::Human.into(),
vec2(1.0, 2.0) * SCALE
);
}
}
/* Debug draw
for node_index in &self.human_nodes {
draw_circle(*self.grid.nodes.get(*node_index).unwrap(), 0.5, GREEN, ZLayer::Human.into());
}*/
self.grid.draw();
}
}

View file

@ -1,14 +1,30 @@
use crate::{activities::Activity, game::ZLayer, State};
use crate::{
activities::Activity,
assets::ASSETS,
game::{ZLayer, GHOST_DISCHARGE_RATE, GHOST_DISCHARGE_RATE_MOVEMENT},
State
};
use comfy::{
draw_circle, draw_rect_outline, draw_sprite, error, info, is_key_down,
main_camera_mut, EngineContext, IVec2, KeyCode, Vec2, RED, WHITE
draw_rect_outline, draw_sprite, error, info, is_key_down, main_camera_mut,
play_sound_id, stop_sound_id, texture_id, vec2, EngineContext, IVec2, KeyCode, Vec2,
RED, WHITE
};
use std::time::Instant;
use worldgen::MovementCost;
pub mod worldgen;
pub fn draw(state: &crate::State, _engine: &comfy::EngineContext<'_>) {
pub fn setup(_state: &State, ctx: &EngineContext<'_>) {
ctx.load_texture_from_bytes(
"human_overworld",
include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/assets/entities/human_overworld.png"
))
);
}
pub fn draw(state: &State, _ctx: &EngineContext<'_>) {
for (coords, tile) in state.overworld.iter_tiles() {
for (i, texture) in tile.textures().iter().rev().enumerate() {
let i = i as i32;
@ -19,15 +35,37 @@ pub fn draw(state: &crate::State, _engine: &comfy::EngineContext<'_>) {
ZLayer::MapMax - i,
Vec2::ONE
);
draw_rect_outline(coords.as_vec2(), Vec2::ONE, 0.1, RED, 10);
if state.dev {
draw_rect_outline(coords.as_vec2(), Vec2::ONE, 0.1, RED, 10);
}
}
}
draw_circle(state.ghost.overworld_pos, 0.5, RED, ZLayer::Ghost.into());
let mut ghost_pos = state.ghost.overworld_pos;
ghost_pos.y += 0.5;
draw_sprite(
texture_id("human_overworld"),
ghost_pos,
WHITE,
ZLayer::Ghost.into(),
vec2(1.0, 1.25)
);
}
fn update_move_player(state: &mut State, ctx: &mut EngineContext<'_>) {
let now = Instant::now();
if !state.overworld.sound_playing {
if let Some(house) = state.house_mut(ctx) {
if house.sound_playing {
stop_sound_id(ASSETS.music.mesmerizing_galaxy_loop);
house.sound_playing = false;
}
}
play_sound_id(ASSETS.music.galactic_rap_speedup);
state.overworld.sound_playing = true;
}
// Are there any pending position updates? If so, we ignore all user input and execute
// the pending updates.
if state.ghost.overworld_movement_pending != Vec2::ZERO {
@ -36,6 +74,8 @@ fn update_move_player(state: &mut State, ctx: &mut EngineContext<'_>) {
state.ghost.overworld_movement_pending
);
state.ghost.update_overworld_pos(now);
}
if state.ghost.overworld_movement_pending != Vec2::ZERO {
return;
}
@ -133,10 +173,10 @@ fn update_move_player(state: &mut State, ctx: &mut EngineContext<'_>) {
},
// we are walking on a path
(MovementCost::Path, MovementCost::Path) => 10.0,
(MovementCost::Path, MovementCost::Path) => 7.0,
// we are walking across an obstacle
(MovementCost::Obstacle, _) | (_, MovementCost::Obstacle) => 1.0,
(MovementCost::Obstacle, _) | (_, MovementCost::Obstacle) => 2.0,
// we are walking on grass
_ => 5.0
@ -170,6 +210,15 @@ pub fn update(state: &mut State, ctx: &mut EngineContext<'_>) {
}
}
// energie lost
{
let ghost = &mut state.ghost;
ghost.charge -= GHOST_DISCHARGE_RATE * ctx.delta;
if ghost.overworld_movement_pending != Vec2::ZERO {
ghost.charge -= GHOST_DISCHARGE_RATE_MOVEMENT * ctx.delta;
}
}
// generate more chunks if needed
{
let half_viewport = (camera.world_viewport() * 0.5 + 3.0).as_ivec2();

View file

@ -422,7 +422,9 @@ impl Chunk {
#[derive(Debug, Default)]
pub struct Overworld {
chunks: HashMap<IVec2, Chunk>
chunks: HashMap<IVec2, Chunk>,
pub sound_playing: bool
}
fn world_to_chunk_and_local_coords(world_coords: IVec2) -> (IVec2, UVec2) {

View file

@ -4,7 +4,10 @@ use crate::{
State
};
use comfy::{EngineContext, Vec2};
use std::{ops::Sub, time::Instant};
use std::{
ops::{Add, Sub},
time::Instant
};
#[derive(Debug)]
pub struct Ghost {
@ -66,9 +69,14 @@ impl Default for Ghost {
}
#[repr(i32)]
pub enum ZLayer {
HumanLayer = -8,
MagneticLayer = -5,
ElectricLayer = -2,
MapMax = -1,
Human = 0,
Ghost = 1
Ghost = 1,
UI = 100,
GameOver = 1000
}
impl From<ZLayer> for i32 {
@ -86,16 +94,89 @@ impl Sub<i32> for ZLayer {
}
}
pub fn setup(_state: &mut State, ctx: &mut EngineContext<'_>) {
Assets::load(ctx);
impl Add<i32> for ZLayer {
type Output = i32;
fn add(self, other: i32) -> Self::Output {
i32::from(self) + other
}
}
pub fn update(state: &mut State, engine: &mut EngineContext<'_>) {
state.score += engine.delta * 10.0;
match state.activity {
Activity::House(_) => house::update(state, engine),
Activity::Overworld => overworld::update(state, engine)
pub fn setup(state: &mut State, ctx: &mut EngineContext<'_>) {
Assets::load(ctx);
overworld::setup(state, ctx);
//house::setup(state, ctx);
ctx.load_texture_from_bytes(
"ghost_house",
include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/assets/entities/ghost.png"
))
);
ctx.load_texture_from_bytes(
"human_house",
include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/assets/entities/human_captured.png"
))
);
}
/// The amount of energy a ghost consumes idle.
pub const GHOST_DISCHARGE_RATE: f32 = 40.0;
/// The amount of energy additionally consumed by a moving ghost.
pub const GHOST_DISCHARGE_RATE_MOVEMENT: f32 = 40.0;
/// The amount of energy a house consumes idle.
pub const HOUSE_DISCHARGE_RATE: f32 = 30.0;
/// The amount of energy a ghost can charge when inside a house.
pub const GHOST_CHARGE_RATE: f32 = 200.0;
pub fn update(state: &mut State, ctx: &mut EngineContext<'_>) {
if state.ghost.charge > 0.0 {
// Update the score. It's based on time.
state.score += ctx.delta * 10.0;
// Update the currently active activity.
match state.activity {
Activity::House(_) => house::update(state, ctx),
Activity::Overworld => overworld::update(state, ctx)
}
// We update the charge of houses here - the charge will always decrease, even if the
// house is not the current activity.
for (house_pos, house) in &mut state.houses {
house.charge -= ctx.delta * HOUSE_DISCHARGE_RATE;
match state.activity {
Activity::House(pos) if *house_pos == pos => {
// The ghost is currently inside the house. Increase its discarge rate.
house.charge -= ctx.delta * GHOST_DISCHARGE_RATE;
if house.charge < 0.0 {
state.ghost.charge += house.charge;
house.charge = 0.0;
}
// And possibly also charge the ghost when inside a house.
if state.ghost.charge < state.ghost.max_charge {
let charge_transfer = (ctx.delta * GHOST_CHARGE_RATE)
.min(state.ghost.max_charge - state.ghost.charge)
.min(house.charge);
state.ghost.charge += charge_transfer;
house.charge -= charge_transfer;
}
},
_ => {}
}
}
} else {
crate::game_over::update(state, ctx);
}
// Make sure the ghost's charge never drops below 0.
state.ghost.charge = state.ghost.charge.max(0.0);
}
pub fn draw(state: &State, engine: &EngineContext<'_>) {
@ -104,4 +185,7 @@ pub fn draw(state: &State, engine: &EngineContext<'_>) {
Activity::Overworld => overworld::draw(state, engine)
}
crate::ui::draw(state, engine);
if state.ghost.charge <= 0.0 {
crate::game_over::draw(state, engine);
}
}

52
src/game_over.rs Normal file
View file

@ -0,0 +1,52 @@
use std::mem::take;
use crate::{game::ZLayer, State};
use comfy::{
draw_rect, egui,
egui::{Align, Layout},
is_key_pressed, main_camera, Color, EngineContext, WHITE
};
use egui::{Align2, RichText};
use log::info;
pub fn update(state: &mut State, _engine: &mut EngineContext<'_>) {
if is_key_pressed(comfy::KeyCode::Return) {
info!("Restart game");
take(state); //reset to default
}
}
pub fn draw(state: &State, _engine: &EngineContext<'_>) {
let cam = main_camera();
draw_rect(
cam.center,
cam.world_viewport() * 1.2,
Color::new(0.0, 0.0, 0.0, 0.5),
ZLayer::GameOver.into()
);
egui::Area::new("game_over")
.anchor(Align2::CENTER_CENTER, egui::vec2(0.0, 0.0))
.show(egui(), |ui| {
ui.with_layout(Layout::top_down_justified(Align::Center), |ui| {
ui.heading(
RichText::new(
"The poor spirit lost all their energy and starved to death"
)
.color(WHITE)
.strong()
.size(50.0)
);
ui.label(
RichText::new(format!("\nYour Score:\n{:.0}", state.score))
.color(WHITE)
.size(30.0)
);
ui.label(
RichText::new("\n\n\npress enter to restart")
.color(WHITE)
.size(30.0)
);
})
});
}

View file

@ -5,22 +5,36 @@
mod assets {
include!(env!("ASSETS_RS"));
}
mod activities;
mod game;
mod game_over;
mod ui;
use std::env::var;
use self::{
activities::{house::HouseState, overworld::worldgen::Overworld, Activity},
game::Ghost
};
use comfy::{
etagere::euclid::default, init_game_config, pollster, run_comfy_main_async,
DevConfig, EngineContext, EngineState, GameConfig, GameLoop, HashMap, IVec2
init_game_config, pollster, run_comfy_main_async, DevConfig, EngineContext,
EngineState, GameConfig, GameLoop, HashMap, IVec2
};
const GAME_NAME: &str = "Powercreep";
/// true if devtools should be enable
fn dev_from_env() -> bool {
match var("DEV").as_ref().map(|f| f.as_str()) {
Ok("false") => false,
Ok("true") => true,
#[cfg(debug_assertions)]
_ => true,
#[cfg(not(debug_assertions))]
_ => false
}
}
#[derive(Debug, Default)]
struct State {
setup_called: bool,
@ -28,7 +42,9 @@ struct State {
ghost: Ghost,
overworld: Overworld,
houses: HashMap<IVec2, HouseState>,
score: f32
score: f32,
/// show dev tools
dev: bool
}
impl State {
@ -43,39 +59,49 @@ impl State {
self.houses.get(&self.get_house_pos()?)
}
fn house_mut(&mut self, ctx: &mut EngineContext<'_>) -> &mut HouseState {
self.houses
.entry(self.get_house_pos().unwrap())
.or_insert_with(|| HouseState::generate_new_house(ctx))
fn house_mut(&mut self, ctx: &mut EngineContext<'_>) -> Option<&mut HouseState> {
Some(
self.houses
.entry(self.get_house_pos()?)
.or_insert_with(|| HouseState::generate_new_house(ctx))
)
}
}
impl GameLoop for State {
fn new(_c: &mut EngineState) -> Self {
Self::default()
Default::default()
}
fn update(&mut self, ctx: &mut EngineContext<'_>) {
if !self.setup_called {
self.dev = dev_from_env();
game::setup(self, ctx);
game::setup(self, ctx);
self.setup_called = true;
}
game::update(self, ctx);
game::draw(self, ctx);
game::update(self, ctx);
game::draw(self, ctx);
}
}
fn config(config: GameConfig) -> GameConfig {
let dev = DevConfig {
#[cfg(debug_assertions)]
show_fps: true,
..Default::default()
let dev = if dev_from_env() {
DevConfig {
show_fps: true,
..Default::default()
}
} else {
Default::default()
};
GameConfig {
tonemapping_enabled: true,
dev,
..Default::default()
target_framerate: 165,
..config
}
}

View file

@ -1,42 +1,66 @@
use crate::State;
use crate::{game::ZLayer, State};
use comfy::{
draw_rect, draw_rect_outline, egui, screen_height, screen_to_world, screen_width,
EngineContext, Vec2, BLUE, RED, WHITE
vec2, Color, EngineContext, Vec2, BLACK, BLUE, PURPLE, RED, WHITE
};
use egui::widget_text::RichText;
pub fn draw_batterie(state: &State, _engine: &EngineContext<'_>) {
// seperate fill state into smaller section for better readability
let section_count = 5;
let mut start_positon = screen_to_world(Vec2::new(screen_width(), screen_height()));
// seperate fill state into smaller section for better readability
const BATTERY_SECTION_COUNT: u8 = 5;
const BATTERY_SECTION_WIDTH: f32 = 1.0;
const BATTERY_SECTION_HEIGHT: f32 = 0.5;
fn draw_battery(mut start_position: Vec2, charge: f32, max_charge: f32, color: Color) {
// section size in world codinates
let section_size = Vec2::new(0.5, 0.25);
start_positon.x -= 0.5 * section_size.x + 0.5 * section_size.y;
start_positon.y += 0.5 * section_size.y + 0.5 * section_size.y;
let section_size = vec2(BATTERY_SECTION_WIDTH, BATTERY_SECTION_HEIGHT);
start_position.x -= 0.5 * section_size.x + 0.5 * section_size.y;
start_position.y += 0.5 * section_size.y + 0.5 * section_size.y;
// draw fill level
{
let ghost = &state.ghost;
let percent = ghost.charge / ghost.max_charge;
let percent = charge / max_charge;
let mut size = section_size;
size.y = section_size.y * section_count as f32 * percent;
let mut position = start_positon;
size.y = section_size.y * BATTERY_SECTION_COUNT as f32;
let mut position = start_position;
position.y += 0.5 * -section_size.y + 0.5 * size.y;
draw_rect(position, size, BLUE, 100);
draw_rect(position, size, BLACK, ZLayer::UI.into());
size.y *= percent;
let mut position = start_position;
position.y += 0.5 * -section_size.y + 0.5 * size.y;
draw_rect(position, size, BLUE, ZLayer::UI + 1);
}
// draw sections
for i in 0 .. section_count {
let mut position = start_positon;
for i in 0 .. BATTERY_SECTION_COUNT {
let mut position = start_position;
position.y += i as f32 * section_size.y;
draw_rect_outline(position, section_size, 0.1, RED, 100);
draw_rect_outline(position, section_size, 0.1, color, ZLayer::UI + 2);
}
}
pub fn draw_highscore(state: &State, _engine: &EngineContext<'_>) {
pub fn draw_ghost_battery(state: &State) {
let start_position = screen_to_world(Vec2::new(screen_width(), screen_height()));
draw_battery(
start_position,
state.ghost.charge,
state.ghost.max_charge,
RED
);
}
pub fn draw_house_battery(state: &State) {
let Some(house) = state.house() else {
return;
};
let mut start_position = screen_to_world(Vec2::new(screen_width(), screen_height()));
start_position.x -= 2.0 * BATTERY_SECTION_WIDTH;
draw_battery(start_position, house.charge, house.max_charge, PURPLE)
}
pub fn draw_highscore(state: &State) {
egui::Area::new("score")
.anchor(egui::Align2::RIGHT_TOP, egui::vec2(0.0, 0.0))
.show(egui(), |ui| {
ui.label(
RichText::new(format!("{:.0}", state.score))
RichText::new(format!("{:.0} ", state.score))
.color(WHITE)
.monospace()
.size(16.0)
@ -45,7 +69,8 @@ pub fn draw_highscore(state: &State, _engine: &EngineContext<'_>) {
});
}
pub fn draw(state: &State, engine: &EngineContext<'_>) {
draw_batterie(state, engine);
draw_highscore(state, engine);
pub fn draw(state: &State, _ctx: &EngineContext<'_>) {
draw_ghost_battery(state);
draw_house_battery(state);
draw_highscore(state);
}