1use crate::{
2 combat::{self, CombatEffect},
3 comp::{
4 Body, CharacterState, LightEmitter, Pos, ProjectileConstructor, StateUpdate,
5 ability::Amount,
6 character_state::OutputEvents,
7 object::Body::{GrenadeClay, LaserBeam, LaserBeamSmall},
8 },
9 event::{LocalEvent, ShootEvent},
10 outcome::Outcome,
11 states::{
12 behavior::{CharacterBehavior, JoinData},
13 utils::*,
14 },
15 util::Dir,
16};
17use rand::{Rng, thread_rng};
18use serde::{Deserialize, Serialize};
19use std::time::Duration;
20
21#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
23pub struct StaticData {
24 pub buildup_duration: Duration,
26 pub recover_duration: Duration,
28 pub projectile_spread: f32,
30 pub projectile: ProjectileConstructor,
32 pub projectile_body: Body,
33 pub projectile_light: Option<LightEmitter>,
34 pub projectile_speed: f32,
35 pub num_projectiles: Amount,
37 pub ability_info: AbilityInfo,
39 pub damage_effect: Option<CombatEffect>,
40 pub movement_modifier: MovementModifier,
42 pub ori_modifier: OrientationModifier,
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 pub movement_modifier: Option<f32>,
59 pub ori_modifier: Option<f32>,
61}
62
63impl CharacterBehavior for Data {
64 fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
65 let mut update = StateUpdate::from(data);
66
67 handle_orientation(data, &mut update, self.ori_modifier.unwrap_or(1.0), None);
68 handle_move(data, &mut update, self.movement_modifier.unwrap_or(0.7));
69 handle_jump(data, output_events, &mut update, 1.0);
70
71 match self.stage_section {
72 StageSection::Buildup => {
73 if self.timer < self.static_data.buildup_duration {
74 update.character = CharacterState::BasicRanged(Data {
76 timer: tick_attack_or_default(data, self.timer, None),
77 ..*self
78 });
79 match self.static_data.projectile_body {
80 Body::Object(LaserBeam) => {
81 output_events.emit_local(LocalEvent::CreateOutcome(
83 Outcome::CyclopsCharge {
84 pos: data.pos.0
85 + *data.ori.look_dir() * (data.body.max_radius()),
86 },
87 ));
88 },
89 Body::Object(GrenadeClay) => {
90 output_events.emit_local(LocalEvent::CreateOutcome(
92 Outcome::FuseCharge {
93 pos: data.pos.0
94 + *data.ori.look_dir() * (2.5 * data.body.max_radius()),
95 },
96 ));
97 },
98 Body::Object(LaserBeamSmall) => {
99 output_events.emit_local(LocalEvent::CreateOutcome(
100 Outcome::TerracottaStatueCharge {
101 pos: data.pos.0
102 + *data.ori.look_dir() * (data.body.max_radius()),
103 },
104 ));
105 },
106 _ => {},
107 }
108 } else {
109 update.character = CharacterState::BasicRanged(Data {
111 timer: Duration::default(),
112 stage_section: StageSection::Recover,
113 movement_modifier: self.static_data.movement_modifier.recover,
114 ori_modifier: self.static_data.ori_modifier.recover,
115 ..*self
116 });
117 }
118 },
119 StageSection::Recover => {
120 if !self.exhausted {
121 let precision_mult = combat::compute_precision_mult(data.inventory, data.msm);
123 let projectile = self.static_data.projectile.create_projectile(
124 Some(*data.uid),
125 precision_mult,
126 self.static_data.damage_effect,
127 );
128 let num_projectiles = self
130 .static_data
131 .num_projectiles
132 .compute(data.heads.map_or(1, |heads| heads.amount() as u32));
133
134 for i in 0..num_projectiles {
135 let body_offsets = data.body.projectile_offsets(
137 update.ori.look_vec(),
138 data.scale.map_or(1.0, |s| s.0),
139 );
140 let pos = Pos(data.pos.0 + body_offsets);
141
142 let dir = {
143 let look_dir = if self.static_data.ori_modifier.buildup.is_some() {
144 data.inputs.look_dir.merge_z(data.ori.look_dir())
145 } else {
146 data.inputs.look_dir
147 };
148
149 Dir::from_unnormalized(look_dir.map(|x| {
153 let offset = (2.0 * thread_rng().gen::<f32>() - 1.0)
154 * self.static_data.projectile_spread
155 * i as f32;
156 x + offset
157 }))
158 .unwrap_or(data.inputs.look_dir)
159 };
160
161 output_events.emit_server(ShootEvent {
163 entity: Some(data.entity),
164 pos,
165 dir,
166 body: self.static_data.projectile_body,
167 projectile: projectile.clone(),
168 light: self.static_data.projectile_light,
169 speed: self.static_data.projectile_speed,
170 object: None,
171 });
172 }
173
174 update.character = CharacterState::BasicRanged(Data {
175 exhausted: true,
176 ..*self
177 });
178 } else if self.timer < self.static_data.recover_duration {
179 update.character = CharacterState::BasicRanged(Data {
181 timer: tick_attack_or_default(
182 data,
183 self.timer,
184 Some(data.stats.recovery_speed_modifier),
185 ),
186 ..*self
187 });
188 } else {
189 if input_is_pressed(data, self.static_data.ability_info.input) {
191 reset_state(self, data, output_events, &mut update);
192 } else {
193 end_ability(data, &mut update);
194 }
195 }
196 },
197 _ => {
198 end_ability(data, &mut update);
200 },
201 }
202
203 handle_interrupts(data, &mut update, output_events);
205
206 update
207 }
208}
209
210fn reset_state(
211 data: &Data,
212 join: &JoinData,
213 output_events: &mut OutputEvents,
214 update: &mut StateUpdate,
215) {
216 handle_input(
217 join,
218 output_events,
219 update,
220 data.static_data.ability_info.input,
221 );
222}