diff --git a/src/gui/inventory.rs b/src/gui/inventory.rs index 1b9af9e..6cd758b 100644 --- a/src/gui/inventory.rs +++ b/src/gui/inventory.rs @@ -32,29 +32,42 @@ pub fn draw_items( x: f32, y: f32, loc: Location, - itemtypes: Vec + itemtypes: Option> ) { let mut y = y; - for itemtype in itemtypes { + if let Some(itemtypes) = itemtypes { + for itemtype in itemtypes { + let filter = match loc { + Location::All => Filter::All(Some(itemtype)), + Location::Backpack => Filter::Backpack(Some(itemtype)), + Location::Equipped => Filter::Equipped, + }; + let inv = items(ecs, filter); + if inv.is_empty() { + continue; + } + draw.text(&font.b(), itemtype.string()).position(x, y).color(Color::WHITE); + y += TILESIZE; + y = print_options(ecs, draw, font, &inv, x, y) + TILESIZE; + } + } else { let filter = match loc { - Location::All => Filter::All(Some(itemtype)), - Location::Backpack => Filter::Backpack(Some(itemtype)), + Location::All => Filter::All(None), + Location::Backpack => Filter::Backpack(None), Location::Equipped => Filter::Equipped, }; let inv = items(ecs, filter); if inv.is_empty() { - continue; + return; } - draw.text(&font.b(), itemtype.string()).position(x, y).color(Color::WHITE); - y += TILESIZE; y = print_options(ecs, draw, font, &inv, x, y) + TILESIZE; } } pub fn draw_all_items(ecs: &World, draw: &mut Draw, font: &Fonts, x: f32, y: f32) { - draw_items(ecs, draw, font, x, y, Location::All, all_itemtypes()); + draw_items(ecs, draw, font, x, y, Location::All, Some(all_itemtypes())); } pub fn draw_backpack_items(ecs: &World, draw: &mut Draw, font: &Fonts, x: f32, y: f32) { - draw_items(ecs, draw, font, x, y, Location::Backpack, all_itemtypes()); + draw_items(ecs, draw, font, x, y, Location::Backpack, Some(all_itemtypes())); } diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 7e50298..d704082 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -1500,70 +1500,37 @@ pub fn drop_item_menu(gs: &mut State, ctx: &mut App) -> (ItemMenuResult, Option< (ItemMenuResult::NoResponse, None) } -pub fn remove_item_menu(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option) { - let player_entity = gs.ecs.fetch::(); - let backpack = gs.ecs.read_storage::(); - let entities = gs.ecs.entities(); - let inventory = (&backpack).join().filter(|item| item.owner == *player_entity); - let count = inventory.count(); - - let (x_offset, y_offset) = (1, 10); - - ctx.print_color( - 1 + x_offset, - 1 + y_offset, - RGB::named(WHITE), - RGB::named(BLACK), - "Unequip what? [aA-zZ][Esc.]" - ); - - let mut equippable: Vec<(Entity, String)> = Vec::new(); - let mut width = 2; - for (entity, _pack) in (&entities, &backpack) - .join() - .filter(|item| item.1.owner == *player_entity) { - let this_name = &obfuscate_name_ecs(&gs.ecs, entity).0; - let this_width = 5 + this_name.len(); - width = if width > this_width { width } else { this_width }; - equippable.push((entity, this_name.to_string())); - } - - let x = 1 + x_offset; - let mut y = 3 + y_offset; - - ctx.draw_box(x, y, width, (count + 1) as i32, RGB::named(WHITE), RGB::named(BLACK)); - y += 1; - - let mut j = 0; - let renderables = gs.ecs.read_storage::(); - for (e, name) in &equippable { - let (mut fg, glyph) = if let Some(renderable) = renderables.get(*e) { - (renderable.fg, renderable.glyph) - } else { - (RGB::named(WHITE), to_cp437('-')) - }; - ctx.set(x + 1, y, RGB::named(YELLOW), RGB::named(BLACK), 97 + (j as FontCharType)); - ctx.set(x + 3, y, fg, RGB::named(BLACK), glyph); - fg = RGB::named(item_colour_ecs(&gs.ecs, *e)); - ctx.print_color(x + 5, y, fg, RGB::named(BLACK), name); - y += 1; - j += 1; - } - - match ctx.key { - None => (ItemMenuResult::NoResponse, None), - Some(key) => - match key { - VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None), - _ => { - let selection = letter_to_option(key); - if selection > -1 && selection < (count as i32) { - return (ItemMenuResult::Selected, Some(equippable[selection as usize].0)); +pub fn remove_item_menu(gs: &mut State, ctx: &mut App) -> (ItemMenuResult, Option) { + let key = &ctx.keyboard; + for keycode in key.pressed.iter() { + match *keycode { + KeyCode::Escape => { + return (ItemMenuResult::Cancel, None); + } + _ => { + let shift = key.shift(); + let selection = if + let Some(key) = letter_to_option::letter_to_option(*keycode, shift) + { + key + } else { + continue; + }; + if check_key(selection) { + // Get the first entity with a Key {} component that has an idx matching "selection". + let entities = gs.ecs.entities(); + let keyed_items = gs.ecs.read_storage::(); + let equipped = gs.ecs.read_storage::(); + for (e, key, _e) in (&entities, &keyed_items, &equipped).join() { + if key.idx == selection { + return (ItemMenuResult::Selected, Some(e)); + } } - (ItemMenuResult::NoResponse, None) } } + } } + (ItemMenuResult::NoResponse, None) } #[derive(PartialEq, Copy, Clone)] @@ -1576,26 +1543,29 @@ pub enum TargetResult { Selected, } -pub fn ranged_target( - gs: &mut State, - ctx: &mut BTerm, +pub fn draw_targeting( + ecs: &World, + draw: &mut Draw, + atlas: &HashMap, x: i32, y: i32, range: i32, aoe: i32 -) -> (TargetResult, Option) { - let bounds = camera::get_screen_bounds(&gs.ecs, false); - let player_entity = gs.ecs.fetch::(); - let player_pos = gs.ecs.fetch::(); - let viewsheds = gs.ecs.read_storage::(); +) { + let bounds = camera::get_screen_bounds(ecs, false); + let player_entity = ecs.fetch::(); + let player_pos = ecs.fetch::(); + let viewsheds = ecs.read_storage::(); - ctx.print_color( - 1 + bounds.x_offset, - 1 + bounds.y_offset, - RGB::named(WHITE), - RGB::named(BLACK), - "Targeting which tile? [mouse input]" - ); + enum DrawType { + AvailableCell, + AOE, + LineToCursor, + Cursor, + CursorUnavailable, + } + + let mut needs_draw: HashMap = HashMap::new(); // Highlight available cells let mut available_cells = Vec::new(); @@ -1613,36 +1583,29 @@ pub fn ranged_target( screen_y > 1 && screen_y < bounds.max_y - bounds.min_y - 1 { - ctx.set_bg( - screen_x + bounds.x_offset, - screen_y + bounds.y_offset, - TARGETING_VALID_COL + needs_draw.insert( + Point::new(screen_x + bounds.x_offset, screen_y + bounds.y_offset), + DrawType::AvailableCell ); available_cells.push(idx); } } } - } else { - return (TargetResult::Cancel, None); } // Draw mouse cursor let mouse_pos = (x, y); - let bounds = camera::get_screen_bounds(&gs.ecs, false); - let x = x.clamp(bounds.x_offset, bounds.x_offset - 1 + VIEWPORT_W); - let y = y.clamp(bounds.y_offset, bounds.y_offset - 1 + VIEWPORT_H); - + let bounds = camera::get_screen_bounds(ecs, false); let mut mouse_pos_adjusted = mouse_pos; mouse_pos_adjusted.0 += bounds.min_x - bounds.x_offset; mouse_pos_adjusted.1 += bounds.min_y - bounds.y_offset; - let map = gs.ecs.fetch::(); + let map = ecs.fetch::(); let mut valid_target = false; for idx in available_cells.iter() { if idx.x == mouse_pos_adjusted.0 && idx.y == mouse_pos_adjusted.1 { valid_target = true; } } - let mut result = (TargetResult::NoResponse { x, y }, None); if valid_target { let path = line2d( LineAlg::Bresenham, @@ -1653,12 +1616,12 @@ pub fn ranged_target( if i == 0 || i == path.len() - 1 { continue; } - ctx.set( - point.x + bounds.x_offset - bounds.min_x, - point.y + bounds.y_offset - bounds.min_y, - RGB::named(TARGETING_LINE_COL), - RGB::named(TARGETING_VALID_COL), - to_cp437('~') + needs_draw.insert( + Point::new( + point.x + bounds.x_offset - bounds.min_x, + point.y + bounds.y_offset - bounds.min_y + ), + DrawType::LineToCursor ); } if aoe > 0 { @@ -1673,58 +1636,126 @@ pub fn ranged_target( |p| p.x > 0 && p.x < map.width - 1 && p.y > 0 && p.y < map.height - 1 ); for tile in blast_tiles.iter() { - let bg = if available_cells.contains(&tile) { - let col1 = TARGETING_AOE_COL; - let col2 = TARGETING_VALID_COL; - ((col1.0 + col2.0) / 2, (col1.1 + col2.1) / 2, (col1.2 + col2.2) / 2) - } else { - let col1 = TARGETING_AOE_COL; - let col2 = BLACK; - ((col1.0 + col2.0) / 2, (col1.1 + col2.1) / 2, (col1.2 + col2.2) / 2) - }; - ctx.set_bg( - tile.x - bounds.min_x + bounds.x_offset, - tile.y - bounds.min_y + bounds.y_offset, - bg + needs_draw.insert( + Point::new( + tile.x - bounds.min_x + bounds.x_offset, + tile.y - bounds.min_y + bounds.y_offset + ), + DrawType::AOE ); } } - - ctx.set_bg(mouse_pos.0, mouse_pos.1, RGB::named(TARGETING_CURSOR_COL)); - result = match ctx.key { - None => result, - Some(key) => - match key { - VirtualKeyCode::Return => { - return ( - TargetResult::Selected, - Some(Point::new(mouse_pos_adjusted.0, mouse_pos_adjusted.1)), - ); - } - _ => result, - } - }; + needs_draw.insert(Point::new(mouse_pos.0, mouse_pos.1), DrawType::Cursor); } else { - ctx.set_bg(mouse_pos.0, mouse_pos.1, RGB::named(RED)); + needs_draw.insert(Point::new(mouse_pos.0, mouse_pos.1), DrawType::CursorUnavailable); } - result = match ctx.key { - None => result, - Some(key) => - match key { - VirtualKeyCode::Escape => (TargetResult::Cancel, None), - VirtualKeyCode::Numpad9 => (TargetResult::NoResponse { x: x + 1, y: y - 1 }, None), - VirtualKeyCode::Numpad7 => (TargetResult::NoResponse { x: x - 1, y: y - 1 }, None), - VirtualKeyCode::Numpad6 => (TargetResult::NoResponse { x: x + 1, y }, None), - VirtualKeyCode::Numpad4 => (TargetResult::NoResponse { x: x - 1, y }, None), - VirtualKeyCode::Numpad8 => (TargetResult::NoResponse { x, y: y - 1 }, None), - VirtualKeyCode::Numpad3 => (TargetResult::NoResponse { x: x + 1, y: y + 1 }, None), - VirtualKeyCode::Numpad2 => (TargetResult::NoResponse { x, y: y + 1 }, None), - VirtualKeyCode::Numpad1 => (TargetResult::NoResponse { x: x - 1, y: y + 1 }, None), - _ => result, + for (k, v) in needs_draw { + let (image, alpha, colour) = match v { + DrawType::AvailableCell => ("217", 0.2, Color::WHITE), + DrawType::AOE => ("175", 0.3, Color::YELLOW), + DrawType::LineToCursor => ("217", 0.3, Color::YELLOW), + DrawType::Cursor => ("217", 0.5, Color::YELLOW), + DrawType::CursorUnavailable => ("217", 0.4, Color::RED), + }; + let texture = atlas.get(image).unwrap(); + draw.image(texture) + .position((k.x as f32) * TILESIZE, (k.y as f32) * TILESIZE) + .alpha(alpha) + .color(colour); + } +} + +pub fn ranged_target( + gs: &mut State, + ctx: &mut App, + x: i32, + y: i32, + range: i32, + _aoe: i32 +) -> (TargetResult, Option) { + let bounds = camera::get_screen_bounds(&gs.ecs, false); + let x = x.clamp(bounds.x_offset, bounds.x_offset - 1 + VIEWPORT_W); + let y = y.clamp(bounds.y_offset, bounds.y_offset - 1 + VIEWPORT_H); + + let key = &ctx.keyboard; + for keycode in key.pressed.iter() { + match *keycode { + KeyCode::Escape => { + return (TargetResult::Cancel, None); } - }; - return result; + KeyCode::Numpad1 => { + return (TargetResult::NoResponse { x: x - 1, y: y + 1 }, None); + } + KeyCode::Numpad2 => { + return (TargetResult::NoResponse { x, y: y + 1 }, None); + } + KeyCode::Numpad3 => { + return (TargetResult::NoResponse { x: x + 1, y: y + 1 }, None); + } + KeyCode::Numpad4 => { + return (TargetResult::NoResponse { x: x - 1, y }, None); + } + KeyCode::Numpad6 => { + return (TargetResult::NoResponse { x: x + 1, y }, None); + } + KeyCode::Numpad7 => { + return (TargetResult::NoResponse { x: x - 1, y: y - 1 }, None); + } + KeyCode::Numpad8 => { + return (TargetResult::NoResponse { x, y: y - 1 }, None); + } + KeyCode::Numpad9 => { + return (TargetResult::NoResponse { x: x + 1, y: y - 1 }, None); + } + KeyCode::Return => { + let bounds = camera::get_screen_bounds(&gs.ecs, false); + let player_entity = gs.ecs.fetch::(); + let player_pos = gs.ecs.fetch::(); + let viewsheds = gs.ecs.read_storage::(); + + let mut available_cells = Vec::new(); + let visible = viewsheds.get(*player_entity); + if let Some(visible) = visible { + // We have a viewshed + 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 - bounds.min_x; + let screen_y = idx.y - bounds.min_y; + if + screen_x > 1 && + screen_x < bounds.max_x - bounds.min_x - 1 && + screen_y > 1 && + screen_y < bounds.max_y - bounds.min_y - 1 + { + available_cells.push(idx); + } + } + } + } + let mouse_pos = (x, y); + let bounds = camera::get_screen_bounds(&gs.ecs, false); + let mut mouse_pos_adjusted = mouse_pos; + mouse_pos_adjusted.0 += bounds.min_x - bounds.x_offset; + mouse_pos_adjusted.1 += bounds.min_y - bounds.y_offset; + let mut valid_target = false; + for idx in available_cells.iter() { + if idx.x == mouse_pos_adjusted.0 && idx.y == mouse_pos_adjusted.1 { + valid_target = true; + } + } + if valid_target { + return ( + TargetResult::Selected, + Some(Point::new(mouse_pos_adjusted.0, mouse_pos_adjusted.1)), + ); + } + } + _ => {} + } + } + (TargetResult::NoResponse { x, y }, None) } #[derive(PartialEq, Copy, Clone)] diff --git a/src/main.rs b/src/main.rs index 3222884..682556a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -183,14 +183,13 @@ struct DrawInfo { draw_type: DrawType, } -fn draw_camera( +fn draw_entities( map: &Map, ecs: &World, draw: &mut Draw, atlas: &HashMap, font: &Fonts ) { - render_map_in_view(&*map, ecs, draw, atlas, false); { let bounds = crate::camera::get_screen_bounds(ecs, false); let positions = ecs.read_storage::(); @@ -530,7 +529,12 @@ fn draw(_app: &mut App, gfx: &mut Graphics, gs: &mut State) { _ => { let map = gs.ecs.fetch::(); draw_bg(&gs.ecs, &mut draw, &gs.atlas); - draw_camera(&*map, &gs.ecs, &mut draw, &gs.atlas, &gs.font); + render_map_in_view(&*map, &gs.ecs, &mut draw, &gs.atlas, false); + // Special case: targeting needs to be drawn *below* entities, but above tiles. + if let RunState::ShowTargeting { range, item: _, x, y, aoe } = runstate { + gui::draw_targeting(&gs.ecs, &mut draw, &gs.atlas, x, y, range, aoe); + } + draw_entities(&*map, &gs.ecs, &mut draw, &gs.atlas, &gs.font); gui::draw_ui2(&gs.ecs, &mut draw, &gs.atlas, &gs.font); log = true; } @@ -561,6 +565,15 @@ fn draw(_app: &mut App, gfx: &mut Graphics, gs: &mut State) { let (x, y) = (((1 + offset.x) as f32) * TILESIZE, ((3 + offset.y) as f32) * TILESIZE); gui::draw_backpack_items(&gs.ecs, &mut draw, &gs.font, x, y); } + RunState::ShowRemoveItem => { + corner_text("Unequip which item? [aA-zZ]/[Esc.]", &mut draw, &gs.font); + let offset = crate::camera::get_offset(); + let (x, y) = (((1 + offset.x) as f32) * TILESIZE, ((3 + offset.y) as f32) * TILESIZE); + gui::draw_items(&gs.ecs, &mut draw, &gs.font, x, y, gui::Location::Equipped, None); + } + RunState::ShowTargeting { range, item, x, y, aoe } => { + corner_text("Targeting which tile? [0-9]/[YUHJKLBN]", &mut draw, &gs.font); + } _ => {} } gfx.render(&draw); diff --git a/src/map/tiletype.rs b/src/map/tiletype.rs index e20c432..96ac5d9 100644 --- a/src/map/tiletype.rs +++ b/src/map/tiletype.rs @@ -1,5 +1,4 @@ use serde::{ Deserialize, Serialize }; -use bracket_lib::random::RandomNumberGenerator; #[derive(PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize, Debug)] pub enum TileType { diff --git a/src/states/state.rs b/src/states/state.rs index 1a99abf..df98109 100644 --- a/src/states/state.rs +++ b/src/states/state.rs @@ -376,8 +376,46 @@ impl State { } } } - // RunState::ShowRemoveItem - // RunState::ShowTargeting + RunState::ShowRemoveItem => { + let result = gui::remove_item_menu(self, ctx); + match result.0 { + gui::ItemMenuResult::Cancel => { + new_runstate = RunState::AwaitingInput; + } + gui::ItemMenuResult::NoResponse => {} + gui::ItemMenuResult::Selected => { + let item_entity = result.1.unwrap(); + let mut intent = self.ecs.write_storage::(); + intent + .insert(*self.ecs.fetch::(), WantsToRemoveItem { + item: item_entity, + }) + .expect("Unable to insert intent"); + new_runstate = RunState::Ticking; + } + } + } + RunState::ShowTargeting { x, y, range, item, aoe } => { + let result = gui::ranged_target(self, ctx, x, y, range, aoe); + match result.0 { + gui::TargetResult::Cancel => { + new_runstate = RunState::AwaitingInput; + } + gui::TargetResult::NoResponse { x, y } => { + new_runstate = RunState::ShowTargeting { x, y, range, item, aoe }; + } + gui::TargetResult::Selected => { + let mut intent = self.ecs.write_storage::(); + intent + .insert(*self.ecs.fetch::(), WantsToUseItem { + item, + target: result.1, + }) + .expect("Unable to insert intent."); + new_runstate = RunState::Ticking; + } + } + } // RunState::ShowRemoveCurse // RunState::ShowIdentify RunState::ActionWithDirection { function } => { @@ -682,7 +720,7 @@ impl State { } } RunState::ShowRemoveItem => { - let result = gui::remove_item_menu(self, ctx); + let result = (gui::ItemMenuResult::Cancel, None); //gui::remove_item_menu(self, ctx); match result.0 { gui::ItemMenuResult::Cancel => { new_runstate = RunState::AwaitingInput; @@ -701,7 +739,7 @@ impl State { } } RunState::ShowTargeting { x, y, range, item, aoe } => { - let result = gui::ranged_target(self, ctx, x, y, range, aoe); + let result = (gui::TargetResult::Cancel, None); //gui::ranged_target(self, ctx, x, y, range, aoe); match result.0 { gui::TargetResult::Cancel => { new_runstate = RunState::AwaitingInput;