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)
95 || update.energy.current() < 1.0)
96 && !self.exhausted
97 {
98 let charge_frac = self.charge_frac();
99 let precision_mult = combat::compute_precision_mult(data.inventory, data.msm);
101 let body_offsets = data
103 .body
104 .projectile_offsets(update.ori.look_vec(), data.scale.map_or(1.0, |s| s.0));
105 let pos = Pos(data.pos.0 + body_offsets);
106 let (projectile, marker) = self
107 .static_data
108 .projectile
109 .clone()
110 .handle_scaling(charge_frac)
111 .create_projectile(
112 Some(*data.uid),
113 precision_mult,
114 Some(self.static_data.ability_info),
115 Some(data.stats),
116 );
117
118 let num_projectiles = self
119 .static_data
120 .num_projectiles
121 .compute(data.heads.map_or(1, |heads| heads.amount() as u32));
122
123 let mut rng = rng();
124 let dirs = if let Some(spread) = self.static_data.projectile_spread {
125 Either::Left(spread.compute_directions(
126 data.inputs.look_dir,
127 *data.ori,
128 num_projectiles,
129 &mut rng,
130 ))
131 } else {
132 Either::Right((0..num_projectiles).map(|_| data.inputs.look_dir))
133 };
134
135 for dir in dirs {
136 output_events.emit_server(ShootEvent {
137 entity: Some(data.entity),
138 source_vel: Some(*data.vel),
139 pos,
140 dir,
141 body: self.static_data.projectile_body,
142 projectile: projectile.clone(),
143 light: self.static_data.projectile_light,
144 speed: (self.static_data.initial_projectile_speed
145 + charge_frac * self.static_data.scaled_projectile_speed)
146 * data.stats.projectile_speed_mult,
147 object: None,
148 marker: self.static_data.marker.or(marker),
149 });
150 }
151
152 if let CharacterState::ChargedRanged(c) = &mut update.character {
153 c.timer = Duration::default();
154 c.stage_section = StageSection::Recover;
155 c.exhausted = true;
156 }
157 } else if self.timer < self.static_data.charge_duration
158 && input_is_pressed(data, self.static_data.ability_info.input)
159 {
160 if let CharacterState::ChargedRanged(c) = &mut update.character {
162 c.timer = tick_attack_or_default(data, self.timer, None);
163 }
164
165 update
167 .energy
168 .change_by(-self.static_data.energy_drain * data.dt.0);
169 } else if input_is_pressed(data, self.static_data.ability_info.input) {
170 if let CharacterState::ChargedRanged(c) = &mut update.character {
172 c.timer = tick_attack_or_default(data, self.timer, None);
173 }
174
175 update
177 .energy
178 .change_by(-self.static_data.idle_drain * data.dt.0);
179 }
180 },
181 StageSection::Recover => {
182 if self.timer < self.static_data.recover_duration {
183 if let CharacterState::ChargedRanged(c) = &mut update.character {
185 c.timer = tick_attack_or_default(data, self.timer, None);
186 }
187 } else {
188 end_ability(data, &mut update);
190 }
191 },
192 _ => {
193 end_ability(data, &mut update);
195 },
196 }
197
198 handle_interrupts(data, &mut update, output_events);
200
201 update
202 }
203}