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
90 .melee_attack
91 .is_none_or(|melee| melee.hit_entities.is_empty());
92 if let CharacterState::LeapRanged(c) = &mut update.character {
93 c.timer = Duration::default();
94 c.stage_section = if early_exit {
95 StageSection::Recover
96 } else {
97 StageSection::Movement
98 };
99 }
100 }
101 },
102 StageSection::Movement => {
103 if self.timer < self.static_data.movement_duration {
104 let frac = {
105 let raw = self.timer.as_secs_f32()
106 / self.static_data.movement_duration.as_secs_f32();
107 raw.clamp(0.0, 1.0)
108 };
109 let progress = 1.0 - frac;
110 handle_forced_movement(data, &mut update, ForcedMovement::Leap {
111 vertical: self.static_data.vert_leap_strength,
112 forward: self.static_data.horiz_leap_strength,
113 progress,
114 direction: MovementDirection::AntiLook,
115 });
116
117 if !self.ranged_done && frac > self.static_data.movement_ranged_timing {
118 let precision_mult =
119 combat::compute_precision_mult(data.inventory, data.msm);
120
121 let projectile = self.static_data.projectile.clone().create_projectile(
122 Some(*data.uid),
123 precision_mult,
124 Some(self.static_data.ability_info),
125 );
126
127 let body_offsets = data.body.projectile_offsets(
128 update.ori.look_vec(),
129 data.scale.map_or(1.0, |s| s.0),
130 );
131 let pos = Pos(data.pos.0 + body_offsets);
132 output_events.emit_server(ShootEvent {
133 entity: Some(data.entity),
134 source_vel: Some(*data.vel),
135 pos,
136 dir: data.inputs.look_dir,
137 body: self.static_data.projectile_body,
138 projectile,
139 light: self.static_data.projectile_light,
140 speed: self.static_data.projectile_speed,
141 object: None,
142 marker: None,
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 if let CharacterState::LeapRanged(c) = &mut update.character {
169 c.timer = tick_attack_or_default(
170 data,
171 self.timer,
172 Some(data.stats.recovery_speed_modifier),
173 );
174 }
175 } else {
176 end_melee_ability(data, &mut update);
177 }
178 },
179 _ => {
180 end_melee_ability(data, &mut update);
181 },
182 }
183
184 handle_interrupts(data, &mut update, output_events);
186
187 update
188 }
189}