veloren_common_systems/
stats.rs

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/// This system kills players, levels them up, and regenerates energy.
38#[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        // Update stats
62        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                // Only call this if we need to since mutable access will trigger sending an
87                // update to the client.
88                health.update_internal_integer_maximum(new_max);
89            }
90
91            // Calculates energy scaling from stats and inventory
92            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                // Only call this if we need to since mutable access will trigger sending an
100                // update to the client.
101                energy.update_internal_integer_maximum(new_max);
102            }
103        });
104
105        // Update energies and poises
106        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                // Sitting accelerates recharging energy the most
110                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                // Accelerate recharging energy.
119                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                // Ability use does not regen and sets the rate back to zero.
136                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                // Abilities that temporarily stall energy gain, but preserve regen_rate.
170                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        // Decay combo
182        (&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}