combat system 2nd pass - multiattacks for natural attackers

This commit is contained in:
Llywelwyn 2023-07-30 05:30:01 +01:00
parent 42113ad6a4
commit a17a2c8f11
7 changed files with 247 additions and 174 deletions

View file

@ -127,7 +127,7 @@
"id": "horse_little",
"name": "pony",
"renderable": { "glyph": "u", "fg": "#b36c29", "bg": "#000000", "order": 1 },
"flags": ["BYSTANDER", "BLOCKS_TILE"],
"flags": ["BYSTANDER", "BLOCKS_TILE", "MULTIATTACK"],
"level": 3,
"bac": 6,
"vision_range": 8,
@ -140,7 +140,7 @@
"id": "horse",
"name": "horse",
"renderable": { "glyph": "u", "fg": "#744d29", "bg": "#000000", "order": 1 },
"flags": ["MONSTER", "BLOCKS_TILE"],
"flags": ["MONSTER", "BLOCKS_TILE", "MULTIATTACK"],
"level": 5,
"bac": 5,
"vision_range": 8,
@ -153,7 +153,7 @@
"id": "horse_large",
"name": "warhorse",
"renderable": { "glyph": "u", "fg": "#8a3520", "bg": "#000000", "order": 1 },
"flags": ["MONSTER", "BLOCKS_TILE"],
"flags": ["MONSTER", "BLOCKS_TILE", "MULTIATTACK"],
"level": 7,
"bac": 4,
"vision_range": 8,

View file

@ -320,3 +320,6 @@ pub struct EntryTrigger {}
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct EntityMoved {}
#[derive(Component, Debug, Serialize, Deserialize, Clone)]
pub struct MultiAttack {}

View file

