1use crate::{
2 combat,
3 comp::{
4 Body, CharacterState, FrontendMarker, LightEmitter, Pos, StateUpdate,
5 ability::Amount,
6 character_state::OutputEvents,
7 object::Body::{FireRing, GrenadeClay, LaserBeam, LaserBeamSmall},
8 projectile::{ProjectileConstructor, aim_projectile},
9 },
10 event::{LocalEvent, ShootEvent},
11 outcome::Outcome,
12 states::{
13 behavior::{CharacterBehavior, JoinData},
14 utils::*,
15 },
16 util::Dir,
17};
18use itertools::Either;
19use rand::rng;
20use serde::{Deserialize, Serialize};
21use std::time::Duration;
22
23#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
25pub struct StaticData {
26 pub buildup_duration: Duration,
28 pub recover_duration: Duration,
30 pub projectile_spread: Option<ProjectileSpread>,
32 pub projectile: ProjectileConstructor,
34 pub projectile_body: Body,
35 pub projectile_light: Option<LightEmitter>,
36 pub projectile_speed: f32,
37 pub num_projectiles: Amount,
39 pub ability_info: AbilityInfo,
41 pub movement_modifier: MovementModifier,
43 pub ori_modifier: OrientationModifier,
45 pub auto_aim: bool,
48 pub vertical_angle_offset: f32,
52 pub marker: Option<FrontendMarker>,
54}
55
56#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
57pub struct Data {
58 pub static_data: StaticData,
61 pub timer: Duration,
63 pub stage_section: StageSection,
65 pub exhausted: bool,
67 pub movement_modifier: Option<f32>,
69 pub ori_modifier: Option<f32>,
71}
72
73impl CharacterBehavior for Data {
74 fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
75 let mut update = StateUpdate::from(data);
76
77 handle_orientation(data, &mut update, self.ori_modifier.unwrap_or(1.0), None);
78 handle_move(data, &mut update, self.movement_modifier.unwrap_or(0.7));
79 handle_jump(data, output_events, &mut update, 1.0);
80
81 match self.stage_section {
82 StageSection::Buildup => {
83 if self.timer < self.static_data.buildup_duration {
84 if let CharacterState::BasicRanged(c) = &mut update.character {
86 c.timer = tick_attack_or_default(data, self.timer, None);
87 }
88 match self.static_data.projectile_body {
89 Body::Object(LaserBeam) => {
90 output_events.emit_local(LocalEvent::CreateOutcome(
92 Outcome::CyclopsCharge {
93 pos: data.pos.0
94 + *data.ori.look_dir() * (data.body.max_radius()),
95 },
96 ));
97 },
98 Body::Object(GrenadeClay) => {
99 output_events.emit_local(LocalEvent::CreateOutcome(
101 Outcome::FuseCharge {
102 pos: data.pos.0
103 + *data.ori.look_dir() * (2.5 * data.body.max_radius()),
104 },
105 ));
106 },
107 Body::Object(LaserBeamSmall) => {
108 output_events.emit_local(LocalEvent::CreateOutcome(
109 Outcome::TerracottaStatueCharge {
110 pos: data.pos.0
111 + *data.ori.look_dir() * (data.body.max_radius()),
112 },
113 ));
114 },
115 Body::Object(FireRing) if self.timer == Duration::default() => {
116 output_events.emit_local(LocalEvent::CreateOutcome(
117 Outcome::FireBreathCharge {
118 pos: data.pos.0
119 + *data.ori.look_dir() * (data.body.max_radius()),
120 },
121 ));
122 },
123 _ => {},
124 }
125 } else {
126 if let CharacterState::BasicRanged(c) = &mut update.character {
128 c.timer = Duration::default();
129 c.stage_section = StageSection::Recover;
130 c.movement_modifier = c.static_data.movement_modifier.recover;
131 c.ori_modifier = c.static_data.ori_modifier.recover;
132 }
133 }
134 },
135 StageSection::Recover => {
136 if !self.exhausted {
137 let precision_mult = combat::compute_precision_mult(data.inventory, data.msm);
139 let (projectile, marker) =
140 self.static_data.projectile.clone().create_projectile(
141 Some(*data.uid),
142 precision_mult,
143 Some(self.static_data.ability_info),
144 Some(data.stats),
145 );
146 let num_projectiles = self
148 .static_data
149 .num_projectiles
150 .compute(data.heads.map_or(1, |heads| heads.amount() as u32));
151
152 let mut rng = rng();
153
154 let aim_dir = if self.static_data.ori_modifier.buildup.is_some() {
155 data.inputs.look_dir.merge_z(data.ori.look_dir())
156 } else {
157 data.inputs.look_dir
158 };
159
160 let aim_dir = if self.static_data.vertical_angle_offset != 0.0 {
163 let cross = vek::Vec3::unit_z().cross(*aim_dir).normalized();
164 Dir::from_unnormalized(
165 vek::Quaternion::rotation_3d(
166 -self.static_data.vertical_angle_offset,
167 cross,
168 ) * *aim_dir,
169 )
170 .unwrap_or(aim_dir)
171 } else {
172 aim_dir
173 };
174
175 let body_offsets = data
177 .body
178 .projectile_offsets(update.ori.look_vec(), data.scale.map_or(1.0, |s| s.0));
179 let pos = Pos(data.pos.0 + body_offsets);
180
181 let aim_dir = if self.static_data.auto_aim
182 && let Some(sel_pos) = self
183 .static_data
184 .ability_info
185 .input_attr
186 .and_then(|ia| ia.select_pos)
187 {
188 if let Some(ideal_dir) =
189 aim_projectile(self.static_data.projectile_speed, pos.0, sel_pos, true)
190 {
191 ideal_dir
192 } else {
193 aim_dir
194 }
195 } else {
196 aim_dir
197 };
198
199 let dirs = if let Some(spread) = self.static_data.projectile_spread {
200 Either::Left(spread.compute_directions(
201 aim_dir,
202 *data.ori,
203 num_projectiles,
204 &mut rng,
205 ))
206 } else {
207 Either::Right((0..num_projectiles).map(|_| aim_dir))
208 };
209
210 for dir in dirs {
211 output_events.emit_server(ShootEvent {
213 entity: Some(data.entity),
214 source_vel: Some(*data.vel),
215 pos,
216 dir,
217 body: self.static_data.projectile_body,
218 projectile: projectile.clone(),
219 light: self.static_data.projectile_light,
220 speed: self.static_data.projectile_speed
221 * data.stats.projectile_speed_mult,
222 object: None,
223 marker: self.static_data.marker.or(marker),
224 });
225 }
226
227 if let CharacterState::BasicRanged(c) = &mut update.character {
228 c.exhausted = true;
229 }
230 } else if self.timer < self.static_data.recover_duration {
231 if let CharacterState::BasicRanged(c) = &mut update.character {
233 c.timer = tick_attack_or_default(data, self.timer, None);
234 }
235 } else {
236 if input_is_pressed(data, self.static_data.ability_info.input) {
238 reset_state(self, data, output_events, &mut update);
239 } else {
240 end_ability(data, &mut update);
241 }
242 }
243 },
244 _ => {
245 end_ability(data, &mut update);
247 },
248 }
249
250 handle_interrupts(data, &mut update, output_events);
252
253 update
254 }
255}
256
257fn reset_state(
258 data: &Data,
259 join: &JoinData,
260 output_events: &mut OutputEvents,
261 update: &mut StateUpdate,
262) {
263 handle_input(
264 join,
265 output_events,
266 update,
267 data.static_data.ability_info.input,
268 );
269}