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
use crate::{
    comp::{
        buff::{BuffChange, BuffKind},
        character_state::{AttackFilters, OutputEvents},
        CharacterState, StateUpdate,
    },
    event::BuffEvent,
    states::{
        behavior::{CharacterBehavior, JoinData},
        utils::*,
    },
    util::Dir,
};
use serde::{Deserialize, Serialize};
use std::time::Duration;

/// Separated out to condense update portions of character state
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct StaticData {
    /// How long until state should roll
    pub buildup_duration: Duration,
    /// How long state is rolling for
    pub movement_duration: Duration,
    /// How long it takes to recover from roll
    pub recover_duration: Duration,
    /// Affects the speed and distance of the roll
    pub roll_strength: f32,
    /// Affects whether you are immune to various attacks while rolling
    pub attack_immunities: AttackFilters,
    /// Information about the ability
    pub ability_info: AbilityInfo,
}

#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Data {
    /// Struct containing data that does not change over the course of the
    /// character state
    pub static_data: StaticData,
    /// Timer for each stage
    pub timer: Duration,
    /// What section the character stage is in
    pub stage_section: StageSection,
    /// Had weapon
    pub was_wielded: bool,
    /// What direction were we previously aiming in?
    pub prev_aimed_dir: Option<Dir>,
    /// Is sneaking, true if previous state was also considered sneaking
    pub is_sneaking: bool,
}

impl CharacterBehavior for Data {
    fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
        let mut update = StateUpdate::from(data);

        // You should not be able to strafe while rolling
        update.should_strafe = false;

        // Smooth orientation
        handle_orientation(data, &mut update, 2.5, None);

        match self.stage_section {
            StageSection::Buildup => {
                handle_move(data, &mut update, 0.3);
                if self.timer < self.static_data.buildup_duration {
                    // Build up
                    update.character = CharacterState::Roll(Data {
                        timer: tick_attack_or_default(data, self.timer, None),
                        ..*self
                    });
                } else {
                    // Remove burning effect if active
                    output_events.emit_server(BuffEvent {
                        entity: data.entity,
                        buff_change: BuffChange::RemoveByKind(BuffKind::Burning),
                    });
                    // Transitions to movement section of stage
                    update.character = CharacterState::Roll(Data {
                        timer: Duration::default(),
                        stage_section: StageSection::Movement,
                        ..*self
                    });
                }
            },
            StageSection::Movement => {
                // Update velocity
                handle_forced_movement(
                    data,
                    &mut update,
                    ForcedMovement::Forward(
                        self.static_data.roll_strength
                            * ((1.0
                                - self.timer.as_secs_f32()
                                    / self.static_data.movement_duration.as_secs_f32())
                                / 2.0
                                + 0.25),
                    ),
                );

                if self.timer < self.static_data.movement_duration {
                    // Movement
                    update.character = CharacterState::Roll(Data {
                        timer: tick_attack_or_default(data, self.timer, None),
                        ..*self
                    });
                } else {
                    // Transition to recover section of stage
                    update.character = CharacterState::Roll(Data {
                        timer: Duration::default(),
                        stage_section: StageSection::Recover,
                        ..*self
                    });
                }
            },
            StageSection::Recover => {
                handle_move(data, &mut update, 1.0);
                // Allows for jumps to interrupt recovery in roll
                if self.timer < self.static_data.recover_duration
                    && !handle_jump(data, output_events, &mut update, 1.5)
                {
                    // Recover
                    update.character = CharacterState::Roll(Data {
                        timer: tick_attack_or_default(
                            data,
                            self.timer,
                            Some(data.stats.recovery_speed_modifier),
                        ),
                        ..*self
                    });
                } else {
                    // Done
                    end_ability(data, &mut update);
                }
            },
            _ => {
                // If it somehow ends up in an incorrect stage section
                end_ability(data, &mut update);
            },
        }

        update
    }
}