use super::{ camera::get_screen_bounds, Attributes, Hidden, Map, Name, Pools, Position, Renderable, World, RGB, }; use crate::TileType; use crate::data::ids::*; use bracket_lib::prelude::*; use specs::prelude::*; struct Tooltip { lines: Vec<(String, RGB)>, } const ATTRIBUTE_COLOUR: RGB = RGB { r: 1.0, g: 0.75, b: 0.8 }; const RED_WARNING: RGB = RGB { r: 1.0, g: 0.0, b: 0.0 }; const ORANGE_WARNING: RGB = RGB { r: 1.0, g: 0.65, b: 0.0 }; const YELLOW_WARNING: RGB = RGB { r: 1.0, g: 1.0, b: 0.0 }; const GREEN_WARNING: RGB = RGB { r: 0.0, g: 1.0, b: 0.0 }; impl Tooltip { fn new() -> Tooltip { return Tooltip { lines: Vec::new() }; } fn add(&mut self, line: S, fg: RGB) { self.lines.push((line.to_string(), fg)); } fn width(&self) -> i32 { let mut max = 0; for s in self.lines.iter() { if s.0.len() > max { max = s.0.len(); } } return (max as i32) + 2i32; } fn height(&self) -> i32 { return (self.lines.len() as i32) + 2i32; } fn render(&self, ctx: &mut BTerm, x: i32, y: i32) { ctx.draw_box( x, y, self.width() - 1, self.height() - 1, RGB::named(WHITE), RGB::named(BLACK) ); for (i, s) in self.lines.iter().enumerate() { ctx.print_color(x + 1, y + (i as i32) + 1, s.1, RGB::named(BLACK), &s.0); } } } #[rustfmt::skip] pub fn draw_tooltips(ecs: &World, ctx: &mut BTerm, xy: Option<(i32, i32)>) { let (min_x, _max_x, min_y, _max_y, x_offset, y_offset) = get_screen_bounds(ecs, ctx); let map = ecs.fetch::(); let names = ecs.read_storage::(); let positions = ecs.read_storage::(); let renderables = ecs.read_storage::(); let hidden = ecs.read_storage::(); let attributes = ecs.read_storage::(); let pools = ecs.read_storage::(); let entities = ecs.entities(); let player_entity = ecs.fetch::(); let mouse_pos = if xy.is_none() { ctx.mouse_pos() } else { xy.unwrap() }; let mut mouse_pos_adjusted = mouse_pos; mouse_pos_adjusted.0 += min_x - x_offset; mouse_pos_adjusted.1 += min_y - y_offset; if mouse_pos_adjusted.0 >= map.width || mouse_pos_adjusted.1 >= map.height || mouse_pos_adjusted.1 < 0 // Might need to be 1, and -1 from map height/width. || mouse_pos_adjusted.0 < 0 { return; } if !(map.visible_tiles[map.xy_idx(mouse_pos_adjusted.0, mouse_pos_adjusted.1)] || map.telepath_tiles[map.xy_idx(mouse_pos_adjusted.0, mouse_pos_adjusted.1)]) { return; } let mut tooltips: Vec = Vec::new(); match map.tiles[map.xy_idx(mouse_pos_adjusted.0, mouse_pos_adjusted.1)] { TileType::ToLocal(n) => { let name = get_local_desc(n); let mut tip = Tooltip::new(); tip.add(format!("You see {}.", name), get_local_col(n)); tooltips.push(tip); } TileType::ToOvermap(n) => { let name = get_local_desc(n); let mut tip = Tooltip::new(); tip.add(format!("You see an exit from {}.", name), get_local_col(n)); tooltips.push(tip); } _ => {} } for (entity, position, renderable, _name, _hidden) in (&entities, &positions, &renderables, &names, !&hidden).join() { if position.x == mouse_pos_adjusted.0 && position.y == mouse_pos_adjusted.1 { let mut tip = Tooltip::new(); tip.add(crate::gui::obfuscate_name_ecs(ecs, entity).0, renderable.fg); let intrinsics = ecs.read_storage::(); if let Some(intrinsics) = intrinsics.get(entity) { if !intrinsics.list.is_empty() { tip.add(intrinsics.describe(), RGB::named(WHITE)); } } // Attributes let attr = attributes.get(entity); if let Some(a) = attr { let mut s = "".to_string(); if a.strength.bonus < -2 { s += "weak "}; if a.strength.bonus > 2 { s += "strong "}; if a.dexterity.bonus < -2 { s += "clumsy "}; if a.dexterity.bonus > 2 { s += "agile "}; if a.constitution.bonus < -2 { s += "frail "}; if a.constitution.bonus > 2 { s += "hardy "}; if a.intelligence.bonus < -2 { s += "dim "}; if a.intelligence.bonus > 2 { s += "smart "}; if a.wisdom.bonus < -2 { s += "unwise "}; if a.wisdom.bonus > 2 { s += "wisened "}; if a.charisma.bonus < -2 { s += "ugly"}; if a.charisma.bonus > 2 { s += "attractive"}; if !s.is_empty() { if s.ends_with(" ") { s.pop(); } tip.add(s, ATTRIBUTE_COLOUR); } } // Pools let pool = pools.get(entity); let player_pool = pools.get(*player_entity).unwrap(); if let Some(p) = pool { let level_diff: i32 = p.level - player_pool.level; if level_diff <= -2 { tip.add("-weak-", YELLOW_WARNING); } else if level_diff >= 2 { tip.add("*threatening*", ORANGE_WARNING); } let health_percent: f32 = p.hit_points.current as f32 / p.hit_points.max as f32; if health_percent == 1.0 { tip.add("healthy", GREEN_WARNING); } else if health_percent <= 0.25 { tip.add("*critical*", RED_WARNING); } else if health_percent <= 0.5 { tip.add("-bloodied-", ORANGE_WARNING); } else if health_percent <= 0.75 { tip.add("injured", YELLOW_WARNING); } } tooltips.push(tip); } } if tooltips.is_empty() { return ; } let white = RGB::named(WHITE); let arrow; let arrow_x; let arrow_y = mouse_pos.1; if mouse_pos.0 > 35 { // Render to the left arrow = to_cp437('→'); arrow_x = mouse_pos.0 - 1; } else { // Render to the right arrow = to_cp437('←'); arrow_x = mouse_pos.0 + 1; } ctx.set(arrow_x, arrow_y, white, RGB::named(BLACK), arrow); let mut total_height = 0; for t in tooltips.iter() { total_height += t.height(); } let mut y = mouse_pos.1 - (total_height / 2); while y + (total_height / 2) > 50 { y -= 1; } for t in tooltips.iter() { let x = if mouse_pos.0 > 35 { mouse_pos.0 - (1 + t.width()) } else { mouse_pos.0 + (1 + 1) }; t.render(ctx, x, y); y += t.height(); } }