diff options
| -rw-r--r-- | Cargo.toml | 1 | ||||
| -rw-r--r-- | assets/speak.ogg | bin | 0 -> 5916 bytes | |||
| -rw-r--r-- | dungeon/src/lib.rs | 29 | ||||
| -rw-r--r-- | dungeon/src/msg.rs | 74 | ||||
| -rw-r--r-- | dungeon/src/player_input.rs | 3 | ||||
| -rw-r--r-- | game/src/main.rs | 27 | ||||
| -rw-r--r-- | graphics/src/audio.rs | 28 | ||||
| -rw-r--r-- | graphics/src/lib.rs | 9 | ||||
| -rw-r--r-- | graphics/src/render.rs | 68 |
9 files changed, 221 insertions, 18 deletions
@@ -40,6 +40,7 @@ features = [ "SUPPORT_MODULE_RTEXTURES", "SUPPORT_MODULE_RAUDIO", "SUPPORT_FILEFORMAT_BMP", + "SUPPORT_FILEFORMAT_OGG", "SUPPORT_STANDARD_FILEIO", "SUPPORT_TRACELOG", ] diff --git a/assets/speak.ogg b/assets/speak.ogg Binary files differnew file mode 100644 index 0000000..dc3ef20 --- /dev/null +++ b/assets/speak.ogg diff --git a/dungeon/src/lib.rs b/dungeon/src/lib.rs index a475f95..0c9e055 100644 --- a/dungeon/src/lib.rs +++ b/dungeon/src/lib.rs @@ -5,6 +5,7 @@ pub mod astar; pub mod bsp; pub mod entity; pub mod map; +pub mod msg; pub mod player_input; pub mod pos; @@ -16,10 +17,23 @@ use rand::{ use crate::{ entity::{Entity, Player}, map::Floor, + msg::Message, player_input::PlayerInput, pos::FPos, }; +/// Lets the caller know what has +/// changed in the game state this +/// tick +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum UpdateResult { + /// Default, entities have moved + EntityMovement, + /// Message on screen updated. + /// Contains if a char was added. + MessageUpdated(bool), +} + /// The `Dungeon` type represents the game state of the /// dungeon crawler. #[derive(Debug, Clone)] @@ -27,6 +41,7 @@ pub struct Dungeon { pub floor: Floor, pub player: Player, pub enemies: Vec<Entity>, + pub msg: Message, seed: u64, rng: SmallRng, } @@ -48,11 +63,13 @@ impl Dungeon { let player = Player::new(floor.player_start()); // TODO: Randomize enemy positions/types let enemies = vec![Entity::zombie(floor.random_walkable_pos(&mut rng))]; + let msg = Message::empty(); Self { floor, player, enemies, + msg, seed, rng, } @@ -91,9 +108,15 @@ impl Dungeon { &mut self.rng } - pub fn update(&mut self, player_input: PlayerInput, delta_time: f32) { - self.act_player(player_input, delta_time); - self.act_non_players(delta_time); + pub fn update(&mut self, player_input: PlayerInput, delta_time: f32) -> UpdateResult { + if self.msg.visible() { + let changed = self.msg.update(player_input); + UpdateResult::MessageUpdated(changed) + } else { + self.act_player(player_input, delta_time); + self.act_non_players(delta_time); + UpdateResult::EntityMovement + } } fn act_player(&mut self, player_input: PlayerInput, delta_time: f32) { diff --git a/dungeon/src/msg.rs b/dungeon/src/msg.rs new file mode 100644 index 0000000..68a6cbd --- /dev/null +++ b/dungeon/src/msg.rs @@ -0,0 +1,74 @@ +//! The `msg` crate contains the core functionality for +//! the on screen message. + +use std::time::{Duration, Instant}; + +use crate::player_input::PlayerInput; + +/// Duration between each character printed +const DURATION_PER: Duration = Duration::from_millis(100); + +#[derive(Debug, Clone)] +pub struct Message { + text: Option<Vec<u8>>, + end: usize, + waiting: bool, + last: Instant, +} +impl Message { + /// Returns the default empty message + pub fn empty() -> Self { + Self { + text: None, + end: 0, + waiting: false, + last: Instant::now(), + } + } + + /// Sets string as the current onscreen message + pub fn set_message(&mut self, msg: &str) { + let bytes = msg.bytes().collect(); + self.text = Some(bytes); + self.end = 0; + } + + /// Attempts to update the message on screen. + /// Returns true if a character was printed + pub fn update(&mut self, input: PlayerInput) -> bool { + let Some(text) = &self.text else { return false }; + + if self.end >= text.len() { + if input.interact { + self.text = None; + } + return false; + } + + if self.waiting && !input.interact { + return false; + } + + if self.last.elapsed() < DURATION_PER { + return false; + } + self.last = Instant::now(); + + if text[self.end] == b'.' { + self.waiting = true; + } + + self.end += 1; + true + } + + /// Returns the current ascii message (if exists) + pub fn current(&self) -> Option<&[u8]> { + self.text.as_ref().map(|text| &text[..self.end]) + } + + /// Returns if the message box is visible + pub const fn visible(&self) -> bool { + self.text.is_some() + } +} diff --git a/dungeon/src/player_input.rs b/dungeon/src/player_input.rs index aa0ef14..ddaad9f 100644 --- a/dungeon/src/player_input.rs +++ b/dungeon/src/player_input.rs @@ -12,5 +12,8 @@ pub struct PlayerInput { /// (eg if the player is holding multiple keys at once, /// or a joystick) pub direction: Option<Direction>, + /// If the player is currently attempting to interact + /// with some object, entity, etc. + pub interact: bool, // other player actions are to be added later } diff --git a/game/src/main.rs b/game/src/main.rs index b79331b..82b1ad2 100644 --- a/game/src/main.rs +++ b/game/src/main.rs @@ -1,5 +1,5 @@ use argh::FromArgs; -use dungeon::{Dungeon, player_input::PlayerInput, pos::Direction}; +use dungeon::{Dungeon, UpdateResult, player_input::PlayerInput, pos::Direction}; use graphics::{Key, Window, WindowBuilder}; struct Game { @@ -15,6 +15,7 @@ impl Game { Some(s) => Dungeon::new(s), None => Dungeon::random(), }; + Self { window, dungeon, @@ -60,18 +61,38 @@ impl Game { fn run(&mut self) { // Main game loop while self.window.is_open() { - // Handle keyboard input + // Handle debug keys if self.window.is_key_pressed(Key::F3) { self.window.toggle_debug(); } + if self.window.is_key_pressed(Key::F4) { + self.dungeon + .msg + .set_message("Lorem ipsum dolor sit amet consectetur adipiscing elit"); + } let inputs = PlayerInput { direction: self.player_dir(), + interact: self.window.is_key_pressed(Key::Return), }; // Update game state - self.dungeon + let result = self + .dungeon .update(inputs, self.window.delta_time().as_secs_f32()); + match result { + UpdateResult::EntityMovement => {} + UpdateResult::MessageUpdated(changed) => { + if changed { + self.window.audio().speak.play(); + } + } + } + + // Update on screen message + if self.dungeon.msg.update(inputs) { + self.window.audio().speak.play(); + } // Draw a single frame self.window.draw_frame(&self.dungeon); diff --git a/graphics/src/audio.rs b/graphics/src/audio.rs index 8cf155f..3a20f62 100644 --- a/graphics/src/audio.rs +++ b/graphics/src/audio.rs @@ -2,14 +2,27 @@ use raylib::audio::RaylibAudio; -#[expect(dead_code)] +macro_rules! load_audio { + ($handle:expr, $filepath:expr) => { + if cfg!(feature = "static") { + let bytes = include_bytes!(concat!("../../", $filepath)); + let wave = $handle.new_wave_from_memory(".ogg", bytes)?; + $handle.new_sound_from_wave(&wave)? + } else { + $handle.new_sound($filepath)? + } + }; +} + type Sound = raylib::audio::Sound<'static>; /// The `Audio` container initalizes the audio subsystem /// for raylib, leaks it (to gurentee audio is statically loaded), /// then loads all needed audio samples #[derive(Debug)] -pub struct Audio {} +pub struct Audio { + pub speak: Sound, +} impl Audio { pub(crate) fn load() -> crate::Result<Self> { // Phantom handle to the raylib audio subsystem @@ -21,12 +34,13 @@ impl Audio { // // NOTE: would this cause issues if `Audio::load` was // called multiple times? - let _handle = Box::leak(Box::new(RaylibAudio::init_audio_device()?)); - - // TODO: load audio samples + let handle = Box::leak(Box::new(RaylibAudio::init_audio_device()?)); - //let example = handle.new_sound("example.ogg")?; + // yes i know this is sans undertale, it was funny + // and i cannot think of anything better yet, haha + // - freya + let speak = load_audio!(handle, "assets/speak.ogg"); - Ok(Self {}) + Ok(Self { speak }) } } diff --git a/graphics/src/lib.rs b/graphics/src/lib.rs index d12eac0..0fdcedb 100644 --- a/graphics/src/lib.rs +++ b/graphics/src/lib.rs @@ -32,8 +32,11 @@ pub enum Key { Down, Left, Right, + // Interact + Return, // Debug keys F3, + F4, // Unknown/Unused key Unknown(KeyboardKey), } @@ -51,8 +54,11 @@ impl From<KeyboardKey> for Key { K::KEY_DOWN => Self::Down, K::KEY_LEFT => Self::Left, K::KEY_RIGHT => Self::Right, + // Interact + K::KEY_ENTER => Self::Return, // Debug keys K::KEY_F3 => Self::F3, + K::KEY_F4 => Self::F4, // Unknown/Unused key _ => Self::Unknown(key), } @@ -71,8 +77,11 @@ impl From<Key> for KeyboardKey { Key::Down => Self::KEY_DOWN, Key::Left => Self::KEY_LEFT, Key::Right => Self::KEY_RIGHT, + // Interact + Key::Return => Self::KEY_ENTER, // Debug keys Key::F3 => Self::KEY_F3, + Key::F4 => Self::KEY_F4, // Unknown/Unused key Key::Unknown(k) => k, } diff --git a/graphics/src/render.rs b/graphics/src/render.rs index 340189b..996aa37 100644 --- a/graphics/src/render.rs +++ b/graphics/src/render.rs @@ -496,13 +496,16 @@ impl Renderer { if self.debug { self.draw_debug_ui(r, dungeon); - return; + } else { + // Draw core ui components + self.draw_minimap(r, dungeon); + self.draw_inventory(r, &dungeon.player); + self.draw_stats(r, &dungeon.player); } - // Draw core ui components - self.draw_minimap(r, dungeon); - self.draw_inventory(r, &dungeon.player); - self.draw_stats(r, &dungeon.player); + if let Some(buf) = dungeon.msg.current() { + self.draw_msg(r, buf); + } } /// Draw in game minimap @@ -686,6 +689,61 @@ impl Renderer { draw_text!(self, r, UI_COL2, UI_ROW3, "FRAME {frame}"); } + /// Draw msg box + fn draw_msg<R>(&self, r: &mut R, msg: &[u8]) + where + R: RaylibDraw, + { + const MAX_LINES: u16 = 5; + const MSG_LEN: u16 = 20; + + const HEIGHT: u16 = MAX_LINES * FONT_SIZE + UI_PADDING * 2; + const WIDTH: u16 = MSG_LEN * FONT_SIZE + UI_PADDING * 2; + const X: u16 = RENDER_WIDTH / 2 - WIDTH / 2; + const Y: u16 = RENDER_HEIGHT - HEIGHT - UI_PADDING; + + // draw background + r.draw_rectangle( + X.into(), + Y.into(), + WIDTH.into(), + HEIGHT.into(), + Color::BLACK, + ); + r.draw_rectangle_lines( + X.into(), + Y.into(), + WIDTH.into(), + HEIGHT.into(), + Color::WHITE, + ); + + let mut x = 0; + let mut y = 0; + + let words = msg.split(u8::is_ascii_whitespace); + for word in words { + let left = MSG_LEN.saturating_sub(x); + if word.len() > left as usize { + y += 1; + x = 0; + } + + for char in word { + self.draw_char( + r, + *char, + X + UI_PADDING + x * FONT_SIZE, + Y + UI_PADDING + y * FONT_SIZE, + ); + + x += 1; + } + + x += 1; + } + } + /// Draws vertical text fn draw_text_vertical<R>(&self, r: &mut R, text: &[u8], x: u16, y_start: u16) where |