mod enums; mod menu; mod tooltip; use std::cmp::Ordering; use ::rltk::{Point, Rltk, RGB}; use ::specs::prelude::*; 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::game_log::GameLog; use crate::{camera, colors, 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, ) { use rltk::to_cp437; 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) { use rltk::to_cp437; 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 = if weapon.damage_bonus < 0 { format!( "┤ {} ({}d{}{})", &name, weapon.damage_n_dice, weapon.damage_die_type, weapon.damage_bonus ) } else if weapon.damage_bonus == 0 { format!( "┤ {} ({}d{})", &name, weapon.damage_n_dice, weapon.damage_die_type ) } else { 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)", 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 log = ecs.fetch::(); let mut y = 46; for s in log.entries.iter().rev() { if y < 59 { ctx.print(2, y, s); } y += 1; } 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) }