diff --git a/raws/items.json b/raws/items.json index 5b477e0..8bf9ffc 100644 --- a/raws/items.json +++ b/raws/items.json @@ -6,7 +6,7 @@ "weight": 1, "value": 50, "flags": ["CONSUMABLE", "DESTRUCTIBLE"], - "effects": { "provides_healing": "2d4+2" }, + "effects": { "provides_healing": "4d4+2" }, "magic": { "class": "uncommon", "naming": "potion" } }, { @@ -16,7 +16,7 @@ "weight": 1, "value": 25, "flags": ["CONSUMABLE", "DESTRUCTIBLE"], - "effects": { "provides_healing": "1d4+2" }, + "effects": { "provides_healing": "2d4+2" }, "magic": { "class": "uncommon", "naming": "potion" } }, { diff --git a/src/ai/quip_system.rs b/src/ai/quip_system.rs index 5f25cee..7ccda9e 100644 --- a/src/ai/quip_system.rs +++ b/src/ai/quip_system.rs @@ -1,4 +1,4 @@ -use crate::{gamelog, Name, Quips, TakingTurn, Viewshed}; +use crate::{gamelog, gui::renderable_colour, Name, Quips, Renderable, TakingTurn, Viewshed}; use rltk::prelude::*; use specs::prelude::*; @@ -7,8 +7,10 @@ pub struct QuipSystem {} impl<'a> System<'a> for QuipSystem { #[allow(clippy::type_complexity)] type SystemData = ( + Entities<'a>, WriteStorage<'a, Quips>, ReadStorage<'a, Name>, + ReadStorage<'a, Renderable>, ReadStorage<'a, TakingTurn>, ReadExpect<'a, Point>, ReadStorage<'a, Viewshed>, @@ -16,8 +18,8 @@ impl<'a> System<'a> for QuipSystem { ); fn run(&mut self, data: Self::SystemData) { - let (mut quips, names, turns, player_pos, viewsheds, mut rng) = data; - for (quip, name, viewshed, _turn) in (&mut quips, &names, &viewsheds, &turns).join() { + let (entities, mut quips, names, renderables, turns, player_pos, viewsheds, mut rng) = data; + for (entity, quip, name, viewshed, _turn) in (&entities, &mut quips, &names, &viewsheds, &turns).join() { if !quip.available.is_empty() && viewshed.visible_tiles.contains(&player_pos) && rng.roll_dice(1, 6) == 1 { let quip_index = if quip.available.len() == 1 { 0 @@ -26,7 +28,9 @@ impl<'a> System<'a> for QuipSystem { }; gamelog::Logger::new() .append("The") - .npc_name(&name.name) + .colour(renderable_colour(&renderables, entity)) + .append(&name.name) + .colour(WHITE) .append_n("says \"") .append_n(&quip.available[quip_index]) .append("\"") diff --git a/src/damage_system.rs b/src/damage_system.rs index 1163daa..ea76b3c 100644 --- a/src/damage_system.rs +++ b/src/damage_system.rs @@ -1,4 +1,7 @@ -use super::{gamelog, Equipped, InBackpack, Item, LootTable, Name, Player, Pools, Position, RunState}; +use super::{ + gamelog, gui::renderable_colour, Equipped, InBackpack, Item, LootTable, Name, Player, Pools, Position, Renderable, + RunState, +}; use rltk::prelude::*; use specs::prelude::*; @@ -11,6 +14,7 @@ pub fn delete_the_dead(ecs: &mut World) { let names = ecs.read_storage::(); let items = ecs.read_storage::(); let entities = ecs.entities(); + let renderables = ecs.read_storage::(); for (entity, stats) in (&entities, &combat_stats).join() { if stats.hit_points.current < 1 { let player = players.get(entity); @@ -22,14 +26,16 @@ pub fn delete_the_dead(ecs: &mut World) { if let Some(_item) = item { gamelog::Logger::new() .append("The") - .npc_name(&victim_name.name) + .colour(renderable_colour(&renderables, entity)) + .append(&victim_name.name) .colour(rltk::WHITE) .append("is destroyed!") .log(); } else { gamelog::Logger::new() .append("The") - .npc_name(&victim_name.name) + .colour(renderable_colour(&renderables, entity)) + .append(&victim_name.name) .colour(rltk::WHITE) .append("dies!") .log(); @@ -52,6 +58,7 @@ pub fn delete_the_dead(ecs: &mut World) { &crate::raws::RAWS.lock().unwrap(), ecs, &loot.0, + None, crate::raws::SpawnType::AtPosition { x: loot.1.x, y: loot.1.y }, 0, ); diff --git a/src/effects/damage.rs b/src/effects/damage.rs index e8adce4..a8d7bc3 100644 --- a/src/effects/damage.rs +++ b/src/effects/damage.rs @@ -1,4 +1,4 @@ -use super::{add_effect, targeting, EffectSpawner, EffectType, Entity, Targets, World}; +use super::{add_effect, get_noncursed, targeting, EffectSpawner, EffectType, Entity, Targets, World}; use crate::{ gamelog, gamesystem::{hp_per_level, mana_per_level}, @@ -40,8 +40,14 @@ pub fn inflict_damage(ecs: &mut World, damage: &EffectSpawner, target: Entity) { pub fn heal_damage(ecs: &mut World, heal: &EffectSpawner, target: Entity) { let mut pools = ecs.write_storage::(); if let Some(pool) = pools.get_mut(target) { - if let EffectType::Healing { amount } = heal.effect_type { + if let EffectType::Healing { amount, buc } = &heal.effect_type { + let before = pool.hit_points.current; pool.hit_points.current = i32::min(pool.hit_points.max, pool.hit_points.current + amount); + if pool.hit_points.current - before < *amount && get_noncursed(buc) { + // If the heal was not fully effective, and healing source was noncursed, increase max HP by 1. + pool.hit_points.max += 1; + pool.hit_points.current += 1; + } add_effect( None, EffectType::Particle { diff --git a/src/effects/mod.rs b/src/effects/mod.rs index aa969a6..b9f637f 100644 --- a/src/effects/mod.rs +++ b/src/effects/mod.rs @@ -23,7 +23,7 @@ lazy_static! { pub enum EffectType { Damage { amount: i32 }, - Healing { amount: i32 }, + Healing { amount: i32, buc: BUC }, Confusion { turns: i32 }, Bloodstain, Particle { glyph: FontCharType, fg: RGB, bg: RGB, lifespan: f32, delay: f32 }, @@ -143,3 +143,11 @@ fn affect_entity(ecs: &mut World, effect: &EffectSpawner, target: Entity) { _ => {} } } + +pub fn get_noncursed(buc: &BUC) -> bool { + if buc == &BUC::Cursed { + false + } else { + true + } +} diff --git a/src/effects/triggers.rs b/src/effects/triggers.rs index b27a869..d01410b 100644 --- a/src/effects/triggers.rs +++ b/src/effects/triggers.rs @@ -84,7 +84,7 @@ fn handle_restore_nutrition(ecs: &mut World, event: &mut EventInfo, mut logger: .append_n(obfuscate_name_ecs(ecs, event.entity).0) .colour(WHITE) .period() - .buc(event.buc.clone(), Some("Blech! Rotten."), Some("Delicious.")); + .buc(event.buc.clone(), Some("Blech! Rotten"), Some("Delicious")); event.log = true; } return logger; @@ -108,8 +108,13 @@ fn handle_magic_mapper(ecs: &mut World, event: &mut EventInfo, mut logger: gamel fn handle_healing(ecs: &mut World, event: &mut EventInfo, mut logger: gamelog::Logger) -> gamelog::Logger { if let Some(healing_item) = ecs.read_storage::().get(event.entity) { let mut rng = ecs.write_resource::(); - let roll = rng.roll_dice(healing_item.n_dice, healing_item.sides) + healing_item.modifier; - add_effect(event.source, EffectType::Healing { amount: roll }, event.target.clone()); + let buc_mod = match event.buc { + BUC::Blessed => 2, + BUC::Cursed => -1, + _ => 0, + }; + let roll = rng.roll_dice(healing_item.n_dice + buc_mod, healing_item.sides) + healing_item.modifier; + add_effect(event.source, EffectType::Healing { amount: roll, buc: event.buc.clone() }, event.target.clone()); for target in get_entity_targets(&event.target) { if ecs.read_storage::().get(target).is_some() || ecs.read_storage::().get(target).is_some() { continue; diff --git a/src/gamelog/builder.rs b/src/gamelog/builder.rs index e317a3f..0d2e63c 100644 --- a/src/gamelog/builder.rs +++ b/src/gamelog/builder.rs @@ -42,9 +42,11 @@ impl Logger { pub fn buc(mut self, buc: BUC, cursed: Option, blessed: Option) -> Self { if buc == BUC::Cursed && cursed.is_some() { - self.fragments.push(LogFragment { colour: RGB::named(RED), text: cursed.unwrap().to_string() }); + self.fragments.push(LogFragment { colour: RGB::named(SALMON), text: cursed.unwrap().to_string() }); + self.fragments.push(LogFragment { colour: self.current_colour, text: ". ".to_string() }); } else if buc == BUC::Blessed && blessed.is_some() { - self.fragments.push(LogFragment { colour: RGB::named(GOLD), text: blessed.unwrap().to_string() }); + self.fragments.push(LogFragment { colour: RGB::named(CYAN), text: blessed.unwrap().to_string() }); + self.fragments.push(LogFragment { colour: self.current_colour, text: ". ".to_string() }); } return self; } diff --git a/src/gui/character_creation.rs b/src/gui/character_creation.rs index fa3f449..91773ff 100644 --- a/src/gui/character_creation.rs +++ b/src/gui/character_creation.rs @@ -1,5 +1,5 @@ use super::{gamesystem::attr_bonus, gamesystem::get_attribute_rolls, Attributes, Pools, Renderable, RunState, State}; -use crate::{ai::NORMAL_SPEED, raws, Attribute, Energy, HasAncestry, HasClass, Pool, Skill, Skills, Telepath}; +use crate::{ai::NORMAL_SPEED, raws, Attribute, Energy, HasAncestry, HasClass, Pool, Skill, Skills, Telepath, BUC}; use rltk::prelude::*; use serde::{Deserialize, Serialize}; use specs::prelude::*; @@ -300,10 +300,25 @@ pub fn setup_player_class(ecs: &mut World, class: Class, ancestry: Ancestry) { let mut rng = RandomNumberGenerator::new(); let starts_with = get_starting_inventory(class, &mut rng); for item in starts_with.0.iter() { - raws::spawn_named_entity(&raws::RAWS.lock().unwrap(), ecs, item, raws::SpawnType::Equipped { by: player }, 0); + let buc = if rng.roll_dice(1, 3) == 1 { Some(BUC::Blessed) } else { Some(BUC::Uncursed) }; + raws::spawn_named_entity( + &raws::RAWS.lock().unwrap(), + ecs, + item, + buc, + raws::SpawnType::Equipped { by: player }, + 0, + ); } for item in starts_with.1.iter() { - raws::spawn_named_entity(&raws::RAWS.lock().unwrap(), ecs, item, raws::SpawnType::Carried { by: player }, 0); + raws::spawn_named_entity( + &raws::RAWS.lock().unwrap(), + ecs, + item, + None, + raws::SpawnType::Carried { by: player }, + 0, + ); } } diff --git a/src/inventory/equip_system.rs b/src/inventory/equip_system.rs index 5f29633..735f88c 100644 --- a/src/inventory/equip_system.rs +++ b/src/inventory/equip_system.rs @@ -78,6 +78,7 @@ impl<'a> System<'a> for ItemEquipSystem { if target == *player_entity { logger = logger .append("You equip the") + .colour(item_colour(wants_to_use_item.item, &beatitudes, &dm)) .append_n( obfuscate_name( wants_to_use_item.item, @@ -90,7 +91,7 @@ impl<'a> System<'a> for ItemEquipSystem { ) .0, ) - .colour(item_colour(wants_to_use_item.item, &beatitudes, &dm)) + .colour(rltk::WHITE) .period(); logger.log(); } diff --git a/src/map/interval_spawning_system.rs b/src/map/interval_spawning_system.rs index e4f5a9b..71c4071 100644 --- a/src/map/interval_spawning_system.rs +++ b/src/map/interval_spawning_system.rs @@ -62,6 +62,7 @@ fn spawn_random_mob_in_free_nonvisible_tile(ecs: &mut World) { &raws::RAWS.lock().unwrap(), ecs, &key, + None, raws::SpawnType::AtPosition { x: idx.0, y: idx.1 }, difficulty, ); diff --git a/src/melee_combat_system.rs b/src/melee_combat_system.rs index ecfd97b..b3fb4dd 100644 --- a/src/melee_combat_system.rs +++ b/src/melee_combat_system.rs @@ -1,8 +1,11 @@ use super::{ effects::{add_effect, EffectType, Targets}, - gamelog, gamesystem, ArmourClassBonus, Attributes, EquipmentSlot, Equipped, HungerClock, HungerState, MeleeWeapon, - MultiAttack, Name, NaturalAttacks, ParticleBuilder, Pools, Position, Skill, Skills, WantsToMelee, WeaponAttribute, + gamelog, gamesystem, + gui::renderable_colour, + ArmourClassBonus, Attributes, EquipmentSlot, Equipped, HungerClock, HungerState, MeleeWeapon, MultiAttack, Name, + NaturalAttacks, ParticleBuilder, Pools, Position, Renderable, Skill, Skills, WantsToMelee, WeaponAttribute, }; +use rltk::prelude::*; use specs::prelude::*; pub struct MeleeCombatSystem {} @@ -11,6 +14,7 @@ impl<'a> System<'a> for MeleeCombatSystem { type SystemData = ( Entities<'a>, ReadExpect<'a, Entity>, + ReadStorage<'a, Renderable>, WriteStorage<'a, WantsToMelee>, ReadStorage<'a, Name>, ReadStorage<'a, Attributes>, @@ -31,6 +35,7 @@ impl<'a> System<'a> for MeleeCombatSystem { let ( entities, player_entity, + renderables, mut wants_melee, names, attributes, @@ -230,22 +235,30 @@ impl<'a> System<'a> for MeleeCombatSystem { something_to_log = true; logger = logger // You hit the . .append("You hit the") - .npc_name_n(&target_name.name) + .colour(renderable_colour(&renderables, wants_melee.target)) + .append_n(&target_name.name) + .colour(WHITE) .period(); } else if wants_melee.target == *player_entity { something_to_log = true; logger = logger // hits you! .append("The") - .npc_name(&name.name) + .colour(renderable_colour(&renderables, entity)) + .append(&name.name) + .colour(WHITE) .append(attack_verb) .append("you!"); } else { gamelog::Logger::new() // misses the . .append("The") - .npc_name(&name.name) + .colour(renderable_colour(&renderables, entity)) + .append(&name.name) + .colour(WHITE) .append(attack_verb) .append("the") - .npc_name_n(&target_name.name) + .colour(renderable_colour(&renderables, wants_melee.target)) + .append_n(&target_name.name) + .colour(WHITE) .period() .log(); } @@ -266,16 +279,20 @@ impl<'a> System<'a> for MeleeCombatSystem { something_to_log = true; logger = logger // misses! .append("The") - .npc_name(&name.name) - .colour(rltk::WHITE) + .colour(renderable_colour(&renderables, entity)) + .append(&name.name) + .colour(WHITE) .append("misses!"); } else { gamelog::Logger::new() // misses the . .append("The") - .npc_name(&name.name) - .colour(rltk::WHITE) + .colour(renderable_colour(&renderables, entity)) + .append(&name.name) + .colour(WHITE) .append("misses the") - .npc_name_n(&target_name.name) + .colour(renderable_colour(&renderables, wants_melee.target)) + .append_n(&target_name.name) + .colour(WHITE) .period() .log(); } diff --git a/src/player.rs b/src/player.rs index 3290ba0..8b01868 100644 --- a/src/player.rs +++ b/src/player.rs @@ -2,11 +2,13 @@ use super::{ effects::{add_effect, EffectType, Targets}, gamelog, gui::obfuscate_name_ecs, + gui::renderable_colour, raws::Reaction, Attributes, BlocksTile, BlocksVisibility, Door, EntityMoved, Faction, HasAncestry, Hidden, HungerClock, HungerState, Item, Map, Name, ParticleBuilder, Player, Pools, Position, Renderable, RunState, State, Telepath, TileType, Viewshed, WantsToMelee, WantsToPickupItem, }; +use rltk::prelude::*; use rltk::{Point, RandomNumberGenerator, Rltk, VirtualKeyCode}; use specs::prelude::*; use std::cmp::{max, min}; @@ -402,7 +404,14 @@ pub fn try_move_player(delta_x: i32, delta_y: i32, ecs: &mut World) -> RunState for m in swap_entities.iter() { if let Some(name) = names.get(m.0) { - gamelog::Logger::new().append("You swap places with the").npc_name_n(&name.name).period().log(); + let renderables = ecs.read_storage::(); + gamelog::Logger::new() + .append("You swap places with the") + .colour(renderable_colour(&renderables, m.0)) + .append_n(&name.name) + .colour(WHITE) + .period() + .log(); } if let Some(their_pos) = positions.get_mut(m.0) { let old_idx = map.xy_idx(their_pos.x, their_pos.y); diff --git a/src/raws/rawmaster.rs b/src/raws/rawmaster.rs index 297b348..dfd0ea5 100644 --- a/src/raws/rawmaster.rs +++ b/src/raws/rawmaster.rs @@ -119,11 +119,12 @@ pub fn spawn_named_entity( raws: &RawMaster, ecs: &mut World, key: &str, + buc: Option, pos: SpawnType, map_difficulty: i32, ) -> Option { if raws.item_index.contains_key(key) { - return spawn_named_item(raws, ecs, key, pos); + return spawn_named_item(raws, ecs, key, buc, pos); } else if raws.mob_index.contains_key(key) { return spawn_named_mob(raws, ecs, key, pos, map_difficulty); } else if raws.prop_index.contains_key(key) { @@ -132,7 +133,13 @@ pub fn spawn_named_entity( None } -pub fn spawn_named_item(raws: &RawMaster, ecs: &mut World, key: &str, pos: SpawnType) -> Option { +pub fn spawn_named_item( + raws: &RawMaster, + ecs: &mut World, + key: &str, + buc: Option, + pos: SpawnType, +) -> Option { if raws.item_index.contains_key(key) { let item_template = &raws.raws.items[raws.item_index[key]]; let dm = ecs.fetch::(); @@ -140,7 +147,7 @@ pub fn spawn_named_item(raws: &RawMaster, ecs: &mut World, key: &str, pos: Spawn let potion_names = dm.potion_map.clone(); let wand_names = dm.wand_map.clone(); let identified_items = dm.identified_items.clone(); - let roll = ecs.write_resource::().roll_dice(1, 3); + let roll = ecs.write_resource::().roll_dice(1, 6); std::mem::drop(dm); let mut eb = ecs.create_entity().marked::>(); @@ -151,9 +158,17 @@ pub fn spawn_named_item(raws: &RawMaster, ecs: &mut World, key: &str, pos: Spawn if let Some(renderable) = &item_template.renderable { eb = eb.with(get_renderable_component(renderable)); } - + // BEATITUDE + let mut buc = if let Some(buc_status) = buc { + buc_status + } else { + match roll { + 1 => BUC::Cursed, + 2 => BUC::Blessed, + _ => BUC::Uncursed, + } + }; let mut weapon_type = -1; - let mut buc = BUC::Uncursed; if let Some(flags) = &item_template.flags { for flag in flags.iter() { match flag.as_str() { @@ -178,13 +193,6 @@ pub fn spawn_named_item(raws: &RawMaster, ecs: &mut World, key: &str, pos: Spawn } } } - if buc == BUC::Uncursed { - match roll { - 1 => buc = BUC::Cursed, - 2 => buc = BUC::Blessed, - _ => {} - } - } eb = eb.with(Beatitude { buc, known: true }); let mut base_damage = "1d4"; @@ -535,7 +543,7 @@ pub fn spawn_named_mob( // Build entity, then check for anything they're wearing if let Some(wielding) = &mob_template.equipped { for tag in wielding.iter() { - spawn_named_entity(raws, ecs, tag, SpawnType::Equipped { by: new_mob }, map_difficulty); + spawn_named_entity(raws, ecs, tag, None, SpawnType::Equipped { by: new_mob }, map_difficulty); } } @@ -669,7 +677,7 @@ pub fn parse_dice_string(dice: &str) -> (i32, i32, i32) { static ref DICE_RE: Regex = Regex::new(r"(\d+)d(\d+)([\+\-]\d+)?").unwrap(); } let mut n_dice = 1; - let mut die_type = 4; + let mut die_type = 1; let mut die_bonus = 0; for cap in DICE_RE.captures_iter(dice) { if let Some(group) = cap.get(1) { diff --git a/src/spawner.rs b/src/spawner.rs index 0791602..bbd3b7b 100644 --- a/src/spawner.rs +++ b/src/spawner.rs @@ -162,6 +162,7 @@ pub fn spawn_entity(ecs: &mut World, spawn: &(&usize, &String)) { &raws::RAWS.lock().unwrap(), ecs, &spawn.1, + None, raws::SpawnType::AtPosition { x, y }, map_difficulty, );