1use common::{
2 combat,
3 comp::{
4 self, CharacterState, Combo, Energy, Health, Inventory, Poise, Stats, StatsModifier,
5 item::MaterialStatManifest,
6 },
7 event::{DestroyEvent, DownedEvent, EmitExt},
8 event_emitters,
9 resources::{DeltaTime, Time},
10};
11use common_ecs::{Job, Origin, Phase, System};
12use specs::{Entities, LendJoin, Read, ReadExpect, ReadStorage, SystemData, WriteStorage, shred};
13
14const ENERGY_REGEN_ACCEL: f32 = 1.0;
15const SIT_ENERGY_REGEN_ACCEL: f32 = 2.5;
16const POISE_REGEN_ACCEL: f32 = 2.0;
17
18event_emitters! {
19 struct Events[Emitters] {
20 destroy: DestroyEvent,
21 downed: DownedEvent,
22 }
23}
24
25#[derive(SystemData)]
26pub struct ReadData<'a> {
27 entities: Entities<'a>,
28 dt: Read<'a, DeltaTime>,
29 time: Read<'a, Time>,
30 events: Events<'a>,
31 char_states: ReadStorage<'a, CharacterState>,
32 inventories: ReadStorage<'a, Inventory>,
33 msm: ReadExpect<'a, MaterialStatManifest>,
34}
35
36#[derive(Default)]
38pub struct Sys;
39impl<'a> System<'a> for Sys {
40 type SystemData = (
41 ReadData<'a>,
42 WriteStorage<'a, Stats>,
43 WriteStorage<'a, Health>,
44 WriteStorage<'a, Poise>,
45 WriteStorage<'a, Energy>,
46 WriteStorage<'a, Combo>,
47 );
48
49 const NAME: &'static str = "stats";
50 const ORIGIN: Origin = Origin::Common;
51 const PHASE: Phase = Phase::Create;
52
53 fn run(
54 _job: &mut Job<Self>,
55 (read_data, stats, mut healths, mut poises, mut energies, mut combos): Self::SystemData,
56 ) {
57 let mut emitters = read_data.events.get_emitters();
58 let dt = read_data.dt.0;
59
60 let join = (
62 &read_data.entities,
63 &stats,
64 &mut healths,
65 &mut energies,
66 read_data.inventories.maybe(),
67 )
68 .lend_join();
69 join.for_each(|(entity, stats, mut health, mut energy, inventory)| {
70 let set_dead = { health.should_die() && !health.is_dead };
71
72 if set_dead {
73 if health.death_protection {
74 emitters.emit(DownedEvent { entity });
75 } else {
76 emitters.emit(DestroyEvent {
77 entity,
78 cause: health.last_change,
79 });
80 }
81 }
82 let stat = stats;
83
84 if let Some(new_max) = health.needs_maximum_update(stat.max_health_modifiers) {
85 health.update_internal_integer_maximum(new_max);
88 }
89
90 let energy_mods = StatsModifier {
92 add_mod: stat.max_energy_modifiers.add_mod
93 + combat::compute_max_energy_mod(inventory, &read_data.msm),
94 mult_mod: stat.max_energy_modifiers.mult_mod,
95 };
96
97 if let Some(new_max) = energy.needs_maximum_update(energy_mods) {
98 energy.update_internal_integer_maximum(new_max);
101 }
102 });
103
104 let join = (&read_data.char_states, &mut energies, &mut poises).lend_join();
106 join.for_each(|(character_state, mut energy, mut poise)| {
107 match character_state {
108 CharacterState::Sit => {
110 if energy.needs_regen() {
111 energy.regen(SIT_ENERGY_REGEN_ACCEL, dt);
112 }
113 if poise.needs_regen() {
114 poise.regen(POISE_REGEN_ACCEL, dt, *read_data.time);
115 }
116 },
117 CharacterState::Idle(_)
119 | CharacterState::Talk(_)
120 | CharacterState::Dance
121 | CharacterState::Skate(_)
122 | CharacterState::Glide(_)
123 | CharacterState::GlideWield(_)
124 | CharacterState::Wielding(_)
125 | CharacterState::Equipping(_)
126 | CharacterState::Boost(_) => {
127 if energy.needs_regen() {
128 energy.regen(ENERGY_REGEN_ACCEL, dt);
129 }
130 if poise.needs_regen() {
131 poise.regen(POISE_REGEN_ACCEL, dt, *read_data.time);
132 }
133 },
134 CharacterState::BasicMelee(_)
136 | CharacterState::DashMelee(_)
137 | CharacterState::LeapMelee(_)
138 | CharacterState::LeapShockwave(_)
139 | CharacterState::ComboMelee2(_)
140 | CharacterState::BasicRanged(_)
141 | CharacterState::Music(_)
142 | CharacterState::ChargedMelee(_)
143 | CharacterState::ChargedRanged(_)
144 | CharacterState::RepeaterRanged(_)
145 | CharacterState::Shockwave(_)
146 | CharacterState::BasicBeam(_)
147 | CharacterState::BasicAura(_)
148 | CharacterState::Blink(_)
149 | CharacterState::Climb(_)
150 | CharacterState::BasicSummon(_)
151 | CharacterState::SelfBuff(_)
152 | CharacterState::SpriteSummon(_)
153 | CharacterState::FinisherMelee(_)
154 | CharacterState::DiveMelee(_)
155 | CharacterState::RiposteMelee(_)
156 | CharacterState::RapidMelee(_)
157 | CharacterState::StaticAura(_) => {
158 if energy.needs_regen_rate_reset() {
159 energy.reset_regen_rate();
160 }
161 },
162 CharacterState::Roll(_)
164 | CharacterState::Crawl
165 | CharacterState::Wallrun(_)
166 | CharacterState::Stunned(_)
167 | CharacterState::BasicBlock(_)
168 | CharacterState::UseItem(_)
169 | CharacterState::Transform(_)
170 | CharacterState::RegrowHead(_)
171 | CharacterState::Interact(_) => {},
172 }
173 });
174
175 (&read_data.entities, &mut combos)
177 .lend_join()
178 .for_each(|(_, mut combo)| {
179 if combo.counter() > 0
180 && read_data.time.0 - combo.last_increase() > comp::combo::COMBO_DECAY_START
181 {
182 combo.reset();
183 }
184 });
185 }
186}