Implemented the PATROL tag for creatures
parent
23e7ea5f06
commit
cef8f23521
|
|
@ -4,6 +4,7 @@
|
|||
[AC:12]
|
||||
[ATTACK:2:1:8]
|
||||
[HUMANOID]
|
||||
[INTELLIGENT]
|
||||
[PATROL]
|
||||
|
||||
[CREATURE:SPIDER]
|
||||
|
|
@ -14,3 +15,18 @@
|
|||
[MULTIATTACK:2]
|
||||
[ANIMAL]
|
||||
[PREFER_DARK]
|
||||
|
||||
[CREATURE:DRAGON]
|
||||
[NAME:dragon:dragons]
|
||||
[HEALTH:180]
|
||||
[AC:17]
|
||||
[ATTACK:10:10:40]
|
||||
[INTELLIGENT]
|
||||
[PATROL]
|
||||
|
||||
[CREATURE:INDESTRUCTIBLE]
|
||||
[NAME:indestructible:indestructibles]
|
||||
[HEALTH:1]
|
||||
[AC:0]
|
||||
[INVULNERABLE]
|
||||
[ATTACK:0:0:0]
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
[SITE:CAVE]
|
||||
[SIZE:1:3]
|
||||
[UNDERGROUND]
|
||||
[NATURAL]
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ pub struct SiteDef {
|
|||
pub min_size: i32,
|
||||
pub max_size: i32,
|
||||
pub is_underground: bool,
|
||||
pub is_natural: bool,
|
||||
}
|
||||
|
||||
impl SiteDef {
|
||||
|
|
@ -47,6 +48,7 @@ impl SiteDef {
|
|||
match tag.name.as_str() {
|
||||
"SIZE" => (result.min_size, result.max_size) = tag.parse_value().unwrap(),
|
||||
"UNDERGROUND" => result.is_underground = true,
|
||||
"NATURAL" => result.is_natural = true,
|
||||
_ => {
|
||||
eprintln!("Unknown tag '{}' in SITE defs", tag.name);
|
||||
}
|
||||
|
|
@ -69,8 +71,10 @@ pub struct CreatureDef {
|
|||
pub multiattack: Option<u32>,
|
||||
pub is_humanoid: bool,
|
||||
pub is_animal: bool,
|
||||
pub is_intelligent: bool,
|
||||
pub patrols: bool,
|
||||
pub prefers_dark: bool,
|
||||
pub invulnerable: bool,
|
||||
}
|
||||
|
||||
impl CreatureDef {
|
||||
|
|
@ -91,8 +95,10 @@ impl CreatureDef {
|
|||
"MULTIATTACK" => result.multiattack = Some(tag.parse_value()?),
|
||||
"HUMANOID" => result.is_humanoid = true,
|
||||
"ANIMAL" => result.is_animal = true,
|
||||
"INTELLIGENT" => result.is_intelligent = true,
|
||||
"PATROL" => result.patrols = true,
|
||||
"PREFER_DARK" => result.prefers_dark = true,
|
||||
"INVULNERABLE" => result.invulnerable = true,
|
||||
_ => {
|
||||
eprintln!("Unknown tag '{}' in CREATURE defs", tag.name);
|
||||
}
|
||||
|
|
|
|||
38
src/main.rs
38
src/main.rs
|
|
@ -1,7 +1,7 @@
|
|||
use std::{collections::HashMap, io::BufReader, path::PathBuf, time::Duration};
|
||||
|
||||
use definitions::parser::DefinitionParser;
|
||||
use sim::{Site, World};
|
||||
use sim::{Creature, Site, SiteArea, World};
|
||||
|
||||
pub mod definitions;
|
||||
pub mod sim;
|
||||
|
|
@ -44,11 +44,43 @@ fn main() {
|
|||
|
||||
let site_def = site_definitions.get("CAVE").unwrap();
|
||||
let mut world = match std::fs::read(SAVE_FILE) {
|
||||
Ok(data) => bincode::deserialize(&data).unwrap(),
|
||||
Ok(data) => bincode::deserialize(&data).expect("Loading world data from file"),
|
||||
Err(_) => {
|
||||
let mut world = World::default();
|
||||
let mut site = Site::from_def(site_def);
|
||||
|
||||
let mut site = Site::generate_from_def(site_def);
|
||||
site.populate_randomly(&creature_definitions.values().collect::<Vec<_>>());
|
||||
|
||||
let bandit_def = creature_definitions.get("BANDIT").unwrap();
|
||||
let spider_def = creature_definitions.get("SPIDER").unwrap();
|
||||
let site = Site::new(
|
||||
site_def.clone(),
|
||||
"Gorbo's cave",
|
||||
vec![
|
||||
SiteArea::from_creatures(&vec![
|
||||
Creature::generate_from_def(
|
||||
creature_definitions.get("DRAGON").unwrap().clone(),
|
||||
),
|
||||
Creature::generate_from_def(
|
||||
creature_definitions.get("INDESTRUCTIBLE").unwrap().clone(),
|
||||
),
|
||||
]),
|
||||
SiteArea::from_creatures(&vec![
|
||||
Creature::generate_from_def(bandit_def.clone()),
|
||||
Creature::generate_from_def(spider_def.clone()),
|
||||
]),
|
||||
SiteArea::from_creatures(&vec![
|
||||
Creature::generate_from_def(bandit_def.clone()),
|
||||
Creature::generate_from_def(bandit_def.clone()),
|
||||
Creature::generate_from_def(bandit_def.clone()),
|
||||
]),
|
||||
SiteArea::from_creatures(&vec![
|
||||
Creature::generate_from_def(spider_def.clone()),
|
||||
Creature::generate_from_def(spider_def.clone()),
|
||||
]),
|
||||
],
|
||||
);
|
||||
|
||||
world.sites.push(site);
|
||||
std::fs::write("world.bin", bincode::serialize(&world).unwrap()).unwrap();
|
||||
world
|
||||
|
|
|
|||
306
src/sim.rs
306
src/sim.rs
|
|
@ -1,7 +1,6 @@
|
|||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fmt::Display,
|
||||
num::NonZeroUsize,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
|
|
@ -27,9 +26,26 @@ impl World {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
|
||||
pub struct SiteId(Uuid);
|
||||
|
||||
impl SiteId {
|
||||
pub fn generate() -> Self {
|
||||
Self(Uuid::new_v4())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SiteId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "s-{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Site {
|
||||
id: SiteId,
|
||||
pub name: String,
|
||||
pub definition: SiteDef,
|
||||
areas: Vec<SiteArea>,
|
||||
accumulated_time: Duration,
|
||||
}
|
||||
|
|
@ -40,33 +56,50 @@ impl Site {
|
|||
Duration::from_secs(3600)
|
||||
}
|
||||
|
||||
pub fn new(definition: SiteDef, name: impl Into<String>, areas: Vec<SiteArea>) -> Self {
|
||||
Self {
|
||||
id: SiteId::generate(),
|
||||
definition,
|
||||
name: name.into(),
|
||||
areas,
|
||||
accumulated_time: Duration::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> SiteId {
|
||||
self.id
|
||||
}
|
||||
|
||||
fn inner_tick(&mut self) {
|
||||
for area in self.areas.iter_mut() {
|
||||
match resolve_combat(&area.population, 600) {
|
||||
Some(results) => {
|
||||
// Maps where creatures are going during patrol
|
||||
let mut patrol_map: HashMap<CreatureId, (usize, usize)> = HashMap::new();
|
||||
let site_size = self.areas.len();
|
||||
for (area_index, area) in self.areas.iter_mut().enumerate() {
|
||||
// Resolve combat per area
|
||||
if let Some(results) = resolve_combat(&area.population, 600) {
|
||||
for attack in results.attacks {
|
||||
println!(
|
||||
"{} attacks {} [{}]: {attack}",
|
||||
attack.attacker.map_or("Natural causes", |attacker| &area
|
||||
.population
|
||||
.iter()
|
||||
.find(|creat| creat.uuid == attacker)
|
||||
.unwrap()
|
||||
.definition
|
||||
.name_singular),
|
||||
area.population
|
||||
.iter()
|
||||
.find(|creat| creat.uuid == attack.target)
|
||||
.unwrap()
|
||||
.definition
|
||||
.name_singular,
|
||||
attack.target
|
||||
);
|
||||
// println!(
|
||||
// "{attacker} [{attacker_id}] attacks {target} [{target_id}]: {attack}",
|
||||
// attacker = attack.attacker.map_or("Natural causes", |attacker| &area
|
||||
// .find_creature(attacker)
|
||||
// .unwrap()
|
||||
// .definition
|
||||
// .name_singular),
|
||||
// attacker_id = attack
|
||||
// .attacker
|
||||
// .map_or("<no id>".into(), |id| id.to_string()),
|
||||
// target = area
|
||||
// .find_creature(attack.target)
|
||||
// .unwrap()
|
||||
// .definition
|
||||
// .name_singular,
|
||||
// target_id = attack.target
|
||||
// );
|
||||
if let Some(damage) = attack.damage {
|
||||
let target = match area
|
||||
.population
|
||||
.iter_mut()
|
||||
.find(|creature| creature.uuid == attack.target)
|
||||
.find(|creature| creature.id == attack.target)
|
||||
{
|
||||
Some(target) => target,
|
||||
None => continue,
|
||||
|
|
@ -76,23 +109,64 @@ impl Site {
|
|||
}
|
||||
for death in results.kills {
|
||||
area.population
|
||||
.retain(|creature| creature.uuid != death.killed);
|
||||
.retain(|creature| creature.id != death.killed);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// If no combat, there is a chance of migration to other areas
|
||||
|
||||
// Resolve patrolling creatures
|
||||
if site_size > 1 {
|
||||
for creature in area.population.iter() {
|
||||
if fastrand::f32() < creature.patrol_chance() {
|
||||
// Make sure the creature patrols to a different area
|
||||
let target_index = {
|
||||
// Remove the current area from the range
|
||||
let index = fastrand::usize(0..site_size - 1);
|
||||
if index < area_index {
|
||||
index
|
||||
} else {
|
||||
// Skip the current area
|
||||
index + 1
|
||||
}
|
||||
};
|
||||
patrol_map.insert(creature.id, (area_index, target_index));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !patrol_map.is_empty() {
|
||||
println!("Patrols:");
|
||||
for (id, (from, to)) in patrol_map.iter() {
|
||||
println!("\t[{id}] patrols [{from}] -> [{to}]")
|
||||
}
|
||||
}
|
||||
|
||||
// Apply patrol map
|
||||
for (creature_id, (from_index, to_index)) in patrol_map {
|
||||
let creature = self
|
||||
.areas
|
||||
.get_mut(from_index)
|
||||
.expect("Patrol: getting the area where the patrolling creature is")
|
||||
.remove_creature(creature_id)
|
||||
.expect("Patrol: finding the creature inside the area");
|
||||
self.areas
|
||||
.get_mut(to_index)
|
||||
.expect("Patrol: getting the area where the creature is moving to")
|
||||
.population
|
||||
.push(creature);
|
||||
}
|
||||
|
||||
println!("Tick! {}", self);
|
||||
}
|
||||
|
||||
pub fn from_def(site_def: &SiteDef) -> Self {
|
||||
pub fn generate_from_def(site_def: &SiteDef) -> Self {
|
||||
Self::new(
|
||||
site_def.clone(),
|
||||
"Gorbo's cave",
|
||||
vec![
|
||||
SiteArea::default();
|
||||
fastrand::usize(site_def.min_size as usize..=site_def.max_size as usize)
|
||||
.try_into()
|
||||
.expect("Generated site with size of 0"),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -112,8 +186,9 @@ impl Site {
|
|||
for _ in 0..fastrand::usize(min_count..=max_count) {
|
||||
let area_t = fastrand::f32().powi(2);
|
||||
let index = (area_t * (area_count - 1) as f32).round() as usize;
|
||||
let creature =
|
||||
Creature::new((*fastrand::choice(creature_defs.iter()).unwrap()).clone());
|
||||
let creature = Creature::generate_from_def(
|
||||
(*fastrand::choice(creature_defs.iter()).unwrap()).clone(),
|
||||
);
|
||||
self.areas
|
||||
.get_mut(index)
|
||||
.expect("Creature generation index for depth is out of bounds")
|
||||
|
|
@ -123,14 +198,6 @@ impl Site {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn new(name: &str, size: NonZeroUsize) -> Self {
|
||||
Self {
|
||||
name: name.into(),
|
||||
areas: vec![SiteArea::default(); size.get()],
|
||||
accumulated_time: Duration::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance_time(&mut self, time: Duration) {
|
||||
self.accumulated_time += time;
|
||||
while self.accumulated_time >= Self::minimum_tick() {
|
||||
|
|
@ -139,13 +206,11 @@ impl Site {
|
|||
}
|
||||
}
|
||||
|
||||
/// Finds a creature with given uuid, removes it from the site and returns it with ownership
|
||||
pub fn remove_by_uuid(&mut self, creature_uuid: Uuid) -> Option<Creature> {
|
||||
/// Finds a creature with given id, removes it from the site and returns it with ownership
|
||||
pub fn remove_creature(&mut self, id: CreatureId) -> Option<Creature> {
|
||||
for area in self.areas.iter_mut() {
|
||||
for i in 0..area.population.len() {
|
||||
if area.population[i].uuid == creature_uuid {
|
||||
return Some(area.population.swap_remove(i));
|
||||
}
|
||||
if let Some(creature) = area.remove_creature(id) {
|
||||
return Some(creature);
|
||||
}
|
||||
}
|
||||
None
|
||||
|
|
@ -154,7 +219,12 @@ impl Site {
|
|||
|
||||
impl Display for Site {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "site \"{}\" [", self.name)?;
|
||||
write!(
|
||||
f,
|
||||
"site \"{name}\" [{id}] [",
|
||||
name = self.name,
|
||||
id = self.id
|
||||
)?;
|
||||
for (index, area) in self.areas.iter().enumerate() {
|
||||
write!(f, "\n\tarea {} [", index)?;
|
||||
for creature in area.population.iter() {
|
||||
|
|
@ -177,27 +247,82 @@ pub struct SiteArea {
|
|||
pub population: Vec<Creature>,
|
||||
}
|
||||
|
||||
impl SiteArea {
|
||||
pub fn from_creatures(creatures: &[Creature]) -> Self {
|
||||
Self {
|
||||
population: creatures.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_creature(&self, id: CreatureId) -> Option<&Creature> {
|
||||
self.population.iter().find(|creature| creature.id == id)
|
||||
}
|
||||
|
||||
pub fn find_creature_mut(&mut self, id: CreatureId) -> Option<&mut Creature> {
|
||||
self.population
|
||||
.iter_mut()
|
||||
.find(|creature| creature.id == id)
|
||||
}
|
||||
|
||||
/// Finds a creature with given id, removes it from the area and returns it with ownership
|
||||
pub fn remove_creature(&mut self, id: CreatureId) -> Option<Creature> {
|
||||
for i in 0..self.population.len() {
|
||||
if self.population[i].id == id {
|
||||
return Some(self.population.swap_remove(i));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
|
||||
pub struct CreatureState {
|
||||
pub damage: i32,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
|
||||
pub struct CreatureId(Uuid);
|
||||
|
||||
impl CreatureId {
|
||||
pub fn generate() -> Self {
|
||||
Self(Uuid::new_v4())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for CreatureId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "c-{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Hash, Debug, Serialize, Deserialize)]
|
||||
pub struct Creature {
|
||||
id: CreatureId,
|
||||
pub definition: CreatureDef,
|
||||
pub state: CreatureState,
|
||||
pub uuid: Uuid,
|
||||
}
|
||||
|
||||
impl Creature {
|
||||
pub fn new(definition: CreatureDef) -> Self {
|
||||
pub fn generate_from_def(definition: CreatureDef) -> Self {
|
||||
Self {
|
||||
uuid: Uuid::new_v4(),
|
||||
id: CreatureId::generate(),
|
||||
state: CreatureState::default(),
|
||||
definition,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> CreatureId {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub fn patrol_chance(&self) -> f32 {
|
||||
if self.definition.patrols {
|
||||
0.5
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn health(&self) -> i32 {
|
||||
self.definition.health - self.state.damage
|
||||
}
|
||||
|
|
@ -209,14 +334,21 @@ impl Creature {
|
|||
|
||||
impl Display for Creature {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{} [{}]", self.definition.name_singular, self.uuid)
|
||||
write!(
|
||||
f,
|
||||
"{singular} ({hp}/{max_hp}) [{id}]",
|
||||
singular = self.definition.name_singular,
|
||||
hp = self.health(),
|
||||
max_hp = self.definition.health,
|
||||
id = self.id
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct KillData {
|
||||
pub killed: Uuid,
|
||||
pub killer: Option<Uuid>,
|
||||
pub killed: CreatureId,
|
||||
pub killer: Option<CreatureId>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
|
|
@ -299,8 +431,8 @@ impl AttackRoll {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct AttackData {
|
||||
pub target: Uuid,
|
||||
pub attacker: Option<Uuid>,
|
||||
pub target: CreatureId,
|
||||
pub attacker: Option<CreatureId>,
|
||||
pub attack_roll: AttackRoll,
|
||||
pub damage: Option<i32>,
|
||||
pub is_deathblow: bool,
|
||||
|
|
@ -353,7 +485,7 @@ pub struct CombatResults {
|
|||
struct CombatState {
|
||||
creature: Creature,
|
||||
damage: i32,
|
||||
enemies: HashSet<Uuid>,
|
||||
enemies: HashSet<CreatureId>,
|
||||
}
|
||||
|
||||
impl CombatState {
|
||||
|
|
@ -373,7 +505,7 @@ impl CombatState {
|
|||
}
|
||||
|
||||
pub fn resolve_combat(combatants: &[Creature], max_rounds: u16) -> Option<CombatResults> {
|
||||
let mut participants: HashMap<Uuid, CombatState> = HashMap::new();
|
||||
let mut participants: HashMap<CreatureId, CombatState> = HashMap::new();
|
||||
for index_1 in 0..combatants.len() {
|
||||
for index_2 in (index_1 + 1)..combatants.len() {
|
||||
if index_1 == index_2 {
|
||||
|
|
@ -381,24 +513,17 @@ pub fn resolve_combat(combatants: &[Creature], max_rounds: u16) -> Option<Combat
|
|||
}
|
||||
let c1 = &combatants[index_1];
|
||||
let c2 = &combatants[index_2];
|
||||
// Simplest friend-or-foe detection
|
||||
let is_enemy = c1.definition.id != c2.definition.id;
|
||||
if is_enemy {
|
||||
participants
|
||||
.entry(c1.uuid)
|
||||
.entry(c1.id)
|
||||
.or_insert(CombatState::from_creature(c1.clone()));
|
||||
participants
|
||||
.entry(c2.uuid)
|
||||
.entry(c2.id)
|
||||
.or_insert(CombatState::from_creature(c2.clone()));
|
||||
participants
|
||||
.get_mut(&c1.uuid)
|
||||
.unwrap()
|
||||
.enemies
|
||||
.insert(c2.uuid);
|
||||
participants
|
||||
.get_mut(&c2.uuid)
|
||||
.unwrap()
|
||||
.enemies
|
||||
.insert(c1.uuid);
|
||||
participants.get_mut(&c1.id).unwrap().enemies.insert(c2.id);
|
||||
participants.get_mut(&c2.id).unwrap().enemies.insert(c1.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -409,32 +534,28 @@ pub fn resolve_combat(combatants: &[Creature], max_rounds: u16) -> Option<Combat
|
|||
}
|
||||
|
||||
// Time for violence
|
||||
let mut order: Vec<Uuid> = participants.keys().copied().collect();
|
||||
let mut order: Vec<CreatureId> = participants.keys().copied().collect();
|
||||
fastrand::shuffle(&mut order);
|
||||
let mut kills: Vec<_> = vec![];
|
||||
let mut attacks: Vec<_> = vec![];
|
||||
|
||||
for _ in 0..max_rounds {
|
||||
let should_continue = participants.iter().any(|(_, combat)| {
|
||||
combat.is_alive()
|
||||
&& combat
|
||||
.enemies
|
||||
.iter()
|
||||
.any(|uuid| participants[uuid].is_alive())
|
||||
combat.is_alive() && combat.enemies.iter().any(|id| participants[id].is_alive())
|
||||
});
|
||||
if !should_continue {
|
||||
break;
|
||||
}
|
||||
|
||||
for uuid in order.iter() {
|
||||
for _ in 0..participants[uuid].creature.definition.attack_count() {
|
||||
if !participants[uuid].is_alive() {
|
||||
for id in order.iter() {
|
||||
for _ in 0..participants[id].creature.definition.attack_count() {
|
||||
if !participants[id].is_alive() {
|
||||
continue;
|
||||
}
|
||||
let targets: Vec<_> = participants[uuid]
|
||||
let targets: Vec<_> = participants[id]
|
||||
.enemies
|
||||
.iter()
|
||||
.filter(|enemy_uuid| participants[*enemy_uuid].is_alive())
|
||||
.filter(|enemy_id| participants[*enemy_id].is_alive())
|
||||
.collect();
|
||||
let target = match fastrand::choice(targets.iter()) {
|
||||
Some(target) => **target,
|
||||
|
|
@ -442,19 +563,32 @@ pub fn resolve_combat(combatants: &[Creature], max_rounds: u16) -> Option<Combat
|
|||
};
|
||||
|
||||
let attack = Attack {
|
||||
to_hit_modifier: participants[uuid].creature.definition.to_hit_modifier,
|
||||
to_hit_modifier: participants[id].creature.definition.to_hit_modifier,
|
||||
// Hard-coded
|
||||
threat_treshold: 20,
|
||||
armor_class: participants[&target].creature.definition.armor_class,
|
||||
};
|
||||
|
||||
let attack_roll = AttackRoll::roll(attack.clone(), roll_d20);
|
||||
let damage = match attack_roll.result() {
|
||||
let mut damage = match attack_roll.result() {
|
||||
AttackResult::CritMiss | AttackResult::Miss => None,
|
||||
AttackResult::Hit => Some(participants[uuid].creature.definition.damage_roll()),
|
||||
AttackResult::Crit => {
|
||||
Some(participants[uuid].creature.definition.damage_roll() * 2)
|
||||
}
|
||||
AttackResult::Hit => Some(participants[id].creature.definition.damage_roll()),
|
||||
AttackResult::Crit => Some(
|
||||
participants[id].creature.definition.max_damage
|
||||
+ participants[id].creature.definition.damage_roll(),
|
||||
),
|
||||
};
|
||||
if let Some(damage) = damage.as_mut() {
|
||||
if participants
|
||||
.get(&target)
|
||||
.unwrap()
|
||||
.creature
|
||||
.definition
|
||||
.invulnerable
|
||||
{
|
||||
*damage = 0;
|
||||
}
|
||||
}
|
||||
let is_deathblow = match damage {
|
||||
Some(damage) => {
|
||||
let enemy = participants.get_mut(&target).unwrap();
|
||||
|
|
@ -466,11 +600,11 @@ pub fn resolve_combat(combatants: &[Creature], max_rounds: u16) -> Option<Combat
|
|||
if is_deathblow {
|
||||
kills.push(KillData {
|
||||
killed: target,
|
||||
killer: Some(*uuid),
|
||||
killer: Some(*id),
|
||||
})
|
||||
}
|
||||
let attack_data = AttackData {
|
||||
attacker: Some(*uuid),
|
||||
attacker: Some(*id),
|
||||
target,
|
||||
attack_roll,
|
||||
damage,
|
||||
|
|
|
|||
Loading…
Reference in New Issue