From 0472c8a8bfcfc82066626324268d6b5635b7723a Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Mon, 24 Jul 2023 03:13:23 +0100 Subject: [PATCH] help screen [?], and [f]orcing doors open --- src/gui.rs | 63 +++++++++++++++--- src/main.rs | 36 +++++++---- src/player.rs | 174 +++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 237 insertions(+), 36 deletions(-) diff --git a/src/gui.rs b/src/gui.rs index 40b5f06..fea8a28 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,6 +1,6 @@ use super::{ - gamelog, player::try_door, rex_assets::RexAssets, CombatStats, Equipped, Hidden, HungerClock, HungerState, - InBackpack, Map, Name, Player, Point, Position, RunState, State, Viewshed, + gamelog, rex_assets::RexAssets, CombatStats, Equipped, Hidden, HungerClock, HungerState, InBackpack, Map, Name, + Player, Point, Position, RunState, State, Viewshed, }; use rltk::{Rltk, VirtualKeyCode, RGB}; use specs::prelude::*; @@ -194,6 +194,53 @@ pub fn print_options(inventory: BTreeMap<(String, String), i32>, mut y: i32, ctx } } +pub fn show_help(ctx: &mut Rltk) -> YesNoResult { + let mut x = 3; + let mut y = 3; + let height = 22; + let width = 25; + ctx.draw_box(x, y, width, height, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK)); + ctx.print_color(x + 3, y, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), " Controls "); + ctx.print_color(x + 3, y + height, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), " ESC/? to close "); + x += 2; + y += 2; + ctx.print_color(x, y, RGB::named(rltk::GREEN), RGB::named(rltk::BLACK), "MOVE COMMANDS"); + y += 2; + ctx.print(x, y, "y k u 7 8 9 > down"); + ctx.print(x, y + 1, " \\|/ \\|/"); + ctx.print(x, y + 2, "h-.-l 4-.-6 < up"); + ctx.print(x, y + 3, " /|\\ /|\\"); + ctx.print(x, y + 4, "b j n 1 2 3 . wait"); + y += 7; + ctx.print_color(x, y, RGB::named(rltk::GREEN), RGB::named(rltk::BLACK), "OBJECT INTERACTION"); + y += 2; + ctx.print(x, y, "g get d drop"); + y += 1; + ctx.print(x, y, "i use r unequip"); + y += 1; + ctx.print(x, y, "o open c close"); + y += 1; + ctx.print(x, y, "f force"); + y += 2; + ctx.print_color(x, y, RGB::named(rltk::GREEN), RGB::named(rltk::BLACK), "MOUSE CONTROL"); + y += 2; + ctx.print(x, y, "hover for tooltips"); + + match ctx.key { + None => YesNoResult::NoSelection, + Some(key) => match key { + VirtualKeyCode::Escape => YesNoResult::Yes, + VirtualKeyCode::Slash => { + if ctx.shift { + return YesNoResult::Yes; + } + return YesNoResult::NoSelection; + } + _ => YesNoResult::NoSelection, + }, + } +} + pub fn show_inventory(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Option) { let player_entity = gs.ecs.fetch::(); let names = gs.ecs.read_storage::(); @@ -463,12 +510,12 @@ pub fn main_menu(gs: &mut State, ctx: &mut Rltk) -> MainMenuResult { } #[derive(PartialEq, Copy, Clone)] -pub enum GameOverResult { +pub enum YesNoResult { NoSelection, - QuitToMenu, + Yes, } -pub fn game_over(ctx: &mut Rltk) -> GameOverResult { +pub fn game_over(ctx: &mut Rltk) -> YesNoResult { ctx.print_color_centered(15, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "Your journey has ended!"); ctx.print_color_centered( 17, @@ -485,10 +532,10 @@ pub fn game_over(ctx: &mut Rltk) -> GameOverResult { ); match ctx.key { - None => GameOverResult::NoSelection, + None => YesNoResult::NoSelection, Some(key) => match key { - VirtualKeyCode::Escape => GameOverResult::QuitToMenu, - _ => GameOverResult::NoSelection, + VirtualKeyCode::Escape => YesNoResult::Yes, + _ => YesNoResult::NoSelection, }, } } diff --git a/src/main.rs b/src/main.rs index fa49fa1..0a0db61 100644 --- a/src/main.rs +++ b/src/main.rs @@ -44,7 +44,7 @@ rltk::embedded_resource!(SCANLINESFS, "../resources/scanlines.fs"); rltk::embedded_resource!(SCANLINESVS, "../resources/scanlines.vs"); //Consts -pub const SHOW_MAPGEN: bool = true; +pub const SHOW_MAPGEN: bool = false; #[derive(PartialEq, Copy, Clone)] pub enum RunState { @@ -61,6 +61,7 @@ pub enum RunState { SaveGame, GameOver, NextLevel, + HelpScreen, MagicMapReveal { row: i32, cursed: bool }, MapGeneration, } @@ -400,7 +401,7 @@ impl GameState for State { } } RunState::ActionWithDirection { function } => { - new_runstate = gui::get_input_direction(&mut self.ecs, ctx, try_door); + new_runstate = gui::get_input_direction(&mut self.ecs, ctx, function); } RunState::MainMenu { .. } => { let result = gui::main_menu(self, ctx); @@ -428,8 +429,8 @@ impl GameState for State { RunState::GameOver => { let result = gui::game_over(ctx); match result { - gui::GameOverResult::NoSelection => {} - gui::GameOverResult::QuitToMenu => { + gui::YesNoResult::NoSelection => {} + gui::YesNoResult::Yes => { self.game_over_cleanup(); new_runstate = RunState::MapGeneration; self.mapgen_next_state = @@ -442,6 +443,15 @@ impl GameState for State { self.mapgen_next_state = Some(RunState::PreRun); new_runstate = RunState::MapGeneration; } + RunState::HelpScreen => { + let result = gui::show_help(ctx); + match result { + gui::YesNoResult::NoSelection => {} + gui::YesNoResult::Yes => { + new_runstate = RunState::AwaitingInput; + } + } + } RunState::MagicMapReveal { row, cursed } => { let mut map = self.ecs.fetch_mut::(); @@ -481,15 +491,17 @@ impl GameState for State { if !SHOW_MAPGEN { new_runstate = self.mapgen_next_state.unwrap(); } - ctx.cls(); - draw_map(&self.mapgen_history[self.mapgen_index], ctx); + if self.mapgen_history.len() != 0 { + ctx.cls(); + draw_map(&self.mapgen_history[self.mapgen_index], ctx); - self.mapgen_timer += ctx.frame_time_ms; - if self.mapgen_timer > 300.0 { - self.mapgen_timer = 0.0; - self.mapgen_index += 1; - if self.mapgen_index >= self.mapgen_history.len() { - new_runstate = self.mapgen_next_state.unwrap(); + self.mapgen_timer += ctx.frame_time_ms; + if self.mapgen_timer > 300.0 { + self.mapgen_timer = 0.0; + self.mapgen_index += 1; + if self.mapgen_index >= self.mapgen_history.len() { + new_runstate = self.mapgen_next_state.unwrap(); + } } } } diff --git a/src/player.rs b/src/player.rs index cf0404c..4a15979 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,7 +1,7 @@ use super::{ gamelog, BlocksTile, BlocksVisibility, CombatStats, Door, EntityMoved, Hidden, HungerClock, HungerState, Item, Map, - Monster, Name, Player, Position, Renderable, RunState, State, Telepath, TileType, Viewshed, WantsToMelee, - WantsToPickupItem, MAPHEIGHT, MAPWIDTH, + Monster, Name, Player, Position, Renderable, RunState, State, SufferDamage, Telepath, TileType, Viewshed, + WantsToMelee, WantsToPickupItem, MAPHEIGHT, MAPWIDTH, }; use rltk::{Point, RandomNumberGenerator, Rltk, VirtualKeyCode}; use specs::prelude::*; @@ -56,16 +56,160 @@ pub fn try_door(i: i32, j: i32, ecs: &mut World) -> RunState { return RunState::PlayerTurn; } } else { - gamelog::Logger::new().append("It's already closed."); + gamelog::Logger::new().append("It's already closed.").log(); } } } } } - gamelog::Logger::new().append("You see no door there."); + gamelog::Logger::new().append("You see no door there.").log(); return RunState::AwaitingInput; } +pub fn open(i: i32, j: i32, ecs: &mut World) -> RunState { + let mut positions = ecs.write_storage::(); + let mut players = ecs.write_storage::(); + let mut viewsheds = ecs.write_storage::(); + let map = ecs.fetch::(); + + let entities = ecs.entities(); + let mut doors = ecs.write_storage::(); + let mut blocks_visibility = ecs.write_storage::(); + let mut blocks_movement = ecs.write_storage::(); + let mut renderables = ecs.write_storage::(); + let names = ecs.read_storage::(); + + for (_entity, _player, pos, viewshed) in (&entities, &mut players, &mut positions, &mut viewsheds).join() { + let delta_x = i; + let delta_y = j; + + if !(pos.x + delta_x < 1 + || pos.x + delta_x > map.width - 1 + || pos.y + delta_y < 1 + || pos.y + delta_y > map.height - 1) + { + let destination_idx = map.xy_idx(pos.x + delta_x, pos.y + delta_y); + + for potential_target in map.tile_content[destination_idx].iter() { + let door = doors.get_mut(*potential_target); + if let Some(door) = door { + if door.open == false { + door.open = true; + blocks_visibility.remove(*potential_target); + blocks_movement.remove(*potential_target); + let render_data = renderables.get_mut(*potential_target).unwrap(); + if let Some(name) = names.get(*potential_target) { + gamelog::Logger::new().append("You open the").item_name_n(&name.name).period().log(); + } + render_data.glyph = rltk::to_cp437('▓'); // Nethack open door, maybe just use '/' instead. + viewshed.dirty = true; + return RunState::PlayerTurn; + } else { + gamelog::Logger::new().append("It's already open.").log(); + } + } + } + } + } + gamelog::Logger::new().append("You see no door there.").log(); + return RunState::AwaitingInput; +} + +pub fn kick(i: i32, j: i32, ecs: &mut World) -> RunState { + let mut something_was_destroyed: Option = None; + { + let mut positions = ecs.write_storage::(); + let mut players = ecs.write_storage::(); + let mut viewsheds = ecs.write_storage::(); + let map = ecs.fetch::(); + + let entities = ecs.entities(); + let mut doors = ecs.write_storage::(); + let names = ecs.read_storage::(); + let mut rng = ecs.write_resource::(); + + for (entity, _player, pos, viewshed) in (&entities, &mut players, &mut positions, &mut viewsheds).join() { + let delta_x = i; + let delta_y = j; + + if !(pos.x + delta_x < 1 + || pos.x + delta_x > map.width - 1 + || pos.y + delta_y < 1 + || pos.y + delta_y > map.height - 1) + { + let destination_idx = map.xy_idx(pos.x + delta_x, pos.y + delta_y); + + if map.tile_content[destination_idx].len() == 0 { + if rng.roll_dice(1, 20) == 20 { + let mut suffer_damage = ecs.write_storage::(); + SufferDamage::new_damage(&mut suffer_damage, entity, 1); + gamelog::Logger::new().append("Ouch! You kick the open air, and pull something.").log(); + break; + } else { + // If there's nothing at all, just kick the air and waste a turn. + gamelog::Logger::new().append("You kick the open air.").log(); + break; + } + } else { + let mut last_non_door_target: Option = None; + let mut target_name = "thing"; + for potential_target in map.tile_content[destination_idx].iter() { + if let Some(name) = names.get(*potential_target) { + target_name = &name.name; + } + + // If it's a door, + let door = doors.get_mut(*potential_target); + if let Some(door) = door { + // If the door is closed, + if door.open == false { + // 33% chance of breaking it down. + if rng.roll_dice(1, 3) == 1 { + gamelog::Logger::new() + .append("As you kick the") + .item_name_n(target_name) + .append(", it crashes open!") + .log(); + something_was_destroyed = Some(*potential_target); + viewshed.dirty = true; + break; + // 66% chance of just kicking it. + } else { + gamelog::Logger::new() + .append("You kick the") + .item_name_n(target_name) + .period() + .log(); + break; + } + // If the door is open and there's nothing else on the tile, + } else if map.tile_content[destination_idx].len() == 1 { + // Just kick the air. + gamelog::Logger::new().append("You kick the open air.").log(); + break; + } + } else { + last_non_door_target = Some(*potential_target); + } + } + if let Some(_) = last_non_door_target { + gamelog::Logger::new().append("You kick the").item_name_n(target_name).period().log(); + // Do something here if it's anything other than a door. + break; + } + } + } + } + } + match something_was_destroyed { + Some(object) => { + ecs.delete_entity(object).expect("Unable to delete."); + } + _ => {} + }; + return RunState::PlayerTurn; +} + pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> bool { let mut positions = ecs.write_storage::(); let mut players = ecs.write_storage::(); @@ -78,9 +222,6 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> bool { let entities = ecs.entities(); let mut wants_to_melee = ecs.write_storage::(); let mut doors = ecs.write_storage::(); - let mut blocks_visibility = ecs.write_storage::(); - let mut blocks_movement = ecs.write_storage::(); - let mut renderables = ecs.write_storage::(); let names = ecs.read_storage::(); for (entity, _player, pos, viewshed) in (&entities, &mut players, &mut positions, &mut viewsheds).join() { @@ -102,16 +243,10 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> bool { let door = doors.get_mut(*potential_target); if let Some(door) = door { if door.open == false { - door.open = true; - blocks_visibility.remove(*potential_target); - blocks_movement.remove(*potential_target); - let render_data = renderables.get_mut(*potential_target).unwrap(); if let Some(name) = names.get(*potential_target) { - gamelog::Logger::new().append("You open the").item_name_n(&name.name).period().log(); + gamelog::Logger::new().append("The").item_name(&name.name).append("is in your way.").log(); } - render_data.glyph = rltk::to_cp437('▓'); // Nethack open door, maybe just use '/' instead. - viewshed.dirty = true; - return true; + return false; } } } @@ -200,7 +335,7 @@ fn get_item(ecs: &mut World) -> bool { pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState { // Player movement - let result; + let mut result = false; match ctx.key { None => return RunState::AwaitingInput, Some(key) => match key { @@ -233,12 +368,19 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState { result = skip_turn(&mut gs.ecs); // (Wait a turn) } } + VirtualKeyCode::Slash => { + if ctx.shift { + return RunState::HelpScreen; + } + } VirtualKeyCode::NumpadDecimal => { result = skip_turn(&mut gs.ecs); } // Items VirtualKeyCode::C => return RunState::ActionWithDirection { function: try_door }, + VirtualKeyCode::O => return RunState::ActionWithDirection { function: open }, + VirtualKeyCode::F => return RunState::ActionWithDirection { function: kick }, VirtualKeyCode::G => { result = get_item(&mut gs.ecs); }