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, pub target: Entity,
} }
#[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct GrantsXP {
pub amount: i32,
}
#[derive(Component, Debug, ConvertSaveload, Clone)] #[derive(Component, Debug, ConvertSaveload, Clone)]
pub struct SufferDamage { pub struct SufferDamage {
pub amount: Vec<i32>, pub amount: Vec<(i32, bool)>,
} }
impl SufferDamage { 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) { if let Some(suffering) = store.get_mut(victim) {
suffering.amount.push(amount); suffering.amount.push((amount, from_player));
} else { } else {
let dmg = SufferDamage { amount: vec![amount] }; let dmg = SufferDamage { amount: vec![(amount, from_player)] };
store.insert(victim, dmg).expect("Unable to insert damage."); 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::*; use specs::prelude::*;
pub struct DamageSystem {} pub struct DamageSystem {}
impl<'a> System<'a> for DamageSystem { impl<'a> System<'a> for DamageSystem {
#[allow(clippy::type_complexity)]
type SystemData = ( type SystemData = (
WriteStorage<'a, Pools>, WriteStorage<'a, Pools>,
WriteStorage<'a, SufferDamage>, WriteStorage<'a, SufferDamage>,
WriteExpect<'a, Map>,
ReadStorage<'a, Position>, ReadStorage<'a, Position>,
WriteExpect<'a, Map>,
Entities<'a>, 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) { 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() { for (entity, mut stats, damage) in (&entities, &mut stats, &damage).join() {
stats.hit_points.current -= damage.amount.iter().sum::<i32>(); for dmg in damage.amount.iter() {
let pos = positions.get(entity); stats.hit_points.current -= dmg.0;
if let Some(pos) = pos { let pos = positions.get(entity);
let idx = map.xy_idx(pos.x, pos.y); if let Some(pos) = pos {
map.bloodstains.insert(idx); 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. /// Appends text in RED to the current message logger.
#[allow(dead_code)]
pub fn damage(mut self, damage: i32) -> Self { pub fn damage(mut self, damage: i32) -> Self {
self.fragments.push(LogFragment { colour: RGB::named(rltk::RED), text: format!("{} ", damage).to_string() }); self.fragments.push(LogFragment { colour: RGB::named(rltk::RED), text: format!("{} ", damage).to_string() });
return self; 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); 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 { pub fn player_hp_at_level(rng: &mut rltk::RandomNumberGenerator, constitution: i32, level: i32) -> i32 {
let mut total = 10 + attr_bonus(constitution); let mut total = 10 + attr_bonus(constitution);
for _i in 0..level { 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::{ use super::{
camera, gamelog, gamesystem, rex_assets::RexAssets, ArmourClassBonus, Attributes, Consumable, Equippable, Equipped, camera, gamelog, gamesystem, rex_assets::RexAssets, ArmourClassBonus, Attributes, Equipped, Hidden, HungerClock,
Hidden, HungerClock, HungerState, InBackpack, Map, Name, Player, Point, Pools, Position, Prop, Renderable, HungerState, InBackpack, Map, Name, Player, Point, Pools, Position, Prop, Renderable, RunState, Skill, Skills,
RunState, Skill, Skills, State, Viewshed, State, Viewshed,
}; };
use rltk::{Rltk, VirtualKeyCode, RGB}; use rltk::{Rltk, VirtualKeyCode, RGB};
use specs::prelude::*; use specs::prelude::*;
use std::collections::BTreeMap; use std::collections::BTreeMap;
mod letter_to_option;
mod tooltip; mod tooltip;
pub fn draw_lerping_bar( pub fn draw_lerping_bar(
@ -92,18 +93,19 @@ pub fn draw_ui(ecs: &World, ctx: &mut Rltk) {
format!("XP{}/{}", stats.level, stats.xp), format!("XP{}/{}", stats.level, stats.xp),
); );
// Draw attributes // Draw attributes
ctx.print_color(36, 53, RGB::named(rltk::RED), RGB::named(rltk::BLACK), "STR"); let x = 38;
ctx.print_color(39, 53, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), attributes.strength.base); ctx.print_color(x, 53, RGB::named(rltk::RED), RGB::named(rltk::BLACK), "STR");
ctx.print_color(43, 53, RGB::named(rltk::GREEN), RGB::named(rltk::BLACK), "DEX"); ctx.print_color(x + 3, 53, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), attributes.strength.base);
ctx.print_color(46, 53, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), attributes.dexterity.base); ctx.print_color(x + 7, 53, RGB::named(rltk::GREEN), RGB::named(rltk::BLACK), "DEX");
ctx.print_color(50, 53, RGB::named(rltk::ORANGE), RGB::named(rltk::BLACK), "CON"); ctx.print_color(x + 10, 53, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), attributes.dexterity.base);
ctx.print_color(53, 53, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), attributes.constitution.base); ctx.print_color(x + 14, 53, RGB::named(rltk::ORANGE), RGB::named(rltk::BLACK), "CON");
ctx.print_color(36, 54, RGB::named(rltk::CYAN), RGB::named(rltk::BLACK), "INT"); ctx.print_color(x + 17, 53, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), attributes.constitution.base);
ctx.print_color(39, 54, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), attributes.intelligence.base); ctx.print_color(x, 54, RGB::named(rltk::CYAN), RGB::named(rltk::BLACK), "INT");
ctx.print_color(43, 54, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "WIS"); ctx.print_color(x + 3, 54, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), attributes.intelligence.base);
ctx.print_color(46, 54, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), attributes.wisdom.base); ctx.print_color(x + 7, 54, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), "WIS");
ctx.print_color(50, 54, RGB::named(rltk::PURPLE), RGB::named(rltk::BLACK), "CHA"); ctx.print_color(x + 10, 54, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), attributes.wisdom.base);
ctx.print_color(53, 54, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), attributes.charisma.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 // Draw hunger
match hunger.state { match hunger.state {
HungerState::Satiated => { HungerState::Satiated => {
@ -123,14 +125,7 @@ pub fn draw_ui(ecs: &World, ctx: &mut Rltk) {
// Draw equipment // Draw equipment
let names = ecs.read_storage::<Name>(); 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(); 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) { for (_equipped, name) in (&equipped, &names).join().filter(|item| item.0.owner == *player_entity) {
equipment.push(format!("- {} (worn)", &name.name)); 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"); ctx.print_color(72, y, RGB::named(rltk::BLACK), RGB::named(rltk::WHITE), "Backpack");
y += 1; y += 1;
let (player_inventory, _inventory_ids) = get_player_inventory(&ecs); 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 // Draw entities seen on screen
let viewsheds = ecs.read_storage::<Viewshed>(); let viewsheds = ecs.read_storage::<Viewshed>();
@ -231,6 +226,7 @@ pub fn get_input_direction(
match ctx.key { match ctx.key {
None => return RunState::ActionWithDirection { function }, None => return RunState::ActionWithDirection { function },
Some(key) => match key { Some(key) => match key {
VirtualKeyCode::Escape => return RunState::AwaitingInput,
// Cardinals // Cardinals
VirtualKeyCode::Left | VirtualKeyCode::Numpad4 | VirtualKeyCode::H => return function(-1, 0, ecs), VirtualKeyCode::Left | VirtualKeyCode::Numpad4 | VirtualKeyCode::H => return function(-1, 0, ecs),
VirtualKeyCode::Right | VirtualKeyCode::Numpad6 | VirtualKeyCode::L => return function(1, 0, ecs), VirtualKeyCode::Right | VirtualKeyCode::Numpad6 | VirtualKeyCode::L => return function(1, 0, ecs),
@ -253,13 +249,19 @@ pub enum ItemMenuResult {
Selected, 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 mut j = 0;
let initial_x: i32 = x; let initial_x: i32 = x;
let mut width: i32 = -1;
for (name, item_count) in &inventory { for (name, item_count) in &inventory {
x = initial_x; x = initial_x;
// Print the character required to access this item. i.e. (a) // 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; 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()); 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; y += 1;
j += 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 { 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>) { pub fn show_inventory(gs: &mut State, ctx: &mut Rltk) -> (ItemMenuResult, Option<Entity>) {
let (player_inventory, inventory_ids) = get_player_inventory(&gs.ecs); let (player_inventory, inventory_ids) = get_player_inventory(&gs.ecs);
let count = player_inventory.len(); 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 { match ctx.key {
None => (ItemMenuResult::NoResponse, None), None => (ItemMenuResult::NoResponse, None),
Some(key) => match key { Some(key) => match key {
VirtualKeyCode::Escape => (ItemMenuResult::Cancel, None), 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 { if selection > -1 && selection < count as i32 {
return (ItemMenuResult::Selected, Some(*inventory_ids.iter().nth(selection as usize).unwrap().1)); 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>) { 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 (player_inventory, inventory_ids) = get_player_inventory(&gs.ecs);
let count = player_inventory.len(); 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 { match ctx.key {
None => (ItemMenuResult::NoResponse, None), 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 names = gs.ecs.read_storage::<Name>();
let backpack = gs.ecs.read_storage::<Equipped>(); let backpack = gs.ecs.read_storage::<Equipped>();
let entities = gs.ecs.entities(); let entities = gs.ecs.entities();
let inventory = (&backpack, &names).join().filter(|item| item.0.owner == *player_entity); let inventory = (&backpack, &names).join().filter(|item| item.0.owner == *player_entity);
let count = inventory.count(); let count = inventory.count();
let mut y = (25 - (count / 2)) as i32; let (x_offset, y_offset) = (1, 10);
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 mut equippable: Vec<Entity> = Vec::new(); ctx.print_color(
let mut j = 0; 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) { 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('(')); let this_name = &name.name;
ctx.set(18, y, RGB::named(rltk::YELLOW), RGB::named(rltk::BLACK), 97 + j as rltk::FontCharType); let this_width = 3 + this_name.len();
ctx.set(19, y, RGB::named(rltk::WHITE), RGB::named(rltk::BLACK), rltk::to_cp437(')')); width = if width > this_width { width } else { this_width };
equippable.push((entity, this_name.to_string()));
}
ctx.print(21, y, &name.name.to_string()); let x = 1 + x_offset;
equippable.push(entity); 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; y += 1;
j += 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); let selection = rltk::letter_to_option(key);
if selection > -1 && selection < count as i32 { 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) (ItemMenuResult::NoResponse, None)
} }

View file

@ -71,7 +71,7 @@ impl<'a> System<'a> for HungerSystem {
} }
} }
HungerState::Fainting => { HungerState::Fainting => {
SufferDamage::new_damage(&mut inflict_damage, entity, 1); SufferDamage::new_damage(&mut inflict_damage, entity, 1, false);
if entity == *player_entity { if entity == *player_entity {
gamelog::Logger::new().colour(rltk::RED).append("You can't go on without food...").log(); 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(); let entity_name = names.get(*mob).unwrap();
match destructible { match destructible {
None => { None => {
SufferDamage::new_damage(&mut suffer_damage, *mob, damage.amount); SufferDamage::new_damage(&mut suffer_damage, *mob, damage.amount, true);
if entity == *player_entity { if entity == *player_entity {
damage_logger = damage_logger damage_logger =
.append("The") damage_logger.append("The").npc_name(&entity_name.name).append("is hit!");
.npc_name(&entity_name.name)
.append("is hit!")
.period();
needs_damage_log = true; needs_damage_log = true;
} }
} }

View file

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

View file

@ -141,18 +141,22 @@ impl<'a> System<'a> for MeleeCombatSystem {
armour_ac_bonus += ac.amount; 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 { if actual_armour_class < 0 {
armour_class = rng.roll_dice(1, armour_class); // 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 { if COMBAT_LOGGING {
rltk::console::log(format!( rltk::console::log(format!(
"ATTACKLOG: {} *{}* {}: rolled ({}) 1d20 vs. {} (10 + {}AC + {}to-hit)", "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! // Target hit!
let base_damage = rng.roll_dice(weapon_info.damage_n_dice, weapon_info.damage_die_type); 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 skill_damage_bonus = gamesystem::skill_bonus(Skill::Melee, &*attacker_skills);
let attribute_damage_bonus = weapon_info.damage_bonus; let mut attribute_damage_bonus = weapon_info.damage_bonus;
let mut damage = match weapon_info.attribute {
i32::max(0, base_damage + attribute_damage_bonus + skill_damage_bonus + attribute_damage_bonus); 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 { if COMBAT_LOGGING {
rltk::console::log(format!( rltk::console::log(format!(
@ -177,8 +191,8 @@ impl<'a> System<'a> for MeleeCombatSystem {
)); ));
} }
if armour_class < 0 { if actual_armour_class < 0 {
let ac_damage_reduction = rng.roll_dice(1, armour_class); let ac_damage_reduction = rng.roll_dice(1, -actual_armour_class);
damage = i32::min(1, damage - ac_damage_reduction); damage = i32::min(1, damage - ac_damage_reduction);
if COMBAT_LOGGING { if COMBAT_LOGGING {
rltk::console::log(format!( rltk::console::log(format!(
@ -192,7 +206,7 @@ impl<'a> System<'a> for MeleeCombatSystem {
if let Some(pos) = pos { if let Some(pos) = pos {
particle_builder.damage_taken(pos.x, pos.y) 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 { if entity == *player_entity {
gamelog::Logger::new() // You hit the <name>. gamelog::Logger::new() // You hit the <name>.
.append("You hit the") .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 map.tile_content[destination_idx].len() == 0 {
if rng.roll_dice(1, 20) == 20 { if rng.roll_dice(1, 20) == 20 {
let mut suffer_damage = ecs.write_storage::<SufferDamage>(); 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(); gamelog::Logger::new().append("Ouch! You kick the open air, and pull something.").log();
break; break;
} else { } 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_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 }; 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 { let pools = Pools {
level: mob_level, level: mob_level,
xp: 0, xp: 0,
@ -330,6 +323,32 @@ pub fn spawn_named_mob(
eb = eb.with(natural); 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(); let new_mob = eb.build();
// Build entity, then check for anything they're wearing // 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); spawn_named_entity(raws, ecs, tag, SpawnType::Equipped { by: new_mob }, map_difficulty);
} }
} }
return Some(new_mob); return Some(new_mob);
} }
None None
@ -421,7 +441,7 @@ pub fn table_by_name(raws: &RawMaster, key: &str, difficulty: i32) -> RandomTabl
let available_options: Vec<&SpawnTableEntry> = spawn_table let available_options: Vec<&SpawnTableEntry> = spawn_table
.table .table
.iter() .iter()
.filter(|entry| entry.difficulty >= lower_bound && entry.difficulty <= upper_bound) .filter(|entry| entry.difficulty > lower_bound && entry.difficulty <= upper_bound)
.collect(); .collect();
let mut rt = RandomTable::new(); let mut rt = RandomTable::new();

View file

@ -63,6 +63,7 @@ pub fn save_game(ecs: &mut World) {
EntryTrigger, EntryTrigger,
Equippable, Equippable,
Equipped, Equipped,
GrantsXP,
Hidden, Hidden,
HungerClock, HungerClock,
InBackpack, InBackpack,
@ -164,6 +165,7 @@ pub fn load_game(ecs: &mut World) {
EntryTrigger, EntryTrigger,
Equippable, Equippable,
Equipped, Equipped,
GrantsXP,
Hidden, Hidden,
HungerClock, HungerClock,
InBackpack, 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 }, raws::SpawnType::Carried { by: player },
0, 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; return player;
} }

View file

@ -60,7 +60,7 @@ impl<'a> System<'a> for TriggerSystem {
let damage = inflicts_damage.get(*entity_id); let damage = inflicts_damage.get(*entity_id);
if let Some(damage) = damage { if let Some(damage) = damage {
particle_builder.damage_taken(pos.x, pos.y); 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); let confuses = confusion.get(*entity_id);