diff --git a/src/game_state.rs b/src/game_state.rs index 7e6e08c7b21c29844a4cd5796afbde974e759d5f..bbdb7e09721e235062044f4eff93f51beadbd6af 100644 --- a/src/game_state.rs +++ b/src/game_state.rs @@ -5,6 +5,7 @@ mod enemy; mod textures; pub mod config; mod tile; +mod combat_log; use tetra::{Context, State}; use tetra::graphics::{self, Color, DrawParams}; @@ -15,6 +16,9 @@ use crate::game_state::tile::Tile; use tetra::math::Vec2; use std::cmp::min; use rand::{thread_rng, Rng}; +use tetra::graphics::text::{VectorFontBuilder, Font, Text}; +use crate::game_state::combat_log::PlayerAction; +use crate::game_state::combat_log::PlayerAction::Move; type Point = (usize, usize); @@ -22,18 +26,25 @@ pub struct GameState { current_run: Run, high_score: usize, textures: Textures, + font: Font, } impl GameState { pub fn new(ctx: &mut Context) -> tetra::Result<GameState> { let textures = Textures::init(ctx); + let font = VectorFontBuilder::new("./resources/corbel.ttf")?.with_size(ctx, 16.0); if textures.is_ok() { - Ok(GameState { - current_run: Run::new(), - high_score: 0, - textures: textures.unwrap(), - }) + if font.is_ok() { + Ok(GameState { + current_run: Run::new(), + high_score: 0, + textures: textures.unwrap(), + font: font.unwrap(), + }) + } else { + Err(font.err().unwrap()) + } } else { Err(textures.err().unwrap()) } @@ -54,6 +65,8 @@ impl State for GameState { graphics::clear(ctx, Color::rgb(0.220, 0.220, 0.220)); self.draw_board(ctx); + self.draw_stats(ctx); + self.draw_combat_log(ctx); Ok(()) } @@ -88,9 +101,12 @@ impl GameState { let target_tile = self.current_run.level.get_tile_at(target_position); match target_tile { - Tile::Empty => self.current_run.level.move_player_to(target_position), + Tile::Empty => { + self.current_run.level.move_player_to(target_position); + self.current_run.combat_log.player_action = Option::from(Move); + }, Tile::Wall => return false, - _ => {} + _ => () } true } @@ -177,7 +193,6 @@ impl GameState { impl GameState { fn finish_turn(&mut self) { - self.current_run.turn_count += 1; self.current_run.level.turn_count += 1; } } @@ -256,4 +271,58 @@ impl GameState { color: Color::WHITE, } } + + fn draw_stats(&self, ctx: &mut Context) { + let text_top = GameState::calculate_text_top(); + let health_string = format!("Health: {} / {}", self.current_run.player.current_hp, self.current_run.player.max_hp); + let turn_string = format!("Turn: {}", self.current_run.level.turn_count); + let level_string = format!("Level: {} High Score: {}", self.current_run.level_count, self.high_score); + + self.draw_string(health_string, 50.0, text_top, ctx); + self.draw_string(turn_string, 50.0, text_top + 25.0, ctx); + self.draw_string(level_string, 50.0, text_top + 50.0, ctx); + } + + fn draw_combat_log(&self, ctx: &mut Context) { + self.draw_player_action(ctx); + self.draw_enemy_action(ctx); + } + + fn draw_player_action(&self, ctx: &mut Context) { + let text_top = GameState::calculate_text_top(); + match self.current_run.combat_log.player_action { + Some(PlayerAction::Move) => + self.draw_string("You moved.".parse().unwrap(), 300.0, text_top, ctx), + Some(PlayerAction::Wait) => + self.draw_string("You waited.".parse().unwrap(), 300.0, text_top, ctx), + Some(PlayerAction::Attack(damage_done, remaining_hp)) => + self.draw_string( + format!("You attacked for {} damage. The enemy has {} hit points remaining.", damage_done, remaining_hp), + 300.0, + text_top, + ctx + ), + None => () + }; + } + + fn draw_enemy_action(&self, ctx: &mut Context) { + let text_top = GameState::calculate_text_top() + 25.0; + + match self.current_run.combat_log.damage_taken { + Some(damage) => + self.draw_string(format!("The enemy attacked you for {} damage.", damage), 300.0, text_top, ctx), + + None => () + }; + } + + fn draw_string(&self, string: String, x: f32, y: f32, ctx: &mut Context) { + Text::new(string, self.font.clone()) + .draw(ctx, Vec2::new(x, y)); + } + + fn calculate_text_top() -> f32 { + 33.0 * (config::BOARD_HEIGHT as f32 + 2.0) + 7.0 + } } \ No newline at end of file diff --git a/src/game_state/combat_log.rs b/src/game_state/combat_log.rs new file mode 100644 index 0000000000000000000000000000000000000000..dc7f853400879bf814b39bcd6224ed0f07b5dc34 --- /dev/null +++ b/src/game_state/combat_log.rs @@ -0,0 +1,12 @@ +#[derive(Debug)] +pub struct CombatLog { + pub player_action: Option<PlayerAction>, + pub damage_taken: Option<usize> +} + +#[derive(Debug, Copy, Clone)] +pub enum PlayerAction { + Move, + Wait, + Attack(usize, usize) // damage, remaining hp +} \ No newline at end of file diff --git a/src/game_state/config.rs b/src/game_state/config.rs index 7d3faa87040fa27c983ceed2339a5740f82b1a18..85121bf2c42beceeff5c0e5b0a505b2b28fa4a3b 100644 --- a/src/game_state/config.rs +++ b/src/game_state/config.rs @@ -1,7 +1,7 @@ -pub const WINDOW_WIDTH: f32 = 1280.0; -pub const WINDOW_HEIGHT: f32 = 720.0; -pub const BOARD_WIDTH: usize = 35; -pub const BOARD_HEIGHT: usize = 19; +pub const WINDOW_WIDTH: f32 = 1920.0; +pub const WINDOW_HEIGHT: f32 = 1080.0; +pub const BOARD_WIDTH: usize = 50; +pub const BOARD_HEIGHT: usize = 28; pub const PLAYER_STARTING_AP: usize = 1; pub const PLAYER_STARTING_HP: usize = 10; diff --git a/src/game_state/player.rs b/src/game_state/player.rs index d37c3279ae70308a570fd64ac453ffca6d5d8106..0da27545759256cb8525a1da38e34d201ca61fc5 100644 --- a/src/game_state/player.rs +++ b/src/game_state/player.rs @@ -1,7 +1,7 @@ pub struct Player { - current_hp: usize, - max_hp: usize, - attack_power: usize, + pub current_hp: usize, + pub max_hp: usize, + pub attack_power: usize, } impl Player { diff --git a/src/game_state/run.rs b/src/game_state/run.rs index 5c909e7f0bdbfe71a0aa25c931f78e2ab608ffdb..9210411548af3df3ef64a38c1004302101d3cf0f 100644 --- a/src/game_state/run.rs +++ b/src/game_state/run.rs @@ -1,12 +1,13 @@ use crate::game_state::player::Player; use crate::game_state::level::Level; use crate::game_state::config; +use crate::game_state::combat_log::CombatLog; pub struct Run { - pub turn_count: usize, - level_count: usize, - player: Player, + pub level_count: usize, + pub player: Player, pub level: Level, + pub combat_log: CombatLog } impl Run { @@ -15,10 +16,13 @@ impl Run { let ap = config::PLAYER_STARTING_AP; Run { - turn_count: 0, level_count: 0, player: Player::new(ap, hp), level: Level::new(0), + combat_log: CombatLog { + player_action: None, + damage_taken: None + } } } } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index c79c92fd7b5de5a2f847ff9779a0d771a6f5aaf3..6033692eef76bb8527adf571f540be99e989f64b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,315 +1,13 @@ -use std::collections::HashMap; -use tetra::graphics::text::{Font, Text, VectorFontBuilder}; -use tetra::graphics::{self, Color, DrawParams, Texture}; -use tetra::input::{self, Key}; -use tetra::math::Vec2; -use tetra::{Context, ContextBuilder, State}; -use rand::{thread_rng, Rng}; +mod game_state; +use tetra::ContextBuilder; -//constants -const WINDOW_WIDTH: f32 = 1920.0; -const WINDOW_HEIGHT: f32 = 1080.0; -const BOARD_WIDTH: usize = 58; -const BOARD_HEIGHT: usize = 28; +use crate::game_state::GameState; +use crate::game_state::config; fn main() -> tetra::Result { - ContextBuilder::new("rusttest", WINDOW_WIDTH as i32, WINDOW_HEIGHT as i32) + ContextBuilder::new("rusttest", config::WINDOW_WIDTH as i32, config::WINDOW_HEIGHT as i32) .quit_on_escape(true) .build()? .run(GameState::new) } - -struct GameState { -player: Entity, -foes: Foes, -tile: Texture, -world: World, -ui: UI, -stats: Stats, -} - -impl GameState { - fn new(ctx: &mut Context) -> tetra::Result<GameState> { - //load graphics - let player_texture = Texture::new(ctx, "./resources/player.png")?; - let tile_texture = Texture::new(ctx, "./resources/tile.png")?; - - //set initial position - let player_position = Vec2::new(5,7); - - //UI - let font = VectorFontBuilder::new("./resources/corbel.ttf")?; - let font2 = font.with_size(ctx, 16.0)?; - - Ok(GameState { - player: Entity::new_pos(player_texture, player_position, 1), - foes: Foes::generate(6, Texture::new(ctx, "./resources/spike.png")?), - tile: tile_texture, - world: World::new(), - ui: UI::new(font2), - stats: Stats::new(), - }) - } -} - -impl State for GameState { - // draw call - fn draw(&mut self, ctx: &mut Context) -> tetra::Result { - graphics::clear(ctx, Color::rgb(0.220, 0.220, 0.220)); - - for x in 0..BOARD_WIDTH{ - for y in 0..BOARD_HEIGHT{ - let tile = self.world.tiles[x][y] as WorldTile; - let mut c = Color::BLACK; - if tile.passable { - c = Color::WHITE; - } - self.tile.draw(ctx, DrawParams{ - position: Vec2::new(x as f32*33.0,y as f32*33.0), - scale: Vec2::new(1.0,1.0), - origin: Vec2::new(0.0,0.0), - rotation: 0.0, - color: c, - }) - } - } - - self.player.draw(ctx); - - let ids: Vec<&u32> = self.foes.list.keys().collect(); - for id in &ids{ - self.foes.list[id].draw(ctx); - } - self.ui.updateStats(ctx, self.stats); - self.ui.updateEvents(ctx, "Something happend."); - - - Ok(()) - } - - //game loop - fn update(&mut self, ctx: &mut Context) -> tetra::Result { - - let mut tick = false; - //input - if input::is_key_pressed(ctx, Key::A) { - if self.world.is_passable(Vec2::new(self.player.position.x-1,self.player.position.y)) { - if self.world.is_occupied_by(Vec2::new(self.player.position.x-1,self.player.position.y))>1 { - //collision - } - else { - self.player.position.x -= 1; - } - - - } - self.player.transform.scale.x = -1.0; - tick = true; - } - if input::is_key_pressed(ctx, Key::D) { - if self.world.is_passable(Vec2::new(self.player.position.x+1,self.player.position.y)) { - self.player.position.x += 1; - tick = true; - } - self.player.transform.scale.x = 1.0; - } - if input::is_key_pressed(ctx, Key::W) { - if self.world.is_passable(Vec2::new(self.player.position.x,self.player.position.y-1)) { - self.player.position.y -= 1; - tick = true; - } - } - if input::is_key_pressed(ctx, Key::S) { - if self.world.is_passable(Vec2::new(self.player.position.x,self.player.position.y+1)) { - self.player.position.y += 1; - tick = true; - } - } - if input::is_key_pressed(ctx, Key::Space) { - tick = true; - } - - - // logic - if tick{ - - - let ids: Vec<&u32> = self.foes.list.keys().collect(); - for id in &ids{ - // tried To move the logic into the entity, but run into reference ownership issues - //self.foes.list[id].update(self); - let mut newpos = self.foes.list[id].position; - let mut rng = thread_rng(); - let n:u8 = rng.gen_range(0, 4); - match n{ - 0 => newpos.x += 1, - 1 => newpos.x -= 1, - 2 => newpos.y += 1, - 3 => newpos.y -= 1, - _ => newpos.y -= 0, //impossible to reach, can we avoid this somehow? - } - if self.world.is_passable(newpos){ - // ownership issues - //self.foes.list[id].position = newpos; - } - } - } - - Ok(()) - } -} - -struct Entity { - texture: Texture, - transform: DrawParams, - position: Vec2<u32>, - id: u32, -} - -impl Entity { - fn new_pos(texture: Texture, initial_position: Vec2<u32>, id: u32) -> Entity{ - Entity::with_darwparams(texture, DrawParams{ - position: Vec2::new(16.0+(initial_position.x as f32) * 33.0, 16.0+(initial_position.y as f32) * 33.0), - scale: Vec2::new(1.0,1.0), - origin: Vec2::new(16.0,16.0), - rotation: 0.0, - color: Color::WHITE, - }, initial_position, - id) - } - fn with_darwparams(texture: Texture, transform: DrawParams, position: Vec2<u32>, id: u32) -> Entity{ - Entity{ - texture, - transform, - position, - id, - } - } - fn get_drawparams(&self) -> DrawParams{ - let mut dp = self.transform.clone(); - dp.position= Vec2::new(16.0+(self.position.x as f32) * 33.0, 16.0+(self.position.y as f32) * 33.0); - dp - } - fn update(&self, gs: &GameState ){ - // let mut newpos = self.position; - // let mut rng = thread_rng(); - // let n:u8 = rng.gen_range(0, 4); - // match n{ - // 0 => newpos.x += 1, - // 1 => newpos.x -= 1, - // 2 => newpos.y += 1, - // 3 => newpos.y -= 1, - // _ => newpos.y -= 0, //impossible to reach, can we avoid this somehow? - // } - // if gs.world.is_passable(newpos){ - // self.position = newpos; - // } - } - - fn draw(&self, ctx: &mut Context){ - self.texture.draw(ctx, self.get_drawparams()); - } -} -#[derive(Copy, Clone)] -struct WorldTile{ - passable: bool, - occupant: u32, -} - -struct World{ - tiles: [[WorldTile; BOARD_HEIGHT]; BOARD_WIDTH], -} - -impl World{ - fn new() -> World{ - let mut new_world: [[WorldTile; BOARD_HEIGHT]; BOARD_WIDTH] = [[WorldTile{passable: false, occupant:0}; BOARD_HEIGHT];BOARD_WIDTH]; - - for x in 0..BOARD_WIDTH{ - for y in 0..BOARD_HEIGHT{ - if x==0||x==BOARD_WIDTH-1||y==0||y==BOARD_HEIGHT-1 { - new_world[x][y].passable = false; - } - else{ - let mut rng = thread_rng(); - let n:u8 = rng.gen_range(0, 10); - if n<2{ - new_world[x][y].passable = false; - } - else {new_world[x][y].passable = true;} - } - } - } - World{tiles: new_world} - } - - fn is_passable(&self, pos:Vec2<u32>) -> bool{ - let tile = self.tiles[pos.x as usize][pos.y as usize] as WorldTile; - tile.passable - } - fn is_occupied_by(&self, pos:Vec2<u32>) -> u32{ - let tile = self.tiles[pos.x as usize][pos.y as usize] as WorldTile; - tile.occupant - } - -} - -struct Foes{ - list: HashMap<u32, Entity> -} - -impl Foes{ - fn generate(n:u32, texture:Texture) -> Foes{ - let mut entities = HashMap::new(); - for i in 2..n{ - // initial foe position to be calculated here - entities.insert(i, Entity::new_pos(texture.clone(),Vec2::new(i,i), i)); - } - Foes{list:entities} - } -} - -struct UI{ - font: Font -} - -impl UI{ - fn new(font:Font) -> UI{ - UI{ - font: font, - } - } - fn updateStats(&mut self, ctx: &mut Context, stats:Stats){ - let mut health = Text::new(format!("Health: {}", stats.health), self.font.clone()); - let mut attack = Text::new(format!("Attack: {}", stats.attack), self.font.clone()); - let mut level = Text::new(format!("Lvl: {}", stats.level), self.font.clone()); - let mut score = Text::new(format!("Score: {}", stats.score), self.font.clone()); - health.draw(ctx, Vec2::new(100.0, 950.0)); - attack.draw(ctx, Vec2::new(100.0, 970.0)); - level.draw(ctx, Vec2::new(100.0, 990.0)); - score.draw(ctx, Vec2::new(100.0, 1010.0)); - } - fn updateEvents(&mut self, ctx: &mut Context, event: &str){ - let mut event1 = Text::new(event, self.font.clone()); - event1.draw(ctx, Vec2::new(500.0, 950.0)); - } -} - -#[derive(Copy, Clone)] -struct Stats{ - health: u32, - attack: u32, - level: u32, - score: u32, -} - -impl Stats{ - fn new() -> Stats{ - Stats{ - health: 100, - attack: 10, - level: 1, - score: 0, - } - } -} \ No newline at end of file