diff --git a/src/gamecore.rs b/src/gamecore.rs index 19b9592..0954c0f 100644 --- a/src/gamecore.rs +++ b/src/gamecore.rs @@ -25,6 +25,7 @@ pub enum GameState { GameQuit, InGame, GameEnd, + InShop } impl fmt::Display for GameState { @@ -76,6 +77,22 @@ impl GameProgress { Ok(()) } + + pub fn update(&mut self, new_progress: &GameProgress) { + + // Bring in new data + self.coins = new_progress.coins; + self.inventory = new_progress.inventory.clone(); + self.max_depth = self.max_depth.max(new_progress.max_depth); + // self.fastest_time = self.fastest_time.min(new_progress.fastest_time); + + // Write to file + let result = self.to_file("./assets/savestate.json".to_string()); + if result.is_err() { + println!("Could not save game state. Holding in RAM"); + } + + } } /// This structure contains the entire game state, and should be passed around to various logic functions. diff --git a/src/items.rs b/src/items.rs index 547477e..f83afd1 100644 --- a/src/items.rs +++ b/src/items.rs @@ -1,9 +1,20 @@ +use raylib::texture::Texture2D; use serde::{Deserialize, Serialize}; +pub trait ItemBase { + fn get_cost(&self) -> u32; + fn get_level(&self) -> u8; + fn get_name(&self) -> String; + fn get_description(&self) -> String; + fn get_texture(&self) -> &Texture2D; +} + #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct StunGun { pub range: f32, pub duration: f64, + pub level: u8, + cost: u32, } impl StunGun { @@ -11,46 +22,201 @@ impl StunGun { Self { range: 30.0, duration: 0.75, + level: 1, + cost: 30, } } pub fn lvl2() -> Self { Self { range: 60.0, duration: 1.25, + level: 2, + cost: 40, + } + } + pub fn lvl3() -> Self { + Self { + range: 80.0, + duration: 1.0, + level: 3, + cost: 50, } } } +impl ItemBase for StunGun { + fn get_cost(&self) -> u32 { + self.cost + } + + fn get_name(&self) -> String { + return "Stun Gun".to_string(); + } + + fn get_description(&self) -> String { + return "Stun your enemies!\nJust don't point it at yourself.".to_string(); + } + + fn get_texture(&self) -> &Texture2D { + todo!() + } + fn get_level(&self) -> u8 { + self.level + } +} + #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] -pub struct AirBag; +pub struct AirBag { + pub extra_oxygen: f32, + pub level: u8, + cost: u32, +} + +impl AirBag { + pub fn lvl1() -> Self { + Self { + extra_oxygen: 0.15, + level: 1, + cost: 30, + } + } + pub fn lvl2() -> Self { + Self { + extra_oxygen: 0.30, + level: 2, + cost: 40, + } + } + pub fn lvl3() -> Self { + Self { + extra_oxygen: 0.45, + level: 3, + cost: 50, + } + } +} + +impl ItemBase for AirBag { + fn get_cost(&self) -> u32 { + self.cost + } + + fn get_name(&self) -> String { + return "Bag of Air".to_string(); + } + + fn get_description(&self) -> String { + return "Its.. a bag.\nFilled with air. Duh".to_string(); + } + + fn get_texture(&self) -> &Texture2D { + todo!() + } + fn get_level(&self) -> u8 { + self.level + } +} #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct Flashlight { - pub radius: f32 + pub radius: f32, + pub level: u8, + cost: u32, } impl Flashlight { pub fn lvl1() -> Self { Self { - radius: 0.25 + radius: 0.25, + level: 1, + cost: 40, } } + pub fn lvl2() -> Self { + Self { + radius: 0.5, + level: 2, + cost: 50, + } + } + pub fn lvl3() -> Self { + Self { + radius: 1.0, + level: 3, + cost: 60, + } + } +} + +impl ItemBase for Flashlight { + fn get_cost(&self) -> u32 { + self.cost + } + + fn get_name(&self) -> String { + return "Flashlight".to_string(); + } + + fn get_description(&self) -> String { + return "See better for longer".to_string(); + } + + fn get_texture(&self) -> &Texture2D { + todo!() + } + fn get_level(&self) -> u8 { + self.level + } } #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct Flippers { pub speed_increase: f32, + pub level: u8, + cost: u32, } impl Flippers { pub fn lvl1() -> Self { Self { - speed_increase: 1.2 + speed_increase: 1.2, + level: 1, + cost: 30, } } pub fn lvl2() -> Self { Self { - speed_increase: 1.5 + speed_increase: 1.5, + level: 2, + cost: 40, + } + } + pub fn lvl3() -> Self { + Self { + speed_increase: 1.8, + level: 3, + cost: 50, } } } + +impl ItemBase for Flippers { + fn get_cost(&self) -> u32 { + self.cost + } + + fn get_name(&self) -> String { + return "Flippers".to_string(); + } + + fn get_description(&self) -> String { + return "Swim faster, and look stupid\nat the same time!".to_string(); + } + + fn get_texture(&self) -> &Texture2D { + todo!() + } + fn get_level(&self) -> u8 { + self.level + } +} diff --git a/src/lib/utils/button.rs b/src/lib/utils/button.rs new file mode 100644 index 0000000..8960a30 --- /dev/null +++ b/src/lib/utils/button.rs @@ -0,0 +1,71 @@ +use raylib::prelude::*; + +pub struct OnScreenButton { + bounds: Rectangle, + text: String, + background: Color, + border: Color, + border_hover: Color, + font_px: i32, + draw_border: bool, +} + +impl OnScreenButton { + pub fn new( + text: String, + bounds: Rectangle, + background: Color, + border: Color, + border_hover: Color, + font_px: i32, + draw_border: bool, + ) -> Self { + Self { + bounds, + text, + background, + border, + border_hover, + font_px, + draw_border, + } + } + + pub fn is_hovered(&self, draw_handle: &RaylibDrawHandle) -> bool { + return self + .bounds + .check_collision_point_rec(draw_handle.get_mouse_position()); + } + + pub fn render(&self, draw_handle: &mut RaylibDrawHandle) { + // Draw the button background + draw_handle.draw_rectangle_rec(self.bounds, self.background); + + // Check mouse info + let is_being_hovered = self.is_hovered(draw_handle); + + // Render the border + if self.draw_border { + draw_handle.draw_rectangle_lines_ex( + self.bounds, + 3, + match is_being_hovered { + true => self.border_hover, + false => self.border, + }, + ); + } + + // Render the text + draw_handle.draw_text( + &self.text, + self.bounds.x as i32 + 10, + self.bounds.y as i32 + ((self.bounds.height as i32 - self.font_px) / 2), + self.font_px, + match is_being_hovered && !self.draw_border { + true => self.border_hover, + false => self.border, + }, + ) + } +} diff --git a/src/lib/utils/mod.rs b/src/lib/utils/mod.rs index baba8af..2f1353d 100644 --- a/src/lib/utils/mod.rs +++ b/src/lib/utils/mod.rs @@ -1,5 +1,6 @@ pub mod profiler; pub mod triangles; +pub mod button; pub fn calculate_linear_slide(playthrough_percent: f64) -> f64 { if playthrough_percent < 0.25 { diff --git a/src/logic/ingame/playerlogic.rs b/src/logic/ingame/playerlogic.rs index a506daa..91e2cef 100644 --- a/src/logic/ingame/playerlogic.rs +++ b/src/logic/ingame/playerlogic.rs @@ -142,11 +142,18 @@ pub fn update_player_movement( // Update the player's breath game_core.player.breath_percent = - (game_core.player.breath_percent - BREATH_DECREASE_PER_SECOND * dt as f32).clamp(0.0, 1.0); + (game_core.player.breath_percent - BREATH_DECREASE_PER_SECOND * dt as f32).max(0.0); // Only do this if the mouse is far enough away let player_stunned = game_core.player.stun_timer > 0.0; let mut player_real_movement = game_core.player.direction * speed_multiplier; + + // Handle the player wearing flippers + if game_core.player.inventory.flippers.is_some() { + player_real_movement *= game_core.player.inventory.flippers.as_ref().unwrap().speed_increase; + } + + // Handle movement and collisions if raw_movement_direction.distance_to(Vector2::zero()) > game_core.player.size.y / 2.0 && !game_core.player.is_stunned() { diff --git a/src/logic/mainmenu.rs b/src/logic/mainmenu.rs index e03d098..3dc3f90 100644 --- a/src/logic/mainmenu.rs +++ b/src/logic/mainmenu.rs @@ -1,6 +1,10 @@ use raylib::prelude::*; -use crate::{gamecore::{GameCore, GameState}, lib::wrappers::audio::player::AudioPlayer, pallette::WATER_DARK}; +use crate::{ + gamecore::{GameCore, GameState}, + lib::wrappers::audio::player::AudioPlayer, + pallette::WATER_DARK, +}; use super::screen::Screen; @@ -38,10 +42,12 @@ impl Screen for MainMenuScreen { // Get mouse position data let mouse_position = draw_handle.get_mouse_position(); - let hovering_play_button = mouse_position.y > (win_width as f32 / 4.0) + let hovering_play_button = mouse_position.y > (win_width as f32 / 4.0) && mouse_position.y < (win_width as f32 / 4.0) + 60.0; - let hovering_quit_button = mouse_position.y > (win_width as f32 / 4.0) + 100.0 + let hovering_shop_button = mouse_position.y > (win_width as f32 / 4.0) + 100.0 && mouse_position.y < (win_width as f32 / 4.0) + 160.0; + let hovering_quit_button = mouse_position.y > (win_width as f32 / 4.0) + 200.0 + && mouse_position.y < (win_width as f32 / 4.0) + 260.0; // Play and quit draw_handle.draw_text( @@ -54,10 +60,20 @@ impl Screen for MainMenuScreen { false => Color::BLACK, }, ); + draw_handle.draw_text( + "Shop", + (win_height / 2) + 120, + (win_width / 4) + 100, + 60, + match hovering_shop_button { + true => Color::GREEN, + false => Color::BLACK, + }, + ); draw_handle.draw_text( "Quit", (win_height / 2) + 130, - (win_width / 4) + 100, + (win_width / 4) + 200, 60, match hovering_quit_button { true => Color::GREEN, @@ -71,8 +87,14 @@ impl Screen for MainMenuScreen { // Check clicks if mouse_clicked { if hovering_play_button { + // Reset the world + game_core.world.reset(&mut game_core.player); + + // Start playing return Some(GameState::InGame); - } else if hovering_quit_button { + } else if hovering_shop_button { + return Some(GameState::InShop); + }else if hovering_quit_button { return Some(GameState::GameQuit); } } diff --git a/src/logic/mod.rs b/src/logic/mod.rs index 362eee2..7b1e5eb 100644 --- a/src/logic/mod.rs +++ b/src/logic/mod.rs @@ -3,4 +3,5 @@ pub mod loadingscreen; pub mod mainmenu; pub mod pausemenu; pub mod ingame; -pub mod gameend; \ No newline at end of file +pub mod gameend; +pub mod shop; \ No newline at end of file diff --git a/src/logic/pausemenu.rs b/src/logic/pausemenu.rs index 30c91f4..48a3d3d 100644 --- a/src/logic/pausemenu.rs +++ b/src/logic/pausemenu.rs @@ -2,7 +2,7 @@ use raylib::prelude::*; use crate::{ gamecore::{GameCore, GameState}, - lib::wrappers::audio::player::AudioPlayer, + lib::{utils::button::OnScreenButton, wrappers::audio::player::AudioPlayer}, }; use super::screen::Screen; @@ -127,7 +127,8 @@ impl Screen for PauseMenuScreen { Color::BLACK, ); - // Close and quit buttons + // Bottom buttons + let bottom_left_button_dimensions = Rectangle { x: (win_width as f32 / 2.0) - (SCREEN_PANEL_SIZE.x / 2.0) + 5.0, y: (win_height as f32 / 2.0) + (SCREEN_PANEL_SIZE.y / 2.0) - 50.0, @@ -141,49 +142,34 @@ impl Screen for PauseMenuScreen { height: bottom_left_button_dimensions.height, }; - // Check if the mouse is over either button - let mouse_over_bottom_left_button = - bottom_left_button_dimensions.check_collision_point_rec(mouse_position); - let mouse_over_bottom_right_button = - bottom_right_button_dimensions.check_collision_point_rec(mouse_position); - - // Render buttons - draw_handle.draw_rectangle_lines_ex( + let menu_button = OnScreenButton::new( + "Menu".to_string(), bottom_left_button_dimensions, - 3, - match mouse_over_bottom_left_button { - true => Color::GRAY, - false => Color::BLACK, - }, - ); - draw_handle.draw_text( - "Quit", - bottom_left_button_dimensions.x as i32 + 15, - bottom_left_button_dimensions.y as i32 + 5, - 30, + Color::WHITE, Color::BLACK, + Color::GRAY, + 30, + true, ); - draw_handle.draw_rectangle_lines_ex( + let close_button = OnScreenButton::new( + "Close".to_string(), bottom_right_button_dimensions, - 3, - match mouse_over_bottom_right_button { - true => Color::GRAY, - false => Color::BLACK, - }, - ); - draw_handle.draw_text( - "Close", - bottom_right_button_dimensions.x as i32 + 15, - bottom_right_button_dimensions.y as i32 + 5, - 30, + Color::WHITE, Color::BLACK, + Color::GRAY, + 30, + true, ); + // Render both + menu_button.render(draw_handle); + close_button.render(draw_handle); + // Handle click actions on the buttons if draw_handle.is_mouse_button_pressed(MouseButton::MOUSE_LEFT_BUTTON) { - if mouse_over_bottom_left_button { - return Some(GameState::GameQuit); - } else if mouse_over_bottom_right_button { + if menu_button.is_hovered(draw_handle) { + return Some(GameState::MainMenu); + } else if close_button.is_hovered(draw_handle) { return Some(game_core.last_state); } } diff --git a/src/logic/shop/item.rs b/src/logic/shop/item.rs new file mode 100644 index 0000000..2c94f3b --- /dev/null +++ b/src/logic/shop/item.rs @@ -0,0 +1,72 @@ +use std::marker::PhantomData; + +use raylib::prelude::*; + +use crate::{items::ItemBase, player::Player, world::World}; + +use super::itemui::ShopItemUi; + +pub struct ShopItemWrapper { + bounds: Rectangle, + ui: ShopItemUi, + item: T, +} + +impl ShopItemWrapper { + pub fn new( + item: T, + from_inventory: &Option, + first_item_bounds: Rectangle, + index: u8 + ) -> Self { + // Build new bounds for the UI row + let new_bounds = Rectangle { + x: first_item_bounds.x, + y: first_item_bounds.y + ((first_item_bounds.height + 5.0) * index as f32), + width: first_item_bounds.width, + height: first_item_bounds.height, + }; + + Self { + bounds: new_bounds, + ui: ShopItemUi::new( + item.get_name(), + match from_inventory { + Some(x) => x.get_level(), + None => 0, + }, + 3, + item.get_cost(), + ), + item, + } + } + + pub fn get_item(&self) -> &T { + &self.item + } + + pub fn can_player_afford(&self, player: &Player) -> bool { + return player.coins >= self.item.get_cost(); + } + + pub fn purchase(&self, player: &mut Player) -> T { + // Take the currency from the player + player.coins -= self.item.get_cost(); + + // Return a clone of the item + return self.item.clone(); + } + + pub fn user_clicked_buy(&self, draw_handle: &mut RaylibDrawHandle) -> bool { + return self.ui.buy_button_hovered && draw_handle.is_mouse_button_pressed(MouseButton::MOUSE_LEFT_BUTTON); + } + + pub fn user_hovering_row(&self, draw_handle: &mut RaylibDrawHandle) -> bool { + return self.bounds.check_collision_point_rec(draw_handle.get_mouse_position()); + } + + pub fn render(&mut self, draw_handle: &mut RaylibDrawHandle, player: &Player) { + self.ui.render(draw_handle, self.bounds, self.can_player_afford(player)); + } +} diff --git a/src/logic/shop/itemui.rs b/src/logic/shop/itemui.rs new file mode 100644 index 0000000..0084e84 --- /dev/null +++ b/src/logic/shop/itemui.rs @@ -0,0 +1,63 @@ +use crate::lib::utils::button::OnScreenButton; +use raylib::prelude::*; + +pub struct ShopItemUi { + name: String, + current_level: u8, + max_level: u8, + pub cost: u32, + pub buy_button_hovered: bool, +} + +impl ShopItemUi { + pub fn new(name: String, current_level: u8, max_level: u8, cost: u32) -> Self { + Self { + name, + current_level, + max_level, + cost, + buy_button_hovered: false, + } + } + + pub fn render( + &mut self, + draw_handle: &mut RaylibDrawHandle, + bounds: Rectangle, + can_be_bought: bool, + ) { + // Render the background box + draw_handle.draw_rectangle_rec(bounds, Color::WHITE); + draw_handle.draw_rectangle_lines_ex(bounds, 3, Color::BLACK); + + // Render the name + draw_handle.draw_text( + &format!("{}: {}/{}", self.name, self.current_level, self.max_level), + bounds.x as i32 + 10, + bounds.y as i32 + ((bounds.height as i32 - 20) / 2), + 20, + Color::BLACK, + ); + + // Render the buy button + let buy_button = OnScreenButton::new( + format!("Buy - {}f", self.cost), + Rectangle { + x: bounds.x + bounds.width - 150.0, + y: bounds.y + 5.0, + width: 145.0, + height: bounds.height - 10.0, + }, + match can_be_bought { + true => Color::WHITE, + false => Color::GRAY, + }, + Color::BLACK, + Color::GRAY, + 20, + true, + ); + buy_button.render(draw_handle); + self.buy_button_hovered = buy_button.is_hovered(draw_handle); + } +} diff --git a/src/logic/shop/mainui.rs b/src/logic/shop/mainui.rs new file mode 100644 index 0000000..759da23 --- /dev/null +++ b/src/logic/shop/mainui.rs @@ -0,0 +1,252 @@ +use raylib::prelude::*; + +use crate::{ + gamecore::{GameCore, GameState}, + items::{AirBag, Flashlight, Flippers, ItemBase, StunGun}, + lib::{utils::button::OnScreenButton, wrappers::audio::player::AudioPlayer}, +}; + +use super::{item::ShopItemWrapper, itemui::ShopItemUi}; + +pub fn render_shop( + draw_handle: &mut RaylibDrawHandle, + _thread: &RaylibThread, + audio_system: &mut AudioPlayer, + game_core: &mut GameCore, + bounds: Rectangle, +) -> Option { + // Render background + draw_handle.draw_rectangle_rec(bounds, Color::WHITE); + draw_handle.draw_rectangle_lines_ex(bounds, 3, Color::BLACK); + + // Title + draw_handle.draw_text( + "SHOP", + bounds.x as i32 + (bounds.width / 2.0) as i32 - 50, + bounds.y as i32 + 20, + 40, + Color::BLACK, + ); + + // Bounds for use in item row sizing + let first_bounds = Rectangle { + x: bounds.x + 5.0, + y: bounds.y + 100.0, + width: bounds.width - 10.0, + height: 50.0, + }; + + // Create items + let mut stun_gun_buy_ui = ShopItemWrapper::new( + match &game_core.player.inventory.stun_gun { + Some(x) => match x.get_level() { + 1 => StunGun::lvl2(), + _ => StunGun::lvl3(), + }, + None => StunGun::lvl1(), + }, + &game_core.player.inventory.stun_gun, + first_bounds, + 0, + ); + let mut air_bag_buy_ui = ShopItemWrapper::new( + match &game_core.player.inventory.air_bag { + Some(x) => match x.get_level() { + 1 => AirBag::lvl2(), + _ => AirBag::lvl3(), + }, + None => AirBag::lvl1(), + }, + &game_core.player.inventory.air_bag, + first_bounds, + 1, + ); + let mut flashlight_buy_ui = ShopItemWrapper::new( + match &game_core.player.inventory.flashlight { + Some(x) => match x.get_level() { + 1 => Flashlight::lvl2(), + _ => Flashlight::lvl3(), + }, + None => Flashlight::lvl1(), + }, + &game_core.player.inventory.flashlight, + first_bounds, + 2, + ); + let mut flippers_buy_ui = ShopItemWrapper::new( + match &game_core.player.inventory.flippers { + Some(x) => match x.get_level() { + 1 => Flippers::lvl2(), + _ => Flippers::lvl3(), + }, + None => Flippers::lvl1(), + }, + &game_core.player.inventory.flippers, + first_bounds, + 3, + ); + + // Render items + stun_gun_buy_ui.render(draw_handle, &game_core.player); + air_bag_buy_ui.render(draw_handle, &game_core.player); + flashlight_buy_ui.render(draw_handle, &game_core.player); + flippers_buy_ui.render(draw_handle, &game_core.player); + + // Handle buying items + if stun_gun_buy_ui.can_player_afford(&game_core.player) + && stun_gun_buy_ui.user_clicked_buy(draw_handle) + { + let item = stun_gun_buy_ui.purchase(&mut game_core.player); + game_core.player.inventory.stun_gun = Some(item); + } + if air_bag_buy_ui.can_player_afford(&game_core.player) + && air_bag_buy_ui.user_clicked_buy(draw_handle) + { + let item = air_bag_buy_ui.purchase(&mut game_core.player); + game_core.player.inventory.air_bag = Some(item); + } + if flashlight_buy_ui.can_player_afford(&game_core.player) + && flashlight_buy_ui.user_clicked_buy(draw_handle) + { + let item = flashlight_buy_ui.purchase(&mut game_core.player); + game_core.player.inventory.flashlight = Some(item); + } + if flippers_buy_ui.can_player_afford(&game_core.player) + && flippers_buy_ui.user_clicked_buy(draw_handle) + { + let item = flippers_buy_ui.purchase(&mut game_core.player); + game_core.player.inventory.flippers = Some(item); + } + + // Render the tooltip box + let hovering_stun_gun = stun_gun_buy_ui.user_hovering_row(draw_handle); + let hovering_air_bag = air_bag_buy_ui.user_hovering_row(draw_handle); + let hovering_flashlight = flashlight_buy_ui.user_hovering_row(draw_handle); + let hovering_flippers = flippers_buy_ui.user_hovering_row(draw_handle); + let should_show_tooltip = + hovering_stun_gun || hovering_air_bag || hovering_flashlight || hovering_flippers; + + if should_show_tooltip { + // Create bounds + let box_bounds = Rectangle { + x: bounds.x + 5.0, + y: bounds.y + 350.0, + width: bounds.width - 10.0, + height: 250.0, + }; + + // Get the hovered item + let hovered_item: &dyn ItemBase; + if hovering_stun_gun { + hovered_item = stun_gun_buy_ui.get_item(); + } else if hovering_air_bag { + hovered_item = air_bag_buy_ui.get_item(); + } else if hovering_flashlight { + hovered_item = flashlight_buy_ui.get_item(); + } else { + hovered_item = flippers_buy_ui.get_item(); + } + + // Draw background box + draw_handle.draw_rectangle_rec(box_bounds, Color::WHITE); + draw_handle.draw_rectangle_lines_ex(box_bounds, 3, Color::BLACK); + + // TODO: draw item sprite + draw_handle.draw_rectangle_v( + Vector2 { + x: box_bounds.x + (box_bounds.width / 2.0) - 40.0, + y: box_bounds.y + 10.0, + }, + Vector2 { x: 80.0, y: 80.0 }, + Color::BLACK, + ); + + // Render item description + draw_handle.draw_text( + &hovered_item.get_description(), + box_bounds.x as i32 + 10, + box_bounds.y as i32 + 100, + 30, + Color::BLACK, + ); + } + + // Handle exit buttons + let bottom_left_button_dimensions = Rectangle { + x: bounds.x + 5.0, + y: bounds.y + bounds.height - 50.0, + width: (bounds.width / 2.0) - 15.0, + height: 40.0, + }; + let bottom_right_button_dimensions = Rectangle { + x: (bounds.x + bottom_left_button_dimensions.width) + 15.0, + y: bottom_left_button_dimensions.y, + width: bottom_left_button_dimensions.width, + height: bottom_left_button_dimensions.height, + }; + + let menu_button = OnScreenButton::new( + "Menu".to_string(), + bottom_left_button_dimensions, + Color::WHITE, + Color::BLACK, + Color::GRAY, + 30, + true, + ); + let play_button = OnScreenButton::new( + "Play".to_string(), + bottom_right_button_dimensions, + Color::WHITE, + Color::BLACK, + Color::GRAY, + 30, + true, + ); + + // Render both + menu_button.render(draw_handle); + play_button.render(draw_handle); + + // Handle click actions on the buttons + if draw_handle.is_mouse_button_pressed(MouseButton::MOUSE_LEFT_BUTTON) { + // Handle saving core state + if menu_button.is_hovered(draw_handle) || play_button.is_hovered(draw_handle) { + let new_progress = game_core + .player + .create_statistics(game_core, draw_handle.get_time()); + game_core.progress.update(&new_progress); + } + + if menu_button.is_hovered(draw_handle) { + return Some(GameState::MainMenu); + } else if play_button.is_hovered(draw_handle) { + // Reset the world + game_core.world.reset(&mut game_core.player); + + // Start playing + return Some(GameState::InGame); + } + } + + return None; +} + +pub fn render_stats( + draw_handle: &mut RaylibDrawHandle, + game_core: &mut GameCore, + bounds: Rectangle, +) { + // Render background + draw_handle.draw_rectangle_rec(bounds, Color::WHITE); + draw_handle.draw_rectangle_lines_ex(bounds, 3, Color::BLACK); + + // Coins + draw_handle.draw_text( + &format!("Fish: {}", game_core.player.coins.min(99)), + bounds.x as i32 + 5, + bounds.y as i32 + 5, + 20, + Color::BLACK, + ); +} diff --git a/src/logic/shop/mod.rs b/src/logic/shop/mod.rs new file mode 100644 index 0000000..9885e04 --- /dev/null +++ b/src/logic/shop/mod.rs @@ -0,0 +1,70 @@ +mod item; +mod itemui; +mod mainui; + +use raylib::prelude::*; + +use crate::{ + gamecore::{GameCore, GameState}, + lib::wrappers::audio::player::AudioPlayer, +}; + +use self::mainui::{render_shop, render_stats}; + +use super::screen::Screen; + +const SCREEN_PANEL_SIZE: Vector2 = Vector2 { x: 300.0, y: 380.0 }; + +#[derive(Debug, Default)] +pub struct ShopScreen { + // shop_items: Vec, +} + +impl ShopScreen { + pub fn new() -> Self { + Self { + ..Default::default() + } + } +} + +impl Screen for ShopScreen { + fn render( + &mut self, + draw_handle: &mut RaylibDrawHandle, + thread: &RaylibThread, + audio_system: &mut AudioPlayer, + game_core: &mut GameCore, + ) -> Option { + let mouse_position = draw_handle.get_mouse_position(); + + // Render the background + draw_handle.draw_texture(&game_core.resources.shop_background, 0, 0, Color::WHITE); + + // Window dimensions + let win_height = draw_handle.get_screen_height(); + let win_width = draw_handle.get_screen_width(); + + // Build a rect for the shop UI to sit inside + let shop_ui_bounds = Rectangle { + x: win_width as f32 - (win_width as f32 / 2.0), + y: 10.0, + width: (win_width as f32 / 2.0) - 10.0, + height: win_height as f32 - 20.0, + }; + let stats_ui_bounds = Rectangle { + x: win_width as f32 - (win_width as f32 / 2.0) - 130.0, + y: 10.0, + width: 120.0, + height: 30.0, + }; + + // Render the shop UI + let next_state = render_shop(draw_handle, thread, audio_system, game_core, shop_ui_bounds); + + // Render the stats UI + render_stats(draw_handle, game_core, stats_ui_bounds); + + return next_state; + } +} diff --git a/src/main.rs b/src/main.rs index aa48d4a..64c8e2a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,22 @@ +mod entities; mod gamecore; +mod items; mod lib; mod logic; -mod resources; -mod player; -mod world; mod pallette; -mod entities; -mod items; +mod player; +mod resources; +mod world; use gamecore::{GameCore, GameProgress, GameState}; use lib::{utils::profiler::GameProfiler, wrappers::audio::player::AudioPlayer}; use log::info; -use logic::{gameend::GameEndScreen, ingame::InGameScreen, loadingscreen::LoadingScreen, mainmenu::MainMenuScreen, pausemenu::PauseMenuScreen, screen::Screen}; +use logic::{ + gameend::GameEndScreen, ingame::InGameScreen, loadingscreen::LoadingScreen, + mainmenu::MainMenuScreen, pausemenu::PauseMenuScreen, screen::Screen, shop::ShopScreen, +}; use raylib::prelude::*; -use world::{World, load_world_colliders}; +use world::{load_world_colliders, World}; // Game Launch Configuration const DEFAULT_WINDOW_DIMENSIONS: Vector2 = Vector2 { @@ -32,7 +35,8 @@ fn main() { .size( DEFAULT_WINDOW_DIMENSIONS.x as i32, DEFAULT_WINDOW_DIMENSIONS.y as i32, - ).msaa_4x() + ) + .msaa_4x() .title(WINDOW_TITLE) .build(); raylib.set_target_fps(MAX_FPS); @@ -41,14 +45,21 @@ fn main() { raylib.set_exit_key(None); // Load the world - let world_colliders = load_world_colliders("./assets/img/map/cave.json".to_string()).expect("Failed to load world colliders"); - let world = World::load_from_json("./assets/worlds/mainworld.json".to_string(), world_colliders).expect("Failed to load main world JSON"); + let world_colliders = load_world_colliders("./assets/img/map/cave.json".to_string()) + .expect("Failed to load world colliders"); + let world = World::load_from_json( + "./assets/worlds/mainworld.json".to_string(), + world_colliders, + ) + .expect("Failed to load main world JSON"); // Load the game progress let game_progress = GameProgress::try_from_file("./assets/savestate.json".to_string()); // Set up the game's core state let mut game_core = GameCore::new(&mut raylib, &raylib_thread, world, game_progress); + game_core.player.inventory = game_core.progress.inventory.clone(); + game_core.player.coins = game_core.progress.coins; // Set up the game's profiler let mut profiler = GameProfiler::new(); @@ -63,6 +74,7 @@ fn main() { let mut pause_menu_screen = PauseMenuScreen::new(); let mut ingame_screen = InGameScreen::new(); let mut game_end_screen = GameEndScreen::new(); + let mut shop_screen = ShopScreen::new(); // Main rendering loop while !raylib.window_should_close() { @@ -101,6 +113,12 @@ fn main() { &mut audio_system, &mut game_core, ), + GameState::InShop => shop_screen.render( + &mut draw_handle, + &raylib_thread, + &mut audio_system, + &mut game_core, + ), }; // If needed, update the global state @@ -109,6 +127,12 @@ fn main() { // Handle game quit if new_state == GameState::GameQuit { + // Save the game state + let new_progress = game_core + .player + .create_statistics(&game_core, draw_handle.get_time()); + game_core.progress.update(&new_progress); + // For now, just quit // This also throws a SEGFAULT.. yay for unsafe code.. info!("User quit game"); @@ -165,6 +189,12 @@ fn main() { game_core.last_frame_time = draw_handle.get_time(); } + // Save the game state + let new_progress = game_core + .player + .create_statistics(&game_core, raylib.get_time()); + game_core.progress.update(&new_progress); + // Cleanup profiler.stop(); } diff --git a/src/player.rs b/src/player.rs index e7f367e..0a78a20 100644 --- a/src/player.rs +++ b/src/player.rs @@ -24,8 +24,8 @@ pub struct PlayerInventory { impl PlayerInventory { pub fn new() -> Self { Self { - stun_gun: Some(StunGun::lvl1()), //TMP - flashlight: Some(Flashlight::lvl1()), //TMP + // stun_gun: Some(StunGun::lvl1()), //TMP + // flashlight: Some(Flashlight::lvl1()), //TMP ..Default::default() } } @@ -62,6 +62,17 @@ impl Player { } } + pub fn reset(&mut self, position: Vector2) { + self.position = position; + self.breath_percent = 1.0; + self.boost_percent = 1.0; + + // Handle an air bag being used + if self.inventory.air_bag.is_some() { + self.breath_percent += self.inventory.air_bag.as_ref().unwrap().extra_oxygen; + } + } + pub fn collides_with_rec(&self, rectangle: &Rectangle) -> bool { // // Build a bounding box of the player by their corners // let top_left_corner = self.position - (self.size / 2.0); diff --git a/src/resources.rs b/src/resources.rs index 3e7b27a..cee06c9 100644 --- a/src/resources.rs +++ b/src/resources.rs @@ -30,7 +30,11 @@ pub struct GlobalResources { pub jellyfish_animation_attack: FrameAnimationWrapper, // Darkness layer - pub darkness_overlay: Texture2D + pub darkness_overlay: Texture2D, + + // Shop & items + pub shop_background: Texture2D, + } impl GlobalResources { @@ -124,6 +128,10 @@ impl GlobalResources { &thread, &Image::load_image("./assets/img/map/darkness.png")?, )?, + shop_background: raylib.load_texture_from_image( + &thread, + &Image::load_image("./assets/img/map/shopHighRes.png")?, + )?, }) } } diff --git a/src/world.rs b/src/world.rs index b4237c4..ed58b00 100644 --- a/src/world.rs +++ b/src/world.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use std::io::Read; use failure::Error; -use crate::entities::{enemy::{jellyfish::JellyFish, octopus::Octopus}, fish::FishEntity}; +use crate::{entities::{enemy::{jellyfish::JellyFish, octopus::Octopus}, fish::FishEntity}, player::Player}; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct World { @@ -52,16 +52,18 @@ impl World { Ok(result) } - pub fn spend_coins(&mut self, count: usize) { - for _ in 0..count { - self.fish.pop(); - } - } + // pub fn spend_coins(&mut self, count: usize) { + // for _ in 0..count { + // self.fish.pop(); + // } + // } - pub fn reset(&mut self) { - for fish in self.fish.iter_mut() { - fish.following_player = false; - } + pub fn reset(&mut self, player: &mut Player) { + // Init all fish + self.fish = FishEntity::new_from_positions(&self.fish_positions); + + // Reset the player + player.reset(self.player_spawn); } }