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