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 move_efficiency: f32,
41}
42
43#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
44pub struct Data {
45 pub static_data: StaticData,
48 pub timer: Duration,
50 pub stage_section: StageSection,
52 pub exhausted: bool,
54}
55
56impl CharacterBehavior for Data {
57 fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
58 let mut update = StateUpdate::from(data);
59
60 handle_orientation(data, &mut update, 1.0, None);
61 handle_move(data, &mut update, self.static_data.move_efficiency);
62 handle_jump(data, output_events, &mut update, 1.0);
63
64 match self.stage_section {
65 StageSection::Buildup => {
66 if self.timer < self.static_data.buildup_duration {
67 update.character = CharacterState::BasicRanged(Data {
69 timer: tick_attack_or_default(data, self.timer, None),
70 ..*self
71 });
72 match self.static_data.projectile_body {
73 Body::Object(LaserBeam) => {
74 output_events.emit_local(LocalEvent::CreateOutcome(
76 Outcome::CyclopsCharge {
77 pos: data.pos.0
78 + *data.ori.look_dir() * (data.body.max_radius()),
79 },
80 ));
81 },
82 Body::Object(GrenadeClay) => {
83 output_events.emit_local(LocalEvent::CreateOutcome(
85 Outcome::FuseCharge {
86 pos: data.pos.0
87 + *data.ori.look_dir() * (2.5 * data.body.max_radius()),
88 },
89 ));
90 },
91 Body::Object(LaserBeamSmall) => {
92 output_events.emit_local(LocalEvent::CreateOutcome(
93 Outcome::TerracottaStatueCharge {
94 pos: data.pos.0
95 + *data.ori.look_dir() * (data.body.max_radius()),
96 },
97 ));
98 },
99 _ => {},
100 }
101 } else {
102 update.character = CharacterState::BasicRanged(Data {
104 timer: Duration::default(),
105 stage_section: StageSection::Recover,
106 ..*self
107 });
108 }
109 },
110 StageSection::Recover => {
111 if !self.exhausted {
112 let precision_mult = combat::compute_precision_mult(data.inventory, data.msm);
114 let projectile = self.static_data.projectile.create_projectile(
115 Some(*data.uid),
116 precision_mult,
117 self.static_data.damage_effect,
118 );
119 let num_projectiles = self
121 .static_data
122 .num_projectiles
123 .compute(data.heads.map_or(1, |heads| heads.amount() as u32));
124
125 for i in 0..num_projectiles {
126 let body_offsets = data.body.projectile_offsets(
128 update.ori.look_vec(),
129 data.scale.map_or(1.0, |s| s.0),
130 );
131 let pos = Pos(data.pos.0 + body_offsets);
132 let dir = Dir::from_unnormalized(data.inputs.look_dir.map(|x| {
135 let offset = (2.0 * thread_rng().gen::<f32>() - 1.0)
136 * self.static_data.projectile_spread
137 * i as f32;
138 x + offset
139 }))
140 .unwrap_or(data.inputs.look_dir);
141 output_events.emit_server(ShootEvent {
143 entity: data.entity,
144 pos,
145 dir,
146 body: self.static_data.projectile_body,
147 projectile: projectile.clone(),
148 light: self.static_data.projectile_light,
149 speed: self.static_data.projectile_speed,
150 object: None,
151 });
152 }
153
154 update.character = CharacterState::BasicRanged(Data {
155 exhausted: true,
156 ..*self
157 });
158 } else if self.timer < self.static_data.recover_duration {
159 update.character = CharacterState::BasicRanged(Data {
161 timer: tick_attack_or_default(
162 data,
163 self.timer,
164 Some(data.stats.recovery_speed_modifier),
165 ),
166 ..*self
167 });
168 } else {
169 if input_is_pressed(data, self.static_data.ability_info.input) {
171 reset_state(self, data, output_events, &mut update);
172 } else {
173 end_ability(data, &mut update);
174 }
175 }
176 },
177 _ => {
178 end_ability(data, &mut update);
180 },
181 }
182
183 handle_interrupts(data, &mut update, output_events);
185
186 update
187 }
188}
189
190fn reset_state(
191 data: &Data,
192 join: &JoinData,
193 output_events: &mut OutputEvents,
194 update: &mut StateUpdate,
195) {
196 handle_input(
197 join,
198 output_events,
199 update,
200 data.static_data.ability_info.input,
201 );
202}