1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
use common::{
    combat,
    comp::{
        self, item::MaterialStatManifest, CharacterState, Combo, Energy, Health, Inventory, Poise,
        Stats, StatsModifier,
    },
    event::{DestroyEvent, EmitExt},
    event_emitters,
    resources::{DeltaTime, Time},
};
use common_ecs::{Job, Origin, Phase, System};
use specs::{shred, Entities, LendJoin, Read, ReadExpect, ReadStorage, SystemData, WriteStorage};

const ENERGY_REGEN_ACCEL: f32 = 1.0;
const SIT_ENERGY_REGEN_ACCEL: f32 = 2.5;
const POISE_REGEN_ACCEL: f32 = 2.0;

event_emitters! {
    struct Events[Emitters] {
        destroy: DestroyEvent,
    }
}

#[derive(SystemData)]
pub struct ReadData<'a> {
    entities: Entities<'a>,
    dt: Read<'a, DeltaTime>,
    time: Read<'a, Time>,
    events: Events<'a>,
    char_states: ReadStorage<'a, CharacterState>,
    inventories: ReadStorage<'a, Inventory>,
    msm: ReadExpect<'a, MaterialStatManifest>,
}

/// This system kills players, levels them up, and regenerates energy.
#[derive(Default)]
pub struct Sys;
impl<'a> System<'a> for Sys {
    type SystemData = (
        ReadData<'a>,
        WriteStorage<'a, Stats>,
        WriteStorage<'a, Health>,
        WriteStorage<'a, Poise>,
        WriteStorage<'a, Energy>,
        WriteStorage<'a, Combo>,
    );

    const NAME: &'static str = "stats";
    const ORIGIN: Origin = Origin::Common;
    const PHASE: Phase = Phase::Create;

    fn run(
        _job: &mut Job<Self>,
        (read_data, stats, mut healths, mut poises, mut energies, mut combos): Self::SystemData,
    ) {
        let mut emitters = read_data.events.get_emitters();
        let dt = read_data.dt.0;

        // Update stats
        let join = (
            &read_data.entities,
            &stats,
            &mut healths,
            &mut energies,
            read_data.inventories.maybe(),
        )
            .lend_join();
        join.for_each(|(entity, stats, mut health, mut energy, inventory)| {
            let set_dead = { health.should_die() && !health.is_dead };

            if set_dead {
                emitters.emit(DestroyEvent {
                    entity,
                    cause: health.last_change,
                });
            }
            let stat = stats;

            if let Some(new_max) = health.needs_maximum_update(stat.max_health_modifiers) {
                // Only call this if we need to since mutable access will trigger sending an
                // update to the client.
                health.update_internal_integer_maximum(new_max);
            }

            // Calculates energy scaling from stats and inventory
            let energy_mods = StatsModifier {
                add_mod: stat.max_energy_modifiers.add_mod
                    + combat::compute_max_energy_mod(inventory, &read_data.msm),
                mult_mod: stat.max_energy_modifiers.mult_mod,
            };

            if let Some(new_max) = energy.needs_maximum_update(energy_mods) {
                // Only call this if we need to since mutable access will trigger sending an
                // update to the client.
                energy.update_internal_integer_maximum(new_max);
            }
        });

        // Update energies and poises
        let join = (&read_data.char_states, &mut energies, &mut poises).lend_join();
        join.for_each(|(character_state, mut energy, mut poise)| {
            match character_state {
                // Sitting accelerates recharging energy the most
                CharacterState::Sit => {
                    if energy.needs_regen() {
                        energy.regen(SIT_ENERGY_REGEN_ACCEL, dt);
                    }
                    if poise.needs_regen() {
                        poise.regen(POISE_REGEN_ACCEL, dt, *read_data.time);
                    }
                },
                // Accelerate recharging energy.
                CharacterState::Idle(_)
                | CharacterState::Talk
                | CharacterState::Dance
                | CharacterState::Pet(_)
                | CharacterState::Skate(_)
                | CharacterState::Glide(_)
                | CharacterState::GlideWield(_)
                | CharacterState::Wielding(_)
                | CharacterState::Equipping(_)
                | CharacterState::Boost(_) => {
                    if energy.needs_regen() {
                        energy.regen(ENERGY_REGEN_ACCEL, dt);
                    }
                    if poise.needs_regen() {
                        poise.regen(POISE_REGEN_ACCEL, dt, *read_data.time);
                    }
                },
                // Ability use does not regen and sets the rate back to zero.
                CharacterState::BasicMelee(_)
                | CharacterState::DashMelee(_)
                | CharacterState::LeapMelee(_)
                | CharacterState::LeapShockwave(_)
                | CharacterState::ComboMelee2(_)
                | CharacterState::BasicRanged(_)
                | CharacterState::Music(_)
                | CharacterState::ChargedMelee(_)
                | CharacterState::ChargedRanged(_)
                | CharacterState::RepeaterRanged(_)
                | CharacterState::Shockwave(_)
                | CharacterState::BasicBeam(_)
                | CharacterState::BasicAura(_)
                | CharacterState::Blink(_)
                | CharacterState::Climb(_)
                | CharacterState::BasicSummon(_)
                | CharacterState::SelfBuff(_)
                | CharacterState::SpriteSummon(_)
                | CharacterState::FinisherMelee(_)
                | CharacterState::DiveMelee(_)
                | CharacterState::RiposteMelee(_)
                | CharacterState::RapidMelee(_)
                | CharacterState::StaticAura(_) => {
                    if energy.needs_regen_rate_reset() {
                        energy.reset_regen_rate();
                    }
                },
                // Abilities that temporarily stall energy gain, but preserve regen_rate.
                CharacterState::Roll(_)
                | CharacterState::Wallrun(_)
                | CharacterState::Stunned(_)
                | CharacterState::BasicBlock(_)
                | CharacterState::UseItem(_)
                | CharacterState::Transform(_)
                | CharacterState::RegrowHead(_)
                | CharacterState::SpriteInteract(_) => {},
            }
        });

        // Decay combo
        (&read_data.entities, &mut combos)
            .lend_join()
            .for_each(|(_, mut combo)| {
                if combo.counter() > 0
                    && read_data.time.0 - combo.last_increase() > comp::combo::COMBO_DECAY_START
                {
                    combo.reset();
                }
            });
    }
}