1use crate::{
2 combat::{self, CombatEffect},
3 comp::{
4 Body, CharacterState, LightEmitter, Pos, ProjectileConstructor, StateUpdate,
5 character_state::OutputEvents,
6 },
7 event::{EnergyChangeEvent, ShootEvent},
8 states::{
9 behavior::{CharacterBehavior, JoinData},
10 utils::{StageSection, *},
11 },
12 util::Dir,
13};
14use rand::{Rng, thread_rng};
15use serde::{Deserialize, Serialize};
16use std::{f32::consts::TAU, time::Duration};
17
18#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
19pub struct StaticData {
21 pub buildup_duration: Duration,
23 pub shoot_duration: Duration,
25 pub recover_duration: Duration,
27 pub energy_cost: f32,
29 pub max_speed: f32,
31 pub half_speed_at: u32,
33 pub projectile: ProjectileConstructor,
35 pub projectile_body: Body,
36 pub projectile_light: Option<LightEmitter>,
37 pub projectile_speed: f32,
38 pub ability_info: AbilityInfo,
40 pub damage_effect: Option<CombatEffect>,
42 pub properties_of_aoe: Option<ProjectileOffset>,
45 pub specifier: Option<FrontendSpecifier>,
47}
48
49#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
50pub struct ProjectileOffset {
51 pub radius: f32,
53 pub height: f32,
55}
56
57#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
58pub struct Data {
59 pub static_data: StaticData,
62 pub timer: Duration,
64 pub stage_section: StageSection,
66 pub speed: f32,
68 pub projectiles_fired: u32,
70}
71
72impl CharacterBehavior for Data {
73 fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
74 let mut update = StateUpdate::from(data);
75 handle_orientation(data, &mut update, 1.0, None);
76 handle_move(data, &mut update, 0.3);
77
78 match self.stage_section {
79 StageSection::Buildup => {
80 if self.timer < self.static_data.buildup_duration {
81 update.character = CharacterState::RepeaterRanged(Data {
83 timer: tick_attack_or_default(data, self.timer, None),
84 ..*self
85 });
86 } else {
87 update.character = CharacterState::RepeaterRanged(Data {
89 timer: Duration::default(),
90 stage_section: StageSection::Action,
91 ..*self
92 });
93 }
94 },
95 StageSection::Action => {
96 if self.timer < self.static_data.shoot_duration {
97 update.character = CharacterState::RepeaterRanged(Data {
99 timer: self
100 .timer
101 .checked_add(Duration::from_secs_f32(data.dt.0 * self.speed))
102 .unwrap_or_default(),
103 ..*self
104 });
105 } else if input_is_pressed(data, self.static_data.ability_info.input)
106 && update.energy.current() >= self.static_data.energy_cost
107 {
108 let precision_mult = combat::compute_precision_mult(data.inventory, data.msm);
110 let pos: Pos = self.static_data.properties_of_aoe.as_ref().map_or_else(
112 || {
113 let body_offsets = data.body.projectile_offsets(
115 update.ori.look_vec(),
116 data.scale.map_or(1.0, |s| s.0),
117 );
118 Pos(data.pos.0 + body_offsets)
119 },
120 |aoe_data| {
121 let rand_pos = {
123 let mut rng = thread_rng();
124 let theta = rng.gen::<f32>() * TAU;
125 let radius = aoe_data.radius * rng.gen::<f32>().sqrt();
126 let x = radius * theta.sin();
127 let y = radius * theta.cos();
128 vek::Vec2::new(x, y)
129 };
130 Pos(data.pos.0 + rand_pos.with_z(aoe_data.height))
131 },
132 );
133
134 let direction: Dir = if self.static_data.properties_of_aoe.is_some() {
135 Dir::down()
136 } else {
137 data.inputs.look_dir
138 };
139
140 let projectile = self.static_data.projectile.create_projectile(
141 Some(*data.uid),
142 precision_mult,
143 self.static_data.damage_effect,
144 );
145 output_events.emit_server(ShootEvent {
146 entity: data.entity,
147 pos,
148 dir: direction,
149 body: self.static_data.projectile_body,
150 projectile,
151 light: self.static_data.projectile_light,
152 speed: self.static_data.projectile_speed,
153 object: None,
154 });
155
156 output_events.emit_server(EnergyChangeEvent {
158 entity: data.entity,
159 change: -self.static_data.energy_cost,
160 reset_rate: false,
161 });
162
163 let new_speed = 1.0
165 + self.projectiles_fired as f32
166 / (self.static_data.half_speed_at as f32
167 + self.projectiles_fired as f32)
168 * self.static_data.max_speed;
169
170 update.character = CharacterState::RepeaterRanged(Data {
171 timer: Duration::default(),
172 speed: new_speed,
173 projectiles_fired: self.projectiles_fired + 1,
174 ..*self
175 });
176 } else {
177 update.character = CharacterState::RepeaterRanged(Data {
179 timer: Duration::default(),
180 stage_section: StageSection::Recover,
181 ..*self
182 });
183 }
184 },
185 StageSection::Recover => {
186 if self.timer < self.static_data.recover_duration {
187 update.character = CharacterState::RepeaterRanged(Data {
189 timer: tick_attack_or_default(
190 data,
191 self.timer,
192 Some(data.stats.recovery_speed_modifier),
193 ),
194 ..*self
195 });
196 } else {
197 end_ability(data, &mut update);
199 }
200 },
201 _ => {
202 end_ability(data, &mut update);
204 },
205 }
206
207 handle_interrupts(data, &mut update, output_events);
209
210 update
211 }
212}
213
214#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
215pub enum FrontendSpecifier {
216 FireRain,
217}