Browse Source

Up to Introducting Area of Effect

Simon Watson 1 year ago
parent
commit
4586141d9a
5 changed files with 193 additions and 32 deletions
  1. 19 3
      src/components.rs
  2. 43 1
      src/gui.rs
  3. 65 17
      src/inventory_system.rs
  4. 30 6
      src/main.rs
  5. 36 5
      src/spawner.rs

+ 19 - 3
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
 }
 

+ 43 - 1
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));
 

+ 65 - 17
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();
     }
 }
 

+ 30 - 6
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();

+ 36 - 5
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);
     }
 }