diff --git a/Readme.md b/Readme.md index ce895db..18440c9 100644 --- a/Readme.md +++ b/Readme.md @@ -15,5 +15,23 @@ rustup override set nightly cargo run --release ``` +## Pictures + +### Main menu + +![Main menu](pictures/start_menu.jpg) + +### Gameplay + +![Gameplay](pictures/ingame.png) + +### Death-screen + +![Deathscreen](pictures/death.jpg) + ## License -Red Life is licensed under the [GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html) license. \ No newline at end of file + +Red Life is licensed under the [GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html) license. + + + diff --git a/game/Cargo.toml b/game/Cargo.toml index cddc498..055c4b5 100644 --- a/game/Cargo.toml +++ b/game/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "game" -version = "0.7.7" +version = "1.0.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/game/src/backend/constants.rs b/game/src/backend/constants.rs index a23769e..dc3ba4c 100644 --- a/game/src/backend/constants.rs +++ b/game/src/backend/constants.rs @@ -3,7 +3,7 @@ use crate::backend::rlcolor::RLColor; use crate::game_core::player::gen_inventory; use crate::game_core::resources::Resources; use crate::languages::german::MACHINE_NAMES; -use crate::machines::machine::State; +use crate::machines::machine::{Machine, State}; use crate::machines::trade::Trade; use ggez::graphics::{Color, Rect}; use std::string::ToString; @@ -37,18 +37,22 @@ pub const MOVEMENT_SPEED: usize = 10; /// Contains the position of the time. pub(crate) const TIME_POSITION: (f32, f32) = (1205., 960.); +/// Change rate fot the event Sandsturm +pub(crate) const SANDSTURM_CR: Resources = Resources { + oxygen: 10, + energy: 0, + life: 0, +}; + #[allow(clippy::too_many_lines)] /// Generates all machines with all their name, position, trades and resources. /// # Returns -/// `String` - The name of the machine. -/// `Rect` - Returns the collision area of the machine. -/// `Vec` - Returns the trades of the machine. -/// `Vec` - Returns the resources of the machine. -pub(crate) fn gen_all_machines() -> [(String, Rect, Vec, Resources); 7] { - [ +/// A Vector of `Machine`s +pub(crate) fn gen_all_machines() -> Vec { + vec![ // Oxygen machine - ( - MACHINE_NAMES[1].to_string(), + Machine::new_by_const(( + MACHINE_NAMES[0].to_string(), Rect { x: 280.0, y: 230.0, @@ -82,14 +86,14 @@ pub(crate) fn gen_all_machines() -> [(String, Rect, Vec, Resources); ), ], Resources { - oxygen: 25, + oxygen: 30, energy: -30, life: 0, }, - ), + )), // Electricity machine - ( - MACHINE_NAMES[2].to_string(), + Machine::new_by_const(( + MACHINE_NAMES[1].to_string(), Rect { x: 282.0, y: 752.0, @@ -127,10 +131,10 @@ pub(crate) fn gen_all_machines() -> [(String, Rect, Vec, Resources); energy: 200, life: 0, }, - ), - // worker machine - ( - MACHINE_NAMES[3].to_string(), + )), + // Worker machine + Machine::new_by_const(( + MACHINE_NAMES[2].to_string(), Rect { x: 1000.0, y: 780.0, @@ -160,10 +164,10 @@ pub(crate) fn gen_all_machines() -> [(String, Rect, Vec, Resources); energy: -15, life: 0, }, - ), - // 3d_printer machine - ( - MACHINE_NAMES[4].to_string(), + )), + // 3d Printer machine + Machine::new_by_const(( + MACHINE_NAMES[3].to_string(), Rect { x: 930.0, y: 230.0, @@ -177,7 +181,7 @@ pub(crate) fn gen_all_machines() -> [(String, Rect, Vec, Resources); State::Broken, State::Idle, false, - gen_inventory(2, 1, 0), + gen_inventory(2, 0, 0), ), Trade::new( "produce_3d_teil".to_string(), @@ -193,10 +197,10 @@ pub(crate) fn gen_all_machines() -> [(String, Rect, Vec, Resources); energy: -25, life: 0, }, - ), + )), // Communication module - ( - MACHINE_NAMES[5].to_string(), + Machine::new_by_const(( + MACHINE_NAMES[4].to_string(), Rect { x: 1640.0, y: 320.0, @@ -218,18 +222,18 @@ pub(crate) fn gen_all_machines() -> [(String, Rect, Vec, Resources); State::Idle, State::Running, true, - gen_inventory(0, 0, 0), + gen_inventory(1, 0, 1), ), ], Resources { oxygen: 0, - energy: -20, + energy: -30, life: 0, }, - ), - // first hole - ( - MACHINE_NAMES[6].to_string(), + )), + // First hole + Machine::new_by_const(( + MACHINE_NAMES[5].to_string(), Rect { x: 780.0, y: 230.0, @@ -245,14 +249,14 @@ pub(crate) fn gen_all_machines() -> [(String, Rect, Vec, Resources); gen_inventory(2, 0, 0), )], Resources { - oxygen: -20, + oxygen: -15, energy: -5, life: 0, }, - ), - // second hole - ( - MACHINE_NAMES[7].to_string(), + )), + // Second hole + Machine::new_by_const(( + MACHINE_NAMES[6].to_string(), Rect { x: 680.0, y: 900.0, @@ -268,10 +272,10 @@ pub(crate) fn gen_all_machines() -> [(String, Rect, Vec, Resources); gen_inventory(2, 0, 0), )], Resources { - oxygen: -20, + oxygen: -15, energy: -5, life: 0, }, - ), + )), ] } diff --git a/game/src/backend/gamestate.rs b/game/src/backend/gamestate.rs index b066869..ff4bb9b 100644 --- a/game/src/backend/gamestate.rs +++ b/game/src/backend/gamestate.rs @@ -60,7 +60,7 @@ pub struct GameState { /// Needed to send Messages to `machine` to make changes to the game pub(crate) sender: Option>, /// Defines if the handbook is currently open - pub handbook_visible: bool, + pub handbook_invisible: bool, } impl PartialEq for GameState { @@ -108,7 +108,7 @@ impl GameState { /// and checks if the player has died. /// # Returns /// * `RLResult`: A `RLResult` to validate the success of the tick function - pub fn tick(&mut self, _ctx: &mut Context) -> RLResult { + pub fn tick(&mut self) -> RLResult { // Update Resources self.player.resources = self .player @@ -124,12 +124,12 @@ impl GameState { if let Some(empty_resource) = Resources::get_death_reason(self.player.resources) { match empty_resource { Both => { - self.player.resources_change.life -= 60; + self.player.resources_change.life = -60; self.machines.iter_mut().for_each(Machine::no_energy); } - Oxygen => self.player.resources_change.life -= 60, + Oxygen => self.player.resources_change.life = -50, Energy => { - self.player.resources_change.life -= 10; + self.player.resources_change.life = -10; self.machines.iter_mut().for_each(Machine::no_energy); } }; @@ -178,7 +178,7 @@ impl GameState { self.player .life_regeneration(&self.screen_sender.as_ref().unwrap().clone())?; for machine in &mut self.machines { - machine.tick(1)?; + machine.tick()?; } Ok(()) @@ -225,24 +225,32 @@ impl GameState { /// # Arguments /// * `canvas`: The canvas to draw on /// * `ctx`: The `Context` of the game + /// # Returns + /// * `RLResult`: A `RLResult` to validate the success of the function pub fn open_handbook(&self, canvas: &mut Canvas, ctx: &mut Context) -> RLResult { let scale = get_scale(ctx); let image = self.assets.get("Handbook.png").unwrap(); draw!(canvas, image, Vec2::new(700.0, 300.0), scale); match self.player.milestone { 1 => { - self.get_handbook_text(canvas, scale, &FIRST_MILESTONE_HANDBOOK_TEXT); + self.draw_handbook_text(canvas, scale, &FIRST_MILESTONE_HANDBOOK_TEXT); } 2 => { - self.get_handbook_text(canvas, scale, &SECOND_MILESTONE_HANDBOOK_TEXT); + self.draw_handbook_text(canvas, scale, &SECOND_MILESTONE_HANDBOOK_TEXT); } _ => {} } Ok(()) } - - pub fn get_handbook_text(&self, canvas: &mut Canvas, scale: Vec2, handbook_text: &[&str]) { + /// Draws the text for the current milestone on the handbook on the screen. + /// # Arguments + /// * `canvas`: The canvas to draw on + /// * `scale`: The scale of the canvas + /// * `handbook_text`: The text to draw on the screen + /// # Returns + /// * `RLResult`: A `RLResult` to validate the success of the function + pub fn draw_handbook_text(&self, canvas: &mut Canvas, scale: Vec2, handbook_text: &[&str]) { handbook_text .iter() .enumerate() @@ -255,9 +263,10 @@ impl GameState { &text, Vec2::new(800.0, 400.0 + (i * 30) as f32), scale - ) + ); }); } + /// Iterates trough the inventory and draws the amount of every item in the inventory. /// # Arguments /// * `canvas` - The current canvas to draw on @@ -528,7 +537,7 @@ impl Screen for GameState { /// Updates the game and handles input. Returns `StackCommand::Pop` when Escape is pressed. fn update(&mut self, ctx: &mut Context) -> RLResult { if ctx.time.check_update_time(DESIRED_FPS) { - self.tick(ctx)?; + self.tick()?; self.move_player(ctx)?; Event::update_events(ctx, self)?; } @@ -554,7 +563,7 @@ impl Screen for GameState { self.draw_resources(&mut canvas, scale, ctx)?; self.draw_machines(&mut canvas, scale, ctx)?; self.draw_items(&mut canvas, ctx)?; - if self.handbook_visible { + if !self.handbook_invisible { self.open_handbook(&mut canvas, ctx)?; } #[cfg(debug_assertions)] diff --git a/game/src/backend/generate_machines.rs b/game/src/backend/generate_machines.rs index 0c91532..8880083 100644 --- a/game/src/backend/generate_machines.rs +++ b/game/src/backend/generate_machines.rs @@ -1,9 +1,6 @@ -//!DIESE DATEI IST ZUM TESTEN VON SANDER -use crate::backend::gamestate::GameState; - -use crate::machines::machine::Machine; - +//! This File handels code surrounding Machine with in `GameState` use crate::backend::constants::gen_all_machines; +use crate::backend::gamestate::GameState; use crate::backend::rlcolor::RLColor; use crate::backend::utils::get_draw_params; use crate::{draw, RLResult}; @@ -13,15 +10,19 @@ use ggez::Context; use tracing::info; impl GameState { + /// Creates all Machines for initial creation and pushes them into a list pub fn create_machine(&mut self) { info!("Generating all Machines"); - let all = gen_all_machines(); - for m in &all { - let new_ms = Machine::new_by_const(m.clone()); - self.machines.push(new_ms); - } + self.machines = gen_all_machines(); } + /// Paints the machine sprites and if applicable it shows the state or time remaining + /// # Arguments + /// * `canvas`: The canvas to draw on + /// * `scale`: The scale of the canvas + /// * `ctx`: The `Context` of the game + /// # Returns + /// * `RLResult`: A `RLResult` to validate the success of the paint function pub fn draw_machines(&self, canvas: &mut Canvas, scale: Vec2, ctx: &mut Context) -> RLResult { for machine in &self.machines { let image = machine.get_graphic(); diff --git a/game/src/backend/movement.rs b/game/src/backend/movement.rs index bdad11f..927dcb0 100644 --- a/game/src/backend/movement.rs +++ b/game/src/backend/movement.rs @@ -23,22 +23,22 @@ impl GameState { } if ctx.keyboard.is_key_just_pressed(VirtualKeyCode::E) { info!("Interacting with Area: {:?}", self.get_interactable()); - let player_ref = &mut self.player.clone(); + let player_ref = &self.player.clone(); if let Some(interactable) = self.get_interactable() { - self.player = interactable.interact(player_ref)?; + interactable.interact(player_ref)?; } } if ctx.keyboard.is_key_just_pressed(VirtualKeyCode::H) { - self.handbook_visible = !self.handbook_visible; + self.handbook_invisible = !self.handbook_invisible; + } + // If we are in debug mode, change the milestone by using Z + #[cfg(debug_assertions)] + if ctx.keyboard.is_key_just_pressed(VirtualKeyCode::Z) { + self.player.milestone += 1; } let keys = ctx.keyboard.pressed_keys(); for key in keys.iter() { match key { - // If we are in debug mode, change the milestone by using Z - #[cfg(debug_assertions)] - VirtualKeyCode::Z => { - self.player.milestone += 1; - } VirtualKeyCode::W => { if !self.collision_detection(( self.player.position.0, diff --git a/game/src/backend/screen.rs b/game/src/backend/screen.rs index 6d887a2..b4aa829 100644 --- a/game/src/backend/screen.rs +++ b/game/src/backend/screen.rs @@ -151,22 +151,23 @@ impl Screenstack { /// Possible commands are: /// `Push`: Pushes a new screen on the stack, /// `Pop`: Pops the current screen, - /// `None`: Does nothing /// `Popup`: Adds a new popup to the stack /// # Arguments /// * `command` - The command to handle fn process_command(&mut self, command: StackCommand) { // Match the command given back by the screen match command { - StackCommand::None => {} StackCommand::Push(mut screen) => { screen.set_sender(self.sender.clone()); self.screens.push(screen); } StackCommand::Pop => { - match self.screens.len() { - 1 => std::process::exit(0), - _ => self.screens.pop(), + if self.screens.len() == 1 { + std::process::exit(0) + } else { + // Clear our popups in order to not display them outside of the Gamestate + self.popup.clear(); + self.screens.pop(); }; } StackCommand::Popup(popup) => self.popup.push(popup), @@ -184,7 +185,6 @@ impl Screenstack { /// We can tell the `Screenstack` to push the `Gamestate` screen onto the /// `Screenstack` pub enum StackCommand { - None, Push(Box), Popup(Popup), Pop, diff --git a/game/src/backend/utils.rs b/game/src/backend/utils.rs index be52119..e46a484 100644 --- a/game/src/backend/utils.rs +++ b/game/src/backend/utils.rs @@ -19,7 +19,7 @@ pub fn get_scale(ctx: &Context) -> Vec2 { /// Returns if the player collides with an area /// # Arguments /// * `player_pos` - The position of the player -/// * `direction` - The direction the player wants to move +/// * `area` - The area to be collision-checked /// # Returns /// * `true` if the player collides with an area /// * `false` if the player does not collide with an area diff --git a/game/src/game_core/event.rs b/game/src/game_core/event.rs index 6dba222..8aad0c7 100644 --- a/game/src/game_core/event.rs +++ b/game/src/game_core/event.rs @@ -1,9 +1,9 @@ -use crate::backend::constants::DESIRED_FPS; +use crate::backend::constants::{DESIRED_FPS, SANDSTURM_CR}; use crate::backend::gamestate::GameState; use crate::backend::screen::{Popup, StackCommand}; use crate::game_core::resources::Resources; use crate::languages::german::{ - INFORMATIONSPOPUP_MARS, INFORMATIONSPOPUP_NASA, KOMETENEINSCHLAG, STROMAUSFALL, + INFORMATIONSPOPUP_MARS, INFORMATIONSPOPUP_NASA, KOMETENEINSCHLAG, SANDSTURM, STROMAUSFALL, }; use crate::languages::german::{MARS_INFO, NASA_INFO, WARNINGS}; use crate::machines::machine::State; @@ -62,8 +62,15 @@ impl Event { /// if no Event is active it either chooses a random event of the Event enum or nothing every 60 seconds pub fn event_generator() -> Option { let rng = fastrand::Rng::new(); - let event = rng.usize(..10); + let event = rng.usize(..15); match event { + 8 => Some(Event::new( + SANDSTURM, + WARNINGS[2], + "warning", + Some(SANDSTURM_CR), + 5, + )), 0 | 3 => Some(Event::new( KOMETENEINSCHLAG, WARNINGS[0], @@ -120,8 +127,7 @@ impl Event { /// Check if event is still active pub fn is_active(&self) -> bool { - // check if time since event creation is greater than the duration of the event - !self.duration == 0 + self.duration != 0 } /// Triggers the event and activates its effect @@ -130,7 +136,7 @@ impl Event { /// * `gamestate` - The gamestate which is used to access the player and the machines pub fn action(&self, restore: bool, gamestate: &mut GameState) -> RLResult { const KOMETENEINSCHLAG_NAME: &str = KOMETENEINSCHLAG[0]; - const STROMAUSTFALL_NAME: &str = STROMAUSFALL[0]; + const STROMAUSFALL_NAME: &str = STROMAUSFALL[0]; let sender = gamestate.get_screen_sender()?.clone(); // handle event effects @@ -147,7 +153,7 @@ impl Event { one_hole.change_state_to(&State::Running); } } - STROMAUSTFALL_NAME => { + STROMAUSFALL_NAME => { gamestate.machines.iter_mut().for_each(|machine| { // if machine is running it will b use tracing::{info, Id};e stopped // event not triggered if machine is broken or idling @@ -194,9 +200,17 @@ impl Event { if ctx.time.ticks() % 20 == 0 { gamestate.events.iter_mut().for_each(|event| { event.duration = event.duration.saturating_sub(20); + if event.name == "Sandsturm" {} }); - - let old_events = gamestate.events.clone(); + // restore resources of inactive events + for event in &gamestate.events { + if !event.is_active() { + if let Some(resources) = event.resources { + gamestate.player.resources_change = + gamestate.player.resources_change + resources; + } + } + } // remove all events which are not active anymore gamestate.events.retain(|event| { if event.is_active() { @@ -206,18 +220,9 @@ impl Event { false } }); - // restore resources of inactive events - for event in &old_events { - if !event.is_active() { - if let Some(resources) = event.resources { - gamestate.player.resources_change = - gamestate.player.resources_change - resources; - } - } - } } // have a maximum of one active event - if ctx.time.ticks() % 200 == 0 { + if ctx.time.ticks() >= 400 && ctx.time.ticks() % 200 == 0 { // generate new event // might not return an event let gen_event = Event::event_generator(); diff --git a/game/src/languages/german.rs b/game/src/languages/german.rs index 5ec3cdd..05f952f 100644 --- a/game/src/languages/german.rs +++ b/game/src/languages/german.rs @@ -53,6 +53,7 @@ pub const ENERGY_STRING: &str = "Kälte"; pub const AIR_AND_ENERGY_STRING: &str = "Kälte und zu wenig Luft"; pub const DEATH_REASON_STRING: &str = "Du bist gestorben an"; pub const ADDITIONAL_INFO_STRING: &str = "Bitte drücke ESC!"; +pub const RESUME_ERROR_STRING: &str = "Du brauchst zuerst einen Spielstand"; /// Constant for all strings used in `IntroScreen` pub const INTRO_TEXT: &str = "Du bist auf dem Mars gestrandet und musst überleben.\nDazu musst du die \ @@ -71,7 +72,10 @@ pub const KOMETENEINSCHLAG: [&str; 2] = [ "KOMETENEINSCHLAG", "Ein KOMETENEINSCHLAG hat die Erde getroffen und hat ein Loch in der Wand erzeugt", ]; -/// Constants for the events that can occur. +pub const SANDSTURM: [&str; 2] = [ + "Sandsturm", + "Ein Sandsturm, welcher zu einer Störung des Sauerstoffgenerators führt", +]; pub const INFORMATIONSPOPUP_NASA: [&str; 2] = [ "InformationspopupNASA", "Ein Informationspopup über die NASA, welches Fakten und Informationen über die NASA enthält", @@ -91,8 +95,7 @@ pub const TIME_NAME: [&str; 1] = ["Zeit"]; /// Constants for the text of the button in the main menu pub const BUTTON_TEXT: [&str; 3] = ["Fortsetzen", "Neues Spiel", "Beenden"]; /// Contains all machine names as a vec of strings. -pub(crate) const MACHINE_NAMES: [&str; 8] = [ - "test", +pub(crate) const MACHINE_NAMES: [&str; 7] = [ "Sauerstoffgenerator", "Stromgenerator", "Werkermaschine", @@ -112,7 +115,7 @@ pub(crate) const FIRST_MILESTONE_HANDBOOK_TEXT: [&str; 10] = [ "- Denk daran sparsam mit Benzin umzugehen!", "- Du kannst den Generator kurz anhalten,", " wenn du genug Energie hast", - "c:", + "\n\n Drücke H zum schließen", ]; pub(crate) const SECOND_MILESTONE_HANDBOOK_TEXT: [&str; 7] = [ @@ -122,5 +125,5 @@ pub(crate) const SECOND_MILESTONE_HANDBOOK_TEXT: [&str; 7] = [ " während du die Nachricht sendest!", "- Wenn du die Nachricht abgeschickt hast,", " gewinnst du automatisch.", - "<*~*>", + "\n\n Drücke H zum schließen", ]; diff --git a/game/src/machines/machine.rs b/game/src/machines/machine.rs index 227ab64..6ab2d52 100644 --- a/game/src/machines/machine.rs +++ b/game/src/machines/machine.rs @@ -1,3 +1,4 @@ +//! This File handels everything about Machine use crate::backend::constants::PLAYER_INTERACTION_RADIUS; use crate::backend::gamestate::GameCommand; use crate::backend::rlcolor::RLColor; @@ -24,7 +25,7 @@ pub enum State { Idle, Running, } - +/// only used for logging purposes impl Display for State { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match *self { @@ -44,38 +45,94 @@ impl From for Color { } } } - +/// The Machine Class handels any internal logik surrounding intractable objects +/// This includes objects that arnt classic Machines per se but since they do behave so similarity +/// we can reuse the same code for it #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Machine { + /// The name of the machine is used as a reference on which assets to load pub name: String, + /// Contains the current state pub state: State, + /// The hitbox is the area the player is prevented from walking into pub hitbox: Rect, + /// The interaction_area is the area the player has to be inside to interact with this Machine pub interaction_area: Rect, + /// Contains a defined list of Trades, things the player can do with the Machine pub trades: Vec, + /// Contains the last trade with a timer, Is uses to get information about the trade after the timer runs out last_trade: Trade, + /// Denotes what amount of Resources is consumed and or produced as long as the Machine is in state running running_resources: Resources, - og_time: i16, + /// Contains the amount of tics until the timer rus out time_remaining: i16, + /// Denotes weather the timer is running or not via 0 or 1 also used for calculations time_change: i16, #[serde(skip)] + /// Contains all the Sprites for this one Machine sprite: Option, #[serde(skip)] + /// Needed to send Messages to the `GameState` to make changes to the screen sender: Option>, #[serde(skip)] + /// Needed to send Messages to the `Screenstack` to make changes to the screen screen_sender: Option>, } impl Machine { - pub(crate) fn is_interactable(&self, pos: (usize, usize)) -> bool { - is_colliding(pos, &self.interaction_area) + /// Creates a new Machine with all non Optional parameters + /// # Arguments + /// * `name` - Name of this Machine and asset group + /// * `hitbox` - A rect containing position and size of the Machine + /// * `trades` - A list of Trades + /// * `running_resources` - Amount of recourse consumed and or produced while running + /// # Returns + /// * 'Machine' + fn new( + name: String, + hitbox: Rect, + trades: Vec, + running_resources: Resources, + ) -> Self { + info!("Creating new machine: name: {}", name); + Self { + name, + hitbox, + interaction_area: Rect { + x: hitbox.x - PLAYER_INTERACTION_RADIUS, + y: hitbox.y - PLAYER_INTERACTION_RADIUS, + w: hitbox.w + (PLAYER_INTERACTION_RADIUS * 2.), + h: hitbox.h + (PLAYER_INTERACTION_RADIUS * 2.), + }, + state: Broken, + sprite: None, + trades, + last_trade: Trade::default(), + running_resources, + time_remaining: 0, + time_change: 0, + sender: None, + screen_sender: None, + } } - pub fn new_by_const( + + /// Alternative new constructor for the machine using one parameter Tupel + /// # Arguments + /// * `(name, hit_box, trades, running_resources)` - Tupel containing the same arguments as `new()` + /// # Returns + /// * 'Machine' + pub(crate) fn new_by_const( (name, hit_box, trades, running_resources): (String, Rect, Vec, Resources), ) -> Self { Machine::new(name, hit_box, trades, running_resources) } - /// Loads the Machine Sprites. Has to be called before drawing. + /// initialises the Maschine with the data that is not Serialize + /// This funktion is required to be called before the firs draw call + /// # Arguments + /// * `images` - A Slice of Images containing the sprites for this Machine + /// * `sender` - A sender of type `Sender` + /// * `screen_sender` - A sender of type `Sender` pub(crate) fn init( &mut self, images: &[Image], @@ -87,7 +144,7 @@ impl Machine { self.screen_sender = Some(screen_sender); if self.name == "Loch" { // Constant (pos of hole) - if self.hitbox.x == 780.0 { + if self.hitbox.x == 780. { self.change_state_to(&Running); } else { self.change_state_to(&Idle); @@ -95,108 +152,60 @@ impl Machine { } } - fn new( - // this function is supposed to be private - name: String, - hit_box: Rect, - trades: Vec, - running_resources: Resources, - ) -> Self { - info!("Creating new machine: name: {}", name); - - //let sprite = MachineSprite::new(gs, name.as_str())?; - Self { - name, - hitbox: hit_box, - interaction_area: Rect { - x: hit_box.x - PLAYER_INTERACTION_RADIUS, - y: hit_box.y - PLAYER_INTERACTION_RADIUS, - w: hit_box.w + (PLAYER_INTERACTION_RADIUS * 2.), - h: hit_box.h + (PLAYER_INTERACTION_RADIUS * 2.), - }, - state: Broken, - sprite: None, - trades, - last_trade: Trade::default(), - running_resources, - og_time: 0, - time_remaining: 0, - time_change: 0, - sender: None, - screen_sender: None, - } - } - fn get_trade(&self) -> Trade { - // returns the first possible trade - if let Some(t) = self.trades.iter().find(|t| t.initial_state == self.state) { - return t.clone(); - } - Trade::default() + /// Fetches the correct sprite depending on the current sate + /// # Returns + /// * `&Image` - a reference to the graphic + pub(crate) fn get_graphic(&self) -> &Image { + self.sprite.as_ref().unwrap().get(self.state.clone()) } - fn check_change(&self, before: &State, after: &State) { - match (before, after) { - (Broken, Idle) => { - let _e = self.sender.as_ref().unwrap().send(GameCommand::Milestone); - } - (Idle, Broken) => {} - (Broken | Idle, Running) => { - let _e = self - .sender - .as_ref() - .unwrap() - .send(GameCommand::ResourceChange(self.running_resources)); - let _e = self.sender.as_ref().unwrap().send(GameCommand::Milestone); - } - (Running, Broken | Idle) => { - let _e = self - .sender - .as_ref() - .unwrap() - .send(GameCommand::ResourceChange( - // 0-n = n*-1 = n.invert() // TODO: add .invert() to Resources - Resources { - oxygen: 0, - energy: 0, - life: 0, - } - self.running_resources, - )); - } - _ => { - info!( - "unexpected case in Match. machine state changed from {} to {}", - before.clone(), - after.clone() - ); - } + /// Calculates the Percentage of time remaining on the timer + /// # Returns + /// * `0...1` - '1' being timer just started equal to 100% and -1 for no Timer + pub(crate) fn get_time_percentage(&self) -> f32 { + if self.last_trade.time_ticks == 0 { + -1.0 + } else { + f32::from(self.time_remaining) / f32::from(self.last_trade.time_ticks) } } - pub(crate) fn change_state_to(&mut self, new_state: &State) { - if self.state != *new_state { - self.check_change(&self.state, new_state); - self.state = new_state.clone(); - } + /// Determines if the Player can interact with this Machine + /// # Arguments + /// * `pos` - a tuples of x and y containing the player position + /// # Returns + /// * `true` if the player collides with this Machine + /// * `false` if the player does not collide with this Machine + pub(crate) fn is_interactable(&self, pos: (usize, usize)) -> bool { + is_colliding(pos, &self.interaction_area) } - pub(crate) fn interact(&mut self, player: &mut Player) -> RLResult { - let trade = self.get_trade(); + /// Handel's the interaction of the Maschine and the player + /// # Arguments + /// * `player` - of type `& Player` is a reference to the player + pub(crate) fn interact(&mut self, player: &Player) -> RLResult { + // Check if there is a possible trade + let trade = match self.trades.iter().find(|t| t.initial_state == self.state) { + Some(t) => t.clone(), + None => return Ok(()), + }; + if trade.name == *"no_Trade" { - return Ok(player.clone()); + return Ok(()); } + // Check if the player has energy (and its needed) if player.resources.energy == 0 && self.running_resources.energy < 0 && self.name != "Loch" { - return Ok(player.clone()); + return Ok(()); } - - // dif = items the player has - the cost of the trade + // dif = the different between items the player has and the cost of the trade let dif = trade .cost .iter() .map(|(item, demand)| (item, player.get_item_amount(item) - demand)) .filter(|(_item, dif)| *dif < 0) .collect::>(); - // If one item is not available in enough quantity + // If one item is not available in enough quantity inform the player and cancel the interaction if dif.iter().any(|(_, demand)| *demand < 0) { let mut missing_items = String::new(); dif.iter() @@ -211,10 +220,10 @@ impl Machine { .as_ref() .unwrap() .send(StackCommand::Popup(popup))?; - return Ok(player.clone()); + return Ok(()); } - // the player has enough items for the trade so we will execute on it + // At this point all checks have passed and continue with executing the trade info!("Executing trade:{} ", trade.name); // Remove the cost of the trade from the players inventory by sending the demand to the AddItem GameCommand @@ -238,7 +247,6 @@ impl Machine { //if no timer is running set timer up self.last_trade = trade.clone(); self.time_remaining = trade.time_ticks; - self.og_time = trade.time_ticks; } //start the timer self.time_change = 1; @@ -248,25 +256,19 @@ impl Machine { self.change_state_to(&trade.resulting_state); } - Ok(player.clone()) - } - - pub(crate) fn get_graphic(&self) -> &Image { - self.sprite.as_ref().unwrap().get(self.state.clone()) + Ok(()) } - pub(crate) fn tick(&mut self, delta_ticks: i16) -> RLResult { - self.time_remaining -= self.time_change * delta_ticks; + /// Handels the timer by being called every tick + pub(crate) fn tick(&mut self) -> RLResult { + self.time_remaining -= self.time_change; + //if the timer has run out if self.time_remaining < 0 { - //timer run out + //reset timer values and stop timer self.time_change = 0; self.time_remaining = 0; if self.last_trade.return_after_timer { - if self.last_trade.name == "Notfall_signal_absetzen" { - let _e = self.sender.as_ref().unwrap().send(GameCommand::Winning); - } - self.change_state_to(&self.last_trade.initial_state.clone()); } else { self.change_state_to(&self.last_trade.resulting_state.clone()); @@ -284,21 +286,73 @@ impl Machine { .unwrap() .send(GameCommand::AddItems(items))?; } + // handel edge case for wining the game + if self.last_trade.name == "Notfall_signal_absetzen" { + return Ok(self.sender.as_ref().unwrap().send(GameCommand::Winning)?); + } Ok(()) } - pub(crate) fn get_time_percentage(&self) -> f32 { - if self.og_time == 0 { - -1.0 - } else { - f32::from(self.time_remaining) / f32::from(self.og_time) + + /// Used to change the State of the Machine gracefully + /// # Arguments + /// * `new_state` - the state that the machine should change to + pub(crate) fn change_state_to(&mut self, new_state: &State) { + if self.state != *new_state { + self.invoke_state_change(&self.state, new_state); + if self.state == Running && self.time_change == 1 { + self.time_change = 0; + } + self.state = new_state.clone(); } } - pub fn no_energy(&mut self) { + /// A helper funktion to disable every funktion in case there is no energy in the system + pub(crate) fn no_energy(&mut self) { if self.running_resources.energy < 0 && self.name != "Loch" { - // if the is no energy and the machine needs some we stop it + // If there is no energy available but this machine needs some, stop this machine. if self.state == Running { self.change_state_to(&Idle); - self.time_change = 0; + } + } + } + + /// Helper funktion that sends appropriate `GameCommand`s depending on change of the state + /// Used to change the State of the Machine gracefully + /// # Arguments + /// * `before` - the current state of the system + /// * `after` - the state that it will be in after the change is complete + fn invoke_state_change(&self, before: &State, after: &State) { + match (before, after) { + (Broken, Idle) => { + let _e = self.sender.as_ref().unwrap().send(GameCommand::Milestone); + } + (Idle, Broken) => {} + (Broken | Idle, Running) => { + let _e = self + .sender + .as_ref() + .unwrap() + .send(GameCommand::ResourceChange(self.running_resources)); + let _e = self.sender.as_ref().unwrap().send(GameCommand::Milestone); + } + (Running, Broken | Idle) => { + let _e = self + .sender + .as_ref() + .unwrap() + .send(GameCommand::ResourceChange( + Resources { + oxygen: 0, + energy: 0, + life: 0, + } - self.running_resources, + )); + } + _ => { + info!( + "unexpected case in Match. machine state changed from {} to {}", + before.clone(), + after.clone() + ); } } } diff --git a/game/src/machines/machine_sprite.rs b/game/src/machines/machine_sprite.rs index 717e4af..1872846 100644 --- a/game/src/machines/machine_sprite.rs +++ b/game/src/machines/machine_sprite.rs @@ -1,7 +1,9 @@ +//! This File contains the code surrounding `MachineSprite` use crate::machines::machine::State; use ggez::graphics::Image; #[derive(Debug, Clone)] +/// Contains all the sprites associated with one Machine pub struct MachineSprite(Vec); impl From<&[Image]> for MachineSprite { fn from(value: &[Image]) -> Self { @@ -10,6 +12,7 @@ impl From<&[Image]> for MachineSprite { } impl MachineSprite { + ///Grabs the correct Image depending on the passed in State pub fn get(&self, state: State) -> &Image { self.0.get(state as usize).unwrap_or(&self.0[0]) } diff --git a/game/src/machines/trade.rs b/game/src/machines/trade.rs index 0ef84e6..e8b5fbe 100644 --- a/game/src/machines/trade.rs +++ b/game/src/machines/trade.rs @@ -1,22 +1,38 @@ +//! This File contains the structure `Trade` use crate::game_core::item::Item; use crate::machines::machine::State; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +/// A trade is one interaction option with a machine +/// This means that repairing, starting or pausing a machine are all trades +/// Trade itself does not contain any logic and is mostly a construct to group values pub struct Trade { + /// is used for debugging and login purposes pub(crate) name: String, + /// the time it takes for the trade to conclude 0 = instant pub time_ticks: i16, - pub initial_state: State, // the Machine needs to be in this state for the trade to be accessible - pub resulting_state: State, // the Machine changes state to resulting state after pressing E + /// the Machine needs to be in `initial_state` for the trade to be accessible + pub initial_state: State, + /// determines the machine state after or during the trade + pub resulting_state: State, + /// determines whether the `resulting_state` is temporary or permanent + /// * true = temporary, meaning the state returns to `initial_state` after the timer + /// * false = permanent, meaning the state is set to `resulting_state` after the timer pub return_after_timer: bool, // how the ms behaves after the timer run out + /// Contains the cost associated with this trade. + /// This stores the amount of item the player loses and or gain. + /// * Positive amount means the Player will **lose** these items. + /// * Negative amount means the Player will **gain** these items. pub(crate) cost: Vec<(Item, i32)>, } impl Default for Trade { + /// Initialises a trade with some default values + /// default values have no meaning and should never be checked on fn default() -> Self { Self { name: "no_Trade".to_string(), - // default values have almost no meaning time_ticks: 0, initial_state: State::Broken, resulting_state: State::Running, @@ -27,6 +43,7 @@ impl Default for Trade { } impl Trade { + ///initialises a new Trade using values passed in pub fn new( name: String, time_ticks: i16, diff --git a/game/src/main_menu/button.rs b/game/src/main_menu/button.rs index 634c2f6..0d3b725 100644 --- a/game/src/main_menu/button.rs +++ b/game/src/main_menu/button.rs @@ -44,13 +44,13 @@ impl Button { // processing button interaction: click, hover // determines if button is clicked pub(crate) fn action(&mut self, ctx: &Context, scale: Vec2) { - info!("User clicked: mouse position: {:?}", ctx.mouse.position()); if self.in_area(ctx.mouse.position(), scale) { self.current_color = self.hover_color; if ctx .mouse .button_just_pressed(ggez::event::MouseButton::Left) { + info!("User clicked: mouse position: {:?}", ctx.mouse.position()); self.click(); } } else { diff --git a/game/src/main_menu/mainmenu.rs b/game/src/main_menu/mainmenu.rs index 50f5b8f..83af6bc 100644 --- a/game/src/main_menu/mainmenu.rs +++ b/game/src/main_menu/mainmenu.rs @@ -5,11 +5,12 @@ use crate::backend::{ utils::get_scale, }; use crate::main_menu::button::Button; -use crate::main_menu::mainmenu::Message::{Exit, NewGame, Start}; +use crate::main_menu::mainmenu::Message::{Exit, NewGame, Resume}; use crate::RLResult; +use crate::backend::screen::Popup; use crate::game_core::infoscreen::InfoScreen; -use crate::languages::german::BUTTON_TEXT; +use crate::languages::german::{BUTTON_TEXT, RESUME_ERROR_STRING}; use ggez::{graphics, Context}; use std::sync::mpsc::{channel, Receiver, Sender}; @@ -18,7 +19,7 @@ use std::sync::mpsc::{channel, Receiver, Sender}; pub enum Message { Exit, NewGame, - Start, + Resume, } /// Main menu screen of the game with buttons to start a new game, load a game or exit the game. @@ -26,7 +27,6 @@ pub enum Message { pub struct MainMenu { buttons: Vec