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, marker) =
122 self.static_data.projectile.clone().create_projectile(
123 Some(*data.uid),
124 precision_mult,
125 Some(self.static_data.ability_info),
126 Some(data.stats),
127 );
128
129 let body_offsets = data.body.projectile_offsets(
130 update.ori.look_vec(),
131 data.scale.map_or(1.0, |s| s.0),
132 );
133 let pos = Pos(data.pos.0 + body_offsets);
134 output_events.emit_server(ShootEvent {
135 entity: Some(data.entity),
136 source_vel: Some(*data.vel),
137 pos,
138 dir: data.inputs.look_dir,
139 body: self.static_data.projectile_body,
140 projectile,
141 light: self.static_data.projectile_light,
142 speed: self.static_data.projectile_speed
143 * data.stats.projectile_speed_mult,
144 object: None,
145 marker,
146 });
147
148 if let CharacterState::LeapRanged(c) = &mut update.character {
149 c.ranged_done = true;
150 }
151 }
152
153 if let CharacterState::LeapRanged(c) = &mut update.character {
154 c.timer = tick_attack_or_default(data, self.timer, None);
155 }
156 } else if data.physics.on_ground.is_some()
157 | data.physics.in_liquid().is_some()
158 | (self.timer
159 > (self.static_data.movement_duration + self.static_data.land_timeout))
160 {
161 if let CharacterState::LeapRanged(c) = &mut update.character {
162 c.timer = Duration::default();
163 c.stage_section = StageSection::Recover;
164 }
165 } else if let CharacterState::LeapRanged(c) = &mut update.character {
166 c.timer = tick_attack_or_default(data, self.timer, None);
167 }
168 },
169 StageSection::Recover => {
170 if self.timer < self.static_data.recover_duration
171 && let CharacterState::LeapRanged(c) = &mut update.character
172 {
173 c.timer = tick_attack_or_default(data, self.timer, None);
174 }
175 },
176 _ => {
177 end_melee_ability(data, &mut update);
178 },
179 }
180
181 handle_interrupts(data, &mut update, output_events);
183
184 update
185 }
186}