diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 37e4317..efab5ff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,6 +51,12 @@ jobs: run: cargo test -- --test-threads=1 #env: #RUSTFLAGS: "-D warnings" + - name: Run cargo doc + if: ${{ runner.os == 'Linux' }} + run: cargo doc --no-deps --document-private-items --all-features + + - name: Run build --release + run: cargo build --release rustfmt: runs-on: ubuntu-20.04 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..098781d --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,23 @@ +# .github/workflows/release.yml + +on: + release: + types: [created] + +jobs: + release: + name: release ${{ matrix.target }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + target: [x86_64-pc-windows-gnu, x86_64-unknown-linux-musl] + steps: + - uses: actions/checkout@master + - name: Compile and release + uses: rust-build/rust-build.action@v1.4.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + RUSTTARGET: ${{ matrix.target }} + EXTRA_FILES: "README.md LICENSE" diff --git a/.gitignore b/.gitignore index 45536f0..80db9cf 100644 --- a/.gitignore +++ b/.gitignore @@ -90,3 +90,5 @@ saves/ game/saves/ +logs/* + diff --git a/Readme.md b/Readme.md index fb07c82..10df3d9 100644 --- a/Readme.md +++ b/Readme.md @@ -7,5 +7,7 @@ Red Life is a small game about an astronaut who is trying to survive in the host To run the game, you need to have [Rust](https://rustup.rs) installed. Then, run the following command in the root directory of the project: ```bash +rustup install nightly +rustup override set nightly cargo run ``` diff --git a/assets/3d_printer.png b/assets/3d_printer.png new file mode 100644 index 0000000..3d19087 Binary files /dev/null and b/assets/3d_printer.png differ diff --git a/assets/3d_printer_Broken.png b/assets/3d_printer_Broken.png new file mode 100644 index 0000000..85c3993 Binary files /dev/null and b/assets/3d_printer_Broken.png differ diff --git a/assets/3d_printer_Idle.png b/assets/3d_printer_Idle.png new file mode 100644 index 0000000..d07a962 Binary files /dev/null and b/assets/3d_printer_Idle.png differ diff --git a/assets/3d_printer_Running.png b/assets/3d_printer_Running.png new file mode 100644 index 0000000..a6fa1f9 Binary files /dev/null and b/assets/3d_printer_Running.png differ diff --git a/assets/Handbook.png b/assets/Handbook.png new file mode 100644 index 0000000..181d0e2 Binary files /dev/null and b/assets/Handbook.png differ diff --git a/assets/Introscreen.png b/assets/Introscreen.png new file mode 100644 index 0000000..a436960 Binary files /dev/null and b/assets/Introscreen.png differ diff --git a/assets/Kommunikationsmodul_Broken.png b/assets/Kommunikationsmodul_Broken.png new file mode 100644 index 0000000..4c00142 Binary files /dev/null and b/assets/Kommunikationsmodul_Broken.png differ diff --git a/assets/Kommunikationsmodul_Idle.png b/assets/Kommunikationsmodul_Idle.png new file mode 100644 index 0000000..59f0af6 Binary files /dev/null and b/assets/Kommunikationsmodul_Idle.png differ diff --git a/assets/Kommunikationsmodul_Running.png b/assets/Kommunikationsmodul_Running.png new file mode 100644 index 0000000..3ad87c2 Binary files /dev/null and b/assets/Kommunikationsmodul_Running.png differ diff --git a/assets/Loch_Broken.png b/assets/Loch_Broken.png new file mode 100644 index 0000000..56d113e Binary files /dev/null and b/assets/Loch_Broken.png differ diff --git a/assets/Loch_Idle.png b/assets/Loch_Idle.png new file mode 100644 index 0000000..32319f8 Binary files /dev/null and b/assets/Loch_Idle.png differ diff --git a/assets/Loch_Running.png b/assets/Loch_Running.png new file mode 100644 index 0000000..e4101cb Binary files /dev/null and b/assets/Loch_Running.png differ diff --git a/assets/Notstromgenerator.png b/assets/Notstromgenerator.png new file mode 100644 index 0000000..0f14669 Binary files /dev/null and b/assets/Notstromgenerator.png differ diff --git a/assets/Oxygen_Broken.png b/assets/Oxygen_Broken.png new file mode 100644 index 0000000..8ceba29 Binary files /dev/null and b/assets/Oxygen_Broken.png differ diff --git a/assets/Oxygen_Idle.png b/assets/Oxygen_Idle.png new file mode 100644 index 0000000..4838f91 Binary files /dev/null and b/assets/Oxygen_Idle.png differ diff --git a/assets/Oxygen_Running.png b/assets/Oxygen_Running.png new file mode 100644 index 0000000..d373f4d Binary files /dev/null and b/assets/Oxygen_Running.png differ diff --git a/assets/Sauerstoffgenerator.png b/assets/Sauerstoffgenerator.png new file mode 100644 index 0000000..7506327 Binary files /dev/null and b/assets/Sauerstoffgenerator.png differ diff --git a/assets/Stromgenerator.png b/assets/Stromgenerator.png new file mode 100644 index 0000000..61a5be4 Binary files /dev/null and b/assets/Stromgenerator.png differ diff --git a/assets/Stromgenerator_Broken.png b/assets/Stromgenerator_Broken.png new file mode 100644 index 0000000..37d8081 Binary files /dev/null and b/assets/Stromgenerator_Broken.png differ diff --git a/assets/Stromgenerator_Idle.png b/assets/Stromgenerator_Idle.png new file mode 100644 index 0000000..3d606b7 Binary files /dev/null and b/assets/Stromgenerator_Idle.png differ diff --git a/assets/Stromgenerator_Running.png b/assets/Stromgenerator_Running.png new file mode 100644 index 0000000..568fcc2 Binary files /dev/null and b/assets/Stromgenerator_Running.png differ diff --git a/assets/Winningscreen.png b/assets/Winningscreen.png new file mode 100644 index 0000000..c28f51f Binary files /dev/null and b/assets/Winningscreen.png differ diff --git a/assets/deathscreen.png b/assets/deathscreen.png new file mode 100644 index 0000000..9fed1be Binary files /dev/null and b/assets/deathscreen.png differ diff --git a/assets/error.png b/assets/error.png new file mode 100644 index 0000000..f7bdd97 Binary files /dev/null and b/assets/error.png differ diff --git a/assets/player.png b/assets/player.png index 4f50124..6a434a7 100644 Binary files a/assets/player.png and b/assets/player.png differ diff --git a/assets/test_Broken.png b/assets/test_Broken.png new file mode 100644 index 0000000..9e8806d Binary files /dev/null and b/assets/test_Broken.png differ diff --git a/assets/test_Idle.png b/assets/test_Idle.png new file mode 100644 index 0000000..859154d Binary files /dev/null and b/assets/test_Idle.png differ diff --git a/assets/test_Running.png b/assets/test_Running.png new file mode 100644 index 0000000..f83cf8d Binary files /dev/null and b/assets/test_Running.png differ diff --git a/assets/test_Maschiene.png b/assets/test_mashine.png similarity index 100% rename from assets/test_Maschiene.png rename to assets/test_mashine.png diff --git a/assets/werkermaschine.png b/assets/werkermaschine.png new file mode 100644 index 0000000..4416184 Binary files /dev/null and b/assets/werkermaschine.png differ diff --git a/assets/werkermaschine_Broken.png b/assets/werkermaschine_Broken.png new file mode 100644 index 0000000..a53c574 Binary files /dev/null and b/assets/werkermaschine_Broken.png differ diff --git a/assets/werkermaschine_Idle.png b/assets/werkermaschine_Idle.png new file mode 100644 index 0000000..24d5865 Binary files /dev/null and b/assets/werkermaschine_Idle.png differ diff --git a/assets/werkermaschine_Running.png b/assets/werkermaschine_Running.png new file mode 100644 index 0000000..13ec3d6 Binary files /dev/null and b/assets/werkermaschine_Running.png differ diff --git a/game/.gitignore b/game/.gitignore index 69b2ff6..f40575a 100644 --- a/game/.gitignore +++ b/game/.gitignore @@ -2,4 +2,6 @@ # saves folder -saves /* \ No newline at end of file +saves /* + +logs/* \ No newline at end of file diff --git a/game/Cargo.toml b/game/Cargo.toml index 414c65f..c219bb3 100644 --- a/game/Cargo.toml +++ b/game/Cargo.toml @@ -6,10 +6,9 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +ggez = { git="https://github.com/ggez/ggez", branch="devel" } serde = "1.0.145" serde_yaml = "0.9.13" -ggez = "0.8.1" -dyn-clone = "1.0.9" tracing = "0.1.37" tracing-subscriber = "0.3.16" fastrand = "1.8.0" diff --git a/game/src/backend/area.rs b/game/src/backend/area.rs deleted file mode 100644 index ebc304d..0000000 --- a/game/src/backend/area.rs +++ /dev/null @@ -1,14 +0,0 @@ -use crate::backend::utils::is_colliding; -use crate::game_core::player::Player; -use dyn_clone::DynClone; -use ggez::graphics::Rect; -use std::fmt::Debug; - -pub trait Area: DynClone + Debug { - fn interact(&mut self, player: &Player); - fn is_interactable(&self, pos: (usize, usize)) -> bool { - is_colliding(pos, &self.get_interaction_area()) - } - fn get_collision_area(&self) -> Rect; - fn get_interaction_area(&self) -> Rect; -} diff --git a/game/src/backend/constants.rs b/game/src/backend/constants.rs new file mode 100644 index 0000000..9e9d140 --- /dev/null +++ b/game/src/backend/constants.rs @@ -0,0 +1,282 @@ +//! This file contains constants that are necessary for the game. +use crate::backend::rlcolor::RLColor; +use crate::backend::utils::gen_inventory; +use crate::game_core::resources::Resources; +use crate::machines::machine::State; +use crate::machines::trade::Trade; +use ggez::graphics::{Color, Rect}; +use std::string::ToString; + +/// Contains the screen resolution of the game. +pub const SCREEN_RESOLUTION: (f32, f32) = (1920., 1080.); + +/// Contains the desired FPS of the game-loop. +pub(crate) const DESIRED_FPS: u32 = 60; + +/// Contains the map border( x-right, y-bottom, x-left, y-top) +pub const MAP_BORDER: [usize; 4] = [1780, 860, 270, 220]; + +/// Contains the position of the resource bars. +pub(crate) const RESOURCE_POSITION: [f32; 3] = [316.0, 639.0, 1373.0]; + +/// Contains the color used for the resource bars. +pub(crate) const COLORS: [Color; 3] = [RLColor::BLUE, RLColor::GOLD, RLColor::DARK_RED]; + +/// Contains the size of the player icon to scale the collision area. +pub(crate) const PLAYER_ICON_SIZE: (usize, usize) = (58, 96); + +/// Contains the interaction radius of the player. +pub(crate) const PLAYER_INTERACTION_RADIUS: f32 = 50.; +// pub const MACHINE_POSITIONS: [[i32; 4]; 4] = [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]; +pub const HANDBOOK_TEXT: &str = "Werker macht was Sauerstoff auch Ich auch"; + +#[allow(clippy::too_many_lines)] +pub(crate) fn gen_all_machines() -> [(String, Rect, Vec, Resources); 7] { + [ + //Die Test maschine wird zu testen des spieles genutzt. Sie gibt einem free items + ( + "test".to_string(), + Rect { + x: 284.0, + y: 230.0, + w: 100.0, + h: 100.0, + }, + vec![ + Trade::new( + "free_items".to_string(), + 10, + State::Broken, + State::Idle, + true, + gen_inventory(-100, -100, -100), + ), + Trade::new( + "reset_items".to_string(), + 0, + State::Idle, + State::Running, + true, + gen_inventory(100, 97, 99), + ), + Trade::new( + "free_items".to_string(), + 0, + State::Running, + State::Idle, + true, + gen_inventory(-100, -100, -100), + ), + ], + Resources { + oxygen: -25, + energy: -25, + life: -4, + }, + ), + //Definition Oxygen Maschine + ( + "Oxygen".to_string(), + Rect { + x: 600.0, + y: 250.0, + w: 100.0, + h: 100.0, + }, + vec![ + Trade::new( + "repair_Oxygen".to_string(), + 100, + State::Broken, + State::Idle, + false, + gen_inventory(2, 0, 0), + ), + Trade::new( + "start_Oxygen".to_string(), + 0, + State::Idle, + State::Running, + true, + gen_inventory(0, 0, 0), + ), + Trade::new( + "stop_Oxygen".to_string(), + 0, + State::Running, + State::Idle, + true, + gen_inventory(0, 0, 0), + ), + ], + Resources { + oxygen: 20, + energy: -30, + life: 0, + }, + ), + //Definition Stromgenerator Maschine + ( + "Stromgenerator".to_string(), + Rect { + x: 284.0, + y: 740.0, + w: 200.0, + h: 200.0, + }, + vec![ + Trade::new( + "fueling_Stromgenerator".to_string(), + 1000, + State::Broken, + State::Running, + true, + gen_inventory(0, 1, 0), + ), + Trade::new( + "start_Stromgenerator".to_string(), + 1, + State::Idle, + State::Running, + true, + gen_inventory(0, 0, 0), + ), + Trade::new( + "stop_Stromgenerator".to_string(), + 0, + State::Running, + State::Idle, + true, + gen_inventory(0, 0, 0), + ), + ], + Resources { + oxygen: -5, + energy: 50, + life: 0, + }, + ), + //Definition werkermaschine Maschine + ( + "werkermaschine".to_string(), + Rect { + x: 600.0, + y: 600.0, + w: 200.0, + h: 100.0, + }, + vec![ + Trade::new( + "repair_werkermaschine".to_string(), + 100, + State::Broken, + State::Idle, + false, + gen_inventory(0, 0, 1), + ), + Trade::new( + "produce_superglue".to_string(), + 120, + State::Idle, + State::Running, + true, + gen_inventory(-1, 0, 0), + ), + ], + Resources { + oxygen: 0, + energy: -15, + life: 0, + }, + ), + //Definition 3d_printer Maschine + ( + "3d_printer".to_string(), + Rect { + x: 1722.0, + y: 840.0, + w: 100.0, + h: 100.0, + }, + vec![ + Trade::new( + "repair_3d_printer".to_string(), + 300, + State::Broken, + State::Idle, + false, + gen_inventory(2, 1, 0), + ), + Trade::new( + "produce_3d_teil".to_string(), + 200, + State::Idle, + State::Running, + true, + gen_inventory(2, 0, -1), + ), + ], + Resources { + oxygen: 0, + energy: -25, + life: 0, + }, + ), + //Definition Kommunikationsmodul Maschine + ( + "Kommunikationsmodul".to_string(), + Rect { + x: 1000.0, + y: 230.0, + w: 300.0, + h: 100.0, + }, + vec![ + Trade::new( + "Kommunikationsmodul_reparieren".to_string(), + 400, + State::Broken, + State::Idle, + false, + gen_inventory(5, 0, 3), + ), + Trade::new( + "Notfall_signal_absetzen".to_string(), + 1000, + State::Idle, + State::Running, + true, + gen_inventory(0, 0, 0), + ), + ], + Resources { + oxygen: 0, + energy: -20, + life: 0, + }, + ), + //Definition vom ersten Loch + ( + "Loch".to_string(), + Rect { + x: 1722.0, + y: 230.0, + w: 100.0, + h: 100.0, + }, + vec![Trade::new( + "repair_Loch".to_string(), + 100, + State::Running, + State::Idle, + false, + gen_inventory(2, 0, 0), + )], + Resources { + oxygen: -20, + energy: -5, + life: -2, + }, + ), + ] +} diff --git a/game/src/backend/error.rs b/game/src/backend/error.rs index 3ad8433..c0850d3 100644 --- a/game/src/backend/error.rs +++ b/game/src/backend/error.rs @@ -11,6 +11,7 @@ pub enum RLError { AssetError(String), Deserialization(serde_yaml::Error), IO(io::Error), + InitError(String), } impl From for RLError { @@ -39,7 +40,7 @@ impl From> for RLError { error!("Could not send StackCommand: {}", value); RLError::IO(io::Error::new( io::ErrorKind::Other, - format!("Could not send StackCommand: {}", value), + format!("Could not send StackCommand: {value}"), )) } } diff --git a/game/src/backend/gamestate.rs b/game/src/backend/gamestate.rs index addbb79..c3efe4f 100644 --- a/game/src/backend/gamestate.rs +++ b/game/src/backend/gamestate.rs @@ -1,92 +1,167 @@ -use crate::backend::area::Area; +//! Contains the game logic, updates the game and draws the current board +use crate::backend::constants::{COLORS, HANDBOOK_TEXT}; +use crate::backend::constants::{DESIRED_FPS, MAP_BORDER, RESOURCE_POSITION}; use crate::backend::rlcolor::RLColor; -use crate::backend::screen::StackCommand; -use crate::backend::utils::{get_scale, is_colliding}; +use crate::backend::screen::{Popup, StackCommand}; +use crate::backend::utils::get_scale; +use crate::backend::utils::*; use crate::backend::{error::RLError, screen::Screen}; -use crate::game_core::deathscreen::DeathScreen; use crate::game_core::event::Event; +use crate::game_core::infoscreen::DeathReason::Both; +use crate::game_core::infoscreen::InfoScreen; +use crate::game_core::item::Item; use crate::game_core::player::Player; use crate::game_core::resources::Resources; -use crate::machines::machine::Maschine; +use crate::languages::german::RESOURCE_NAME; +use crate::machines::machine::Machine; use crate::machines::machine::State::Broken; use crate::{draw, RLResult}; use ggez::glam::Vec2; -use ggez::graphics::{Canvas, Color, Image}; +use ggez::graphics::{Canvas, Image, TextFragment}; use ggez::graphics::{DrawMode, Mesh, Rect}; +use ggez::input::mouse::CursorIcon::Text; use ggez::{graphics, Context}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fs; use std::fs::read_dir; -use std::sync::mpsc::Sender; +use std::sync::mpsc::{channel, Receiver, Sender}; use tracing::info; -const RESOURCE_POSITION: [f32; 3] = [316.0, 639.0, 1373.0]; -const RESOURCE_NAME: [&str; 3] = ["Luft", "Energie", "Leben"]; -const COLORS: [Color; 3] = [RLColor::BLUE, RLColor::GOLD, RLColor::DARK_RED]; +pub enum GameCommand { + AddItems(Vec<(Item, i32)>), + ResourceChange(Resources), + Milestone(), + Winning(), +} + /// This is the game state. It contains all the data that is needed to run the game. #[derive(Debug, Default, Serialize, Deserialize)] pub struct GameState { /// Contains the current player position, resources(air, energy, life) and the inventory and their change rates pub player: Player, - pub machines: Vec, - events: Option, + pub(crate) events: Vec, + pub machines: Vec, #[serde(skip)] assets: HashMap, #[serde(skip)] - areas: Vec>, - #[serde(skip)] pub(crate) screen_sender: Option>, + #[serde(skip)] + pub(crate) receiver: Option>, + #[serde(skip)] + pub(crate) sender: Option>, + pub handbook_visible: bool, } impl PartialEq for GameState { fn eq(&self, other: &Self) -> bool { - self.player == other.player - && self.player.milestone == other.player.milestone - && self.machines == other.machines + self.player == other.player && self.player.milestone == other.player.milestone } } impl GameState { + pub(crate) fn get_screen_sender(&mut self) -> RLResult<&mut Sender> { + self.screen_sender.as_mut().ok_or(RLError::InitError( + "No Screen Sender found. The game was not initialized properly".to_string(), + )) + } + + pub(crate) fn get_receiver(&mut self) -> RLResult<&Receiver> { + self.receiver.as_ref().ok_or(RLError::InitError( + "No Receiver found. The game was not initialized properly".to_string(), + )) + } + + /// Creates a new game state at the beginning of the game and after every loading. + /// It loads all the assets and creates the areas of the machines. pub fn new(ctx: &mut Context) -> RLResult { info!("Creating new gamestate"); + let (sender, receiver) = channel(); let mut result = GameState::default(); - result.load_assets(ctx)?; + result.sender = Some(sender); + result.receiver = Some(receiver); + result.init(ctx)?; Ok(result) } - pub fn tick(&mut self) -> RLResult { - // Iterate over every resource and add the change rate to the current value - self.get_current_milestone(); - self.player.resources = Resources::from_iter( - self.player - .resources - .into_iter() - .zip(self.player.resources_change.into_iter()) - .map(|(a, b)| a.saturating_add_signed(b)), - ); - // Check if player is able to regenerate life - self.player - .life_regeneration(self.screen_sender.as_ref().unwrap().clone()); - // Check if the player is dead - if let Some(empty_resource) = Resources::get_death_reason(&self.player.resources) { - self.player.resources_change.life = -10; - if self.player.resources.life == 0 { - let gamestate = GameState::load(true).unwrap_or_default(); - gamestate.save(false).unwrap(); - let cloned_sender = self.screen_sender.as_mut().unwrap().clone(); - self.screen_sender - .as_mut() - .expect("No screen sender") - .send(StackCommand::Push(Box::new(DeathScreen::new( - empty_resource, - cloned_sender, - ))))?; - }; + /// Gets called every tick in the update fn to update the internal game logic. + /// It updates the player resources, checks on the current milestone if the player has reached a new one + /// and checks if the player has died. + pub fn tick(&mut self, ctx: &mut Context) -> RLResult { + // TODO: Remove this if fixed + assert!(self.receiver.is_some(), "No receiver found"); + // Update Resources + self.player.resources = self + .player + .resources + .into_iter() + .zip(self.player.resources_change.into_iter()) + .map(|(a, b)| a.saturating_add_signed(b)) + .collect::>(); + + // Everything inside will only be checked every 15 ticks + match ctx.time.ticks() % 15 { + 3 => { + // Check if the player is dead + if let Some(empty_resource) = Resources::get_death_reason(&self.player.resources) { + match empty_resource { + Both => self.player.resources_change.life = -20, + _ => self.player.resources_change.life = -10, + } + if self.player.resources.life == 0 { + let gamestate = GameState::load(true).unwrap_or_default(); + gamestate.save(false)?; + let cloned_sender = self.get_screen_sender()?.clone(); + self.get_screen_sender()?.send(StackCommand::Push(Box::new( + InfoScreen::new_deathscreen(empty_resource, cloned_sender), + )))?; + }; + } else if self.player.resources_change.life < 0 { + self.player.resources_change.life = 0; + } + } + 9 => { + // process received GameCommands + if let Ok(msg) = self.get_receiver()?.try_recv() { + match msg { + GameCommand::ResourceChange(new_rs) => { + self.player.resources_change = self.player.resources_change + new_rs; + } + GameCommand::AddItems(items) => { + for (item, amount) in &items { + self.player.add_item(item, *amount); + } + } + GameCommand::Milestone() => { + self.get_current_milestone(ctx); + } + GameCommand::Winning() => match self.player.milestone { + 0 => { + let sender = self.get_screen_sender()?; + let popup = Popup::new(RLColor::GREEN, "Die Nachricht kann nicht gesendet werden solange das System nicht wiederhergestellt ist".to_string(), 5); + sender.send(StackCommand::Popup(popup)).unwrap() + } + 1 => { + self.player.milestone += 1; + self.get_current_milestone(ctx) + } + _ => {} + }, + _ => {} + }; + } + } + _ => {} } + // Regenerate life if applicable + self.player + .life_regeneration(&self.screen_sender.as_ref().unwrap().clone()); + self.machines.iter_mut().for_each(|machine| machine.tick(1)); + Ok(()) } - /// Paints the current resource level of air, energy and life as a bar on the screen. + /// Paints the current resource level of air, energy and life as a bar on the screen and + /// draws the amount of every resource in the inventory. fn draw_resources(&self, canvas: &mut Canvas, scale: Vec2, ctx: &mut Context) -> RLResult { self.player .resources @@ -116,6 +191,42 @@ impl GameState { .for_each(drop); Ok(()) } + /// draws the handbook while pressing the H key + pub fn draw_handbook(&self, canvas: &mut Canvas, ctx: &mut Context) { + let scale = get_scale(ctx); + let image = self.assets.get("Handbook.png").unwrap(); + draw!(canvas, image, Vec2::new(700.0, 300.0), scale); + let trade_text = self + .machines + .iter() + .map(|machine| { + machine + .trades + .iter() + .map(|trade| (trade.name.clone(), trade.cost.clone())) + }) + .flatten() + .collect::)>>(); + trade_text + .into_iter() + .enumerate() + .for_each(|(number, trade)| { + let mut text = format!("{}: ", trade.0); + trade.1.iter().for_each(|item| { + text.push_str(&format!("{}: {}, ", item.0.name, item.1)); + }); + let mut graphic_text = + graphics::Text::new(TextFragment::new(text).color(RLColor::BLACK)); + graphic_text.set_scale(15.0); + draw!( + canvas, + &graphic_text, + Vec2::new(800.0, 400.0 + (number * 20) as f32), + scale + ); + }); + } + /// Iterates trough the inventory and draws the amount of every item in the inventory. fn draw_items(&self, canvas: &mut Canvas, ctx: &mut Context) -> RLResult { self.player .inventory @@ -143,7 +254,7 @@ impl GameState { Ok(()) } /// Loads the assets. Has to be called before drawing the game. - pub(crate) fn load_assets(&mut self, ctx: &mut Context) -> RLResult { + pub(crate) fn init(&mut self, ctx: &mut Context) -> RLResult { info!("Loading assets"); read_dir("assets")?.for_each(|file| { let file = file.unwrap(); @@ -152,14 +263,57 @@ impl GameState { self.assets .insert(name, Image::from_bytes(ctx, bytes.as_slice()).unwrap()); }); + if self.assets.is_empty() { return Err(RLError::AssetError("Could not find assets!".to_string())); } + let (sender, receiver) = channel(); + self.sender = Some(sender); + self.receiver = Some(receiver); Ok(()) } + pub(crate) fn init_all_machines(&mut self) { + let machine_assets: Vec> = self + .machines + .iter() + .map(|m| m.name.clone()) + .map(|name| { + if self.assets.contains_key(&format!("{name}.png")) { + vec![self.assets.get(&format!("{name}.png")).unwrap().clone()] + } else { + vec![ + self.assets + .get(&format!("{name}_Broken.png")) + .unwrap() + .clone(), + self.assets + .get(&format!("{name}_Idle.png")) + .unwrap() + .clone(), + self.assets + .get(&format!("{name}_Running.png")) + .unwrap() + .clone(), + ] + } + }) + .collect(); + self.machines + .iter_mut() + .zip(machine_assets) + .for_each(|(m, a)| { + m.init( + a.as_slice(), + self.sender.clone().unwrap(), + self.screen_sender.clone().unwrap(), + ); + }); + } /// Saves the active game state to a file. The boolean value "milestone" determines whether this is a milestone or an autosave. /// If the file already exists, it will be overwritten. + /// # Arguments + /// * `milestone` - Boolean value that determines whether this is a milestone save or an autosave. pub(crate) fn save(&self, milestone: bool) -> RLResult { let save_data = serde_yaml::to_string(self)?; // Create the folder if it doesn't exist @@ -175,6 +329,8 @@ impl GameState { } /// Loads a game state from a file. The boolean value "milestone" determines whether this is a milestone or an autosave. /// If the file doesn't exist, it will return a default game state. + /// # Arguments + /// * `milestone` - Whether to load the milestone or the autosave pub fn load(milestone: bool) -> RLResult { let save_data = if milestone { info!("Loading milestone..."); @@ -183,73 +339,102 @@ impl GameState { info!("Loading autosave..."); fs::read_to_string("./saves/autosave.yaml") }?; - let game_state: GameState = serde_yaml::from_str(&save_data)?; + let mut game_state: GameState = serde_yaml::from_str(&save_data)?; + Ok(game_state) } - - pub(crate) fn get_interactable(&self) -> Option<&Box> { - self.areas - .iter() - .find(|area| area.is_interactable(self.player.position)) + /// Returns the area the player needs to stand in to interact with a machine + pub(crate) fn get_interactable(&mut self) -> Option<&mut Machine> { + self.machines + .iter_mut() + .find(|machine| machine.is_interactable(self.player.position)) } /// Returns if the player would collide with a border if they moved in the given direction + /// # Arguments + /// * `next_player_pos` - The direction the player wants to move fn border_collision_detection(next_player_pos: (usize, usize)) -> bool { - next_player_pos.0 >= 1750 // Right border - || next_player_pos.1 >= 850 // Bottom border - || next_player_pos.0 <= 255 // Left border - || next_player_pos.1 <= 220 // Top border + next_player_pos.0 >= MAP_BORDER[0] // Right border + || next_player_pos.1 >= MAP_BORDER[1] // Bottom border + || next_player_pos.0 <= MAP_BORDER[2] // Left border + || next_player_pos.1 <= MAP_BORDER[3] // Top border } /// Returns a boolean indicating whether the player would collide with a machine or border if they moved in the given direction /// /// # Arguments /// * `next_player_pos` - A tuple containing the next position of the player pub(crate) fn collision_detection(&self, next_player_pos: (usize, usize)) -> bool { - self.areas + self.machines .iter() - .map(|area| area.get_collision_area()) + .map(|area| area.hitbox) .any(|area| is_colliding(next_player_pos, &area)) || Self::border_collision_detection(next_player_pos) } /// Returns the asset if it exists - fn get_asset(&self, name: &str) -> RLResult<&Image> { + /// # Arguments + /// * `name` - The name of the asset + pub fn get_asset(&self, name: &str) -> RLResult<&Image> { self.assets.get(name).ok_or(RLError::AssetError(format!( "Could not find asset with name {}", name ))) } - pub fn check_on_milestone(&mut self, milestone_machines: Vec) { + /// Checks if the milestone is reached which means the vec of repaired machines + /// contain the vec of machines needed to reach the next milestone. + /// # Arguments + /// * `milestone_machines` - A vec of machines needed to reach the next milestone + pub fn check_on_milestone_machines(&mut self, milestone_machines: Vec) -> bool { + //let a = self.areas.get(0).unwrap().deref(); erst einfügen, wenn man es auch benutzt + let running_machine = self .machines .iter() - .filter(|machine| machine.state != Broken) - .map(|m| m.name.clone()) - .collect::>(); + .filter(|m| m.state != Broken) + .map(|m| &m.name) + .collect::>(); + if milestone_machines .iter() - .all(|machine| running_machine.contains(&machine.to_string())) + .all(|machine| running_machine.contains(&machine)) { - self.player.milestone += 1; - info!("Player reached milestone {}", self.player.milestone); - self.save(true).unwrap(); + return true; } + return false; } - fn get_current_milestone(&mut self) { + /// Decides what happens if a certain milestone is reached + /// divided into 3 milestones + fn get_current_milestone(&mut self, ctx: &mut Context) { match self.player.milestone { - 1 => { + 0 => { if self.player.match_milestone == 0 { self.player.resources_change.oxygen = -1; self.player.resources_change.energy = -1; self.player.last_damage = 0; + self.events = Vec::new(); self.player.match_milestone = 1; } - self.check_on_milestone(vec![ - "Sauerstoffgenerator".to_string(), + if self.check_on_milestone_machines(vec![ + "Oxygen".to_string(), "Stromgenerator".to_string(), - ]); + ]) { + self.player.milestone += 1; + info!("Player reached milestone {}", self.player.milestone); + self.save(true).unwrap(); + } } 2 => { - self.check_on_milestone(vec!["Kommunikationsmodul".to_string()]); + info!("Player won the Game"); + self.player.milestone += 1; + self.save(true).unwrap(); + self.save(false).unwrap(); + let cloned_sender = self.screen_sender.as_mut().unwrap().clone(); + self.screen_sender + .as_mut() + .expect("No Screensender") + .send(StackCommand::Push(Box::new(InfoScreen::new_winningscreen( + cloned_sender, + )))) + .expect("Show Winning Screen"); } _ => {} } @@ -272,12 +457,12 @@ impl GameState { } impl Screen for GameState { - /// Updates the game and handles input. Returns StackCommand::Pop when Escape is pressed. + /// Updates the game and handles input. Returns `StackCommand::Pop` when Escape is pressed. fn update(&mut self, ctx: &mut Context) -> RLResult { - const DESIRED_FPS: u32 = 60; if ctx.time.check_update_time(DESIRED_FPS) { - self.tick()?; + self.tick(ctx)?; self.move_player(ctx)?; + Event::update_events(ctx, self); } Ok(()) } @@ -295,17 +480,23 @@ impl Screen for GameState { scale ); self.draw_resources(&mut canvas, scale, ctx)?; + self.draw_machines(&mut canvas, scale, ctx)?; self.draw_items(&mut canvas, ctx)?; + if self.handbook_visible { + self.draw_handbook(&mut canvas, ctx); + } #[cfg(debug_assertions)] { let fps = graphics::Text::new(format!("FPS: {}", ctx.time.fps())); draw!(canvas, &fps, Vec2::new(0.0, 0.0), scale); } + canvas.finish(ctx)?; Ok(()) } fn set_sender(&mut self, sender: Sender) { self.screen_sender = Some(sender); + self.init_all_machines(); } } @@ -341,6 +532,7 @@ mod test { GameState::default().save(true).unwrap(); let _gamestate_loaded = GameState::load(true).unwrap(); } + #[test] fn test_delete_saves() { GameState::delete_saves().unwrap(); diff --git a/game/src/backend/generate_machines.rs b/game/src/backend/generate_machines.rs new file mode 100644 index 0000000..099bf1f --- /dev/null +++ b/game/src/backend/generate_machines.rs @@ -0,0 +1,78 @@ +//!DIESE DATEI IST ZUM TESTEN VON SANDER +use crate::backend::gamestate::GameState; + +use crate::machines::machine::Machine; + +use crate::backend::constants::gen_all_machines; +use crate::backend::rlcolor::RLColor; +use crate::backend::utils::*; +use crate::{draw, RLResult}; +use ggez::glam::Vec2; +use ggez::graphics::{Canvas, Mesh, Rect}; +use ggez::Context; +use tracing::info; + +impl GameState { + pub fn create_machine(&mut self) { + info!("Generating all Machines"); + let all = gen_all_machines(); + for m in &all { + //code can panic @cargo bene fix + let new_ms = Machine::new_by_const(m.clone()); + // self.inti_machine(&mut new_ms); + /* if new_ms.name == *"Loch" { + new_ms.change_state_to(&Running); + }*/ + self.machines.push(new_ms); + } + } + + pub fn draw_machines(&self, canvas: &mut Canvas, scale: Vec2, ctx: &mut Context) -> RLResult { + for machine in &self.machines { + let image = machine.get_graphic(); + let mut pos = Vec2 { + x: machine.hitbox.x, + y: machine.hitbox.y, + }; + draw!(canvas, image, pos, scale); + // Draws the machine status on top of the machine + let status = Mesh::new_circle( + ctx, + ggez::graphics::DrawMode::fill(), + Vec2::new(0., 0.), + 15.0, + 0.1, + machine.state.clone().into(), + )?; + // Draws the machine timer on top of the machine + let time = machine.get_time_percentage(); + + pos.x += 20.; + pos.y += 20.; + draw!(canvas, &status, pos, scale); + if time > 0. { + // Bar for machine Timer + pos.x += 40.; + pos.y -= 30.; + let rect1 = Mesh::new_rounded_rectangle( + ctx, + ggez::graphics::DrawMode::fill(), + Rect::new(0.0, 0.0, 100.0, 10.0), + 15., + RLColor::DARK_GREY, + )?; + draw!(canvas, &rect1, pos, scale); + // Bar of current time + let rect2 = Mesh::new_rounded_rectangle( + ctx, + ggez::graphics::DrawMode::fill(), + Rect::new(0.0, 0.0, 100.0 * time, 10.0), + 15., + RLColor::BLACK, + )?; + draw!(canvas, &rect2, pos, scale); + } + } + Ok(()) + } +} diff --git a/game/src/backend/mod.rs b/game/src/backend/mod.rs index 252766c..34e71db 100644 --- a/game/src/backend/mod.rs +++ b/game/src/backend/mod.rs @@ -1,9 +1,9 @@ -pub(crate) mod area; pub(crate) mod asset_service; +pub(crate) mod constants; pub(crate) mod error; pub(crate) mod gamestate; +pub(crate) mod generate_machines; pub(crate) mod movement; -pub(crate) mod popup_messages; pub(crate) mod rlcolor; pub(crate) mod screen; pub(crate) mod utils; diff --git a/game/src/backend/movement.rs b/game/src/backend/movement.rs index d8c5685..580c02d 100644 --- a/game/src/backend/movement.rs +++ b/game/src/backend/movement.rs @@ -1,6 +1,5 @@ use crate::backend::gamestate::GameState; use crate::backend::screen::StackCommand; - use crate::RLResult; use ggez::winit::event::VirtualKeyCode; use ggez::Context; @@ -9,18 +8,28 @@ use tracing::info; const MOVEMENT_SPEED: usize = 10; impl GameState { + /// Handles the player movement and updates the player position + /// Checks on every move if the next step is inside the borders of the map if not it will not move + /// Handles escape which will pause the game and go to the main menu pub fn move_player(&mut self, ctx: &mut Context) -> RLResult { + if ctx.keyboard.is_key_just_pressed(VirtualKeyCode::Escape) { + info!("Exiting..."); + self.save(false)?; + self.get_screen_sender()?.send(StackCommand::Pop)?; + } + if ctx.keyboard.is_key_just_pressed(VirtualKeyCode::E) { + info!("Interacting with Area: {:?}", self.get_interactable()); + let player_ref = &mut self.player.clone(); + if let Some(interactable) = self.get_interactable() { + self.player = interactable.interact(player_ref); + } + } + if ctx.keyboard.is_key_just_pressed(VirtualKeyCode::H) { + self.handbook_visible = !self.handbook_visible; + } let keys = ctx.keyboard.pressed_keys(); for key in keys.iter() { match key { - VirtualKeyCode::Escape => { - info!("Escape pressed"); - self.save(false)?; - self.screen_sender - .as_mut() - .unwrap() - .send(StackCommand::Pop)?; - } VirtualKeyCode::W => { if !self.collision_detection(( self.player.position.0, @@ -57,10 +66,6 @@ impl GameState { self.player.position.0.saturating_add(MOVEMENT_SPEED); } } - // TODO: Interact with the possible area - VirtualKeyCode::E => { - info!("In interaction area: {:?}", self.get_interactable()); - } _ => {} } } diff --git a/game/src/backend/popup_messages.rs b/game/src/backend/popup_messages.rs deleted file mode 100644 index 57a531b..0000000 --- a/game/src/backend/popup_messages.rs +++ /dev/null @@ -1,22 +0,0 @@ -/// This is the text for the popups that appears in the top left corner. - -pub const WARNINGS: [&str; 4] = [ - "$m : Ein Meteorit ist auf dem Weg!", - "$m : Der Strom ist ausgefallen!", - "$m : Ein Sandsturm ist auf dem Weg!", - "Machine $m ist ausgefallen!", -]; -pub const MARS_INFO: [&str; 5] = [ - "Der Mars ist der 4. Planet in unserem Sonnensystem", - "Der Mars zählt zu den erdähnlichen Planeten", - "Der Durchmesser des Mars beträgt knapp 6800 km", - "Die Masse des Mars beträgt etwa ein Zehntel der Erdmasse", - "Die Entfernung zum Mars beträgt durchschnittlich 228 Millionen km", -]; -pub const NASA_INFO: [&str; 5] = [ - "NASA steht für: National Aeronautics and Space Administration", - "Die NASA wurde 1958 gegründet", - "Die NASA hat ihren Sitz in Washington D.C.", - "Im Rahmen der Apollo-Missionen gelang es der NASA, den ersten Menschen auf den Mond zu bringen"," Die NASA hat über 17.000 Mitarbeiter" -]; -pub const GAME_INFO: [&str; 1] = ["Lebensregeneration gestartet"]; diff --git a/game/src/backend/rlcolor.rs b/game/src/backend/rlcolor.rs index 329866b..3ba0730 100644 --- a/game/src/backend/rlcolor.rs +++ b/game/src/backend/rlcolor.rs @@ -1,5 +1,5 @@ +//! Contains the color definitions for the game. Every color is defined as a constant. use ggez::graphics::Color; - pub struct RLColor {} impl RLColor { @@ -69,4 +69,22 @@ impl RLColor { b: 0., a: 1., }; + pub const STATUS_GREEN: Color = Color { + r: 0., + g: 1., + b: 0., + a: 1., + }; + pub const STATUS_YELLOW: Color = Color { + r: 1., + g: 1., + b: 0., + a: 1., + }; + pub const STATUS_RED: Color = Color { + r: 1., + g: 0., + b: 0., + a: 1., + }; } diff --git a/game/src/backend/screen.rs b/game/src/backend/screen.rs index ebbe402..27a3413 100644 --- a/game/src/backend/screen.rs +++ b/game/src/backend/screen.rs @@ -1,5 +1,5 @@ use crate::backend::rlcolor::RLColor; -use crate::backend::utils::get_scale; +use crate::backend::utils::*; use crate::error::RLError; use crate::main_menu::mainmenu::MainMenu; use crate::{draw, RLResult}; @@ -12,9 +12,10 @@ use std::sync::mpsc::{channel, Receiver, Sender}; use std::time::Instant; use tracing::info; -/// A screen is every drawable object in the game, so the main menu is a screen too +/// Screens are used to facilitate drawing menus, the game etc. to the screen. They can also send +/// back command to the `ScreenStack` to change the current screen. pub trait Screen: Debug { - /// Used for updating the screen. Returns a StackCommand used to either push a new screen or pop + /// Used for updating the screen. Returns a `StackCommand` used to either push a new screen or pop /// the current one. fn update(&mut self, ctx: &mut Context) -> RLResult; /// Used for drawing the last screen in the game. @@ -23,14 +24,15 @@ pub trait Screen: Debug { fn set_sender(&mut self, sender: Sender); } -/// A Screenstack contains multiple screens, the first one of which is the current screen +/// A Screenstack contains multiple screens, the first one of which is drawn to the screen and +/// updated. pub struct Screenstack { screens: Vec>, popup: Vec, receiver: Receiver, sender: Sender, } -/// Popups are used to display ingame information/notification on screen (toplevel) +/// Popups are used to display information sent by the game on screen (toplevel) #[derive(Debug, PartialEq, Clone)] pub struct Popup { color: Color, @@ -44,12 +46,17 @@ impl Popup { } pub fn mars(text: String) -> Self { info!("New MARS popup created"); - Self::new(RLColor::LIGHT_GREY, text, 10) + Self::new(RLColor::DARK_RED, text, 10) } pub fn warning(text: String) -> Self { info!("New WARNING popup created"); Self::new(RLColor::RED, text, 10) } + pub fn info(text: String) -> Self { + info!("New INFO popup created"); + Self::new(RLColor::BLACK, text, 10) + } + pub(crate) fn new(color: Color, text: String, duration: u64) -> Self { info!("New popup created: text: {}, duration: {}", text, duration); Self { @@ -60,10 +67,12 @@ impl Popup { } } impl Screenstack { + /// Draws a new popup at the top left of the screen with the given text and color + /// The popup will be removed after the given duration fn draw_popups(&mut self, ctx: &mut Context) -> RLResult { let mut canvas = graphics::Canvas::from_frame(ctx, None); + let scale = get_scale(ctx); for (pos, popup) in self.popup.iter().enumerate() { - let scale = get_scale(ctx); let mut text = graphics::Text::new(popup.text.clone()); text.set_scale(18.); let dimensions = text.measure(ctx)?; @@ -72,28 +81,36 @@ impl Screenstack { let rect = graphics::Mesh::new_rectangle( ctx, graphics::DrawMode::fill(), - graphics::Rect::new(0., 0., x + 2., y + 2.), + graphics::Rect::new(0., 0., x + 4., y + 4.), RLColor::LIGHT_GREY, )?; let outer = graphics::Mesh::new_rectangle( ctx, graphics::DrawMode::stroke(3.), - graphics::Rect::new(0., 0., x + 3., y + 3.), + graphics::Rect::new(0., 0., x + 4., y + 4.), RLColor::BLACK, )?; - draw!(canvas, &rect, vec2(0., pos as f32 * 100.), scale); - draw!(canvas, &outer, vec2(0., pos as f32 * 100.), scale); + draw!(canvas, &rect, vec2(0., pos as f32 * (y + 4.)), scale); + draw!(canvas, &outer, vec2(0., pos as f32 * (y + 4.)), scale); canvas.draw( &text, graphics::DrawParam::default() .scale(scale) - .dest([0., pos as f32 * 100. * scale.y]) + .dest([0., pos as f32 * (y + 4.) * scale.y]) .color(popup.color), ); } canvas.finish(ctx)?; Ok(()) } + /// Handles what to do with the given commands (Push, Pop, None) + /// + /// # Arguments + /// * `command` - The command to handle + /// + /// Push: Pushes a new screen on the stack, + /// Pop: Pops the current screen, + /// None: Does nothing fn process_command(&mut self, command: StackCommand) { // Match the command given back by the screen match command { @@ -111,6 +128,7 @@ impl Screenstack { StackCommand::Popup(popup) => self.popup.push(popup), } } + /// removes the expired popups fn remove_popups(&mut self) { self.popup.retain(|popup| popup.expiration > Instant::now()); } @@ -126,7 +144,7 @@ pub enum StackCommand { } impl event::EventHandler for Screenstack { - // Redirect the update function to the last screen and handle the returned StackCommand + /// Redirect the update function to the last screen and handle the returned StackCommand fn update(&mut self, ctx: &mut Context) -> RLResult { self.remove_popups(); self.screens @@ -147,9 +165,8 @@ impl event::EventHandler for Screenstack { self.draw_popups(ctx)?; Ok(()) } - /// Override the quit event so we don't actually quit the game. - fn quit_event(&mut self, ctx: &mut Context) -> RLResult { - self.screens.last_mut().unwrap().update(ctx)?; + /// Overrides the quit event so we do nothing instead of quitting the game. + fn quit_event(&mut self, _ctx: &mut Context) -> RLResult { Ok(true) } } diff --git a/game/src/backend/utils.rs b/game/src/backend/utils.rs index 03a4c9a..b3a6a10 100644 --- a/game/src/backend/utils.rs +++ b/game/src/backend/utils.rs @@ -1,7 +1,10 @@ +//! miscellaneous utilities used by the backend +use crate::backend::constants::{PLAYER_ICON_SIZE, SCREEN_RESOLUTION}; +use crate::game_core::item::Item; +use crate::languages::german::{BENZIN, GEDRUCKTESTEIL, SUPER_GLUE}; use ggez::glam::Vec2; -use ggez::graphics::Rect; +use ggez::graphics::{Color, Rect}; use ggez::Context; -use std::cmp::{max, min}; /// This returns the scale so we can have resolution-agnostic scaling /// Use it in your drawing calls like this: @@ -12,14 +15,25 @@ use std::cmp::{max, min}; #[inline(always)] pub fn get_scale(ctx: &Context) -> Vec2 { let (width, height) = ctx.gfx.drawable_size(); - Vec2::new(width / 1920., height / 1080.) + Vec2::new(width / SCREEN_RESOLUTION.0, height / SCREEN_RESOLUTION.1) } /// Returns if the player would collide with a machine if they moved in the given direction #[inline(always)] pub fn is_colliding(player_pos: (usize, usize), area: &Rect) -> bool { - max(area.x as usize, player_pos.0) <= min((area.x + area.w) as usize, player_pos.0 + 41) - && max(area.y as usize, player_pos.1) <= min((area.y + area.h) as usize, player_pos.1 + 50) + area.x < player_pos.0 as f32 + PLAYER_ICON_SIZE.0 as f32 + && area.x + area.w > player_pos.0 as f32 + && area.y < player_pos.1 as f32 + PLAYER_ICON_SIZE.1 as f32 + && area.y + area.h > player_pos.1 as f32 +} +///Returns a Inventory with set sizen for all items +#[inline(always)] +pub fn gen_inventory(super_glue: i32, benzin: i32, gedrucktesteil: i32) -> Vec<(Item, i32)> { + vec![ + (Item::new(SUPER_GLUE), super_glue), + (Item::new(BENZIN), benzin), + (Item::new(GEDRUCKTESTEIL), gedrucktesteil), + ] } /// This macro is used for simplifying drawing with scaling. @@ -27,17 +41,27 @@ pub fn is_colliding(player_pos: (usize, usize), area: &Rect) -> bool { #[macro_export] macro_rules! draw { ($canvas: expr, $asset: expr, $position: expr, $scale: expr) => { - $canvas.draw( - $asset, - ggez::graphics::DrawParam::default() - .dest($position * $scale) - .scale($scale), - ); + $canvas.draw($asset, get_draw_params(Some($position), $scale, None)) }; ($canvas: expr, $drawable: expr, $scale: expr) => { - $canvas.draw( - $drawable, - ggez::graphics::DrawParam::default().scale($scale), - ); + $canvas.draw($drawable, get_draw_params(None, $scale, None)) + }; + ($canvas: expr, $asset: expr, $position: expr, $scale: expr, $color: expr) => { + $canvas.draw($asset, get_draw_params($position, $scale, $color)) }; } + +pub fn get_draw_params( + position: Option, + scale: Vec2, + color: Option, +) -> ggez::graphics::DrawParam { + let mut param = ggez::graphics::DrawParam::new().scale(scale); + if let Some(pos) = position { + param = param.dest(pos * scale); + } + if let Some(col) = color { + param = param.color(col); + } + param +} diff --git a/game/src/game_core/deathscreen.rs b/game/src/game_core/deathscreen.rs deleted file mode 100644 index 813cc12..0000000 --- a/game/src/game_core/deathscreen.rs +++ /dev/null @@ -1,97 +0,0 @@ -use crate::backend::screen::{Screen, StackCommand}; -use crate::backend::utils::get_scale; -use crate::main_menu::button::Button; -use crate::main_menu::mainmenu::MainMenu; -use crate::{draw, RLResult}; -use ggez::glam::Vec2; -use ggez::winit::event::VirtualKeyCode; -use ggez::{graphics, Context}; -use std::fmt::{Debug, Display, Formatter}; -use std::sync::mpsc::Sender; -use tracing::info; - -/// Create DeathScreen using deathscreen::new() and pass reason of death from DeathReason enum. -/// # Example -/// StackCommand::Push(Box::new(deathscreen::new(death_reason: DeathReason::Oxygen)?)) - -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum DeathReason { - Oxygen, - Energy, -} -impl Display for DeathReason { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - DeathReason::Oxygen => write!(f, "Luft"), - DeathReason::Energy => write!(f, "Energie"), - } - } -} -/// Deathscreen, telling the user why they died. -#[derive(Debug)] -pub struct DeathScreen { - buttons: Vec