levels, and ui changes

This commit is contained in:
Llywelwyn 2023-07-29 05:56:52 +01:00
parent be2c8a35a5
commit 3dab5202f8
15 changed files with 337 additions and 104 deletions

View file

@ -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<i32>,
pub amount: Vec<(i32, bool)>,
}
impl SufferDamage {
pub fn new_damage(store: &mut WriteStorage<SufferDamage>, victim: Entity, amount: i32) {
pub fn new_damage(store: &mut WriteStorage<SufferDamage>, 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.");
}
}

View file

@ -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::<i32>();
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;
}
}

View file

@ -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;

View file

@ -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 {

View file

@ -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,
}
}
}

View file

@ -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::<Name>();
let backpack = ecs.read_storage::<InBackpack>();
let equippables = ecs.read_storage::<Equippable>();
let mut equipment: Vec<String> = 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::<Viewshed>();
@ -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<Entity>) {
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<Entity>) {
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::<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 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<Entity> = 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)
}

View file

@ -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();
}

View file

@ -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;
}
}

View file

@ -531,6 +531,7 @@ fn main() -> rltk::BError {
gs.ecs.register::<WantsToMelee>();
gs.ecs.register::<SufferDamage>();
gs.ecs.register::<Item>();
gs.ecs.register::<GrantsXP>();
gs.ecs.register::<Equippable>();
gs.ecs.register::<Equipped>();
gs.ecs.register::<MeleeWeapon>();

View file

@ -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 <name>.
.append("You hit the")

View file

@ -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>();
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 {

View file

@ -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();

View file

@ -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,

View file

@ -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;
}

View file

@ -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);