updates?
This commit is contained in:
parent
e7d6b74aab
commit
35413b563c
33 changed files with 10238 additions and 1891 deletions
|
|
@ -1,224 +1,59 @@
|
|||
use bevy_ecs::prelude::*;
|
||||
use proxisim_models::{
|
||||
bundle::{
|
||||
armour::{ArmourBodyPart, ArmourBodyParts},
|
||||
passive::{FactionUpgrades, Merits},
|
||||
player::{
|
||||
Attacker, BodyPart, ChooseWeapon, CombatTurns, Current, CurrentTarget, Defeated,
|
||||
Defender, FightEndType, Level, MaxHealth, PartDamageBonus, Player, PlayerStrategy,
|
||||
Weapons,
|
||||
},
|
||||
stat::{
|
||||
AmmoControl, Clips, CritRate, DamageBonus, Defence, Dexterity, EffectiveStat, Health,
|
||||
SimpleStatBundle, SimpleStatEffective, Speed, Strength, WeaponAccuracy,
|
||||
},
|
||||
weapon::{
|
||||
Ammo, DamageStat, NeedsReload, NonTargeted, RateOfFire, Usable, Uses, Weapon,
|
||||
WeaponSlot,
|
||||
},
|
||||
},
|
||||
hierarchy::Children,
|
||||
};
|
||||
use rand::Rng as _;
|
||||
use strum::Display;
|
||||
|
||||
use crate::{
|
||||
armour,
|
||||
FightStatus, Rng, Stages,
|
||||
effect::Effects,
|
||||
hierarchy::Children,
|
||||
log,
|
||||
log::Logger,
|
||||
metrics::Metrics,
|
||||
passives::{EducationPartDamageBonus, FactionUpgrades, Merits},
|
||||
weapon::{
|
||||
bonus::{BonusPartDamageBonus, MultiTurnBonus},
|
||||
temp::{NonTargeted, Uses},
|
||||
Ammo, DamageProcEffect, DamageStat, NeedsReload, RateOfFire, TurnTriggeredEffect, Usable,
|
||||
Weapon, WeaponSlot,
|
||||
},
|
||||
FightStatus, Id, Name, Rng, Stages,
|
||||
};
|
||||
|
||||
use self::stats::{
|
||||
AmmoControl, Clips, CritRate, DamageBonus, Defence, Dexterity, EffectiveStat, Health,
|
||||
SimpleStatBundle, SimpleStatEffective, Speed, Strength, WeaponAccuracy,
|
||||
weapon::{DamageProcEffect, TurnTriggeredEffect, bonus::MultiTurnBonus},
|
||||
};
|
||||
|
||||
pub mod stats;
|
||||
pub mod status_effect;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Attacker;
|
||||
fn select_weapon(
|
||||
weapons: &Weapons,
|
||||
slot: WeaponSlot,
|
||||
reload: bool,
|
||||
usable_q: &Query<(Has<NeedsReload>, Option<&Children>), With<Usable>>,
|
||||
) -> Option<(Entity, Option<Children>)> {
|
||||
let id = match slot {
|
||||
WeaponSlot::Primary => weapons.primary?,
|
||||
WeaponSlot::Secondary => weapons.secondary?,
|
||||
WeaponSlot::Melee => weapons.melee?,
|
||||
WeaponSlot::Temporary => weapons.temporary?,
|
||||
WeaponSlot::Fists => weapons.fists,
|
||||
WeaponSlot::Kick => weapons.kick,
|
||||
};
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Defender;
|
||||
let (needs_reload, children) = usable_q.get(id).ok()?;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Defeated;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Current;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct CurrentTarget;
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct Player;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Level(pub u16);
|
||||
|
||||
impl Default for Level {
|
||||
fn default() -> Self {
|
||||
Self(1)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Debug)]
|
||||
pub struct MaxHealth(pub u16);
|
||||
|
||||
impl Default for MaxHealth {
|
||||
fn default() -> Self {
|
||||
Self(100)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Debug, Default)]
|
||||
pub struct CombatTurns(pub u16);
|
||||
|
||||
#[derive(Component, Default, Debug)]
|
||||
pub struct Weapons {
|
||||
pub primary: Option<Entity>,
|
||||
pub secondary: Option<Entity>,
|
||||
pub melee: Option<Entity>,
|
||||
pub temporary: Option<Entity>,
|
||||
pub fists: Option<Entity>,
|
||||
pub kick: Option<Entity>,
|
||||
}
|
||||
|
||||
impl Weapons {
|
||||
fn select(
|
||||
&self,
|
||||
slot: WeaponSlot,
|
||||
reload: bool,
|
||||
usable_q: &Query<(Has<NeedsReload>, &Children), With<Usable>>,
|
||||
) -> Option<(Entity, Children)> {
|
||||
let id = match slot {
|
||||
WeaponSlot::Primary => self.primary?,
|
||||
WeaponSlot::Secondary => self.secondary?,
|
||||
WeaponSlot::Melee => self.melee?,
|
||||
WeaponSlot::Temporary => self.temporary?,
|
||||
WeaponSlot::Fists => self.fists?,
|
||||
WeaponSlot::Kick => self.kick?,
|
||||
};
|
||||
|
||||
let (needs_reload, children) = usable_q.get(id).ok()?;
|
||||
|
||||
if !reload && needs_reload {
|
||||
None
|
||||
} else {
|
||||
Some((id, children.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component, Debug)]
|
||||
#[cfg_attr(feature = "json", derive(serde::Deserialize))]
|
||||
#[cfg_attr(feature = "json", serde(tag = "type", rename_all = "snake_case"))]
|
||||
pub enum PlayerStrategy {
|
||||
AlwaysFists,
|
||||
AlwaysKicks,
|
||||
PrimaryMelee {
|
||||
reload: bool,
|
||||
},
|
||||
InOrder {
|
||||
order: Vec<WeaponSlot>,
|
||||
reload: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl Default for PlayerStrategy {
|
||||
fn default() -> Self {
|
||||
Self::AlwaysFists
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Event)]
|
||||
pub struct ChooseWeapon(pub Entity);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum BodyPart {
|
||||
LeftHand,
|
||||
RightHand,
|
||||
LeftArm,
|
||||
RightArm,
|
||||
LeftFoot,
|
||||
RightFoot,
|
||||
LeftLeg,
|
||||
RightLeg,
|
||||
Stomach,
|
||||
Chest,
|
||||
Groin,
|
||||
Head,
|
||||
Throat,
|
||||
Heart,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for BodyPart {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::LeftHand => write!(f, "Left hand"),
|
||||
Self::RightHand => write!(f, "Right hand"),
|
||||
Self::LeftArm => write!(f, "Left arm"),
|
||||
Self::RightArm => write!(f, "Right arm"),
|
||||
Self::LeftFoot => write!(f, "Left foot"),
|
||||
Self::RightFoot => write!(f, "Right foot"),
|
||||
Self::LeftLeg => write!(f, "Left leg"),
|
||||
Self::RightLeg => write!(f, "Right leg"),
|
||||
Self::Stomach => write!(f, "Stomach"),
|
||||
Self::Chest => write!(f, "Chest"),
|
||||
Self::Groin => write!(f, "Groin"),
|
||||
Self::Head => write!(f, "Head"),
|
||||
Self::Throat => write!(f, "Throat"),
|
||||
Self::Heart => write!(f, "Heart"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Display)]
|
||||
pub enum FightEndType {
|
||||
Victory,
|
||||
Stalemate,
|
||||
Loss,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub enum PartDamageBonus {
|
||||
Education(EducationPartDamageBonus),
|
||||
WeaponBonus {
|
||||
value: f32,
|
||||
bonus: BonusPartDamageBonus,
|
||||
},
|
||||
}
|
||||
|
||||
impl PartDamageBonus {
|
||||
pub fn dmg_bonus(&self, part: BodyPart) -> Option<f32> {
|
||||
match self {
|
||||
Self::Education(edu) => edu.dmg_bonus(part),
|
||||
Self::WeaponBonus { value, bonus } => bonus.dmg_bonus(part, *value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Bundle)]
|
||||
pub struct PlayerBundle {
|
||||
pub name: Name,
|
||||
pub id: Id,
|
||||
pub player: Player,
|
||||
pub level: Level,
|
||||
pub crit_rate: SimpleStatBundle<CritRate>,
|
||||
// TODO: since these two need to be tracked here anyways it might be preferable to shift all
|
||||
// player specific passives here instead of tracking them on the weapons
|
||||
pub acc_bonus: SimpleStatBundle<WeaponAccuracy>,
|
||||
pub dmg_bonus: SimpleStatBundle<DamageBonus>,
|
||||
|
||||
pub strategy: PlayerStrategy,
|
||||
pub combat_turns: CombatTurns,
|
||||
}
|
||||
|
||||
impl PlayerBundle {
|
||||
pub fn new(name: impl ToString, id: usize, level: u16, strategy: PlayerStrategy) -> Self {
|
||||
Self {
|
||||
name: Name(name.to_string()),
|
||||
id: Id(id),
|
||||
player: Player,
|
||||
level: Level(level),
|
||||
crit_rate: SimpleStatBundle::new(24),
|
||||
acc_bonus: SimpleStatBundle::new(0.0),
|
||||
dmg_bonus: SimpleStatBundle::new(0.0),
|
||||
strategy,
|
||||
combat_turns: Default::default(),
|
||||
}
|
||||
if !reload && needs_reload {
|
||||
None
|
||||
} else {
|
||||
Some((id, children.cloned()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -250,7 +85,7 @@ fn designate_first(
|
|||
mut commands: Commands,
|
||||
) {
|
||||
let attacker = attacker_q.iter().next().unwrap();
|
||||
let defender = defender_q.single();
|
||||
let defender = defender_q.single().unwrap();
|
||||
commands.entity(attacker).insert(Current);
|
||||
commands.entity(defender).insert(CurrentTarget);
|
||||
}
|
||||
|
|
@ -260,8 +95,8 @@ fn change_roles(
|
|||
target_q: Query<Entity, With<CurrentTarget>>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
let current = current_q.single();
|
||||
let target = target_q.single();
|
||||
let current = current_q.single().unwrap();
|
||||
let target = target_q.single().unwrap();
|
||||
|
||||
// TODO: Group fights
|
||||
commands
|
||||
|
|
@ -291,36 +126,38 @@ pub fn pick_action(
|
|||
(With<Current>, With<Player>),
|
||||
>,
|
||||
target_q: Query<Entity, With<CurrentTarget>>,
|
||||
usable_q: Query<(Has<NeedsReload>, &Children), With<Usable>>,
|
||||
usable_q: Query<(Has<NeedsReload>, Option<&Children>), With<Usable>>,
|
||||
weapon_trigger_q: Query<&TurnTriggeredEffect>,
|
||||
mut commands: Commands,
|
||||
mut effects: Effects,
|
||||
metrics: Res<Metrics>,
|
||||
) {
|
||||
let (current, weapons, strat, mut turns) = p_query.single_mut();
|
||||
let (current, weapons, strat, mut turns) = p_query.single_mut().unwrap();
|
||||
let (weapon, children) = match strat {
|
||||
PlayerStrategy::AlwaysFists => (weapons.fists.unwrap(), Default::default()),
|
||||
PlayerStrategy::AlwaysKicks => weapons
|
||||
.select(WeaponSlot::Kick, true, &usable_q)
|
||||
.unwrap_or_else(|| (weapons.fists.unwrap(), Default::default())),
|
||||
PlayerStrategy::PrimaryMelee { reload } => weapons
|
||||
.select(WeaponSlot::Primary, *reload, &usable_q)
|
||||
.or_else(|| weapons.select(WeaponSlot::Melee, true, &usable_q))
|
||||
.unwrap_or_else(|| (weapons.fists.unwrap(), Default::default())),
|
||||
PlayerStrategy::AlwaysFists => (weapons.fists, None),
|
||||
PlayerStrategy::AlwaysKicks => select_weapon(weapons, WeaponSlot::Kick, true, &usable_q)
|
||||
.unwrap_or_else(|| (weapons.fists, Default::default())),
|
||||
PlayerStrategy::PrimaryMelee { reload } => {
|
||||
select_weapon(weapons, WeaponSlot::Primary, *reload, &usable_q)
|
||||
.or_else(|| select_weapon(weapons, WeaponSlot::Melee, true, &usable_q))
|
||||
.unwrap_or_else(|| (weapons.fists, Default::default()))
|
||||
}
|
||||
PlayerStrategy::InOrder { order, reload } => order
|
||||
.iter()
|
||||
.find_map(|slot| weapons.select(*slot, *reload, &usable_q))
|
||||
.unwrap_or_else(|| (weapons.fists.unwrap(), Default::default())),
|
||||
.find_map(|slot| select_weapon(weapons, *slot, *reload, &usable_q))
|
||||
.unwrap_or_else(|| (weapons.fists, Default::default())),
|
||||
};
|
||||
metrics.increment_counter(current, "turn", 1);
|
||||
metrics.increment_counter(weapon, "turn", 1);
|
||||
metrics.increment_counter(Some(current), "turn", 1);
|
||||
metrics.increment_counter(Some(weapon), "turn", 1);
|
||||
|
||||
commands.entity(weapon).insert(Current);
|
||||
|
||||
let target = target_q.single();
|
||||
let target = target_q.single().unwrap();
|
||||
|
||||
for effect in weapon_trigger_q.iter_many(children.get()) {
|
||||
effect.trigger(&mut effects, current, target);
|
||||
if let Some(children) = children {
|
||||
for effect in weapon_trigger_q.iter_many(children.get()) {
|
||||
effect.trigger(&mut effects, current, target);
|
||||
}
|
||||
}
|
||||
|
||||
turns.0 += 1;
|
||||
|
|
@ -368,12 +205,12 @@ pub fn use_damaging_weapon(
|
|||
Entity,
|
||||
&EffectiveStat<Dexterity>,
|
||||
&EffectiveStat<Defence>,
|
||||
&armour::ArmourBodyParts,
|
||||
&ArmourBodyParts,
|
||||
&mut SimpleStatEffective<Health>,
|
||||
),
|
||||
With<CurrentTarget>,
|
||||
>,
|
||||
armour_q: Query<&armour::ArmourBodyPart>,
|
||||
armour_q: Query<&ArmourBodyPart>,
|
||||
(damage_proc_q, part_bonus_q): (Query<&DamageProcEffect>, Query<&PartDamageBonus>),
|
||||
(mut ammo_q, mut temp_q): (
|
||||
Query<(
|
||||
|
|
@ -392,13 +229,13 @@ pub fn use_damaging_weapon(
|
|||
Effects,
|
||||
),
|
||||
) {
|
||||
let Ok((weapon, w_dmg, acc, dmg_bonus, crit, children, non_targeted)) = weapon_q.get_single()
|
||||
let Ok((weapon, w_dmg, acc, dmg_bonus, crit, children, non_targeted)) = weapon_q.single()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let (player, player_spd, player_str, player_crit, acc_bonus, p_dmg_bonus, attacker) =
|
||||
player_q.single();
|
||||
let (target, target_dex, target_def, armour_parts, mut health) = target_q.single_mut();
|
||||
player_q.single().unwrap();
|
||||
let (target, target_dex, target_def, armour_parts, mut health) = target_q.single_mut().unwrap();
|
||||
|
||||
if let Ok(mut uses) = temp_q.get_mut(weapon) {
|
||||
uses.0 -= 1;
|
||||
|
|
@ -448,10 +285,10 @@ pub fn use_damaging_weapon(
|
|||
+ 30.0;
|
||||
|
||||
loop {
|
||||
let rounds = ammo.as_mut().map(|(ref mut ammo, clips, rof)| {
|
||||
let rounds = (rng.gen_range(rof.clone()).round() as u16).clamp(1, ammo.0);
|
||||
metrics.increment_counter(player, "rounds_fired", rounds.into());
|
||||
metrics.increment_counter(weapon, "rounds_fired", rounds.into());
|
||||
let rounds = ammo.as_mut().map(|(ammo, clips, rof)| {
|
||||
let rounds = (rng.random_range(rof.clone()).round() as u16).clamp(1, ammo.0);
|
||||
metrics.increment_counter(Some(player), "rounds_fired", rounds.into());
|
||||
metrics.increment_counter(Some(weapon), "rounds_fired", rounds.into());
|
||||
ammo.0 -= rounds;
|
||||
if ammo.0 == 0 {
|
||||
if clips.value == 0 {
|
||||
|
|
@ -469,7 +306,7 @@ pub fn use_damaging_weapon(
|
|||
base_hit_chance + acc_eff.value * (1.0 - base_hit_chance)
|
||||
};
|
||||
|
||||
if hit_chance <= 1.0 && !rng.gen_bool(hit_chance as f64) {
|
||||
if hit_chance <= 1.0 && !rng.random_bool(hit_chance as f64) {
|
||||
log!(logger, "miss_target", {
|
||||
weapon: weapon,
|
||||
actor: player,
|
||||
|
|
@ -477,8 +314,8 @@ pub fn use_damaging_weapon(
|
|||
rounds: rounds,
|
||||
hit_chance: hit_chance,
|
||||
});
|
||||
metrics.increment_counter(player, "miss", 1);
|
||||
metrics.increment_counter(weapon, "miss", 1);
|
||||
metrics.increment_counter(Some(player), "miss", 1);
|
||||
metrics.increment_counter(Some(weapon), "miss", 1);
|
||||
|
||||
if multi_attack_proc.is_none() {
|
||||
return;
|
||||
|
|
@ -492,8 +329,8 @@ pub fn use_damaging_weapon(
|
|||
|
||||
let mult = match body_part {
|
||||
BodyPart::Head | BodyPart::Heart | BodyPart::Throat => {
|
||||
metrics.increment_counter(player, "crit", 1);
|
||||
metrics.increment_counter(weapon, "crit", 1);
|
||||
metrics.increment_counter(Some(player), "crit", 1);
|
||||
metrics.increment_counter(Some(weapon), "crit", 1);
|
||||
1.0
|
||||
}
|
||||
BodyPart::LeftHand
|
||||
|
|
@ -506,8 +343,8 @@ pub fn use_damaging_weapon(
|
|||
BodyPart::Groin | BodyPart::Stomach | BodyPart::Chest => 1.0 / 1.75,
|
||||
};
|
||||
|
||||
metrics.increment_counter(player, "hit", 1);
|
||||
metrics.increment_counter(weapon, "hit", 1);
|
||||
metrics.increment_counter(Some(player), "hit", 1);
|
||||
metrics.increment_counter(Some(weapon), "hit", 1);
|
||||
|
||||
let armour_parts = armour_q.get(armour_parts.0[body_part.into()]).unwrap();
|
||||
let piece = rng.sample(armour_parts);
|
||||
|
|
@ -530,15 +367,15 @@ pub fn use_damaging_weapon(
|
|||
|
||||
let dmg = dmg_intrinsic
|
||||
* w_dmg.0
|
||||
* dmg_bonus.value
|
||||
* (1.0 + dmg_bonus.value)
|
||||
* (1.0 - armour_mitigation)
|
||||
* (1.0 - def_mitigation)
|
||||
* mult
|
||||
* dmg_spread;
|
||||
let dmg = dmg.round() as u32;
|
||||
|
||||
metrics.record_histogram(player, "dmg", dmg);
|
||||
metrics.record_histogram(weapon, "dmg", dmg);
|
||||
metrics.record_histogram(Some(player), "dmg", dmg);
|
||||
metrics.record_histogram(Some(weapon), "dmg", dmg);
|
||||
|
||||
if dmg > 0 {
|
||||
for effect in damage_proc_q.iter_many(children.get()) {
|
||||
|
|
@ -548,7 +385,7 @@ pub fn use_damaging_weapon(
|
|||
continue;
|
||||
}
|
||||
let chance = (value / 100.0) as f64;
|
||||
if chance > 1.0 || rng.gen_bool(chance) {
|
||||
if chance > 1.0 || rng.random_bool(chance) {
|
||||
match bonus {
|
||||
MultiTurnBonus::Blindfire => {
|
||||
multi_attack_proc = Some(MultiAttack::Blindfire)
|
||||
|
|
@ -559,26 +396,26 @@ pub fn use_damaging_weapon(
|
|||
}
|
||||
MultiTurnBonus::Rage => {
|
||||
multi_attack_proc =
|
||||
Some(MultiAttack::Rage(rng.gen_range(2..=8)))
|
||||
Some(MultiAttack::Rage(rng.random_range(2..=8)))
|
||||
}
|
||||
MultiTurnBonus::DoubleTap => {
|
||||
multi_attack_proc =
|
||||
Some(MultiAttack::DoubleTap { first_shot: true })
|
||||
}
|
||||
};
|
||||
metrics.increment_counter(player, bonus.counter_label(), 1);
|
||||
metrics.increment_counter(weapon, bonus.counter_label(), 1);
|
||||
metrics.increment_counter(Some(player), bonus.counter_label(), 1);
|
||||
metrics.increment_counter(Some(weapon), bonus.counter_label(), 1);
|
||||
}
|
||||
}
|
||||
DamageProcEffect::SelfEffect { value, bonus } => {
|
||||
let chance = (value / 100.0) as f64;
|
||||
if chance > 1.0 || rng.gen_bool(chance) {
|
||||
if chance > 1.0 || rng.random_bool(chance) {
|
||||
bonus.spawn(player, &mut effects);
|
||||
}
|
||||
}
|
||||
DamageProcEffect::OpponentEffect { value, bonus } => {
|
||||
let chance = (value / 100.0) as f64;
|
||||
if chance > 1.0 || rng.gen_bool(chance) {
|
||||
if chance > 1.0 || rng.random_bool(chance) {
|
||||
bonus.spawn(target, &mut effects, &mut rng.0);
|
||||
}
|
||||
}
|
||||
|
|
@ -592,19 +429,20 @@ pub fn use_damaging_weapon(
|
|||
|
||||
log!(logger, "hit_target", {
|
||||
actor: player,
|
||||
acc: acc_eff.value,
|
||||
recipient: target,
|
||||
weapon: weapon,
|
||||
weapon,
|
||||
part: %body_part,
|
||||
part_mult: mult,
|
||||
dmg: dmg,
|
||||
rounds: rounds,
|
||||
rounds,
|
||||
health_before: health_before,
|
||||
health_after: health.value,
|
||||
dmg_spread: dmg_spread,
|
||||
dmg_intrinsic: dmg_intrinsic,
|
||||
armour_mitigation: armour_mitigation,
|
||||
def_mitigation: def_mitigation,
|
||||
weapon_dmg: w_dmg.0,
|
||||
dmg,
|
||||
dmg_spread,
|
||||
dmg_intrinsic,
|
||||
dmg_weapon: w_dmg.0,
|
||||
armour_mitigation,
|
||||
def_mitigation,
|
||||
bonus_dmg: dmg_bonus.value,
|
||||
hit_chance: hit_chance,
|
||||
crit_rate: crit.value,
|
||||
|
|
@ -623,17 +461,23 @@ pub fn use_damaging_weapon(
|
|||
FightEndType::Loss
|
||||
},
|
||||
});
|
||||
metrics.increment_counter(player, "victory", 1);
|
||||
metrics.increment_counter(Some(player), "victory", 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Technically only douple tap and blindfire have this condition, but we can run into with
|
||||
// invalid bonus/weapon combinations without checking this for all bonuses
|
||||
if ammo.as_ref().is_some_and(|(a, _, _)| a.0 == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
match multi_attack_proc {
|
||||
Some(MultiAttack::Blindfire) => {
|
||||
if !ammo.as_ref().map(|(a, _, _)| a.0 != 0).unwrap_or_default() {
|
||||
acc_eff.value -= 5.0 / 50.0;
|
||||
// Prevent infinite loop if used on a melee
|
||||
if ammo.is_none() {
|
||||
break;
|
||||
}
|
||||
|
||||
acc_eff.value -= 5.0 / 50.0;
|
||||
}
|
||||
Some(MultiAttack::Fury { first_hit: true }) => {
|
||||
multi_attack_proc = Some(MultiAttack::Fury { first_hit: false })
|
||||
|
|
@ -658,7 +502,7 @@ pub fn check_stalemate(
|
|||
mut logger: Logger,
|
||||
metrics: Res<Metrics>,
|
||||
) {
|
||||
let (current, current_turns, attacker) = current_q.single();
|
||||
let (current, current_turns, attacker) = current_q.single().unwrap();
|
||||
if *state == FightStatus::Ongoing && current_turns.0 >= 25 && attacker {
|
||||
commands.entity(current).insert(Defeated);
|
||||
let target = target_q.single();
|
||||
|
|
@ -668,7 +512,7 @@ pub fn check_stalemate(
|
|||
recipient: target,
|
||||
fight_end_type: %FightEndType::Stalemate,
|
||||
});
|
||||
metrics.increment_counter(current, "stalemate", 1);
|
||||
metrics.increment_counter(Some(current), "stalemate", 1);
|
||||
|
||||
if other_attackers_q.is_empty() {
|
||||
*state = FightStatus::Over
|
||||
|
|
@ -705,7 +549,7 @@ fn record_post_fight_stats(
|
|||
metrics: Res<Metrics>,
|
||||
) {
|
||||
for (player, health) in player_q.iter() {
|
||||
metrics.record_histogram(player, "rem_health", health.value as u32);
|
||||
metrics.record_histogram(Some(player), "rem_health", health.value as u32);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue