4586141d9ab7f39a46e3c2a0baaa72af96ed8e30
commit 4586141d9ab7f39a46e3c2a0baaa72af96ed8e30
Author: Simon Watson <spw01@protonmail.com>
Date: Sun Oct 2 23:52:38 2022 -0400

Up to Introducting Area of Effect

diff --git a/src/components.rs b/src/components.rs
index 812e3c2..65e742e 100644
--- a/src/components.rs
+++ b/src/components.rs
@@ -3,6 +3,21 @@ use specs_derive::*;
use rltk::{RGB};

// COMPONENTS
+#[derive(Component, Debug)]
+pub struct AreaOfEffect {
+ pub radius : i32
+}
+
+#[derive(Component, Debug)]
+pub struct Ranged {
+ pub range : i32
+}
+
+#[derive(Component, Debug)]
+pub struct InflictsDamage {
+ pub damage : i32
+}
+
#[derive(Component, Debug)]
pub struct Consumable {}

@@ -12,8 +27,9 @@ pub struct WantsToDropItem {
}

#[derive(Component, Debug)]
-pub struct WantsToDrinkPotion {
- pub potion : Entity
+pub struct WantsToUseItem {
+ pub item : Entity,
+ pub target: Option<rltk::Point>
}

#[derive(Component, Debug, Clone)]
@@ -31,7 +47,7 @@ pub struct InBackpack {
pub struct Item {}

#[derive(Component, Debug)]
-pub struct Potion {
+pub struct ProvidesHealing {
pub heal_amount : i32
}

diff --git a/src/gui.rs b/src/gui.rs
index a7e7634..0f7e684 100644
--- a/src/gui.rs
+++ b/src/gui.rs
@@ -3,10 +3,52 @@ use specs::prelude::*;
use super::{CombatStats, Player, GameLog,
MAPHEIGHT, Map, Name,
Position, Point, InBackpack,
- State};
+ State, Viewshed};

const GUI_HEIGHT: usize = 50 - MAPHEIGHT - 1;

+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 {
+ // We have a viewshed
+ 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)
+}
+
pub fn draw_ui(ecs: &World, ctx : &mut Rltk) {
ctx.draw_box(0, 38, 79, GUI_HEIGHT, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK));

diff --git a/src/inventory_system.rs b/src/inventory_system.rs
index 55a2330..e6e6c47 100644
--- a/src/inventory_system.rs
+++ b/src/inventory_system.rs
@@ -1,7 +1,10 @@
use specs::prelude::*;
+use crate::{Consumable, ProvidesHealing, InflictsDamage};
+
use super::{WantsToPickupItem, Name, InBackpack,
- Position, GameLog, WantsToDrinkPotion,
- CombatStats, Potion, WantsToDropItem};
+ Position, GameLog, WantsToUseItem,
+ CombatStats, Item, WantsToDropItem,
+ Map, SufferDamage};

pub struct ItemCollectionSystem {}

@@ -31,37 +34,82 @@ impl<'a> System<'a> for ItemCollectionSystem {
}
}

-pub struct PotionUseSystem {}
+pub struct ItemUseSystem {}

-impl<'a> System<'a> for PotionUseSystem {
+impl<'a> System<'a> for ItemUseSystem {
#[allow(clippy::type_complexity)]
type SystemData = ( ReadExpect<'a, Entity>,
WriteExpect<'a, GameLog>,
+ ReadExpect<'a, Map>,
Entities<'a>,
- WriteStorage<'a, WantsToDrinkPotion>,
+ WriteStorage<'a, WantsToUseItem>,
ReadStorage<'a, Name>,
- ReadStorage<'a, Potion>,
- WriteStorage<'a, CombatStats>
+ ReadStorage<'a, ProvidesHealing>,
+ WriteStorage<'a, CombatStats>,
+ ReadStorage<'a, Consumable>,
+ ReadStorage<'a, Item>,
+ ReadStorage<'a, InflictsDamage>,
+ WriteStorage<'a, SufferDamage>
);

fn run(&mut self, data : Self::SystemData) {
- let (player_entity, mut gamelog, entities, mut wants_drink, names, potions, mut combat_stats) = data;
-
- for (entity, drink, stats) in (&entities, &wants_drink, &mut combat_stats).join() {
- let potion = potions.get(drink.potion);
- match potion {
+ let (player_entity,
+ mut gamelog,
+ map,
+ entities,
+ mut wants_use,
+ names,
+ healing,
+ mut combat_stats,
+ consumables,
+ item,
+ inflict_damage,
+ mut suffer_damage) = data;
+
+ for (entity, useitem, stats) in (&entities, &wants_use, &mut combat_stats).join() {
+ let mut used_item = true;
+ // If it inflicts damage, apply it to the target cell
+ let item_damages = inflict_damage.get(useitem.item);
+ match item_damages {
None => {}
- Some(potion) => {
- stats.hp = i32::min(stats.max_hp, stats.hp + potion.heal_amount);
+ 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 {} damage.", item_name.name, mob_name.name, damage.damage));
+ }
+
+ used_item = true;
+ }
+ }
+ }
+
+ let item_heals = healing.get(useitem.item);
+ match item_heals {
+ None => {}
+ Some(healer) => {
+ stats.hp = i32::min(stats.max_hp, stats.hp + healer.heal_amount);
if entity == *player_entity {
- gamelog.entries.push(format!("You drink the {}, healing {} hp.", names.get(drink.potion).unwrap().name, potion.heal_amount));
+ gamelog.entries.push(format!("You drink the {}, healing {} hp.", names.get(useitem.item).unwrap().name, healer.heal_amount));
}
- entities.delete(drink.potion).expect("Delete failed");
}
}
+
+ let consumable = consumables.get(useitem.item);
+ match consumable {
+ None => {}
+ Some(_) => {
+ entities.delete(useitem.item).expect("Delete failed");
+ }
+}
}

- wants_drink.clear();
+ wants_use.clear();
}
}

diff --git a/src/main.rs b/src/main.rs
index 64a740b..b2d8940 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -37,6 +37,9 @@ pub enum RunState {
MonsterTurn,
ShowInventory,
ShowDropItem,
+ ShowTargeting {
+ range : i32,
+ item : Entity },
}

pub struct State {
@@ -57,8 +60,8 @@ impl State {
dmgs.run_now(&self.ecs);
let mut pickup = ItemCollectionSystem{};
pickup.run_now(&self.ecs);
- let mut potions = PotionUseSystem{};
- potions.run_now(&self.ecs);
+ let mut items = ItemUseSystem{};
+ items.run_now(&self.ecs);
let mut drop_items = ItemDropSystem{};
drop_items.run_now(&self.ecs);
self.ecs.maintain();
@@ -100,8 +103,26 @@ impl GameState for State {
gui::ItemMenuResult::NoResponse => {}
gui::ItemMenuResult::Selected => {
let item_entity = result.1.unwrap();
- let mut intent = self.ecs.write_storage::<WantsToDrinkPotion>();
- intent.insert(*self.ecs.fetch::<Entity>(), WantsToDrinkPotion{ potion: item_entity }).expect("Unable to insert intent");
+ 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>();
+ intent.insert(*self.ecs.fetch::<Entity>(), WantsToUseItem{ item: item_entity, target: None }).expect("Unable to insert intent");
+ newrunstate = RunState::PlayerTurn;
+ }
+ }
+ }
+ }
+ 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("Unable to insert intent");
newrunstate = RunState::PlayerTurn;
}
}
@@ -170,11 +191,14 @@ fn main() -> rltk::BError {
gs.ecs.register::<WantsToMelee>();
gs.ecs.register::<SufferDamage>();
gs.ecs.register::<Item>();
- gs.ecs.register::<Potion>();
gs.ecs.register::<InBackpack>();
gs.ecs.register::<WantsToPickupItem>();
- gs.ecs.register::<WantsToDrinkPotion>();
+ gs.ecs.register::<WantsToUseItem>();
+ gs.ecs.register::<ProvidesHealing>();
gs.ecs.register::<WantsToDropItem>();
+ gs.ecs.register::<Consumable>();
+ gs.ecs.register::<Ranged>();
+ gs.ecs.register::<InflictsDamage>();


let map = Map::new_map_rooms_and_corridors();
diff --git a/src/spawner.rs b/src/spawner.rs
index 2fe0807..6209566 100644
--- a/src/spawner.rs
+++ b/src/spawner.rs
@@ -3,10 +3,40 @@ use specs::prelude::*;
use super::{CombatStats, Player, Renderable,
Name, Position, Viewshed,
Monster, BlocksTile, Rect,
- MAPWIDTH, Item, Potion};
+ MAPWIDTH, Item, ProvidesHealing,
+ Consumable, InflictsDamage, Ranged};

const MAX_MONSTERS : i32 = 4;
-const MAX_ITEMS : i32 = 2;
+const MAX_ITEMS : i32 = 2; // PER MAP
+
+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) }
+ }
+}
+
+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{ name : "Magic Missile Scroll".to_string() })
+ .with(Item{})
+ .with(Consumable{})
+ .with(Ranged{ range: 6 })
+ .with(InflictsDamage{ damage: 8 })
+ .build();
+}

fn health_potion(ecs: &mut World, x: i32, y: i32) {
ecs.create_entity()
@@ -19,7 +49,8 @@ fn health_potion(ecs: &mut World, x: i32, y: i32) {
})
.with(Name{ name : "Health Potion".to_string() })
.with(Item{})
- .with(Potion{ heal_amount: 12 })
+ .with(Consumable{})
+ .with(ProvidesHealing{ heal_amount: 8 })
.build();
}

@@ -69,11 +100,11 @@ pub fn spawn_room(ecs: &mut World, room : &Rect) {
random_monster(ecs, x as i32, y as i32);
}

- // Actually spawn the potions
+ // Actually spawn the items
for idx in item_spawn_points.iter() {
let x = *idx % MAPWIDTH;
let y = *idx / MAPWIDTH;
- health_potion(ecs, x as i32, y as i32);
+ random_item(ecs, x as i32, y as i32);
}
}