1use crate::{
2 combat::{self, CombatEffect},
3 comp::{
4 CharacterState, LightEmitter, Pos, ProjectileConstructor, StateUpdate,
5 character_state::OutputEvents, item::ToolKind, slot::EquipSlot,
6 },
7 event::ThrowEvent,
8 states::{
9 behavior::{CharacterBehavior, JoinData},
10 utils::*,
11 },
12 util::Dir,
13};
14use serde::{Deserialize, Serialize};
15use std::time::Duration;
16
17#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
18pub enum ProjectileDir {
19 LookDir,
21 Upwards(f32),
24}
25
26#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
28pub struct StaticData {
29 pub buildup_duration: Duration,
31 pub charge_duration: Duration,
33 pub throw_duration: Duration,
35 pub recover_duration: Duration,
37 pub energy_drain: f32,
39 pub projectile: ProjectileConstructor,
41 pub projectile_light: Option<LightEmitter>,
42 pub projectile_dir: ProjectileDir,
43 pub initial_projectile_speed: f32,
44 pub scaled_projectile_speed: f32,
45 pub move_speed: f32,
47 pub ability_info: AbilityInfo,
49 pub damage_effect: Option<CombatEffect>,
51 pub equip_slot: EquipSlot,
53 pub item_hash: u64,
55 pub tool_kind: ToolKind,
58 pub hand_info: HandInfo,
61}
62
63#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
64pub struct Data {
65 pub static_data: StaticData,
68 pub timer: Duration,
70 pub stage_section: StageSection,
72 pub exhausted: bool,
74}
75
76impl Data {
77 pub fn charge_frac(&self) -> f32 {
79 if let StageSection::Charge = self.stage_section {
80 (self.timer.as_secs_f32() / self.static_data.charge_duration.as_secs_f32()).min(1.0)
81 } else {
82 0.0
83 }
84 }
85}
86
87impl CharacterBehavior for Data {
88 fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
89 let mut update = StateUpdate::from(data);
90
91 handle_orientation(data, &mut update, 1.0, None);
92 handle_move(data, &mut update, self.static_data.move_speed);
93 handle_jump(data, output_events, &mut update, 1.0);
94
95 match self.stage_section {
96 StageSection::Buildup => {
97 if self.timer < self.static_data.buildup_duration {
98 if let CharacterState::Throw(c) = &mut update.character {
100 c.timer = tick_attack_or_default(data, self.timer, None);
101 }
102 } else {
103 if let CharacterState::Throw(c) = &mut update.character {
105 c.timer = Duration::default();
106 c.stage_section = StageSection::Charge;
107 }
108 }
109 },
110 StageSection::Charge => {
111 if !input_is_pressed(data, self.static_data.ability_info.input)
112 && !self.exhausted
113 && data
114 .inventory
115 .and_then(|inv| inv.equipped(self.static_data.equip_slot))
116 .is_some_and(|item| item.item_hash() == self.static_data.item_hash)
117 {
118 let charge_frac = self.charge_frac();
119
120 let body_offsets = data
121 .body
122 .projectile_offsets(update.ori.look_vec(), data.scale.map_or(1.0, |s| s.0));
123 let pos = Pos(data.pos.0 + body_offsets);
124
125 let dir = match self.static_data.projectile_dir {
126 ProjectileDir::LookDir => data.inputs.look_dir,
127 ProjectileDir::Upwards(interpolation_factor) => {
128 Dir::up().slerped_to(data.inputs.look_dir, interpolation_factor)
129 },
130 };
131
132 let precision_mult = combat::compute_precision_mult(data.inventory, data.msm);
133 let projectile = {
134 let projectile = self.static_data.projectile.clone();
135 if self.static_data.projectile.scaled.is_some() {
136 projectile.handle_scaling(charge_frac)
137 } else {
138 projectile
139 }
140 };
141 let projectile = projectile.create_projectile(
142 Some(*data.uid),
143 precision_mult,
144 Some(self.static_data.ability_info),
145 );
146
147 output_events.emit_server(ThrowEvent {
150 entity: data.entity,
151 pos,
152 dir,
153 projectile,
154 light: self.static_data.projectile_light,
155 speed: self.static_data.initial_projectile_speed
156 + charge_frac * self.static_data.scaled_projectile_speed,
157 object: None,
158 equip_slot: self.static_data.equip_slot,
159 });
160
161 if let CharacterState::Throw(c) = &mut update.character {
163 c.timer = Duration::default();
164 c.stage_section = StageSection::Action;
165 c.exhausted = true;
166 }
167 } else if self.timer < self.static_data.charge_duration
168 && input_is_pressed(data, self.static_data.ability_info.input)
169 {
170 if let CharacterState::Throw(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.energy_drain * data.dt.0);
179 } else if input_is_pressed(data, self.static_data.ability_info.input) {
180 if let CharacterState::Throw(c) = &mut update.character {
182 c.timer = tick_attack_or_default(data, self.timer, None);
183 }
184
185 update
187 .energy
188 .change_by(-self.static_data.energy_drain * data.dt.0 / 5.0);
189 }
190 },
191 StageSection::Action => {
192 if self.timer < self.static_data.throw_duration {
193 if let CharacterState::Throw(c) = &mut update.character {
195 c.timer = tick_attack_or_default(data, self.timer, None);
196 }
197 } else {
198 if let CharacterState::Throw(c) = &mut update.character {
200 c.timer = Duration::default();
201 c.stage_section = StageSection::Recover;
202 }
203 }
204 },
205 StageSection::Recover => {
206 if self.timer < self.static_data.recover_duration {
207 if let CharacterState::Throw(c) = &mut update.character {
209 c.timer = tick_attack_or_default(
210 data,
211 self.timer,
212 Some(data.stats.recovery_speed_modifier),
213 );
214 }
215 } else {
216 end_ability(data, &mut update);
218 }
219 },
220 _ => {
221 end_ability(data, &mut update);
223 },
224 }
225
226 handle_interrupts(data, &mut update, output_events);
228
229 update
230 }
231}