feat: added parry and homerun

This commit is contained in:
TotallyNot 2025-11-05 19:40:16 +01:00
parent 71169690b7
commit 67131c7985
Signed by: pyrite
GPG key ID: 7F1BA9170CD35D15
9 changed files with 440 additions and 181 deletions

View file

@ -59,7 +59,7 @@ pub enum WeaponBonusType {
Roshambo,
Throttle,
// Attack nullification types
// Action nullification types
Homerun,
Parry,

View file

@ -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<f32> {
match self {

View file

@ -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()

View file

@ -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
}
}

View file

@ -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": {

View file

@ -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<NeedsReload>, Option<&Children>), With<Usable>>,
) -> Option<(Entity, Option<&'a Children>)> {
usable_q: &Query<Has<NeedsReload>, With<Usable>>,
) -> Option<WeaponSlot> {
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<Player>, Without<PickedAction>)>,
usable_q: Query<Has<NeedsReload>, With<Usable>>,
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<Current>, With<Player>),
>,
target_q: Query<Entity, With<CurrentTarget>>,
usable_q: Query<(Has<NeedsReload>, Option<&Children>), With<Usable>>,
child_q: Query<Option<&Children>>,
weapon_trigger_q: Query<&TurnTriggeredEffect>,
mut commands: Commands,
mut effects: Effects,
metrics: Res<Metrics>,
) {
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<DamageBonus>,
&'static SimpleStatEffective<MaxHealth>,
&'static Children,
&'static PickedAction,
&'static mut Health,
Has<Attacker>,
),
@ -395,6 +461,8 @@ struct EntityParams<'w, 's> {
&'static EffectiveStat<Defence>,
&'static SimpleStatEffective<MaxHealth>,
&'static ArmourBodyParts,
&'static Children,
&'static PickedAction,
&'static mut Health,
),
(With<CurrentTarget>, Without<Current>),
@ -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<Has<Attacker>, (With<Current>, With<Player>)>,
picked_q: Query<Entity, With<PickedAction>>,
mut commands: Commands,
) {
if !attacker_turn.single().unwrap() {
for player in picked_q {
commands.entity(player).remove::<PickedAction>();
}
}
}
fn remove_picked_actions(picked_q: Query<Entity, With<PickedAction>>, mut commands: Commands) {
for player in picked_q {
commands.entity(player).remove::<PickedAction>();
}
}
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::<ChooseWeapon>();
stages.add_event::<HealthChange>();
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));
}

View file

@ -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<Clips>>,
weapon_q: Query<&ChildOf, With<Weapon>>,
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:?}"),
}
}

View file

@ -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);

View file

@ -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<Current>>,
target_q: Query<Entity, With<CurrentTarget>>,
mut temp_q: Query<(Entity, &DebuffingTemp, &mut Uses, &ChildOf), With<Current>>,
target_q: Query<(Entity, &Children, &PickedAction), With<CurrentTarget>>,
nullification_q: Query<&ActionNullification>,
mut effects: Effects,
mut commands: Commands,
mut rng: ResMut<Rng>,
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::<Usable>();
}
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::<Usable>();
}
}
fn use_buffing_temp(