1use crate::{
2 combat,
3 comp::{
4 Body, CharacterState, LightEmitter, MeleeConstructor, Pos, ProjectileConstructor,
5 StateUpdate, character_state::OutputEvents,
6 },
7 event::ShootEvent,
8 states::{
9 behavior::{CharacterBehavior, JoinData},
10 utils::{StageSection, *},
11 },
12};
13use serde::{Deserialize, Serialize};
14use std::time::Duration;
15
16#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
18pub struct StaticData {
19 pub buildup_duration: Duration,
20 pub buildup_melee_timing: f32,
21 pub movement_duration: Duration,
22 pub movement_ranged_timing: f32,
23 pub land_timeout: Duration,
24 pub recover_duration: Duration,
25 pub melee: Option<MeleeConstructor>,
26 pub melee_required: bool,
27 pub projectile: ProjectileConstructor,
28 pub projectile_body: Body,
29 pub projectile_light: Option<LightEmitter>,
30 pub projectile_speed: f32,
31 pub horiz_leap_strength: f32,
32 pub vert_leap_strength: f32,
33 pub ability_info: AbilityInfo,
34}
35
36#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
37pub struct Data {
38 pub static_data: StaticData,
39 pub timer: Duration,
40 pub stage_section: StageSection,
41 pub melee_done: bool,
42 pub ranged_done: bool,
43}
44
45impl CharacterBehavior for Data {
46 fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
47 let mut update = StateUpdate::from(data);
48
49 handle_orientation(data, &mut update, 1.0, None);
50 handle_move(data, &mut update, 0.3);
51
52 match self.stage_section {
53 StageSection::Buildup => {
54 if self.timer < self.static_data.buildup_duration {
55 let frac = {
56 let raw = self.timer.as_secs_f32()
57 / self.static_data.buildup_duration.as_secs_f32();
58 raw.clamp(0.0, 1.0)
59 };
60 if self.static_data.melee.is_some()
61 && !self.melee_done
62 && frac > self.static_data.buildup_melee_timing
63 {
64 let precision_mult =
65 combat::compute_precision_mult(data.inventory, data.msm);
66 let tool_stats = get_tool_stats(data, self.static_data.ability_info);
67
68 if let Some(melee) = &self.static_data.melee {
69 data.updater.insert(
70 data.entity,
71 melee.clone().create_melee(
72 precision_mult,
73 tool_stats,
74 self.static_data.ability_info,
75 ),
76 );
77 }
78
79 if let CharacterState::LeapRanged(c) = &mut update.character {
80 c.melee_done = true;
81 }
82 }
83
84 if let CharacterState::LeapRanged(c) = &mut update.character {
85 c.timer = tick_attack_or_default(data, self.timer, None);
86 }
87 } else {
88 let early_exit = self.static_data.melee_required
89 && data.melee_attack.is_none_or(|melee| melee.hit_count == 0);
90 if let CharacterState::LeapRanged(c) = &mut update.character {
91 c.timer = Duration::default();
92 c.stage_section = if early_exit {
93 StageSection::Recover
94 } else {
95 StageSection::Movement
96 };
97 }
98 }
99 },
100 StageSection::Movement => {
101 if self.timer < self.static_data.movement_duration {
102 let frac = {
103 let raw = self.timer.as_secs_f32()
104 / self.static_data.movement_duration.as_secs_f32();
105 raw.clamp(0.0, 1.0)
106 };
107 let progress = 1.0 - frac;
108 handle_forced_movement(data, &mut update, ForcedMovement::Leap {
109 vertical: self.static_data.vert_leap_strength,
110 forward: self.static_data.horiz_leap_strength,
111 progress,
112 direction: MovementDirection::AntiLook,
113 });
114
115 if !self.ranged_done && frac > self.static_data.movement_ranged_timing {
116 let precision_mult =
117 combat::compute_precision_mult(data.inventory, data.msm);
118
119 let projectile = self.static_data.projectile.clone().create_projectile(
120 Some(*data.uid),
121 precision_mult,
122 Some(self.static_data.ability_info),
123 );
124
125 let body_offsets = data.body.projectile_offsets(
126 update.ori.look_vec(),
127 data.scale.map_or(1.0, |s| s.0),
128 );
129 let pos = Pos(data.pos.0 + body_offsets);
130 output_events.emit_server(ShootEvent {
131 entity: Some(data.entity),
132 source_vel: Some(*data.vel),
133 pos,
134 dir: data.inputs.look_dir,
135 body: self.static_data.projectile_body,
136 projectile,
137 light: self.static_data.projectile_light,
138 speed: self.static_data.projectile_speed,
139 object: None,
140 marker: None,
141 });
142
143 if let CharacterState::LeapRanged(c) = &mut update.character {
144 c.ranged_done = true;
145 }
146 }
147
148 if let CharacterState::LeapRanged(c) = &mut update.character {
149 c.timer = tick_attack_or_default(data, self.timer, None);
150 }
151 } else if data.physics.on_ground.is_some()
152 | data.physics.in_liquid().is_some()
153 | (self.timer
154 > (self.static_data.movement_duration + self.static_data.land_timeout))
155 {
156 if let CharacterState::LeapRanged(c) = &mut update.character {
157 c.timer = Duration::default();
158 c.stage_section = StageSection::Recover;
159 }
160 } else if let CharacterState::LeapRanged(c) = &mut update.character {
161 c.timer = tick_attack_or_default(data, self.timer, None);
162 }
163 },
164 StageSection::Recover => {
165 if self.timer < self.static_data.recover_duration {
166 if let CharacterState::LeapRanged(c) = &mut update.character {
167 c.timer = tick_attack_or_default(
168 data,
169 self.timer,
170 Some(data.stats.recovery_speed_modifier),
171 );
172 }
173 } else {
174 end_melee_ability(data, &mut update);
175 }
176 },
177 _ => {
178 end_melee_ability(data, &mut update);
179 },
180 }
181
182 handle_interrupts(data, &mut update, output_events);
184
185 update
186 }
187}