initial commit
This commit is contained in:
commit
86f9333aec
21 changed files with 6449 additions and 0 deletions
278
src/weapon/bonus.rs
Normal file
278
src/weapon/bonus.rs
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
use bevy_ecs::prelude::*;
|
||||
|
||||
use crate::{
|
||||
effect::{Effects, TurnLimitedEffect},
|
||||
hierarchy::{HierarchyBuilder, Parent},
|
||||
player::stats::{
|
||||
AdditiveBonus, AmmoControl, Clips, CritRate, DamageBonus, SimpleStatBonus,
|
||||
SimpleStatEffective, Speed, Strength, WeaponAccuracy,
|
||||
},
|
||||
Stages,
|
||||
};
|
||||
|
||||
use super::{DamageProcEffect, FirstTurnEffect, TurnTriggeredEffect};
|
||||
|
||||
#[derive(Component, Debug, Clone, Copy)]
|
||||
#[cfg_attr(feature = "json", derive(serde::Deserialize))]
|
||||
#[cfg_attr(feature = "json", serde(rename_all = "snake_case"))]
|
||||
pub enum WeaponBonus {
|
||||
// Weapon passives
|
||||
Berserk,
|
||||
Conserve,
|
||||
Expose,
|
||||
Grace,
|
||||
Powerful,
|
||||
Specialist,
|
||||
|
||||
// Turn triggered passives
|
||||
Empower,
|
||||
Quicken,
|
||||
|
||||
// First turn effects
|
||||
Assassinate,
|
||||
|
||||
// Additive status effects triggered by damaging hits
|
||||
Cripple,
|
||||
Demoralise,
|
||||
Freeze,
|
||||
Motivate,
|
||||
Slow,
|
||||
Toxin,
|
||||
Weaken,
|
||||
Wither,
|
||||
|
||||
// DOT status effects
|
||||
Bleed,
|
||||
Burning,
|
||||
Lacerate,
|
||||
Poison,
|
||||
SevereBurning,
|
||||
|
||||
// Other status effects
|
||||
Eviscerate,
|
||||
Paralyse,
|
||||
Schock,
|
||||
Stun,
|
||||
|
||||
// Multi attack bonuses
|
||||
Blindfire,
|
||||
Fury,
|
||||
DoubleTap,
|
||||
Rage,
|
||||
|
||||
// Body part multipliers
|
||||
Achilles,
|
||||
Crusher,
|
||||
Cupid,
|
||||
Deadeye,
|
||||
Roshambo,
|
||||
Throttle,
|
||||
|
||||
// Attack nullification types
|
||||
Homerun,
|
||||
Parry,
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct BonusValue(f32);
|
||||
|
||||
#[derive(Bundle)]
|
||||
pub struct WeaponBonusBundle {
|
||||
pub bonus: WeaponBonus,
|
||||
pub value: BonusValue,
|
||||
}
|
||||
|
||||
impl WeaponBonusBundle {
|
||||
pub fn new(bonus: WeaponBonus, value: f32) -> Self {
|
||||
Self {
|
||||
bonus,
|
||||
value: BonusValue(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum TurnTriggeredBonus {
|
||||
Empower,
|
||||
Quicken,
|
||||
}
|
||||
|
||||
impl TurnTriggeredBonus {
|
||||
pub fn trigger(self, value: f32, effects: &mut Effects, current: Entity, _target: Entity) {
|
||||
match self {
|
||||
Self::Empower => {
|
||||
effects.spawn_and_insert(
|
||||
AdditiveBonus::<Strength>::new("empower", value / 100.0),
|
||||
current,
|
||||
TurnLimitedEffect::new(current, 0),
|
||||
);
|
||||
}
|
||||
Self::Quicken => {
|
||||
effects.spawn_and_insert(
|
||||
AdditiveBonus::<Speed>::new("empower", value / 100.0),
|
||||
current,
|
||||
TurnLimitedEffect::new(current, 0),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum FirstTurnBonus {
|
||||
Assassinate,
|
||||
}
|
||||
|
||||
impl FirstTurnBonus {
|
||||
pub(crate) fn spawn(self, effects: &mut Effects, weapon: Entity, owner: Entity, value: f32) {
|
||||
match self {
|
||||
Self::Assassinate => effects.spawn_and_insert(
|
||||
SimpleStatBonus::<DamageBonus>::new("assassinate", value / 100.0),
|
||||
weapon,
|
||||
TurnLimitedEffect::new(owner, 1),
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum MultiTurnBonus {
|
||||
Blindfire,
|
||||
Fury,
|
||||
DoubleTap,
|
||||
Rage,
|
||||
}
|
||||
|
||||
pub(crate) fn prepare_bonuses(
|
||||
bonus_q: Query<(
|
||||
&Parent,
|
||||
&WeaponBonus,
|
||||
&BonusValue,
|
||||
Option<&SimpleStatEffective<Clips>>,
|
||||
)>,
|
||||
mut effects: Effects,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
for (weapon, bonus, value, clips) in bonus_q.iter() {
|
||||
match bonus {
|
||||
WeaponBonus::Berserk => {
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<DamageBonus>::new("beserk", value.0 / 100.0),
|
||||
weapon.get(),
|
||||
);
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<WeaponAccuracy>::new("beserk", -value.0 / 2.0 / 50.0),
|
||||
weapon.get(),
|
||||
);
|
||||
}
|
||||
WeaponBonus::Conserve => {
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<AmmoControl>::new("conserve", value.0 / 100.0),
|
||||
weapon.get(),
|
||||
);
|
||||
}
|
||||
WeaponBonus::Expose => {
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<CritRate>::new("expose", (value.0 / 0.5) as u16),
|
||||
weapon.get(),
|
||||
);
|
||||
}
|
||||
WeaponBonus::Grace => {
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<DamageBonus>::new("grace", -value.0 / 2.0 / 100.0),
|
||||
weapon.get(),
|
||||
);
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<WeaponAccuracy>::new("grace", value.0 / 50.0),
|
||||
weapon.get(),
|
||||
);
|
||||
}
|
||||
WeaponBonus::Powerful => {
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<DamageBonus>::new("powerful", value.0 / 100.0),
|
||||
weapon.get(),
|
||||
);
|
||||
}
|
||||
WeaponBonus::Specialist => {
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<DamageBonus>::new("specialist", value.0 / 100.0),
|
||||
weapon.get(),
|
||||
);
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<Clips>::new(
|
||||
"specialist",
|
||||
-clips.map(|c| c.value as i16).unwrap_or_default(),
|
||||
),
|
||||
weapon.get(),
|
||||
);
|
||||
}
|
||||
|
||||
WeaponBonus::Empower => {
|
||||
commands
|
||||
.spawn(TurnTriggeredEffect::Bonus {
|
||||
value: value.0,
|
||||
bonus: TurnTriggeredBonus::Empower,
|
||||
})
|
||||
.set_parent(weapon.get());
|
||||
}
|
||||
WeaponBonus::Quicken => {
|
||||
commands
|
||||
.spawn(TurnTriggeredEffect::Bonus {
|
||||
value: value.0,
|
||||
bonus: TurnTriggeredBonus::Quicken,
|
||||
})
|
||||
.set_parent(weapon.get());
|
||||
}
|
||||
|
||||
WeaponBonus::Assassinate => {
|
||||
commands
|
||||
.spawn(FirstTurnEffect::Bonus {
|
||||
value: value.0,
|
||||
bonus: FirstTurnBonus::Assassinate,
|
||||
})
|
||||
.set_parent(weapon.get());
|
||||
}
|
||||
|
||||
WeaponBonus::Blindfire => {
|
||||
commands
|
||||
.spawn(DamageProcEffect::MultiTurn {
|
||||
value: value.0,
|
||||
bonus: MultiTurnBonus::Blindfire,
|
||||
})
|
||||
.set_parent(weapon.get());
|
||||
}
|
||||
WeaponBonus::Fury => {
|
||||
commands
|
||||
.spawn(DamageProcEffect::MultiTurn {
|
||||
value: value.0,
|
||||
bonus: MultiTurnBonus::Fury,
|
||||
})
|
||||
.set_parent(weapon.get());
|
||||
}
|
||||
WeaponBonus::Rage => {
|
||||
commands
|
||||
.spawn(DamageProcEffect::MultiTurn {
|
||||
value: value.0,
|
||||
bonus: MultiTurnBonus::Rage,
|
||||
})
|
||||
.set_parent(weapon.get());
|
||||
}
|
||||
WeaponBonus::DoubleTap => {
|
||||
commands
|
||||
.spawn(DamageProcEffect::MultiTurn {
|
||||
value: value.0,
|
||||
bonus: MultiTurnBonus::DoubleTap,
|
||||
})
|
||||
.set_parent(weapon.get());
|
||||
}
|
||||
|
||||
val => unimplemented!("{val:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn configure(stages: &mut Stages) {
|
||||
stages
|
||||
.pre_fight
|
||||
.add_systems(prepare_bonuses.after(super::apply_passives));
|
||||
}
|
||||
955
src/weapon/mod.rs
Normal file
955
src/weapon/mod.rs
Normal file
|
|
@ -0,0 +1,955 @@
|
|||
use bevy_ecs::prelude::*;
|
||||
use macros::LogMessage;
|
||||
use rand::Rng as _;
|
||||
|
||||
use crate::{
|
||||
effect::{Effects, TurnLimitedEffect},
|
||||
hierarchy::{HierarchyBuilder, Parent},
|
||||
log::Logger,
|
||||
metrics::Metrics,
|
||||
passives::{Education, FactionUpgrades, Merits},
|
||||
player::{
|
||||
stats::{
|
||||
AdditiveBonus, AmmoControl, ClipSize, Clips, CritRate, DamageBonus, Dexterity,
|
||||
EffectiveStat, SimpleStatBonus, SimpleStatBundle, SimpleStatEffective, Speed,
|
||||
WeaponAccuracy,
|
||||
},
|
||||
BodyPart, Current, CurrentTarget, InitiateHit, Player, Weapons,
|
||||
},
|
||||
Id, Name, Rng, Stages,
|
||||
};
|
||||
|
||||
use self::{
|
||||
bonus::{FirstTurnBonus, MultiTurnBonus, TurnTriggeredBonus},
|
||||
temp::{NonTargeted, Uses},
|
||||
};
|
||||
|
||||
pub mod bonus;
|
||||
pub mod temp;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Usable;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Weapon;
|
||||
|
||||
#[derive(Component, Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "json", derive(serde::Deserialize))]
|
||||
#[cfg_attr(feature = "json", serde(rename_all = "snake_case"))]
|
||||
pub enum WeaponSlot {
|
||||
Primary,
|
||||
Secondary,
|
||||
Melee,
|
||||
Temporary,
|
||||
Fists,
|
||||
Kick,
|
||||
}
|
||||
|
||||
#[derive(Component, Debug, Clone, Copy)]
|
||||
pub enum WeaponVerb {
|
||||
Hit,
|
||||
Kicked,
|
||||
Fired,
|
||||
Threw,
|
||||
Exploded,
|
||||
}
|
||||
|
||||
#[derive(Component, Clone, Copy)]
|
||||
#[cfg_attr(feature = "json", derive(serde::Deserialize))]
|
||||
#[cfg_attr(feature = "json", serde(rename_all = "snake_case"))]
|
||||
pub enum WeaponCategory {
|
||||
HeavyArtillery,
|
||||
MachineGun,
|
||||
Rifle,
|
||||
Smg,
|
||||
Shotgun,
|
||||
Pistol,
|
||||
Club,
|
||||
Piercing,
|
||||
Slashing,
|
||||
Mechanical,
|
||||
Temporary,
|
||||
HandToHand,
|
||||
}
|
||||
|
||||
#[derive(Component, Debug)]
|
||||
pub struct DamageStat(pub f32);
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Japanese;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Ammo(pub u16);
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct RateOfFire(pub [u16; 2]);
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct NeedsReload;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[cfg_attr(feature = "json", derive(serde::Deserialize))]
|
||||
#[cfg_attr(feature = "json", serde(rename_all = "snake_case"))]
|
||||
pub enum WeaponMod {
|
||||
ReflexSight,
|
||||
HolographicSight,
|
||||
AcogSight,
|
||||
ThermalSight,
|
||||
Laser1mw,
|
||||
Laser5mw,
|
||||
Laser30mw,
|
||||
Laser100mw,
|
||||
SmallSuppressor,
|
||||
StandardSuppressor,
|
||||
LargeSuppressor,
|
||||
ExtendedMags,
|
||||
HighCapacityMags,
|
||||
ExtraClip,
|
||||
ExtraClip2,
|
||||
AdjustableTrigger,
|
||||
HairTrigger,
|
||||
Bipod,
|
||||
Tripod,
|
||||
CustomGrip,
|
||||
SkeetChoke,
|
||||
ImprovedChoke,
|
||||
FullChoke,
|
||||
RecoilPad,
|
||||
StandardBrake,
|
||||
HeavyDutyBreak,
|
||||
TacticalBrake,
|
||||
SmallLight,
|
||||
PrecisionLight,
|
||||
TacticalIlluminator,
|
||||
}
|
||||
|
||||
#[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::<Dexterity>::new("bipod", -0.3),
|
||||
current,
|
||||
TurnLimitedEffect::new(current, 0),
|
||||
);
|
||||
}
|
||||
Self::Tripod => {
|
||||
effects.spawn_and_insert(
|
||||
AdditiveBonus::<Dexterity>::new("tripod", -0.3),
|
||||
current,
|
||||
TurnLimitedEffect::new(current, 0),
|
||||
);
|
||||
}
|
||||
Self::SmallLight => {
|
||||
effects.spawn_and_insert(
|
||||
SimpleStatBonus::<WeaponAccuracy>::new("small light", -3.0 / 50.0),
|
||||
target,
|
||||
TurnLimitedEffect::new(target, 1),
|
||||
);
|
||||
}
|
||||
Self::PrecisionLight => {
|
||||
effects.spawn_and_insert(
|
||||
SimpleStatBonus::<WeaponAccuracy>::new("precision light", -4.0 / 50.0),
|
||||
target,
|
||||
TurnLimitedEffect::new(target, 1),
|
||||
);
|
||||
}
|
||||
Self::TacticalIlluminator => {
|
||||
effects.spawn_and_insert(
|
||||
SimpleStatBonus::<WeaponAccuracy>::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::<WeaponAccuracy>::new("adjustable trigger", 5.0 / 50.0),
|
||||
weapon,
|
||||
TurnLimitedEffect::new(owner, 1),
|
||||
),
|
||||
Self::HairTrigger => effects.spawn_and_insert(
|
||||
SimpleStatBonus::<WeaponAccuracy>::new("hair trigger", 7.5 / 50.0),
|
||||
weapon,
|
||||
TurnLimitedEffect::new(owner, 1),
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub enum FirstTurnEffect {
|
||||
Mod(FirstTurnMod),
|
||||
Bonus { value: f32, bonus: FirstTurnBonus },
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub enum DamageProcEffect {
|
||||
MultiTurn { value: f32, bonus: MultiTurnBonus },
|
||||
}
|
||||
|
||||
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 struct EquippedMods(pub Vec<WeaponMod>);
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Experience(pub f32);
|
||||
|
||||
#[derive(LogMessage)]
|
||||
pub struct ReloadWeapon {
|
||||
#[log(player)]
|
||||
pub actor: Entity,
|
||||
#[log(weapon)]
|
||||
pub weapon: Entity,
|
||||
}
|
||||
|
||||
#[derive(LogMessage)]
|
||||
pub struct MissTarget {
|
||||
#[log(player)]
|
||||
pub actor: Entity,
|
||||
#[log(player)]
|
||||
pub recipient: Entity,
|
||||
#[log(weapon)]
|
||||
pub weapon: Entity,
|
||||
pub rounds: Option<u16>,
|
||||
}
|
||||
|
||||
#[derive(Bundle)]
|
||||
pub struct WeaponBundle {
|
||||
pub usable: Usable,
|
||||
pub weapon: Weapon,
|
||||
pub name: Name,
|
||||
pub id: Id,
|
||||
pub verb: WeaponVerb,
|
||||
pub slot: WeaponSlot,
|
||||
}
|
||||
|
||||
#[derive(Bundle)]
|
||||
pub struct DamagingWeaponBundle {
|
||||
pub crit_rate: SimpleStatBundle<CritRate>,
|
||||
pub dmg: DamageStat,
|
||||
pub acc: SimpleStatBundle<WeaponAccuracy>,
|
||||
pub dmg_bonus: SimpleStatBundle<DamageBonus>,
|
||||
pub equipped_mods: EquippedMods,
|
||||
pub experience: Experience,
|
||||
pub category: WeaponCategory,
|
||||
}
|
||||
|
||||
#[derive(Bundle)]
|
||||
pub struct AmmoWeaponBundle {
|
||||
pub ammo: Ammo,
|
||||
pub clips: SimpleStatBundle<Clips>,
|
||||
pub clip_size: SimpleStatBundle<ClipSize>,
|
||||
pub rate_of_fire: RateOfFire,
|
||||
pub ammo_control: SimpleStatBundle<AmmoControl>,
|
||||
}
|
||||
|
||||
impl WeaponBundle {
|
||||
pub fn new(name: String, id: usize, verb: WeaponVerb, slot: WeaponSlot) -> Self {
|
||||
Self {
|
||||
usable: Usable,
|
||||
weapon: Weapon,
|
||||
name: Name(name),
|
||||
id: Id(id),
|
||||
verb,
|
||||
slot,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fists(id: usize) -> Self {
|
||||
Self::new("Fists".to_owned(), id, WeaponVerb::Hit, WeaponSlot::Fists)
|
||||
}
|
||||
|
||||
pub fn kick(id: usize) -> Self {
|
||||
Self::new("Kick".to_owned(), id, WeaponVerb::Kicked, WeaponSlot::Kick)
|
||||
}
|
||||
}
|
||||
|
||||
impl DamagingWeaponBundle {
|
||||
pub fn new(
|
||||
dmg: f32,
|
||||
acc: f32,
|
||||
mods: Vec<WeaponMod>,
|
||||
exp: f32,
|
||||
category: WeaponCategory,
|
||||
) -> Self {
|
||||
Self {
|
||||
crit_rate: SimpleStatBundle::new(0),
|
||||
dmg: DamageStat(dmg / 10.0),
|
||||
acc: SimpleStatBundle::new((acc - 50.0) / 50.0),
|
||||
dmg_bonus: SimpleStatBundle::new(1.0),
|
||||
equipped_mods: EquippedMods(mods),
|
||||
experience: Experience(exp),
|
||||
category,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fists() -> Self {
|
||||
// NOTE: The accuracy value is taken from the attack page. The damage value here differs
|
||||
// from the one in Proxima's simulator, but in some quick tests 10.0 proofed to be a better fit
|
||||
// This might have changed in the weapon damage update
|
||||
Self::new(10.0, 50.0, Vec::default(), 0.0, WeaponCategory::HandToHand)
|
||||
}
|
||||
|
||||
pub fn kick() -> Self {
|
||||
// NOTE: The accuracy value is taken from the attack page. The damage value here differs
|
||||
// from the one in Proxima's simulator, but in some quick tests 30.0 proofed to be a better fit
|
||||
// This might have changed in the weapon damage update
|
||||
Self::new(30.0, 40.0, Vec::default(), 0.0, WeaponCategory::HandToHand)
|
||||
}
|
||||
}
|
||||
|
||||
impl AmmoWeaponBundle {
|
||||
pub fn new(clips: u16, clip_size: u16, rof: [u16; 2]) -> Self {
|
||||
Self {
|
||||
ammo: Ammo(clip_size),
|
||||
clips: SimpleStatBundle::new(clips - 1),
|
||||
clip_size: SimpleStatBundle::new(clip_size),
|
||||
rate_of_fire: RateOfFire(rof),
|
||||
ammo_control: SimpleStatBundle::new(0.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.unwrap()).set_parent(player);
|
||||
if let Some(kick) = weapons.kick {
|
||||
commands.entity(kick).set_parent(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_passives(
|
||||
weapon_q: Query<(
|
||||
Entity,
|
||||
&EquippedMods,
|
||||
&Experience,
|
||||
&WeaponCategory,
|
||||
&WeaponSlot,
|
||||
Has<Japanese>,
|
||||
&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::<WeaponAccuracy>::new("experience", exp * 0.2 / 50.0),
|
||||
weapon,
|
||||
);
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<DamageBonus>::new("experience", exp / 100.0),
|
||||
weapon,
|
||||
);
|
||||
}
|
||||
|
||||
for w_mod in &mods.0 {
|
||||
match w_mod {
|
||||
WeaponMod::ReflexSight => {
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<WeaponAccuracy>::new("reflex sight", 1.0 / 50.0),
|
||||
weapon,
|
||||
);
|
||||
}
|
||||
WeaponMod::HolographicSight => {
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<WeaponAccuracy>::new("holographic sight", 1.25 / 50.0),
|
||||
weapon,
|
||||
);
|
||||
}
|
||||
WeaponMod::AcogSight => {
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<WeaponAccuracy>::new("ACOG sight", 1.5 / 50.0),
|
||||
weapon,
|
||||
);
|
||||
}
|
||||
WeaponMod::ThermalSight => {
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<WeaponAccuracy>::new("thermal sight", 1.75 / 50.0),
|
||||
weapon,
|
||||
);
|
||||
}
|
||||
WeaponMod::Laser1mw => {
|
||||
effects.spawn(SimpleStatBonus::<CritRate>::new("1mw laser", 4), weapon);
|
||||
}
|
||||
WeaponMod::Laser5mw => {
|
||||
effects.spawn(SimpleStatBonus::<CritRate>::new("5mw laser", 6), weapon);
|
||||
}
|
||||
WeaponMod::Laser30mw => {
|
||||
effects.spawn(SimpleStatBonus::<CritRate>::new("30mw laser", 8), weapon);
|
||||
}
|
||||
WeaponMod::Laser100mw => {
|
||||
effects.spawn(SimpleStatBonus::<CritRate>::new("100mw laser", 10), weapon);
|
||||
}
|
||||
WeaponMod::SmallSuppressor => {
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<DamageBonus>::new("small suppressor", -0.05),
|
||||
weapon,
|
||||
);
|
||||
}
|
||||
WeaponMod::StandardSuppressor => {
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<DamageBonus>::new("standard suppressor", -0.05),
|
||||
weapon,
|
||||
);
|
||||
}
|
||||
WeaponMod::LargeSuppressor => {
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<DamageBonus>::new("large suppressor", -0.05),
|
||||
weapon,
|
||||
);
|
||||
}
|
||||
WeaponMod::ExtendedMags => {
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<ClipSize>::new("extended mags", 1.2),
|
||||
weapon,
|
||||
);
|
||||
}
|
||||
WeaponMod::HighCapacityMags => {
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<ClipSize>::new("high capacity mags", 1.3),
|
||||
weapon,
|
||||
);
|
||||
}
|
||||
WeaponMod::CustomGrip => {
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<WeaponAccuracy>::new("custom grip", 0.75),
|
||||
weapon,
|
||||
);
|
||||
}
|
||||
WeaponMod::SkeetChoke => {
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<DamageBonus>::new("skeet choke", 0.06),
|
||||
weapon,
|
||||
);
|
||||
}
|
||||
WeaponMod::ImprovedChoke => {
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<DamageBonus>::new("improved choke", 0.08),
|
||||
weapon,
|
||||
);
|
||||
}
|
||||
WeaponMod::FullChoke => {
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<DamageBonus>::new("full choke", 0.10),
|
||||
weapon,
|
||||
);
|
||||
}
|
||||
WeaponMod::ExtraClip => {
|
||||
effects.spawn(SimpleStatBonus::<Clips>::new("extra clip", 1), weapon);
|
||||
}
|
||||
WeaponMod::ExtraClip2 => {
|
||||
effects.spawn(SimpleStatBonus::<Clips>::new("extra clip x2", 2), weapon);
|
||||
}
|
||||
WeaponMod::RecoilPad => {
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<AmmoControl>::new("recoil pad", 0.24),
|
||||
weapon,
|
||||
);
|
||||
}
|
||||
WeaponMod::StandardBrake => {
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<WeaponAccuracy>::new("standard brake", 1.00 / 50.0),
|
||||
weapon,
|
||||
);
|
||||
}
|
||||
WeaponMod::HeavyDutyBreak => {
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<WeaponAccuracy>::new("heavy duty brake", 1.25 / 50.0),
|
||||
weapon,
|
||||
);
|
||||
}
|
||||
WeaponMod::TacticalBrake => {
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<WeaponAccuracy>::new("tactical brake", 1.50 / 50.0),
|
||||
weapon,
|
||||
);
|
||||
}
|
||||
WeaponMod::Bipod => {
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<WeaponAccuracy>::new("bipod", 1.75 / 50.0),
|
||||
weapon,
|
||||
);
|
||||
commands
|
||||
.spawn(TurnTriggeredEffect::Mod(TurnTriggeredMod::Bipod))
|
||||
.set_parent(weapon);
|
||||
}
|
||||
WeaponMod::Tripod => {
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<WeaponAccuracy>::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::Club => (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::<DamageBonus>::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::<WeaponAccuracy>::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::<DamageBonus>::new("BIO2350", 0.01), weapon);
|
||||
}
|
||||
|
||||
if education.def3770 && *slot == WeaponSlot::Fists {
|
||||
effects.spawn(SimpleStatBonus::<DamageBonus>::new("DEF3770", 1.0), weapon);
|
||||
}
|
||||
|
||||
if education.his2170 && *slot == WeaponSlot::Melee {
|
||||
effects.spawn(SimpleStatBonus::<DamageBonus>::new("HIS2170", 0.02), weapon);
|
||||
}
|
||||
|
||||
if matches!(*slot, WeaponSlot::Primary | WeaponSlot::Secondary) {
|
||||
if education.mth2310 {
|
||||
effects.spawn(SimpleStatBonus::<AmmoControl>::new("MTH2310", 0.05), weapon);
|
||||
}
|
||||
if education.mth3330 {
|
||||
effects.spawn(SimpleStatBonus::<AmmoControl>::new("MTH3330", 0.20), weapon);
|
||||
}
|
||||
}
|
||||
|
||||
if education.his2160 && japanese {
|
||||
effects.spawn(SimpleStatBonus::<DamageBonus>::new("HIS2160", 0.10), weapon);
|
||||
}
|
||||
|
||||
if mastery > 0 {
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<WeaponAccuracy>::new("mastery", (mastery as f32) * 0.2 / 50.0),
|
||||
weapon,
|
||||
);
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<DamageBonus>::new("mastery", (mastery as f32) / 100.0),
|
||||
weapon,
|
||||
);
|
||||
}
|
||||
|
||||
if faction.dmg > 0 {
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<DamageBonus>::new("faction", (faction.dmg as f32) * 0.01),
|
||||
weapon,
|
||||
);
|
||||
}
|
||||
if faction.acc > 0 {
|
||||
effects.spawn(
|
||||
SimpleStatBonus::<WeaponAccuracy>::new(
|
||||
"faction",
|
||||
(faction.dmg as f32) * 0.2 / 50.0,
|
||||
),
|
||||
weapon,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn unset_current(weapon_q: Query<Entity, (With<Current>, With<Weapon>)>, mut commands: Commands) {
|
||||
for weapon in weapon_q.iter() {
|
||||
commands.entity(weapon).remove::<Current>();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Move all mitigation aspects of this into the player system
|
||||
pub fn use_damaging_weapon(
|
||||
mut rng: ResMut<Rng>,
|
||||
weapon_q: Query<
|
||||
(
|
||||
Entity,
|
||||
&DamageStat,
|
||||
&SimpleStatEffective<WeaponAccuracy>,
|
||||
&SimpleStatEffective<DamageBonus>,
|
||||
&SimpleStatEffective<CritRate>,
|
||||
Has<NonTargeted>,
|
||||
),
|
||||
(With<Weapon>, With<Current>, Without<NeedsReload>),
|
||||
>,
|
||||
player_q: Query<
|
||||
(
|
||||
Entity,
|
||||
&EffectiveStat<Speed>,
|
||||
&SimpleStatEffective<CritRate>,
|
||||
&SimpleStatEffective<WeaponAccuracy>,
|
||||
&SimpleStatEffective<DamageBonus>,
|
||||
),
|
||||
(With<Player>, With<Current>),
|
||||
>,
|
||||
target_q: Query<(Entity, &EffectiveStat<Dexterity>), With<CurrentTarget>>,
|
||||
(mut ammo_q, mut temp_q): (
|
||||
Query<(
|
||||
&mut Ammo,
|
||||
&SimpleStatEffective<Clips>,
|
||||
&RateOfFire,
|
||||
&SimpleStatEffective<AmmoControl>,
|
||||
)>,
|
||||
Query<&mut Uses>,
|
||||
),
|
||||
mut hit_events: EventWriter<InitiateHit>,
|
||||
(mut logger, mut commands, metrics): (Logger, Commands, Res<Metrics>),
|
||||
) {
|
||||
let Ok((weapon, dmg, acc, dmg_bonus, crit, non_targeted)) = weapon_q.get_single() else {
|
||||
return;
|
||||
};
|
||||
let (player, player_spd, player_crit, acc_bonus, p_dmg_bonus) = player_q.single();
|
||||
let (target, target_dex) = target_q.single();
|
||||
|
||||
if let Ok(mut uses) = temp_q.get_mut(weapon) {
|
||||
uses.0 -= 1;
|
||||
if uses.0 == 0 {
|
||||
commands.entity(weapon).remove::<Usable>();
|
||||
}
|
||||
}
|
||||
|
||||
let spd_dex_ratio = (player_spd.value / target_dex.value).clamp(1.0 / 64.0, 64.0);
|
||||
let base_hit_chance = if spd_dex_ratio < 1.0 {
|
||||
0.5 * (8.0 * spd_dex_ratio.sqrt() - 1.0) / 7.0
|
||||
} else {
|
||||
1.0 - 0.5 * (8.0 / spd_dex_ratio.sqrt() - 1.0) / 7.0
|
||||
};
|
||||
|
||||
let mut acc_eff = acc + acc_bonus;
|
||||
|
||||
let mut ammo = ammo_q
|
||||
.get_mut(weapon)
|
||||
.ok()
|
||||
.map(|(ammo, clips, rof, ammo_ctrl)| {
|
||||
let ammo_ctrl = 1.0 - (ammo_ctrl).value;
|
||||
let rof_eff = ((rof.0[0] as f32) * ammo_ctrl)..((rof.0[1] as f32) * ammo_ctrl);
|
||||
(ammo, clips, rof_eff)
|
||||
});
|
||||
|
||||
enum MultiAttack {
|
||||
Blindfire,
|
||||
Rage(usize),
|
||||
Fury(usize),
|
||||
DoubleTap { fired_first: bool },
|
||||
}
|
||||
|
||||
let mut multi_attack_proc = None;
|
||||
let crit = player_crit + crit;
|
||||
|
||||
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());
|
||||
ammo.0 -= rounds;
|
||||
if ammo.0 == 0 {
|
||||
if clips.value == 0 {
|
||||
commands.entity(weapon).remove::<Usable>();
|
||||
} else {
|
||||
commands.entity(weapon).insert(NeedsReload);
|
||||
}
|
||||
}
|
||||
rounds
|
||||
});
|
||||
|
||||
let hit_chance = if base_hit_chance < 0.5 {
|
||||
base_hit_chance + acc_eff.value * base_hit_chance
|
||||
} else {
|
||||
base_hit_chance + acc_eff.value * (1.0 - base_hit_chance)
|
||||
};
|
||||
|
||||
if hit_chance <= 1.0 && !rng.gen_bool(hit_chance as f64) {
|
||||
logger.log(|| MissTarget {
|
||||
weapon,
|
||||
actor: player,
|
||||
recipient: target,
|
||||
rounds,
|
||||
});
|
||||
metrics.increment_counter(player, "miss", 1);
|
||||
metrics.increment_counter(weapon, "miss", 1);
|
||||
|
||||
if multi_attack_proc.is_none() {
|
||||
return;
|
||||
};
|
||||
} else {
|
||||
let body_part = if !non_targeted {
|
||||
rng.sample(crit)
|
||||
} else {
|
||||
BodyPart::Stomach
|
||||
};
|
||||
|
||||
hit_events.send(InitiateHit {
|
||||
body_part,
|
||||
weapon,
|
||||
rounds,
|
||||
dmg: dmg.0,
|
||||
dmg_bonus_weapon: dmg_bonus.value,
|
||||
dmg_bonus_player: p_dmg_bonus.value,
|
||||
hit_chance,
|
||||
crit_rate: crit.value,
|
||||
});
|
||||
}
|
||||
|
||||
match multi_attack_proc {
|
||||
Some(MultiAttack::Blindfire) => acc_eff.value -= 5.0 / 50.0,
|
||||
Some(MultiAttack::Rage(turns @ 1..)) => {
|
||||
multi_attack_proc = Some(MultiAttack::Rage(turns - 1))
|
||||
}
|
||||
Some(MultiAttack::Fury(turns @ 1..)) => {
|
||||
multi_attack_proc = Some(MultiAttack::Fury(turns - 1))
|
||||
}
|
||||
Some(MultiAttack::DoubleTap { fired_first: false }) => {
|
||||
multi_attack_proc = Some(MultiAttack::DoubleTap { fired_first: true })
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
/*
|
||||
let hit_chance = if base_hit_chance < 0.5 {
|
||||
base_hit_chance + acc_eff.value * base_hit_chance
|
||||
} else {
|
||||
base_hit_chance + acc_eff.value * (1.0 - base_hit_chance)
|
||||
};
|
||||
|
||||
if hit_chance <= 1.0 && !rng.gen_bool(hit_chance as f64) {
|
||||
logger.log(|| MissTarget {
|
||||
weapon,
|
||||
actor: player,
|
||||
recipient: target,
|
||||
rounds: rounds.map(|(rounds, _)| rounds),
|
||||
});
|
||||
metrics.increment_counter(player, "miss", 1);
|
||||
metrics.increment_counter(weapon, "miss", 1);
|
||||
return;
|
||||
}
|
||||
|
||||
let crit = player_crit + crit;
|
||||
|
||||
let body_part = if !non_targeted {
|
||||
rng.sample(crit)
|
||||
} else {
|
||||
BodyPart::Stomach
|
||||
};
|
||||
|
||||
let def_str_ratio = (target_def.value / player_str.value).clamp(1.0 / 32.0, 14.0);
|
||||
let mitigation = if def_str_ratio < 1.0 {
|
||||
0.5 * def_str_ratio.log(32.0) + 0.5
|
||||
} else {
|
||||
0.5 * def_str_ratio.log(14.0) + 0.5
|
||||
};
|
||||
|
||||
let dmg_intrinsic = 7.0 * (player_str.value / 10.0).log10().powi(2)
|
||||
+ 27.0 * (player_str.value / 10.0).log10()
|
||||
+ 30.0;
|
||||
|
||||
init_hit.send(InitiateHit {
|
||||
body_part,
|
||||
weapon,
|
||||
rounds: rounds.map(|(rounds, _)| rounds),
|
||||
crit_rate: crit.value,
|
||||
dmg: dmg.0,
|
||||
dmg_bonus_weapon: (dmg_bonus + p_dmg_bonus).value,
|
||||
dmg_intrinsic,
|
||||
def_mitigation: mitigation,
|
||||
hit_chance,
|
||||
}); */
|
||||
}
|
||||
|
||||
fn reload_weapon(
|
||||
mut weapon_q: Query<
|
||||
(
|
||||
Entity,
|
||||
&Parent,
|
||||
&mut Ammo,
|
||||
&mut SimpleStatEffective<Clips>,
|
||||
&SimpleStatEffective<ClipSize>,
|
||||
),
|
||||
(With<NeedsReload>, With<Current>),
|
||||
>,
|
||||
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;
|
||||
|
||||
logger.log(|| ReloadWeapon {
|
||||
actor: player.get(),
|
||||
weapon,
|
||||
});
|
||||
|
||||
commands.entity(weapon).remove::<NeedsReload>();
|
||||
}
|
||||
}
|
||||
|
||||
fn restore_usability(weapon_q: Query<Entity, With<Weapon>>, 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<ClipSize>)>,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
for (weapon, mut ammo, clip_size) in ammo_q.iter_mut() {
|
||||
ammo.0 = clip_size.value;
|
||||
commands.entity(weapon).remove::<NeedsReload>();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
stages.pre_fight.add_systems((
|
||||
apply_passives,
|
||||
apply_first_turn_effects
|
||||
.after(apply_passives)
|
||||
.after(bonus::prepare_bonuses),
|
||||
));
|
||||
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);
|
||||
}
|
||||
249
src/weapon/temp.rs
Normal file
249
src/weapon/temp.rs
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
use bevy_ecs::prelude::*;
|
||||
use strum::Display;
|
||||
|
||||
use crate::{
|
||||
effect::Effects,
|
||||
player::{
|
||||
status_effect::{
|
||||
ConcussionGrenade, FlashGrenade, PepperSpray, Sand, SmokeGrenade, TearGas,
|
||||
TempDebuffEffect,
|
||||
},
|
||||
Current, CurrentTarget,
|
||||
},
|
||||
Stages,
|
||||
};
|
||||
|
||||
use super::{DamagingWeaponBundle, Usable, WeaponBundle, WeaponCategory, WeaponSlot, WeaponVerb};
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct NonTargeted;
|
||||
|
||||
#[derive(Component, Default)]
|
||||
pub struct Temporary;
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct Uses(pub u16);
|
||||
|
||||
impl Default for Uses {
|
||||
fn default() -> Self {
|
||||
Self(1)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
pub struct AssociatedWeapon(pub Entity);
|
||||
|
||||
#[derive(Component, Debug, Clone, Copy, Display)]
|
||||
pub enum DebuffingTemp {
|
||||
TearGas,
|
||||
SmokeGrenade,
|
||||
PepperSpray,
|
||||
ConcussionGrenade,
|
||||
FlashGrenade,
|
||||
Sand,
|
||||
}
|
||||
|
||||
#[derive(Bundle, Default)]
|
||||
pub struct TemporaryBundle {
|
||||
pub temporary: Temporary,
|
||||
pub uses: Uses,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[cfg_attr(feature = "json", derive(serde::Deserialize))]
|
||||
pub enum Temp {
|
||||
Heg,
|
||||
NailBomb,
|
||||
Grenade,
|
||||
Fireworks,
|
||||
ClaymoreMine,
|
||||
TearGas,
|
||||
SmokeGrenade,
|
||||
PepperSpray,
|
||||
ConcussionGrenade,
|
||||
FlashGrenade,
|
||||
Sand,
|
||||
}
|
||||
|
||||
impl Temp {
|
||||
pub fn spawn(self, world: &mut World, id: usize) -> EntityWorldMut<'_> {
|
||||
match self {
|
||||
Self::Heg => world.spawn((
|
||||
WeaponBundle::new(
|
||||
"HEG".to_owned(),
|
||||
id,
|
||||
WeaponVerb::Exploded,
|
||||
WeaponSlot::Temporary,
|
||||
),
|
||||
DamagingWeaponBundle::new(90.00, 116.00, vec![], 0.0, WeaponCategory::Temporary),
|
||||
TemporaryBundle::default(),
|
||||
NonTargeted,
|
||||
)),
|
||||
Self::NailBomb => world.spawn((
|
||||
WeaponBundle::new(
|
||||
"Nail Bomb".to_owned(),
|
||||
id,
|
||||
WeaponVerb::Exploded,
|
||||
WeaponSlot::Temporary,
|
||||
),
|
||||
DamagingWeaponBundle::new(99.00, 106.00, vec![], 0.0, WeaponCategory::Temporary),
|
||||
TemporaryBundle::default(),
|
||||
NonTargeted,
|
||||
)),
|
||||
Self::Grenade => world.spawn((
|
||||
WeaponBundle::new(
|
||||
"Grenade".to_owned(),
|
||||
id,
|
||||
WeaponVerb::Exploded,
|
||||
WeaponSlot::Temporary,
|
||||
),
|
||||
DamagingWeaponBundle::new(86.00, 106.00, vec![], 0.0, WeaponCategory::Temporary),
|
||||
TemporaryBundle::default(),
|
||||
NonTargeted,
|
||||
)),
|
||||
Self::Fireworks => world.spawn((
|
||||
WeaponBundle::new(
|
||||
"Fireworks".to_owned(),
|
||||
id,
|
||||
WeaponVerb::Exploded,
|
||||
WeaponSlot::Temporary,
|
||||
),
|
||||
DamagingWeaponBundle::new(45.00, 34.00, vec![], 0.0, WeaponCategory::Temporary),
|
||||
TemporaryBundle::default(),
|
||||
NonTargeted,
|
||||
)),
|
||||
Self::ClaymoreMine => world.spawn((
|
||||
WeaponBundle::new(
|
||||
"Claymore Mine".to_owned(),
|
||||
id,
|
||||
WeaponVerb::Exploded,
|
||||
WeaponSlot::Temporary,
|
||||
),
|
||||
DamagingWeaponBundle::new(83.00, 27.00, vec![], 0.0, WeaponCategory::Temporary),
|
||||
TemporaryBundle::default(),
|
||||
NonTargeted,
|
||||
)),
|
||||
Self::TearGas => world.spawn((
|
||||
WeaponBundle::new(
|
||||
"Tear Gas".to_owned(),
|
||||
id,
|
||||
WeaponVerb::Exploded,
|
||||
WeaponSlot::Temporary,
|
||||
),
|
||||
TemporaryBundle::default(),
|
||||
DebuffingTemp::TearGas,
|
||||
)),
|
||||
Self::SmokeGrenade => world.spawn((
|
||||
WeaponBundle::new(
|
||||
"Smoke Grenade".to_owned(),
|
||||
id,
|
||||
WeaponVerb::Exploded,
|
||||
WeaponSlot::Temporary,
|
||||
),
|
||||
TemporaryBundle::default(),
|
||||
DebuffingTemp::SmokeGrenade,
|
||||
)),
|
||||
Self::PepperSpray => world.spawn((
|
||||
WeaponBundle::new(
|
||||
"Pepper Spray".to_owned(),
|
||||
id,
|
||||
WeaponVerb::Exploded,
|
||||
WeaponSlot::Temporary,
|
||||
),
|
||||
TemporaryBundle::default(),
|
||||
DebuffingTemp::PepperSpray,
|
||||
)),
|
||||
Self::ConcussionGrenade => world.spawn((
|
||||
WeaponBundle::new(
|
||||
"Concussion Grenade".to_owned(),
|
||||
id,
|
||||
WeaponVerb::Exploded,
|
||||
WeaponSlot::Temporary,
|
||||
),
|
||||
TemporaryBundle::default(),
|
||||
DebuffingTemp::ConcussionGrenade,
|
||||
)),
|
||||
Self::FlashGrenade => world.spawn((
|
||||
WeaponBundle::new(
|
||||
"Flash Grenade".to_owned(),
|
||||
id,
|
||||
WeaponVerb::Exploded,
|
||||
WeaponSlot::Temporary,
|
||||
),
|
||||
TemporaryBundle::default(),
|
||||
DebuffingTemp::FlashGrenade,
|
||||
)),
|
||||
Self::Sand => world.spawn((
|
||||
WeaponBundle::new(
|
||||
"Sand".to_owned(),
|
||||
id,
|
||||
WeaponVerb::Exploded,
|
||||
WeaponSlot::Temporary,
|
||||
),
|
||||
TemporaryBundle::default(),
|
||||
DebuffingTemp::Sand,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn use_debuffing_temp(
|
||||
mut temp_q: Query<(Entity, &DebuffingTemp, &mut Uses), With<Current>>,
|
||||
target_q: Query<Entity, With<CurrentTarget>>,
|
||||
mut effects: Effects,
|
||||
mut commands: Commands,
|
||||
) {
|
||||
let Ok((weapon, temp, mut uses)) = temp_q.get_single_mut() else {
|
||||
return;
|
||||
};
|
||||
let target = target_q.single();
|
||||
|
||||
match temp {
|
||||
DebuffingTemp::TearGas => effects.spawn_and_insert(
|
||||
TempDebuffEffect::<TearGas>::default(),
|
||||
target,
|
||||
AssociatedWeapon(weapon),
|
||||
),
|
||||
DebuffingTemp::SmokeGrenade => effects.spawn_and_insert(
|
||||
TempDebuffEffect::<SmokeGrenade>::default(),
|
||||
target,
|
||||
AssociatedWeapon(weapon),
|
||||
),
|
||||
DebuffingTemp::PepperSpray => effects.spawn_and_insert(
|
||||
TempDebuffEffect::<PepperSpray>::default(),
|
||||
target,
|
||||
AssociatedWeapon(weapon),
|
||||
),
|
||||
DebuffingTemp::ConcussionGrenade => effects.spawn_and_insert(
|
||||
TempDebuffEffect::<ConcussionGrenade>::default(),
|
||||
target,
|
||||
AssociatedWeapon(weapon),
|
||||
),
|
||||
DebuffingTemp::FlashGrenade => effects.spawn_and_insert(
|
||||
TempDebuffEffect::<FlashGrenade>::default(),
|
||||
target,
|
||||
AssociatedWeapon(weapon),
|
||||
),
|
||||
DebuffingTemp::Sand => effects.spawn_and_insert(
|
||||
TempDebuffEffect::<Sand>::default(),
|
||||
target,
|
||||
AssociatedWeapon(weapon),
|
||||
),
|
||||
};
|
||||
|
||||
uses.0 -= 1;
|
||||
if uses.0 == 0 {
|
||||
commands.entity(weapon).remove::<Usable>();
|
||||
}
|
||||
}
|
||||
|
||||
fn restore_uses(mut uses_q: Query<&mut Uses>) {
|
||||
for mut uses in uses_q.iter_mut() {
|
||||
uses.0 = 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn configure(stages: &mut Stages) {
|
||||
stages.turn.add_systems(use_debuffing_temp);
|
||||
stages.restore.add_systems(restore_uses);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue