use bevy hierarchy system

This commit is contained in:
TotallyNot 2025-11-03 18:54:07 +01:00
parent 35413b563c
commit cfe2631578
Signed by: pyrite
GPG key ID: 7F1BA9170CD35D15
15 changed files with 246 additions and 643 deletions

View file

@ -1,6 +1,6 @@
use std::borrow::Cow;
use bevy_ecs::{entity::Entity, world::World};
use bevy_ecs::{prelude::*, relationship::RelatedSpawner};
use strum::Display;
use crate::{
@ -186,8 +186,8 @@ impl ArmourDto {
.collect()
}
pub fn spawn(self, world: &mut World) -> Entity {
let mut commands = world.spawn(draw_id());
pub fn spawn(self, spawner: &mut RelatedSpawner<'_, ChildOf>) -> Entity {
let mut commands = spawner.spawn(draw_id());
commands.insert((
Name(self.name.to_string()),

View file

@ -52,19 +52,6 @@ pub struct PlayerDto {
impl PlayerDto {
pub fn spawn(self, world: &mut World) -> EntityWorldMut<'_> {
let primary = self.weapons.primary.map(|p| p.spawn(world));
let secondary = self.weapons.secondary.map(|s| s.spawn(world));
let melee = self.weapons.melee.map(|m| m.spawn(world));
let temporary = self.weapons.temporary.map(|m| m.spawn(world));
let fists = WeaponDto::FISTS.spawn(world);
let kick = WeaponDto::KITCHEN_KNIFE.spawn(world);
let head = self.armour.helmet.map(|a| a.spawn(world));
let torso = self.armour.body.map(|a| a.spawn(world));
let legs = self.armour.pants.map(|a| a.spawn(world));
let hands = self.armour.gloves.map(|a| a.spawn(world));
let feet = self.armour.boots.map(|a| a.spawn(world));
let mut commands = world.spawn(draw_id());
commands.insert((
@ -85,22 +72,48 @@ impl PlayerDto {
faction,
});
commands.insert(Weapons {
primary,
secondary,
melee,
temporary,
kick,
fists,
let mut weapons = None;
commands.with_children(|spawner| {
let primary = self.weapons.primary.map(|p| p.spawn(spawner));
let secondary = self.weapons.secondary.map(|s| s.spawn(spawner));
let melee = self.weapons.melee.map(|m| m.spawn(spawner));
let temporary = self.weapons.temporary.map(|m| m.spawn(spawner));
let fists = WeaponDto::FISTS.spawn(spawner);
let kick = WeaponDto::KICK.spawn(spawner);
weapons = Some(Weapons {
primary,
secondary,
melee,
temporary,
kick,
fists,
});
});
if let Some(weapons) = weapons {
commands.insert(weapons);
}
let mut armour = None;
commands.with_children(|spawner| {
let head = self.armour.helmet.map(|a| a.spawn(spawner));
let torso = self.armour.body.map(|a| a.spawn(spawner));
let legs = self.armour.pants.map(|a| a.spawn(spawner));
let hands = self.armour.gloves.map(|a| a.spawn(spawner));
let feet = self.armour.boots.map(|a| a.spawn(spawner));
armour = Some(PlayerArmour {
torso,
head,
legs,
feet,
hands,
})
});
commands.insert(PlayerArmour {
torso,
head,
legs,
feet,
hands,
});
if let Some(armour) = armour {
commands.insert(armour);
}
commands
}

View file

@ -1,6 +1,6 @@
use std::borrow::Cow;
use bevy_ecs::prelude::*;
use bevy_ecs::{prelude::*, relationship::RelatedSpawner};
use crate::{
bundle::{
@ -252,8 +252,8 @@ impl WeaponDto {
})
}
pub fn spawn(self, world: &mut World) -> Entity {
let mut commands = world.spawn(draw_id());
pub fn spawn(self, spawner: &mut RelatedSpawner<'_, ChildOf>) -> Entity {
let mut commands = spawner.spawn(draw_id());
let verb = match self.kind {
WeaponSlot::Primary | WeaponSlot::Secondary => WeaponVerb::Fired,

View file

@ -1,178 +0,0 @@
use bevy_ecs::{
prelude::*,
system::{Command, EntityCommands},
};
#[derive(Component, Debug, Clone, Copy)]
pub struct Parent(Entity);
impl Parent {
pub fn get(&self) -> Entity {
self.0
}
}
#[derive(Component, Debug, Clone, Default)]
pub struct Children(Vec<Entity>);
impl Children {
pub fn get(&self) -> &[Entity] {
&self.0
}
}
struct AddChild {
parent: Entity,
child: Entity,
}
impl Command for AddChild {
fn apply(self, world: &mut World) {
let mut parent = world.entity_mut(self.parent);
if let Some(mut children) = parent.get_mut::<Children>() {
children.0.push(self.child);
} else {
parent.insert(Children(vec![self.child]));
}
}
}
struct AddChildren {
parent: Entity,
children: Vec<Entity>,
}
impl Command for AddChildren {
fn apply(mut self, world: &mut World) {
let mut parent = world.entity_mut(self.parent);
if let Some(mut children) = parent.get_mut::<Children>() {
children.0.append(&mut self.children);
} else {
parent.insert(Children(self.children));
}
}
}
struct RemoveChild {
parent: Entity,
child: Entity,
}
impl Command for RemoveChild {
fn apply(self, world: &mut World) {
let mut parent = world.entity_mut(self.parent);
let mut children = parent
.get_mut::<Children>()
.expect("Parent component has no children");
children.0.retain(|child| *child != self.child);
}
}
pub trait HierarchyBuilder {
fn add_child(&mut self, child: Entity) -> &mut Self;
fn add_children(&mut self, children: impl AsRef<[Entity]>) -> &mut Self;
fn remove_child(&mut self, child: Entity) -> &mut Self;
fn set_parent(&mut self, parent: Entity) -> &mut Self;
}
impl<'a> HierarchyBuilder for EntityCommands<'a> {
fn add_child(&mut self, child: Entity) -> &mut Self {
let parent = self.id();
self.commands().queue(AddChild { parent, child });
self.commands().entity(child).insert(Parent(parent));
self
}
fn add_children(&mut self, children: impl AsRef<[Entity]>) -> &mut Self {
let children = children.as_ref();
let parent = self.id();
self.commands().queue(AddChildren {
parent,
children: children.to_owned(),
});
self.commands().insert_batch(
children
.iter()
.map(|e| (*e, Parent(parent)))
.collect::<Vec<_>>(),
);
self
}
fn remove_child(&mut self, child: Entity) -> &mut Self {
let parent = self.id();
self.commands().queue(RemoveChild { parent, child });
self.commands().entity(child).remove::<Parent>();
self
}
fn set_parent(&mut self, parent: Entity) -> &mut Self {
let child = self.id();
self.commands().queue(AddChild { parent, child });
self.commands().entity(child).insert(Parent(parent));
self
}
}
impl<'w> HierarchyBuilder for EntityWorldMut<'w> {
fn add_child(&mut self, child: Entity) -> &mut Self {
let parent_id = self.id();
unsafe {
self.world_mut()
.entity_mut(child)
.insert(Parent(parent_id))
.update_location();
}
if let Some(mut children) = self.get_mut::<Children>() {
children.0.push(child);
self
} else {
self.insert(Children(vec![child]))
}
}
fn add_children(&mut self, children: impl AsRef<[Entity]>) -> &mut Self {
let parent_id = self.id();
unsafe {
for child in children.as_ref() {
self.world_mut()
.entity_mut(*child)
.insert(Parent(parent_id))
.update_location();
}
}
if let Some(mut old_children) = self.get_mut::<Children>() {
old_children.0.append(&mut children.as_ref().to_owned());
self
} else {
self.insert(Children(children.as_ref().to_owned()))
}
}
fn remove_child(&mut self, child: Entity) -> &mut Self {
unsafe {
self.world_mut()
.entity_mut(child)
.remove::<Parent>()
.update_location();
}
if let Some(mut children) = self.get_mut::<Children>() {
children.0.retain(|c| *c != child);
}
self
}
fn set_parent(&mut self, parent: Entity) -> &mut Self {
let child_id = self.id();
unsafe {
self.world_mut()
.entity_mut(parent)
.add_child(child_id)
.update_location()
}
self
}
}

View file

@ -1,3 +1,2 @@
pub mod bundle;
pub mod dto;
// pub mod hierarchy;

View file

@ -1,18 +1,15 @@
use bevy_ecs::prelude::*;
use proxisim_models::{
bundle::armour::{
ArmourBodyPart, ArmourBodyPartSlot, ArmourBodyParts, ArmourCoverage, ArmourValue,
ArmourVec, BodyPartCoverage, Immunities, Immunity, PlayerArmour,
},
hierarchy::HierarchyBuilder,
use proxisim_models::bundle::armour::{
ArmourBodyPart, ArmourBodyPartSlot, ArmourBodyParts, ArmourCoverage, ArmourValue, ArmourVec,
BodyPartCoverage, Immunities, Immunity, PlayerArmour,
};
use strum::IntoEnumIterator;
use crate::{
Stages,
player::status_effect::{
ConcussionGrenade, FlashGrenade, PepperSpray, TearGas, TempDebuffImmunity,
},
Stages,
};
fn generate_body_parts(
@ -24,7 +21,7 @@ fn generate_body_parts(
let mut parts = ArmourVec::<ArmourBodyPart>::default();
for (armour, coverage, armour_value, immunities) in armour_q.iter_many(equipped_armour) {
commands.entity(armour).set_parent(player);
// commands.entity(player).add_child(armour);
if let Some(immunities) = immunities {
let mut player = commands.entity(player);

View file

@ -1,10 +1,7 @@
use std::{any::TypeId, collections::HashMap, sync::Mutex};
use bevy_ecs::{prelude::*, system::SystemParam};
use proxisim_models::{
bundle::player::{Current, Defender, Player},
hierarchy::{HierarchyBuilder, Parent},
};
use proxisim_models::bundle::player::{Current, Defender, Player};
use crate::Stages;
@ -77,13 +74,9 @@ impl<'w, 's> Effects<'w, 's> {
) -> Entity {
let id = self.registry.type_map.get(&TypeId::of::<T>()).unwrap();
let spawned_effect = self
.commands
.spawn(effect)
.insert(*id)
.insert(addins)
.set_parent(to)
.id();
let spawned_effect = self.commands.spawn(effect).insert(*id).insert(addins).id();
self.commands.entity(to).add_child(spawned_effect);
self.scheduled
.create
@ -202,7 +195,7 @@ pub(crate) fn run_effects(world: &mut World) {
};
for entity in entities {
let parent = world.entity(entity).get::<Parent>().unwrap().get();
let parent = world.entity(entity).get::<ChildOf>().unwrap().parent();
world.entity_mut(parent).remove_child(entity);
world.despawn(entity);
}
@ -277,12 +270,12 @@ fn mark_permanent_effects(effect_q: Query<Entity, With<EffectId>>, mut commands:
}
fn remove_transient_effects(
effect_q: Query<(Entity, &Parent), (With<EffectId>, Without<Permanent>)>,
effect_q: Query<(Entity, &ChildOf), (With<EffectId>, Without<Permanent>)>,
mut commands: Commands,
) {
for (effect, target) in effect_q.iter() {
commands.entity(effect).despawn();
commands.entity(target.get()).remove_child(effect);
commands.entity(target.parent()).remove_child(effect);
}
}

View file

@ -3,12 +3,11 @@ use std::collections::HashMap;
use bevy_ecs::prelude::*;
use proxisim_models::{
bundle::{
Id, Name,
player::{Attacker, Player},
weapon::Weapon,
Id, Name,
},
dto::metrics::EntityInfo,
hierarchy::Parent,
};
use crate::Stages;
@ -18,7 +17,7 @@ pub struct EntityRegistry(pub HashMap<Entity, EntityInfo>);
fn read_entities(
player_q: Query<(Entity, &Name, &Id, Has<Attacker>), With<Player>>,
weapon_q: Query<(Entity, &Parent, &Name, &Id), With<Weapon>>,
weapon_q: Query<(Entity, &ChildOf, &Name, &Id), With<Weapon>>,
mut registry: ResMut<EntityRegistry>,
) {
for (player, name, id, is_attacker) in player_q.iter() {
@ -33,7 +32,7 @@ fn read_entities(
}
for (weapon, player, name, id) in weapon_q.iter() {
let (_, _, player_id, _) = player_q.get(player.get()).unwrap();
let (_, _, player_id, _) = player_q.get(player.parent()).unwrap();
registry.0.insert(
weapon,
EntityInfo::Weapon {

View file

@ -1,178 +0,0 @@
use bevy_ecs::{
prelude::*,
system::{Command, EntityCommands},
};
#[derive(Component, Clone, Copy)]
pub struct Parent(Entity);
impl Parent {
pub fn get(&self) -> Entity {
self.0
}
}
#[derive(Component, Clone, Default)]
pub struct Children(Vec<Entity>);
impl Children {
pub fn get(&self) -> &[Entity] {
&self.0
}
}
struct AddChild {
parent: Entity,
child: Entity,
}
impl Command for AddChild {
fn apply(self, world: &mut World) {
let mut parent = world.entity_mut(self.parent);
if let Some(mut children) = parent.get_mut::<Children>() {
children.0.push(self.child);
} else {
parent.insert(Children(vec![self.child]));
}
}
}
struct AddChildren {
parent: Entity,
children: Vec<Entity>,
}
impl Command for AddChildren {
fn apply(mut self, world: &mut World) {
let mut parent = world.entity_mut(self.parent);
if let Some(mut children) = parent.get_mut::<Children>() {
children.0.append(&mut self.children);
} else {
parent.insert(Children(self.children));
}
}
}
struct RemoveChild {
parent: Entity,
child: Entity,
}
impl Command for RemoveChild {
fn apply(self, world: &mut World) {
let mut parent = world.entity_mut(self.parent);
let mut children = parent
.get_mut::<Children>()
.expect("Parent component has no children");
children.0.retain(|child| *child != self.child);
}
}
pub trait HierarchyBuilder {
fn add_child(&mut self, child: Entity) -> &mut Self;
fn add_children(&mut self, children: impl AsRef<[Entity]>) -> &mut Self;
fn remove_child(&mut self, child: Entity) -> &mut Self;
fn set_parent(&mut self, parent: Entity) -> &mut Self;
}
impl<'a> HierarchyBuilder for EntityCommands<'a> {
fn add_child(&mut self, child: Entity) -> &mut Self {
let parent = self.id();
self.commands().queue(AddChild { parent, child });
self.commands().entity(child).insert(Parent(parent));
self
}
fn add_children(&mut self, children: impl AsRef<[Entity]>) -> &mut Self {
let children = children.as_ref();
let parent = self.id();
self.commands().queue(AddChildren {
parent,
children: children.to_owned(),
});
self.commands().insert_batch(
children
.iter()
.map(|e| (*e, Parent(parent)))
.collect::<Vec<_>>(),
);
self
}
fn remove_child(&mut self, child: Entity) -> &mut Self {
let parent = self.id();
self.commands().queue(RemoveChild { parent, child });
self.commands().entity(child).remove::<Parent>();
self
}
fn set_parent(&mut self, parent: Entity) -> &mut Self {
let child = self.id();
self.commands().queue(AddChild { parent, child });
self.commands().entity(child).insert(Parent(parent));
self
}
}
impl<'w> HierarchyBuilder for EntityWorldMut<'w> {
fn add_child(&mut self, child: Entity) -> &mut Self {
let parent_id = self.id();
unsafe {
self.world_mut()
.entity_mut(child)
.insert(Parent(parent_id))
.update_location();
}
if let Some(mut children) = self.get_mut::<Children>() {
children.0.push(child);
self
} else {
self.insert(Children(vec![child]))
}
}
fn add_children(&mut self, children: impl AsRef<[Entity]>) -> &mut Self {
let parent_id = self.id();
unsafe {
for child in children.as_ref() {
self.world_mut()
.entity_mut(*child)
.insert(Parent(parent_id))
.update_location();
}
}
if let Some(mut old_children) = self.get_mut::<Children>() {
old_children.0.append(&mut children.as_ref().to_owned());
self
} else {
self.insert(Children(children.as_ref().to_owned()))
}
}
fn remove_child(&mut self, child: Entity) -> &mut Self {
unsafe {
self.world_mut()
.entity_mut(child)
.remove::<Parent>()
.update_location();
}
if let Some(mut children) = self.get_mut::<Children>() {
children.0.retain(|c| *c != child);
}
self
}
fn set_parent(&mut self, parent: Entity) -> &mut Self {
let child_id = self.id();
unsafe {
self.world_mut()
.entity_mut(parent)
.add_child(child_id)
.update_location()
}
self
}
}

View file

@ -1,16 +1,13 @@
use std::sync::Mutex;
use bevy_ecs::{prelude::*, query::QuerySingleError, system::SystemParam};
use proxisim_models::{
bundle::{
stat::{
AdditiveBonus, BaselineStat, ClipSize, Clips, CritRate, DamageBonus, Defence,
Dexterity, EffectiveStat, MultiplicativeBonus, SimpleStatBaseline, SimpleStatBonus,
SimpleStatEffective, SimpleStatMarker, Speed, StatMarker, Strength, WeaponAccuracy,
},
weapon::WeaponVerb,
use proxisim_models::bundle::{
stat::{
AdditiveBonus, BaselineStat, ClipSize, Clips, CritRate, DamageBonus, Defence, Dexterity,
EffectiveStat, MultiplicativeBonus, SimpleStatBaseline, SimpleStatBonus,
SimpleStatEffective, SimpleStatMarker, Speed, StatMarker, Strength, WeaponAccuracy,
},
hierarchy::Children,
weapon::WeaponVerb,
};
use crate::{Stages, entity_registry::EntityRegistry};
@ -533,11 +530,11 @@ fn log_stat_changes<Stat: StatMarker>(
) {
for (player, baseline, effective, children) in stat_q.iter() {
let effects_add: Vec<_> = add_q
.iter_many(children.get())
.iter_many(children)
.map(|bonus| (bonus.label, 100.0 * bonus.value))
.collect();
let effects_mult: Vec<_> = mult_q
.iter_many(children.get())
.iter_many(children)
.map(|bonus| (bonus.label, 100.0 * bonus.value))
.collect();
@ -570,7 +567,7 @@ fn log_simple_stat_changes<Stat: SimpleStatMarker>(
{
for (target, baseline, effective, children) in stat_q.iter() {
let bonuses: Vec<_> = bonus_q
.iter_many(children.get())
.iter_many(children)
.map(|bonus| (bonus.label, Stat::denormalise_bonus(bonus.value)))
.collect();

View file

@ -1,23 +1,18 @@
use bevy_ecs::prelude::*;
use proxisim_models::{
bundle::{
armour::{ArmourBodyPart, ArmourBodyParts},
passive::{FactionUpgrades, Merits},
player::{
Attacker, BodyPart, ChooseWeapon, CombatTurns, Current, CurrentTarget, Defeated,
Defender, FightEndType, Level, MaxHealth, PartDamageBonus, Player, PlayerStrategy,
Weapons,
},
stat::{
AmmoControl, Clips, CritRate, DamageBonus, Defence, Dexterity, EffectiveStat, Health,
SimpleStatBundle, SimpleStatEffective, Speed, Strength, WeaponAccuracy,
},
weapon::{
Ammo, DamageStat, NeedsReload, NonTargeted, RateOfFire, Usable, Uses, Weapon,
WeaponSlot,
},
use proxisim_models::bundle::{
armour::{ArmourBodyPart, ArmourBodyParts},
passive::{FactionUpgrades, Merits},
player::{
Attacker, BodyPart, ChooseWeapon, CombatTurns, Current, CurrentTarget, Defeated, Defender,
FightEndType, Level, MaxHealth, PartDamageBonus, Player, PlayerStrategy, Weapons,
},
stat::{
AmmoControl, Clips, CritRate, DamageBonus, Defence, Dexterity, EffectiveStat, Health,
SimpleStatBundle, SimpleStatEffective, Speed, Strength, WeaponAccuracy,
},
weapon::{
Ammo, DamageStat, NeedsReload, NonTargeted, RateOfFire, Usable, Uses, Weapon, WeaponSlot,
},
hierarchy::Children,
};
use rand::Rng as _;
@ -33,12 +28,12 @@ use crate::{
pub mod stats;
pub mod status_effect;
fn select_weapon(
fn select_weapon<'a>(
weapons: &Weapons,
slot: WeaponSlot,
reload: bool,
usable_q: &Query<(Has<NeedsReload>, Option<&Children>), With<Usable>>,
) -> Option<(Entity, Option<Children>)> {
usable_q: &'a Query<(Has<NeedsReload>, Option<&Children>), With<Usable>>,
) -> Option<(Entity, Option<&'a Children>)> {
let id = match slot {
WeaponSlot::Primary => weapons.primary?,
WeaponSlot::Secondary => weapons.secondary?,
@ -53,7 +48,7 @@ fn select_weapon(
if !reload && needs_reload {
None
} else {
Some((id, children.cloned()))
Some((id, children))
}
}
@ -155,7 +150,7 @@ pub fn pick_action(
let target = target_q.single().unwrap();
if let Some(children) = children {
for effect in weapon_trigger_q.iter_many(children.get()) {
for effect in weapon_trigger_q.iter_many(children) {
effect.trigger(&mut effects, current, target);
}
}
@ -357,7 +352,7 @@ pub fn use_damaging_weapon(
let mut dmg_bonus = dmg_bonus + p_dmg_bonus;
for part_bonus in part_bonus_q.iter_many(children.get()) {
for part_bonus in part_bonus_q.iter_many(children) {
if let Some(bonus) = part_bonus.dmg_bonus(body_part) {
dmg_bonus.value += bonus;
}
@ -378,7 +373,7 @@ pub fn use_damaging_weapon(
metrics.record_histogram(Some(weapon), "dmg", dmg);
if dmg > 0 {
for effect in damage_proc_q.iter_many(children.get()) {
for effect in damage_proc_q.iter_many(children) {
match *effect {
DamageProcEffect::MultiTurn { value, bonus } => {
if multi_attack_proc.is_some() {

View file

@ -1,21 +1,18 @@
use std::marker::PhantomData;
use bevy_ecs::prelude::*;
use proxisim_models::{
bundle::stat::{
AdditiveBonus, AdditiveBonuses, AmmoControl, BaselineStat, ClipSize, Clips, CritRate,
DamageBonus, Defence, Dexterity, EffectiveStat, Health, MultiplicativeBonus,
MultiplicativeBonuses, SimpleStatBonus, SimpleStatEffective, SimpleStatMarker,
SimpleStatSnapshot, Speed, StatMarker, StatSnapshot, Strength, WeaponAccuracy,
},
hierarchy::Parent,
use proxisim_models::bundle::stat::{
AdditiveBonus, AdditiveBonuses, AmmoControl, BaselineStat, ClipSize, Clips, CritRate,
DamageBonus, Defence, Dexterity, EffectiveStat, Health, MultiplicativeBonus,
MultiplicativeBonuses, SimpleStatBonus, SimpleStatEffective, SimpleStatMarker,
SimpleStatSnapshot, Speed, StatMarker, StatSnapshot, Strength, WeaponAccuracy,
};
use crate::Stages;
fn add_additive_bonus<Stat: StatMarker>(
In(entities): In<Vec<Entity>>,
effect_q: Query<(&AdditiveBonus<Stat>, &Parent)>,
effect_q: Query<(&AdditiveBonus<Stat>, &ChildOf)>,
mut stat_q: Query<(
&BaselineStat<Stat>,
&mut AdditiveBonuses<Stat>,
@ -24,7 +21,7 @@ fn add_additive_bonus<Stat: StatMarker>(
)>,
) {
for (bonus, player) in effect_q.iter_many(entities) {
let (baseline, mut add, mult, mut eff) = stat_q.get_mut(player.get()).unwrap();
let (baseline, mut add, mult, mut eff) = stat_q.get_mut(player.parent()).unwrap();
add.factor += bonus.value;
eff.value = baseline.value * add.factor * mult.factor;
}
@ -32,7 +29,7 @@ fn add_additive_bonus<Stat: StatMarker>(
fn revert_additive_bonus<Stat: StatMarker>(
In(entities): In<Vec<Entity>>,
effect_q: Query<(&AdditiveBonus<Stat>, &Parent)>,
effect_q: Query<(&AdditiveBonus<Stat>, &ChildOf)>,
mut stat_q: Query<(
&BaselineStat<Stat>,
&mut AdditiveBonuses<Stat>,
@ -41,7 +38,7 @@ fn revert_additive_bonus<Stat: StatMarker>(
)>,
) {
for (bonus, player) in effect_q.iter_many(entities) {
let (baseline, mut add, mult, mut eff) = stat_q.get_mut(player.get()).unwrap();
let (baseline, mut add, mult, mut eff) = stat_q.get_mut(player.parent()).unwrap();
add.factor -= bonus.value;
eff.value = baseline.value * add.factor * mult.factor;
}
@ -49,7 +46,7 @@ fn revert_additive_bonus<Stat: StatMarker>(
fn add_multiplicative_bonus<Stat: StatMarker>(
In(entities): In<Vec<Entity>>,
effect_q: Query<(&MultiplicativeBonus<Stat>, &Parent)>,
effect_q: Query<(&MultiplicativeBonus<Stat>, &ChildOf)>,
mut stat_q: Query<(
&BaselineStat<Stat>,
&AdditiveBonuses<Stat>,
@ -58,7 +55,7 @@ fn add_multiplicative_bonus<Stat: StatMarker>(
)>,
) {
for (bonus, player) in effect_q.iter_many(entities) {
let (baseline, add, mut mult, mut eff) = stat_q.get_mut(player.get()).unwrap();
let (baseline, add, mut mult, mut eff) = stat_q.get_mut(player.parent()).unwrap();
mult.factor *= bonus.value;
eff.value = baseline.value * add.factor * mult.factor;
}
@ -66,7 +63,7 @@ fn add_multiplicative_bonus<Stat: StatMarker>(
fn revert_multiplicative_bonus<Stat: StatMarker>(
In(entities): In<Vec<Entity>>,
effect_q: Query<(&MultiplicativeBonus<Stat>, &Parent)>,
effect_q: Query<(&MultiplicativeBonus<Stat>, &ChildOf)>,
mut stat_q: Query<(
&BaselineStat<Stat>,
&AdditiveBonuses<Stat>,
@ -75,7 +72,7 @@ fn revert_multiplicative_bonus<Stat: StatMarker>(
)>,
) {
for (bonus, player) in effect_q.iter_many(entities) {
let (baseline, add, mut mult, mut eff) = stat_q.get_mut(player.get()).unwrap();
let (baseline, add, mut mult, mut eff) = stat_q.get_mut(player.parent()).unwrap();
mult.factor /= bonus.value;
eff.value = baseline.value * add.factor * mult.factor;
}
@ -117,22 +114,22 @@ fn restore_stats<Stat: StatMarker>(
fn apply_simple_stat_bonus<Stat: SimpleStatMarker>(
In(entities): In<Vec<Entity>>,
effect_q: Query<(&SimpleStatBonus<Stat>, &Parent)>,
effect_q: Query<(&SimpleStatBonus<Stat>, &ChildOf)>,
mut stat_q: Query<&mut SimpleStatEffective<Stat>>,
) {
for (bonus, target) in effect_q.iter_many(&entities) {
let mut effective = stat_q.get_mut(target.get()).unwrap();
let mut effective = stat_q.get_mut(target.parent()).unwrap();
effective.value = Stat::apply_bonus(effective.value, bonus.value);
}
}
fn revert_simple_stat_bonus<Stat: SimpleStatMarker>(
In(entities): In<Vec<Entity>>,
effect_q: Query<(&SimpleStatBonus<Stat>, &Parent)>,
effect_q: Query<(&SimpleStatBonus<Stat>, &ChildOf)>,
mut stat_q: Query<&mut SimpleStatEffective<Stat>>,
) {
for (bonus, target) in effect_q.iter_many(entities) {
let mut effective = stat_q.get_mut(target.get()).unwrap();
let mut effective = stat_q.get_mut(target.parent()).unwrap();
effective.value = Stat::revert_bonus(effective.value, bonus.value);
}
}

View file

@ -1,11 +1,8 @@
use std::{collections::VecDeque, marker::PhantomData};
use bevy_ecs::prelude::*;
use proxisim_models::{
bundle::stat::{
AdditiveBonus, Defence, Dexterity, MultiplicativeBonus, Speed, StatMarker, Strength,
},
hierarchy::Parent,
use proxisim_models::bundle::stat::{
AdditiveBonus, Defence, Dexterity, MultiplicativeBonus, Speed, StatMarker, Strength,
};
use rand::Rng as _;
@ -345,7 +342,7 @@ impl AdditiveStatusEffectMarker<2> for Frozen {
fn apply_additive_status_effect<const N: usize, M: AdditiveStatusEffectMarker<N>>(
In(entities): In<Vec<Entity>>,
effect_q: Query<(Entity, &Parent, &AdditiveStatusEffect<N, M>)>,
effect_q: Query<(Entity, &ChildOf, &AdditiveStatusEffect<N, M>)>,
mut parent_q: Query<Option<&mut StatusEffectStack<M>>>,
mut commands: Commands,
mut effects: Effects,
@ -353,15 +350,15 @@ fn apply_additive_status_effect<const N: usize, M: AdditiveStatusEffectMarker<N>
) {
for (entity, player, effect) in effect_q.iter_many(entities) {
log!(logger, "apply_status_effect", {
recipient: player.get(),
recipient: player.parent(),
effect: std::any::type_name::<M>(),
});
let stack = parent_q.get_mut(player.get()).unwrap();
let stack = parent_q.get_mut(player.parent()).unwrap();
let new_effects = <M::AffectedStats as Stats<N>>::spawn_additive_effects(
&mut effects,
player.get(),
player.parent(),
M::factor() * (1.0 + effect.extra_effectiveness),
std::any::type_name::<M>(),
);
@ -389,13 +386,13 @@ fn apply_additive_status_effect<const N: usize, M: AdditiveStatusEffectMarker<N>
fn remove_additive_status_effect<const N: usize, M: AdditiveStatusEffectMarker<N>>(
In(entities): In<Vec<Entity>>,
effect_q: Query<(Entity, &Parent)>,
effect_q: Query<(Entity, &ChildOf)>,
mut parent_q: Query<Option<&mut StatusEffectStack<M>>>,
linked_q: Query<&LinkedComponents<N>>,
mut effects: Effects,
) {
for (effect, player) in effect_q.iter_many(entities) {
if let Some(mut stack) = parent_q.get_mut(player.get()).unwrap()
if let Some(mut stack) = parent_q.get_mut(player.parent()).unwrap()
&& stack.effects.front() == Some(&effect)
{
stack.effects.pop_front();
@ -412,8 +409,8 @@ fn remove_additive_status_effect<const N: usize, M: AdditiveStatusEffectMarker<N
fn apply_temp_debuff_effect<Temp: DebuffingTempMarker>(
In(entities): In<Vec<Entity>>,
mut rng: ResMut<Rng>,
temp_q: Query<(Entity, &Parent, &AssociatedWeapon)>,
weapon_q: Query<&Parent>,
temp_q: Query<(Entity, &ChildOf, &AssociatedWeapon)>,
weapon_q: Query<&ChildOf>,
mut parent_q: Query<(
Option<&mut StatusEffectStack<Temp>>,
Has<TempDebuffImmunity<Temp>>,
@ -422,14 +419,14 @@ fn apply_temp_debuff_effect<Temp: DebuffingTempMarker>(
mut logger: Logger,
) {
for (effect, player, weapon) in temp_q.iter_many(entities) {
let (stack, immunity) = parent_q.get_mut(player.get()).unwrap();
let (stack, immunity) = parent_q.get_mut(player.parent()).unwrap();
let user = weapon_q.get(weapon.0).unwrap();
if immunity {
commands.entity(effect).despawn();
commands.entity(player.get()).remove_child(effect);
commands.entity(player.parent()).remove_child(effect);
log!(logger, "used_debuff_temp", {
actor: user.get(),
recipient: player.get(),
actor: user.parent(),
recipient: player.parent(),
weapon: weapon.0,
immune: true,
});
@ -447,7 +444,7 @@ fn apply_temp_debuff_effect<Temp: DebuffingTempMarker>(
let bonus = effects.spawn(
MultiplicativeBonus::<Temp::Stat>::new(std::any::type_name::<Temp>(), effective_factor),
player.get(),
player.parent(),
);
if let Some(mut stack) = stack {
@ -456,7 +453,7 @@ fn apply_temp_debuff_effect<Temp: DebuffingTempMarker>(
stack.effects.push_back(effect);
} else {
commands
.entity(player.get())
.entity(player.parent())
.insert(StatusEffectStack::<Temp> {
effects: VecDeque::from([effect]),
bonus,
@ -465,8 +462,8 @@ fn apply_temp_debuff_effect<Temp: DebuffingTempMarker>(
}
log!(logger, "used_debuff_temp", {
actor: user.get(),
recipient: player.get(),
actor: user.parent(),
recipient: player.parent(),
weapon: weapon.0,
immune: false,
});
@ -475,14 +472,14 @@ fn apply_temp_debuff_effect<Temp: DebuffingTempMarker>(
fn remove_temp_debuff_effect<Temp: DebuffingTempMarker>(
In(entities): In<Vec<Entity>>,
temp_q: Query<&Parent>,
temp_q: Query<&ChildOf>,
mut parent_q: Query<(&mut StatusEffectStack<Temp>, Has<TempDebuffImmunity<Temp>>)>,
mut commands: Commands,
_logger: Logger,
mut effects: Effects,
) {
for player in temp_q.iter_many(entities) {
let (mut stack, immunity) = parent_q.get_mut(player.get()).unwrap();
let (mut stack, immunity) = parent_q.get_mut(player.parent()).unwrap();
if immunity {
continue;
}
@ -501,13 +498,13 @@ fn remove_temp_debuff_effect<Temp: DebuffingTempMarker>(
std::any::type_name::<Temp>(),
effective_factor,
),
player.get(),
player.parent(),
);
stack.effects.pop_front();
} else {
commands
.entity(player.get())
.entity(player.parent())
.remove::<StatusEffectStack<Temp>>();
}
}

View file

@ -1,14 +1,11 @@
use bevy_ecs::prelude::*;
use proxisim_models::{
bundle::{
bonus::{BonusPartDamageBonus, BonusValue, WeaponBonusType},
player::PartDamageBonus,
stat::{
AdditiveBonus, AmmoControl, Clips, CritRate, DamageBonus, SimpleStatBonus,
SimpleStatEffective, Speed, Strength, WeaponAccuracy,
},
use proxisim_models::bundle::{
bonus::{BonusPartDamageBonus, BonusValue, WeaponBonusType},
player::PartDamageBonus,
stat::{
AdditiveBonus, AmmoControl, Clips, CritRate, DamageBonus, SimpleStatBonus,
SimpleStatEffective, Speed, Strength, WeaponAccuracy,
},
hierarchy::{HierarchyBuilder, Parent},
};
use crate::{
@ -197,7 +194,7 @@ impl BonusPartDamageBonus {
} */
pub(crate) fn prepare_bonuses(
bonus_q: Query<(&Parent, &WeaponBonusType, &BonusValue)>,
bonus_q: Query<(&ChildOf, &WeaponBonusType, &BonusValue)>,
clips_q: Query<&SimpleStatEffective<Clips>>,
mut effects: Effects,
mut commands: Commands,
@ -207,227 +204,227 @@ pub(crate) fn prepare_bonuses(
WeaponBonusType::Berserk => {
effects.spawn(
SimpleStatBonus::<DamageBonus>::new("beserk", value.0 / 100.0),
weapon.get(),
weapon.parent(),
);
effects.spawn(
SimpleStatBonus::<WeaponAccuracy>::new("beserk", -value.0 / 2.0 / 50.0),
weapon.get(),
weapon.parent(),
);
}
WeaponBonusType::Conserve => {
effects.spawn(
SimpleStatBonus::<AmmoControl>::new("conserve", value.0 / 100.0),
weapon.get(),
weapon.parent(),
);
}
WeaponBonusType::Expose => {
effects.spawn(
SimpleStatBonus::<CritRate>::new("expose", (value.0 / 0.5) as u16),
weapon.get(),
weapon.parent(),
);
}
WeaponBonusType::Grace => {
effects.spawn(
SimpleStatBonus::<DamageBonus>::new("grace", -value.0 / 2.0 / 100.0),
weapon.get(),
weapon.parent(),
);
effects.spawn(
SimpleStatBonus::<WeaponAccuracy>::new("grace", value.0 / 50.0),
weapon.get(),
weapon.parent(),
);
}
WeaponBonusType::Powerful => {
effects.spawn(
SimpleStatBonus::<DamageBonus>::new("powerful", value.0 / 100.0),
weapon.get(),
weapon.parent(),
);
}
WeaponBonusType::Specialist => {
effects.spawn(
SimpleStatBonus::<DamageBonus>::new("specialist", value.0 / 100.0),
weapon.get(),
weapon.parent(),
);
if let Ok(clips) = clips_q.get(weapon.get()) {
if let Ok(clips) = clips_q.get(weapon.parent()) {
effects.spawn(
SimpleStatBonus::<Clips>::new("specialist", -(clips.value as i16)),
weapon.get(),
weapon.parent(),
);
}
}
WeaponBonusType::Empower => {
commands
.spawn(TurnTriggeredEffect::Bonus {
.entity(weapon.parent())
.with_child(TurnTriggeredEffect::Bonus {
value: value.0,
bonus: TurnTriggeredBonus::Empower,
})
.set_parent(weapon.get());
});
}
WeaponBonusType::Quicken => {
commands
.spawn(TurnTriggeredEffect::Bonus {
.entity(weapon.parent())
.with_child(TurnTriggeredEffect::Bonus {
value: value.0,
bonus: TurnTriggeredBonus::Quicken,
})
.set_parent(weapon.get());
});
}
WeaponBonusType::Assassinate => {
commands
.spawn(FirstTurnEffect::Bonus {
.entity(weapon.parent())
.with_child(FirstTurnEffect::Bonus {
value: value.0,
bonus: FirstTurnBonus::Assassinate,
})
.set_parent(weapon.get());
});
}
WeaponBonusType::Blindfire => {
commands
.spawn(DamageProcEffect::MultiTurn {
.entity(weapon.parent())
.with_child(DamageProcEffect::MultiTurn {
value: value.0,
bonus: MultiTurnBonus::Blindfire,
})
.set_parent(weapon.get());
});
}
WeaponBonusType::Fury => {
commands
.spawn(DamageProcEffect::MultiTurn {
.entity(weapon.parent())
.with_child(DamageProcEffect::MultiTurn {
value: value.0,
bonus: MultiTurnBonus::Fury,
})
.set_parent(weapon.get());
});
}
WeaponBonusType::Rage => {
commands
.spawn(DamageProcEffect::MultiTurn {
.entity(weapon.parent())
.with_child(DamageProcEffect::MultiTurn {
value: value.0,
bonus: MultiTurnBonus::Rage,
})
.set_parent(weapon.get());
});
}
WeaponBonusType::DoubleTap => {
commands
.spawn(DamageProcEffect::MultiTurn {
.entity(weapon.parent())
.with_child(DamageProcEffect::MultiTurn {
value: value.0,
bonus: MultiTurnBonus::DoubleTap,
})
.set_parent(weapon.get());
});
}
WeaponBonusType::Achilles => {
commands
.spawn(PartDamageBonus::WeaponBonus {
.entity(weapon.parent())
.with_child(PartDamageBonus::WeaponBonus {
value: value.0 / 100.0,
bonus: BonusPartDamageBonus::Achilles,
})
.set_parent(weapon.get());
});
}
WeaponBonusType::Cupid => {
commands
.spawn(PartDamageBonus::WeaponBonus {
.entity(weapon.parent())
.with_child(PartDamageBonus::WeaponBonus {
value: value.0 / 100.0,
bonus: BonusPartDamageBonus::Cupid,
})
.set_parent(weapon.get());
});
}
WeaponBonusType::Crusher => {
commands
.spawn(PartDamageBonus::WeaponBonus {
.entity(weapon.parent())
.with_child(PartDamageBonus::WeaponBonus {
value: value.0 / 100.0,
bonus: BonusPartDamageBonus::Crusher,
})
.set_parent(weapon.get());
});
}
WeaponBonusType::Deadeye => {
commands
.spawn(PartDamageBonus::WeaponBonus {
.entity(weapon.parent())
.with_child(PartDamageBonus::WeaponBonus {
value: value.0 / 100.0,
bonus: BonusPartDamageBonus::Deadeye,
})
.set_parent(weapon.get());
});
}
WeaponBonusType::Throttle => {
commands
.spawn(PartDamageBonus::WeaponBonus {
.entity(weapon.parent())
.with_child(PartDamageBonus::WeaponBonus {
value: value.0 / 100.0,
bonus: BonusPartDamageBonus::Throttle,
})
.set_parent(weapon.get());
});
}
WeaponBonusType::Roshambo => {
commands
.spawn(PartDamageBonus::WeaponBonus {
.entity(weapon.parent())
.with_child(PartDamageBonus::WeaponBonus {
value: value.0 / 100.0,
bonus: BonusPartDamageBonus::Roshambo,
})
.set_parent(weapon.get());
});
}
WeaponBonusType::Cripple => {
commands
.spawn(DamageProcEffect::OpponentEffect {
.entity(weapon.parent())
.with_child(DamageProcEffect::OpponentEffect {
value: value.0,
bonus: OpponentStatusEffect::Cripple,
})
.set_parent(weapon.get());
});
}
WeaponBonusType::Demoralise => {
commands
.spawn(DamageProcEffect::OpponentEffect {
.entity(weapon.parent())
.with_child(DamageProcEffect::OpponentEffect {
value: value.0,
bonus: OpponentStatusEffect::Demoralise,
})
.set_parent(weapon.get());
});
}
WeaponBonusType::Freeze => {
commands
.spawn(DamageProcEffect::OpponentEffect {
.entity(weapon.parent())
.with_child(DamageProcEffect::OpponentEffect {
value: value.0,
bonus: OpponentStatusEffect::Freeze,
})
.set_parent(weapon.get());
});
}
WeaponBonusType::Slow => {
commands
.spawn(DamageProcEffect::OpponentEffect {
.entity(weapon.parent())
.with_child(DamageProcEffect::OpponentEffect {
value: value.0,
bonus: OpponentStatusEffect::Slow,
})
.set_parent(weapon.get());
});
}
WeaponBonusType::Toxin => {
commands
.spawn(DamageProcEffect::OpponentEffect {
.entity(weapon.parent())
.with_child(DamageProcEffect::OpponentEffect {
value: value.0,
bonus: OpponentStatusEffect::Toxin,
})
.set_parent(weapon.get());
});
}
WeaponBonusType::Weaken => {
commands
.spawn(DamageProcEffect::OpponentEffect {
.entity(weapon.parent())
.with_child(DamageProcEffect::OpponentEffect {
value: value.0,
bonus: OpponentStatusEffect::Weaken,
})
.set_parent(weapon.get());
});
}
WeaponBonusType::Wither => {
commands
.spawn(DamageProcEffect::OpponentEffect {
.entity(weapon.parent())
.with_child(DamageProcEffect::OpponentEffect {
value: value.0,
bonus: OpponentStatusEffect::Wither,
})
.set_parent(weapon.get());
});
}
WeaponBonusType::Motivate => {
commands
.spawn(DamageProcEffect::SelfEffect {
.entity(weapon.parent())
.with_child(DamageProcEffect::SelfEffect {
value: value.0,
bonus: SelfStatusEffect::Motivate,
})
.set_parent(weapon.get());
});
}
val => unimplemented!("{val:?}"),

View file

@ -1,25 +1,22 @@
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,
},
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::{
Stages,
effect::{Effects, TurnLimitedEffect},
log,
log::Logger,
Stages,
};
use self::bonus::{
@ -157,25 +154,6 @@ pub enum DamageProcEffect {
},
}
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,
@ -184,7 +162,7 @@ fn apply_passives(
&WeaponCategory,
&WeaponSlot,
Has<Japanese>,
&Parent,
&ChildOf,
)>,
player_q: Query<(&Merits, &Education, &FactionUpgrades)>,
mut effects: Effects,
@ -331,8 +309,8 @@ fn apply_passives(
weapon,
);
commands
.spawn(TurnTriggeredEffect::Mod(TurnTriggeredMod::Bipod))
.set_parent(weapon);
.entity(weapon)
.with_child(TurnTriggeredEffect::Mod(TurnTriggeredMod::Bipod));
}
WeaponMod::Tripod => {
effects.spawn(
@ -340,40 +318,38 @@ fn apply_passives(
weapon,
);
commands
.spawn(TurnTriggeredEffect::Mod(TurnTriggeredMod::Tripod))
.set_parent(weapon);
.entity(weapon)
.with_child(TurnTriggeredEffect::Mod(TurnTriggeredMod::Tripod));
}
WeaponMod::SmallLight => {
commands
.spawn(TurnTriggeredEffect::Mod(TurnTriggeredMod::SmallLight))
.set_parent(weapon);
.entity(weapon)
.with_child(TurnTriggeredEffect::Mod(TurnTriggeredMod::SmallLight));
}
WeaponMod::PrecisionLight => {
commands
.spawn(TurnTriggeredEffect::Mod(TurnTriggeredMod::PrecisionLight))
.set_parent(weapon);
.entity(weapon)
.with_child(TurnTriggeredEffect::Mod(TurnTriggeredMod::PrecisionLight));
}
WeaponMod::TacticalIlluminator => {
commands
.spawn(TurnTriggeredEffect::Mod(
TurnTriggeredMod::TacticalIlluminator,
))
.set_parent(weapon);
commands.entity(weapon).with_child(TurnTriggeredEffect::Mod(
TurnTriggeredMod::TacticalIlluminator,
));
}
WeaponMod::AdjustableTrigger => {
commands
.spawn(FirstTurnEffect::Mod(FirstTurnMod::AdjustableTrigger))
.set_parent(weapon);
.entity(weapon)
.with_child(FirstTurnEffect::Mod(FirstTurnMod::AdjustableTrigger));
}
WeaponMod::HairTrigger => {
commands
.spawn(FirstTurnEffect::Mod(FirstTurnMod::HairTrigger))
.set_parent(weapon);
.entity(weapon)
.with_child(FirstTurnEffect::Mod(FirstTurnMod::HairTrigger));
}
}
}
let (merits, education, faction) = player_q.get(player.get()).unwrap();
let (merits, education, faction) = player_q.get(player.parent()).unwrap();
let (mastery, edu_acc) = match cat {
WeaponCategory::HeavyArtillery => (
@ -445,10 +421,10 @@ fn apply_passives(
if education.bio2380 {
commands
.spawn(PartDamageBonus::Education(
.entity(weapon)
.with_child(PartDamageBonus::Education(
EducationPartDamageBonus::Bio2380,
))
.set_parent(weapon);
));
}
if mastery > 0 {
@ -490,7 +466,7 @@ fn reload_weapon(
mut weapon_q: Query<
(
Entity,
&Parent,
&ChildOf,
&mut Ammo,
&mut SimpleStatEffective<Clips>,
&SimpleStatEffective<ClipSize>,
@ -505,7 +481,7 @@ fn reload_weapon(
clips.value -= 1;
log!(logger, "reload_weapon", {
actor: player.get(),
actor: player.parent(),
weapon
});
@ -530,18 +506,17 @@ fn restore_ammo(
}
fn apply_first_turn_effects(
effect_q: Query<(&Parent, &FirstTurnEffect)>,
player_q: Query<&Parent>,
effect_q: Query<(&ChildOf, &FirstTurnEffect)>,
player_q: Query<&ChildOf>,
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());
let player = player_q.get(weapon.parent()).unwrap();
effect.spawn(&mut effects, weapon.parent(), player.parent());
}
}
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);