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
67 if let Some(melee) = &self.static_data.melee {
68 data.updater.insert(
69 data.entity,
70 melee
71 .clone()
72 .create_melee(precision_mult, self.static_data.ability_info),
73 );
74 }
75
76 if let CharacterState::LeapRanged(c) = &mut update.character {
77 c.melee_done = true;
78 }
79 }
80
81 if let CharacterState::LeapRanged(c) = &mut update.character {
82 c.timer = tick_attack_or_default(data, self.timer, None);
83 }
84 } else {
85 let early_exit = self.static_data.melee_required
86 && data
87 .melee_attack
88 .is_none_or(|melee| melee.hit_entities.is_empty());
89 if let CharacterState::LeapRanged(c) = &mut update.character {
90 c.timer = Duration::default();
91 c.stage_section = if early_exit {
92 StageSection::Recover
93 } else {
94 StageSection::Movement
95 };
96 }
97 }
98 },
99 StageSection::Movement => {
100 if self.timer < self.static_data.movement_duration {
101 let frac = {
102 let raw = self.timer.as_secs_f32()
103 / self.static_data.movement_duration.as_secs_f32();
104 raw.clamp(0.0, 1.0)
105 };
106 let progress = 1.0 - frac;
107 handle_forced_movement(data, &mut update, ForcedMovement::Leap {
108 vertical: self.static_data.vert_leap_strength,
109 forward: self.static_data.horiz_leap_strength,
110 progress,
111 direction: MovementDirection::AntiLook,
112 });
113
114 if !self.ranged_done && frac > self.static_data.movement_ranged_timing {
115 let precision_mult =
116 combat::compute_precision_mult(data.inventory, data.msm);
117
118 let (projectile, marker) =
119 self.static_data.projectile.clone().create_projectile(
120 Some(*data.uid),
121 precision_mult,
122 Some(self.static_data.ability_info),
123 Some(data.stats),
124 );
125
126 let body_offsets = data.body.projectile_offsets(
127 update.ori.look_vec(),
128 data.scale.map_or(1.0, |s| s.0),
129 );
130 let pos = Pos(data.pos.0 + body_offsets);
131 output_events.emit_server(ShootEvent {
132 entity: Some(data.entity),
133 source_vel: Some(*data.vel),
134 pos,
135 dir: data.inputs.look_dir,
136 body: self.static_data.projectile_body,
137 projectile,
138 light: self.static_data.projectile_light,
139 speed: self.static_data.projectile_speed
140 * data.stats.projectile_speed_mult,
141 object: None,
142 marker,
143 });
144
145 if let CharacterState::LeapRanged(c) = &mut update.character {
146 c.ranged_done = true;
147 }
148 }
149
150 if let CharacterState::LeapRanged(c) = &mut update.character {
151 c.timer = tick_attack_or_default(data, self.timer, None);
152 }
153 } else if data.physics.on_ground.is_some()
154 | data.physics.in_liquid().is_some()
155 | (self.timer
156 > (self.static_data.movement_duration + self.static_data.land_timeout))
157 {
158 if let CharacterState::LeapRanged(c) = &mut update.character {
159 c.timer = Duration::default();
160 c.stage_section = StageSection::Recover;
161 }
162 } else if let CharacterState::LeapRanged(c) = &mut update.character {
163 c.timer = tick_attack_or_default(data, self.timer, None);
164 }
165 },
166 StageSection::Recover => {
167 if self.timer < self.static_data.recover_duration
168 && let CharacterState::LeapRanged(c) = &mut update.character
169 {
170 c.timer = tick_attack_or_default(data, self.timer, None);
171 }
172 },
173 _ => {
174 end_melee_ability(data, &mut update);
175 },
176 }
177
178 handle_interrupts(data, &mut update, output_events);
180
181 update
182 }
183}