use bevy_ecs::prelude::*; use proxisim_models::{ bundle::{ passive::{Education, EducationPartDamageBonus, FactionUpgrades, Merits}, player::{Current, PartDamageBonus, Weapons}, stat::{ AdditiveBonus, AmmoControl, ClipSize, Clips, CritRate, DamageBonus, Dexterity, SimpleStatBonus, SimpleStatEffective, WeaponAccuracy, }, weapon::{ Ammo, EquippedMods, Experience, Japanese, NeedsReload, Usable, Weapon, WeaponCategory, WeaponMod, WeaponSlot, }, }, hierarchy::{HierarchyBuilder, Parent}, }; use crate::{ effect::{Effects, TurnLimitedEffect}, log, log::Logger, Stages, }; use self::bonus::{ FirstTurnBonus, MultiTurnBonus, OpponentStatusEffect, SelfStatusEffect, TurnTriggeredBonus, }; pub mod bonus; pub mod temp; #[derive(Clone, Copy)] pub enum TurnTriggeredMod { Bipod, Tripod, SmallLight, PrecisionLight, TacticalIlluminator, } impl TurnTriggeredMod { pub fn trigger(self, effects: &mut Effects, current: Entity, target: Entity) { match self { Self::Bipod => { effects.spawn_and_insert( AdditiveBonus::::new("bipod", -0.3), current, TurnLimitedEffect::new(current, 0), ); } Self::Tripod => { effects.spawn_and_insert( AdditiveBonus::::new("tripod", -0.3), current, TurnLimitedEffect::new(current, 0), ); } Self::SmallLight => { effects.spawn_and_insert( SimpleStatBonus::::new("small light", -3.0 / 50.0), target, TurnLimitedEffect::new(target, 1), ); } Self::PrecisionLight => { effects.spawn_and_insert( SimpleStatBonus::::new("precision light", -4.0 / 50.0), target, TurnLimitedEffect::new(target, 1), ); } Self::TacticalIlluminator => { effects.spawn_and_insert( SimpleStatBonus::::new("tactical illuminator", -5.0 / 50.0), target, TurnLimitedEffect::new(target, 1), ); } } } } /// Effects that are triggered by selecting a weapon #[derive(Component)] pub enum TurnTriggeredEffect { Mod(TurnTriggeredMod), Bonus { value: f32, bonus: TurnTriggeredBonus, }, } impl TurnTriggeredEffect { pub fn trigger(&self, effects: &mut Effects, current: Entity, target: Entity) { match self { TurnTriggeredEffect::Mod(weapon_mod) => { weapon_mod.trigger(effects, current, target); } TurnTriggeredEffect::Bonus { value, bonus } => { bonus.trigger(*value, effects, current, target) } } } } #[derive(Clone, Copy)] pub enum FirstTurnMod { AdjustableTrigger, HairTrigger, } impl FirstTurnMod { fn spawn(self, effects: &mut Effects, weapon: Entity, owner: Entity) { match self { Self::AdjustableTrigger => effects.spawn_and_insert( SimpleStatBonus::::new("adjustable trigger", 5.0 / 50.0), weapon, TurnLimitedEffect::new(owner, 1), ), Self::HairTrigger => effects.spawn_and_insert( SimpleStatBonus::::new("hair trigger", 7.5 / 50.0), weapon, TurnLimitedEffect::new(owner, 1), ), }; } } #[derive(Component)] pub enum FirstTurnEffect { Mod(FirstTurnMod), Bonus { value: f32, bonus: FirstTurnBonus }, } impl FirstTurnEffect { fn spawn(&self, effects: &mut Effects, weapon: Entity, owner: Entity) { match self { Self::Mod(weapon_mod) => weapon_mod.spawn(effects, weapon, owner), Self::Bonus { value, bonus } => bonus.spawn(effects, weapon, owner, *value), }; } } #[derive(Component)] pub enum DamageProcEffect { MultiTurn { value: f32, bonus: MultiTurnBonus, }, OpponentEffect { value: f32, bonus: OpponentStatusEffect, }, SelfEffect { value: f32, bonus: SelfStatusEffect, }, } fn set_owner(weapons_q: Query<(Entity, &Weapons)>, mut commands: Commands) { for (player, weapons) in weapons_q.iter() { if let Some(primary) = weapons.primary { commands.entity(primary).set_parent(player); } if let Some(secondary) = weapons.secondary { commands.entity(secondary).set_parent(player); } if let Some(melee) = weapons.melee { commands.entity(melee).set_parent(player); } if let Some(temp) = weapons.temporary { commands.entity(temp).set_parent(player); } commands.entity(weapons.fists).set_parent(player); commands.entity(weapons.kick).set_parent(player); } } fn apply_passives( weapon_q: Query<( Entity, &EquippedMods, &Experience, &WeaponCategory, &WeaponSlot, Has, &Parent, )>, player_q: Query<(&Merits, &Education, &FactionUpgrades)>, mut effects: Effects, mut commands: Commands, ) { for (weapon, mods, exp, cat, slot, japanese, player) in weapon_q.iter() { let exp = (exp.0 / 10.0).round(); if exp > 0.0 { effects.spawn( SimpleStatBonus::::new("experience", exp * 0.2 / 50.0), weapon, ); effects.spawn( SimpleStatBonus::::new("experience", exp / 100.0), weapon, ); } for w_mod in &mods.0 { match w_mod { WeaponMod::ReflexSight => { effects.spawn( SimpleStatBonus::::new("reflex sight", 1.0 / 50.0), weapon, ); } WeaponMod::HolographicSight => { effects.spawn( SimpleStatBonus::::new("holographic sight", 1.25 / 50.0), weapon, ); } WeaponMod::AcogSight => { effects.spawn( SimpleStatBonus::::new("ACOG sight", 1.5 / 50.0), weapon, ); } WeaponMod::ThermalSight => { effects.spawn( SimpleStatBonus::::new("thermal sight", 1.75 / 50.0), weapon, ); } WeaponMod::Laser1mw => { effects.spawn(SimpleStatBonus::::new("1mw laser", 4), weapon); } WeaponMod::Laser5mw => { effects.spawn(SimpleStatBonus::::new("5mw laser", 6), weapon); } WeaponMod::Laser30mw => { effects.spawn(SimpleStatBonus::::new("30mw laser", 8), weapon); } WeaponMod::Laser100mw => { effects.spawn(SimpleStatBonus::::new("100mw laser", 10), weapon); } WeaponMod::SmallSuppressor => { effects.spawn( SimpleStatBonus::::new("small suppressor", -0.05), weapon, ); } WeaponMod::StandardSuppressor => { effects.spawn( SimpleStatBonus::::new("standard suppressor", -0.05), weapon, ); } WeaponMod::LargeSuppressor => { effects.spawn( SimpleStatBonus::::new("large suppressor", -0.05), weapon, ); } WeaponMod::ExtendedMags => { effects.spawn( SimpleStatBonus::::new("extended mags", 1.2), weapon, ); } WeaponMod::HighCapacityMags => { effects.spawn( SimpleStatBonus::::new("high capacity mags", 1.3), weapon, ); } WeaponMod::CustomGrip => { effects.spawn( SimpleStatBonus::::new("custom grip", 0.75), weapon, ); } WeaponMod::SkeetChoke => { effects.spawn( SimpleStatBonus::::new("skeet choke", 0.06), weapon, ); } WeaponMod::ImprovedChoke => { effects.spawn( SimpleStatBonus::::new("improved choke", 0.08), weapon, ); } WeaponMod::FullChoke => { effects.spawn( SimpleStatBonus::::new("full choke", 0.10), weapon, ); } WeaponMod::ExtraClip => { effects.spawn(SimpleStatBonus::::new("extra clip", 1), weapon); } WeaponMod::ExtraClip2 => { effects.spawn(SimpleStatBonus::::new("extra clip x2", 2), weapon); } WeaponMod::RecoilPad => { effects.spawn( SimpleStatBonus::::new("recoil pad", 0.24), weapon, ); } WeaponMod::StandardBrake => { effects.spawn( SimpleStatBonus::::new("standard brake", 1.00 / 50.0), weapon, ); } WeaponMod::HeavyDutyBrake => { effects.spawn( SimpleStatBonus::::new("heavy duty brake", 1.25 / 50.0), weapon, ); } WeaponMod::TacticalBrake => { effects.spawn( SimpleStatBonus::::new("tactical brake", 1.50 / 50.0), weapon, ); } WeaponMod::Bipod => { effects.spawn( SimpleStatBonus::::new("bipod", 1.75 / 50.0), weapon, ); commands .spawn(TurnTriggeredEffect::Mod(TurnTriggeredMod::Bipod)) .set_parent(weapon); } WeaponMod::Tripod => { effects.spawn( SimpleStatBonus::::new("bipod", 2.00 / 50.0), weapon, ); commands .spawn(TurnTriggeredEffect::Mod(TurnTriggeredMod::Tripod)) .set_parent(weapon); } WeaponMod::SmallLight => { commands .spawn(TurnTriggeredEffect::Mod(TurnTriggeredMod::SmallLight)) .set_parent(weapon); } WeaponMod::PrecisionLight => { commands .spawn(TurnTriggeredEffect::Mod(TurnTriggeredMod::PrecisionLight)) .set_parent(weapon); } WeaponMod::TacticalIlluminator => { commands .spawn(TurnTriggeredEffect::Mod( TurnTriggeredMod::TacticalIlluminator, )) .set_parent(weapon); } WeaponMod::AdjustableTrigger => { commands .spawn(FirstTurnEffect::Mod(FirstTurnMod::AdjustableTrigger)) .set_parent(weapon); } WeaponMod::HairTrigger => { commands .spawn(FirstTurnEffect::Mod(FirstTurnMod::HairTrigger)) .set_parent(weapon); } } } let (merits, education, faction) = player_q.get(player.get()).unwrap(); let (mastery, edu_acc) = match cat { WeaponCategory::HeavyArtillery => ( merits.heavy_artillery_mastery, education.cbt2860.then_some("CBT2860"), ), WeaponCategory::MachineGun => ( merits.machine_gun_mastery, education.cbt2830.then_some("CTB2830"), ), WeaponCategory::Rifle => (merits.rifle_mastery, education.cbt2850.then_some("CBT2850")), WeaponCategory::Smg => (merits.smg_mastery, education.cbt2830.then_some("CBT2830")), WeaponCategory::Shotgun => ( merits.shotgun_mastery, education.cbt2125.then_some("CBT2125"), ), WeaponCategory::Pistol => ( merits.pistol_mastery, education.cbt2840.then_some("CBT2840"), ), WeaponCategory::Clubbing => (merits.club_mastery, None), WeaponCategory::Piercing => (merits.piercing_mastery, None), WeaponCategory::Slashing => (merits.slashing_mastery, None), WeaponCategory::Mechanical => (merits.mechanical_mastery, None), WeaponCategory::Temporary => { if education.gen2119 { effects.spawn(SimpleStatBonus::::new("GEN2119", 0.05), weapon); } ( merits.temporary_mastery, education.gen2116.then_some("GEN2116"), ) } WeaponCategory::HandToHand => (0, None), }; if let Some(label) = edu_acc { effects.spawn( SimpleStatBonus::::new(label, 1.0 / 50.0), weapon, ); } // NOTE: Even though it says "weapons" in the description, this also applies to h2h if education.bio2350 { effects.spawn(SimpleStatBonus::::new("BIO2350", 0.01), weapon); } if education.def3770 && *slot == WeaponSlot::Fists { effects.spawn(SimpleStatBonus::::new("DEF3770", 1.0), weapon); } if education.his2170 && *slot == WeaponSlot::Melee { effects.spawn(SimpleStatBonus::::new("HIS2170", 0.02), weapon); } if matches!(*slot, WeaponSlot::Primary | WeaponSlot::Secondary) { if education.mth2310 { effects.spawn(SimpleStatBonus::::new("MTH2310", 0.05), weapon); } if education.mth3330 { effects.spawn(SimpleStatBonus::::new("MTH3330", 0.20), weapon); } } if education.his2160 && japanese { effects.spawn(SimpleStatBonus::::new("HIS2160", 0.10), weapon); } if education.bio2380 { commands .spawn(PartDamageBonus::Education( EducationPartDamageBonus::Bio2380, )) .set_parent(weapon); } if mastery > 0 { effects.spawn( SimpleStatBonus::::new("mastery", (mastery as f32) * 0.2 / 50.0), weapon, ); effects.spawn( SimpleStatBonus::::new("mastery", (mastery as f32) / 100.0), weapon, ); } if faction.dmg > 0 { effects.spawn( SimpleStatBonus::::new("faction", (faction.dmg as f32) * 0.01), weapon, ); } if faction.acc > 0 { effects.spawn( SimpleStatBonus::::new( "faction", (faction.dmg as f32) * 0.2 / 50.0, ), weapon, ); } } } fn unset_current(weapon_q: Query, With)>, mut commands: Commands) { for weapon in weapon_q.iter() { commands.entity(weapon).remove::(); } } fn reload_weapon( mut weapon_q: Query< ( Entity, &Parent, &mut Ammo, &mut SimpleStatEffective, &SimpleStatEffective, ), (With, With), >, mut commands: Commands, mut logger: Logger, ) { for (weapon, player, mut ammo, mut clips, clip_size) in weapon_q.iter_mut() { ammo.0 = clip_size.value; clips.value -= 1; log!(logger, "reload_weapon", { actor: player.get(), weapon }); commands.entity(weapon).remove::(); } } fn restore_usability(weapon_q: Query>, mut commands: Commands) { for weapon in weapon_q.iter() { commands.entity(weapon).insert(Usable); } } fn restore_ammo( mut ammo_q: Query<(Entity, &mut Ammo, &SimpleStatEffective)>, mut commands: Commands, ) { for (weapon, mut ammo, clip_size) in ammo_q.iter_mut() { ammo.0 = clip_size.value; commands.entity(weapon).remove::(); } } fn apply_first_turn_effects( effect_q: Query<(&Parent, &FirstTurnEffect)>, player_q: Query<&Parent>, mut effects: Effects, ) { for (weapon, effect) in effect_q.iter() { let player = player_q.get(weapon.get()).unwrap(); effect.spawn(&mut effects, weapon.get(), player.get()); } } pub(crate) fn configure(stages: &mut Stages) { stages.equip.add_systems(set_owner); // 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.pre_fight.add_systems(apply_passives); 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)); temp::configure(stages); bonus::configure(stages); }