Finished section 1
This commit is contained in:
parent
fb5b70ce3b
commit
aad1402f81
@ -135,3 +135,41 @@ pub struct SerializeMe;
|
|||||||
pub struct SerializationHelper {
|
pub struct SerializationHelper {
|
||||||
pub map: super::map::Map,
|
pub map: super::map::Map,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Copy, Clone, Serialize, Deserialize)]
|
||||||
|
pub enum EquipmentSlot {
|
||||||
|
Melee,
|
||||||
|
Shield,
|
||||||
|
Head,
|
||||||
|
Shoulder,
|
||||||
|
Chest,
|
||||||
|
Legs,
|
||||||
|
Hands,
|
||||||
|
Feet,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, Copy, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Equippable {
|
||||||
|
pub slot: EquipmentSlot,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, ConvertSaveload, Clone)]
|
||||||
|
pub struct Equipped {
|
||||||
|
pub owner: Entity,
|
||||||
|
pub slot: EquipmentSlot,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, ConvertSaveload, Clone)]
|
||||||
|
pub struct MeleePowerBonus {
|
||||||
|
pub power: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, ConvertSaveload, Clone)]
|
||||||
|
pub struct DefenseBonus {
|
||||||
|
pub defense: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Component, ConvertSaveload, Clone)]
|
||||||
|
pub struct WantsToRemoveItem {
|
||||||
|
pub item: Entity,
|
||||||
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use crate::gamelog::GameLog;
|
|
||||||
use crate::Name;
|
|
||||||
use rltk::console;
|
use rltk::console;
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
|
||||||
|
use crate::{Name, RunState};
|
||||||
use crate::components::{CombatStats, Player, SufferDamage};
|
use crate::components::{CombatStats, Player, SufferDamage};
|
||||||
|
use crate::gamelog::GameLog;
|
||||||
|
|
||||||
pub struct DamageSystem {}
|
pub struct DamageSystem {}
|
||||||
|
|
||||||
@ -44,7 +44,10 @@ pub fn delete_the_dead(ecs: &mut World) {
|
|||||||
dead.push(entity)
|
dead.push(entity)
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(_) => console::log("You are dead"),
|
Some(_) => {
|
||||||
|
let mut runstate = ecs.write_resource::<RunState>();
|
||||||
|
*runstate = RunState::GameOver;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
111
src/gui.rs
111
src/gui.rs
@ -1,14 +1,14 @@
|
|||||||
use rltk::Rltk;
|
use rltk::{Rltk};
|
||||||
use rltk::RGB;
|
|
||||||
use rltk::{Point, VirtualKeyCode};
|
use rltk::{Point, VirtualKeyCode};
|
||||||
|
use rltk::RGB;
|
||||||
use specs::prelude::*;
|
use specs::prelude::*;
|
||||||
|
|
||||||
|
use crate::{CombatStats, InBackpack, RunState, State, Viewshed};
|
||||||
|
use crate::{Equipped, Map};
|
||||||
use crate::gamelog::GameLog;
|
use crate::gamelog::GameLog;
|
||||||
use crate::Map;
|
|
||||||
use crate::Name;
|
use crate::Name;
|
||||||
use crate::Player;
|
use crate::Player;
|
||||||
use crate::Position;
|
use crate::Position;
|
||||||
use crate::{CombatStats, InBackpack, RunState, State, Viewshed};
|
|
||||||
|
|
||||||
pub fn draw_ui(ecs: &World, ctx: &mut Rltk) {
|
pub fn draw_ui(ecs: &World, ctx: &mut Rltk) {
|
||||||
ctx.draw_box(
|
ctx.draw_box(
|
||||||
@ -514,3 +514,106 @@ pub fn main_menu(gs: &mut State, ctx: &mut Rltk) -> MainMenuResult {
|
|||||||
selected: MainMenuSelection::NewGame,
|
selected: MainMenuSelection::NewGame,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn remove_item_menu(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Option<Entity>) {
|
||||||
|
let player_entity = gs.ecs.fetch::<Entity>();
|
||||||
|
let names = gs.ecs.read_storage::<Name>();
|
||||||
|
let backpack = gs.ecs.read_storage::<Equipped>();
|
||||||
|
let entities = gs.ecs.entities();
|
||||||
|
|
||||||
|
let inventory = (&backpack, &names)
|
||||||
|
.join()
|
||||||
|
.filter(|item| item.0.owner == *player_entity);
|
||||||
|
let count = inventory.count();
|
||||||
|
|
||||||
|
let mut y = (25 - (count / 2)) as i32;
|
||||||
|
ctx.draw_box(
|
||||||
|
15,
|
||||||
|
y - 2,
|
||||||
|
31,
|
||||||
|
(count + 3) as i32,
|
||||||
|
RGB::named(rltk::WHITE),
|
||||||
|
RGB::named(rltk::BLACK),
|
||||||
|
);
|
||||||
|
ctx.print_color(
|
||||||
|
18,
|
||||||
|
y - 2,
|
||||||
|
RGB::named(rltk::YELLOW),
|
||||||
|
RGB::named(rltk::BLACK),
|
||||||
|
"Remove Which Item?",
|
||||||
|
);
|
||||||
|
ctx.print_color(
|
||||||
|
18,
|
||||||
|
y + count as i32 + 1,
|
||||||
|
RGB::named(rltk::YELLOW),
|
||||||
|
RGB::named(rltk::BLACK),
|
||||||
|
"ESCAPE to cancel",
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut equippable: Vec<Entity> = Vec::new();
|
||||||
|
let mut j = 0;
|
||||||
|
for (entity, _pack, name) in (&entities, &backpack, &names)
|
||||||
|
.join()
|
||||||
|
.filter(|item| item.1.owner == *player_entity)
|
||||||
|
{
|
||||||
|
ctx.set(
|
||||||
|
17,
|
||||||
|
y,
|
||||||
|
RGB::named(rltk::WHITE),
|
||||||
|
RGB::named(rltk::BLACK),
|
||||||
|
rltk::to_cp437('('),
|
||||||
|
);
|
||||||
|
ctx.set(
|
||||||
|
18,
|
||||||
|
y,
|
||||||
|
RGB::named(rltk::YELLOW),
|
||||||
|
RGB::named(rltk::BLACK),
|
||||||
|
97 + j as rltk::FontCharType,
|
||||||
|
);
|
||||||
|
ctx.set(
|
||||||
|
19,
|
||||||
|
y,
|
||||||
|
RGB::named(rltk::WHITE),
|
||||||
|
RGB::named(rltk::BLACK),
|
||||||
|
rltk::to_cp437(')'),
|
||||||
|
);
|
||||||
|
|
||||||
|
ctx.print(21, y, &name.name.to_string());
|
||||||
|
equippable.push(entity);
|
||||||
|
y += 1;
|
||||||
|
j += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
match ctx.key {
|
||||||
|
None => (ItemMenuResult::NoResponse, None),
|
||||||
|
Some(key) => match key {
|
||||||
|
VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None),
|
||||||
|
_ => {
|
||||||
|
let selection = rltk::letter_to_option(key);
|
||||||
|
if selection > -1 && selection < count as i32 {
|
||||||
|
return (
|
||||||
|
ItemMenuResult::Selected,
|
||||||
|
Some(equippable[selection as usize]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
(ItemMenuResult::NoResponse, None)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Copy, Clone)]
|
||||||
|
pub enum GameOverResult { NoSelection, QuitToMenu }
|
||||||
|
|
||||||
|
pub fn game_over(ctx: &mut Rltk) -> GameOverResult {
|
||||||
|
ctx.print_color_centered(15, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "Your journey has ended!");
|
||||||
|
ctx.print_color_centered(17, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), "One day, we'll tell you all about how you did.");
|
||||||
|
ctx.print_color_centered(18, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), "That day, sadly, is not in this chapter..");
|
||||||
|
|
||||||
|
ctx.print_color_centered(20, RGB::named(rltk::MAGENTA), RGB::named(rltk::BLACK), "Press any key to return to the menu.");
|
||||||
|
|
||||||
|
match ctx.key {
|
||||||
|
None => GameOverResult::NoSelection,
|
||||||
|
Some(_) => GameOverResult::QuitToMenu
|
||||||
|
}
|
||||||
|
}
|
@ -2,14 +2,15 @@ use specs::prelude::*;
|
|||||||
|
|
||||||
use crate::gamelog::GameLog;
|
use crate::gamelog::GameLog;
|
||||||
use crate::{
|
use crate::{
|
||||||
AreaOfEffect, CombatStats, Confusion, Consumable, InBackpack, InflictsDamage, Map, Name,
|
AreaOfEffect, CombatStats, Confusion, Consumable, Equippable, Equipped, InBackpack,
|
||||||
Position, ProvidesHealing, Ranged, SufferDamage, WantsToDropItem, WantsToPickupItem,
|
InflictsDamage, Map, Name, Position, ProvidesHealing, Ranged, SufferDamage, WantsToDropItem,
|
||||||
WantsToUseItem,
|
WantsToPickupItem, WantsToRemoveItem, WantsToUseItem,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct ItemCollectionSystem {}
|
pub struct ItemCollectionSystem {}
|
||||||
|
|
||||||
impl<'a> System<'a> for ItemCollectionSystem {
|
impl<'a> System<'a> for ItemCollectionSystem {
|
||||||
|
#[allow(clippy::complexity)]
|
||||||
type SystemData = (
|
type SystemData = (
|
||||||
ReadExpect<'a, Entity>,
|
ReadExpect<'a, Entity>,
|
||||||
WriteExpect<'a, GameLog>,
|
WriteExpect<'a, GameLog>,
|
||||||
@ -49,6 +50,7 @@ impl<'a> System<'a> for ItemCollectionSystem {
|
|||||||
pub struct ItemUseSystem {}
|
pub struct ItemUseSystem {}
|
||||||
|
|
||||||
impl<'a> System<'a> for ItemUseSystem {
|
impl<'a> System<'a> for ItemUseSystem {
|
||||||
|
#[allow(clippy::complexity)]
|
||||||
type SystemData = (
|
type SystemData = (
|
||||||
ReadExpect<'a, Entity>,
|
ReadExpect<'a, Entity>,
|
||||||
WriteExpect<'a, GameLog>,
|
WriteExpect<'a, GameLog>,
|
||||||
@ -64,6 +66,9 @@ impl<'a> System<'a> for ItemUseSystem {
|
|||||||
WriteStorage<'a, SufferDamage>,
|
WriteStorage<'a, SufferDamage>,
|
||||||
ReadStorage<'a, AreaOfEffect>,
|
ReadStorage<'a, AreaOfEffect>,
|
||||||
WriteStorage<'a, Confusion>,
|
WriteStorage<'a, Confusion>,
|
||||||
|
ReadStorage<'a, Equippable>,
|
||||||
|
WriteStorage<'a, Equipped>,
|
||||||
|
WriteStorage<'a, InBackpack>,
|
||||||
);
|
);
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
fn run(&mut self, data: Self::SystemData) {
|
||||||
@ -82,6 +87,9 @@ impl<'a> System<'a> for ItemUseSystem {
|
|||||||
mut suffer_damage,
|
mut suffer_damage,
|
||||||
aoe,
|
aoe,
|
||||||
mut confused,
|
mut confused,
|
||||||
|
equippable,
|
||||||
|
mut equipped,
|
||||||
|
mut backpack,
|
||||||
) = data;
|
) = data;
|
||||||
|
|
||||||
for (entity, use_item) in (&entities, &wants_use).join() {
|
for (entity, use_item) in (&entities, &wants_use).join() {
|
||||||
@ -116,6 +124,43 @@ impl<'a> System<'a> for ItemUseSystem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(item_equippable) = equippable.get(use_item.item) {
|
||||||
|
let target_slot = item_equippable.slot;
|
||||||
|
let target = targets[0];
|
||||||
|
let mut to_unequip: Vec<Entity> = Vec::new();
|
||||||
|
for (item_entity, already_equipped, name) in (&entities, &equipped, &names).join() {
|
||||||
|
if already_equipped.owner == target && already_equipped.slot == target_slot {
|
||||||
|
to_unequip.push(item_entity);
|
||||||
|
if target == *player_entity {
|
||||||
|
game_log.entries.push(format!("You unequip {}.", name.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for item in to_unequip.iter() {
|
||||||
|
equipped.remove(*item);
|
||||||
|
backpack
|
||||||
|
.insert(*item, InBackpack { owner: target })
|
||||||
|
.expect("Unable to insert item into backpack");
|
||||||
|
}
|
||||||
|
|
||||||
|
equipped
|
||||||
|
.insert(
|
||||||
|
use_item.item,
|
||||||
|
Equipped {
|
||||||
|
owner: target,
|
||||||
|
slot: target_slot,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("Unable to equip item");
|
||||||
|
backpack.remove(use_item.item);
|
||||||
|
if target == *player_entity {
|
||||||
|
game_log.entries.push(format!(
|
||||||
|
"You equip item {}.",
|
||||||
|
names.get(use_item.item).unwrap().name
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(item_damages) = inflicts_damage.get(use_item.item) {
|
if let Some(item_damages) = inflicts_damage.get(use_item.item) {
|
||||||
used_item = false;
|
used_item = false;
|
||||||
for mob in targets.iter() {
|
for mob in targets.iter() {
|
||||||
@ -242,3 +287,26 @@ impl<'a> System<'a> for ItemDropSystem {
|
|||||||
wants_drop.clear();
|
wants_drop.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ItemRemoveSystem {}
|
||||||
|
|
||||||
|
impl<'a> System<'a> for ItemRemoveSystem {
|
||||||
|
type SystemData = (
|
||||||
|
Entities<'a>,
|
||||||
|
WriteStorage<'a, WantsToRemoveItem>,
|
||||||
|
WriteStorage<'a, Equipped>,
|
||||||
|
WriteStorage<'a, InBackpack>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fn run(&mut self, data: Self::SystemData) {
|
||||||
|
let (entities, mut wants_to_remove_item, mut equipped, mut backpack) = data;
|
||||||
|
for (entity, to_remove) in (&entities, &wants_to_remove_item).join() {
|
||||||
|
equipped.remove(to_remove.item);
|
||||||
|
backpack
|
||||||
|
.insert(to_remove.item, InBackpack { owner: entity })
|
||||||
|
.expect("Unable to insert item into backpack");
|
||||||
|
}
|
||||||
|
|
||||||
|
wants_to_remove_item.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
96
src/main.rs
96
src/main.rs
@ -14,7 +14,7 @@ use player::*;
|
|||||||
use visibility_system::*;
|
use visibility_system::*;
|
||||||
|
|
||||||
use crate::gamelog::GameLog;
|
use crate::gamelog::GameLog;
|
||||||
use crate::inventory_system::{ItemCollectionSystem, ItemDropSystem, ItemUseSystem};
|
use crate::inventory_system::{ItemCollectionSystem, ItemDropSystem, ItemRemoveSystem, ItemUseSystem};
|
||||||
|
|
||||||
mod components;
|
mod components;
|
||||||
mod damage_system;
|
mod damage_system;
|
||||||
@ -49,25 +49,75 @@ pub enum RunState {
|
|||||||
},
|
},
|
||||||
SaveGame,
|
SaveGame,
|
||||||
NextLevel,
|
NextLevel,
|
||||||
|
ShowRemoveItem,
|
||||||
|
GameOver,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct State {
|
pub struct State {
|
||||||
pub ecs: World,
|
pub ecs: World,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn game_over_cleanup(&mut self) {
|
||||||
|
// Delete everything
|
||||||
|
let mut to_delete = Vec::new();
|
||||||
|
for e in self.ecs.entities().join() {
|
||||||
|
to_delete.push(e);
|
||||||
|
}
|
||||||
|
for del in to_delete.iter() {
|
||||||
|
self.ecs.delete_entity(*del).expect("Deletion failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a new map and place the player
|
||||||
|
let worldmap;
|
||||||
|
{
|
||||||
|
let mut worldmap_resource = self.ecs.write_resource::<Map>();
|
||||||
|
*worldmap_resource = Map::new_map_rooms_and_corridors(1);
|
||||||
|
worldmap = worldmap_resource.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spawn bad guys
|
||||||
|
for room in worldmap.rooms.iter().skip(1) {
|
||||||
|
spawner::spawn_room(&mut self.ecs, room, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Place the player and update resources
|
||||||
|
let (player_x, player_y) = worldmap.rooms[0].center();
|
||||||
|
let player_entity = spawner::player(&mut self.ecs, player_x, player_y);
|
||||||
|
let mut player_position = self.ecs.write_resource::<Point>();
|
||||||
|
*player_position = Point::new(player_x, player_y);
|
||||||
|
let mut position_components = self.ecs.write_storage::<Position>();
|
||||||
|
let mut player_entity_writer = self.ecs.write_resource::<Entity>();
|
||||||
|
*player_entity_writer = player_entity;
|
||||||
|
let player_pos_comp = position_components.get_mut(player_entity);
|
||||||
|
if let Some(player_pos_comp) = player_pos_comp {
|
||||||
|
player_pos_comp.x = player_x;
|
||||||
|
player_pos_comp.y = player_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark the player's visibility as dirty
|
||||||
|
let mut viewshed_components = self.ecs.write_storage::<Viewshed>();
|
||||||
|
let vs = viewshed_components.get_mut(player_entity);
|
||||||
|
if let Some(vs) = vs {
|
||||||
|
vs.dirty = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
fn entities_to_remove_on_level_change(&mut self) -> Vec<Entity> {
|
fn entities_to_remove_on_level_change(&mut self) -> Vec<Entity> {
|
||||||
let entities = self.ecs.entities();
|
let entities = self.ecs.entities();
|
||||||
let player = self.ecs.read_storage::<Player>();
|
let player = self.ecs.read_storage::<Player>();
|
||||||
let backpack = self.ecs.read_storage::<InBackpack>();
|
let backpack = self.ecs.read_storage::<InBackpack>();
|
||||||
let player_entity = self.ecs.fetch::<Entity>();
|
let player_entity = self.ecs.fetch::<Entity>();
|
||||||
|
let equipped = self.ecs.read_storage::<Equipped>();
|
||||||
|
|
||||||
let mut to_delete: Vec<Entity> = Vec::new();
|
let mut to_delete: Vec<Entity> = Vec::new();
|
||||||
for entity in entities.join() {
|
for entity in entities.join() {
|
||||||
let mut should_delete = true;
|
let mut should_delete = true;
|
||||||
|
|
||||||
let p = player.get(entity);
|
let p = player.get(entity);
|
||||||
if let Some(p) = p {
|
if let Some(_p) = p {
|
||||||
should_delete = false;
|
should_delete = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,6 +128,12 @@ impl State {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(eq) = equipped.get(entity) {
|
||||||
|
if eq.owner == *player_entity {
|
||||||
|
should_delete = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if should_delete {
|
if should_delete {
|
||||||
to_delete.push(entity);
|
to_delete.push(entity);
|
||||||
}
|
}
|
||||||
@ -162,6 +218,9 @@ impl State {
|
|||||||
let mut drop_items = ItemDropSystem {};
|
let mut drop_items = ItemDropSystem {};
|
||||||
drop_items.run_now(&self.ecs);
|
drop_items.run_now(&self.ecs);
|
||||||
|
|
||||||
|
let mut remove_items = ItemRemoveSystem {};
|
||||||
|
remove_items.run_now(&self.ecs);
|
||||||
|
|
||||||
self.ecs.maintain();
|
self.ecs.maintain();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -309,6 +368,34 @@ impl GameState for State {
|
|||||||
self.goto_next_level();
|
self.goto_next_level();
|
||||||
new_run_state = RunState::PreRun;
|
new_run_state = RunState::PreRun;
|
||||||
}
|
}
|
||||||
|
RunState::ShowRemoveItem => {
|
||||||
|
let result = gui::remove_item_menu(self, ctx);
|
||||||
|
match result.0 {
|
||||||
|
gui::ItemMenuResult::Cancel => new_run_state = 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_run_state = RunState::PlayerTurn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RunState::GameOver => {
|
||||||
|
let result = gui::game_over(ctx);
|
||||||
|
match result {
|
||||||
|
gui::GameOverResult::NoSelection => {}
|
||||||
|
gui::GameOverResult::QuitToMenu => {
|
||||||
|
self.game_over_cleanup();
|
||||||
|
new_run_state = RunState::MainMenu { menu_selection: gui::MainMenuSelection::NewGame };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -354,6 +441,11 @@ fn main() -> rltk::BError {
|
|||||||
gs.ecs.register::<Confusion>();
|
gs.ecs.register::<Confusion>();
|
||||||
gs.ecs.register::<SimpleMarker<SerializeMe>>();
|
gs.ecs.register::<SimpleMarker<SerializeMe>>();
|
||||||
gs.ecs.register::<SerializationHelper>();
|
gs.ecs.register::<SerializationHelper>();
|
||||||
|
gs.ecs.register::<Equippable>();
|
||||||
|
gs.ecs.register::<Equipped>();
|
||||||
|
gs.ecs.register::<MeleePowerBonus>();
|
||||||
|
gs.ecs.register::<DefenseBonus>();
|
||||||
|
gs.ecs.register::<WantsToRemoveItem>();
|
||||||
|
|
||||||
gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new());
|
gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new());
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ use specs::prelude::*;
|
|||||||
|
|
||||||
use crate::components::{CombatStats, Name, SufferDamage, WantsToMelee};
|
use crate::components::{CombatStats, Name, SufferDamage, WantsToMelee};
|
||||||
use crate::gamelog::GameLog;
|
use crate::gamelog::GameLog;
|
||||||
|
use crate::{DefenseBonus, Equipped, MeleePowerBonus};
|
||||||
|
|
||||||
pub struct MeleeCombatSystem {}
|
pub struct MeleeCombatSystem {}
|
||||||
|
|
||||||
@ -13,20 +14,54 @@ impl<'a> System<'a> for MeleeCombatSystem {
|
|||||||
ReadStorage<'a, Name>,
|
ReadStorage<'a, Name>,
|
||||||
ReadStorage<'a, CombatStats>,
|
ReadStorage<'a, CombatStats>,
|
||||||
WriteStorage<'a, SufferDamage>,
|
WriteStorage<'a, SufferDamage>,
|
||||||
|
ReadStorage<'a, MeleePowerBonus>,
|
||||||
|
ReadStorage<'a, DefenseBonus>,
|
||||||
|
ReadStorage<'a, Equipped>,
|
||||||
);
|
);
|
||||||
|
|
||||||
fn run(&mut self, data: Self::SystemData) {
|
fn run(&mut self, data: Self::SystemData) {
|
||||||
let (entities, mut log, mut wants_melee, names, combat_stats, mut inflict_damage) = data;
|
let (
|
||||||
|
entities,
|
||||||
|
mut log,
|
||||||
|
mut wants_melee,
|
||||||
|
names,
|
||||||
|
combat_stats,
|
||||||
|
mut inflict_damage,
|
||||||
|
melee_bonus,
|
||||||
|
defense_bonus,
|
||||||
|
equipped,
|
||||||
|
) = data;
|
||||||
|
|
||||||
for (_entity, wants_melee, name, stats) in
|
for (entity, wants_melee, name, stats) in
|
||||||
(&entities, &wants_melee, &names, &combat_stats).join()
|
(&entities, &wants_melee, &names, &combat_stats).join()
|
||||||
{
|
{
|
||||||
if stats.hp > 0 {
|
if stats.hp > 0 {
|
||||||
|
let mut offensive_bonus = 0;
|
||||||
|
for (_item_entity, power_bonus, equipped_by) in
|
||||||
|
(&entities, &melee_bonus, &equipped).join()
|
||||||
|
{
|
||||||
|
if equipped_by.owner == entity {
|
||||||
|
offensive_bonus += power_bonus.power;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let target_stats = combat_stats.get(wants_melee.target).unwrap();
|
let target_stats = combat_stats.get(wants_melee.target).unwrap();
|
||||||
if target_stats.hp > 0 {
|
if target_stats.hp > 0 {
|
||||||
let target_name = names.get(wants_melee.target).unwrap();
|
let target_name = names.get(wants_melee.target).unwrap();
|
||||||
|
|
||||||
let damage = i32::max(0, stats.power - target_stats.defense);
|
let mut defensive_bonus = 0;
|
||||||
|
for (_item_entity, defense_bonus, equipped_by) in
|
||||||
|
(&entities, &defense_bonus, &equipped).join()
|
||||||
|
{
|
||||||
|
if equipped_by.owner == wants_melee.target {
|
||||||
|
defensive_bonus += defense_bonus.defense;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let damage = i32::max(
|
||||||
|
0,
|
||||||
|
(stats.power + offensive_bonus) - (target_stats.defense + defensive_bonus),
|
||||||
|
);
|
||||||
|
|
||||||
if damage == 0 {
|
if damage == 0 {
|
||||||
log.entries.push(format!(
|
log.entries.push(format!(
|
||||||
|
@ -109,6 +109,7 @@ pub fn player_input(gs: &mut State, ctx: &mut Rltk) -> RunState {
|
|||||||
VirtualKeyCode::I => return RunState::ShowInventory,
|
VirtualKeyCode::I => return RunState::ShowInventory,
|
||||||
VirtualKeyCode::D => return RunState::ShowDropItem,
|
VirtualKeyCode::D => return RunState::ShowDropItem,
|
||||||
VirtualKeyCode::Escape => return RunState::SaveGame,
|
VirtualKeyCode::Escape => return RunState::SaveGame,
|
||||||
|
VirtualKeyCode::R => return RunState::ShowRemoveItem,
|
||||||
VirtualKeyCode::Period => {
|
VirtualKeyCode::Period => {
|
||||||
if try_next_level(&mut gs.ecs) {
|
if try_next_level(&mut gs.ecs) {
|
||||||
return RunState::NextLevel;
|
return RunState::NextLevel;
|
||||||
@ -132,7 +133,7 @@ fn skip_turn(ecs: &mut World) -> RunState {
|
|||||||
for tile in viewshed.visible_tiles.iter() {
|
for tile in viewshed.visible_tiles.iter() {
|
||||||
let idx = worldmap_resource.xy_idx(tile.x, tile.y);
|
let idx = worldmap_resource.xy_idx(tile.x, tile.y);
|
||||||
for entity_id in worldmap_resource.tile_content[idx].iter() {
|
for entity_id in worldmap_resource.tile_content[idx].iter() {
|
||||||
if let Some(mob) = monsters.get(*entity_id) {
|
if let Some(_mob) = monsters.get(*entity_id) {
|
||||||
can_heal = true;
|
can_heal = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,8 +29,10 @@ impl RandomTable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn add<S: ToString>(mut self, name: S, weight: i32) -> RandomTable {
|
pub fn add<S: ToString>(mut self, name: S, weight: i32) -> RandomTable {
|
||||||
self.total_weight += weight;
|
if weight > 0 {
|
||||||
self.entries.push(RandomEntry::new(name, weight));
|
self.total_weight += weight;
|
||||||
|
self.entries.push(RandomEntry::new(name, weight));
|
||||||
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,7 +71,12 @@ pub fn save_game(ecs: &mut World) {
|
|||||||
WantsToPickupItem,
|
WantsToPickupItem,
|
||||||
WantsToUseItem,
|
WantsToUseItem,
|
||||||
WantsToDropItem,
|
WantsToDropItem,
|
||||||
SerializationHelper
|
SerializationHelper,
|
||||||
|
Equippable,
|
||||||
|
Equipped,
|
||||||
|
MeleePowerBonus,
|
||||||
|
DefenseBonus,
|
||||||
|
WantsToRemoveItem
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,7 +150,12 @@ pub fn load_game(ecs: &mut World) {
|
|||||||
WantsToPickupItem,
|
WantsToPickupItem,
|
||||||
WantsToUseItem,
|
WantsToUseItem,
|
||||||
WantsToDropItem,
|
WantsToDropItem,
|
||||||
SerializationHelper
|
SerializationHelper,
|
||||||
|
Equippable,
|
||||||
|
Equipped,
|
||||||
|
MeleePowerBonus,
|
||||||
|
DefenseBonus,
|
||||||
|
WantsToRemoveItem
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
194
src/spawner.rs
194
src/spawner.rs
@ -7,9 +7,9 @@ use specs::saveload::{MarkedBuilder, SimpleMarker};
|
|||||||
use crate::random_table::RandomTable;
|
use crate::random_table::RandomTable;
|
||||||
use crate::rect::Rect;
|
use crate::rect::Rect;
|
||||||
use crate::{
|
use crate::{
|
||||||
AreaOfEffect, BlocksTile, CombatStats, Confusion, Consumable, InflictsDamage, Item, Monster,
|
AreaOfEffect, BlocksTile, CombatStats, Confusion, Consumable, DefenseBonus, EquipmentSlot,
|
||||||
Name, Player, Position, ProvidesHealing, Ranged, Renderable, SerializeMe, Viewshed, MAP_WIDTH,
|
Equippable, InflictsDamage, Item, MeleePowerBonus, Monster, Name, Player, Position,
|
||||||
MAX_ITEMS, MAX_MONSTER,
|
ProvidesHealing, Ranged, Renderable, SerializeMe, Viewshed, MAP_WIDTH, MAX_ITEMS, MAX_MONSTER,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity {
|
pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity {
|
||||||
@ -117,6 +117,14 @@ pub fn spawn_room(ecs: &mut World, room: &Rect, map_depth: i32) {
|
|||||||
"Fireball Scroll" => fireball_scroll(ecs, x, y),
|
"Fireball Scroll" => fireball_scroll(ecs, x, y),
|
||||||
"Confusion Scroll" => confusion_scroll(ecs, x, y),
|
"Confusion Scroll" => confusion_scroll(ecs, x, y),
|
||||||
"Magic Missile Scroll" => magic_missile_scroll(ecs, x, y),
|
"Magic Missile Scroll" => magic_missile_scroll(ecs, x, y),
|
||||||
|
"Dagger" => dagger(ecs, x, y),
|
||||||
|
"Longsword" => longsword(ecs, x, y),
|
||||||
|
"Shield" => shield(ecs, x, y),
|
||||||
|
"Tower Shield" => tower_shield(ecs, x, y),
|
||||||
|
"Helmet" => helmet(ecs, x, y),
|
||||||
|
"Breastplate" => breastplate(ecs, x, y),
|
||||||
|
"Leggings" => leggings(ecs, x, y),
|
||||||
|
"Sabatons" => sabatons(ecs, x, y),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -210,4 +218,184 @@ pub fn room_table(map_depth: i32) -> RandomTable {
|
|||||||
.add("Fireball Scroll", 2 + map_depth)
|
.add("Fireball Scroll", 2 + map_depth)
|
||||||
.add("Confusion Scroll", 2 + map_depth)
|
.add("Confusion Scroll", 2 + map_depth)
|
||||||
.add("Magic Missile Scroll", 4)
|
.add("Magic Missile Scroll", 4)
|
||||||
|
.add("Dagger", 3)
|
||||||
|
.add("Longsword", map_depth - 1)
|
||||||
|
.add("Shield", 3)
|
||||||
|
.add("Tower Shield", map_depth - 1)
|
||||||
|
.add("Helmet", map_depth - 2)
|
||||||
|
.add("Breastplate", map_depth - 3)
|
||||||
|
.add("Leggings", map_depth - 4)
|
||||||
|
.add("Sabatons", map_depth - 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dagger(ecs: &mut World, x: i32, y: i32) {
|
||||||
|
ecs.create_entity()
|
||||||
|
.with(Position { x, y })
|
||||||
|
.with(Renderable {
|
||||||
|
glyph: rltk::to_cp437('/'),
|
||||||
|
render_order: 2,
|
||||||
|
fg: RGB::named(rltk::CYAN),
|
||||||
|
bg: RGB::named(rltk::BLACK),
|
||||||
|
})
|
||||||
|
.with(Item {})
|
||||||
|
.with(Name {
|
||||||
|
name: "Dagger".to_string(),
|
||||||
|
})
|
||||||
|
.with(Equippable {
|
||||||
|
slot: EquipmentSlot::Melee,
|
||||||
|
})
|
||||||
|
.with(MeleePowerBonus { power: 2 })
|
||||||
|
.marked::<SimpleMarker<SerializeMe>>()
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn longsword(ecs: &mut World, x: i32, y: i32) {
|
||||||
|
ecs.create_entity()
|
||||||
|
.with(Position { x, y })
|
||||||
|
.with(Renderable {
|
||||||
|
glyph: rltk::to_cp437('/'),
|
||||||
|
render_order: 2,
|
||||||
|
fg: RGB::named(rltk::CYAN),
|
||||||
|
bg: RGB::named(rltk::BLACK),
|
||||||
|
})
|
||||||
|
.with(Item {})
|
||||||
|
.with(Name {
|
||||||
|
name: "Longsword".to_string(),
|
||||||
|
})
|
||||||
|
.with(Equippable {
|
||||||
|
slot: EquipmentSlot::Melee,
|
||||||
|
})
|
||||||
|
.with(MeleePowerBonus { power: 4 })
|
||||||
|
.marked::<SimpleMarker<SerializeMe>>()
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shield(ecs: &mut World, x: i32, y: i32) {
|
||||||
|
ecs.create_entity()
|
||||||
|
.with(Position { x, y })
|
||||||
|
.with(Renderable {
|
||||||
|
glyph: rltk::to_cp437('('),
|
||||||
|
render_order: 2,
|
||||||
|
fg: RGB::named(rltk::CYAN),
|
||||||
|
bg: RGB::named(rltk::BLACK),
|
||||||
|
})
|
||||||
|
.with(Item {})
|
||||||
|
.with(Name {
|
||||||
|
name: "Shield".to_string(),
|
||||||
|
})
|
||||||
|
.with(Equippable {
|
||||||
|
slot: EquipmentSlot::Shield,
|
||||||
|
})
|
||||||
|
.with(DefenseBonus { defense: 1 })
|
||||||
|
.marked::<SimpleMarker<SerializeMe>>()
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn tower_shield(ecs: &mut World, x: i32, y: i32) {
|
||||||
|
ecs.create_entity()
|
||||||
|
.with(Position { x, y })
|
||||||
|
.with(Renderable {
|
||||||
|
glyph: rltk::to_cp437('('),
|
||||||
|
render_order: 2,
|
||||||
|
fg: RGB::named(rltk::CYAN),
|
||||||
|
bg: RGB::named(rltk::BLACK),
|
||||||
|
})
|
||||||
|
.with(Item {})
|
||||||
|
.with(Name {
|
||||||
|
name: "Tower Shield".to_string(),
|
||||||
|
})
|
||||||
|
.with(Equippable {
|
||||||
|
slot: EquipmentSlot::Shield,
|
||||||
|
})
|
||||||
|
.with(DefenseBonus { defense: 3 })
|
||||||
|
.marked::<SimpleMarker<SerializeMe>>()
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn helmet(ecs: &mut World, x: i32, y: i32) {
|
||||||
|
ecs.create_entity()
|
||||||
|
.with(Position { x, y })
|
||||||
|
.with(Renderable {
|
||||||
|
glyph: rltk::to_cp437('^'),
|
||||||
|
render_order: 2,
|
||||||
|
fg: RGB::named(rltk::CYAN),
|
||||||
|
bg: RGB::named(rltk::BLACK),
|
||||||
|
})
|
||||||
|
.with(Item {})
|
||||||
|
.with(Name {
|
||||||
|
name: "Helmet".to_string(),
|
||||||
|
})
|
||||||
|
.with(Equippable {
|
||||||
|
slot: EquipmentSlot::Head,
|
||||||
|
})
|
||||||
|
.with(DefenseBonus { defense: 1 })
|
||||||
|
.with(MeleePowerBonus { power: 1 })
|
||||||
|
.marked::<SimpleMarker<SerializeMe>>()
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn breastplate(ecs: &mut World, x: i32, y: i32) {
|
||||||
|
ecs.create_entity()
|
||||||
|
.with(Position { x, y })
|
||||||
|
.with(Renderable {
|
||||||
|
glyph: rltk::to_cp437('x'),
|
||||||
|
render_order: 2,
|
||||||
|
fg: RGB::named(rltk::CYAN),
|
||||||
|
bg: RGB::named(rltk::BLACK),
|
||||||
|
})
|
||||||
|
.with(Item {})
|
||||||
|
.with(Name {
|
||||||
|
name: "Breastplate".to_string(),
|
||||||
|
})
|
||||||
|
.with(Equippable {
|
||||||
|
slot: EquipmentSlot::Chest,
|
||||||
|
})
|
||||||
|
.with(DefenseBonus { defense: 2 })
|
||||||
|
.marked::<SimpleMarker<SerializeMe>>()
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn leggings(ecs: &mut World, x: i32, y: i32) {
|
||||||
|
ecs.create_entity()
|
||||||
|
.with(Position { x, y })
|
||||||
|
.with(Renderable {
|
||||||
|
glyph: rltk::to_cp437('"'),
|
||||||
|
render_order: 2,
|
||||||
|
fg: RGB::named(rltk::CYAN),
|
||||||
|
bg: RGB::named(rltk::BLACK),
|
||||||
|
})
|
||||||
|
.with(Item {})
|
||||||
|
.with(Name {
|
||||||
|
name: "Leggings".to_string(),
|
||||||
|
})
|
||||||
|
.with(Equippable {
|
||||||
|
slot: EquipmentSlot::Legs,
|
||||||
|
})
|
||||||
|
.with(DefenseBonus { defense: 1 })
|
||||||
|
.marked::<SimpleMarker<SerializeMe>>()
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sabatons(ecs: &mut World, x: i32, y: i32) {
|
||||||
|
ecs.create_entity()
|
||||||
|
.with(Position { x, y })
|
||||||
|
.with(Renderable {
|
||||||
|
glyph: rltk::to_cp437(','),
|
||||||
|
render_order: 2,
|
||||||
|
fg: RGB::named(rltk::CYAN),
|
||||||
|
bg: RGB::named(rltk::BLACK),
|
||||||
|
})
|
||||||
|
.with(Item {})
|
||||||
|
.with(Name {
|
||||||
|
name: "Sabatons".to_string(),
|
||||||
|
})
|
||||||
|
.with(Equippable {
|
||||||
|
slot: EquipmentSlot::Feet,
|
||||||
|
})
|
||||||
|
.with(DefenseBonus { defense: 1 })
|
||||||
|
.with(MeleePowerBonus { power: 1 })
|
||||||
|
.marked::<SimpleMarker<SerializeMe>>()
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user