Implement ranged item attacks
This commit is contained in:
parent
ef8d51de1f
commit
d2ebe5dc1d
@ -108,6 +108,7 @@ pub struct WantsToPickupItem {
|
|||||||
#[derive(Component, Debug)]
|
#[derive(Component, Debug)]
|
||||||
pub struct WantsToUseItem {
|
pub struct WantsToUseItem {
|
||||||
pub item: Entity,
|
pub item: Entity,
|
||||||
|
pub target: Option<rltk::Point>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Debug, Clone)]
|
#[derive(Component, Debug, Clone)]
|
||||||
@ -117,3 +118,13 @@ pub struct WantsToDropItem {
|
|||||||
|
|
||||||
#[derive(Component, Debug)]
|
#[derive(Component, Debug)]
|
||||||
pub struct Consumable {}
|
pub struct Consumable {}
|
||||||
|
|
||||||
|
#[derive(Component, Debug)]
|
||||||
|
pub struct Ranged {
|
||||||
|
pub range: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Debug)]
|
||||||
|
pub struct InflictsDamage {
|
||||||
|
pub damage: i32,
|
||||||
|
}
|
||||||
|
63
src/gui.rs
63
src/gui.rs
@ -1,4 +1,6 @@
|
|||||||
use crate::{game_log::GameLog, CombatStats, InBackpack, Map, Name, Player, Position, State};
|
use crate::{
|
||||||
|
game_log::GameLog, CombatStats, InBackpack, Map, Name, Player, Position, State, Viewshed,
|
||||||
|
};
|
||||||
use rltk::{Point, Rltk, VirtualKeyCode, RGB};
|
use rltk::{Point, Rltk, VirtualKeyCode, RGB};
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
|
||||||
@ -334,3 +336,62 @@ pub fn drop_item_menu(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Option
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ranged_target(
|
||||||
|
gs: &mut State,
|
||||||
|
ctx: &mut Rltk,
|
||||||
|
range: i32,
|
||||||
|
) -> (ItemMenuResult, Option<Point>) {
|
||||||
|
let player_entity = gs.ecs.fetch::<Entity>();
|
||||||
|
let player_pos = gs.ecs.fetch::<Point>();
|
||||||
|
let viewsheds = gs.ecs.read_storage::<Viewshed>();
|
||||||
|
|
||||||
|
ctx.print_color(
|
||||||
|
5,
|
||||||
|
0,
|
||||||
|
RGB::named(rltk::YELLOW),
|
||||||
|
RGB::named(rltk::BLACK),
|
||||||
|
"Select Target:",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Highlight available target cells
|
||||||
|
let mut available_cells = Vec::new();
|
||||||
|
let visible = viewsheds.get(*player_entity);
|
||||||
|
if let Some(visible) = visible {
|
||||||
|
for idx in visible.visible_tiles.iter() {
|
||||||
|
let distance = rltk::DistanceAlg::Pythagoras.distance2d(*player_pos, *idx);
|
||||||
|
if distance <= range as f32 {
|
||||||
|
ctx.set_bg(idx.x, idx.y, RGB::named(rltk::BLUE));
|
||||||
|
available_cells.push(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return (ItemMenuResult::Cancel, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw mouse cursor
|
||||||
|
let mouse_pos = ctx.mouse_pos();
|
||||||
|
let mut valid_target = false;
|
||||||
|
for idx in available_cells.iter() {
|
||||||
|
if idx.x == mouse_pos.0 && idx.y == mouse_pos.1 {
|
||||||
|
valid_target = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if valid_target {
|
||||||
|
ctx.set_bg(mouse_pos.0, mouse_pos.1, RGB::named(rltk::CYAN));
|
||||||
|
if ctx.left_click {
|
||||||
|
return (
|
||||||
|
ItemMenuResult::Selected,
|
||||||
|
Some(Point::new(mouse_pos.0, mouse_pos.1)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.set_bg(mouse_pos.0, mouse_pos.1, RGB::named(rltk::RED));
|
||||||
|
if ctx.left_click {
|
||||||
|
return (ItemMenuResult::Cancel, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(ItemMenuResult::NoResponse, None)
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
game_log::GameLog, CombatStats, Consumable, InBackpack, Map, Name, Position, ProvidesHealing,
|
game_log::GameLog, CombatStats, Consumable, InBackpack, InflictsDamage, Map, Name, Position,
|
||||||
WantsToDropItem, WantsToPickupItem, WantsToUseItem,
|
ProvidesHealing, SufferDamage, WantsToDropItem, WantsToPickupItem, WantsToUseItem,
|
||||||
};
|
};
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
|
||||||
@ -57,7 +57,9 @@ impl<'a> System<'a> for ItemUseSystem {
|
|||||||
ReadStorage<'a, Name>,
|
ReadStorage<'a, Name>,
|
||||||
ReadStorage<'a, Consumable>,
|
ReadStorage<'a, Consumable>,
|
||||||
ReadStorage<'a, ProvidesHealing>,
|
ReadStorage<'a, ProvidesHealing>,
|
||||||
|
ReadStorage<'a, InflictsDamage>,
|
||||||
WriteStorage<'a, CombatStats>,
|
WriteStorage<'a, CombatStats>,
|
||||||
|
WriteStorage<'a, SufferDamage>,
|
||||||
);
|
);
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
fn run(&mut self, data: Self::SystemData) {
|
||||||
@ -70,13 +72,20 @@ impl<'a> System<'a> for ItemUseSystem {
|
|||||||
names,
|
names,
|
||||||
consumables,
|
consumables,
|
||||||
healing,
|
healing,
|
||||||
|
inflict_damage,
|
||||||
mut combat_stats,
|
mut combat_stats,
|
||||||
|
mut suffer_damage,
|
||||||
) = data;
|
) = data;
|
||||||
|
|
||||||
for (entity, useitem, stats) in (&entities, &wants_use, &mut combat_stats).join() {
|
for (entity, useitem, stats) in (&entities, &wants_use, &mut combat_stats).join() {
|
||||||
|
let mut used_item = true;
|
||||||
|
|
||||||
|
// If the item heals, apply the healing
|
||||||
match healing.get(useitem.item) {
|
match healing.get(useitem.item) {
|
||||||
None => {}
|
None => {}
|
||||||
Some(healer) => {
|
Some(healer) => {
|
||||||
|
used_item = false;
|
||||||
|
|
||||||
stats.hp = i32::min(stats.max_hp, stats.hp + healer.heal_amount);
|
stats.hp = i32::min(stats.max_hp, stats.hp + healer.heal_amount);
|
||||||
if entity == *player_entity {
|
if entity == *player_entity {
|
||||||
gamelog.entries.push(format!(
|
gamelog.entries.push(format!(
|
||||||
@ -85,9 +94,38 @@ impl<'a> System<'a> for ItemUseSystem {
|
|||||||
healer.heal_amount
|
healer.heal_amount
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
used_item = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If it inflicts damage, apply it to the target cell
|
||||||
|
match inflict_damage.get(useitem.item) {
|
||||||
|
None => {}
|
||||||
|
Some(damage) => {
|
||||||
|
let target_point = useitem.target.unwrap();
|
||||||
|
let idx = map.xy_idx(target_point.x, target_point.y);
|
||||||
|
used_item = false;
|
||||||
|
|
||||||
|
for mob in map.tile_content[idx].iter() {
|
||||||
|
SufferDamage::new_damage(&mut suffer_damage, *mob, damage.damage);
|
||||||
|
if entity == *player_entity {
|
||||||
|
let mob_name = names.get(*mob).unwrap();
|
||||||
|
let item_name = names.get(useitem.item).unwrap();
|
||||||
|
|
||||||
|
gamelog.entries.push(format!(
|
||||||
|
"You use {} on {}, inflicting {} hp.",
|
||||||
|
item_name.name, mob_name.name, damage.damage
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
used_item = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's a consumable, delete it on use
|
||||||
|
if used_item {
|
||||||
let consumable = consumables.get(useitem.item);
|
let consumable = consumables.get(useitem.item);
|
||||||
match consumable {
|
match consumable {
|
||||||
None => {}
|
None => {}
|
||||||
@ -98,6 +136,7 @@ impl<'a> System<'a> for ItemUseSystem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
wants_use.clear();
|
wants_use.clear();
|
||||||
}
|
}
|
||||||
|
36
src/main.rs
36
src/main.rs
@ -48,6 +48,7 @@ pub enum RunState {
|
|||||||
MonsterTurn,
|
MonsterTurn,
|
||||||
ShowInventory,
|
ShowInventory,
|
||||||
ShowDropItem,
|
ShowDropItem,
|
||||||
|
ShowTargeting { range: i32, item: Entity },
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct State {
|
pub struct State {
|
||||||
@ -144,12 +145,22 @@ impl GameState for State {
|
|||||||
gui::ItemMenuResult::NoResponse => {}
|
gui::ItemMenuResult::NoResponse => {}
|
||||||
gui::ItemMenuResult::Selected => {
|
gui::ItemMenuResult::Selected => {
|
||||||
let item_entity = result.1.unwrap();
|
let item_entity = result.1.unwrap();
|
||||||
|
let is_ranged = self.ecs.read_storage::<Ranged>();
|
||||||
|
let is_item_ranged = is_ranged.get(item_entity);
|
||||||
|
|
||||||
|
if let Some(is_item_ranged) = is_item_ranged {
|
||||||
|
newrunstate = RunState::ShowTargeting {
|
||||||
|
range: is_item_ranged.range,
|
||||||
|
item: item_entity,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
let mut intent = self.ecs.write_storage::<WantsToUseItem>();
|
let mut intent = self.ecs.write_storage::<WantsToUseItem>();
|
||||||
intent
|
intent
|
||||||
.insert(
|
.insert(
|
||||||
*self.ecs.fetch::<Entity>(),
|
*self.ecs.fetch::<Entity>(),
|
||||||
WantsToUseItem {
|
WantsToUseItem {
|
||||||
item: item_entity,
|
item: item_entity,
|
||||||
|
target: None,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.expect("failed to add intent to use item");
|
.expect("failed to add intent to use item");
|
||||||
@ -158,6 +169,7 @@ impl GameState for State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
RunState::ShowDropItem => {
|
RunState::ShowDropItem => {
|
||||||
let result = gui::drop_item_menu(self, ctx);
|
let result = gui::drop_item_menu(self, ctx);
|
||||||
|
|
||||||
@ -178,6 +190,28 @@ impl GameState for State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
RunState::ShowTargeting { range, item } => {
|
||||||
|
let result = gui::ranged_target(self, ctx, range);
|
||||||
|
match result.0 {
|
||||||
|
gui::ItemMenuResult::Cancel => newrunstate = RunState::AwaitingInput,
|
||||||
|
gui::ItemMenuResult::NoResponse => {}
|
||||||
|
gui::ItemMenuResult::Selected => {
|
||||||
|
let mut intent = self.ecs.write_storage::<WantsToUseItem>();
|
||||||
|
|
||||||
|
intent
|
||||||
|
.insert(
|
||||||
|
*self.ecs.fetch::<Entity>(),
|
||||||
|
WantsToUseItem {
|
||||||
|
item,
|
||||||
|
target: result.1,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("failed to add intent to use item");
|
||||||
|
|
||||||
|
newrunstate = RunState::PlayerTurn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -217,6 +251,8 @@ fn main() -> rltk::BError {
|
|||||||
WantsToUseItem,
|
WantsToUseItem,
|
||||||
WantsToDropItem,
|
WantsToDropItem,
|
||||||
Consumable,
|
Consumable,
|
||||||
|
Ranged,
|
||||||
|
InflictsDamage,
|
||||||
);
|
);
|
||||||
|
|
||||||
let map = Map::new_map_rooms_and_corridors();
|
let map = Map::new_map_rooms_and_corridors();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
BlocksTile, CombatStats, Consumable, Item, Monster, Name, Player, Position, ProvidesHealing,
|
BlocksTile, CombatStats, Consumable, InflictsDamage, Item, Monster, Name, Player, Position,
|
||||||
Rect, Renderable, Viewshed, MAP_WIDTH,
|
ProvidesHealing, Ranged, Rect, Renderable, Viewshed, MAP_WIDTH,
|
||||||
};
|
};
|
||||||
use rltk::{RandomNumberGenerator, RGB};
|
use rltk::{RandomNumberGenerator, RGB};
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
@ -126,12 +126,12 @@ pub fn spawn_room(ecs: &mut World, room: &Rect) {
|
|||||||
random_monster(ecs, x as i32, y as i32);
|
random_monster(ecs, x as i32, y as i32);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actually spawn the potions
|
// Actually spawn the items
|
||||||
for idx in item_spawn_points.iter() {
|
for idx in item_spawn_points.iter() {
|
||||||
let x = *idx % MAP_WIDTH;
|
let x = *idx % MAP_WIDTH;
|
||||||
let y = *idx / MAP_WIDTH;
|
let y = *idx / MAP_WIDTH;
|
||||||
|
|
||||||
health_potion(ecs, x as i32, y as i32);
|
random_item(ecs, x as i32, y as i32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,11 +144,39 @@ fn health_potion(ecs: &mut World, x: i32, y: i32) {
|
|||||||
bg: RGB::named(rltk::BLACK),
|
bg: RGB::named(rltk::BLACK),
|
||||||
render_order: 2,
|
render_order: 2,
|
||||||
})
|
})
|
||||||
.with(Name {
|
.with(Name::new("Health Potion"))
|
||||||
name: "Health Potion".to_string(),
|
|
||||||
})
|
|
||||||
.with(Item {})
|
.with(Item {})
|
||||||
.with(Consumable {})
|
.with(Consumable {})
|
||||||
.with(ProvidesHealing { heal_amount: 8 })
|
.with(ProvidesHealing { heal_amount: 8 })
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn magic_missile_scroll(ecs: &mut World, x: i32, y: i32) {
|
||||||
|
ecs.create_entity()
|
||||||
|
.with(Position { x, y })
|
||||||
|
.with(Renderable {
|
||||||
|
glyph: rltk::to_cp437(')'),
|
||||||
|
fg: RGB::named(rltk::CYAN),
|
||||||
|
bg: RGB::named(rltk::BLACK),
|
||||||
|
render_order: 2,
|
||||||
|
})
|
||||||
|
.with(Name::new("Magic Missile Scroll"))
|
||||||
|
.with(Item {})
|
||||||
|
.with(Consumable {})
|
||||||
|
.with(Ranged { range: 6 })
|
||||||
|
.with(InflictsDamage { damage: 8 })
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn random_item(ecs: &mut World, x: i32, y: i32) {
|
||||||
|
let roll: i32;
|
||||||
|
{
|
||||||
|
let mut rng = ecs.write_resource::<RandomNumberGenerator>();
|
||||||
|
roll = rng.roll_dice(1, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
match roll {
|
||||||
|
1 => health_potion(ecs, x, y),
|
||||||
|
_ => magic_missile_scroll(ecs, x, y),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user