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