From c810310fc2eee6eda8473c407eadfa9cc6b07d3e Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Wed, 2 Feb 2022 09:45:19 -0500 Subject: [PATCH] Officially cleanup gui module --- src/gui.rs | 499 +----------------- src/gui/cheat_menu.rs | 70 +++ src/gui/drop_item_menu.rs | 83 +++ src/gui/enums.rs | 39 -- src/gui/game_over_menu.rs | 62 +++ src/gui/hud.rs | 276 ++++++++++ src/gui/identify_menu.rs | 112 ++++ src/gui/inventory_menu.rs | 83 +++ src/gui/item_render.rs | 55 ++ src/gui/main_menu.rs | 110 ++++ src/gui/menu.rs | 786 ---------------------------- src/gui/ranged_target.rs | 71 +++ src/gui/remove_curse_menu.rs | 120 +++++ src/gui/remove_item_menu.rs | 91 ++++ src/gui/{tooltip.rs => tooltips.rs} | 2 +- src/gui/vendor_menu.rs | 189 +++++++ 16 files changed, 1349 insertions(+), 1299 deletions(-) create mode 100644 src/gui/cheat_menu.rs create mode 100644 src/gui/drop_item_menu.rs create mode 100644 src/gui/game_over_menu.rs create mode 100644 src/gui/hud.rs create mode 100644 src/gui/identify_menu.rs create mode 100644 src/gui/inventory_menu.rs create mode 100644 src/gui/item_render.rs create mode 100644 src/gui/main_menu.rs delete mode 100644 src/gui/menu.rs create mode 100644 src/gui/ranged_target.rs create mode 100644 src/gui/remove_curse_menu.rs create mode 100644 src/gui/remove_item_menu.rs rename src/gui/{tooltip.rs => tooltips.rs} (98%) create mode 100644 src/gui/vendor_menu.rs diff --git a/src/gui.rs b/src/gui.rs index 3c2c89f..a81b180 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,477 +1,30 @@ //! Interface-building output +mod cheat_menu; +mod drop_item_menu; mod enums; -mod menu; -mod tooltip; +mod game_over_menu; +mod hud; +mod identify_menu; +mod inventory_menu; +mod item_render; +mod main_menu; +mod ranged_target; +mod remove_curse_menu; +mod remove_item_menu; +mod tooltips; +mod vendor_menu; -use std::cmp::Ordering; - -use ::rltk::prelude::*; -use ::rltk::{Point, Rltk, RGB}; -use ::specs::prelude::*; +pub use cheat_menu::*; +pub use drop_item_menu::*; pub use enums::*; -pub use menu::*; -use tooltip::draw_tooltips; - -use crate::components::{ - Attribute, Attributes, Consumable, CursedItem, Duration, Equipped, HungerClock, HungerState, - InBackpack, KnownSpells, MagicItem, MagicItemClass, Name, ObfuscatedName, Pools, StatusEffect, - Viewshed, Weapon, -}; -use crate::{camera, colors, gamelog, Map, MasterDungeonMap, State}; - -pub fn get_item_color(ecs: &World, item: Entity) -> RGB { - let dm = ecs.fetch::(); - - // If the item is cursed, and you have identified it, display it in red - if let Some(name) = ecs.read_storage::().get(item) { - if ecs.read_storage::().get(item).is_some() - && dm.identified_items.contains(&name.name) - { - return colors::RED; - } - } - - if let Some(magic) = ecs.read_storage::().get(item) { - return match magic.class { - MagicItemClass::Common => colors::EQUIP_COMMON, - MagicItemClass::Rare => colors::EQUIP_RARE, - MagicItemClass::Legendary => colors::EQUIP_LEGEND, - }; - } - - colors::WHITE -} - -pub fn get_item_display_name(ecs: &World, item: Entity) -> String { - if let Some(name) = ecs.read_storage::().get(item) { - if ecs.read_storage::().get(item).is_some() { - let dm = ecs.fetch::(); - if dm.identified_items.contains(&name.name) { - if let Some(c) = ecs.read_storage::().get(item) { - if c.max_charges > 1 { - format!("{} ({})", name.name.clone(), c.charges) - } else { - name.name.clone() - } - } else { - name.name.clone() - } - } else if let Some(obfuscated) = ecs.read_storage::().get(item) { - obfuscated.name.clone() - } else { - "Unidentified magic item".to_string() - } - } else { - name.name.clone() - } - } else { - "Nameless item (bug)".to_string() - } -} - -pub fn draw_hollow_box( - console: &mut Rltk, - sx: i32, - sy: i32, - width: i32, - height: i32, - fg: RGB, - bg: RGB, -) { - console.set(sx, sy, fg, bg, to_cp437('┌')); - console.set(sx + width, sy, fg, bg, to_cp437('┐')); - console.set(sx, sy + height, fg, bg, to_cp437('└')); - console.set(sx + width, sy + height, fg, bg, to_cp437('┘')); - - for x in sx + 1..sx + width { - console.set(x, sy, fg, bg, to_cp437('─')); - console.set(x, sy + height, fg, bg, to_cp437('─')); - } - for y in sy + 1..sy + height { - console.set(sx, y, fg, bg, to_cp437('│')); - console.set(sx + width, y, fg, bg, to_cp437('│')); - } -} - -pub fn draw_ui(ecs: &World, ctx: &mut Rltk) { - draw_hollow_box(ctx, 0, 0, 79, 59, colors::BOX_GRAY, colors::BLACK); // Overall box - draw_hollow_box(ctx, 0, 0, 49, 45, colors::BOX_GRAY, colors::BLACK); // Map box - draw_hollow_box(ctx, 0, 45, 79, 14, colors::BOX_GRAY, colors::BLACK); // Log box - draw_hollow_box(ctx, 49, 0, 30, 8, colors::BOX_GRAY, colors::BLACK); // Top-right panel - - ctx.set(0, 45, colors::BOX_GRAY, colors::BLACK, to_cp437('├')); - ctx.set(49, 8, colors::BOX_GRAY, colors::BLACK, to_cp437('├')); - ctx.set(49, 0, colors::BOX_GRAY, colors::BLACK, to_cp437('┬')); - ctx.set(49, 45, colors::BOX_GRAY, colors::BLACK, to_cp437('┴')); - ctx.set(79, 8, colors::BOX_GRAY, colors::BLACK, to_cp437('┤')); - ctx.set(79, 45, colors::BOX_GRAY, colors::BLACK, to_cp437('┤')); - - // Draw the town name - let map = ecs.fetch::(); - let name_length = map.name.len() + 2; - let x_pos = (22 - (name_length / 2)) as i32; - ctx.set(x_pos, 0, colors::BOX_GRAY, colors::BLACK, to_cp437('┤')); - ctx.set( - x_pos + name_length as i32, - 0, - colors::BOX_GRAY, - colors::BLACK, - to_cp437('├'), - ); - ctx.print_color(x_pos + 1, 0, colors::WHITE, colors::BLACK, &map.name); - std::mem::drop(map); - - // Draw stats - let player_entity = ecs.fetch::(); - let pools = ecs.read_storage::(); - let player_pools = pools.get(*player_entity).unwrap(); - let health = format!( - "Health: {}/{}", - player_pools.hit_points.current, player_pools.hit_points.max - ); - let mana = format!( - "Mana: {}/{}", - player_pools.mana.current, player_pools.mana.max - ); - let xp = format!("Level: {}", player_pools.level); - ctx.print_color(50, 1, colors::WHITE, colors::BLACK, &health); - ctx.print_color(50, 2, colors::WHITE, colors::BLACK, &mana); - ctx.print_color(50, 3, colors::WHITE, colors::BLACK, &xp); - ctx.draw_bar_horizontal( - 64, - 1, - 14, - player_pools.hit_points.current, - player_pools.hit_points.max, - colors::RED, - colors::BLACK, - ); - ctx.draw_bar_horizontal( - 64, - 2, - 14, - player_pools.mana.current, - player_pools.mana.max, - colors::BLUE, - colors::BLACK, - ); - let xp_level_start = (player_pools.level - 1) * 1000; - ctx.draw_bar_horizontal( - 64, - 3, - 14, - player_pools.xp - xp_level_start, - 1000, - colors::GOLD, - colors::BLACK, - ); - - // Attributes - let attributes = ecs.read_storage::(); - let attr = attributes.get(*player_entity).unwrap(); - draw_attribute("Might:", &attr.might, 4, ctx); - draw_attribute("Quickness:", &attr.quickness, 5, ctx); - draw_attribute("Fitness:", &attr.fitness, 6, ctx); - draw_attribute("Intelligence:", &attr.intelligence, 7, ctx); - - // Initiative and weight - ctx.print_color( - 50, - 9, - colors::WHITE, - colors::BLACK, - &format!( - "{:.0} lbs ({} lbs max)", - player_pools.total_weight, - (attr.might.base + attr.might.modifiers) * 15 - ), - ); - ctx.print_color( - 50, - 10, - colors::WHITE, - colors::BLACK, - &format!( - "Initiative Penalty: {:.0}", - player_pools.total_initiative_penalty - ), - ); - ctx.print_color( - 50, - 11, - colors::GOLD, - colors::BLACK, - &format!("Gold: {:.1}", player_pools.gold), - ); - - // Equipped - let mut y = 13; - let entities = ecs.entities(); - let equipped = ecs.read_storage::(); - let weapon = ecs.read_storage::(); - for (entity, equipped_by) in (&entities, &equipped).join() { - if equipped_by.owner == *player_entity { - let name = get_item_display_name(ecs, entity); - ctx.print_color(50, y, get_item_color(ecs, entity), colors::BLACK, &name); - y += 1; - - if let Some(weapon) = weapon.get(entity) { - let mut weapon_info = match weapon.damage_bonus.cmp(&0) { - Ordering::Less => { - format!( - "┤ {} ({}d{}{})", - &name, - weapon.damage_n_dice, - weapon.damage_die_type, - weapon.damage_bonus - ) - } - Ordering::Equal => { - format!( - "┤ {} ({}d{})", - &name, weapon.damage_n_dice, weapon.damage_die_type - ) - } - Ordering::Greater => { - format!( - "┤ {} ({}d{}+{})", - &name, - weapon.damage_n_dice, - weapon.damage_die_type, - weapon.damage_bonus - ) - } - }; - - if let Some(range) = weapon.range { - weapon_info += &format!(" (range: {}, F to fire, V cycle targets)", range); - } - weapon_info += " ├"; - ctx.print_color(3, 45, colors::YELLOW, colors::BLACK, &weapon_info); - } - } - } - - // Consumables - y += 1; - let consumables = ecs.read_storage::(); - let backpack = ecs.read_storage::(); - let mut index = 1; - for (entity, carried_by, _consumable) in (&entities, &backpack, &consumables).join() { - if carried_by.owner == *player_entity && index < 10 { - ctx.print_color(50, y, colors::YELLOW, colors::BLACK, &format!("↑{}", index)); - ctx.print_color( - 53, - y, - get_item_color(ecs, entity), - colors::BLACK, - &get_item_display_name(ecs, entity), - ); - y += 1; - index += 1; - } - } - - // Spells - y += 1; - let known_spells_storage = ecs.read_storage::(); - let known_spells = &known_spells_storage.get(*player_entity).unwrap().spells; - let mut index = 1; - for spell in known_spells.iter() { - ctx.print_color(50, y, colors::CYAN, colors::BLACK, &format!("^{}", index)); - ctx.print_color( - 53, - y, - colors::CYAN, - colors::BLACK, - &format!("{} ({})", spell.display_name, spell.mana_cost), - ); - index += 1; - y += 1; - } - - // Status - y = 44; - let hunger = ecs.read_storage::(); - let hc = hunger.get(*player_entity).unwrap(); - match hc.state { - HungerState::WellFed => { - ctx.print_color(50, y, colors::GREEN, colors::BLACK, "Well Fed"); - y -= 1; - } - HungerState::Normal => {} - HungerState::Hungry => { - ctx.print_color(50, y, colors::ORANGE, colors::BLACK, "Hungry"); - y -= 1; - } - HungerState::Starving => { - ctx.print_color(50, y, colors::RED, colors::BLACK, "Starving"); - y -= 1; - } - } - let statuses = ecs.read_storage::(); - let durations = ecs.read_storage::(); - let names = ecs.read_storage::(); - for (status, duration, name) in (&statuses, &durations, &names).join() { - if status.target == *player_entity { - ctx.print_color( - 50, - y, - colors::RED, - colors::BLACK, - &format!("{} ({})", name.name, duration.turns), - ); - y -= 1; - } - } - - // Draw the log - let mut block = TextBlock::new(1, 46, 79, 58); - block - .print(&gamelog::log_display()) - .expect("Failed to print game log."); - block.render(&mut ::rltk::BACKEND_INTERNAL.lock().consoles[0].console); - - draw_tooltips(ecs, ctx); -} - -fn draw_attribute(name: &str, attribute: &Attribute, y: i32, ctx: &mut Rltk) { - ctx.print_color(50, y, colors::ATTR_GRAY, colors::BLACK, name); - - let color = match attribute.modifiers.cmp(&0) { - Ordering::Less => colors::RED, - Ordering::Equal => colors::WHITE, - Ordering::Greater => colors::GREEN, - }; - - ctx.print_color( - 67, - y, - color, - colors::BLACK, - &format!("{}", attribute.base + attribute.modifiers), - ); - ctx.print_color(73, y, color, colors::BLACK, &format!("{}", attribute.bonus)); - - if attribute.bonus > 0 { - ctx.set(72, y, color, colors::BLACK, rltk::to_cp437('+')); - } -} - -pub fn ranged_target( - gs: &mut State, - ctx: &mut Rltk, - range: i32, -) -> (ItemMenuResult, Option) { - let (min_x, max_x, min_y, max_y) = camera::get_screen_bounds(&gs.ecs, ctx); - let player_entity = gs.ecs.fetch::(); - let player_pos = gs.ecs.fetch::(); - let viewsheds = gs.ecs.read_storage::(); - - ctx.print_color(5, 0, colors::YELLOW, colors::BLACK, "Select Target:"); - - // Highlight available target cells - let mut available_cells = Vec::new(); - if let Some(visible) = viewsheds.get(*player_entity) { - for idx in visible.visible_tiles.iter() { - let distance = rltk::DistanceAlg::Pythagoras.distance2d(*player_pos, *idx); - if distance <= range as f32 { - let screen_x = idx.x - min_x; - let screen_y = idx.y - min_y; - - if screen_x > 1 - && screen_x < (max_x - min_x) - 1 - && screen_y > 1 - && screen_y < (max_y - min_y) - 1 - { - ctx.set_bg(screen_x, screen_y, colors::BLUE); - available_cells.push(idx); - } - } - } - } else { - return (ItemMenuResult::Cancel, None); - } - - // Draw mouse cursor - let mouse_pos = ctx.mouse_pos(); - let mut mouse_map_pos = mouse_pos; - mouse_map_pos.0 += min_x; - mouse_map_pos.1 += min_y; - let mut valid_target = false; - for idx in available_cells.iter() { - if idx.x == mouse_map_pos.0 && idx.y == mouse_map_pos.1 { - valid_target = true; - } - } - - if valid_target { - ctx.set_bg(mouse_pos.0, mouse_pos.1, colors::CYAN); - if ctx.left_click { - return ( - ItemMenuResult::Selected, - Some(Point::new(mouse_map_pos.0, mouse_map_pos.1)), - ); - } - } else { - ctx.set_bg(mouse_pos.0, mouse_pos.1, colors::RED); - if ctx.left_click { - return (ItemMenuResult::Cancel, None); - } - } - - (ItemMenuResult::NoResponse, None) -} - -pub fn game_over(ctx: &mut Rltk) -> GameOverResult { - ctx.print_color_centered(15, colors::YELLOW, colors::BLACK, "Your journey has ended!"); - ctx.print_color_centered( - 17, - colors::WHITE, - colors::BLACK, - "One day, we'll tell you all about how you did.", - ); - ctx.print_color_centered( - 18, - colors::WHITE, - colors::BLACK, - "That day, sadly, is not in this chapter...", - ); - - ctx.print_color_centered( - 19, - colors::WHITE, - colors::BLACK, - format!("You lived for {} turns.", gamelog::get_event_count("Turn")), - ); - ctx.print_color_centered( - 20, - colors::RED, - colors::BLACK, - &format!( - "You suffered {} points of damage.", - gamelog::get_event_count("Damage Taken") - ), - ); - ctx.print_color_centered( - 21, - colors::RED, - colors::BLACK, - &format!( - "You inflicted {} points of damage.", - gamelog::get_event_count("Damage Inflicted") - ), - ); - - ctx.print_color_centered( - 23, - colors::MAGENTA, - colors::BLACK, - "Press any key to return to the menu.", - ); - - match ctx.key { - None => GameOverResult::NoSelection, - Some(_) => GameOverResult::QuitToMenu, - } -} +pub use game_over_menu::*; +pub use hud::*; +pub use identify_menu::*; +pub use inventory_menu::*; +pub use item_render::*; +pub use main_menu::*; +pub use ranged_target::*; +pub use remove_curse_menu::*; +pub use remove_item_menu::*; +pub use tooltips::*; +pub use vendor_menu::*; diff --git a/src/gui/cheat_menu.rs b/src/gui/cheat_menu.rs new file mode 100644 index 0000000..22876d0 --- /dev/null +++ b/src/gui/cheat_menu.rs @@ -0,0 +1,70 @@ +use ::rltk::{Rltk, VirtualKeyCode}; + +use crate::{colors, State}; + +#[derive(PartialEq, Copy, Clone)] +pub enum CheatMenuResult { + NoResponse, + Cancel, + TeleportToExit, + Heal, + Reveal, + GodMode, +} + +pub fn show_cheat_mode(_gs: &mut State, ctx: &mut Rltk) -> CheatMenuResult { + let count = 4; + let mut y = (25 - (count / 2)) as i32; + ctx.draw_box( + 15, + y - 2, + 31, + (count + 3) as i32, + colors::WHITE, + colors::BLACK, + ); + ctx.print_color(18, y - 2, colors::YELLOW, colors::BLACK, "Cheating!"); + ctx.print_color( + 18, + y + count as i32 + 1, + colors::YELLOW, + colors::BLACK, + "ESCAPE to cancel", + ); + + ctx.set(17, y, colors::WHITE, colors::BLACK, rltk::to_cp437('(')); + ctx.set(18, y, colors::YELLOW, colors::BLACK, rltk::to_cp437('T')); + ctx.set(19, y, colors::WHITE, colors::BLACK, rltk::to_cp437(')')); + + ctx.print(21, y, "Teleport to next level"); + + y += 1; + ctx.set(17, y, colors::WHITE, colors::BLACK, rltk::to_cp437('(')); + ctx.set(18, y, colors::YELLOW, colors::BLACK, rltk::to_cp437('H')); + ctx.set(19, y, colors::WHITE, colors::BLACK, rltk::to_cp437(')')); + ctx.print(21, y, "Heal all wounds"); + + y += 1; + ctx.set(17, y, colors::WHITE, colors::BLACK, rltk::to_cp437('(')); + ctx.set(18, y, colors::YELLOW, colors::BLACK, rltk::to_cp437('R')); + ctx.set(19, y, colors::WHITE, colors::BLACK, rltk::to_cp437(')')); + ctx.print(21, y, "Reveal the map"); + + y += 1; + ctx.set(17, y, colors::WHITE, colors::BLACK, rltk::to_cp437('(')); + ctx.set(18, y, colors::YELLOW, colors::BLACK, rltk::to_cp437('G')); + ctx.set(19, y, colors::WHITE, colors::BLACK, rltk::to_cp437(')')); + ctx.print(21, y, "God Mode (No Death)"); + + match ctx.key { + None => CheatMenuResult::NoResponse, + Some(key) => match key { + VirtualKeyCode::T => CheatMenuResult::TeleportToExit, + VirtualKeyCode::H => CheatMenuResult::Heal, + VirtualKeyCode::R => CheatMenuResult::Reveal, + VirtualKeyCode::G => CheatMenuResult::GodMode, + VirtualKeyCode::Escape => CheatMenuResult::Cancel, + _ => CheatMenuResult::NoResponse, + }, + } +} diff --git a/src/gui/drop_item_menu.rs b/src/gui/drop_item_menu.rs new file mode 100644 index 0000000..c741026 --- /dev/null +++ b/src/gui/drop_item_menu.rs @@ -0,0 +1,83 @@ +use ::rltk::{Rltk, VirtualKeyCode}; +use ::specs::prelude::*; + +use super::enums::*; +use super::{get_item_color, get_item_display_name}; +use crate::components::{InBackpack, Name}; +use crate::{colors, State}; + +pub fn drop_item_menu(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Option) { + let player_entity = gs.ecs.fetch::(); + let names = gs.ecs.read_storage::(); + let backpack = gs.ecs.read_storage::(); + let entities = gs.ecs.entities(); + + let inventory = (&backpack, &names) + .join() + .filter(|item| item.0.owner == *player_entity); + let count = inventory.count(); + + let mut y = (25 - (count / 2)) as i32; + ctx.draw_box( + 15, + y - 2, + 31, + (count + 3) as i32, + colors::WHITE, + colors::BLACK, + ); + ctx.print_color(18, y - 2, colors::YELLOW, colors::BLACK, "Drop Which Item?"); + ctx.print_color( + 18, + y + count as i32 + 1, + colors::YELLOW, + colors::BLACK, + "ESCAPE to cancel", + ); + + let mut equippable: Vec = Vec::new(); + let mut j = 0; + #[allow(clippy::explicit_counter_loop)] + for (entity, _pack) in (&entities, &backpack) + .join() + .filter(|item| item.1.owner == *player_entity) + { + ctx.set(17, y, colors::WHITE, colors::BLACK, rltk::to_cp437('(')); + ctx.set( + 18, + y, + colors::YELLOW, + colors::BLACK, + 97 + j as rltk::FontCharType, + ); + ctx.set(19, y, colors::WHITE, colors::BLACK, rltk::to_cp437(')')); + + ctx.print_color( + 21, + y, + get_item_color(&gs.ecs, entity), + colors::BLACK, + &get_item_display_name(&gs.ecs, entity), + ); + equippable.push(entity); + y += 1; + j += 1; + } + + match ctx.key { + None => (ItemMenuResult::NoResponse, None), + Some(key) => match key { + VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None), + _ => { + let selection = rltk::letter_to_option(key); + if selection > -1 && selection < count as i32 { + return ( + ItemMenuResult::Selected, + Some(equippable[selection as usize]), + ); + } + (ItemMenuResult::NoResponse, None) + } + }, + } +} diff --git a/src/gui/enums.rs b/src/gui/enums.rs index 577bd34..71c2330 100644 --- a/src/gui/enums.rs +++ b/src/gui/enums.rs @@ -4,42 +4,3 @@ pub enum ItemMenuResult { NoResponse, Selected, } - -#[derive(PartialEq, Copy, Clone)] -pub enum MainMenuSelection { - NewGame, - LoadGame, - Quit, -} - -#[derive(PartialEq, Copy, Clone)] -pub enum MainMenuResult { - NoSelection { selected: MainMenuSelection }, - Selected { selected: MainMenuSelection }, -} - -#[derive(PartialEq, Copy, Clone)] -pub enum GameOverResult { - NoSelection, - QuitToMenu, -} - -#[derive(PartialEq, Copy, Clone)] -pub enum CheatMenuResult { - NoResponse, - Cancel, - TeleportToExit, - Heal, - Reveal, - GodMode, -} - -#[derive(PartialEq, Copy, Clone)] -pub enum VendorResult { - NoResponse, - Cancel, - Sell, - BuyMode, - SellMode, - Buy, -} diff --git a/src/gui/game_over_menu.rs b/src/gui/game_over_menu.rs new file mode 100644 index 0000000..1845ea1 --- /dev/null +++ b/src/gui/game_over_menu.rs @@ -0,0 +1,62 @@ +use ::rltk::Rltk; + +use crate::{colors, gamelog}; + +#[derive(PartialEq, Copy, Clone)] +pub enum GameOverResult { + NoSelection, + QuitToMenu, +} + +pub fn game_over(ctx: &mut Rltk) -> GameOverResult { + ctx.print_color_centered(15, colors::YELLOW, colors::BLACK, "Your journey has ended!"); + ctx.print_color_centered( + 17, + colors::WHITE, + colors::BLACK, + "One day, we'll tell you all about how you did.", + ); + ctx.print_color_centered( + 18, + colors::WHITE, + colors::BLACK, + "That day, sadly, is not in this chapter...", + ); + + ctx.print_color_centered( + 19, + colors::WHITE, + colors::BLACK, + format!("You lived for {} turns.", gamelog::get_event_count("Turn")), + ); + ctx.print_color_centered( + 20, + colors::RED, + colors::BLACK, + &format!( + "You suffered {} points of damage.", + gamelog::get_event_count("Damage Taken") + ), + ); + ctx.print_color_centered( + 21, + colors::RED, + colors::BLACK, + &format!( + "You inflicted {} points of damage.", + gamelog::get_event_count("Damage Inflicted") + ), + ); + + ctx.print_color_centered( + 23, + colors::MAGENTA, + colors::BLACK, + "Press any key to return to the menu.", + ); + + match ctx.key { + None => GameOverResult::NoSelection, + Some(_) => GameOverResult::QuitToMenu, + } +} diff --git a/src/gui/hud.rs b/src/gui/hud.rs new file mode 100644 index 0000000..e8662c9 --- /dev/null +++ b/src/gui/hud.rs @@ -0,0 +1,276 @@ +use std::cmp::Ordering; + +use ::rltk::prelude::*; +use ::rltk::{Point, Rltk}; +use ::specs::prelude::*; + +use super::{draw_tooltips, get_item_color, get_item_display_name}; +use crate::components::{ + Attribute, Attributes, Consumable, Duration, Equipped, HungerClock, HungerState, InBackpack, + KnownSpells, Name, Pools, StatusEffect, Weapon, +}; +use crate::{colors, gamelog, Map}; + +pub fn draw_ui(ecs: &World, ctx: &mut Rltk) { + ctx.draw_hollow_box(0, 0, 79, 59, colors::BOX_GRAY, colors::BLACK); // Overall box + ctx.draw_hollow_box(0, 0, 49, 45, colors::BOX_GRAY, colors::BLACK); // Map box + ctx.draw_hollow_box(0, 45, 79, 14, colors::BOX_GRAY, colors::BLACK); // Log box + ctx.draw_hollow_box(49, 0, 30, 8, colors::BOX_GRAY, colors::BLACK); // Top-right panel + + ctx.set(0, 45, colors::BOX_GRAY, colors::BLACK, to_cp437('├')); + ctx.set(49, 8, colors::BOX_GRAY, colors::BLACK, to_cp437('├')); + ctx.set(49, 0, colors::BOX_GRAY, colors::BLACK, to_cp437('┬')); + ctx.set(49, 45, colors::BOX_GRAY, colors::BLACK, to_cp437('┴')); + ctx.set(79, 8, colors::BOX_GRAY, colors::BLACK, to_cp437('┤')); + ctx.set(79, 45, colors::BOX_GRAY, colors::BLACK, to_cp437('┤')); + + // Draw the town name + let map = ecs.fetch::(); + let name_length = map.name.len() + 2; + let x_pos = (22 - (name_length / 2)) as i32; + ctx.set(x_pos, 0, colors::BOX_GRAY, colors::BLACK, to_cp437('┤')); + ctx.set( + x_pos + name_length as i32, + 0, + colors::BOX_GRAY, + colors::BLACK, + to_cp437('├'), + ); + ctx.print_color(x_pos + 1, 0, colors::WHITE, colors::BLACK, &map.name); + std::mem::drop(map); + + // Draw stats + let player_entity = ecs.fetch::(); + let pools = ecs.read_storage::(); + let player_pools = pools.get(*player_entity).unwrap(); + let health = format!( + "Health: {}/{}", + player_pools.hit_points.current, player_pools.hit_points.max + ); + let mana = format!( + "Mana: {}/{}", + player_pools.mana.current, player_pools.mana.max + ); + let xp = format!("Level: {}", player_pools.level); + ctx.print_color(50, 1, colors::WHITE, colors::BLACK, &health); + ctx.print_color(50, 2, colors::WHITE, colors::BLACK, &mana); + ctx.print_color(50, 3, colors::WHITE, colors::BLACK, &xp); + ctx.draw_bar_horizontal( + 64, + 1, + 14, + player_pools.hit_points.current, + player_pools.hit_points.max, + colors::RED, + colors::BLACK, + ); + ctx.draw_bar_horizontal( + 64, + 2, + 14, + player_pools.mana.current, + player_pools.mana.max, + colors::BLUE, + colors::BLACK, + ); + let xp_level_start = (player_pools.level - 1) * 1000; + ctx.draw_bar_horizontal( + 64, + 3, + 14, + player_pools.xp - xp_level_start, + 1000, + colors::GOLD, + colors::BLACK, + ); + + // Attributes + let attributes = ecs.read_storage::(); + let attr = attributes.get(*player_entity).unwrap(); + draw_attribute("Might:", &attr.might, 4, ctx); + draw_attribute("Quickness:", &attr.quickness, 5, ctx); + draw_attribute("Fitness:", &attr.fitness, 6, ctx); + draw_attribute("Intelligence:", &attr.intelligence, 7, ctx); + + // Initiative and weight + ctx.print_color( + 50, + 9, + colors::WHITE, + colors::BLACK, + &format!( + "{:.0} lbs ({} lbs max)", + player_pools.total_weight, + (attr.might.base + attr.might.modifiers) * 15 + ), + ); + ctx.print_color( + 50, + 10, + colors::WHITE, + colors::BLACK, + &format!( + "Initiative Penalty: {:.0}", + player_pools.total_initiative_penalty + ), + ); + ctx.print_color( + 50, + 11, + colors::GOLD, + colors::BLACK, + &format!("Gold: {:.1}", player_pools.gold), + ); + + // Equipped + let mut y = 13; + let entities = ecs.entities(); + let equipped = ecs.read_storage::(); + let weapon = ecs.read_storage::(); + for (entity, equipped_by) in (&entities, &equipped).join() { + if equipped_by.owner == *player_entity { + let name = get_item_display_name(ecs, entity); + ctx.print_color(50, y, get_item_color(ecs, entity), colors::BLACK, &name); + y += 1; + + if let Some(weapon) = weapon.get(entity) { + let mut weapon_info = match weapon.damage_bonus.cmp(&0) { + Ordering::Less => { + format!( + "┤ {} ({}d{}{})", + &name, + weapon.damage_n_dice, + weapon.damage_die_type, + weapon.damage_bonus + ) + } + Ordering::Equal => { + format!( + "┤ {} ({}d{})", + &name, weapon.damage_n_dice, weapon.damage_die_type + ) + } + Ordering::Greater => { + format!( + "┤ {} ({}d{}+{})", + &name, + weapon.damage_n_dice, + weapon.damage_die_type, + weapon.damage_bonus + ) + } + }; + + if let Some(range) = weapon.range { + weapon_info += &format!(" (range: {}, F to fire, V cycle targets)", range); + } + weapon_info += " ├"; + ctx.print_color(3, 45, colors::YELLOW, colors::BLACK, &weapon_info); + } + } + } + + // Consumables + y += 1; + let consumables = ecs.read_storage::(); + let backpack = ecs.read_storage::(); + let mut index = 1; + for (entity, carried_by, _consumable) in (&entities, &backpack, &consumables).join() { + if carried_by.owner == *player_entity && index < 10 { + ctx.print_color(50, y, colors::YELLOW, colors::BLACK, &format!("↑{}", index)); + ctx.print_color( + 53, + y, + get_item_color(ecs, entity), + colors::BLACK, + &get_item_display_name(ecs, entity), + ); + y += 1; + index += 1; + } + } + + // Spells + y += 1; + let known_spells_storage = ecs.read_storage::(); + let known_spells = &known_spells_storage.get(*player_entity).unwrap().spells; + let mut index = 1; + for spell in known_spells.iter() { + ctx.print_color(50, y, colors::CYAN, colors::BLACK, &format!("^{}", index)); + ctx.print_color( + 53, + y, + colors::CYAN, + colors::BLACK, + &format!("{} ({})", spell.display_name, spell.mana_cost), + ); + index += 1; + y += 1; + } + + // Status + y = 44; + let hunger = ecs.read_storage::(); + let hc = hunger.get(*player_entity).unwrap(); + match hc.state { + HungerState::WellFed => { + ctx.print_color(50, y, colors::GREEN, colors::BLACK, "Well Fed"); + y -= 1; + } + HungerState::Normal => {} + HungerState::Hungry => { + ctx.print_color(50, y, colors::ORANGE, colors::BLACK, "Hungry"); + y -= 1; + } + HungerState::Starving => { + ctx.print_color(50, y, colors::RED, colors::BLACK, "Starving"); + y -= 1; + } + } + let statuses = ecs.read_storage::(); + let durations = ecs.read_storage::(); + let names = ecs.read_storage::(); + for (status, duration, name) in (&statuses, &durations, &names).join() { + if status.target == *player_entity { + ctx.print_color( + 50, + y, + colors::RED, + colors::BLACK, + &format!("{} ({})", name.name, duration.turns), + ); + y -= 1; + } + } + + // Draw the log + gamelog::print_log( + &mut ::rltk::BACKEND_INTERNAL.lock().consoles[1].console, + Point::new(1, 23), + ); + + draw_tooltips(ecs, ctx); +} + +fn draw_attribute(name: &str, attribute: &Attribute, y: i32, ctx: &mut Rltk) { + ctx.print_color(50, y, colors::ATTR_GRAY, colors::BLACK, name); + + let color = match attribute.modifiers.cmp(&0) { + Ordering::Less => colors::RED, + Ordering::Equal => colors::WHITE, + Ordering::Greater => colors::GREEN, + }; + + ctx.print_color( + 67, + y, + color, + colors::BLACK, + &format!("{}", attribute.base + attribute.modifiers), + ); + ctx.print_color(73, y, color, colors::BLACK, &format!("{}", attribute.bonus)); + + if attribute.bonus > 0 { + ctx.set(72, y, color, colors::BLACK, rltk::to_cp437('+')); + } +} diff --git a/src/gui/identify_menu.rs b/src/gui/identify_menu.rs new file mode 100644 index 0000000..9404bb8 --- /dev/null +++ b/src/gui/identify_menu.rs @@ -0,0 +1,112 @@ +use ::rltk::{FontCharType, Rltk, VirtualKeyCode}; +use ::specs::prelude::*; + +use super::enums::*; +use super::{get_item_color, get_item_display_name}; +use crate::components::{Equipped, InBackpack, Item, Name, ObfuscatedName}; +use crate::{colors, MasterDungeonMap, State}; + +pub fn identify_menu(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Option) { + use ::rltk::to_cp437; + + let player_entity = gs.ecs.fetch::(); + let equipped = gs.ecs.read_storage::(); + let backpack = gs.ecs.read_storage::(); + let entities = gs.ecs.entities(); + let items = gs.ecs.read_storage::(); + let names = gs.ecs.read_storage::(); + let dm = gs.ecs.fetch::(); + let obfuscated = gs.ecs.read_storage::(); + + let build_cursed_iterator = || { + (&entities, &items).join().filter(|(item_entity, _item)| { + let mut keep = false; + if let Some(bp) = backpack.get(*item_entity) { + if bp.owner == *player_entity { + if let Some(name) = names.get(*item_entity) { + if obfuscated.get(*item_entity).is_some() + && !dm.identified_items.contains(&name.name) + { + keep = true; + } + } + } + } + + // It's equipped, so we know it's cursed + if let Some(equip) = equipped.get(*item_entity) { + if equip.owner == *player_entity { + if let Some(name) = names.get(*item_entity) { + if obfuscated.get(*item_entity).is_some() + && !dm.identified_items.contains(&name.name) + { + keep = true; + } + } + } + } + + keep + }) + }; + + let count = build_cursed_iterator().count(); + + let mut y = (25 - (count / 2)) as i32; + ctx.draw_box( + 15, + y - 2, + 31, + (count + 3) as i32, + colors::WHITE, + colors::BLACK, + ); + ctx.print_color( + 18, + y - 2, + colors::YELLOW, + colors::BLACK, + "Identify Which Item?", + ); + ctx.print_color( + 18, + y + count as i32 + 1, + colors::YELLOW, + colors::BLACK, + "ESCAPE to cancel", + ); + + let mut equippable: Vec = Vec::new(); + for (j, (entity, _item)) in build_cursed_iterator().enumerate() { + ctx.set(17, y, colors::WHITE, colors::BLACK, to_cp437('(')); + ctx.set(18, y, colors::YELLOW, colors::BLACK, 97 + j as FontCharType); + ctx.set(19, y, colors::WHITE, colors::BLACK, to_cp437(')')); + + ctx.print_color( + 21, + y, + get_item_color(&gs.ecs, entity), + colors::BLACK, + &get_item_display_name(&gs.ecs, entity), + ); + equippable.push(entity); + y += 1; + } + + match ctx.key { + None => (ItemMenuResult::NoResponse, None), + Some(key) => match key { + VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None), + _ => { + let selection = ::rltk::letter_to_option(key); + if selection > -1 && selection < count as i32 { + return ( + ItemMenuResult::Selected, + Some(equippable[selection as usize]), + ); + } + (ItemMenuResult::NoResponse, None) + } + }, + } +} diff --git a/src/gui/inventory_menu.rs b/src/gui/inventory_menu.rs new file mode 100644 index 0000000..e4ef196 --- /dev/null +++ b/src/gui/inventory_menu.rs @@ -0,0 +1,83 @@ +use ::rltk::{Rltk, VirtualKeyCode}; +use ::specs::prelude::*; + +use super::enums::*; +use super::{get_item_color, get_item_display_name}; +use crate::components::{InBackpack, Name}; +use crate::{colors, State}; + +pub fn show_inventory(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Option) { + let player_entity = gs.ecs.fetch::(); + let names = gs.ecs.read_storage::(); + let backpack = gs.ecs.read_storage::(); + let entities = gs.ecs.entities(); + + let inventory = (&backpack, &names) + .join() + .filter(|item| item.0.owner == *player_entity); + let count = inventory.count(); + + let mut y = (25 - (count / 2)) as i32; + ctx.draw_box( + 15, + y - 2, + 31, + (count + 3) as i32, + colors::WHITE, + colors::BLACK, + ); + ctx.print_color(18, y - 2, colors::YELLOW, colors::BLACK, "Inventory"); + ctx.print_color( + 18, + y + count as i32 + 1, + colors::YELLOW, + colors::BLACK, + "ESCAPE to cancel", + ); + + let mut equippable: Vec = Vec::new(); + let mut j = 0; + #[allow(clippy::explicit_counter_loop)] + for (entity, _pack) in (&entities, &backpack) + .join() + .filter(|item| item.1.owner == *player_entity) + { + ctx.set(17, y, colors::WHITE, colors::BLACK, rltk::to_cp437('(')); + ctx.set( + 18, + y, + colors::YELLOW, + colors::BLACK, + 97 + j as rltk::FontCharType, + ); + ctx.set(19, y, colors::WHITE, colors::BLACK, rltk::to_cp437(')')); + + ctx.print_color( + 21, + y, + get_item_color(&gs.ecs, entity), + colors::BLACK, + &get_item_display_name(&gs.ecs, entity), + ); + equippable.push(entity); + y += 1; + j += 1; + } + + match ctx.key { + None => (ItemMenuResult::NoResponse, None), + Some(key) => match key { + VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None), + _ => { + let selection = rltk::letter_to_option(key); + if selection > -1 && selection < count as i32 { + return ( + ItemMenuResult::Selected, + Some(equippable[selection as usize]), + ); + } + (ItemMenuResult::NoResponse, None) + } + }, + } +} diff --git a/src/gui/item_render.rs b/src/gui/item_render.rs new file mode 100644 index 0000000..7edd100 --- /dev/null +++ b/src/gui/item_render.rs @@ -0,0 +1,55 @@ +use ::rltk::RGB; +use ::specs::prelude::*; + +use crate::components::{Consumable, CursedItem, MagicItem, MagicItemClass, Name, ObfuscatedName}; +use crate::{colors, MasterDungeonMap}; + +pub fn get_item_color(ecs: &World, item: Entity) -> RGB { + let dm = ecs.fetch::(); + + // If the item is cursed, and you have identified it, display it in red + if let Some(name) = ecs.read_storage::().get(item) { + if ecs.read_storage::().get(item).is_some() + && dm.identified_items.contains(&name.name) + { + return colors::RED; + } + } + + if let Some(magic) = ecs.read_storage::().get(item) { + return match magic.class { + MagicItemClass::Common => colors::EQUIP_COMMON, + MagicItemClass::Rare => colors::EQUIP_RARE, + MagicItemClass::Legendary => colors::EQUIP_LEGEND, + }; + } + + colors::WHITE +} + +pub fn get_item_display_name(ecs: &World, item: Entity) -> String { + if let Some(name) = ecs.read_storage::().get(item) { + if ecs.read_storage::().get(item).is_some() { + let dm = ecs.fetch::(); + if dm.identified_items.contains(&name.name) { + if let Some(c) = ecs.read_storage::().get(item) { + if c.max_charges > 1 { + format!("{} ({})", name.name.clone(), c.charges) + } else { + name.name.clone() + } + } else { + name.name.clone() + } + } else if let Some(obfuscated) = ecs.read_storage::().get(item) { + obfuscated.name.clone() + } else { + "Unidentified magic item".to_string() + } + } else { + name.name.clone() + } + } else { + "Nameless item (bug)".to_string() + } +} diff --git a/src/gui/main_menu.rs b/src/gui/main_menu.rs new file mode 100644 index 0000000..d809926 --- /dev/null +++ b/src/gui/main_menu.rs @@ -0,0 +1,110 @@ +use ::rltk::{Rltk, VirtualKeyCode}; + +use crate::rex_assets::RexAssets; +use crate::{colors, RunState, State}; + +#[derive(PartialEq, Copy, Clone)] +pub enum MainMenuSelection { + NewGame, + LoadGame, + Quit, +} + +#[derive(PartialEq, Copy, Clone)] +pub enum MainMenuResult { + NoSelection { selected: MainMenuSelection }, + Selected { selected: MainMenuSelection }, +} + +pub fn main_menu(gs: &mut State, ctx: &mut Rltk) -> MainMenuResult { + let save_exists = crate::saveload_system::does_save_exist(); + let runstate = gs.ecs.fetch::(); + let assets = gs.ecs.fetch::(); + ctx.render_xp_sprite(&assets.menu, 0, 0); + + ctx.draw_box_double(24, 18, 31, 10, colors::WHEAT, colors::BLACK); + + ctx.print_color_centered(20, colors::YELLOW, colors::BLACK, "Rust Roguelike"); + ctx.print_color_centered(21, colors::CYAN, colors::BLACK, "by Timothy J. Warren"); + ctx.print_color_centered( + 22, + colors::GRAY, + colors::BLACK, + "Use Up/Down Arrows and Enter", + ); + + let mut y = 24; + if let RunState::MainMenu { + menu_selection: selection, + } = *runstate + { + if selection == MainMenuSelection::NewGame { + ctx.print_color_centered(y, colors::MAGENTA, colors::BLACK, "Begin New Game"); + } else { + ctx.print_color_centered(y, colors::WHITE, colors::BLACK, "Begin New Game"); + } + y += 1; + + if save_exists { + if selection == MainMenuSelection::LoadGame { + ctx.print_color_centered(y, colors::MAGENTA, colors::BLACK, "Load Game"); + } else { + ctx.print_color_centered(y, colors::WHITE, colors::BLACK, "Load Game"); + } + y += 1; + } + + if selection == MainMenuSelection::Quit { + ctx.print_color_centered(y, colors::MAGENTA, colors::BLACK, "Quit"); + } else { + ctx.print_color_centered(y, colors::WHITE, colors::BLACK, "Quit"); + } + + return match ctx.key { + None => MainMenuResult::NoSelection { + selected: selection, + }, + Some(key) => match key { + VirtualKeyCode::Escape => MainMenuResult::NoSelection { + selected: MainMenuSelection::Quit, + }, + VirtualKeyCode::Up => MainMenuResult::NoSelection { + selected: match selection { + MainMenuSelection::NewGame => MainMenuSelection::Quit, + MainMenuSelection::LoadGame => MainMenuSelection::NewGame, + MainMenuSelection::Quit => { + if save_exists { + MainMenuSelection::LoadGame + } else { + MainMenuSelection::NewGame + } + } + }, + }, + VirtualKeyCode::Down => MainMenuResult::NoSelection { + selected: match selection { + MainMenuSelection::NewGame => { + if save_exists { + MainMenuSelection::LoadGame + } else { + MainMenuSelection::Quit + } + } + MainMenuSelection::LoadGame => MainMenuSelection::Quit, + MainMenuSelection::Quit => MainMenuSelection::NewGame, + }, + }, + VirtualKeyCode::Return => MainMenuResult::Selected { + selected: selection, + }, + _ => MainMenuResult::NoSelection { + selected: selection, + }, + }, + }; + } + + MainMenuResult::NoSelection { + selected: MainMenuSelection::NewGame, + } +} diff --git a/src/gui/menu.rs b/src/gui/menu.rs deleted file mode 100644 index 39f9d90..0000000 --- a/src/gui/menu.rs +++ /dev/null @@ -1,786 +0,0 @@ -use ::rltk::{FontCharType, Rltk, VirtualKeyCode, RGB}; -use ::specs::prelude::*; - -use super::enums::*; -use super::{get_item_color, get_item_display_name}; -use crate::components::{CursedItem, Equipped, InBackpack, Item, Name, ObfuscatedName, Vendor}; -use crate::rex_assets::RexAssets; -use crate::{colors, MasterDungeonMap, RunState, State, VendorMode}; - -pub fn show_inventory(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Option) { - let player_entity = gs.ecs.fetch::(); - let names = gs.ecs.read_storage::(); - let backpack = gs.ecs.read_storage::(); - let entities = gs.ecs.entities(); - - let inventory = (&backpack, &names) - .join() - .filter(|item| item.0.owner == *player_entity); - let count = inventory.count(); - - let mut y = (25 - (count / 2)) as i32; - ctx.draw_box( - 15, - y - 2, - 31, - (count + 3) as i32, - colors::WHITE, - colors::BLACK, - ); - ctx.print_color(18, y - 2, colors::YELLOW, colors::BLACK, "Inventory"); - ctx.print_color( - 18, - y + count as i32 + 1, - colors::YELLOW, - colors::BLACK, - "ESCAPE to cancel", - ); - - let mut equippable: Vec = Vec::new(); - let mut j = 0; - #[allow(clippy::explicit_counter_loop)] - for (entity, _pack) in (&entities, &backpack) - .join() - .filter(|item| item.1.owner == *player_entity) - { - ctx.set(17, y, colors::WHITE, colors::BLACK, rltk::to_cp437('(')); - ctx.set( - 18, - y, - colors::YELLOW, - colors::BLACK, - 97 + j as rltk::FontCharType, - ); - ctx.set(19, y, colors::WHITE, colors::BLACK, rltk::to_cp437(')')); - - ctx.print_color( - 21, - y, - get_item_color(&gs.ecs, entity), - colors::BLACK, - &get_item_display_name(&gs.ecs, entity), - ); - equippable.push(entity); - y += 1; - j += 1; - } - - match ctx.key { - None => (ItemMenuResult::NoResponse, None), - Some(key) => match key { - VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None), - _ => { - let selection = rltk::letter_to_option(key); - if selection > -1 && selection < count as i32 { - return ( - ItemMenuResult::Selected, - Some(equippable[selection as usize]), - ); - } - (ItemMenuResult::NoResponse, None) - } - }, - } -} - -pub fn drop_item_menu(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Option) { - let player_entity = gs.ecs.fetch::(); - let names = gs.ecs.read_storage::(); - let backpack = gs.ecs.read_storage::(); - let entities = gs.ecs.entities(); - - let inventory = (&backpack, &names) - .join() - .filter(|item| item.0.owner == *player_entity); - let count = inventory.count(); - - let mut y = (25 - (count / 2)) as i32; - ctx.draw_box( - 15, - y - 2, - 31, - (count + 3) as i32, - colors::WHITE, - colors::BLACK, - ); - ctx.print_color(18, y - 2, colors::YELLOW, colors::BLACK, "Drop Which Item?"); - ctx.print_color( - 18, - y + count as i32 + 1, - colors::YELLOW, - colors::BLACK, - "ESCAPE to cancel", - ); - - let mut equippable: Vec = Vec::new(); - let mut j = 0; - #[allow(clippy::explicit_counter_loop)] - for (entity, _pack) in (&entities, &backpack) - .join() - .filter(|item| item.1.owner == *player_entity) - { - ctx.set(17, y, colors::WHITE, colors::BLACK, rltk::to_cp437('(')); - ctx.set( - 18, - y, - colors::YELLOW, - colors::BLACK, - 97 + j as rltk::FontCharType, - ); - ctx.set(19, y, colors::WHITE, colors::BLACK, rltk::to_cp437(')')); - - ctx.print_color( - 21, - y, - get_item_color(&gs.ecs, entity), - colors::BLACK, - &get_item_display_name(&gs.ecs, entity), - ); - equippable.push(entity); - y += 1; - j += 1; - } - - match ctx.key { - None => (ItemMenuResult::NoResponse, None), - Some(key) => match key { - VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None), - _ => { - let selection = rltk::letter_to_option(key); - if selection > -1 && selection < count as i32 { - return ( - ItemMenuResult::Selected, - Some(equippable[selection as usize]), - ); - } - (ItemMenuResult::NoResponse, None) - } - }, - } -} - -pub fn remove_item_menu(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Option) { - let player_entity = gs.ecs.fetch::(); - let names = gs.ecs.read_storage::(); - let backpack = gs.ecs.read_storage::(); - let entities = gs.ecs.entities(); - - let inventory = (&backpack, &names) - .join() - .filter(|item| item.0.owner == *player_entity); - let count = inventory.count(); - - let mut y = (25 - (count / 2)) as i32; - ctx.draw_box( - 15, - y - 2, - 31, - (count + 3) as i32, - colors::WHITE, - colors::BLACK, - ); - ctx.print_color( - 18, - y - 2, - colors::YELLOW, - colors::BLACK, - "Remove Which Item?", - ); - ctx.print_color( - 18, - y + count as i32 + 1, - colors::YELLOW, - colors::BLACK, - "ESCAPE to cancel", - ); - - let mut equippable: Vec = Vec::new(); - let mut j = 0; - #[allow(clippy::explicit_counter_loop)] - for (entity, _pack) in (&entities, &backpack) - .join() - .filter(|item| item.1.owner == *player_entity) - { - ctx.set(17, y, colors::WHITE, colors::BLACK, rltk::to_cp437('(')); - ctx.set( - 18, - y, - colors::YELLOW, - colors::BLACK, - 97 + j as rltk::FontCharType, - ); - ctx.set(19, y, colors::WHITE, colors::BLACK, rltk::to_cp437(')')); - - ctx.print_color( - 21, - y, - get_item_color(&gs.ecs, entity), - colors::BLACK, - &get_item_display_name(&gs.ecs, entity), - ); - - equippable.push(entity); - y += 1; - j += 1; - } - - match ctx.key { - None => (ItemMenuResult::NoResponse, None), - Some(key) => match key { - VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None), - _ => { - let selection = rltk::letter_to_option(key); - if selection > -1 && selection < count as i32 { - return ( - ItemMenuResult::Selected, - Some(equippable[selection as usize]), - ); - } - - (ItemMenuResult::NoResponse, None) - } - }, - } -} - -pub fn main_menu(gs: &mut State, ctx: &mut Rltk) -> MainMenuResult { - let save_exists = crate::saveload_system::does_save_exist(); - let runstate = gs.ecs.fetch::(); - let assets = gs.ecs.fetch::(); - ctx.render_xp_sprite(&assets.menu, 0, 0); - - ctx.draw_box_double(24, 18, 31, 10, colors::WHEAT, colors::BLACK); - - ctx.print_color_centered(20, colors::YELLOW, colors::BLACK, "Rust Roguelike"); - ctx.print_color_centered(21, colors::CYAN, colors::BLACK, "by Timothy J. Warren"); - ctx.print_color_centered( - 22, - colors::GRAY, - colors::BLACK, - "Use Up/Down Arrows and Enter", - ); - - let mut y = 24; - if let RunState::MainMenu { - menu_selection: selection, - } = *runstate - { - if selection == MainMenuSelection::NewGame { - ctx.print_color_centered(y, colors::MAGENTA, colors::BLACK, "Begin New Game"); - } else { - ctx.print_color_centered(y, colors::WHITE, colors::BLACK, "Begin New Game"); - } - y += 1; - - if save_exists { - if selection == MainMenuSelection::LoadGame { - ctx.print_color_centered(y, colors::MAGENTA, colors::BLACK, "Load Game"); - } else { - ctx.print_color_centered(y, colors::WHITE, colors::BLACK, "Load Game"); - } - y += 1; - } - - if selection == MainMenuSelection::Quit { - ctx.print_color_centered(y, colors::MAGENTA, colors::BLACK, "Quit"); - } else { - ctx.print_color_centered(y, colors::WHITE, colors::BLACK, "Quit"); - } - - return match ctx.key { - None => MainMenuResult::NoSelection { - selected: selection, - }, - Some(key) => match key { - VirtualKeyCode::Escape => MainMenuResult::NoSelection { - selected: MainMenuSelection::Quit, - }, - VirtualKeyCode::Up => MainMenuResult::NoSelection { - selected: match selection { - MainMenuSelection::NewGame => MainMenuSelection::Quit, - MainMenuSelection::LoadGame => MainMenuSelection::NewGame, - MainMenuSelection::Quit => { - if save_exists { - MainMenuSelection::LoadGame - } else { - MainMenuSelection::NewGame - } - } - }, - }, - VirtualKeyCode::Down => MainMenuResult::NoSelection { - selected: match selection { - MainMenuSelection::NewGame => { - if save_exists { - MainMenuSelection::LoadGame - } else { - MainMenuSelection::Quit - } - } - MainMenuSelection::LoadGame => MainMenuSelection::Quit, - MainMenuSelection::Quit => MainMenuSelection::NewGame, - }, - }, - VirtualKeyCode::Return => MainMenuResult::Selected { - selected: selection, - }, - _ => MainMenuResult::NoSelection { - selected: selection, - }, - }, - }; - } - - MainMenuResult::NoSelection { - selected: MainMenuSelection::NewGame, - } -} - -pub fn show_cheat_mode(_gs: &mut State, ctx: &mut Rltk) -> CheatMenuResult { - let count = 4; - let mut y = (25 - (count / 2)) as i32; - ctx.draw_box( - 15, - y - 2, - 31, - (count + 3) as i32, - colors::WHITE, - colors::BLACK, - ); - ctx.print_color(18, y - 2, colors::YELLOW, colors::BLACK, "Cheating!"); - ctx.print_color( - 18, - y + count as i32 + 1, - colors::YELLOW, - colors::BLACK, - "ESCAPE to cancel", - ); - - ctx.set(17, y, colors::WHITE, colors::BLACK, rltk::to_cp437('(')); - ctx.set(18, y, colors::YELLOW, colors::BLACK, rltk::to_cp437('T')); - ctx.set(19, y, colors::WHITE, colors::BLACK, rltk::to_cp437(')')); - - ctx.print(21, y, "Teleport to next level"); - - y += 1; - ctx.set(17, y, colors::WHITE, colors::BLACK, rltk::to_cp437('(')); - ctx.set(18, y, colors::YELLOW, colors::BLACK, rltk::to_cp437('H')); - ctx.set(19, y, colors::WHITE, colors::BLACK, rltk::to_cp437(')')); - ctx.print(21, y, "Heal all wounds"); - - y += 1; - ctx.set(17, y, colors::WHITE, colors::BLACK, rltk::to_cp437('(')); - ctx.set(18, y, colors::YELLOW, colors::BLACK, rltk::to_cp437('R')); - ctx.set(19, y, colors::WHITE, colors::BLACK, rltk::to_cp437(')')); - ctx.print(21, y, "Reveal the map"); - - y += 1; - ctx.set(17, y, colors::WHITE, colors::BLACK, rltk::to_cp437('(')); - ctx.set(18, y, colors::YELLOW, colors::BLACK, rltk::to_cp437('G')); - ctx.set(19, y, colors::WHITE, colors::BLACK, rltk::to_cp437(')')); - ctx.print(21, y, "God Mode (No Death)"); - - match ctx.key { - None => CheatMenuResult::NoResponse, - Some(key) => match key { - VirtualKeyCode::T => CheatMenuResult::TeleportToExit, - VirtualKeyCode::H => CheatMenuResult::Heal, - VirtualKeyCode::R => CheatMenuResult::Reveal, - VirtualKeyCode::G => CheatMenuResult::GodMode, - VirtualKeyCode::Escape => CheatMenuResult::Cancel, - _ => CheatMenuResult::NoResponse, - }, - } -} - -fn vendor_sell_menu( - gs: &mut State, - ctx: &mut Rltk, - _vendor: Entity, - _mode: VendorMode, -) -> (VendorResult, Option, Option, Option) { - let player_entity = gs.ecs.fetch::(); - let names = gs.ecs.read_storage::(); - let backpack = gs.ecs.read_storage::(); - let items = gs.ecs.read_storage::(); - let entities = gs.ecs.entities(); - - let inventory = (&backpack, &names) - .join() - .filter(|item| item.0.owner == *player_entity); - let count = inventory.count(); - - let mut y = (25 - (count / 2)) as i32; - ctx.draw_box( - 15, - y - 2, - 51, - (count + 3) as i32, - colors::WHITE, - colors::BLACK, - ); - ctx.print_color( - 18, - y - 2, - colors::YELLOW, - colors::BLACK, - "Sell Which Item? (space to switch to buy mode)", - ); - ctx.print_color( - 18, - y + count as i32 + 1, - colors::YELLOW, - colors::BLACK, - "ESCAPE to cancel", - ); - - let mut equippable: Vec = Vec::new(); - for (j, (entity, _pack, name, item)) in (&entities, &backpack, &names, &items) - .join() - .filter(|item| item.1.owner == *player_entity) - .enumerate() - { - ctx.set(17, y, colors::WHITE, colors::BLACK, rltk::to_cp437('(')); - ctx.set( - 18, - y, - colors::YELLOW, - colors::BLACK, - 97 + j as rltk::FontCharType, - ); - ctx.set(19, y, colors::WHITE, colors::BLACK, rltk::to_cp437(')')); - - ctx.print(21, y, &name.name.to_string()); - ctx.print(50, y, &format!("{:.1} gp", item.base_value * 0.8)); - equippable.push(entity); - y += 1; - } - - match ctx.key { - None => (VendorResult::NoResponse, None, None, None), - Some(key) => match key { - VirtualKeyCode::Space => (VendorResult::BuyMode, None, None, None), - VirtualKeyCode::Escape => (VendorResult::Cancel, None, None, None), - _ => { - let selection = rltk::letter_to_option(key); - if selection > -1 && selection < count as i32 { - return ( - VendorResult::Sell, - Some(equippable[selection as usize]), - None, - None, - ); - } - (VendorResult::NoResponse, None, None, None) - } - }, - } -} - -fn vendor_buy_menu( - gs: &mut State, - ctx: &mut Rltk, - vendor: Entity, - _mode: VendorMode, -) -> (VendorResult, Option, Option, Option) { - use crate::raws::*; - - let vendors = gs.ecs.read_storage::(); - - let inventory = get_vendor_items( - &vendors.get(vendor).unwrap().categories, - &RAWS.lock().unwrap(), - ); - let count = inventory.len(); - - let mut y = (25 - (count / 2)) as i32; - ctx.draw_box( - 15, - y - 2, - 51, - (count + 3) as i32, - colors::WHITE, - colors::BLACK, - ); - ctx.print_color( - 18, - y - 2, - colors::YELLOW, - colors::BLACK, - "Buy Which Item? (space to switch to sell mode)", - ); - ctx.print_color( - 18, - y + count as i32 + 1, - colors::YELLOW, - colors::BLACK, - "ESCAPE to cancel", - ); - - for (j, sale) in inventory.iter().enumerate() { - ctx.set(17, y, colors::WHITE, colors::BLACK, rltk::to_cp437('(')); - ctx.set( - 18, - y, - colors::YELLOW, - colors::BLACK, - 97 + j as rltk::FontCharType, - ); - ctx.set(19, y, colors::WHITE, colors::BLACK, rltk::to_cp437(')')); - - ctx.print(21, y, &sale.0); - ctx.print(50, y, &format!("{:.1} gp", sale.1 * 1.2)); - y += 1; - } - - match ctx.key { - None => (VendorResult::NoResponse, None, None, None), - Some(key) => match key { - VirtualKeyCode::Space => (VendorResult::SellMode, None, None, None), - VirtualKeyCode::Escape => (VendorResult::Cancel, None, None, None), - _ => { - let selection = ::rltk::letter_to_option(key); - if selection > -1 && selection < count as i32 { - return ( - VendorResult::Buy, - None, - Some(inventory[selection as usize].0.clone()), - Some(inventory[selection as usize].1), - ); - } - - (VendorResult::NoResponse, None, None, None) - } - }, - } -} - -pub fn show_vendor_menu( - gs: &mut State, - ctx: &mut Rltk, - vendor: Entity, - mode: VendorMode, -) -> (VendorResult, Option, Option, Option) { - match mode { - VendorMode::Buy => vendor_buy_menu(gs, ctx, vendor, mode), - VendorMode::Sell => vendor_sell_menu(gs, ctx, vendor, mode), - } -} - -pub fn remove_curse_menu(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Option) { - let player_entity = gs.ecs.fetch::(); - let equipped = gs.ecs.read_storage::(); - let backpack = gs.ecs.read_storage::(); - let entities = gs.ecs.entities(); - let items = gs.ecs.read_storage::(); - let cursed = gs.ecs.read_storage::(); - let names = gs.ecs.read_storage::(); - let dm = gs.ecs.fetch::(); - - let build_cursed_iterator = || { - (&entities, &items, &cursed) - .join() - .filter(|(item_entity, _item, _cursed)| { - let mut keep = false; - if let Some(bp) = backpack.get(*item_entity) { - if bp.owner == *player_entity { - if let Some(name) = names.get(*item_entity) { - if dm.identified_items.contains(&name.name) { - keep = true; - } - } - } - } - // It's equipped, so we know it's cursed - if let Some(equip) = equipped.get(*item_entity) { - if equip.owner == *player_entity { - keep = true; - } - } - keep - }) - }; - - let count = build_cursed_iterator().count(); - - let mut y = (25 - (count / 2)) as i32; - ctx.draw_box( - 15, - y - 2, - 31, - (count + 3) as i32, - RGB::named(rltk::WHITE), - RGB::named(rltk::BLACK), - ); - ctx.print_color( - 18, - y - 2, - RGB::named(rltk::YELLOW), - RGB::named(rltk::BLACK), - "Remove Curse From Which Item?", - ); - ctx.print_color( - 18, - y + count as i32 + 1, - RGB::named(rltk::YELLOW), - RGB::named(rltk::BLACK), - "ESCAPE to cancel", - ); - - let mut equippable: Vec = Vec::new(); - for (j, (entity, _item, _cursed)) in build_cursed_iterator().enumerate() { - ctx.set( - 17, - y, - RGB::named(rltk::WHITE), - RGB::named(rltk::BLACK), - rltk::to_cp437('('), - ); - ctx.set( - 18, - y, - RGB::named(rltk::YELLOW), - RGB::named(rltk::BLACK), - 97 + j as rltk::FontCharType, - ); - ctx.set( - 19, - y, - RGB::named(rltk::WHITE), - RGB::named(rltk::BLACK), - rltk::to_cp437(')'), - ); - - ctx.print_color( - 21, - y, - get_item_color(&gs.ecs, entity), - RGB::from_f32(0.0, 0.0, 0.0), - &get_item_display_name(&gs.ecs, entity), - ); - equippable.push(entity); - y += 1; - } - - match ctx.key { - None => (ItemMenuResult::NoResponse, None), - Some(key) => match key { - VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None), - _ => { - let selection = rltk::letter_to_option(key); - if selection > -1 && selection < count as i32 { - return ( - ItemMenuResult::Selected, - Some(equippable[selection as usize]), - ); - } - (ItemMenuResult::NoResponse, None) - } - }, - } -} - -pub fn identify_menu(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Option) { - use ::rltk::to_cp437; - - let player_entity = gs.ecs.fetch::(); - let equipped = gs.ecs.read_storage::(); - let backpack = gs.ecs.read_storage::(); - let entities = gs.ecs.entities(); - let items = gs.ecs.read_storage::(); - let names = gs.ecs.read_storage::(); - let dm = gs.ecs.fetch::(); - let obfuscated = gs.ecs.read_storage::(); - - let build_cursed_iterator = || { - (&entities, &items).join().filter(|(item_entity, _item)| { - let mut keep = false; - if let Some(bp) = backpack.get(*item_entity) { - if bp.owner == *player_entity { - if let Some(name) = names.get(*item_entity) { - if obfuscated.get(*item_entity).is_some() - && !dm.identified_items.contains(&name.name) - { - keep = true; - } - } - } - } - - // It's equipped, so we know it's cursed - if let Some(equip) = equipped.get(*item_entity) { - if equip.owner == *player_entity { - if let Some(name) = names.get(*item_entity) { - if obfuscated.get(*item_entity).is_some() - && !dm.identified_items.contains(&name.name) - { - keep = true; - } - } - } - } - - keep - }) - }; - - let count = build_cursed_iterator().count(); - - let mut y = (25 - (count / 2)) as i32; - ctx.draw_box( - 15, - y - 2, - 31, - (count + 3) as i32, - colors::WHITE, - colors::BLACK, - ); - ctx.print_color( - 18, - y - 2, - colors::YELLOW, - colors::BLACK, - "Identify Which Item?", - ); - ctx.print_color( - 18, - y + count as i32 + 1, - colors::YELLOW, - colors::BLACK, - "ESCAPE to cancel", - ); - - let mut equippable: Vec = Vec::new(); - for (j, (entity, _item)) in build_cursed_iterator().enumerate() { - ctx.set(17, y, colors::WHITE, colors::BLACK, to_cp437('(')); - ctx.set(18, y, colors::YELLOW, colors::BLACK, 97 + j as FontCharType); - ctx.set(19, y, colors::WHITE, colors::BLACK, to_cp437(')')); - - ctx.print_color( - 21, - y, - get_item_color(&gs.ecs, entity), - colors::BLACK, - &get_item_display_name(&gs.ecs, entity), - ); - equippable.push(entity); - y += 1; - } - - match ctx.key { - None => (ItemMenuResult::NoResponse, None), - Some(key) => match key { - VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None), - _ => { - let selection = ::rltk::letter_to_option(key); - if selection > -1 && selection < count as i32 { - return ( - ItemMenuResult::Selected, - Some(equippable[selection as usize]), - ); - } - (ItemMenuResult::NoResponse, None) - } - }, - } -} diff --git a/src/gui/ranged_target.rs b/src/gui/ranged_target.rs new file mode 100644 index 0000000..4eb10ca --- /dev/null +++ b/src/gui/ranged_target.rs @@ -0,0 +1,71 @@ +use ::rltk::{DistanceAlg, Point, Rltk}; +use ::specs::prelude::*; + +use super::enums::*; +use crate::components::Viewshed; +use crate::{camera, colors, State}; + +pub fn ranged_target( + gs: &mut State, + ctx: &mut Rltk, + range: i32, +) -> (ItemMenuResult, Option) { + let (min_x, max_x, min_y, max_y) = camera::get_screen_bounds(&gs.ecs, ctx); + let player_entity = gs.ecs.fetch::(); + let player_pos = gs.ecs.fetch::(); + let viewsheds = gs.ecs.read_storage::(); + + ctx.print_color(5, 0, colors::YELLOW, colors::BLACK, "Select Target:"); + + // Highlight available target cells + let mut available_cells = Vec::new(); + if let Some(visible) = viewsheds.get(*player_entity) { + for idx in visible.visible_tiles.iter() { + let distance = DistanceAlg::Pythagoras.distance2d(*player_pos, *idx); + if distance <= range as f32 { + let screen_x = idx.x - min_x; + let screen_y = idx.y - min_y; + + if screen_x > 1 + && screen_x < (max_x - min_x) - 1 + && screen_y > 1 + && screen_y < (max_y - min_y) - 1 + { + ctx.set_bg(screen_x, screen_y, colors::BLUE); + available_cells.push(idx); + } + } + } + } else { + return (ItemMenuResult::Cancel, None); + } + + // Draw mouse cursor + let mouse_pos = ctx.mouse_pos(); + let mut mouse_map_pos = mouse_pos; + mouse_map_pos.0 += min_x; + mouse_map_pos.1 += min_y; + let mut valid_target = false; + for idx in available_cells.iter() { + if idx.x == mouse_map_pos.0 && idx.y == mouse_map_pos.1 { + valid_target = true; + } + } + + if valid_target { + ctx.set_bg(mouse_pos.0, mouse_pos.1, colors::CYAN); + if ctx.left_click { + return ( + ItemMenuResult::Selected, + Some(Point::new(mouse_map_pos.0, mouse_map_pos.1)), + ); + } + } else { + ctx.set_bg(mouse_pos.0, mouse_pos.1, colors::RED); + if ctx.left_click { + return (ItemMenuResult::Cancel, None); + } + } + + (ItemMenuResult::NoResponse, None) +} diff --git a/src/gui/remove_curse_menu.rs b/src/gui/remove_curse_menu.rs new file mode 100644 index 0000000..160da0d --- /dev/null +++ b/src/gui/remove_curse_menu.rs @@ -0,0 +1,120 @@ +use ::rltk::{Rltk, VirtualKeyCode, RGB}; +use ::specs::prelude::*; + +use super::enums::*; +use super::{get_item_color, get_item_display_name}; +use crate::components::{CursedItem, Equipped, InBackpack, Item, Name}; +use crate::{MasterDungeonMap, State}; + +pub fn remove_curse_menu(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Option) { + let player_entity = gs.ecs.fetch::(); + let equipped = gs.ecs.read_storage::(); + let backpack = gs.ecs.read_storage::(); + let entities = gs.ecs.entities(); + let items = gs.ecs.read_storage::(); + let cursed = gs.ecs.read_storage::(); + let names = gs.ecs.read_storage::(); + let dm = gs.ecs.fetch::(); + + let build_cursed_iterator = || { + (&entities, &items, &cursed) + .join() + .filter(|(item_entity, _item, _cursed)| { + let mut keep = false; + if let Some(bp) = backpack.get(*item_entity) { + if bp.owner == *player_entity { + if let Some(name) = names.get(*item_entity) { + if dm.identified_items.contains(&name.name) { + keep = true; + } + } + } + } + // It's equipped, so we know it's cursed + if let Some(equip) = equipped.get(*item_entity) { + if equip.owner == *player_entity { + keep = true; + } + } + keep + }) + }; + + let count = build_cursed_iterator().count(); + + let mut y = (25 - (count / 2)) as i32; + ctx.draw_box( + 15, + y - 2, + 31, + (count + 3) as i32, + RGB::named(rltk::WHITE), + RGB::named(rltk::BLACK), + ); + ctx.print_color( + 18, + y - 2, + RGB::named(rltk::YELLOW), + RGB::named(rltk::BLACK), + "Remove Curse From Which Item?", + ); + ctx.print_color( + 18, + y + count as i32 + 1, + RGB::named(rltk::YELLOW), + RGB::named(rltk::BLACK), + "ESCAPE to cancel", + ); + + let mut equippable: Vec = Vec::new(); + for (j, (entity, _item, _cursed)) in build_cursed_iterator().enumerate() { + ctx.set( + 17, + y, + RGB::named(rltk::WHITE), + RGB::named(rltk::BLACK), + rltk::to_cp437('('), + ); + ctx.set( + 18, + y, + RGB::named(rltk::YELLOW), + RGB::named(rltk::BLACK), + 97 + j as rltk::FontCharType, + ); + ctx.set( + 19, + y, + RGB::named(rltk::WHITE), + RGB::named(rltk::BLACK), + rltk::to_cp437(')'), + ); + + ctx.print_color( + 21, + y, + get_item_color(&gs.ecs, entity), + RGB::from_f32(0.0, 0.0, 0.0), + &get_item_display_name(&gs.ecs, entity), + ); + equippable.push(entity); + y += 1; + } + + match ctx.key { + None => (ItemMenuResult::NoResponse, None), + Some(key) => match key { + VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None), + _ => { + let selection = rltk::letter_to_option(key); + if selection > -1 && selection < count as i32 { + return ( + ItemMenuResult::Selected, + Some(equippable[selection as usize]), + ); + } + (ItemMenuResult::NoResponse, None) + } + }, + } +} diff --git a/src/gui/remove_item_menu.rs b/src/gui/remove_item_menu.rs new file mode 100644 index 0000000..79fb2b5 --- /dev/null +++ b/src/gui/remove_item_menu.rs @@ -0,0 +1,91 @@ +use ::rltk::{Rltk, VirtualKeyCode}; +use ::specs::prelude::*; + +use super::enums::*; +use super::{get_item_color, get_item_display_name}; +use crate::components::{Equipped, Name}; +use crate::{colors, State}; + +pub fn remove_item_menu(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Option) { + let player_entity = gs.ecs.fetch::(); + let names = gs.ecs.read_storage::(); + let backpack = gs.ecs.read_storage::(); + let entities = gs.ecs.entities(); + + let inventory = (&backpack, &names) + .join() + .filter(|item| item.0.owner == *player_entity); + let count = inventory.count(); + + let mut y = (25 - (count / 2)) as i32; + ctx.draw_box( + 15, + y - 2, + 31, + (count + 3) as i32, + colors::WHITE, + colors::BLACK, + ); + ctx.print_color( + 18, + y - 2, + colors::YELLOW, + colors::BLACK, + "Remove Which Item?", + ); + ctx.print_color( + 18, + y + count as i32 + 1, + colors::YELLOW, + colors::BLACK, + "ESCAPE to cancel", + ); + + let mut equippable: Vec = Vec::new(); + let mut j = 0; + #[allow(clippy::explicit_counter_loop)] + for (entity, _pack) in (&entities, &backpack) + .join() + .filter(|item| item.1.owner == *player_entity) + { + ctx.set(17, y, colors::WHITE, colors::BLACK, rltk::to_cp437('(')); + ctx.set( + 18, + y, + colors::YELLOW, + colors::BLACK, + 97 + j as rltk::FontCharType, + ); + ctx.set(19, y, colors::WHITE, colors::BLACK, rltk::to_cp437(')')); + + ctx.print_color( + 21, + y, + get_item_color(&gs.ecs, entity), + colors::BLACK, + &get_item_display_name(&gs.ecs, entity), + ); + + equippable.push(entity); + y += 1; + j += 1; + } + + match ctx.key { + None => (ItemMenuResult::NoResponse, None), + Some(key) => match key { + VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None), + _ => { + let selection = rltk::letter_to_option(key); + if selection > -1 && selection < count as i32 { + return ( + ItemMenuResult::Selected, + Some(equippable[selection as usize]), + ); + } + + (ItemMenuResult::NoResponse, None) + } + }, + } +} diff --git a/src/gui/tooltip.rs b/src/gui/tooltips.rs similarity index 98% rename from src/gui/tooltip.rs rename to src/gui/tooltips.rs index f69510d..ecf88da 100644 --- a/src/gui/tooltip.rs +++ b/src/gui/tooltips.rs @@ -53,7 +53,7 @@ impl Tooltip { } } -pub(super) fn draw_tooltips(ecs: &World, ctx: &mut Rltk) { +pub fn draw_tooltips(ecs: &World, ctx: &mut Rltk) { use ::rltk::{to_cp437, Algorithm2D}; let (min_x, _max_x, min_y, _max_y) = camera::get_screen_bounds(ecs, ctx); diff --git a/src/gui/vendor_menu.rs b/src/gui/vendor_menu.rs new file mode 100644 index 0000000..a434326 --- /dev/null +++ b/src/gui/vendor_menu.rs @@ -0,0 +1,189 @@ +use ::rltk::{Rltk, VirtualKeyCode}; +use ::specs::prelude::*; + +use crate::components::{InBackpack, Item, Name, Vendor}; +use crate::{colors, State, VendorMode}; + +#[derive(PartialEq, Copy, Clone)] +pub enum VendorResult { + NoResponse, + Cancel, + Sell, + BuyMode, + SellMode, + Buy, +} + +fn vendor_sell_menu( + gs: &mut State, + ctx: &mut Rltk, + _vendor: Entity, + _mode: VendorMode, +) -> (VendorResult, Option, Option, Option) { + let player_entity = gs.ecs.fetch::(); + let names = gs.ecs.read_storage::(); + let backpack = gs.ecs.read_storage::(); + let items = gs.ecs.read_storage::(); + let entities = gs.ecs.entities(); + + let inventory = (&backpack, &names) + .join() + .filter(|item| item.0.owner == *player_entity); + let count = inventory.count(); + + let mut y = (25 - (count / 2)) as i32; + ctx.draw_box( + 15, + y - 2, + 51, + (count + 3) as i32, + colors::WHITE, + colors::BLACK, + ); + ctx.print_color( + 18, + y - 2, + colors::YELLOW, + colors::BLACK, + "Sell Which Item? (space to switch to buy mode)", + ); + ctx.print_color( + 18, + y + count as i32 + 1, + colors::YELLOW, + colors::BLACK, + "ESCAPE to cancel", + ); + + let mut equippable: Vec = Vec::new(); + for (j, (entity, _pack, name, item)) in (&entities, &backpack, &names, &items) + .join() + .filter(|item| item.1.owner == *player_entity) + .enumerate() + { + ctx.set(17, y, colors::WHITE, colors::BLACK, rltk::to_cp437('(')); + ctx.set( + 18, + y, + colors::YELLOW, + colors::BLACK, + 97 + j as rltk::FontCharType, + ); + ctx.set(19, y, colors::WHITE, colors::BLACK, rltk::to_cp437(')')); + + ctx.print(21, y, &name.name.to_string()); + ctx.print(50, y, &format!("{:.1} gp", item.base_value * 0.8)); + equippable.push(entity); + y += 1; + } + + match ctx.key { + None => (VendorResult::NoResponse, None, None, None), + Some(key) => match key { + VirtualKeyCode::Space => (VendorResult::BuyMode, None, None, None), + VirtualKeyCode::Escape => (VendorResult::Cancel, None, None, None), + _ => { + let selection = rltk::letter_to_option(key); + if selection > -1 && selection < count as i32 { + return ( + VendorResult::Sell, + Some(equippable[selection as usize]), + None, + None, + ); + } + (VendorResult::NoResponse, None, None, None) + } + }, + } +} + +fn vendor_buy_menu( + gs: &mut State, + ctx: &mut Rltk, + vendor: Entity, + _mode: VendorMode, +) -> (VendorResult, Option, Option, Option) { + use crate::raws::*; + + let vendors = gs.ecs.read_storage::(); + + let inventory = get_vendor_items( + &vendors.get(vendor).unwrap().categories, + &RAWS.lock().unwrap(), + ); + let count = inventory.len(); + + let mut y = (25 - (count / 2)) as i32; + ctx.draw_box( + 15, + y - 2, + 51, + (count + 3) as i32, + colors::WHITE, + colors::BLACK, + ); + ctx.print_color( + 18, + y - 2, + colors::YELLOW, + colors::BLACK, + "Buy Which Item? (space to switch to sell mode)", + ); + ctx.print_color( + 18, + y + count as i32 + 1, + colors::YELLOW, + colors::BLACK, + "ESCAPE to cancel", + ); + + for (j, sale) in inventory.iter().enumerate() { + ctx.set(17, y, colors::WHITE, colors::BLACK, rltk::to_cp437('(')); + ctx.set( + 18, + y, + colors::YELLOW, + colors::BLACK, + 97 + j as rltk::FontCharType, + ); + ctx.set(19, y, colors::WHITE, colors::BLACK, rltk::to_cp437(')')); + + ctx.print(21, y, &sale.0); + ctx.print(50, y, &format!("{:.1} gp", sale.1 * 1.2)); + y += 1; + } + + match ctx.key { + None => (VendorResult::NoResponse, None, None, None), + Some(key) => match key { + VirtualKeyCode::Space => (VendorResult::SellMode, None, None, None), + VirtualKeyCode::Escape => (VendorResult::Cancel, None, None, None), + _ => { + let selection = ::rltk::letter_to_option(key); + if selection > -1 && selection < count as i32 { + return ( + VendorResult::Buy, + None, + Some(inventory[selection as usize].0.clone()), + Some(inventory[selection as usize].1), + ); + } + + (VendorResult::NoResponse, None, None, None) + } + }, + } +} + +pub fn show_vendor_menu( + gs: &mut State, + ctx: &mut Rltk, + vendor: Entity, + mode: VendorMode, +) -> (VendorResult, Option, Option, Option) { + match mode { + VendorMode::Buy => vendor_buy_menu(gs, ctx, vendor, mode), + VendorMode::Sell => vendor_sell_menu(gs, ctx, vendor, mode), + } +}