1use crate::{
2 combat,
3 comp::{
4 Body, CharacterState, FrontendMarker, LightEmitter, Pos, StateUpdate, ability::Amount,
5 character_state::OutputEvents, projectile::ProjectileConstructor,
6 },
7 event::ShootEvent,
8 states::{
9 behavior::{CharacterBehavior, JoinData},
10 utils::*,
11 },
12};
13use itertools::Either;
14use rand::rng;
15use serde::{Deserialize, Serialize};
16use std::time::Duration;
17
18#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
20pub struct StaticData {
21 pub buildup_duration: Duration,
23 pub charge_duration: Duration,
25 pub recover_duration: Duration,
27 pub energy_drain: f32,
29 pub idle_drain: f32,
31 pub projectile: ProjectileConstructor,
33 pub projectile_body: Body,
34 pub projectile_light: Option<LightEmitter>,
35 pub initial_projectile_speed: f32,
36 pub scaled_projectile_speed: f32,
37 pub projectile_spread: Option<ProjectileSpread>,
38 pub num_projectiles: Amount,
39 pub marker: Option<FrontendMarker>,
40 pub move_speed: f32,
42 pub ability_info: AbilityInfo,
44}
45
46#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
47pub struct Data {
48 pub static_data: StaticData,
51 pub timer: Duration,
53 pub stage_section: StageSection,
55 pub exhausted: bool,
57}
58
59impl Data {
60 pub fn charge_frac(&self) -> f32 {
62 if let StageSection::Charge = self.stage_section {
63 (self.timer.as_secs_f32() / self.static_data.charge_duration.as_secs_f32()).min(1.0)
64 } else {
65 0.0
66 }
67 }
68}
69
70impl CharacterBehavior for Data {
71 fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
72 let mut update = StateUpdate::from(data);
73
74 handle_orientation(data, &mut update, 1.0, None);
75 handle_move(data, &mut update, self.static_data.move_speed);
76 handle_jump(data, output_events, &mut update, 1.0);
77
78 match self.stage_section {
79 StageSection::Buildup => {
80 if self.timer < self.static_data.buildup_duration {
81 if let CharacterState::ChargedRanged(c) = &mut update.character {
83 c.timer = tick_attack_or_default(data, self.timer, None);
84 }
85 } else {
86 if let CharacterState::ChargedRanged(c) = &mut update.character {
88 c.timer = Duration::default();
89 c.stage_section = StageSection::Charge;
90 }
91 }
92 },
93 StageSection::Charge => {
94 if !input_is_pressed(data, self.static_data.ability_info.input) && !self.exhausted {
95 let charge_frac = self.charge_frac();
96 let precision_mult = combat::compute_precision_mult(data.inventory, data.msm);
98 let body_offsets = data
100 .body
101 .projectile_offsets(update.ori.look_vec(), data.scale.map_or(1.0, |s| s.0));
102 let pos = Pos(data.pos.0 + body_offsets);
103 let projectile = self
104 .static_data
105 .projectile
106 .clone()
107 .handle_scaling(charge_frac)
108 .create_projectile(
109 Some(*data.uid),
110 precision_mult,
111 Some(self.static_data.ability_info),
112 );
113
114 let num_projectiles = self
115 .static_data
116 .num_projectiles
117 .compute(data.heads.map_or(1, |heads| heads.amount() as u32));
118
119 let mut rng = rng();
120 let dirs = if let Some(spread) = self.static_data.projectile_spread {
121 Either::Left(spread.compute_directions(
122 data.inputs.look_dir,
123 *data.ori,
124 num_projectiles,
125 &mut rng,
126 ))
127 } else {
128 Either::Right((0..num_projectiles).map(|_| data.inputs.look_dir))
129 };
130
131 for dir in dirs {
132 output_events.emit_server(ShootEvent {
133 entity: Some(data.entity),
134 source_vel: Some(*data.vel),
135 pos,
136 dir,
137 body: self.static_data.projectile_body,
138 projectile: projectile.clone(),
139 light: self.static_data.projectile_light,
140 speed: self.static_data.initial_projectile_speed
141 + charge_frac * self.static_data.scaled_projectile_speed,
142 object: None,
143 marker: self.static_data.marker,
144 });
145 }
146
147 if let CharacterState::ChargedRanged(c) = &mut update.character {
148 c.timer = Duration::default();
149 c.stage_section = StageSection::Recover;
150 c.exhausted = true;
151 }
152 } else if self.timer < self.static_data.charge_duration
153 && input_is_pressed(data, self.static_data.ability_info.input)
154 {
155 if let CharacterState::ChargedRanged(c) = &mut update.character {
157 c.timer = tick_attack_or_default(data, self.timer, None);
158 }
159
160 update
162 .energy
163 .change_by(-self.static_data.energy_drain * data.dt.0);
164 } else if input_is_pressed(data, self.static_data.ability_info.input) {
165 if let CharacterState::ChargedRanged(c) = &mut update.character {
167 c.timer = tick_attack_or_default(data, self.timer, None);
168 }
169
170 update
172 .energy
173 .change_by(-self.static_data.idle_drain * data.dt.0);
174 }
175 },
176 StageSection::Recover => {
177 if self.timer < self.static_data.recover_duration {
178 if let CharacterState::ChargedRanged(c) = &mut update.character {
180 c.timer = tick_attack_or_default(
181 data,
182 self.timer,
183 Some(data.stats.recovery_speed_modifier),
184 );
185 }
186 } else {
187 end_ability(data, &mut update);
189 }
190 },
191 _ => {
192 end_ability(data, &mut update);
194 },
195 }
196
197 handle_interrupts(data, &mut update, output_events);
199
200 update
201 }
202}