diff --git a/models/src/bundle/bonus.rs b/models/src/bundle/bonus.rs index 86678d9..81f07fe 100644 --- a/models/src/bundle/bonus.rs +++ b/models/src/bundle/bonus.rs @@ -59,7 +59,7 @@ pub enum WeaponBonusType { Roshambo, Throttle, - // Attack nullification types + // Action nullification types Homerun, Parry, diff --git a/models/src/bundle/player.rs b/models/src/bundle/player.rs index 47f41e2..c2de1a9 100644 --- a/models/src/bundle/player.rs +++ b/models/src/bundle/player.rs @@ -21,6 +21,9 @@ pub struct Defeated; #[derive(Component)] pub struct Current; +#[derive(Component)] +pub struct PickedAction(pub WeaponSlot); + #[derive(Component)] pub struct CurrentTarget; @@ -139,6 +142,13 @@ pub enum HealthChange { DoubleEdged { dmg: u32 }, } +#[derive(Component, Debug)] +pub enum ActionNullification { + Parry { chance: f64 }, + Homerun { chance: f64 }, + GentsStripClub, +} + impl PartDamageBonus { pub fn dmg_bonus(&self, part: BodyPart) -> Option { match self { diff --git a/models/src/dto/weapon.rs b/models/src/dto/weapon.rs index 2efe8c1..d145659 100644 --- a/models/src/dto/weapon.rs +++ b/models/src/dto/weapon.rs @@ -305,11 +305,11 @@ impl WeaponDto { commands.insert(EquippedMods(mods)); if let Some(bonus) = &self.bonuses[0] { - commands.insert(children![WeaponBonusBundle::new(bonus.bonus, bonus.value)]); + commands.with_child(WeaponBonusBundle::new(bonus.bonus, bonus.value)); } if let Some(bonus) = &self.bonuses[1] { - commands.insert(children![WeaponBonusBundle::new(bonus.bonus, bonus.value)]); + commands.with_child(WeaponBonusBundle::new(bonus.bonus, bonus.value)); } commands.id() diff --git a/src/attacker.json b/src/attacker.json index 06c2dce..9b7febb 100644 --- a/src/attacker.json +++ b/src/attacker.json @@ -10,15 +10,16 @@ "strategy": { "type": "in_order", "order": [ - "secondary" + "temporary", + "melee" ], - "reload": false + "reload": true }, "education": { "bio2350": true, "bio2380": true, "bio2410": true, - "cbt2790": false, + "cbt2790": true, "cbt2820": true, "cbt2830": true, "cbt2840": true, @@ -27,33 +28,33 @@ "cbt2125": true, "gen2116": true, "gen2119": true, - "haf2104": false, - "haf2105": false, - "haf2106": false, - "haf2107": false, - "haf2108": false, - "haf2109": false, + "haf2104": true, + "haf2105": true, + "haf2106": true, + "haf2107": true, + "haf2108": true, + "haf2109": true, "his2160": true, "his2170": true, - "mth2240": false, - "mth2250": false, - "mth2260": false, - "mth2320": false, + "mth2240": true, + "mth2250": true, + "mth2260": true, + "mth2320": true, "mth2310": true, "mth3330": true, - "psy2640": false, - "psy2650": false, - "psy2660": false, - "psy2670": false, - "def2710": false, - "def2730": false, - "def2740": false, - "def2750": false, - "def2760": false, + "psy2640": true, + "psy2650": true, + "psy2660": true, + "psy2670": true, + "def2710": true, + "def2730": true, + "def2740": true, + "def2750": true, + "def2760": true, "def3770": true, "spt2480": true, - "spt2490": false, - "spt2500": false + "spt2490": true, + "spt2500": true }, "merits": { "life": 10, @@ -86,148 +87,142 @@ }, "weapons": { "primary": null, - "secondary": { - "id": 490, - "name": "Blunderbuss", - "kind": "secondary", - "cat": "shotgun", - "base_dmg": 46, - "base_acc": 24, - "dmg": 46, - "acc": 24, - "ammo": { - "clip_size": 1, - "rate_of_fire": [ - 1, - 1 - ] - }, + "secondary": null, + "melee": { + "id": 247, + "name": "Katana", + "kind": "melee", + "cat": "slashing", + "base_dmg": 52, + "base_acc": 55, + "dmg": 52, + "acc": 55, + "ammo": null, "mods": [ null, null ], "bonuses": [ - { - "bonus": "bleed", - "value": 100 - }, + null, null ], - "compatible_mods": [ - "laser1mw", - "laser5mw", - "laser30mw", - "laser100mw", - "extra_clip", - "extra_clip2", - "adjustable_trigger", - "hair_trigger", - "custom_grip", - "skeet_choke", - "improved_choke", - "full_choke", - "small_light", - "precision_light", - "tactical_illuminator" + "compatible_mods": [], + "experience": 0, + "japanese": true + }, + "temporary": { + "id": 463, + "name": "Epinephrine", + "kind": "temporary", + "cat": "temporary", + "base_dmg": 0, + "base_acc": 0, + "dmg": 0, + "acc": 0, + "ammo": null, + "mods": [ + null, + null ], + "bonuses": [ + null, + null + ], + "compatible_mods": [], "experience": 0, "japanese": false - }, - "melee": null, - "temporary": null + } }, "armour": { "helmet": { "slot": "head", - "id": 1355, - "name": "Vanguard Respirator", - "base_armour": 48, - "armour": 48, + "id": 675, + "name": "Marauder Face Mask", + "base_armour": 40, + "armour": 40, "coverage": { - "body": 2.819999933242798, + "body": 11.489999771118164, "heart": 0, "stomach": 0, "chest": 0, "arm": 0, "groin": 0, "leg": 0, - "throat": 0, + "throat": 60.56999969482422, "hand": 0, "foot": 0, - "head": 39.599998474121094 + "head": 100 }, "immunities": [ - "pepper_spray", - "nerve_gas", - "tear_gas" + "pepper_spray" ], "bonus": { - "kind": "irrepressible", - "value": null + "kind": "invulnerable", + "value": 4 } }, "body": { "slot": "body", - "id": 1356, - "name": "Vanguard Body", - "base_armour": 48, - "armour": 48, + "id": 676, + "name": "Marauder Body", + "base_armour": 52, + "armour": 52, "coverage": { - "body": 44.279998779296875, + "body": 42.470001220703125, "heart": 100, "stomach": 100, "chest": 100, "arm": 100, - "groin": 35.38999938964844, - "leg": 0.2800000011920929, - "throat": 83.52999877929688, - "hand": 0.12999999523162842, + "groin": 0, + "leg": 0, + "throat": 84.72000122070312, + "hand": 5.050000190734863, "foot": 0, "head": 0 }, "immunities": [], "bonus": { - "kind": "irrepressible", - "value": null + "kind": "imperviable", + "value": 4 } }, "pants": { "slot": "legs", - "id": 1357, - "name": "Vanguard Pants", - "base_armour": 48, - "armour": 48, + "id": 677, + "name": "Marauder Pants", + "base_armour": 52, + "armour": 52, "coverage": { - "body": 24.959999084472656, + "body": 20.989999771118164, "heart": 0, - "stomach": 0, + "stomach": 0.5199999809265137, "chest": 0, "arm": 0, - "groin": 99.06999969482422, - "leg": 100, + "groin": 100, + "leg": 96.69000244140625, "throat": 0, "hand": 0, - "foot": 25.200000762939453, + "foot": 0, "head": 0 }, "immunities": [], "bonus": { - "kind": "irrepressible", - "value": null + "kind": "imperviable", + "value": 4 } }, "gloves": { "slot": "hands", - "id": 1359, - "name": "Vanguard Gloves", - "base_armour": 48, - "armour": 48, + "id": 679, + "name": "Marauder Gloves", + "base_armour": 52, + "armour": 52, "coverage": { - "body": 14.399999618530273, + "body": 14.369999885559082, "heart": 0, "stomach": 0, "chest": 0, - "arm": 0.7300000190734863, + "arm": 0.5, "groin": 0, "leg": 0, "throat": 0, @@ -237,24 +232,24 @@ }, "immunities": [], "bonus": { - "kind": "irrepressible", - "value": null + "kind": "invulnerable", + "value": 4 } }, "boots": { "slot": "feet", - "id": 1358, - "name": "Vanguard Boots", - "base_armour": 48, - "armour": 48, + "id": 678, + "name": "Marauder Boots", + "base_armour": 52, + "armour": 52, "coverage": { - "body": 15.130000114440918, + "body": 15.649999618530273, "heart": 0, "stomach": 0, "chest": 0, "arm": 0, "groin": 0, - "leg": 5.829999923706055, + "leg": 9.529999732971191, "throat": 0, "hand": 0, "foot": 100, @@ -262,9 +257,12 @@ }, "immunities": [], "bonus": { - "kind": "irrepressible", - "value": null + "kind": "invulnerable", + "value": 4 } } + }, + "property": { + "damage": true } } diff --git a/src/defender.json b/src/defender.json index 4c74f16..720dcfc 100644 --- a/src/defender.json +++ b/src/defender.json @@ -10,7 +10,8 @@ "strategy": { "type": "in_order", "order": [ - "primary" + "primary", + "melee" ], "reload": false }, @@ -85,9 +86,92 @@ "side_effects": 10 }, "weapons": { - "primary": null, + "primary": { + "id": 399, + "name": "ArmaLite M-15A4", + "kind": "primary", + "cat": "rifle", + "base_dmg": 68, + "base_acc": 57, + "dmg": 68, + "acc": 57, + "ammo": { + "ammo_kind": "standard", + "clip_size": 15, + "rate_of_fire": [ + 3, + 5 + ] + }, + "mods": [ + null, + null + ], + "bonuses": [ + null, + null + ], + "compatible_mods": [ + "reflex_sight", + "holographic_sight", + "acog_sight", + "thermal_sight", + "laser1mw", + "laser5mw", + "laser30mw", + "laser100mw", + "small_suppressor", + "standard_suppressor", + "large_suppressor", + "extended_mags", + "high_capacity_mags", + "extra_clip", + "extra_clip2", + "bipod", + "tripod", + "adjustable_trigger", + "hair_trigger", + "custom_grip", + "recoil_pad", + "standard_brake", + "heavy_duty_brake", + "tactical_brake", + "small_light", + "precision_light", + "tactical_illuminator" + ], + "experience": 0, + "japanese": false + }, "secondary": null, - "melee": null, + "melee": { + "id": 237, + "name": "Kodachi", + "kind": "melee", + "cat": "slashing", + "base_dmg": 62, + "base_acc": 56, + "dmg": 62, + "acc": 56, + "ammo": null, + "mods": [ + null, + null + ], + "bonuses": [ + { + "bonus": "parry", + "value": 100 + }, + { + "bonus": "homerun", + "value": 100 + } + ], + "compatible_mods": [], + "experience": 0, + "japanese": true + }, "temporary": null }, "armour": { @@ -121,27 +205,27 @@ }, "body": { "slot": "body", - "id": 681, - "name": "EOD Apron", - "base_armour": 55, - "armour": 55, + "id": 676, + "name": "Marauder Body", + "base_armour": 52, + "armour": 52, "coverage": { - "body": 55.2400016784668, + "body": 42.470001220703125, "heart": 100, "stomach": 100, "chest": 100, "arm": 100, - "groin": 100, - "leg": 17.579999923706055, - "throat": 100, - "hand": 16.8799991607666, + "groin": 0, + "leg": 0, + "throat": 84.72000122070312, + "hand": 5.050000190734863, "foot": 0, - "head": 4.679999828338623 + "head": 0 }, "immunities": [], "bonus": { - "kind": "impassable", - "value": 0 + "kind": "imperviable", + "value": null } }, "pants": { diff --git a/src/player/mod.rs b/src/player/mod.rs index 976202b..25e99cb 100644 --- a/src/player/mod.rs +++ b/src/player/mod.rs @@ -3,8 +3,9 @@ use proxisim_models::bundle::{ armour::{ArmourBodyPart, ArmourBodyParts, BodyPartCoverage}, bonus::{ArmourBypassBonus, DamageMitigationBonus, MiscBonus}, player::{ - Attacker, BodyPart, ChooseWeapon, CombatTurns, Current, CurrentTarget, Defeated, Defender, - FightEndType, Health, HealthChange, PartDamageBonus, Player, PlayerStrategy, Weapons, + ActionNullification, Attacker, BodyPart, ChooseWeapon, CombatTurns, Current, CurrentTarget, + Defeated, Defender, FightEndType, Health, HealthChange, PartDamageBonus, PickedAction, + Player, PlayerStrategy, Weapons, }, stat::{ AmmoControl, Clips, CritRate, DamageBonus, Defence, Dexterity, EffectiveStat, MaxHealth, @@ -33,12 +34,12 @@ use crate::{ pub mod stats; pub mod status_effect; -fn select_weapon<'a>( +fn select_weapon( weapons: &Weapons, slot: WeaponSlot, reload: bool, - usable_q: &'a Query<(Has, Option<&Children>), With>, -) -> Option<(Entity, Option<&'a Children>)> { + usable_q: &Query, With>, +) -> Option { let id = match slot { WeaponSlot::Primary => weapons.primary?, WeaponSlot::Secondary => weapons.secondary?, @@ -48,12 +49,12 @@ fn select_weapon<'a>( WeaponSlot::Kick => weapons.kick, }; - let (needs_reload, children) = usable_q.get(id).ok()?; + let needs_reload = usable_q.get(id).ok()?; if !reload && needs_reload { None } else { - Some((id, children)) + Some(slot) } } @@ -99,37 +100,61 @@ fn check_term_condition( } pub fn pick_action( + p_query: Query<(Entity, &Weapons, &PlayerStrategy), (With, Without)>, + usable_q: Query, With>, + mut commands: Commands, +) { + for (player, weapons, strat) in p_query { + let slot = match strat { + PlayerStrategy::AlwaysFists => WeaponSlot::Fists, + PlayerStrategy::AlwaysKicks => { + select_weapon(weapons, WeaponSlot::Kick, true, &usable_q) + .unwrap_or(WeaponSlot::Fists) + } + PlayerStrategy::PrimaryMelee { reload } => { + select_weapon(weapons, WeaponSlot::Primary, *reload, &usable_q) + .or_else(|| select_weapon(weapons, WeaponSlot::Melee, true, &usable_q)) + .unwrap_or(WeaponSlot::Fists) + } + PlayerStrategy::InOrder { order, reload } => order + .iter() + .find_map(|slot| select_weapon(weapons, *slot, *reload, &usable_q)) + .unwrap_or(WeaponSlot::Fists), + }; + + commands.entity(player).insert(PickedAction(slot)); + } +} + +pub fn prepare_weapon( mut p_query: Query< - (Entity, &Weapons, &PlayerStrategy, &mut CombatTurns), + (Entity, &Weapons, &PickedAction, &mut CombatTurns), (With, With), >, target_q: Query>, - usable_q: Query<(Has, Option<&Children>), With>, + child_q: Query>, weapon_trigger_q: Query<&TurnTriggeredEffect>, mut commands: Commands, mut effects: Effects, metrics: Res, ) { - let (current, weapons, strat, mut turns) = p_query.single_mut().unwrap(); - let (weapon, children) = match strat { - 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| select_weapon(weapons, *slot, *reload, &usable_q)) - .unwrap_or_else(|| (weapons.fists, Default::default())), + let (current, weapons, picked_action, mut turns) = p_query.single_mut().unwrap(); + let weapon = match picked_action.0 { + WeaponSlot::Primary => weapons.primary.unwrap_or(weapons.fists), + WeaponSlot::Secondary => weapons.secondary.unwrap_or(weapons.fists), + WeaponSlot::Melee => weapons.melee.unwrap_or(weapons.fists), + WeaponSlot::Temporary => weapons.temporary.unwrap_or(weapons.fists), + WeaponSlot::Fists => weapons.fists, + WeaponSlot::Kick => weapons.kick, }; + metrics.increment_counter(Some(current), "turn", 1); metrics.increment_counter(Some(weapon), "turn", 1); commands.entity(weapon).insert(Current); + let children = child_q.get(weapon).unwrap(); + let target = target_q.single().unwrap(); if let Some(children) = children { @@ -297,6 +322,46 @@ fn check_damage_bonuses( } } +#[derive(SystemParam)] +struct NullificationParams<'w, 's> { + nullification_q: Query<'w, 's, &'static ActionNullification>, +} + +fn check_action_nullification( + params: &NullificationParams, + shared: &mut SharedParams, + target_children: &Children, + target_action: &PickedAction, + player_action: &PickedAction, +) -> Option<&'static str> { + for bonus in params.nullification_q.iter_many(target_children) { + match bonus { + ActionNullification::Parry { chance } + if player_action.0 == WeaponSlot::Melee + && target_action.0 == WeaponSlot::Melee + && (*chance >= 1.0 || shared.rng.random_bool(*chance)) => + { + return Some("parry"); + } + ActionNullification::Homerun { chance } + if player_action.0 == WeaponSlot::Temporary + && target_action.0 == WeaponSlot::Melee + && (*chance >= 1.0 || shared.rng.random_bool(*chance)) => + { + return Some("homerun"); + } + ActionNullification::GentsStripClub + if player_action.0 == WeaponSlot::Melee && shared.rng.random_bool(0.25) => + { + return Some("dodged"); + } + _ => (), + } + } + + None +} + #[derive(SystemParam)] struct DeferredDamageParams<'w, 's> { damage_q: Query<'w, 's, (Entity, &'static DeferredDamage)>, @@ -379,6 +444,7 @@ struct EntityParams<'w, 's> { &'static SimpleStatEffective, &'static SimpleStatEffective, &'static Children, + &'static PickedAction, &'static mut Health, Has, ), @@ -395,6 +461,8 @@ struct EntityParams<'w, 's> { &'static EffectiveStat, &'static SimpleStatEffective, &'static ArmourBodyParts, + &'static Children, + &'static PickedAction, &'static mut Health, ), (With, Without), @@ -405,11 +473,12 @@ struct EntityParams<'w, 's> { // of multi turn bonuses #[allow(clippy::too_many_arguments)] fn use_damaging_weapon( - (mut shared, mut bonus_mitigation, deferred, misc_bonus): ( + (mut shared, mut bonus_mitigation, deferred, misc_bonus, nullification): ( SharedParams, BonusMitigationParams, DeferredDamageParams, DamageBonusParams, + NullificationParams, ), mut entities: EntityParams, armour_q: Query<&ArmourBodyPart>, @@ -440,11 +509,20 @@ fn use_damaging_weapon( p_dmg_bonus, p_max_health, p_children, + p_action, mut p_health, attacker, ) = entities.player_q.single_mut().unwrap(); - let (target, target_dex, target_def, target_max_health, armour_parts, mut health) = - entities.target_q.single_mut().unwrap(); + let ( + target, + target_dex, + target_def, + target_max_health, + armour_parts, + t_children, + t_action, + mut health, + ) = entities.target_q.single_mut().unwrap(); if check_deferred_damage( &deferred, @@ -549,6 +627,26 @@ fn use_damaging_weapon( return; }; } else { + if multi_attack_proc.is_none() + && let Some(kind) = check_action_nullification( + &nullification, + &mut shared, + t_children, + t_action, + p_action, + ) + { + log!(shared.logger, "nullified", { + actor: player, + recipient: player, + weapon: weapon, + rounds, + kind, + }); + + return; + } + let body_part = if !non_targeted { shared.rng.sample(crit) } else { @@ -641,7 +739,7 @@ fn use_damaging_weapon( shared.metrics.record_histogram(Some(player), "dmg", dmg_i); shared.metrics.record_histogram(Some(weapon), "dmg", dmg_i); - if dmg_i > 0 { + if multi_attack_proc.is_none() && dmg_i > 0 { for effect in damage_proc_q.iter_many(children) { match *effect { DamageProcEffect::MultiTurn { value, bonus } => { @@ -930,6 +1028,24 @@ fn process_health_changes( } } +fn remove_action_end_of_turn( + attacker_turn: Query, (With, With)>, + picked_q: Query>, + mut commands: Commands, +) { + if !attacker_turn.single().unwrap() { + for player in picked_q { + commands.entity(player).remove::(); + } + } +} + +fn remove_picked_actions(picked_q: Query>, mut commands: Commands) { + for player in picked_q { + commands.entity(player).remove::(); + } +} + pub(crate) fn configure(stages: &mut Stages) { stats::configure(stages); status_effect::configure(stages); @@ -937,19 +1053,28 @@ pub(crate) fn configure(stages: &mut Stages) { stages.add_event::(); stages.add_event::(); stages.equip.add_systems(designate_first); - stages.pre_fight.add_systems(restore_health); - stages.pre_turn.add_systems(pick_action); - stages.turn.add_systems(use_damaging_weapon); + stages.pre_fight.add_systems((restore_health, pick_action)); + stages.pre_turn.add_systems(prepare_weapon); + stages + .turn + .add_systems((use_damaging_weapon, remove_action_end_of_turn)); stages .post_turn - .add_systems((check_term_condition, change_roles, process_health_changes)) + .add_systems(( + check_term_condition, + change_roles, + process_health_changes, + pick_action, + )) .add_systems( check_stalemate .after(check_term_condition) .before(change_roles), ); - stages.post_fight.add_systems(record_post_fight_stats); + stages + .post_fight + .add_systems((record_post_fight_stats, remove_picked_actions)); stages .restore - .add_systems((restore_initial_state, restore_health)); + .add_systems((restore_initial_state, restore_health, pick_action)); } diff --git a/src/weapon/bonus.rs b/src/weapon/bonus.rs index c7d2bec..3dac0ae 100644 --- a/src/weapon/bonus.rs +++ b/src/weapon/bonus.rs @@ -1,11 +1,12 @@ use bevy_ecs::prelude::*; use proxisim_models::bundle::{ bonus::{ArmourBypassBonus, BonusPartDamageBonus, BonusValue, MiscBonus, WeaponBonusType}, - player::PartDamageBonus, + player::{ActionNullification, PartDamageBonus}, stat::{ AdditiveBonus, AmmoControl, Clips, CritRate, DamageBonus, SimpleStatBonus, SimpleStatEffective, Speed, Strength, WeaponAccuracy, }, + weapon::Weapon, }; use crate::{ @@ -197,10 +198,11 @@ impl BonusPartDamageBonus { pub(crate) fn prepare_bonuses( bonus_q: Query<(&ChildOf, &WeaponBonusType, &BonusValue)>, clips_q: Query<&SimpleStatEffective>, + weapon_q: Query<&ChildOf, With>, mut effects: Effects, mut commands: Commands, ) { - for (weapon, bonus, value) in bonus_q.iter() { + for (weapon, bonus, value) in bonus_q { match bonus { WeaponBonusType::Berserk => { effects.spawn( @@ -495,6 +497,23 @@ pub(crate) fn prepare_bonuses( chance: value.0 as f64 / 100.0, }); } + + WeaponBonusType::Parry => { + let player = weapon_q.get(weapon.parent()).unwrap(); + commands + .entity(player.parent()) + .with_child(ActionNullification::Parry { + chance: value.0 as f64 / 100.0, + }); + } + WeaponBonusType::Homerun => { + let player = weapon_q.get(weapon.parent()).unwrap(); + commands + .entity(player.parent()) + .with_child(ActionNullification::Homerun { + chance: value.0 as f64 / 100.0, + }); + } val => unimplemented!("{val:?}"), } } diff --git a/src/weapon/mod.rs b/src/weapon/mod.rs index 28ef2ce..135c958 100644 --- a/src/weapon/mod.rs +++ b/src/weapon/mod.rs @@ -540,12 +540,15 @@ pub(crate) fn configure(stages: &mut Stages) { // running this in the snapshot layer ensures that the stat increases aren't restored at the // end of the run stages.snapshot.add_systems(apply_first_turn_effects); - stages.equip.add_systems(apply_passives); + stages + .equip + .add_systems((apply_passives, restore_usability)); stages.turn.add_systems(reload_weapon); stages.post_turn.add_systems(unset_current); stages - .restore - .add_systems((restore_ammo, restore_usability, apply_first_turn_effects)); + .post_fight + .add_systems((restore_usability, restore_ammo)); + stages.restore.add_systems(apply_first_turn_effects); temp::configure(stages); bonus::configure(stages); diff --git a/src/weapon/temp.rs b/src/weapon/temp.rs index 011996e..0840dfa 100644 --- a/src/weapon/temp.rs +++ b/src/weapon/temp.rs @@ -1,11 +1,12 @@ use bevy_ecs::prelude::*; use proxisim_models::bundle::{ - player::{Current, CurrentTarget, HealthChange, Player}, - weapon::{BuffingTemp, DebuffingTemp, Usable, Uses}, + player::{ActionNullification, Current, CurrentTarget, HealthChange, PickedAction, Player}, + weapon::{BuffingTemp, DebuffingTemp, Usable, Uses, WeaponSlot}, }; +use rand::Rng as _; use crate::{ - Stages, + Rng, Stages, effect::Effects, log, log::Logger, @@ -19,15 +20,39 @@ use crate::{ pub struct AssociatedWeapon(pub Entity); fn use_debuffing_temp( - mut temp_q: Query<(Entity, &DebuffingTemp, &mut Uses), With>, - target_q: Query>, + mut temp_q: Query<(Entity, &DebuffingTemp, &mut Uses, &ChildOf), With>, + target_q: Query<(Entity, &Children, &PickedAction), With>, + nullification_q: Query<&ActionNullification>, mut effects: Effects, mut commands: Commands, + mut rng: ResMut, + mut logger: Logger, ) { - let Ok((weapon, temp, mut uses)) = temp_q.single_mut() else { + let Ok((weapon, temp, mut uses, player)) = temp_q.single_mut() else { return; }; - let target = target_q.single().unwrap(); + let (target, children, picked_action) = target_q.single().unwrap(); + + uses.0 -= 1; + if uses.0 == 0 { + commands.entity(weapon).remove::(); + } + + for bonus in nullification_q.iter_many(children) { + if let ActionNullification::Homerun { chance } = bonus + && picked_action.0 == WeaponSlot::Melee + && (*chance >= 1.0 || rng.random_bool(*chance)) + { + log!(logger, "nullified", { + actor: player.parent(), + recipient: target, + kind: "homerun", + weapon, + }); + + return; + } + } match temp { DebuffingTemp::TearGas => effects.spawn_and_insert( @@ -56,11 +81,6 @@ fn use_debuffing_temp( AssociatedWeapon(weapon), ), }; - - uses.0 -= 1; - if uses.0 == 0 { - commands.entity(weapon).remove::(); - } } fn use_buffing_temp(