From 3dab5202f8befadfc5d840346b24c6c9ee2d57b6 Mon Sep 17 00:00:00 2001 From: Llywelwyn Date: Sat, 29 Jul 2023 05:56:52 +0100 Subject: [PATCH] levels, and ui changes --- src/components.rs | 13 ++- src/damage_system.rs | 99 ++++++++++++++++++++-- src/gamelog/builder.rs | 1 + src/gamesystem.rs | 1 + src/gui/letter_to_option.rs | 65 +++++++++++++++ src/gui/mod.rs | 158 ++++++++++++++++++++++++------------ src/hunger_system.rs | 2 +- src/inventory_system.rs | 9 +- src/main.rs | 1 + src/melee_combat_system.rs | 36 +++++--- src/player.rs | 2 +- src/raws/rawmaster.rs | 36 ++++++-- src/saveload_system.rs | 2 + src/spawner.rs | 14 ---- src/trigger_system.rs | 2 +- 15 files changed, 337 insertions(+), 104 deletions(-) create mode 100644 src/gui/letter_to_option.rs diff --git a/src/components.rs b/src/components.rs index 86d2df2..c17978f 100644 --- a/src/components.rs +++ b/src/components.rs @@ -149,17 +149,22 @@ pub struct WantsToMelee { pub target: Entity, } +#[derive(Component, Debug, ConvertSaveload, Clone)] +pub struct GrantsXP { + pub amount: i32, +} + #[derive(Component, Debug, ConvertSaveload, Clone)] pub struct SufferDamage { - pub amount: Vec, + pub amount: Vec<(i32, bool)>, } impl SufferDamage { - pub fn new_damage(store: &mut WriteStorage, victim: Entity, amount: i32) { + pub fn new_damage(store: &mut WriteStorage, victim: Entity, amount: i32, from_player: bool) { if let Some(suffering) = store.get_mut(victim) { - suffering.amount.push(amount); + suffering.amount.push((amount, from_player)); } else { - let dmg = SufferDamage { amount: vec![amount] }; + let dmg = SufferDamage { amount: vec![(amount, from_player)] }; store.insert(victim, dmg).expect("Unable to insert damage."); } } diff --git a/src/damage_system.rs b/src/damage_system.rs index b535a62..2aafdab 100644 --- a/src/damage_system.rs +++ b/src/damage_system.rs @@ -1,26 +1,109 @@ -use super::{gamelog, Entities, Item, Map, Name, Player, Pools, Position, RunState, SufferDamage}; +use super::{ + gamelog, Attributes, GrantsXP, Item, Map, Name, ParticleBuilder, Player, Pools, Position, RunState, SufferDamage, +}; +use crate::gamesystem::{mana_per_level, player_hp_per_level}; use specs::prelude::*; pub struct DamageSystem {} impl<'a> System<'a> for DamageSystem { + #[allow(clippy::type_complexity)] type SystemData = ( WriteStorage<'a, Pools>, WriteStorage<'a, SufferDamage>, - WriteExpect<'a, Map>, ReadStorage<'a, Position>, + WriteExpect<'a, Map>, Entities<'a>, + ReadExpect<'a, Entity>, + ReadStorage<'a, Attributes>, + WriteExpect<'a, rltk::RandomNumberGenerator>, + ReadStorage<'a, GrantsXP>, + WriteExpect<'a, ParticleBuilder>, + ReadExpect<'a, rltk::Point>, ); fn run(&mut self, data: Self::SystemData) { - let (mut stats, mut damage, mut map, positions, entities) = data; + let ( + mut stats, + mut damage, + positions, + mut map, + entities, + player, + attributes, + mut rng, + xp_granters, + mut particle_builder, + player_pos, + ) = data; + let mut xp_gain = 0; for (entity, mut stats, damage) in (&entities, &mut stats, &damage).join() { - stats.hit_points.current -= damage.amount.iter().sum::(); - let pos = positions.get(entity); - if let Some(pos) = pos { - let idx = map.xy_idx(pos.x, pos.y); - map.bloodstains.insert(idx); + for dmg in damage.amount.iter() { + stats.hit_points.current -= dmg.0; + let pos = positions.get(entity); + if let Some(pos) = pos { + let idx = map.xy_idx(pos.x, pos.y); + map.bloodstains.insert(idx); + } + + if stats.hit_points.current < 1 && dmg.1 { + let gives_xp = xp_granters.get(entity); + if let Some(xp_value) = gives_xp { + xp_gain += xp_value.amount; + } + } + } + } + + if xp_gain != 0 { + let mut player_stats = stats.get_mut(*player).unwrap(); + let player_attributes = attributes.get(*player).unwrap(); + player_stats.xp += xp_gain; + rltk::console::log(xp_gain); + + let mut next_level_requirement = -1; + if player_stats.level < 10 { + next_level_requirement = 20 * 2_i32.pow(player_stats.level as u32 - 1); + } else if player_stats.level < 20 { + next_level_requirement = 10000 * 2_i32.pow(player_stats.level as u32 - 10); + } else if player_stats.level < 30 { + next_level_requirement = 10000000 * (player_stats.level - 19); + } + + if next_level_requirement != -1 && player_stats.xp >= next_level_requirement { + // We've gone up a level! + player_stats.level += 1; + gamelog::Logger::new() + .append("Welcome to experience level") + .append(player_stats.level) + .append(".") + .log(); + for i in 0..5 { + if player_pos.y - i > 1 { + particle_builder.request( + player_pos.x, + player_pos.y - i, + rltk::RGB::named(rltk::GOLD), + rltk::RGB::named(rltk::BLACK), + rltk::to_cp437('*'), + 200.0, + ); + } + } + let hp_gained = player_hp_per_level( + &mut rng, + player_attributes.constitution.base + player_attributes.constitution.modifiers, + ); + player_stats.hit_points.max += hp_gained; + player_stats.hit_points.current += hp_gained; + + let mana_gained = mana_per_level( + &mut rng, + player_attributes.intelligence.base + player_attributes.intelligence.modifiers, + ); + player_stats.mana.max += mana_gained; + player_stats.mana.current += mana_gained; } } diff --git a/src/gamelog/builder.rs b/src/gamelog/builder.rs index a29f9ad..c80f545 100644 --- a/src/gamelog/builder.rs +++ b/src/gamelog/builder.rs @@ -73,6 +73,7 @@ impl Logger { } /// Appends text in RED to the current message logger. + #[allow(dead_code)] pub fn damage(mut self, damage: i32) -> Self { self.fragments.push(LogFragment { colour: RGB::named(rltk::RED), text: format!("{} ", damage).to_string() }); return self; diff --git a/src/gamesystem.rs b/src/gamesystem.rs index f191450..afd5e06 100644 --- a/src/gamesystem.rs +++ b/src/gamesystem.rs @@ -8,6 +8,7 @@ pub fn player_hp_per_level(rng: &mut rltk::RandomNumberGenerator, constitution: return rng.roll_dice(1, 8) + attr_bonus(constitution); } +#[allow(dead_code)] pub fn player_hp_at_level(rng: &mut rltk::RandomNumberGenerator, constitution: i32, level: i32) -> i32 { let mut total = 10 + attr_bonus(constitution); for _i in 0..level { diff --git a/src/gui/letter_to_option.rs b/src/gui/letter_to_option.rs new file mode 100644 index 0000000..a346bff --- /dev/null +++ b/src/gui/letter_to_option.rs @@ -0,0 +1,65 @@ +use rltk::prelude::*; + +pub fn letter_to_option(key: VirtualKeyCode, shift: bool) -> i32 { + if shift { + match key { + VirtualKeyCode::A => 26, + VirtualKeyCode::B => 27, + VirtualKeyCode::C => 28, + VirtualKeyCode::D => 29, + VirtualKeyCode::E => 30, + VirtualKeyCode::F => 31, + VirtualKeyCode::G => 32, + VirtualKeyCode::H => 33, + VirtualKeyCode::I => 34, + VirtualKeyCode::J => 35, + VirtualKeyCode::K => 36, + VirtualKeyCode::L => 37, + VirtualKeyCode::M => 38, + VirtualKeyCode::N => 39, + VirtualKeyCode::O => 40, + VirtualKeyCode::P => 41, + VirtualKeyCode::Q => 42, + VirtualKeyCode::R => 43, + VirtualKeyCode::S => 44, + VirtualKeyCode::T => 45, + VirtualKeyCode::U => 46, + VirtualKeyCode::V => 47, + VirtualKeyCode::W => 48, + VirtualKeyCode::X => 49, + VirtualKeyCode::Y => 50, + VirtualKeyCode::Z => 51, + _ => -1, + } + } else { + match key { + VirtualKeyCode::A => 0, + VirtualKeyCode::B => 1, + VirtualKeyCode::C => 2, + VirtualKeyCode::D => 3, + VirtualKeyCode::E => 4, + VirtualKeyCode::F => 5, + VirtualKeyCode::G => 6, + VirtualKeyCode::H => 7, + VirtualKeyCode::I => 8, + VirtualKeyCode::J => 9, + VirtualKeyCode::K => 10, + VirtualKeyCode::L => 11, + VirtualKeyCode::M => 12, + VirtualKeyCode::N => 13, + VirtualKeyCode::O => 14, + VirtualKeyCode::P => 15, + VirtualKeyCode::Q => 16, + VirtualKeyCode::R => 17, + VirtualKeyCode::S => 18, + VirtualKeyCode::T => 19, + VirtualKeyCode::U => 20, + VirtualKeyCode::V => 21, + VirtualKeyCode::W => 22, + VirtualKeyCode::X => 23, + VirtualKeyCode::Y => 24, + VirtualKeyCode::Z => 25, + _ => -1, + } + } +} diff --git a/src/gui/mod.rs b/src/gui/mod.rs index 77d32b2..f9e58f5 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -1,11 +1,12 @@ use super::{ - camera, gamelog, gamesystem, rex_assets::RexAssets, ArmourClassBonus, Attributes, Consumable, Equippable, Equipped, - Hidden, HungerClock, HungerState, InBackpack, Map, Name, Player, Point, Pools, Position, Prop, Renderable, - RunState, Skill, Skills, State, Viewshed, + camera, gamelog, gamesystem, rex_assets::RexAssets, ArmourClassBonus, Attributes, Equipped, Hidden, HungerClock, + HungerState, InBackpack, Map, Name, Player, Point, Pools, Position, Prop, Renderable, RunState, Skill, Skills, + State, Viewshed, }; use rltk::{Rltk, VirtualKeyCode, RGB}; use specs::prelude::*; use std::collections::BTreeMap; +mod letter_to_option; mod tooltip; pub fn draw_lerping_bar( @@ -92,18 +93,19 @@ pub fn draw_ui(ecs: &World, ctx: &mut Rltk) { format!("XP{}/{}", stats.level, stats.xp), ); // Draw attributes - ctx.print_color(36, 53, RGB::named(rltk::RED), RGB::named(rltk::BLACK), "STR"); - ctx.print_color(39, 53, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), attributes.strength.base); - ctx.print_color(43, 53, RGB::named(rltk::GREEN), RGB::named(rltk::BLACK), "DEX"); - ctx.print_color(46, 53, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), attributes.dexterity.base); - ctx.print_color(50, 53, RGB::named(rltk::ORANGE), RGB::named(rltk::BLACK), "CON"); - ctx.print_color(53, 53, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), attributes.constitution.base); - ctx.print_color(36, 54, RGB::named(rltk::CYAN), RGB::named(rltk::BLACK), "INT"); - ctx.print_color(39, 54, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), attributes.intelligence.base); - ctx.print_color(43, 54, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "WIS"); - ctx.print_color(46, 54, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), attributes.wisdom.base); - ctx.print_color(50, 54, RGB::named(rltk::PURPLE), RGB::named(rltk::BLACK), "CHA"); - ctx.print_color(53, 54, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), attributes.charisma.base); + let x = 38; + ctx.print_color(x, 53, RGB::named(rltk::RED), RGB::named(rltk::BLACK), "STR"); + ctx.print_color(x + 3, 53, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), attributes.strength.base); + ctx.print_color(x + 7, 53, RGB::named(rltk::GREEN), RGB::named(rltk::BLACK), "DEX"); + ctx.print_color(x + 10, 53, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), attributes.dexterity.base); + ctx.print_color(x + 14, 53, RGB::named(rltk::ORANGE), RGB::named(rltk::BLACK), "CON"); + ctx.print_color(x + 17, 53, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), attributes.constitution.base); + ctx.print_color(x, 54, RGB::named(rltk::CYAN), RGB::named(rltk::BLACK), "INT"); + ctx.print_color(x + 3, 54, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), attributes.intelligence.base); + ctx.print_color(x + 7, 54, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "WIS"); + ctx.print_color(x + 10, 54, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), attributes.wisdom.base); + ctx.print_color(x + 14, 54, RGB::named(rltk::PURPLE), RGB::named(rltk::BLACK), "CHA"); + ctx.print_color(x + 17, 54, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), attributes.charisma.base); // Draw hunger match hunger.state { HungerState::Satiated => { @@ -123,14 +125,7 @@ pub fn draw_ui(ecs: &World, ctx: &mut Rltk) { // Draw equipment let names = ecs.read_storage::(); - let backpack = ecs.read_storage::(); - let equippables = ecs.read_storage::(); let mut equipment: Vec = Vec::new(); - for (_entity, _pack, name) in - (&equippables, &backpack, &names).join().filter(|item| item.1.owner == *player_entity) - { - equipment.push(format!("- {}", &name.name)); - } for (_equipped, name) in (&equipped, &names).join().filter(|item| item.0.owner == *player_entity) { equipment.push(format!("- {} (worn)", &name.name)); } @@ -148,7 +143,7 @@ pub fn draw_ui(ecs: &World, ctx: &mut Rltk) { ctx.print_color(72, y, RGB::named(rltk::BLACK), RGB::named(rltk::WHITE), "Backpack"); y += 1; let (player_inventory, _inventory_ids) = get_player_inventory(&ecs); - y = print_options(player_inventory, 72, y, ctx); + y = print_options(player_inventory, 72, y, ctx).0; // Draw entities seen on screen let viewsheds = ecs.read_storage::(); @@ -231,6 +226,7 @@ pub fn get_input_direction( match ctx.key { None => return RunState::ActionWithDirection { function }, Some(key) => match key { + VirtualKeyCode::Escape => return RunState::AwaitingInput, // Cardinals VirtualKeyCode::Left | VirtualKeyCode::Numpad4 | VirtualKeyCode::H => return function(-1, 0, ecs), VirtualKeyCode::Right | VirtualKeyCode::Numpad6 | VirtualKeyCode::L => return function(1, 0, ecs), @@ -253,13 +249,19 @@ pub enum ItemMenuResult { Selected, } -pub fn print_options(inventory: BTreeMap<(String, String), i32>, mut x: i32, mut y: i32, ctx: &mut Rltk) -> i32 { +pub fn print_options(inventory: BTreeMap<(String, String), i32>, mut x: i32, mut y: i32, ctx: &mut Rltk) -> (i32, i32) { let mut j = 0; let initial_x: i32 = x; + let mut width: i32 = -1; for (name, item_count) in &inventory { x = initial_x; // Print the character required to access this item. i.e. (a) - ctx.set(x, y, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), 97 + j as rltk::FontCharType); + if j < 26 { + ctx.set(x, y, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), 97 + j as rltk::FontCharType); + } else { + // If we somehow have more than 26, start using capitals + ctx.set(x, y, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), 65 - 26 + j as rltk::FontCharType); + } x += 2; @@ -286,11 +288,33 @@ pub fn print_options(inventory: BTreeMap<(String, String), i32>, mut x: i32, mut } ctx.print(x, y, name.0.to_string()); } + let this_width = x - initial_x + name.0.len() as i32; + width = if width > this_width { width } else { this_width }; + y += 1; j += 1; } - return y; + return (y, width); +} + +pub fn get_max_inventory_width(inventory: &BTreeMap<(String, String), i32>) -> i32 { + let mut width: i32 = 0; + for (name, count) in inventory { + let mut this_width = name.0.len() as i32; + if count < &1 { + this_width += 4; + if name.0.ends_with("s") { + this_width += 3; + } else if ['a', 'e', 'i', 'o', 'u'].iter().any(|&v| name.0.starts_with(v)) { + this_width += 1; + } + } else { + this_width += 4; + } + width = if width > this_width { width } else { this_width }; + } + return width; } pub fn show_help(ctx: &mut Rltk) -> YesNoResult { @@ -362,19 +386,29 @@ pub fn get_player_inventory(ecs: &World) -> (BTreeMap<(String, String), i32>, BT pub fn show_inventory(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Option) { let (player_inventory, inventory_ids) = get_player_inventory(&gs.ecs); let count = player_inventory.len(); - let y = (25 - (count / 2)) as i32; - ctx.draw_box(15, y - 2, 45, (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), "Inventory"); - ctx.print_color(18, y + count as i32 + 1, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "ESC to cancel"); - print_options(player_inventory, 17, y, ctx); + let (x_offset, y_offset) = (1, 10); + + ctx.print_color( + 1 + x_offset, + 1 + y_offset, + RGB::named(rltk::WHITE), + RGB::named(rltk::BLACK), + "Interact with what item? [aA-zZ][Esc.]", + ); + + let x = 1 + x_offset; + let y = 3 + y_offset; + let width = get_max_inventory_width(&player_inventory); + ctx.draw_box(x, y, width + 2, (count + 1) as i32, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK)); + print_options(player_inventory, x + 1, y + 1, ctx); match ctx.key { None => (ItemMenuResult::NoResponse, None), Some(key) => match key { VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None), _ => { - let selection = rltk::letter_to_option(key); + let selection = letter_to_option::letter_to_option(key, ctx.shift); if selection > -1 && selection < count as i32 { return (ItemMenuResult::Selected, Some(*inventory_ids.iter().nth(selection as usize).unwrap().1)); } @@ -387,12 +421,22 @@ pub fn show_inventory(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Option pub fn drop_item_menu(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Option) { let (player_inventory, inventory_ids) = get_player_inventory(&gs.ecs); let count = player_inventory.len(); - let y = (25 - (count / 2)) as i32; - ctx.draw_box(15, y - 2, 45, (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), "Drop what?"); - ctx.print_color(18, y + count as i32 + 1, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "ESC to cancel"); - print_options(player_inventory, 17, y, ctx); + let (x_offset, y_offset) = (1, 10); + + ctx.print_color( + 1 + x_offset, + 1 + y_offset, + RGB::named(rltk::WHITE), + RGB::named(rltk::BLACK), + "Drop what? [aA-zZ][Esc.]", + ); + + let x = 1 + x_offset; + let y = 3 + y_offset; + let width = get_max_inventory_width(&player_inventory); + ctx.draw_box(x, y, width + 2, (count + 1) as i32, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK)); + print_options(player_inventory, x + 1, y + 1, ctx); match ctx.key { None => (ItemMenuResult::NoResponse, None), @@ -414,24 +458,38 @@ pub fn remove_item_menu(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Opti let names = gs.ecs.read_storage::(); let backpack = gs.ecs.read_storage::(); 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 what?"); - ctx.print_color(18, y + count as i32 + 1, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "ESC to cancel"); + let (x_offset, y_offset) = (1, 10); - let mut equippable: Vec = Vec::new(); - let mut j = 0; + ctx.print_color( + 1 + x_offset, + 1 + y_offset, + RGB::named(rltk::WHITE), + RGB::named(rltk::BLACK), + "Drop what? [aA-zZ][Esc.]", + ); + + let mut equippable: Vec<(Entity, String)> = Vec::new(); + let mut width = 3; 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(')')); + let this_name = &name.name; + let this_width = 3 + this_name.len(); + width = if width > this_width { width } else { this_width }; + equippable.push((entity, this_name.to_string())); + } - ctx.print(21, y, &name.name.to_string()); - equippable.push(entity); + let x = 1 + x_offset; + let mut y = 3 + y_offset; + + ctx.draw_box(x, y, width, (count + 1) as i32, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK)); + y += 1; + + let mut j = 0; + for (_, name) in &equippable { + ctx.set(x + 1, y, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), 97 + j as rltk::FontCharType); + ctx.print(x + 3, y, name); y += 1; j += 1; } @@ -443,7 +501,7 @@ pub fn remove_item_menu(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Opti _ => { let selection = rltk::letter_to_option(key); if selection > -1 && selection < count as i32 { - return (ItemMenuResult::Selected, Some(equippable[selection as usize])); + return (ItemMenuResult::Selected, Some(equippable[selection as usize].0)); } (ItemMenuResult::NoResponse, None) } diff --git a/src/hunger_system.rs b/src/hunger_system.rs index fcc0a3a..90cc604 100644 --- a/src/hunger_system.rs +++ b/src/hunger_system.rs @@ -71,7 +71,7 @@ impl<'a> System<'a> for HungerSystem { } } HungerState::Fainting => { - SufferDamage::new_damage(&mut inflict_damage, entity, 1); + SufferDamage::new_damage(&mut inflict_damage, entity, 1, false); if entity == *player_entity { gamelog::Logger::new().colour(rltk::RED).append("You can't go on without food...").log(); } diff --git a/src/inventory_system.rs b/src/inventory_system.rs index 9f90e7d..93d923a 100644 --- a/src/inventory_system.rs +++ b/src/inventory_system.rs @@ -298,13 +298,10 @@ impl<'a> System<'a> for ItemUseSystem { let entity_name = names.get(*mob).unwrap(); match destructible { None => { - SufferDamage::new_damage(&mut suffer_damage, *mob, damage.amount); + SufferDamage::new_damage(&mut suffer_damage, *mob, damage.amount, true); if entity == *player_entity { - damage_logger = damage_logger - .append("The") - .npc_name(&entity_name.name) - .append("is hit!") - .period(); + damage_logger = + damage_logger.append("The").npc_name(&entity_name.name).append("is hit!"); needs_damage_log = true; } } diff --git a/src/main.rs b/src/main.rs index 04c47cc..8dff816 100644 --- a/src/main.rs +++ b/src/main.rs @@ -531,6 +531,7 @@ fn main() -> rltk::BError { gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); + gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); gs.ecs.register::(); diff --git a/src/melee_combat_system.rs b/src/melee_combat_system.rs index 6994c17..3d03703 100644 --- a/src/melee_combat_system.rs +++ b/src/melee_combat_system.rs @@ -141,18 +141,22 @@ impl<'a> System<'a> for MeleeCombatSystem { armour_ac_bonus += ac.amount; } } - let mut armour_class = bac - attribute_ac_bonus - skill_ac_bonus - armour_ac_bonus; + let actual_armour_class = bac - attribute_ac_bonus - skill_ac_bonus - armour_ac_bonus; + let mut armour_class_roll = actual_armour_class; - if armour_class < 0 { - armour_class = rng.roll_dice(1, armour_class); + if actual_armour_class < 0 { + // Invert armour class so we can roll 1d(AC) + armour_class_roll = rng.roll_dice(1, -actual_armour_class); + // Invert result so it's a negative again + armour_class_roll = -armour_class_roll; } - let target_number = 10 + armour_class + attacker_bonuses; + let target_number = 10 + armour_class_roll + attacker_bonuses; if COMBAT_LOGGING { rltk::console::log(format!( "ATTACKLOG: {} *{}* {}: rolled ({}) 1d20 vs. {} (10 + {}AC + {}to-hit)", - &name.name, attack_verb, &target_name.name, d20, target_number, armour_class, attacker_bonuses + &name.name, attack_verb, &target_name.name, d20, target_number, armour_class_roll, attacker_bonuses )); } @@ -160,9 +164,19 @@ impl<'a> System<'a> for MeleeCombatSystem { // Target hit! let base_damage = rng.roll_dice(weapon_info.damage_n_dice, weapon_info.damage_die_type); let skill_damage_bonus = gamesystem::skill_bonus(Skill::Melee, &*attacker_skills); - let attribute_damage_bonus = weapon_info.damage_bonus; - let mut damage = - i32::max(0, base_damage + attribute_damage_bonus + skill_damage_bonus + attribute_damage_bonus); + let mut attribute_damage_bonus = weapon_info.damage_bonus; + match weapon_info.attribute { + WeaponAttribute::Dexterity => attribute_damage_bonus += attacker_attributes.dexterity.bonus, + WeaponAttribute::Strength => attribute_damage_bonus += attacker_attributes.strength.bonus, + WeaponAttribute::Finesse => { + if attacker_attributes.dexterity.bonus > attacker_attributes.strength.bonus { + attribute_damage_bonus += attacker_attributes.dexterity.bonus; + } else { + attribute_damage_bonus += attacker_attributes.strength.bonus; + } + } + } + let mut damage = i32::max(0, base_damage + skill_damage_bonus + attribute_damage_bonus); if COMBAT_LOGGING { rltk::console::log(format!( @@ -177,8 +191,8 @@ impl<'a> System<'a> for MeleeCombatSystem { )); } - if armour_class < 0 { - let ac_damage_reduction = rng.roll_dice(1, armour_class); + if actual_armour_class < 0 { + let ac_damage_reduction = rng.roll_dice(1, -actual_armour_class); damage = i32::min(1, damage - ac_damage_reduction); if COMBAT_LOGGING { rltk::console::log(format!( @@ -192,7 +206,7 @@ impl<'a> System<'a> for MeleeCombatSystem { if let Some(pos) = pos { particle_builder.damage_taken(pos.x, pos.y) } - SufferDamage::new_damage(&mut inflict_damage, wants_melee.target, damage); + SufferDamage::new_damage(&mut inflict_damage, wants_melee.target, damage, entity == *player_entity); if entity == *player_entity { gamelog::Logger::new() // You hit the . .append("You hit the") diff --git a/src/player.rs b/src/player.rs index 7add704..6ed7144 100644 --- a/src/player.rs +++ b/src/player.rs @@ -190,7 +190,7 @@ pub fn kick(i: i32, j: i32, ecs: &mut World) -> RunState { if map.tile_content[destination_idx].len() == 0 { if rng.roll_dice(1, 20) == 20 { let mut suffer_damage = ecs.write_storage::(); - SufferDamage::new_damage(&mut suffer_damage, entity, 1); + SufferDamage::new_damage(&mut suffer_damage, entity, 1, false); gamelog::Logger::new().append("Ouch! You kick the open air, and pull something.").log(); break; } else { diff --git a/src/raws/rawmaster.rs b/src/raws/rawmaster.rs index 48e0faf..af4c0dd 100644 --- a/src/raws/rawmaster.rs +++ b/src/raws/rawmaster.rs @@ -275,13 +275,6 @@ pub fn spawn_named_mob( let mob_mana = mana_at_level(&mut rng, mob_int, mob_level); let mob_bac = if mob_template.bac.is_some() { mob_template.bac.unwrap() } else { 10 }; - if SPAWN_LOGGING { - rltk::console::log(format!( - "SPAWNLOG: {} ({}HP, {}MANA, {}BAC) spawned at level {} ({}[base], {}[map difficulty], {}[player level])", - &mob_template.name, mob_hp, mob_mana, mob_bac, mob_level, base_mob_level, map_difficulty, player_level - )); - } - let pools = Pools { level: mob_level, xp: 0, @@ -330,6 +323,32 @@ pub fn spawn_named_mob( eb = eb.with(natural); } + let mut xp_value = 1; + xp_value += mob_level * mob_level; + // if speed > 18, +5 + // if speed > 12, +3 + if mob_bac < 0 { + xp_value += 14 + 2 * mob_bac; + } else if mob_bac == 0 { + xp_value += 7; + } else if mob_bac == 1 { + xp_value += 6; + } else if mob_bac == 2 { + xp_value += 5; + } + if mob_level > 9 { + xp_value += 50; + } + + eb = eb.with(GrantsXP { amount: xp_value }); + + if SPAWN_LOGGING { + rltk::console::log(format!( + "SPAWNLOG: {} ({}HP, {}MANA, {}BAC) spawned at level {} ({}[base], {}[map difficulty], {}[player level]), worth {} XP", + &mob_template.name, mob_hp, mob_mana, mob_bac, mob_level, base_mob_level, map_difficulty, player_level, xp_value + )); + } + let new_mob = eb.build(); // Build entity, then check for anything they're wearing @@ -338,6 +357,7 @@ pub fn spawn_named_mob( spawn_named_entity(raws, ecs, tag, SpawnType::Equipped { by: new_mob }, map_difficulty); } } + return Some(new_mob); } None @@ -421,7 +441,7 @@ pub fn table_by_name(raws: &RawMaster, key: &str, difficulty: i32) -> RandomTabl let available_options: Vec<&SpawnTableEntry> = spawn_table .table .iter() - .filter(|entry| entry.difficulty >= lower_bound && entry.difficulty <= upper_bound) + .filter(|entry| entry.difficulty > lower_bound && entry.difficulty <= upper_bound) .collect(); let mut rt = RandomTable::new(); diff --git a/src/saveload_system.rs b/src/saveload_system.rs index d72c441..64d09c6 100644 --- a/src/saveload_system.rs +++ b/src/saveload_system.rs @@ -63,6 +63,7 @@ pub fn save_game(ecs: &mut World) { EntryTrigger, Equippable, Equipped, + GrantsXP, Hidden, HungerClock, InBackpack, @@ -164,6 +165,7 @@ pub fn load_game(ecs: &mut World) { EntryTrigger, Equippable, Equipped, + GrantsXP, Hidden, HungerClock, InBackpack, diff --git a/src/spawner.rs b/src/spawner.rs index d520d2c..e213390 100644 --- a/src/spawner.rs +++ b/src/spawner.rs @@ -71,20 +71,6 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity { raws::SpawnType::Carried { by: player }, 0, ); - raws::spawn_named_entity( - &raws::RAWS.lock().unwrap(), - ecs, - "scroll_magicmissile", - raws::SpawnType::Carried { by: player }, - 0, - ); - raws::spawn_named_entity( - &raws::RAWS.lock().unwrap(), - ecs, - "scroll_fireball", - raws::SpawnType::Carried { by: player }, - 0, - ); return player; } diff --git a/src/trigger_system.rs b/src/trigger_system.rs index 5d0ee68..231c097 100644 --- a/src/trigger_system.rs +++ b/src/trigger_system.rs @@ -60,7 +60,7 @@ impl<'a> System<'a> for TriggerSystem { let damage = inflicts_damage.get(*entity_id); if let Some(damage) = damage { particle_builder.damage_taken(pos.x, pos.y); - SufferDamage::new_damage(&mut inflict_damage, entity, damage.amount); + SufferDamage::new_damage(&mut inflict_damage, entity, damage.amount, false); } let confuses = confusion.get(*entity_id);