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,
y: f32,
loc: Location,
itemtypes: Vec<ItemType>
itemtypes: Option<Vec<ItemType>>
) {
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()));
}

View file

@ -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<Entity>) {
let player_entity = gs.ecs.fetch::<Entity>();
let backpack = gs.ecs.read_storage::<Equipped>();
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::<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));
pub fn remove_item_menu(gs: &mut State, ctx: &mut App) -> (ItemMenuResult, Option<Entity>) {
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::<Key>();
let equipped = gs.ecs.read_storage::<Equipped>();
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<String, Texture>,
x: i32,
y: i32,
range: i32,
aoe: i32
) -> (TargetResult, Option<Point>) {
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 bounds = camera::get_screen_bounds(ecs, false);
let player_entity = ecs.fetch::<Entity>();
let player_pos = ecs.fetch::<Point>();
let viewsheds = ecs.read_storage::<Viewshed>();
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<Point, DrawType> = 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::<Map>();
let map = ecs.fetch::<Map>();
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<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);
}
};
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::<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)]

View file

@ -183,14 +183,13 @@ struct DrawInfo {
draw_type: DrawType,
}
fn draw_camera(
fn draw_entities(
map: &Map,
ecs: &World,
draw: &mut Draw,
atlas: &HashMap<String, Texture>,
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::<Position>();
@ -530,7 +529,12 @@ fn draw(_app: &mut App, gfx: &mut Graphics, gs: &mut State) {
_ => {
let map = gs.ecs.fetch::<Map>();
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);

View file

@ -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 {

View file

@ -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::<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::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;