summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml1
-rw-r--r--assets/speak.oggbin0 -> 5916 bytes
-rw-r--r--dungeon/src/lib.rs29
-rw-r--r--dungeon/src/msg.rs74
-rw-r--r--dungeon/src/player_input.rs3
-rw-r--r--game/src/main.rs27
-rw-r--r--graphics/src/audio.rs28
-rw-r--r--graphics/src/lib.rs9
-rw-r--r--graphics/src/render.rs68
9 files changed, 221 insertions, 18 deletions
diff --git a/Cargo.toml b/Cargo.toml
index 98c9585..fd65dd7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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
new file mode 100644
index 0000000..dc3ef20
--- /dev/null
+++ b/assets/speak.ogg
Binary files differ
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