@ -43,7 +43,7 @@ mod rex_assets;
extern crate lazy_static;
//Consts
pub const SHOW_MAPGEN: bool = true;
pub const SHOW_MAPGEN: bool = false;
#[derive(PartialEq, Copy, Clone)]
pub enum RunState {
@ -558,6 +558,7 @@ fn main() -> rltk::BError {
gs.ecs.register::<Hidden>();
gs.ecs.register::<EntryTrigger>();
gs.ecs.register::<EntityMoved>();
gs.ecs.register::<MultiAttack>();
gs.ecs.register::<ParticleLifetime>();
gs.ecs.register::<SimpleMarker<SerializeMe>>();
gs.ecs.register::<SerializationHelper>();

View file

@ -1,6 +1,7 @@
use super::{
gamelog, gamesystem, ArmourClassBonus, Attributes, EquipmentSlot, Equipped, HungerClock, HungerState, MeleeWeapon,
Name, NaturalAttacks, ParticleBuilder, Pools, Position, Skill, Skills, SufferDamage, WantsToMelee, WeaponAttribute,
MultiAttack, Name, NaturalAttack, NaturalAttacks, ParticleBuilder, Pools, Position, Skill, Skills, SufferDamage,
WantsToMelee, WeaponAttribute,
};
use specs::prelude::*;
@ -23,6 +24,7 @@ impl<'a> System<'a> for MeleeCombatSystem {
ReadStorage<'a, NaturalAttacks>,
ReadStorage<'a, ArmourClassBonus>,
ReadStorage<'a, HungerClock>,
ReadStorage<'a, MultiAttack>,
WriteExpect<'a, rltk::RandomNumberGenerator>,
);
@ -43,6 +45,7 @@ impl<'a> System<'a> for MeleeCombatSystem {
natural_attacks,
ac,
hunger_clock,
multi_attackers,
mut rng,
) = data;
@ -64,10 +67,44 @@ impl<'a> System<'a> for MeleeCombatSystem {
for (entity, wants_melee, name, attacker_attributes, attacker_skills, attacker_pools) in
(&entities, &wants_melee, &names, &attributes, &skills, &pools).join()
{
// Create blank vector of attacks being attempted.
let mut attacks: Vec<(MeleeWeapon, String)> = Vec::new();
let mut multi_attack = false;
// Check if attacker can multi-attack.
if let Some(_) = multi_attackers.get(entity) {
multi_attack = true;
}
// Check if attacker is using a weapon.
let mut using_weapon = false;
for (wielded, melee) in (&equipped, &melee_weapons).join() {
if wielded.owner == entity && wielded.slot == EquipmentSlot::Melee {
using_weapon = get_weapon_attack(wielded, melee, entity, &mut attacks);
}
}
// If not using a weapon, get natural attacks. If we
// are a multiattacker, get every natural attack. If
// not, just pick one at random.
if !using_weapon {
if let Some(nat) = natural_attacks.get(entity) {
get_natural_attacks(&mut rng, nat.clone(), multi_attack, &mut attacks);
} else {
attacks.push((
MeleeWeapon {
attribute: WeaponAttribute::Strength,
hit_bonus: 0,
damage_n_dice: 1,
damage_die_type: 4,
damage_bonus: 0,
},
"punches".to_string(),
));
}
}
// For every attack, do combat calcs. Break if someone dies.
for attack in attacks {
let target_pools = pools.get(wants_melee.target).unwrap();
let target_attributes = attributes.get(wants_melee.target).unwrap();
let target_skills = skills.get(wants_melee.target).unwrap();
if attacker_pools.hit_points.current <= 0 {
break;
}
@ -75,37 +112,8 @@ impl<'a> System<'a> for MeleeCombatSystem {
break;
}
let target_name = names.get(wants_melee.target).unwrap();
let mut weapon_info = MeleeWeapon {
attribute: WeaponAttribute::Strength,
hit_bonus: 0,
damage_n_dice: 1,
damage_die_type: 4,
damage_bonus: 0,
};
let mut attack_verb = "hits";
if let Some(nat) = natural_attacks.get(entity) {
if !nat.attacks.is_empty() {
let attack_index = if nat.attacks.len() == 1 {
0
} else {
rng.roll_dice(1, nat.attacks.len() as i32) as usize - 1
};
weapon_info.hit_bonus = nat.attacks[attack_index].hit_bonus;
weapon_info.damage_n_dice = nat.attacks[attack_index].damage_n_dice;
weapon_info.damage_die_type = nat.attacks[attack_index].damage_die_type;
weapon_info.damage_bonus = nat.attacks[attack_index].damage_bonus;
attack_verb = &nat.attacks[attack_index].name;
}
}
for (wielded, melee) in (&equipped, &melee_weapons).join() {
if wielded.owner == entity && wielded.slot == EquipmentSlot::Melee {
weapon_info = melee.clone();
}
}
let weapon_info = attack.0;
let attack_verb = attack.1;
// Get all offensive bonuses
let d20 = rng.roll_dice(1, 20);
@ -153,10 +161,17 @@ impl<'a> System<'a> for MeleeCombatSystem {
let target_number = 10 + armour_class_roll + attacker_bonuses;
let target_name = names.get(wants_melee.target).unwrap();
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_roll, attacker_bonuses
&name.name,
attack_verb,
&target_name.name,
d20,
target_number,
armour_class_roll,
attacker_bonuses
));
}
@ -262,7 +277,58 @@ impl<'a> System<'a> for MeleeCombatSystem {
}
}
}
}
wants_melee.clear();
}
}
fn get_natural_attacks(
rng: &mut rltk::RandomNumberGenerator,
nat: NaturalAttacks,
multi_attack: bool,
attacks: &mut Vec<(MeleeWeapon, String)>,
) {
if !nat.attacks.is_empty() {
if multi_attack {
for a in nat.attacks.iter() {
attacks.push((
MeleeWeapon {
attribute: WeaponAttribute::Strength,
hit_bonus: a.hit_bonus,
damage_n_dice: a.damage_n_dice,
damage_die_type: a.damage_die_type,
damage_bonus: a.damage_bonus,
},
a.name.to_string(),
));
}
} else {
let attack_index =
if nat.attacks.len() == 1 { 0 } else { rng.roll_dice(1, nat.attacks.len() as i32) as usize - 1 };
attacks.push((
MeleeWeapon {
attribute: WeaponAttribute::Strength,
hit_bonus: nat.attacks[attack_index].hit_bonus,
damage_n_dice: nat.attacks[attack_index].damage_n_dice,
damage_die_type: nat.attacks[attack_index].damage_die_type,
damage_bonus: nat.attacks[attack_index].damage_bonus,
},
nat.attacks[attack_index].name.to_string(),
));
}
}
}
fn get_weapon_attack(
wielded: &Equipped,
melee: &MeleeWeapon,
entity: Entity,
attacks: &mut Vec<(MeleeWeapon, String)>,
) -> bool {
if wielded.owner == entity && wielded.slot == EquipmentSlot::Melee {
attacks.push((melee.clone(), "hits".to_string()));
return true;
}
return false;
}

View file

@ -209,6 +209,7 @@ pub fn spawn_named_mob(
"BLOCKS_TILE" => eb = eb.with(BlocksTile {}),
"BYSTANDER" => eb = eb.with(Bystander {}),
"MONSTER" => eb = eb.with(Monster {}),
"MULTIATTACK" => eb = eb.with(MultiAttack {}),
_ => rltk::console::log(format!("Unrecognised flag: {}", flag.as_str())),
}
}

View file

@ -73,6 +73,7 @@ pub fn save_game(ecs: &mut World) {
MeleeWeapon,
Mind,
Monster,
MultiAttack,
NaturalAttacks,
Name,
ParticleLifetime,
@ -175,6 +176,7 @@ pub fn load_game(ecs: &mut World) {
MeleeWeapon,
Mind,
Monster,
MultiAttack,
NaturalAttacks,
Name,
ParticleLifetime,