fixed some attributes double-dipping, and added COMBAT_LOGGING
This commit is contained in:
parent
c2759e9e8e
commit
6e6d364aa5
5 changed files with 119 additions and 19 deletions
|
|
@ -97,7 +97,7 @@
|
|||
"name": "giant rat",
|
||||
"renderable": { "glyph": "r", "fg": "#bb8000", "bg": "#000000", "order": 1 },
|
||||
"flags": ["MONSTER", "BLOCKS_TILE"],
|
||||
"level": 2,
|
||||
"level": 1,
|
||||
"bac": 7,
|
||||
"vision_range": 8,
|
||||
"attacks": [
|
||||
|
|
@ -133,6 +133,7 @@
|
|||
"name": "goblin",
|
||||
"renderable": { "glyph": "g", "fg": "#00FF00", "bg": "#000000", "order": 1 },
|
||||
"flags": ["MONSTER", "BLOCKS_TILE"],
|
||||
"level": 1,
|
||||
"vision_range": 12,
|
||||
"attacks": [
|
||||
{ "name": "hits", "hit_bonus": 0, "damage": "1d4"}
|
||||
|
|
@ -143,6 +144,7 @@
|
|||
"name": "kobold",
|
||||
"renderable": { "glyph": "k", "fg": "#AA5500", "bg": "#000000", "order": 1 },
|
||||
"flags": ["MONSTER", "BLOCKS_TILE"],
|
||||
"level": 1,
|
||||
"vision_range": 7,
|
||||
"attacks": [
|
||||
{ "name": "hits", "hit_bonus": 0, "damage": "1d4"}
|
||||
|
|
@ -175,6 +177,7 @@
|
|||
"name": "coyote",
|
||||
"renderable": { "glyph": "d", "fg": "#6E3215", "bg": "#000000", "order": 1 },
|
||||
"flags": ["MONSTER", "BLOCKS_TILE"],
|
||||
"level": 1,
|
||||
"bac": 7,
|
||||
"vision_range": 12,
|
||||
"attacks": [
|
||||
|
|
@ -186,6 +189,7 @@
|
|||
"name": "wolf",
|
||||
"renderable": { "glyph": "d", "fg": "#5E4225", "bg": "#000000", "order": 1 },
|
||||
"flags": ["MONSTER", "BLOCKS_TILE"],
|
||||
"level": 5,
|
||||
"bac": 4,
|
||||
"vision_range": 12,
|
||||
"attacks": [
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ pub fn player_hp_at_level(rng: &mut rltk::RandomNumberGenerator, constitution: i
|
|||
}
|
||||
|
||||
pub fn npc_hp(rng: &mut rltk::RandomNumberGenerator, constitution: i32, level: i32) -> i32 {
|
||||
if level == 0 {
|
||||
return rng.roll_dice(1, 4);
|
||||
}
|
||||
let mut total = 1;
|
||||
for _i in 0..level {
|
||||
total += rng.roll_dice(1, 8) + attr_bonus(constitution);
|
||||
|
|
|
|||
|
|
@ -46,6 +46,21 @@ impl<'a> System<'a> for MeleeCombatSystem {
|
|||
mut rng,
|
||||
) = data;
|
||||
|
||||
// Combat works with the older system of AC being a bonus to to-hit to the attacker. When an
|
||||
// attacker tries to hit a victim, the attacker rolls a d20, and must roll *less than* the
|
||||
// value of 10 + victim's AC + attacker's to-hit bonuses.
|
||||
//
|
||||
// e.g. An attacker with +0 to-hit hitting a target with 10 AC:
|
||||
// 1d20 must be less than 20, 95% chance of a hit.
|
||||
//
|
||||
// e.g. An attacker with +1 to-hit from being satiated hits a rat with 7 AC:
|
||||
// 1d20 must be less than 18 (10+7+1), 85% chance of a hit.
|
||||
//
|
||||
// e.g. An attacker with +0 to-hit hitting a target with 0 AC:
|
||||
// 1d20 must be less than 10, 45% chance of a hit
|
||||
|
||||
const COMBAT_LOGGING: bool = true;
|
||||
|
||||
for (entity, wants_melee, name, attacker_attributes, attacker_skills, attacker_pools) in
|
||||
(&entities, &wants_melee, &names, &attributes, &skills, &pools).join()
|
||||
{
|
||||
|
|
@ -72,7 +87,6 @@ impl<'a> System<'a> for MeleeCombatSystem {
|
|||
let mut attack_verb = "hits";
|
||||
|
||||
if let Some(nat) = natural_attacks.get(entity) {
|
||||
rltk::console::log("Natural attack found");
|
||||
if !nat.attacks.is_empty() {
|
||||
let attack_index = if nat.attacks.len() == 1 {
|
||||
0
|
||||
|
|
@ -95,7 +109,7 @@ impl<'a> System<'a> for MeleeCombatSystem {
|
|||
|
||||
// Get all offensive bonuses
|
||||
let d20 = rng.roll_dice(1, 20);
|
||||
let attribute_hit_bonus = attacker_attributes.strength.bonus;
|
||||
let attribute_hit_bonus = attacker_attributes.dexterity.bonus;
|
||||
let skill_hit_bonus = gamesystem::skill_bonus(Skill::Melee, &*attacker_skills);
|
||||
let weapon_hit_bonus = weapon_info.hit_bonus;
|
||||
let mut status_hit_bonus = 0;
|
||||
|
|
@ -114,7 +128,8 @@ impl<'a> System<'a> for MeleeCombatSystem {
|
|||
_ => {}
|
||||
}
|
||||
}
|
||||
let modified_hit_roll = d20 - attribute_hit_bonus - skill_hit_bonus - weapon_hit_bonus - status_hit_bonus;
|
||||
let attacker_bonuses =
|
||||
attacker_pools.level + attribute_hit_bonus + skill_hit_bonus + weapon_hit_bonus + status_hit_bonus;
|
||||
|
||||
// Get armour class
|
||||
let bac = target_pools.bac;
|
||||
|
|
@ -126,18 +141,54 @@ impl<'a> System<'a> for MeleeCombatSystem {
|
|||
armour_ac_bonus += ac.amount;
|
||||
}
|
||||
}
|
||||
let armour_class = bac - attribute_ac_bonus - skill_ac_bonus - armour_ac_bonus;
|
||||
let mut armour_class = bac - attribute_ac_bonus - skill_ac_bonus - armour_ac_bonus;
|
||||
|
||||
let target_number = 10 + armour_class + attacker_pools.level;
|
||||
if armour_class < 0 {
|
||||
armour_class = rng.roll_dice(1, armour_class);
|
||||
}
|
||||
|
||||
if d20 != 1 && (d20 == 20 || modified_hit_roll < target_number) {
|
||||
let target_number = 10 + armour_class + attacker_bonuses;
|
||||
|
||||
if COMBAT_LOGGING {
|
||||
rltk::console::log(format!(
|
||||
"ATTACKLOG: {} *ATTACKED* {}: rolled ({}) 1d20 vs. {} (10 + {}AC + {}to-hit)",
|
||||
&name.name, &target_name.name, d20, target_number, armour_class, attacker_bonuses
|
||||
));
|
||||
}
|
||||
|
||||
if d20 < target_number {
|
||||
// Target hit!
|
||||
let base_damage = rng.roll_dice(weapon_info.damage_n_dice, weapon_info.damage_die_type);
|
||||
let attribute_damage_bonus = attacker_attributes.strength.bonus;
|
||||
let skill_damage_bonus = gamesystem::skill_bonus(Skill::Melee, &*attacker_skills);
|
||||
let weapon_damage_bonus = weapon_info.damage_bonus;
|
||||
let damage =
|
||||
i32::max(0, base_damage + attribute_damage_bonus + skill_damage_bonus + weapon_damage_bonus);
|
||||
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);
|
||||
|
||||
if COMBAT_LOGGING {
|
||||
rltk::console::log(format!(
|
||||
"ATTACKLOG: {} *{}* {} for {} ({}[{}d{}]+{}[skill]+{}[attr])",
|
||||
&name.name,
|
||||
attack_verb,
|
||||
&target_name.name,
|
||||
damage,
|
||||
base_damage,
|
||||
weapon_info.damage_n_dice,
|
||||
weapon_info.damage_die_type,
|
||||
skill_damage_bonus,
|
||||
attribute_damage_bonus
|
||||
));
|
||||
}
|
||||
|
||||
if armour_class < 0 {
|
||||
let ac_damage_reduction = rng.roll_dice(1, armour_class);
|
||||
damage = i32::min(1, damage - ac_damage_reduction);
|
||||
if COMBAT_LOGGING {
|
||||
rltk::console::log(format!(
|
||||
"ATTACKLOG: {} reduced their damage taken by {} (1dAC), and took {} hp damage.",
|
||||
&target_name.name, ac_damage_reduction, damage
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let pos = positions.get(wants_melee.target);
|
||||
if let Some(pos) = pos {
|
||||
|
|
@ -168,6 +219,10 @@ impl<'a> System<'a> for MeleeCombatSystem {
|
|||
.log();
|
||||
}
|
||||
} else {
|
||||
if COMBAT_LOGGING {
|
||||
rltk::console::log(format!("ATTACKLOG: {} *MISSED* {}", &name.name, &target_name.name,));
|
||||
}
|
||||
|
||||
let pos = positions.get(wants_melee.target);
|
||||
if let Some(pos) = pos {
|
||||
particle_builder.attack_miss(pos.x, pos.y)
|
||||
|
|
|
|||
|
|
@ -72,11 +72,17 @@ impl RawMaster {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn spawn_named_entity(raws: &RawMaster, ecs: &mut World, key: &str, pos: SpawnType) -> Option<Entity> {
|
||||
pub fn spawn_named_entity(
|
||||
raws: &RawMaster,
|
||||
ecs: &mut World,
|
||||
key: &str,
|
||||
pos: SpawnType,
|
||||
map_difficulty: i32,
|
||||
) -> Option<Entity> {
|
||||
if raws.item_index.contains_key(key) {
|
||||
return spawn_named_item(raws, ecs, key, pos);
|
||||
} else if raws.mob_index.contains_key(key) {
|
||||
return spawn_named_mob(raws, ecs, key, pos);
|
||||
return spawn_named_mob(raws, ecs, key, pos, map_difficulty);
|
||||
} else if raws.prop_index.contains_key(key) {
|
||||
return spawn_named_prop(raws, ecs, key, pos);
|
||||
}
|
||||
|
|
@ -166,7 +172,13 @@ pub fn spawn_named_item(raws: &RawMaster, ecs: &mut World, key: &str, pos: Spawn
|
|||
None
|
||||
}
|
||||
|
||||
pub fn spawn_named_mob(raws: &RawMaster, ecs: &mut World, key: &str, pos: SpawnType) -> Option<Entity> {
|
||||
pub fn spawn_named_mob(
|
||||
raws: &RawMaster,
|
||||
ecs: &mut World,
|
||||
key: &str,
|
||||
pos: SpawnType,
|
||||
map_difficulty: i32,
|
||||
) -> Option<Entity> {
|
||||
if raws.mob_index.contains_key(key) {
|
||||
let mob_template = &raws.raws.mobs[raws.mob_index[key]];
|
||||
|
||||
|
|
@ -228,7 +240,20 @@ pub fn spawn_named_mob(raws: &RawMaster, ecs: &mut World, key: &str, pos: SpawnT
|
|||
}
|
||||
}
|
||||
eb = eb.with(attr);
|
||||
let mob_level = if mob_template.level.is_some() { mob_template.level.unwrap() } else { 1 };
|
||||
|
||||
let base_mob_level = if mob_template.level.is_some() { mob_template.level.unwrap() } else { 0 };
|
||||
let mut mob_level = base_mob_level;
|
||||
let difficulty = 0;
|
||||
if base_mob_level > difficulty {
|
||||
mob_level -= 1;
|
||||
} else if base_mob_level < difficulty {
|
||||
mob_level += (difficulty - base_mob_level) / 5;
|
||||
|
||||
if mob_level as f32 > 1.5 * base_mob_level as f32 {
|
||||
let mob_levelf32 = (1.5 * base_mob_level as f32).trunc();
|
||||
mob_level = mob_levelf32 as i32;
|
||||
}
|
||||
}
|
||||
|
||||
// Should really use existing RNG here
|
||||
let mut rng = rltk::RandomNumberGenerator::new();
|
||||
|
|
@ -290,7 +315,7 @@ pub fn spawn_named_mob(raws: &RawMaster, ecs: &mut World, key: &str, pos: SpawnT
|
|||
// 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 });
|
||||
spawn_named_entity(raws, ecs, tag, SpawnType::Equipped { by: new_mob }, map_difficulty);
|
||||
}
|
||||
}
|
||||
return Some(new_mob);
|
||||
|
|
|
|||
|
|
@ -62,8 +62,15 @@ pub fn player(ecs: &mut World, player_x: i32, player_y: i32) -> Entity {
|
|||
ecs,
|
||||
"equip_dagger",
|
||||
raws::SpawnType::Equipped { by: player },
|
||||
0,
|
||||
);
|
||||
raws::spawn_named_entity(
|
||||
&raws::RAWS.lock().unwrap(),
|
||||
ecs,
|
||||
"food_apple",
|
||||
raws::SpawnType::Carried { by: player },
|
||||
0,
|
||||
);
|
||||
raws::spawn_named_entity(&raws::RAWS.lock().unwrap(), ecs, "food_apple", raws::SpawnType::Carried { by: player });
|
||||
|
||||
return player;
|
||||
}
|
||||
|
|
@ -143,13 +150,19 @@ pub fn spawn_region(map: &Map, rng: &mut RandomNumberGenerator, area: &[usize],
|
|||
/// Spawns a named entity (name in tuple.1) at the location in (tuple.0)
|
||||
pub fn spawn_entity(ecs: &mut World, spawn: &(&usize, &String)) {
|
||||
let map = ecs.fetch::<Map>();
|
||||
let map_difficulty = map.difficulty;
|
||||
let width = map.width as usize;
|
||||
let x = (*spawn.0 % width) as i32;
|
||||
let y = (*spawn.0 / width) as i32;
|
||||
std::mem::drop(map);
|
||||
|
||||
let spawn_result =
|
||||
raws::spawn_named_entity(&raws::RAWS.lock().unwrap(), ecs, &spawn.1, raws::SpawnType::AtPosition { x, y });
|
||||
let spawn_result = raws::spawn_named_entity(
|
||||
&raws::RAWS.lock().unwrap(),
|
||||
ecs,
|
||||
&spawn.1,
|
||||
raws::SpawnType::AtPosition { x, y },
|
||||
map_difficulty,
|
||||
);
|
||||
if spawn_result.is_some() {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue