fixed first round effect and migrated logging
This commit is contained in:
parent
b45d04b872
commit
e7d6b74aab
10 changed files with 539 additions and 222 deletions
|
|
@ -9,9 +9,9 @@ use crate::{
|
|||
log,
|
||||
log::Logger,
|
||||
metrics::Metrics,
|
||||
passives::{Education, FactionUpgrades, Merits},
|
||||
passives::{EducationPartDamageBonus, FactionUpgrades, Merits},
|
||||
weapon::{
|
||||
bonus::MultiTurnBonus,
|
||||
bonus::{BonusPartDamageBonus, MultiTurnBonus},
|
||||
temp::{NonTargeted, Uses},
|
||||
Ammo, DamageProcEffect, DamageStat, NeedsReload, RateOfFire, TurnTriggeredEffect, Usable,
|
||||
Weapon, WeaponSlot,
|
||||
|
|
@ -172,6 +172,24 @@ pub enum FightEndType {
|
|||
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,
|
||||
|
|
@ -341,7 +359,6 @@ pub fn use_damaging_weapon(
|
|||
&SimpleStatEffective<CritRate>,
|
||||
&SimpleStatEffective<WeaponAccuracy>,
|
||||
&SimpleStatEffective<DamageBonus>,
|
||||
&Education,
|
||||
Has<Attacker>,
|
||||
),
|
||||
(With<Player>, With<Current>),
|
||||
|
|
@ -357,7 +374,7 @@ pub fn use_damaging_weapon(
|
|||
With<CurrentTarget>,
|
||||
>,
|
||||
armour_q: Query<&armour::ArmourBodyPart>,
|
||||
damage_proc_q: Query<&DamageProcEffect>,
|
||||
(damage_proc_q, part_bonus_q): (Query<&DamageProcEffect>, Query<&PartDamageBonus>),
|
||||
(mut ammo_q, mut temp_q): (
|
||||
Query<(
|
||||
&mut Ammo,
|
||||
|
|
@ -367,18 +384,19 @@ pub fn use_damaging_weapon(
|
|||
)>,
|
||||
Query<&mut Uses>,
|
||||
),
|
||||
(mut logger, mut commands, dmg_spread, metrics): (
|
||||
(mut logger, mut commands, dmg_spread, metrics, mut effects): (
|
||||
Logger,
|
||||
Commands,
|
||||
Local<DamageSpread>,
|
||||
Res<Metrics>,
|
||||
Effects,
|
||||
),
|
||||
) {
|
||||
let Ok((weapon, w_dmg, acc, dmg_bonus, crit, children, non_targeted)) = weapon_q.get_single()
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let (player, player_spd, player_str, player_crit, acc_bonus, p_dmg_bonus, edu, attacker) =
|
||||
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();
|
||||
|
||||
|
|
@ -457,6 +475,7 @@ pub fn use_damaging_weapon(
|
|||
actor: player,
|
||||
recipient: target,
|
||||
rounds: rounds,
|
||||
hit_chance: hit_chance,
|
||||
});
|
||||
metrics.increment_counter(player, "miss", 1);
|
||||
metrics.increment_counter(weapon, "miss", 1);
|
||||
|
|
@ -494,13 +513,17 @@ pub fn use_damaging_weapon(
|
|||
let piece = rng.sample(armour_parts);
|
||||
let armour_mitigation = piece.map_or(0.0, |p| p.armour_value);
|
||||
|
||||
// NOTE: The beta distribution is defined on [0,1], so we rescale here
|
||||
// NOTE: Proxima's simulator seems to have the damage spread be between 95% and 105%,
|
||||
// but from my brief tests it seems that 100% to 110% lines up better, at least for h2h.
|
||||
// It might be better to revivisit this detail later down the line and run more tests.
|
||||
let dmg_spread = rng.sample(dmg_spread.0) / 10.0 + 1.0;
|
||||
|
||||
let mut dmg_bonus = dmg_bonus + p_dmg_bonus;
|
||||
|
||||
if edu.bio2380 && body_part == BodyPart::Throat {
|
||||
dmg_bonus.value += 0.10;
|
||||
for part_bonus in part_bonus_q.iter_many(children.get()) {
|
||||
if let Some(bonus) = part_bonus.dmg_bonus(body_part) {
|
||||
dmg_bonus.value += bonus;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: special ammo
|
||||
|
|
@ -519,11 +542,13 @@ pub fn use_damaging_weapon(
|
|||
|
||||
if dmg > 0 {
|
||||
for effect in damage_proc_q.iter_many(children.get()) {
|
||||
match effect {
|
||||
DamageProcEffect::MultiTurn { value, bonus }
|
||||
if multi_attack_proc.is_none() =>
|
||||
{
|
||||
if rng.gen_bool((*value / 100.0) as f64) {
|
||||
match *effect {
|
||||
DamageProcEffect::MultiTurn { value, bonus } => {
|
||||
if multi_attack_proc.is_some() {
|
||||
continue;
|
||||
}
|
||||
let chance = (value / 100.0) as f64;
|
||||
if chance > 1.0 || rng.gen_bool(chance) {
|
||||
match bonus {
|
||||
MultiTurnBonus::Blindfire => {
|
||||
multi_attack_proc = Some(MultiAttack::Blindfire)
|
||||
|
|
@ -545,7 +570,18 @@ pub fn use_damaging_weapon(
|
|||
metrics.increment_counter(weapon, bonus.counter_label(), 1);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
DamageProcEffect::SelfEffect { value, bonus } => {
|
||||
let chance = (value / 100.0) as f64;
|
||||
if chance > 1.0 || rng.gen_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) {
|
||||
bonus.spawn(target, &mut effects, &mut rng.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -561,6 +597,7 @@ pub fn use_damaging_weapon(
|
|||
part: %body_part,
|
||||
part_mult: mult,
|
||||
dmg: dmg,
|
||||
rounds: rounds,
|
||||
health_before: health_before,
|
||||
health_after: health.value,
|
||||
dmg_spread: dmg_spread,
|
||||
|
|
@ -584,7 +621,7 @@ pub fn use_damaging_weapon(
|
|||
FightEndType::Victory
|
||||
} else {
|
||||
FightEndType::Loss
|
||||
}
|
||||
},
|
||||
});
|
||||
metrics.increment_counter(player, "victory", 1);
|
||||
}
|
||||
|
|
@ -613,7 +650,7 @@ pub fn use_damaging_weapon(
|
|||
}
|
||||
|
||||
pub fn check_stalemate(
|
||||
current_q: Query<(Entity, &CombatTurns, Option<&Attacker>), (With<Current>, With<Player>)>,
|
||||
current_q: Query<(Entity, &CombatTurns, Has<Attacker>), (With<Current>, With<Player>)>,
|
||||
target_q: Query<Entity, With<CurrentTarget>>,
|
||||
other_attackers_q: Query<(), (With<Attacker>, Without<Current>)>,
|
||||
mut state: ResMut<FightStatus>,
|
||||
|
|
@ -622,14 +659,14 @@ pub fn check_stalemate(
|
|||
metrics: Res<Metrics>,
|
||||
) {
|
||||
let (current, current_turns, attacker) = current_q.single();
|
||||
if *state == FightStatus::Ongoing && current_turns.0 >= 25 && attacker.is_some() {
|
||||
if *state == FightStatus::Ongoing && current_turns.0 >= 25 && attacker {
|
||||
commands.entity(current).insert(Defeated);
|
||||
let target = target_q.single();
|
||||
|
||||
log!(logger, "fight_end", {
|
||||
actor: current,
|
||||
recipient: target,
|
||||
fight_end_type: %FightEndType::Stalemate
|
||||
fight_end_type: %FightEndType::Stalemate,
|
||||
});
|
||||
metrics.increment_counter(current, "stalemate", 1);
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@ pub trait SimpleStatMarker: Send + Sync + 'static {
|
|||
|
||||
fn apply_bonus(value: Self::ValueType, bonus: Self::BonusType) -> Self::ValueType;
|
||||
fn revert_bonus(value: Self::ValueType, bonus: Self::BonusType) -> Self::ValueType;
|
||||
fn denormalise_value(value: Self::ValueType) -> Self::ValueType {
|
||||
value
|
||||
}
|
||||
fn denormalise_bonus(value: Self::BonusType) -> Self::BonusType {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
|
|
@ -143,6 +149,12 @@ impl SimpleStatMarker for AmmoControl {
|
|||
fn revert_bonus(value: Self::ValueType, bonus: Self::BonusType) -> Self::ValueType {
|
||||
value - bonus
|
||||
}
|
||||
fn denormalise_value(value: Self::ValueType) -> Self::ValueType {
|
||||
value * 100.0
|
||||
}
|
||||
fn denormalise_bonus(value: Self::BonusType) -> Self::BonusType {
|
||||
value * 100.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -157,6 +169,12 @@ impl SimpleStatMarker for DamageBonus {
|
|||
fn revert_bonus(value: Self::ValueType, bonus: Self::BonusType) -> Self::ValueType {
|
||||
value - bonus
|
||||
}
|
||||
fn denormalise_value(value: Self::ValueType) -> Self::ValueType {
|
||||
value * 100.0
|
||||
}
|
||||
fn denormalise_bonus(value: Self::BonusType) -> Self::BonusType {
|
||||
value * 100.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -171,6 +189,12 @@ impl SimpleStatMarker for WeaponAccuracy {
|
|||
fn revert_bonus(value: Self::ValueType, bonus: Self::BonusType) -> Self::ValueType {
|
||||
value - bonus
|
||||
}
|
||||
fn denormalise_value(value: Self::ValueType) -> Self::ValueType {
|
||||
value * 50.0 + 50.0
|
||||
}
|
||||
fn denormalise_bonus(value: Self::BonusType) -> Self::BonusType {
|
||||
value * 50.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -490,7 +514,7 @@ fn apply_simple_stat_bonus<Stat: SimpleStatMarker>(
|
|||
effect_q: Query<(&SimpleStatBonus<Stat>, &Parent)>,
|
||||
mut stat_q: Query<&mut SimpleStatEffective<Stat>>,
|
||||
) {
|
||||
for (bonus, target) in effect_q.iter_many(entities) {
|
||||
for (bonus, target) in effect_q.iter_many(&entities) {
|
||||
let mut effective = stat_q.get_mut(target.get()).unwrap();
|
||||
effective.value = Stat::apply_bonus(effective.value, bonus.value);
|
||||
}
|
||||
|
|
@ -568,5 +592,6 @@ pub(crate) fn configure(stages: &mut Stages) {
|
|||
register_simple_stat_effects::<DamageBonus>(stages);
|
||||
register_simple_stat_effects::<WeaponAccuracy>(stages);
|
||||
register_simple_stat_effects::<ClipSize>(stages);
|
||||
register_simple_stat_effects::<Clips>(stages);
|
||||
register_simple_stat_effects::<Health>(stages);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
use std::{collections::VecDeque, marker::PhantomData};
|
||||
|
||||
use bevy_ecs::prelude::*;
|
||||
use macros::LogMessage;
|
||||
use rand::Rng as _;
|
||||
|
||||
use crate::{
|
||||
effect::{Effects, TimeLimitedEffect},
|
||||
hierarchy::{HierarchyBuilder, Parent},
|
||||
log,
|
||||
log::Logger,
|
||||
weapon::temp::AssociatedWeapon,
|
||||
Rng, Stages,
|
||||
|
|
@ -117,7 +117,7 @@ impl DebuffingTempMarker for Sand {
|
|||
#[derive(Component)]
|
||||
struct LinkedComponents<const N: usize>([Entity; N]);
|
||||
|
||||
trait Stats<const N: usize> {
|
||||
pub trait Stats<const N: usize> {
|
||||
fn spawn_additive_effects(
|
||||
effects: &mut Effects,
|
||||
target: Entity,
|
||||
|
|
@ -160,15 +160,18 @@ impl_n_stats!(2, A, B);
|
|||
impl_n_stats!(3, A, B, C);
|
||||
impl_n_stats!(4, A, B, C, D);
|
||||
|
||||
trait AdditiveStatusEffectMarker<const N: usize>: Send + Sync + 'static {
|
||||
// TODO: the const generic arguably isn't worth the trouble and it might be better to remove it
|
||||
pub trait AdditiveStatusEffectMarker<const N: usize>: Send + Sync + 'static {
|
||||
type AffectedStats: Stats<N>;
|
||||
fn max_stack() -> usize;
|
||||
fn factor() -> f32;
|
||||
fn duration() -> f32;
|
||||
}
|
||||
|
||||
// TODO: instead of tracking it in the status effect itself, add generic
|
||||
// `StatusEffectEffectiveness` and `StatusEffectExtraDuration` components
|
||||
#[derive(Component)]
|
||||
struct AdditiveStatusEffect<const N: usize, M>
|
||||
pub struct AdditiveStatusEffect<const N: usize, M>
|
||||
where
|
||||
M: AdditiveStatusEffectMarker<N>,
|
||||
{
|
||||
|
|
@ -187,16 +190,6 @@ impl<const N: usize, M: AdditiveStatusEffectMarker<N>> Default for AdditiveStatu
|
|||
}
|
||||
}
|
||||
|
||||
impl<const N: usize, M: AdditiveStatusEffectMarker<N>> AdditiveStatusEffect<N, M> {
|
||||
pub fn new(extra_effectiveness: f32, extra_duration: f32) -> Self {
|
||||
Self {
|
||||
marker: PhantomData,
|
||||
extra_effectiveness,
|
||||
extra_duration,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Withered;
|
||||
|
||||
impl AdditiveStatusEffectMarker<1> for Withered {
|
||||
|
|
@ -368,8 +361,14 @@ fn apply_additive_status_effect<const N: usize, M: AdditiveStatusEffectMarker<N>
|
|||
mut parent_q: Query<Option<&mut StatusEffectStack<M>>>,
|
||||
mut commands: Commands,
|
||||
mut effects: Effects,
|
||||
mut logger: Logger,
|
||||
) {
|
||||
for (entity, player, effect) in effect_q.iter_many(entities) {
|
||||
log!(logger, "apply_status_effect", {
|
||||
recipient: player.get(),
|
||||
effect: std::any::type_name::<M>(),
|
||||
});
|
||||
|
||||
let stack = parent_q.get_mut(player.get()).unwrap();
|
||||
|
||||
let new_effects = <M::AffectedStats as Stats<N>>::spawn_additive_effects(
|
||||
|
|
@ -434,24 +433,13 @@ fn apply_temp_debuff_effect<Temp: DebuffingTempMarker>(
|
|||
(mut commands, mut effects): (Commands, Effects),
|
||||
mut logger: Logger,
|
||||
) {
|
||||
#[derive(LogMessage)]
|
||||
pub struct UsedDebuffTemp {
|
||||
#[log(player)]
|
||||
pub actor: Entity,
|
||||
#[log(player)]
|
||||
pub recipient: Entity,
|
||||
#[log(weapon)]
|
||||
pub weapon: Entity,
|
||||
pub immune: bool,
|
||||
}
|
||||
|
||||
for (effect, player, weapon) in temp_q.iter_many(entities) {
|
||||
let (stack, immunity) = parent_q.get_mut(player.get()).unwrap();
|
||||
let user = weapon_q.get(weapon.0).unwrap();
|
||||
if immunity {
|
||||
commands.entity(effect).despawn();
|
||||
commands.entity(player.get()).remove_child(effect);
|
||||
logger.log(|| UsedDebuffTemp {
|
||||
log!(logger, "used_debuff_temp", {
|
||||
actor: user.get(),
|
||||
recipient: player.get(),
|
||||
weapon: weapon.0,
|
||||
|
|
@ -488,7 +476,7 @@ fn apply_temp_debuff_effect<Temp: DebuffingTempMarker>(
|
|||
});
|
||||
}
|
||||
|
||||
logger.log(|| UsedDebuffTemp {
|
||||
log!(logger, "used_debuff_temp", {
|
||||
actor: user.get(),
|
||||
recipient: player.get(),
|
||||
weapon: weapon.0,
|
||||
|
|
@ -505,14 +493,6 @@ fn remove_temp_debuff_effect<Temp: DebuffingTempMarker>(
|
|||
_logger: Logger,
|
||||
mut effects: Effects,
|
||||
) {
|
||||
#[derive(LogMessage)]
|
||||
struct RemovedDebuffTemp {
|
||||
#[log(player)]
|
||||
recipient: Entity,
|
||||
factor: f32,
|
||||
factor_remaining: f32,
|
||||
}
|
||||
|
||||
for player in temp_q.iter_many(entities) {
|
||||
let (mut stack, immunity) = parent_q.get_mut(player.get()).unwrap();
|
||||
if immunity {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue