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
use crate::{
    combat,
    comp::{character_state::OutputEvents, CharacterState, MeleeConstructor, StateUpdate},
    states::{
        behavior::{CharacterBehavior, JoinData},
        utils::{StageSection, *},
    },
};
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 {
    /// If Some(_), the state can be activated on the ground
    pub buildup_duration: Option<Duration>,
    /// How long the state is moving
    pub movement_duration: Duration,
    /// How long the weapon swings
    pub swing_duration: Duration,
    /// How long the state has until exiting
    pub recover_duration: Duration,
    /// The minimum vertical speed the state needed
    pub vertical_speed: f32,
    /// Used to construct the Melee attack
    pub melee_constructor: MeleeConstructor,
    /// Caps the amount of scaling the attack will receive from vertical speed
    pub max_scaling: f32,
    /// What key is used to press 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,
    /// Whether the attack can deal more damage
    pub exhausted: bool,
    /// The maximum negative vertical velocity achieved during the state
    pub max_vertical_speed: f32,
}

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

        if let CharacterState::DiveMelee(c) = &mut update.character {
            c.max_vertical_speed = c.max_vertical_speed.max(-data.vel.0.z);
        }

        match self.stage_section {
            StageSection::Buildup => {
                handle_orientation(data, &mut update, 0.5, None);
                handle_move(data, &mut update, 0.3);

                if let Some(buildup_duration) = self.static_data.buildup_duration {
                    if self.timer < buildup_duration {
                        if let CharacterState::DiveMelee(c) = &mut update.character {
                            c.timer = tick_attack_or_default(data, self.timer, None);
                        }
                    } else if let CharacterState::DiveMelee(c) = &mut update.character {
                        c.timer = Duration::default();
                        c.stage_section = StageSection::Action;
                    }
                } else if let CharacterState::DiveMelee(c) = &mut update.character {
                    c.timer = Duration::default();
                    c.stage_section = StageSection::Action;
                }
            },
            StageSection::Movement => {
                handle_move(data, &mut update, 1.0);
                if data.physics.on_ground.is_some() {
                    // Transitions to swing portion of state upon hitting ground
                    if let CharacterState::DiveMelee(c) = &mut update.character {
                        c.timer = Duration::default();
                        c.stage_section = StageSection::Action;
                    }
                } else if self.timer < self.static_data.movement_duration {
                    if let CharacterState::DiveMelee(c) = &mut update.character {
                        c.timer = self
                            .timer
                            .checked_add(Duration::from_secs_f32(data.dt.0))
                            .unwrap_or_default();
                    }
                } else {
                    // In character state for too long, leaving in case somethign went wrong
                    end_ability(data, &mut update);
                }
            },
            StageSection::Action => {
                if !self.exhausted {
                    // Attack
                    let precision_mult = combat::compute_precision_mult(data.inventory, data.msm);
                    let tool_stats = get_tool_stats(data, self.static_data.ability_info);
                    let scaling = self.max_vertical_speed / self.static_data.vertical_speed;
                    let scaling = scaling.min(self.static_data.max_scaling);

                    data.updater.insert(
                        data.entity,
                        self.static_data
                            .melee_constructor
                            .handle_scaling(scaling)
                            .create_melee(precision_mult, tool_stats),
                    );

                    if let CharacterState::DiveMelee(c) = &mut update.character {
                        c.timer = tick_attack_or_default(data, self.timer, None);
                        c.exhausted = true;
                    }
                } else if self.timer < self.static_data.swing_duration {
                    if let CharacterState::DiveMelee(c) = &mut update.character {
                        c.timer = tick_attack_or_default(data, self.timer, None);
                    }
                } else {
                    // Transitions to recover portion
                    if let CharacterState::DiveMelee(c) = &mut update.character {
                        c.timer = Duration::default();
                        c.stage_section = StageSection::Recover;
                    }
                }
            },
            StageSection::Recover => {
                handle_orientation(data, &mut update, 0.5, None);
                handle_move(data, &mut update, 0.3);

                if self.timer < self.static_data.recover_duration {
                    // Complete recovery delay before finishing state
                    if let CharacterState::DiveMelee(c) = &mut update.character {
                        c.timer = tick_attack_or_default(
                            data,
                            self.timer,
                            Some(data.stats.recovery_speed_modifier),
                        );
                    }
                } else {
                    // Done
                    end_melee_ability(data, &mut update);
                }
            },
            _ => {
                // If it somehow ends up in an incorrect stage section
                end_melee_ability(data, &mut update);
            },
        }

        update
    }
}