fixed first round effect and migrated logging
This commit is contained in:
parent
b45d04b872
commit
e7d6b74aab
10 changed files with 539 additions and 222 deletions
|
|
@ -238,7 +238,7 @@ fn generate_body_parts(
|
||||||
// TODO: Going to need this if irradiate is ever added
|
// TODO: Going to need this if irradiate is ever added
|
||||||
Immunity::Radiation => (),
|
Immunity::Radiation => (),
|
||||||
// NOTE: It's an unreleased DOT temp, so the exact effect is currently
|
// NOTE: It's an unreleased DOT temp, so the exact effect is currently
|
||||||
// unknwown
|
// unknown
|
||||||
Immunity::NerveGas => (),
|
Immunity::NerveGas => (),
|
||||||
Immunity::TearGas => {
|
Immunity::TearGas => {
|
||||||
player.insert(TempDebuffImmunity::<TearGas>::default());
|
player.insert(TempDebuffImmunity::<TearGas>::default());
|
||||||
|
|
|
||||||
|
|
@ -68,8 +68,6 @@ pub struct Effects<'w, 's> {
|
||||||
commands: Commands<'w, 's>,
|
commands: Commands<'w, 's>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Schedule effects using commands in order to avoid the need for having a separate
|
|
||||||
// `Schedule` dedicated to them.
|
|
||||||
impl<'w, 's> Effects<'w, 's> {
|
impl<'w, 's> Effects<'w, 's> {
|
||||||
pub fn spawn_and_insert<T: Component + 'static>(
|
pub fn spawn_and_insert<T: Component + 'static>(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|
@ -203,6 +201,8 @@ pub(crate) fn run_effects(world: &mut World) {
|
||||||
};
|
};
|
||||||
|
|
||||||
for entity in entities {
|
for entity in entities {
|
||||||
|
let parent = world.entity(entity).get::<Parent>().unwrap().get();
|
||||||
|
world.entity_mut(parent).remove_child(entity);
|
||||||
world.despawn(entity);
|
world.despawn(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -233,11 +233,11 @@ fn update_round_limited_effects(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
effect.turns -= 1;
|
||||||
if effect.turns == 0 {
|
if effect.turns == 0 {
|
||||||
effects.remove(entity);
|
effects.remove(entity);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
effect.turns -= 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -291,9 +291,9 @@ pub(crate) fn configure(stages: &mut Stages) {
|
||||||
stages.world.init_resource::<Clock>();
|
stages.world.init_resource::<Clock>();
|
||||||
|
|
||||||
stages.snapshot.add_systems(mark_permanent_effects);
|
stages.snapshot.add_systems(mark_permanent_effects);
|
||||||
|
stages.pre_turn.add_systems(advance_clock);
|
||||||
stages
|
stages
|
||||||
.pre_turn
|
.post_turn
|
||||||
.add_systems((advance_clock, update_round_limited_effects));
|
.add_systems((update_time_limited_effects, update_round_limited_effects));
|
||||||
stages.post_turn.add_systems(update_time_limited_effects);
|
|
||||||
stages.restore.add_systems(remove_transient_effects);
|
stages.restore.add_systems(remove_transient_effects);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
20
src/lib.rs
20
src/lib.rs
|
|
@ -149,7 +149,7 @@ impl Simulation {
|
||||||
|
|
||||||
pub fn truncate_log(&mut self) {
|
pub fn truncate_log(&mut self) {
|
||||||
let mut log = self.0.world.resource_mut::<Log>();
|
let mut log = self.0.world.resource_mut::<Log>();
|
||||||
log.entries.clear();
|
log.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_metrics(&mut self, recording: bool) {
|
pub fn set_metrics(&mut self, recording: bool) {
|
||||||
|
|
@ -158,6 +158,16 @@ impl Simulation {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn consume_metrics(&mut self) -> (Vec<dto::Counter>, Vec<dto::Histogram>) {
|
pub fn consume_metrics(&mut self) -> (Vec<dto::Counter>, Vec<dto::Histogram>) {
|
||||||
|
let entities = self.0.world.entities().len();
|
||||||
|
let components = self.0.world.components().len();
|
||||||
|
self.0
|
||||||
|
.world
|
||||||
|
.resource::<metrics::Metrics>()
|
||||||
|
.increment_counter(Entity::from_raw(0), "entities", entities.into());
|
||||||
|
self.0
|
||||||
|
.world
|
||||||
|
.resource::<metrics::Metrics>()
|
||||||
|
.increment_counter(Entity::from_raw(0), "components", components as u64);
|
||||||
metrics::consume_metrics(&self.0.world)
|
metrics::consume_metrics(&self.0.world)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -352,7 +362,7 @@ mod tests {
|
||||||
clip_size: 25,
|
clip_size: 25,
|
||||||
rate_of_fire: [3, 5],
|
rate_of_fire: [3, 5],
|
||||||
},
|
},
|
||||||
mods: Vec::default(),
|
mods: vec![WeaponMod::HairTrigger],
|
||||||
bonuses: vec![WeaponBonusInfo {
|
bonuses: vec![WeaponBonusInfo {
|
||||||
bonus: WeaponBonus::Expose,
|
bonus: WeaponBonus::Expose,
|
||||||
value: 9.0,
|
value: 9.0,
|
||||||
|
|
@ -431,17 +441,15 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn init_simulator() {
|
fn init_simulator() {
|
||||||
let mut sim = Simulation::new(attacker(), defender());
|
Simulation::new(attacker(), defender());
|
||||||
sim.run_once();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn metrics() {
|
fn metrics() {
|
||||||
let mut sim = Simulation::new(attacker(), defender());
|
let mut sim = Simulation::new(attacker(), defender());
|
||||||
sim.set_metrics(true);
|
sim.set_metrics(true);
|
||||||
for _ in 0..20 {
|
|
||||||
sim.run_once();
|
sim.run_once();
|
||||||
}
|
|
||||||
sim.consume_metrics();
|
sim.consume_metrics();
|
||||||
|
panic!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
234
src/log.rs
234
src/log.rs
|
|
@ -1,7 +1,6 @@
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use bevy_ecs::{prelude::*, system::SystemParam};
|
use bevy_ecs::{prelude::*, system::SystemParam};
|
||||||
use macros::LogMessage;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
entity_registry::EntityRegistry,
|
entity_registry::EntityRegistry,
|
||||||
|
|
@ -9,7 +8,7 @@ use crate::{
|
||||||
player::stats::{
|
player::stats::{
|
||||||
AdditiveBonus, BaselineStat, CritRate, DamageBonus, Defence, Dexterity, EffectiveStat,
|
AdditiveBonus, BaselineStat, CritRate, DamageBonus, Defence, Dexterity, EffectiveStat,
|
||||||
MultiplicativeBonus, SimpleStatBaseline, SimpleStatBonus, SimpleStatEffective,
|
MultiplicativeBonus, SimpleStatBaseline, SimpleStatBonus, SimpleStatEffective,
|
||||||
SimpleStatMarker, Speed, StatMarker, StatType, Strength, WeaponAccuracy,
|
SimpleStatMarker, Speed, StatMarker, Strength, WeaponAccuracy,
|
||||||
},
|
},
|
||||||
weapon::WeaponVerb,
|
weapon::WeaponVerb,
|
||||||
Stages,
|
Stages,
|
||||||
|
|
@ -19,14 +18,11 @@ use crate::{
|
||||||
pub struct Logging(pub bool);
|
pub struct Logging(pub bool);
|
||||||
|
|
||||||
#[derive(Event)]
|
#[derive(Event)]
|
||||||
struct LogEvent(Mutex<Option<Box<dyn LogMessage>>>);
|
struct LogEvent(Mutex<Option<DynamicLogMessage>>);
|
||||||
|
|
||||||
impl<T> From<T> for LogEvent
|
impl From<DynamicLogMessage> for LogEvent {
|
||||||
where
|
fn from(value: DynamicLogMessage) -> Self {
|
||||||
T: LogMessage,
|
LogEvent(Mutex::new(Some(value)))
|
||||||
{
|
|
||||||
fn from(value: T) -> Self {
|
|
||||||
Self(Mutex::new(Some(Box::new(value))))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,6 +49,8 @@ pub enum LogValue<'a> {
|
||||||
Debug(&'a (dyn std::fmt::Debug + Send + Sync)),
|
Debug(&'a (dyn std::fmt::Debug + Send + Sync)),
|
||||||
Player(Entity),
|
Player(Entity),
|
||||||
Weapon(Entity),
|
Weapon(Entity),
|
||||||
|
Array(Vec<LogValue<'a>>),
|
||||||
|
Map(Vec<(&'static str, LogValue<'a>)>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<String> for LogValue<'a> {
|
impl<'a> From<String> for LogValue<'a> {
|
||||||
|
|
@ -61,6 +59,12 @@ impl<'a> From<String> for LogValue<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a str> for LogValue<'static> {
|
||||||
|
fn from(value: &'a str) -> Self {
|
||||||
|
Self::String(value.to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> From<f32> for LogValue<'a> {
|
impl<'a> From<f32> for LogValue<'a> {
|
||||||
fn from(value: f32) -> Self {
|
fn from(value: f32) -> Self {
|
||||||
Self::Float(value)
|
Self::Float(value)
|
||||||
|
|
@ -103,6 +107,24 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, V> From<Vec<V>> for LogValue<'a>
|
||||||
|
where
|
||||||
|
V: Into<LogValue<'a>>,
|
||||||
|
{
|
||||||
|
fn from(value: Vec<V>) -> Self {
|
||||||
|
LogValue::Array(value.into_iter().map(Into::into).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, V> From<Vec<(&'static str, V)>> for LogValue<'a>
|
||||||
|
where
|
||||||
|
V: Into<LogValue<'a>>,
|
||||||
|
{
|
||||||
|
fn from(value: Vec<(&'static str, V)>) -> Self {
|
||||||
|
LogValue::Map(value.into_iter().map(|(k, v)| (k, v.into())).collect())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "json")]
|
#[cfg(feature = "json")]
|
||||||
impl<'a> LogValue<'a> {
|
impl<'a> LogValue<'a> {
|
||||||
fn to_value(&self, entity_registry: &EntityRegistry) -> serde_json::Value {
|
fn to_value(&self, entity_registry: &EntityRegistry) -> serde_json::Value {
|
||||||
|
|
@ -125,11 +147,19 @@ impl<'a> LogValue<'a> {
|
||||||
LogValue::Entity(id) => {
|
LogValue::Entity(id) => {
|
||||||
serde_json::to_value(entity_registry.0.get(id).unwrap()).unwrap()
|
serde_json::to_value(entity_registry.0.get(id).unwrap()).unwrap()
|
||||||
}
|
}
|
||||||
|
LogValue::Array(items) => serde_json::Value::Array(
|
||||||
|
items.iter().map(|v| v.to_value(entity_registry)).collect(),
|
||||||
|
),
|
||||||
|
LogValue::Map(map) => serde_json::Value::Object(
|
||||||
|
map.iter()
|
||||||
|
.map(|(k, v)| ((*k).to_owned(), v.to_value(entity_registry)))
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait LogMessage: Send + Sync + 'static {
|
trait LogMessage: Send + Sync + 'static {
|
||||||
fn tag(&self) -> &'static str;
|
fn tag(&self) -> &'static str;
|
||||||
|
|
||||||
fn entries(&self) -> Vec<(&'static str, LogValue<'_>)>;
|
fn entries(&self) -> Vec<(&'static str, LogValue<'_>)>;
|
||||||
|
|
@ -137,7 +167,7 @@ pub trait LogMessage: Send + Sync + 'static {
|
||||||
|
|
||||||
#[derive(Resource, Default)]
|
#[derive(Resource, Default)]
|
||||||
pub struct Log {
|
pub struct Log {
|
||||||
pub entries: Vec<Box<dyn LogMessage>>,
|
entries: Vec<DynamicLogMessage>,
|
||||||
|
|
||||||
pub expanded: bool,
|
pub expanded: bool,
|
||||||
}
|
}
|
||||||
|
|
@ -158,6 +188,10 @@ impl Log {
|
||||||
).collect::<Vec<_>>()
|
).collect::<Vec<_>>()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.entries.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Log {
|
impl std::fmt::Display for Log {
|
||||||
|
|
@ -184,6 +218,7 @@ impl std::fmt::Display for Log {
|
||||||
LogValue::Player(id) | LogValue::Weapon(id) | LogValue::Entity(id) => {
|
LogValue::Player(id) | LogValue::Weapon(id) | LogValue::Entity(id) => {
|
||||||
write!(f, "{:?}", id)?
|
write!(f, "{:?}", id)?
|
||||||
}
|
}
|
||||||
|
LogValue::Array(_) | LogValue::Map(_) => (),
|
||||||
};
|
};
|
||||||
|
|
||||||
if fields.peek().is_some() {
|
if fields.peek().is_some() {
|
||||||
|
|
@ -391,10 +426,9 @@ pub struct Logger<'w> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'w> Logger<'w> {
|
impl<'w> Logger<'w> {
|
||||||
pub fn log<B, M>(&mut self, body: B)
|
pub fn log<B>(&mut self, body: B)
|
||||||
where
|
where
|
||||||
B: FnOnce() -> M,
|
B: FnOnce() -> DynamicLogMessage,
|
||||||
M: LogMessage,
|
|
||||||
{
|
{
|
||||||
if self.logging.0 {
|
if self.logging.0 {
|
||||||
self.event_writer.send(body().into());
|
self.event_writer.send(body().into());
|
||||||
|
|
@ -408,100 +442,10 @@ fn logging_enabled(logging: Res<Logging>) -> bool {
|
||||||
|
|
||||||
fn append_log_messages(mut events: EventReader<LogEvent>, mut log: ResMut<Log>) {
|
fn append_log_messages(mut events: EventReader<LogEvent>, mut log: ResMut<Log>) {
|
||||||
for event in events.read() {
|
for event in events.read() {
|
||||||
log.entries.push(event.0.lock().unwrap().take().unwrap());
|
if let Some(entry) = event.0.lock().unwrap().take() {
|
||||||
|
log.entries.push(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(macros::LogMessage)]
|
|
||||||
struct StatChange {
|
|
||||||
#[log(player)]
|
|
||||||
target: Entity,
|
|
||||||
#[log(debug)]
|
|
||||||
stat: StatType,
|
|
||||||
|
|
||||||
#[log(debug)]
|
|
||||||
effects_add: Vec<(&'static str, f32)>,
|
|
||||||
|
|
||||||
#[log(debug)]
|
|
||||||
effects_mult: Vec<(&'static str, f32)>,
|
|
||||||
|
|
||||||
baseline: f32,
|
|
||||||
|
|
||||||
effective: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn log_stat_changes<Stat: StatMarker>(
|
|
||||||
stat_q: Query<
|
|
||||||
(Entity, &BaselineStat<Stat>, &EffectiveStat<Stat>, &Children),
|
|
||||||
Changed<EffectiveStat<Stat>>,
|
|
||||||
>,
|
|
||||||
add_q: Query<&AdditiveBonus<Stat>>,
|
|
||||||
mult_q: Query<&MultiplicativeBonus<Stat>>,
|
|
||||||
mut logger: Logger,
|
|
||||||
) {
|
|
||||||
for (player, baseline, effective, children) in stat_q.iter() {
|
|
||||||
let effects_add = add_q
|
|
||||||
.iter_many(children.get())
|
|
||||||
.map(|eff| (eff.label, eff.value))
|
|
||||||
.collect();
|
|
||||||
let effects_mult = mult_q
|
|
||||||
.iter_many(children.get())
|
|
||||||
.map(|eff| (eff.label, eff.value))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
logger.log(|| StatChange {
|
|
||||||
target: player,
|
|
||||||
stat: Stat::stat_type(),
|
|
||||||
effects_add,
|
|
||||||
effects_mult,
|
|
||||||
baseline: baseline.value,
|
|
||||||
effective: effective.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn log_simple_stat_changes<Stat: SimpleStatMarker>(
|
|
||||||
stat_q: Query<
|
|
||||||
(
|
|
||||||
Entity,
|
|
||||||
&SimpleStatBaseline<Stat>,
|
|
||||||
&SimpleStatEffective<Stat>,
|
|
||||||
&Children,
|
|
||||||
),
|
|
||||||
Changed<SimpleStatEffective<Stat>>,
|
|
||||||
>,
|
|
||||||
bonus_q: Query<&SimpleStatBonus<Stat>>,
|
|
||||||
mut logger: Logger,
|
|
||||||
) where
|
|
||||||
Stat::ValueType: Into<LogValue<'static>>,
|
|
||||||
Stat::BonusType: std::fmt::Debug,
|
|
||||||
{
|
|
||||||
#[derive(LogMessage)]
|
|
||||||
struct AppliedBonus {
|
|
||||||
#[log(debug)]
|
|
||||||
target: Entity,
|
|
||||||
#[log(display)]
|
|
||||||
stat: &'static str,
|
|
||||||
baseline: LogValue<'static>,
|
|
||||||
effective: LogValue<'static>,
|
|
||||||
#[log(debug)]
|
|
||||||
bonuses: Vec<(&'static str, String)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
for (target, baseline, effective, children) in stat_q.iter() {
|
|
||||||
let bonuses = bonus_q
|
|
||||||
.iter_many(children.get())
|
|
||||||
.map(|eff| (eff.label, format!("{:?}", eff.value)))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
logger.log(|| AppliedBonus {
|
|
||||||
target,
|
|
||||||
stat: std::any::type_name::<Stat>(),
|
|
||||||
baseline: baseline.value.into(),
|
|
||||||
effective: effective.value.into(),
|
|
||||||
bonuses,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct DynamicLogMessage {
|
pub struct DynamicLogMessage {
|
||||||
|
|
@ -547,18 +491,94 @@ macro_rules! log_values {
|
||||||
(@ { $(,)* $($out:expr),* } $label:ident: %$val:expr, $($rest:tt)*) => {
|
(@ { $(,)* $($out:expr),* } $label:ident: %$val:expr, $($rest:tt)*) => {
|
||||||
$crate::log_values!(@ { $($out),*, (stringify!($label),$crate::log::LogValue::String(format!("{}",$val))) } $($rest)*)
|
$crate::log_values!(@ { $($out),*, (stringify!($label),$crate::log::LogValue::String(format!("{}",$val))) } $($rest)*)
|
||||||
};
|
};
|
||||||
|
(@ { $(,)* $($out:expr),* } $value:ident, $($rest:tt)*) => {
|
||||||
|
$crate::log_values!(@ { $($out),*, (stringify!($value),$value.into()) } $($rest)*)
|
||||||
|
};
|
||||||
|
(@ { $(,)* $($out:expr),* } ?$value:ident, $($rest:tt)*) => {
|
||||||
|
$crate::log_values!(@ { $($out),*, (stringify!($value),$crate::log::LogValue::String(format!("{:?}",$val))) } $($rest)*)
|
||||||
|
};
|
||||||
|
(@ { $(,)* $($out:expr),* } %$value:ident, $($rest:tt)*) => {
|
||||||
|
$crate::log_values!(@ { $($out),*, (stringify!($value),$crate::log::LogValue::String(format!("{}",$val))) } $($rest)*)
|
||||||
|
};
|
||||||
|
|
||||||
($($args:tt)* ) => {
|
($($args:tt)* ) => {
|
||||||
$crate::log_values!(@ { } $($args)*,)
|
$crate::log_values!(@ { } $($args)*,)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn log_stat_changes<Stat: StatMarker>(
|
||||||
|
stat_q: Query<
|
||||||
|
(Entity, &BaselineStat<Stat>, &EffectiveStat<Stat>, &Children),
|
||||||
|
Changed<EffectiveStat<Stat>>,
|
||||||
|
>,
|
||||||
|
add_q: Query<&AdditiveBonus<Stat>>,
|
||||||
|
mult_q: Query<&MultiplicativeBonus<Stat>>,
|
||||||
|
mut logger: Logger,
|
||||||
|
) {
|
||||||
|
for (player, baseline, effective, children) in stat_q.iter() {
|
||||||
|
let effects_add: Vec<_> = add_q
|
||||||
|
.iter_many(children.get())
|
||||||
|
.map(|bonus| (bonus.label, 100.0 * bonus.value))
|
||||||
|
.collect();
|
||||||
|
let effects_mult: Vec<_> = mult_q
|
||||||
|
.iter_many(children.get())
|
||||||
|
.map(|bonus| (bonus.label, 100.0 * bonus.value))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
log!(logger, "stat_change", {
|
||||||
|
target: player,
|
||||||
|
stat: ?Stat::stat_type(),
|
||||||
|
effects_add,
|
||||||
|
effects_mult,
|
||||||
|
baseline: baseline.value,
|
||||||
|
effective: effective.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log_simple_stat_changes<Stat: SimpleStatMarker>(
|
||||||
|
stat_q: Query<
|
||||||
|
(
|
||||||
|
Entity,
|
||||||
|
&SimpleStatBaseline<Stat>,
|
||||||
|
&SimpleStatEffective<Stat>,
|
||||||
|
&Children,
|
||||||
|
),
|
||||||
|
Changed<SimpleStatEffective<Stat>>,
|
||||||
|
>,
|
||||||
|
bonus_q: Query<&SimpleStatBonus<Stat>>,
|
||||||
|
mut logger: Logger,
|
||||||
|
) where
|
||||||
|
Stat::ValueType: Into<LogValue<'static>>,
|
||||||
|
Stat::BonusType: Into<LogValue<'static>>,
|
||||||
|
{
|
||||||
|
for (target, baseline, effective, children) in stat_q.iter() {
|
||||||
|
let bonuses: Vec<_> = bonus_q
|
||||||
|
.iter_many(children.get())
|
||||||
|
.map(|bonus| (bonus.label, Stat::denormalise_bonus(bonus.value)))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
log!(logger, "applied_bonus", {
|
||||||
|
target,
|
||||||
|
stat: std::any::type_name::<Stat>(),
|
||||||
|
baseline: Stat::denormalise_value(baseline.value),
|
||||||
|
effective: Stat::denormalise_value(effective.value),
|
||||||
|
bonuses
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn configure(stages: &mut Stages) {
|
pub(crate) fn configure(stages: &mut Stages) {
|
||||||
stages.world.insert_resource(Log::default());
|
stages.world.insert_resource(Log::default());
|
||||||
stages.world.insert_resource(Logging(false));
|
stages.world.insert_resource(Logging(false));
|
||||||
stages.add_event::<LogEvent>();
|
stages.add_event::<LogEvent>();
|
||||||
|
|
||||||
stages.post_turn.add_systems(append_log_messages);
|
stages
|
||||||
|
.post_turn
|
||||||
|
.add_systems(append_log_messages.run_if(logging_enabled));
|
||||||
|
stages
|
||||||
|
.post_fight
|
||||||
|
.add_systems(append_log_messages.run_if(logging_enabled));
|
||||||
stages.turn.add_systems(
|
stages.turn.add_systems(
|
||||||
(
|
(
|
||||||
log_stat_changes::<Strength>,
|
log_stat_changes::<Strength>,
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@ use bevy_ecs::prelude::*;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
effect::Effects,
|
effect::Effects,
|
||||||
player::stats::{
|
player::{
|
||||||
AdditiveBonus, CritRate, Defence, Dexterity, SimpleStatBonus, Speed, Strength,
|
stats::{AdditiveBonus, CritRate, Defence, Dexterity, SimpleStatBonus, Speed, Strength},
|
||||||
|
BodyPart,
|
||||||
},
|
},
|
||||||
Stages,
|
Stages,
|
||||||
};
|
};
|
||||||
|
|
@ -191,6 +192,20 @@ pub enum DrugCooldown {
|
||||||
Vicodin,
|
Vicodin,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum EducationPartDamageBonus {
|
||||||
|
Bio2380,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl EducationPartDamageBonus {
|
||||||
|
pub fn dmg_bonus(self, part: BodyPart) -> Option<f32> {
|
||||||
|
match part {
|
||||||
|
BodyPart::Throat => Some(0.10),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Bundle, Default)]
|
#[derive(Bundle, Default)]
|
||||||
pub(crate) struct PassiveBundle {
|
pub(crate) struct PassiveBundle {
|
||||||
pub merits: Merits,
|
pub merits: Merits,
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,9 @@ use crate::{
|
||||||
log,
|
log,
|
||||||
log::Logger,
|
log::Logger,
|
||||||
metrics::Metrics,
|
metrics::Metrics,
|
||||||
passives::{Education, FactionUpgrades, Merits},
|
passives::{EducationPartDamageBonus, FactionUpgrades, Merits},
|
||||||
weapon::{
|
weapon::{
|
||||||
bonus::MultiTurnBonus,
|
bonus::{BonusPartDamageBonus, MultiTurnBonus},
|
||||||
temp::{NonTargeted, Uses},
|
temp::{NonTargeted, Uses},
|
||||||
Ammo, DamageProcEffect, DamageStat, NeedsReload, RateOfFire, TurnTriggeredEffect, Usable,
|
Ammo, DamageProcEffect, DamageStat, NeedsReload, RateOfFire, TurnTriggeredEffect, Usable,
|
||||||
Weapon, WeaponSlot,
|
Weapon, WeaponSlot,
|
||||||
|
|
@ -172,6 +172,24 @@ pub enum FightEndType {
|
||||||
Loss,
|
Loss,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub enum PartDamageBonus {
|
||||||
|
Education(EducationPartDamageBonus),
|
||||||
|
WeaponBonus {
|
||||||
|
value: f32,
|
||||||
|
bonus: BonusPartDamageBonus,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartDamageBonus {
|
||||||
|
pub fn dmg_bonus(&self, part: BodyPart) -> Option<f32> {
|
||||||
|
match self {
|
||||||
|
Self::Education(edu) => edu.dmg_bonus(part),
|
||||||
|
Self::WeaponBonus { value, bonus } => bonus.dmg_bonus(part, *value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Bundle)]
|
#[derive(Bundle)]
|
||||||
pub struct PlayerBundle {
|
pub struct PlayerBundle {
|
||||||
pub name: Name,
|
pub name: Name,
|
||||||
|
|
@ -341,7 +359,6 @@ pub fn use_damaging_weapon(
|
||||||
&SimpleStatEffective<CritRate>,
|
&SimpleStatEffective<CritRate>,
|
||||||
&SimpleStatEffective<WeaponAccuracy>,
|
&SimpleStatEffective<WeaponAccuracy>,
|
||||||
&SimpleStatEffective<DamageBonus>,
|
&SimpleStatEffective<DamageBonus>,
|
||||||
&Education,
|
|
||||||
Has<Attacker>,
|
Has<Attacker>,
|
||||||
),
|
),
|
||||||
(With<Player>, With<Current>),
|
(With<Player>, With<Current>),
|
||||||
|
|
@ -357,7 +374,7 @@ pub fn use_damaging_weapon(
|
||||||
With<CurrentTarget>,
|
With<CurrentTarget>,
|
||||||
>,
|
>,
|
||||||
armour_q: Query<&armour::ArmourBodyPart>,
|
armour_q: Query<&armour::ArmourBodyPart>,
|
||||||
damage_proc_q: Query<&DamageProcEffect>,
|
(damage_proc_q, part_bonus_q): (Query<&DamageProcEffect>, Query<&PartDamageBonus>),
|
||||||
(mut ammo_q, mut temp_q): (
|
(mut ammo_q, mut temp_q): (
|
||||||
Query<(
|
Query<(
|
||||||
&mut Ammo,
|
&mut Ammo,
|
||||||
|
|
@ -367,18 +384,19 @@ pub fn use_damaging_weapon(
|
||||||
)>,
|
)>,
|
||||||
Query<&mut Uses>,
|
Query<&mut Uses>,
|
||||||
),
|
),
|
||||||
(mut logger, mut commands, dmg_spread, metrics): (
|
(mut logger, mut commands, dmg_spread, metrics, mut effects): (
|
||||||
Logger,
|
Logger,
|
||||||
Commands,
|
Commands,
|
||||||
Local<DamageSpread>,
|
Local<DamageSpread>,
|
||||||
Res<Metrics>,
|
Res<Metrics>,
|
||||||
|
Effects,
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
let Ok((weapon, w_dmg, acc, dmg_bonus, crit, children, non_targeted)) = weapon_q.get_single()
|
let Ok((weapon, w_dmg, acc, dmg_bonus, crit, children, non_targeted)) = weapon_q.get_single()
|
||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let (player, player_spd, player_str, player_crit, acc_bonus, p_dmg_bonus, edu, attacker) =
|
let (player, player_spd, player_str, player_crit, acc_bonus, p_dmg_bonus, attacker) =
|
||||||
player_q.single();
|
player_q.single();
|
||||||
let (target, target_dex, target_def, armour_parts, mut health) = target_q.single_mut();
|
let (target, target_dex, target_def, armour_parts, mut health) = target_q.single_mut();
|
||||||
|
|
||||||
|
|
@ -457,6 +475,7 @@ pub fn use_damaging_weapon(
|
||||||
actor: player,
|
actor: player,
|
||||||
recipient: target,
|
recipient: target,
|
||||||
rounds: rounds,
|
rounds: rounds,
|
||||||
|
hit_chance: hit_chance,
|
||||||
});
|
});
|
||||||
metrics.increment_counter(player, "miss", 1);
|
metrics.increment_counter(player, "miss", 1);
|
||||||
metrics.increment_counter(weapon, "miss", 1);
|
metrics.increment_counter(weapon, "miss", 1);
|
||||||
|
|
@ -494,13 +513,17 @@ pub fn use_damaging_weapon(
|
||||||
let piece = rng.sample(armour_parts);
|
let piece = rng.sample(armour_parts);
|
||||||
let armour_mitigation = piece.map_or(0.0, |p| p.armour_value);
|
let armour_mitigation = piece.map_or(0.0, |p| p.armour_value);
|
||||||
|
|
||||||
// NOTE: The beta distribution is defined on [0,1], so we rescale here
|
// NOTE: Proxima's simulator seems to have the damage spread be between 95% and 105%,
|
||||||
|
// but from my brief tests it seems that 100% to 110% lines up better, at least for h2h.
|
||||||
|
// It might be better to revivisit this detail later down the line and run more tests.
|
||||||
let dmg_spread = rng.sample(dmg_spread.0) / 10.0 + 1.0;
|
let dmg_spread = rng.sample(dmg_spread.0) / 10.0 + 1.0;
|
||||||
|
|
||||||
let mut dmg_bonus = dmg_bonus + p_dmg_bonus;
|
let mut dmg_bonus = dmg_bonus + p_dmg_bonus;
|
||||||
|
|
||||||
if edu.bio2380 && body_part == BodyPart::Throat {
|
for part_bonus in part_bonus_q.iter_many(children.get()) {
|
||||||
dmg_bonus.value += 0.10;
|
if let Some(bonus) = part_bonus.dmg_bonus(body_part) {
|
||||||
|
dmg_bonus.value += bonus;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: special ammo
|
// TODO: special ammo
|
||||||
|
|
@ -519,11 +542,13 @@ pub fn use_damaging_weapon(
|
||||||
|
|
||||||
if dmg > 0 {
|
if dmg > 0 {
|
||||||
for effect in damage_proc_q.iter_many(children.get()) {
|
for effect in damage_proc_q.iter_many(children.get()) {
|
||||||
match effect {
|
match *effect {
|
||||||
DamageProcEffect::MultiTurn { value, bonus }
|
DamageProcEffect::MultiTurn { value, bonus } => {
|
||||||
if multi_attack_proc.is_none() =>
|
if multi_attack_proc.is_some() {
|
||||||
{
|
continue;
|
||||||
if rng.gen_bool((*value / 100.0) as f64) {
|
}
|
||||||
|
let chance = (value / 100.0) as f64;
|
||||||
|
if chance > 1.0 || rng.gen_bool(chance) {
|
||||||
match bonus {
|
match bonus {
|
||||||
MultiTurnBonus::Blindfire => {
|
MultiTurnBonus::Blindfire => {
|
||||||
multi_attack_proc = Some(MultiAttack::Blindfire)
|
multi_attack_proc = Some(MultiAttack::Blindfire)
|
||||||
|
|
@ -545,7 +570,18 @@ pub fn use_damaging_weapon(
|
||||||
metrics.increment_counter(weapon, bonus.counter_label(), 1);
|
metrics.increment_counter(weapon, bonus.counter_label(), 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => (),
|
DamageProcEffect::SelfEffect { value, bonus } => {
|
||||||
|
let chance = (value / 100.0) as f64;
|
||||||
|
if chance > 1.0 || rng.gen_bool(chance) {
|
||||||
|
bonus.spawn(player, &mut effects);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DamageProcEffect::OpponentEffect { value, bonus } => {
|
||||||
|
let chance = (value / 100.0) as f64;
|
||||||
|
if chance > 1.0 || rng.gen_bool(chance) {
|
||||||
|
bonus.spawn(target, &mut effects, &mut rng.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -561,6 +597,7 @@ pub fn use_damaging_weapon(
|
||||||
part: %body_part,
|
part: %body_part,
|
||||||
part_mult: mult,
|
part_mult: mult,
|
||||||
dmg: dmg,
|
dmg: dmg,
|
||||||
|
rounds: rounds,
|
||||||
health_before: health_before,
|
health_before: health_before,
|
||||||
health_after: health.value,
|
health_after: health.value,
|
||||||
dmg_spread: dmg_spread,
|
dmg_spread: dmg_spread,
|
||||||
|
|
@ -584,7 +621,7 @@ pub fn use_damaging_weapon(
|
||||||
FightEndType::Victory
|
FightEndType::Victory
|
||||||
} else {
|
} else {
|
||||||
FightEndType::Loss
|
FightEndType::Loss
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
metrics.increment_counter(player, "victory", 1);
|
metrics.increment_counter(player, "victory", 1);
|
||||||
}
|
}
|
||||||
|
|
@ -613,7 +650,7 @@ pub fn use_damaging_weapon(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_stalemate(
|
pub fn check_stalemate(
|
||||||
current_q: Query<(Entity, &CombatTurns, Option<&Attacker>), (With<Current>, With<Player>)>,
|
current_q: Query<(Entity, &CombatTurns, Has<Attacker>), (With<Current>, With<Player>)>,
|
||||||
target_q: Query<Entity, With<CurrentTarget>>,
|
target_q: Query<Entity, With<CurrentTarget>>,
|
||||||
other_attackers_q: Query<(), (With<Attacker>, Without<Current>)>,
|
other_attackers_q: Query<(), (With<Attacker>, Without<Current>)>,
|
||||||
mut state: ResMut<FightStatus>,
|
mut state: ResMut<FightStatus>,
|
||||||
|
|
@ -622,14 +659,14 @@ pub fn check_stalemate(
|
||||||
metrics: Res<Metrics>,
|
metrics: Res<Metrics>,
|
||||||
) {
|
) {
|
||||||
let (current, current_turns, attacker) = current_q.single();
|
let (current, current_turns, attacker) = current_q.single();
|
||||||
if *state == FightStatus::Ongoing && current_turns.0 >= 25 && attacker.is_some() {
|
if *state == FightStatus::Ongoing && current_turns.0 >= 25 && attacker {
|
||||||
commands.entity(current).insert(Defeated);
|
commands.entity(current).insert(Defeated);
|
||||||
let target = target_q.single();
|
let target = target_q.single();
|
||||||
|
|
||||||
log!(logger, "fight_end", {
|
log!(logger, "fight_end", {
|
||||||
actor: current,
|
actor: current,
|
||||||
recipient: target,
|
recipient: target,
|
||||||
fight_end_type: %FightEndType::Stalemate
|
fight_end_type: %FightEndType::Stalemate,
|
||||||
});
|
});
|
||||||
metrics.increment_counter(current, "stalemate", 1);
|
metrics.increment_counter(current, "stalemate", 1);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,12 @@ pub trait SimpleStatMarker: Send + Sync + 'static {
|
||||||
|
|
||||||
fn apply_bonus(value: Self::ValueType, bonus: Self::BonusType) -> Self::ValueType;
|
fn apply_bonus(value: Self::ValueType, bonus: Self::BonusType) -> Self::ValueType;
|
||||||
fn revert_bonus(value: Self::ValueType, bonus: Self::BonusType) -> Self::ValueType;
|
fn revert_bonus(value: Self::ValueType, bonus: Self::BonusType) -> Self::ValueType;
|
||||||
|
fn denormalise_value(value: Self::ValueType) -> Self::ValueType {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
fn denormalise_bonus(value: Self::BonusType) -> Self::BonusType {
|
||||||
|
value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
|
|
@ -143,6 +149,12 @@ impl SimpleStatMarker for AmmoControl {
|
||||||
fn revert_bonus(value: Self::ValueType, bonus: Self::BonusType) -> Self::ValueType {
|
fn revert_bonus(value: Self::ValueType, bonus: Self::BonusType) -> Self::ValueType {
|
||||||
value - bonus
|
value - bonus
|
||||||
}
|
}
|
||||||
|
fn denormalise_value(value: Self::ValueType) -> Self::ValueType {
|
||||||
|
value * 100.0
|
||||||
|
}
|
||||||
|
fn denormalise_bonus(value: Self::BonusType) -> Self::BonusType {
|
||||||
|
value * 100.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
@ -157,6 +169,12 @@ impl SimpleStatMarker for DamageBonus {
|
||||||
fn revert_bonus(value: Self::ValueType, bonus: Self::BonusType) -> Self::ValueType {
|
fn revert_bonus(value: Self::ValueType, bonus: Self::BonusType) -> Self::ValueType {
|
||||||
value - bonus
|
value - bonus
|
||||||
}
|
}
|
||||||
|
fn denormalise_value(value: Self::ValueType) -> Self::ValueType {
|
||||||
|
value * 100.0
|
||||||
|
}
|
||||||
|
fn denormalise_bonus(value: Self::BonusType) -> Self::BonusType {
|
||||||
|
value * 100.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
@ -171,6 +189,12 @@ impl SimpleStatMarker for WeaponAccuracy {
|
||||||
fn revert_bonus(value: Self::ValueType, bonus: Self::BonusType) -> Self::ValueType {
|
fn revert_bonus(value: Self::ValueType, bonus: Self::BonusType) -> Self::ValueType {
|
||||||
value - bonus
|
value - bonus
|
||||||
}
|
}
|
||||||
|
fn denormalise_value(value: Self::ValueType) -> Self::ValueType {
|
||||||
|
value * 50.0 + 50.0
|
||||||
|
}
|
||||||
|
fn denormalise_bonus(value: Self::BonusType) -> Self::BonusType {
|
||||||
|
value * 50.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
@ -490,7 +514,7 @@ fn apply_simple_stat_bonus<Stat: SimpleStatMarker>(
|
||||||
effect_q: Query<(&SimpleStatBonus<Stat>, &Parent)>,
|
effect_q: Query<(&SimpleStatBonus<Stat>, &Parent)>,
|
||||||
mut stat_q: Query<&mut SimpleStatEffective<Stat>>,
|
mut stat_q: Query<&mut SimpleStatEffective<Stat>>,
|
||||||
) {
|
) {
|
||||||
for (bonus, target) in effect_q.iter_many(entities) {
|
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.get()).unwrap();
|
||||||
effective.value = Stat::apply_bonus(effective.value, bonus.value);
|
effective.value = Stat::apply_bonus(effective.value, bonus.value);
|
||||||
}
|
}
|
||||||
|
|
@ -568,5 +592,6 @@ pub(crate) fn configure(stages: &mut Stages) {
|
||||||
register_simple_stat_effects::<DamageBonus>(stages);
|
register_simple_stat_effects::<DamageBonus>(stages);
|
||||||
register_simple_stat_effects::<WeaponAccuracy>(stages);
|
register_simple_stat_effects::<WeaponAccuracy>(stages);
|
||||||
register_simple_stat_effects::<ClipSize>(stages);
|
register_simple_stat_effects::<ClipSize>(stages);
|
||||||
|
register_simple_stat_effects::<Clips>(stages);
|
||||||
register_simple_stat_effects::<Health>(stages);
|
register_simple_stat_effects::<Health>(stages);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
use std::{collections::VecDeque, marker::PhantomData};
|
use std::{collections::VecDeque, marker::PhantomData};
|
||||||
|
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use macros::LogMessage;
|
|
||||||
use rand::Rng as _;
|
use rand::Rng as _;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
effect::{Effects, TimeLimitedEffect},
|
effect::{Effects, TimeLimitedEffect},
|
||||||
hierarchy::{HierarchyBuilder, Parent},
|
hierarchy::{HierarchyBuilder, Parent},
|
||||||
|
log,
|
||||||
log::Logger,
|
log::Logger,
|
||||||
weapon::temp::AssociatedWeapon,
|
weapon::temp::AssociatedWeapon,
|
||||||
Rng, Stages,
|
Rng, Stages,
|
||||||
|
|
@ -117,7 +117,7 @@ impl DebuffingTempMarker for Sand {
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
struct LinkedComponents<const N: usize>([Entity; N]);
|
struct LinkedComponents<const N: usize>([Entity; N]);
|
||||||
|
|
||||||
trait Stats<const N: usize> {
|
pub trait Stats<const N: usize> {
|
||||||
fn spawn_additive_effects(
|
fn spawn_additive_effects(
|
||||||
effects: &mut Effects,
|
effects: &mut Effects,
|
||||||
target: Entity,
|
target: Entity,
|
||||||
|
|
@ -160,15 +160,18 @@ impl_n_stats!(2, A, B);
|
||||||
impl_n_stats!(3, A, B, C);
|
impl_n_stats!(3, A, B, C);
|
||||||
impl_n_stats!(4, A, B, C, D);
|
impl_n_stats!(4, A, B, C, D);
|
||||||
|
|
||||||
trait AdditiveStatusEffectMarker<const N: usize>: Send + Sync + 'static {
|
// TODO: the const generic arguably isn't worth the trouble and it might be better to remove it
|
||||||
|
pub trait AdditiveStatusEffectMarker<const N: usize>: Send + Sync + 'static {
|
||||||
type AffectedStats: Stats<N>;
|
type AffectedStats: Stats<N>;
|
||||||
fn max_stack() -> usize;
|
fn max_stack() -> usize;
|
||||||
fn factor() -> f32;
|
fn factor() -> f32;
|
||||||
fn duration() -> f32;
|
fn duration() -> f32;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: instead of tracking it in the status effect itself, add generic
|
||||||
|
// `StatusEffectEffectiveness` and `StatusEffectExtraDuration` components
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
struct AdditiveStatusEffect<const N: usize, M>
|
pub struct AdditiveStatusEffect<const N: usize, M>
|
||||||
where
|
where
|
||||||
M: AdditiveStatusEffectMarker<N>,
|
M: AdditiveStatusEffectMarker<N>,
|
||||||
{
|
{
|
||||||
|
|
@ -187,16 +190,6 @@ impl<const N: usize, M: AdditiveStatusEffectMarker<N>> Default for AdditiveStatu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const N: usize, M: AdditiveStatusEffectMarker<N>> AdditiveStatusEffect<N, M> {
|
|
||||||
pub fn new(extra_effectiveness: f32, extra_duration: f32) -> Self {
|
|
||||||
Self {
|
|
||||||
marker: PhantomData,
|
|
||||||
extra_effectiveness,
|
|
||||||
extra_duration,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Withered;
|
pub struct Withered;
|
||||||
|
|
||||||
impl AdditiveStatusEffectMarker<1> for Withered {
|
impl AdditiveStatusEffectMarker<1> for Withered {
|
||||||
|
|
@ -368,8 +361,14 @@ fn apply_additive_status_effect<const N: usize, M: AdditiveStatusEffectMarker<N>
|
||||||
mut parent_q: Query<Option<&mut StatusEffectStack<M>>>,
|
mut parent_q: Query<Option<&mut StatusEffectStack<M>>>,
|
||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
mut effects: Effects,
|
mut effects: Effects,
|
||||||
|
mut logger: Logger,
|
||||||
) {
|
) {
|
||||||
for (entity, player, effect) in effect_q.iter_many(entities) {
|
for (entity, player, effect) in effect_q.iter_many(entities) {
|
||||||
|
log!(logger, "apply_status_effect", {
|
||||||
|
recipient: player.get(),
|
||||||
|
effect: std::any::type_name::<M>(),
|
||||||
|
});
|
||||||
|
|
||||||
let stack = parent_q.get_mut(player.get()).unwrap();
|
let stack = parent_q.get_mut(player.get()).unwrap();
|
||||||
|
|
||||||
let new_effects = <M::AffectedStats as Stats<N>>::spawn_additive_effects(
|
let new_effects = <M::AffectedStats as Stats<N>>::spawn_additive_effects(
|
||||||
|
|
@ -434,24 +433,13 @@ fn apply_temp_debuff_effect<Temp: DebuffingTempMarker>(
|
||||||
(mut commands, mut effects): (Commands, Effects),
|
(mut commands, mut effects): (Commands, Effects),
|
||||||
mut logger: Logger,
|
mut logger: Logger,
|
||||||
) {
|
) {
|
||||||
#[derive(LogMessage)]
|
|
||||||
pub struct UsedDebuffTemp {
|
|
||||||
#[log(player)]
|
|
||||||
pub actor: Entity,
|
|
||||||
#[log(player)]
|
|
||||||
pub recipient: Entity,
|
|
||||||
#[log(weapon)]
|
|
||||||
pub weapon: Entity,
|
|
||||||
pub immune: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
for (effect, player, weapon) in temp_q.iter_many(entities) {
|
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.get()).unwrap();
|
||||||
let user = weapon_q.get(weapon.0).unwrap();
|
let user = weapon_q.get(weapon.0).unwrap();
|
||||||
if immunity {
|
if immunity {
|
||||||
commands.entity(effect).despawn();
|
commands.entity(effect).despawn();
|
||||||
commands.entity(player.get()).remove_child(effect);
|
commands.entity(player.get()).remove_child(effect);
|
||||||
logger.log(|| UsedDebuffTemp {
|
log!(logger, "used_debuff_temp", {
|
||||||
actor: user.get(),
|
actor: user.get(),
|
||||||
recipient: player.get(),
|
recipient: player.get(),
|
||||||
weapon: weapon.0,
|
weapon: weapon.0,
|
||||||
|
|
@ -488,7 +476,7 @@ fn apply_temp_debuff_effect<Temp: DebuffingTempMarker>(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.log(|| UsedDebuffTemp {
|
log!(logger, "used_debuff_temp", {
|
||||||
actor: user.get(),
|
actor: user.get(),
|
||||||
recipient: player.get(),
|
recipient: player.get(),
|
||||||
weapon: weapon.0,
|
weapon: weapon.0,
|
||||||
|
|
@ -505,14 +493,6 @@ fn remove_temp_debuff_effect<Temp: DebuffingTempMarker>(
|
||||||
_logger: Logger,
|
_logger: Logger,
|
||||||
mut effects: Effects,
|
mut effects: Effects,
|
||||||
) {
|
) {
|
||||||
#[derive(LogMessage)]
|
|
||||||
struct RemovedDebuffTemp {
|
|
||||||
#[log(player)]
|
|
||||||
recipient: Entity,
|
|
||||||
factor: f32,
|
|
||||||
factor_remaining: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
for player in temp_q.iter_many(entities) {
|
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.get()).unwrap();
|
||||||
if immunity {
|
if immunity {
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,16 @@ use bevy_ecs::prelude::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
effect::{Effects, TurnLimitedEffect},
|
effect::{Effects, TurnLimitedEffect},
|
||||||
hierarchy::{HierarchyBuilder, Parent},
|
hierarchy::{HierarchyBuilder, Parent},
|
||||||
player::stats::{
|
player::{
|
||||||
|
stats::{
|
||||||
AdditiveBonus, AmmoControl, Clips, CritRate, DamageBonus, SimpleStatBonus,
|
AdditiveBonus, AmmoControl, Clips, CritRate, DamageBonus, SimpleStatBonus,
|
||||||
SimpleStatEffective, Speed, Strength, WeaponAccuracy,
|
SimpleStatEffective, Speed, Strength, WeaponAccuracy,
|
||||||
},
|
},
|
||||||
|
status_effect::{
|
||||||
|
AdditiveStatusEffect, Crippled, Demoralise, Frozen, Motivate, Slow, Weakened, Withered,
|
||||||
|
},
|
||||||
|
BodyPart, PartDamageBonus,
|
||||||
|
},
|
||||||
Stages,
|
Stages,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -155,6 +161,117 @@ impl MultiTurnBonus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum OpponentStatusEffect {
|
||||||
|
Cripple,
|
||||||
|
// TODO: implement for group fights
|
||||||
|
Demoralise,
|
||||||
|
Freeze,
|
||||||
|
Slow,
|
||||||
|
Toxin,
|
||||||
|
Weaken,
|
||||||
|
Wither,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OpponentStatusEffect {
|
||||||
|
pub fn spawn(self, target: Entity, effects: &mut Effects, rng: &mut impl rand::Rng) {
|
||||||
|
match self {
|
||||||
|
Self::Cripple => {
|
||||||
|
effects.spawn(AdditiveStatusEffect::<1, Crippled>::default(), target);
|
||||||
|
}
|
||||||
|
Self::Demoralise => {
|
||||||
|
effects.spawn(AdditiveStatusEffect::<4, Demoralise>::default(), target);
|
||||||
|
}
|
||||||
|
Self::Freeze => {
|
||||||
|
effects.spawn(AdditiveStatusEffect::<2, Frozen>::default(), target);
|
||||||
|
}
|
||||||
|
Self::Slow => {
|
||||||
|
effects.spawn(AdditiveStatusEffect::<1, Slow>::default(), target);
|
||||||
|
}
|
||||||
|
Self::Toxin => match rng.gen_range(0..4) {
|
||||||
|
0 => OpponentStatusEffect::Cripple.spawn(target, effects, rng),
|
||||||
|
1 => OpponentStatusEffect::Slow.spawn(target, effects, rng),
|
||||||
|
2 => OpponentStatusEffect::Weaken.spawn(target, effects, rng),
|
||||||
|
_ => OpponentStatusEffect::Wither.spawn(target, effects, rng),
|
||||||
|
},
|
||||||
|
Self::Weaken => {
|
||||||
|
effects.spawn(AdditiveStatusEffect::<1, Weakened>::default(), target);
|
||||||
|
}
|
||||||
|
Self::Wither => {
|
||||||
|
effects.spawn(AdditiveStatusEffect::<1, Withered>::default(), target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum SelfStatusEffect {
|
||||||
|
Motivate,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SelfStatusEffect {
|
||||||
|
pub fn spawn(self, current: Entity, effects: &mut Effects) {
|
||||||
|
match self {
|
||||||
|
Self::Motivate => {
|
||||||
|
effects.spawn(AdditiveStatusEffect::<4, Motivate>::default(), current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum BonusPartDamageBonus {
|
||||||
|
Achilles,
|
||||||
|
Crusher,
|
||||||
|
Cupid,
|
||||||
|
Deadeye,
|
||||||
|
Roshambo,
|
||||||
|
Throttle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BonusPartDamageBonus {
|
||||||
|
pub fn dmg_bonus(self, part: BodyPart, value: f32) -> Option<f32> {
|
||||||
|
match self {
|
||||||
|
Self::Achilles => match part {
|
||||||
|
BodyPart::LeftFoot | BodyPart::RightFoot => Some(value),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
Self::Crusher => {
|
||||||
|
if part == BodyPart::Head {
|
||||||
|
Some(value)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Cupid => {
|
||||||
|
if part == BodyPart::Heart {
|
||||||
|
Some(value)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Deadeye => match part {
|
||||||
|
BodyPart::Head | BodyPart::Heart | BodyPart::Throat => Some(value),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
Self::Roshambo => {
|
||||||
|
if part == BodyPart::Groin {
|
||||||
|
Some(value)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Throttle => {
|
||||||
|
if part == BodyPart::Throat {
|
||||||
|
Some(value)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn prepare_bonuses(
|
pub(crate) fn prepare_bonuses(
|
||||||
bonus_q: Query<(
|
bonus_q: Query<(
|
||||||
&Parent,
|
&Parent,
|
||||||
|
|
@ -278,6 +395,121 @@ pub(crate) fn prepare_bonuses(
|
||||||
.set_parent(weapon.get());
|
.set_parent(weapon.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WeaponBonus::Achilles => {
|
||||||
|
commands
|
||||||
|
.spawn(PartDamageBonus::WeaponBonus {
|
||||||
|
value: value.0 / 100.0,
|
||||||
|
bonus: BonusPartDamageBonus::Achilles,
|
||||||
|
})
|
||||||
|
.set_parent(weapon.get());
|
||||||
|
}
|
||||||
|
WeaponBonus::Cupid => {
|
||||||
|
commands
|
||||||
|
.spawn(PartDamageBonus::WeaponBonus {
|
||||||
|
value: value.0 / 100.0,
|
||||||
|
bonus: BonusPartDamageBonus::Cupid,
|
||||||
|
})
|
||||||
|
.set_parent(weapon.get());
|
||||||
|
}
|
||||||
|
WeaponBonus::Crusher => {
|
||||||
|
commands
|
||||||
|
.spawn(PartDamageBonus::WeaponBonus {
|
||||||
|
value: value.0 / 100.0,
|
||||||
|
bonus: BonusPartDamageBonus::Crusher,
|
||||||
|
})
|
||||||
|
.set_parent(weapon.get());
|
||||||
|
}
|
||||||
|
WeaponBonus::Deadeye => {
|
||||||
|
commands
|
||||||
|
.spawn(PartDamageBonus::WeaponBonus {
|
||||||
|
value: value.0 / 100.0,
|
||||||
|
bonus: BonusPartDamageBonus::Deadeye,
|
||||||
|
})
|
||||||
|
.set_parent(weapon.get());
|
||||||
|
}
|
||||||
|
WeaponBonus::Throttle => {
|
||||||
|
commands
|
||||||
|
.spawn(PartDamageBonus::WeaponBonus {
|
||||||
|
value: value.0 / 100.0,
|
||||||
|
bonus: BonusPartDamageBonus::Throttle,
|
||||||
|
})
|
||||||
|
.set_parent(weapon.get());
|
||||||
|
}
|
||||||
|
WeaponBonus::Roshambo => {
|
||||||
|
commands
|
||||||
|
.spawn(PartDamageBonus::WeaponBonus {
|
||||||
|
value: value.0 / 100.0,
|
||||||
|
bonus: BonusPartDamageBonus::Roshambo,
|
||||||
|
})
|
||||||
|
.set_parent(weapon.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
WeaponBonus::Cripple => {
|
||||||
|
commands
|
||||||
|
.spawn(DamageProcEffect::OpponentEffect {
|
||||||
|
value: value.0,
|
||||||
|
bonus: OpponentStatusEffect::Cripple,
|
||||||
|
})
|
||||||
|
.set_parent(weapon.get());
|
||||||
|
}
|
||||||
|
WeaponBonus::Demoralise => {
|
||||||
|
commands
|
||||||
|
.spawn(DamageProcEffect::OpponentEffect {
|
||||||
|
value: value.0,
|
||||||
|
bonus: OpponentStatusEffect::Demoralise,
|
||||||
|
})
|
||||||
|
.set_parent(weapon.get());
|
||||||
|
}
|
||||||
|
WeaponBonus::Freeze => {
|
||||||
|
commands
|
||||||
|
.spawn(DamageProcEffect::OpponentEffect {
|
||||||
|
value: value.0,
|
||||||
|
bonus: OpponentStatusEffect::Freeze,
|
||||||
|
})
|
||||||
|
.set_parent(weapon.get());
|
||||||
|
}
|
||||||
|
WeaponBonus::Slow => {
|
||||||
|
commands
|
||||||
|
.spawn(DamageProcEffect::OpponentEffect {
|
||||||
|
value: value.0,
|
||||||
|
bonus: OpponentStatusEffect::Slow,
|
||||||
|
})
|
||||||
|
.set_parent(weapon.get());
|
||||||
|
}
|
||||||
|
WeaponBonus::Toxin => {
|
||||||
|
commands
|
||||||
|
.spawn(DamageProcEffect::OpponentEffect {
|
||||||
|
value: value.0,
|
||||||
|
bonus: OpponentStatusEffect::Toxin,
|
||||||
|
})
|
||||||
|
.set_parent(weapon.get());
|
||||||
|
}
|
||||||
|
WeaponBonus::Weaken => {
|
||||||
|
commands
|
||||||
|
.spawn(DamageProcEffect::OpponentEffect {
|
||||||
|
value: value.0,
|
||||||
|
bonus: OpponentStatusEffect::Weaken,
|
||||||
|
})
|
||||||
|
.set_parent(weapon.get());
|
||||||
|
}
|
||||||
|
WeaponBonus::Wither => {
|
||||||
|
commands
|
||||||
|
.spawn(DamageProcEffect::OpponentEffect {
|
||||||
|
value: value.0,
|
||||||
|
bonus: OpponentStatusEffect::Wither,
|
||||||
|
})
|
||||||
|
.set_parent(weapon.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
WeaponBonus::Motivate => {
|
||||||
|
commands
|
||||||
|
.spawn(DamageProcEffect::SelfEffect {
|
||||||
|
value: value.0,
|
||||||
|
bonus: SelfStatusEffect::Motivate,
|
||||||
|
})
|
||||||
|
.set_parent(weapon.get());
|
||||||
|
}
|
||||||
|
|
||||||
val => unimplemented!("{val:?}"),
|
val => unimplemented!("{val:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,24 @@
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use macros::LogMessage;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
effect::{Effects, TurnLimitedEffect},
|
effect::{Effects, TurnLimitedEffect},
|
||||||
hierarchy::{HierarchyBuilder, Parent},
|
hierarchy::{HierarchyBuilder, Parent},
|
||||||
|
log,
|
||||||
log::Logger,
|
log::Logger,
|
||||||
passives::{Education, FactionUpgrades, Merits},
|
passives::{Education, EducationPartDamageBonus, FactionUpgrades, Merits},
|
||||||
player::{
|
player::{
|
||||||
stats::{
|
stats::{
|
||||||
AdditiveBonus, AmmoControl, ClipSize, Clips, CritRate, DamageBonus, Dexterity,
|
AdditiveBonus, AmmoControl, ClipSize, Clips, CritRate, DamageBonus, Dexterity,
|
||||||
SimpleStatBonus, SimpleStatBundle, SimpleStatEffective, WeaponAccuracy,
|
SimpleStatBonus, SimpleStatBundle, SimpleStatEffective, WeaponAccuracy,
|
||||||
},
|
},
|
||||||
Current, Weapons,
|
Current, PartDamageBonus, Weapons,
|
||||||
},
|
},
|
||||||
Id, Name, Stages,
|
Id, Name, Stages,
|
||||||
};
|
};
|
||||||
|
|
||||||
use self::bonus::{FirstTurnBonus, MultiTurnBonus, TurnTriggeredBonus};
|
use self::bonus::{
|
||||||
|
FirstTurnBonus, MultiTurnBonus, OpponentStatusEffect, SelfStatusEffect, TurnTriggeredBonus,
|
||||||
|
};
|
||||||
|
|
||||||
pub mod bonus;
|
pub mod bonus;
|
||||||
pub mod temp;
|
pub mod temp;
|
||||||
|
|
@ -58,7 +60,7 @@ pub enum WeaponCategory {
|
||||||
Smg,
|
Smg,
|
||||||
Shotgun,
|
Shotgun,
|
||||||
Pistol,
|
Pistol,
|
||||||
Club,
|
Clubbing,
|
||||||
Piercing,
|
Piercing,
|
||||||
Slashing,
|
Slashing,
|
||||||
Mechanical,
|
Mechanical,
|
||||||
|
|
@ -110,7 +112,7 @@ pub enum WeaponMod {
|
||||||
FullChoke,
|
FullChoke,
|
||||||
RecoilPad,
|
RecoilPad,
|
||||||
StandardBrake,
|
StandardBrake,
|
||||||
HeavyDutyBreak,
|
HeavyDutyBrake,
|
||||||
TacticalBrake,
|
TacticalBrake,
|
||||||
SmallLight,
|
SmallLight,
|
||||||
PrecisionLight,
|
PrecisionLight,
|
||||||
|
|
@ -220,11 +222,6 @@ pub enum FirstTurnEffect {
|
||||||
Bonus { value: f32, bonus: FirstTurnBonus },
|
Bonus { value: f32, bonus: FirstTurnBonus },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component)]
|
|
||||||
pub enum DamageProcEffect {
|
|
||||||
MultiTurn { value: f32, bonus: MultiTurnBonus },
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FirstTurnEffect {
|
impl FirstTurnEffect {
|
||||||
fn spawn(&self, effects: &mut Effects, weapon: Entity, owner: Entity) {
|
fn spawn(&self, effects: &mut Effects, weapon: Entity, owner: Entity) {
|
||||||
match self {
|
match self {
|
||||||
|
|
@ -234,31 +231,28 @@ impl FirstTurnEffect {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Component)]
|
||||||
|
pub enum DamageProcEffect {
|
||||||
|
MultiTurn {
|
||||||
|
value: f32,
|
||||||
|
bonus: MultiTurnBonus,
|
||||||
|
},
|
||||||
|
OpponentEffect {
|
||||||
|
value: f32,
|
||||||
|
bonus: OpponentStatusEffect,
|
||||||
|
},
|
||||||
|
SelfEffect {
|
||||||
|
value: f32,
|
||||||
|
bonus: SelfStatusEffect,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct EquippedMods(pub Vec<WeaponMod>);
|
pub struct EquippedMods(pub Vec<WeaponMod>);
|
||||||
|
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
pub struct Experience(pub f32);
|
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)]
|
#[derive(Bundle)]
|
||||||
pub struct WeaponBundle {
|
pub struct WeaponBundle {
|
||||||
pub usable: Usable,
|
pub usable: Usable,
|
||||||
|
|
@ -514,7 +508,7 @@ fn apply_passives(
|
||||||
weapon,
|
weapon,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
WeaponMod::HeavyDutyBreak => {
|
WeaponMod::HeavyDutyBrake => {
|
||||||
effects.spawn(
|
effects.spawn(
|
||||||
SimpleStatBonus::<WeaponAccuracy>::new("heavy duty brake", 1.25 / 50.0),
|
SimpleStatBonus::<WeaponAccuracy>::new("heavy duty brake", 1.25 / 50.0),
|
||||||
weapon,
|
weapon,
|
||||||
|
|
@ -595,7 +589,7 @@ fn apply_passives(
|
||||||
merits.pistol_mastery,
|
merits.pistol_mastery,
|
||||||
education.cbt2840.then_some("CBT2840"),
|
education.cbt2840.then_some("CBT2840"),
|
||||||
),
|
),
|
||||||
WeaponCategory::Club => (merits.club_mastery, None),
|
WeaponCategory::Clubbing => (merits.club_mastery, None),
|
||||||
WeaponCategory::Piercing => (merits.piercing_mastery, None),
|
WeaponCategory::Piercing => (merits.piercing_mastery, None),
|
||||||
WeaponCategory::Slashing => (merits.slashing_mastery, None),
|
WeaponCategory::Slashing => (merits.slashing_mastery, None),
|
||||||
WeaponCategory::Mechanical => (merits.mechanical_mastery, None),
|
WeaponCategory::Mechanical => (merits.mechanical_mastery, None),
|
||||||
|
|
@ -644,6 +638,14 @@ fn apply_passives(
|
||||||
effects.spawn(SimpleStatBonus::<DamageBonus>::new("HIS2160", 0.10), weapon);
|
effects.spawn(SimpleStatBonus::<DamageBonus>::new("HIS2160", 0.10), weapon);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if education.bio2380 {
|
||||||
|
commands
|
||||||
|
.spawn(PartDamageBonus::Education(
|
||||||
|
EducationPartDamageBonus::Bio2380,
|
||||||
|
))
|
||||||
|
.set_parent(weapon);
|
||||||
|
}
|
||||||
|
|
||||||
if mastery > 0 {
|
if mastery > 0 {
|
||||||
effects.spawn(
|
effects.spawn(
|
||||||
SimpleStatBonus::<WeaponAccuracy>::new("mastery", (mastery as f32) * 0.2 / 50.0),
|
SimpleStatBonus::<WeaponAccuracy>::new("mastery", (mastery as f32) * 0.2 / 50.0),
|
||||||
|
|
@ -697,9 +699,9 @@ fn reload_weapon(
|
||||||
ammo.0 = clip_size.value;
|
ammo.0 = clip_size.value;
|
||||||
clips.value -= 1;
|
clips.value -= 1;
|
||||||
|
|
||||||
logger.log(|| ReloadWeapon {
|
log!(logger, "reload_weapon", {
|
||||||
actor: player.get(),
|
actor: player.get(),
|
||||||
weapon,
|
weapon
|
||||||
});
|
});
|
||||||
|
|
||||||
commands.entity(weapon).remove::<NeedsReload>();
|
commands.entity(weapon).remove::<NeedsReload>();
|
||||||
|
|
@ -735,12 +737,10 @@ fn apply_first_turn_effects(
|
||||||
|
|
||||||
pub(crate) fn configure(stages: &mut Stages) {
|
pub(crate) fn configure(stages: &mut Stages) {
|
||||||
stages.equip.add_systems(set_owner);
|
stages.equip.add_systems(set_owner);
|
||||||
stages.pre_fight.add_systems((
|
// running this in the snapshot layer ensures that the stat increases aren't restored at the
|
||||||
apply_passives,
|
// end of the run
|
||||||
apply_first_turn_effects
|
stages.snapshot.add_systems(apply_first_turn_effects);
|
||||||
.after(apply_passives)
|
stages.pre_fight.add_systems(apply_passives);
|
||||||
.after(bonus::prepare_bonuses),
|
|
||||||
));
|
|
||||||
stages.turn.add_systems(reload_weapon);
|
stages.turn.add_systems(reload_weapon);
|
||||||
stages.post_turn.add_systems(unset_current);
|
stages.post_turn.add_systems(unset_current);
|
||||||
stages
|
stages
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue