rudimentary ShowTargeting - it needs a visual improvement, but it's back

This commit is contained in:
Llywelwyn 2023-10-05 09:55:16 +01:00
parent a92f60bb15
commit 95c642d4ef
5 changed files with 249 additions and 155 deletions

View file

@ -32,29 +32,42 @@ pub fn draw_items(
x: f32, x: f32,
y: f32, y: f32,
loc: Location, loc: Location,
itemtypes: Vec<ItemType> itemtypes: Option<Vec<ItemType>>
) { ) {
let mut y = y; 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 { let filter = match loc {
Location::All => Filter::All(Some(itemtype)), Location::All => Filter::All(None),
Location::Backpack => Filter::Backpack(Some(itemtype)), Location::Backpack => Filter::Backpack(None),
Location::Equipped => Filter::Equipped, Location::Equipped => Filter::Equipped,
}; };
let inv = items(ecs, filter); let inv = items(ecs, filter);
if inv.is_empty() { 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; 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) { 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) { 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()));
} }

View file

@ -1500,70 +1500,37 @@ pub fn drop_item_menu(gs: &mut State, ctx: &mut App) -> (ItemMenuResult, Option<
(ItemMenuResult::NoResponse, None) (ItemMenuResult::NoResponse, None)
} }
pub fn remove_item_menu(gs: &mut State, ctx: &mut BTerm) -> (ItemMenuResult, Option<Entity>) { pub fn remove_item_menu(gs: &mut State, ctx: &mut App) -> (ItemMenuResult, Option<Entity>) {
let player_entity = gs.ecs.fetch::<Entity>(); let key = &ctx.keyboard;
let backpack = gs.ecs.read_storage::<Equipped>(); for keycode in key.pressed.iter() {
let entities = gs.ecs.entities(); match *keycode {
let inventory = (&backpack).join().filter(|item| item.owner == *player_entity); KeyCode::Escape => {
let count = inventory.count(); return (ItemMenuResult::Cancel, None);
}
let (x_offset, y_offset) = (1, 10); _ => {
let shift = key.shift();
ctx.print_color( let selection = if
1 + x_offset, let Some(key) = letter_to_option::letter_to_option(*keycode, shift)
1 + y_offset, {
RGB::named(WHITE), key
RGB::named(BLACK), } else {
"Unequip what? [aA-zZ][Esc.]" continue;
); };
if check_key(selection) {
let mut equippable: Vec<(Entity, String)> = Vec::new(); // Get the first entity with a Key {} component that has an idx matching "selection".
let mut width = 2; let entities = gs.ecs.entities();
for (entity, _pack) in (&entities, &backpack) let keyed_items = gs.ecs.read_storage::<Key>();
.join() let equipped = gs.ecs.read_storage::<Equipped>();
.filter(|item| item.1.owner == *player_entity) { for (e, key, _e) in (&entities, &keyed_items, &equipped).join() {
let this_name = &obfuscate_name_ecs(&gs.ecs, entity).0; if key.idx == selection {
let this_width = 5 + this_name.len(); return (ItemMenuResult::Selected, Some(e));
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::<Renderable>();
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));
} }
(ItemMenuResult::NoResponse, None)
} }
} }
}
} }
(ItemMenuResult::NoResponse, None)
} }
#[derive(PartialEq, Copy, Clone)] #[derive(PartialEq, Copy, Clone)]
@ -1576,26 +1543,29 @@ pub enum TargetResult {
Selected, Selected,
} }
pub fn ranged_target( pub fn draw_targeting(
gs: &mut State, ecs: &World,
ctx: &mut BTerm, draw: &mut Draw,
atlas: &HashMap<String, Texture>,
x: i32, x: i32,
y: i32, y: i32,
range: i32, range: i32,
aoe: i32 aoe: i32
) -> (TargetResult, Option<Point>) { ) {
let bounds = camera::get_screen_bounds(&gs.ecs, false); let bounds = camera::get_screen_bounds(ecs, false);
let player_entity = gs.ecs.fetch::<Entity>(); let player_entity = ecs.fetch::<Entity>();
let player_pos = gs.ecs.fetch::<Point>(); let player_pos = ecs.fetch::<Point>();
let viewsheds = gs.ecs.read_storage::<Viewshed>(); let viewsheds = ecs.read_storage::<Viewshed>();
ctx.print_color( enum DrawType {
1 + bounds.x_offset, AvailableCell,
1 + bounds.y_offset, AOE,
RGB::named(WHITE), LineToCursor,
RGB::named(BLACK), Cursor,
"Targeting which tile? [mouse input]" CursorUnavailable,
); }
let mut needs_draw: HashMap<Point, DrawType> = HashMap::new();
// Highlight available cells // Highlight available cells
let mut available_cells = Vec::new(); let mut available_cells = Vec::new();
@ -1613,36 +1583,29 @@ pub fn ranged_target(
screen_y > 1 && screen_y > 1 &&
screen_y < bounds.max_y - bounds.min_y - 1 screen_y < bounds.max_y - bounds.min_y - 1
{ {
ctx.set_bg( needs_draw.insert(
screen_x + bounds.x_offset, Point::new(screen_x + bounds.x_offset, screen_y + bounds.y_offset),
screen_y + bounds.y_offset, DrawType::AvailableCell
TARGETING_VALID_COL
); );
available_cells.push(idx); available_cells.push(idx);
} }
} }
} }
} else {
return (TargetResult::Cancel, None);
} }
// Draw mouse cursor // Draw mouse cursor
let mouse_pos = (x, y); let mouse_pos = (x, y);
let bounds = camera::get_screen_bounds(&gs.ecs, false); let bounds = camera::get_screen_bounds(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 mut mouse_pos_adjusted = mouse_pos; let mut mouse_pos_adjusted = mouse_pos;
mouse_pos_adjusted.0 += bounds.min_x - bounds.x_offset; mouse_pos_adjusted.0 += bounds.min_x - bounds.x_offset;
mouse_pos_adjusted.1 += bounds.min_y - bounds.y_offset; mouse_pos_adjusted.1 += bounds.min_y - bounds.y_offset;
let map = gs.ecs.fetch::<Map>(); let map = ecs.fetch::<Map>();
let mut valid_target = false; let mut valid_target = false;
for idx in available_cells.iter() { for idx in available_cells.iter() {
if idx.x == mouse_pos_adjusted.0 && idx.y == mouse_pos_adjusted.1 { if idx.x == mouse_pos_adjusted.0 && idx.y == mouse_pos_adjusted.1 {
valid_target = true; valid_target = true;
} }
} }
let mut result = (TargetResult::NoResponse { x, y }, None);
if valid_target { if valid_target {
let path = line2d( let path = line2d(
LineAlg::Bresenham, LineAlg::Bresenham,
@ -1653,12 +1616,12 @@ pub fn ranged_target(
if i == 0 || i == path.len() - 1 { if i == 0 || i == path.len() - 1 {
continue; continue;
} }
ctx.set( needs_draw.insert(
point.x + bounds.x_offset - bounds.min_x, Point::new(
point.y + bounds.y_offset - bounds.min_y, point.x + bounds.x_offset - bounds.min_x,
RGB::named(TARGETING_LINE_COL), point.y + bounds.y_offset - bounds.min_y
RGB::named(TARGETING_VALID_COL), ),
to_cp437('~') DrawType::LineToCursor
); );
} }
if aoe > 0 { 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 |p| p.x > 0 && p.x < map.width - 1 && p.y > 0 && p.y < map.height - 1
); );
for tile in blast_tiles.iter() { for tile in blast_tiles.iter() {
let bg = if available_cells.contains(&tile) { needs_draw.insert(
let col1 = TARGETING_AOE_COL; Point::new(
let col2 = TARGETING_VALID_COL; tile.x - bounds.min_x + bounds.x_offset,
((col1.0 + col2.0) / 2, (col1.1 + col2.1) / 2, (col1.2 + col2.2) / 2) tile.y - bounds.min_y + bounds.y_offset
} else { ),
let col1 = TARGETING_AOE_COL; DrawType::AOE
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(mouse_pos.0, mouse_pos.1), DrawType::Cursor);
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,
}
};
} else { } 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 { for (k, v) in needs_draw {
None => result, let (image, alpha, colour) = match v {
Some(key) => DrawType::AvailableCell => ("217", 0.2, Color::WHITE),
match key { DrawType::AOE => ("175", 0.3, Color::YELLOW),
VirtualKeyCode::Escape => (TargetResult::Cancel, None), DrawType::LineToCursor => ("217", 0.3, Color::YELLOW),
VirtualKeyCode::Numpad9 => (TargetResult::NoResponse { x: x + 1, y: y - 1 }, None), DrawType::Cursor => ("217", 0.5, Color::YELLOW),
VirtualKeyCode::Numpad7 => (TargetResult::NoResponse { x: x - 1, y: y - 1 }, None), DrawType::CursorUnavailable => ("217", 0.4, Color::RED),
VirtualKeyCode::Numpad6 => (TargetResult::NoResponse { x: x + 1, y }, None), };
VirtualKeyCode::Numpad4 => (TargetResult::NoResponse { x: x - 1, y }, None), let texture = atlas.get(image).unwrap();
VirtualKeyCode::Numpad8 => (TargetResult::NoResponse { x, y: y - 1 }, None), draw.image(texture)
VirtualKeyCode::Numpad3 => (TargetResult::NoResponse { x: x + 1, y: y + 1 }, None), .position((k.x as f32) * TILESIZE, (k.y as f32) * TILESIZE)
VirtualKeyCode::Numpad2 => (TargetResult::NoResponse { x, y: y + 1 }, None), .alpha(alpha)
VirtualKeyCode::Numpad1 => (TargetResult::NoResponse { x: x - 1, y: y + 1 }, None), .color(colour);
_ => result, }
}
pub fn ranged_target(
gs: &mut State,
ctx: &mut App,
x: i32,
y: i32,
range: i32,
_aoe: i32
) -> (TargetResult, Option<Point>) {
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);
} }
}; KeyCode::Numpad1 => {
return result; 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::<Entity>();
let player_pos = gs.ecs.fetch::<Point>();
let viewsheds = gs.ecs.read_storage::<Viewshed>();
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)] #[derive(PartialEq, Copy, Clone)]

View file

@ -183,14 +183,13 @@ struct DrawInfo {
draw_type: DrawType, draw_type: DrawType,
} }
fn draw_camera( fn draw_entities(
map: &Map, map: &Map,
ecs: &World, ecs: &World,
draw: &mut Draw, draw: &mut Draw,
atlas: &HashMap<String, Texture>, atlas: &HashMap<String, Texture>,
font: &Fonts font: &Fonts
) { ) {
render_map_in_view(&*map, ecs, draw, atlas, false);
{ {
let bounds = crate::camera::get_screen_bounds(ecs, false); let bounds = crate::camera::get_screen_bounds(ecs, false);
let positions = ecs.read_storage::<Position>(); let positions = ecs.read_storage::<Position>();
@ -530,7 +529,12 @@ fn draw(_app: &mut App, gfx: &mut Graphics, gs: &mut State) {
_ => { _ => {
let map = gs.ecs.fetch::<Map>(); let map = gs.ecs.fetch::<Map>();
draw_bg(&gs.ecs, &mut draw, &gs.atlas); 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); gui::draw_ui2(&gs.ecs, &mut draw, &gs.atlas, &gs.font);
log = true; 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); 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); 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); gfx.render(&draw);

View file

@ -1,5 +1,4 @@
use serde::{ Deserialize, Serialize }; use serde::{ Deserialize, Serialize };
use bracket_lib::random::RandomNumberGenerator;
#[derive(PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize, Debug)] #[derive(PartialEq, Eq, Hash, Copy, Clone, Serialize, Deserialize, Debug)]
pub enum TileType { pub enum TileType {

View file

@ -376,8 +376,46 @@ impl State {
} }
} }
} }
// RunState::ShowRemoveItem RunState::ShowRemoveItem => {
// RunState::ShowTargeting 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::<WantsToRemoveItem>();
intent
.insert(*self.ecs.fetch::<Entity>(), 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::<WantsToUseItem>();
intent
.insert(*self.ecs.fetch::<Entity>(), WantsToUseItem {
item,
target: result.1,
})
.expect("Unable to insert intent.");
new_runstate = RunState::Ticking;
}
}
}
// RunState::ShowRemoveCurse // RunState::ShowRemoveCurse
// RunState::ShowIdentify // RunState::ShowIdentify
RunState::ActionWithDirection { function } => { RunState::ActionWithDirection { function } => {
@ -682,7 +720,7 @@ impl State {
} }
} }
RunState::ShowRemoveItem => { 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 { match result.0 {
gui::ItemMenuResult::Cancel => { gui::ItemMenuResult::Cancel => {
new_runstate = RunState::AwaitingInput; new_runstate = RunState::AwaitingInput;
@ -701,7 +739,7 @@ impl State {
} }
} }
RunState::ShowTargeting { x, y, range, item, aoe } => { 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 { match result.0 {
gui::TargetResult::Cancel => { gui::TargetResult::Cancel => {
new_runstate = RunState::AwaitingInput; new_runstate = RunState::AwaitingInput;