Add hunger

This commit is contained in:
Kasper Juul Hermansen 2022-01-29 00:31:00 +01:00
parent cb17a9c356
commit 74c05f97af
Signed by: kjuulh
GPG Key ID: DCD9397082D97069
10 changed files with 280 additions and 74 deletions

View File

@ -109,6 +109,25 @@ pub struct ProvidesHealing {
pub heal_amount: i32, pub heal_amount: i32,
} }
#[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct Heals {
pub amount: Vec<i32>,
}
impl Heals {
pub fn new_healing(store: &mut WriteStorage<Heals>, target: Entity, amount: i32) {
if let Some(healing) = store.get_mut(target) {
healing.amount.push(amount);
} else {
let heal = Heals {
amount: vec![amount],
};
store.insert(target, heal).expect("Unable to insert heal");
}
}
}
#[derive(Component, Debug, Clone, ConvertSaveload)] #[derive(Component, Debug, Clone, ConvertSaveload)]
pub struct Ranged { pub struct Ranged {
pub range: i32, pub range: i32,
@ -178,3 +197,21 @@ pub struct WantsToRemoveItem {
pub struct ParticleLifetime { pub struct ParticleLifetime {
pub lifetime_ms: f32, pub lifetime_ms: f32,
} }
#[derive(PartialEq, Serialize, Deserialize, Clone, Copy)]
pub enum HungerState {
WellFed,
Normal,
Hungry,
Starving,
}
#[derive(Component, Serialize, Deserialize, Clone)]
pub struct HungerClock {
pub state: HungerState,
pub duration: i32,
}
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct ProvidesFood {}

View File

@ -1,14 +1,14 @@
use rltk::Rltk;
use rltk::RGB;
use rltk::{Point, VirtualKeyCode}; use rltk::{Point, VirtualKeyCode};
use rltk::RGB;
use rltk::Rltk;
use specs::prelude::*; use specs::prelude::*;
use crate::gamelog::GameLog; use crate::{HungerClock, HungerState, HungerSystem, Name};
use crate::Name;
use crate::Player;
use crate::Position;
use crate::{CombatStats, InBackpack, RunState, State, Viewshed}; use crate::{CombatStats, InBackpack, RunState, State, Viewshed};
use crate::{Equipped, Map}; use crate::{Equipped, Map};
use crate::gamelog::GameLog;
use crate::Player;
use crate::Position;
pub fn draw_ui(ecs: &World, ctx: &mut Rltk) { pub fn draw_ui(ecs: &World, ctx: &mut Rltk) {
ctx.draw_box( ctx.draw_box(
@ -22,9 +22,10 @@ pub fn draw_ui(ecs: &World, ctx: &mut Rltk) {
let combat_stats = ecs.read_storage::<CombatStats>(); let combat_stats = ecs.read_storage::<CombatStats>();
let players = ecs.read_storage::<Player>(); let players = ecs.read_storage::<Player>();
let hunger = ecs.read_storage::<HungerClock>();
let log = ecs.fetch::<GameLog>(); let log = ecs.fetch::<GameLog>();
for (_player, stats) in (&players, &combat_stats).join() { for (_player, stats, hc) in (&players, &combat_stats, &hunger).join() {
let health = format!(" HP: {} / {}", stats.hp, stats.max_hp); let health = format!(" HP: {} / {}", stats.hp, stats.max_hp);
ctx.print_color( ctx.print_color(
12, 12,
@ -43,8 +44,16 @@ pub fn draw_ui(ecs: &World, ctx: &mut Rltk) {
RGB::named(rltk::RED), RGB::named(rltk::RED),
RGB::named(rltk::BLACK), RGB::named(rltk::BLACK),
); );
match hc.state {
HungerState::WellFed => ctx.print_color(71, 42, RGB::named(rltk::GREEN), RGB::named(rltk::BLACK), "Well Fed"),
HungerState::Normal => {}
HungerState::Hungry => ctx.print_color(71, 42, RGB::named(rltk::ORANGE), RGB::named(rltk::BLACK), "Hungry"),
HungerState::Starving => ctx.print_color(71, 42, RGB::named(rltk::RED), RGB::named(rltk::BLACK), "Starving"),
}
} }
let mut y = 44; let mut y = 44;
for s in log.entries.iter().rev() { for s in log.entries.iter().rev() {
if y < 49 { if y < 49 {

22
src/healing_system.rs Normal file
View File

@ -0,0 +1,22 @@
use specs::prelude::*;
use crate::{CombatStats, GameLog, Heals};
pub struct HealingSystem {}
impl<'a> System<'a> for HealingSystem {
type SystemData = (
WriteStorage<'a, Heals>,
WriteStorage<'a, CombatStats>,
);
fn run(&mut self, data: Self::SystemData) {
let (mut heals, mut stats) = data;
for (heal, mut stats) in (&heals, &mut stats).join() {
stats.hp = i32::min(stats.max_hp, stats.hp + heal.amount.iter().sum::<i32>());
}
heals.clear();
}
}

71
src/hunger_system.rs Normal file
View File

@ -0,0 +1,71 @@
use specs::prelude::*;
use crate::{GameLog, Heals, HungerClock, HungerState, ProvidesHealing, RunState, SufferDamage};
use crate::spawner::player;
pub struct HungerSystem {}
impl<'a> System<'a> for HungerSystem {
type SystemData = (
Entities<'a>,
WriteStorage<'a, HungerClock>,
ReadExpect<'a, Entity>,
ReadExpect<'a, RunState>,
WriteStorage<'a, SufferDamage>,
WriteExpect<'a, GameLog>,
WriteStorage<'a, Heals>
);
fn run(&mut self, data: Self::SystemData) {
let (entities, mut hunger_clock, player_entity, run_state, mut suffer_damage, mut game_log, mut heals) = data;
for (entity, mut clock) in (&entities, &mut hunger_clock).join() {
let mut proceed = false;
match *run_state {
RunState::PlayerTurn => {
if entity == *player_entity { proceed = true; } }
RunState::MonsterTurn => { if entity != *player_entity { proceed = true; } }
_ => { proceed = false; }
}
if !proceed {
continue;
}
clock.duration -= 1;
if clock.duration > 0 {
continue;
}
match clock.state {
HungerState::WellFed => {
clock.state = HungerState::Normal;
clock.duration = 200;
if entity == *player_entity {
game_log.entries.push("You are no longer well-feed".to_string())
}
}
HungerState::Normal => {
clock.state = HungerState::Hungry;
clock.duration = 200;
if entity == *player_entity {
game_log.entries.push("You are hungry".to_string())
}
}
HungerState::Hungry => {
clock.state = HungerState::Starving;
clock.duration = 200;
if entity == *player_entity {
game_log.entries.push("You are Starving!".to_string())
}
}
HungerState::Starving => {
if entity == *player_entity {
game_log.entries.push("Your hunger pangs are getting painful".to_string())
}
SufferDamage::new_damage(&mut suffer_damage, entity, 1);
}
}
}
}
}

View File

@ -1,12 +1,8 @@
use specs::prelude::*; use specs::prelude::*;
use crate::{AreaOfEffect, CombatStats, Confusion, Consumable, Equippable, Equipped, HungerClock, HungerState, InBackpack, InflictsDamage, Map, Name, Position, ProvidesFood, ProvidesHealing, Ranged, SufferDamage, WantsToDropItem, WantsToPickupItem, WantsToRemoveItem, WantsToUseItem};
use crate::gamelog::GameLog; use crate::gamelog::GameLog;
use crate::particle_system::ParticleBuilder; use crate::particle_system::ParticleBuilder;
use crate::{
AreaOfEffect, CombatStats, Confusion, Consumable, Equippable, Equipped, InBackpack,
InflictsDamage, Map, Name, Position, ProvidesHealing, Ranged, SufferDamage, WantsToDropItem,
WantsToPickupItem, WantsToRemoveItem, WantsToUseItem,
};
pub struct ItemCollectionSystem {} pub struct ItemCollectionSystem {}
@ -72,6 +68,8 @@ impl<'a> System<'a> for ItemUseSystem {
WriteStorage<'a, InBackpack>, WriteStorage<'a, InBackpack>,
WriteExpect<'a, ParticleBuilder>, WriteExpect<'a, ParticleBuilder>,
ReadStorage<'a, Position>, ReadStorage<'a, Position>,
ReadStorage<'a, ProvidesFood>,
WriteStorage<'a, HungerClock>,
); );
fn run(&mut self, data: Self::SystemData) { fn run(&mut self, data: Self::SystemData) {
@ -95,6 +93,8 @@ impl<'a> System<'a> for ItemUseSystem {
mut backpack, mut backpack,
mut particle_builder, mut particle_builder,
positions, positions,
provides_food,
mut hunger_clocks
) = data; ) = data;
for (entity, use_item) in (&entities, &wants_use).join() { for (entity, use_item) in (&entities, &wants_use).join() {
@ -133,11 +133,21 @@ impl<'a> System<'a> for ItemUseSystem {
200., 200.,
); );
} }
}
} }
} }
} }
if let Some(item_edible) = provides_food.get(use_item.item) {
used_item = true;
let target = targets[0];
if let Some(hc) = hunger_clocks.get_mut(target) {
hc.state = HungerState::WellFed;
hc.duration = 20;
game_log.entries.push(format!("You eat the {}.", names.get(use_item.item).unwrap().name));
}
}
if let Some(item_equippable) = equippable.get(use_item.item) { if let Some(item_equippable) = equippable.get(use_item.item) {
let target_slot = item_equippable.slot; let target_slot = item_equippable.slot;
let target = targets[0]; let target = targets[0];

View File

@ -14,6 +14,8 @@ use player::*;
use visibility_system::*; use visibility_system::*;
use crate::gamelog::GameLog; use crate::gamelog::GameLog;
use crate::healing_system::HealingSystem;
use crate::hunger_system::HungerSystem;
use crate::inventory_system::{ use crate::inventory_system::{
ItemCollectionSystem, ItemDropSystem, ItemRemoveSystem, ItemUseSystem, ItemCollectionSystem, ItemDropSystem, ItemRemoveSystem, ItemUseSystem,
}; };
@ -35,6 +37,8 @@ mod rect;
mod save_load_system; mod save_load_system;
mod spawner; mod spawner;
mod visibility_system; mod visibility_system;
mod hunger_system;
mod healing_system;
#[derive(PartialEq, Copy, Clone)] #[derive(PartialEq, Copy, Clone)]
pub enum RunState { pub enum RunState {
@ -213,6 +217,9 @@ impl State {
let mut damage_system = DamageSystem {}; let mut damage_system = DamageSystem {};
damage_system.run_now(&self.ecs); damage_system.run_now(&self.ecs);
let mut healing_system = HealingSystem {};
healing_system.run_now(&self.ecs);
let mut inventory = ItemCollectionSystem {}; let mut inventory = ItemCollectionSystem {};
inventory.run_now(&self.ecs); inventory.run_now(&self.ecs);
@ -225,6 +232,9 @@ impl State {
let mut remove_items = ItemRemoveSystem {}; let mut remove_items = ItemRemoveSystem {};
remove_items.run_now(&self.ecs); remove_items.run_now(&self.ecs);
let mut hunger = HungerSystem {};
hunger.run_now(&self.ecs);
let mut particle_spawn = ParticleSpawnSystem {}; let mut particle_spawn = ParticleSpawnSystem {};
particle_spawn.run_now(&self.ecs); particle_spawn.run_now(&self.ecs);
@ -458,6 +468,9 @@ fn main() -> rltk::BError {
gs.ecs.register::<DefenseBonus>(); gs.ecs.register::<DefenseBonus>();
gs.ecs.register::<WantsToRemoveItem>(); gs.ecs.register::<WantsToRemoveItem>();
gs.ecs.register::<ParticleLifetime>(); gs.ecs.register::<ParticleLifetime>();
gs.ecs.register::<HungerClock>();
gs.ecs.register::<Heals>();
gs.ecs.register::<ProvidesFood>();
gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new()); gs.ecs.insert(SimpleMarkerAllocator::<SerializeMe>::new());

View File

@ -1,9 +1,9 @@
use specs::prelude::*; use specs::prelude::*;
use crate::{DefenseBonus, Equipped, HungerClock, HungerState, MeleePowerBonus, Position};
use crate::components::{CombatStats, Name, SufferDamage, WantsToMelee}; use crate::components::{CombatStats, Name, SufferDamage, WantsToMelee};
use crate::gamelog::GameLog; use crate::gamelog::GameLog;
use crate::particle_system::ParticleBuilder; use crate::particle_system::ParticleBuilder;
use crate::{DefenseBonus, Equipped, MeleePowerBonus, Position};
pub struct MeleeCombatSystem {} pub struct MeleeCombatSystem {}
@ -21,6 +21,7 @@ impl<'a> System<'a> for MeleeCombatSystem {
ReadStorage<'a, Equipped>, ReadStorage<'a, Equipped>,
WriteExpect<'a, ParticleBuilder>, WriteExpect<'a, ParticleBuilder>,
ReadStorage<'a, Position>, ReadStorage<'a, Position>,
ReadStorage<'a, HungerClock>
); );
fn run(&mut self, data: Self::SystemData) { fn run(&mut self, data: Self::SystemData) {
@ -36,62 +37,71 @@ impl<'a> System<'a> for MeleeCombatSystem {
equipped, equipped,
mut particle_builder, mut particle_builder,
positions, positions,
hunger_clock
) = data; ) = 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; continue;
for (_item_entity, power_bonus, equipped_by) in }
(&entities, &melee_bonus, &equipped).join()
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;
}
}
if let Some(hc) = hunger_clock.get(entity) {
if hc.state == HungerState::WellFed {
offensive_bonus += 1;
}
}
let target_stats = combat_stats.get(wants_melee.target).unwrap();
if target_stats.hp > 0 {
let target_name = names.get(wants_melee.target).unwrap();
let mut defensive_bonus = 0;
for (_item_entity, defense_bonus, equipped_by) in
(&entities, &defense_bonus, &equipped).join()
{ {
if equipped_by.owner == entity { if equipped_by.owner == wants_melee.target {
offensive_bonus += power_bonus.power; defensive_bonus += defense_bonus.defense;
} }
} }
let target_stats = combat_stats.get(wants_melee.target).unwrap(); if let Some(pos) = positions.get(wants_melee.target) {
if target_stats.hp > 0 { particle_builder.request(
let target_name = names.get(wants_melee.target).unwrap(); pos.x,
pos.y,
let mut defensive_bonus = 0; rltk::RGB::named(rltk::ORANGE),
for (_item_entity, defense_bonus, equipped_by) in rltk::RGB::named(rltk::BLACK),
(&entities, &defense_bonus, &equipped).join() rltk::to_cp437('‼'),
{ 200.0,
if equipped_by.owner == wants_melee.target {
defensive_bonus += defense_bonus.defense;
}
}
if let Some(pos) = positions.get(wants_melee.target) {
particle_builder.request(
pos.x,
pos.y,
rltk::RGB::named(rltk::ORANGE),
rltk::RGB::named(rltk::BLACK),
rltk::to_cp437('‼'),
200.0,
);
}
let damage = i32::max(
0,
(stats.power + offensive_bonus) - (target_stats.defense + defensive_bonus),
); );
}
if damage == 0 { let damage = i32::max(
log.entries.push(format!( 0,
"{} is unable to hurt {}", (stats.power + offensive_bonus) - (target_stats.defense + defensive_bonus),
&name.name, &target_name.name );
));
} else { if damage == 0 {
log.entries.push(format!( log.entries.push(format!(
"{} hits {}, for {} hp.", "{} is unable to hurt {}",
&name.name, &target_name.name, damage &name.name, &target_name.name
)); ));
SufferDamage::new_damage(&mut inflict_damage, wants_melee.target, damage) } else {
} log.entries.push(format!(
"{} hits {}, for {} hp.",
&name.name, &target_name.name, damage
));
SufferDamage::new_damage(&mut inflict_damage, wants_melee.target, damage)
} }
} }
} }

View File

@ -3,13 +3,9 @@ use std::cmp::{max, min};
use rltk::{Point, Rltk, VirtualKeyCode}; use rltk::{Point, Rltk, VirtualKeyCode};
use specs::prelude::*; use specs::prelude::*;
use crate::{components::{CombatStats, Player, Position, Viewshed, WantsToMelee}, HungerClock, HungerState, Item, map::Map, Monster, RunState, State, TileType, WantsToPickupItem};
use crate::gamelog::GameLog; use crate::gamelog::GameLog;
use crate::spawner::player; use crate::spawner::player;
use crate::{
components::{CombatStats, Player, Position, Viewshed, WantsToMelee},
map::Map,
Item, Monster, RunState, State, TileType, WantsToPickupItem,
};
pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) { pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) {
let mut positions = ecs.write_storage::<Position>(); let mut positions = ecs.write_storage::<Position>();
@ -21,7 +17,7 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) {
let map = ecs.fetch::<Map>(); let map = ecs.fetch::<Map>();
for (entity, _player, pos, viewshed) in for (entity, _player, pos, viewshed) in
(&entities, &mut players, &mut positions, &mut viewsheds).join() (&entities, &mut players, &mut positions, &mut viewsheds).join()
{ {
if pos.x + delta_x < 1 if pos.x + delta_x < 1
|| pos.x + delta_x > map.width - 1 || pos.x + delta_x > map.width - 1
@ -139,10 +135,20 @@ fn skip_turn(ecs: &mut World) -> RunState {
} }
} }
let hunger_clock = ecs.read_storage::<HungerClock>();
let mut heal_bonus = 0;
if let Some(hc) = hunger_clock.get(*player_entity) {
match hc.state {
HungerState::Hungry | HungerState::Starving => {can_heal = false;}
HungerState::WellFed => {heal_bonus = 1;}
_ => {}
}
}
if can_heal { if can_heal {
let mut health_component = ecs.write_storage::<CombatStats>(); let mut health_component = ecs.write_storage::<CombatStats>();
let player_hp = health_component.get_mut(*player_entity).unwrap(); let player_hp = health_component.get_mut(*player_entity).unwrap();
player_hp.hp = i32::min(player_hp.hp + 1, player_hp.max_hp); player_hp.hp = i32::min(player_hp.hp + 1 + heal_bonus, player_hp.max_hp);
} }
RunState::PlayerTurn RunState::PlayerTurn

View File

@ -77,7 +77,10 @@ pub fn save_game(ecs: &mut World) {
MeleePowerBonus, MeleePowerBonus,
DefenseBonus, DefenseBonus,
WantsToRemoveItem, WantsToRemoveItem,
ParticleLifetime ParticleLifetime,
HungerClock,
Heals,
ProvidesFood
); );
} }
@ -157,7 +160,10 @@ pub fn load_game(ecs: &mut World) {
MeleePowerBonus, MeleePowerBonus,
DefenseBonus, DefenseBonus,
WantsToRemoveItem, WantsToRemoveItem,
ParticleLifetime ParticleLifetime,
HungerClock,
Heals,
ProvidesFood
); );
} }

View File

@ -4,13 +4,9 @@ use rltk::{FontCharType, RandomNumberGenerator, RGB};
use specs::prelude::*; use specs::prelude::*;
use specs::saveload::{MarkedBuilder, SimpleMarker}; use specs::saveload::{MarkedBuilder, SimpleMarker};
use crate::{AreaOfEffect, BlocksTile, CombatStats, Confusion, Consumable, DefenseBonus, EquipmentSlot, Equippable, HungerClock, HungerState, InflictsDamage, Item, MAP_WIDTH, MAX_MONSTER, MeleePowerBonus, Monster, Name, Player, Position, ProvidesFood, ProvidesHealing, Ranged, Renderable, SerializeMe, Viewshed};
use crate::random_table::RandomTable; use crate::random_table::RandomTable;
use crate::rect::Rect; use crate::rect::Rect;
use crate::{
AreaOfEffect, BlocksTile, CombatStats, Confusion, Consumable, DefenseBonus, EquipmentSlot,
Equippable, InflictsDamage, Item, MeleePowerBonus, Monster, Name, Player, Position,
ProvidesHealing, Ranged, Renderable, SerializeMe, Viewshed, MAP_WIDTH, 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 {
ecs.create_entity() ecs.create_entity()
@ -39,6 +35,10 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity {
defense: 2, defense: 2,
power: 5, power: 5,
}) })
.with(HungerClock {
duration: 20,
state: HungerState::WellFed,
})
.marked::<SimpleMarker<SerializeMe>>() .marked::<SimpleMarker<SerializeMe>>()
.build() .build()
} }
@ -125,6 +125,7 @@ pub fn spawn_room(ecs: &mut World, room: &Rect, map_depth: i32) {
"Breastplate" => breastplate(ecs, x, y), "Breastplate" => breastplate(ecs, x, y),
"Leggings" => leggings(ecs, x, y), "Leggings" => leggings(ecs, x, y),
"Sabatons" => sabatons(ecs, x, y), "Sabatons" => sabatons(ecs, x, y),
"Rations" => rations(ecs, x, y),
_ => {} _ => {}
} }
} }
@ -226,6 +227,7 @@ pub fn room_table(map_depth: i32) -> RandomTable {
.add("Breastplate", map_depth - 3) .add("Breastplate", map_depth - 3)
.add("Leggings", map_depth - 4) .add("Leggings", map_depth - 4)
.add("Sabatons", map_depth - 4) .add("Sabatons", map_depth - 4)
.add("Rations", 10)
} }
fn dagger(ecs: &mut World, x: i32, y: i32) { fn dagger(ecs: &mut World, x: i32, y: i32) {
@ -397,3 +399,23 @@ fn sabatons(ecs: &mut World, x: i32, y: i32) {
.marked::<SimpleMarker<SerializeMe>>() .marked::<SimpleMarker<SerializeMe>>()
.build(); .build();
} }
fn rations(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::GREEN),
bg: RGB::named(rltk::BLACK),
})
.with(Item {})
.with(Name {
name: "Rations".to_string(),
})
.with(Consumable{})
.with(ProvidesFood{})
.marked::<SimpleMarker<SerializeMe>>()
.build();
}