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, _marker) = projectile.create_projectile(
142 Some(*data.uid),
143 precision_mult,
144 Some(self.static_data.ability_info),
145 Some(data.stats),
146 );
147
148 output_events.emit_server(ThrowEvent {
151 entity: data.entity,
152 pos,
153 dir,
154 projectile,
155 light: self.static_data.projectile_light,
156 speed: self.static_data.initial_projectile_speed
157 + charge_frac * self.static_data.scaled_projectile_speed,
158 object: None,
159 equip_slot: self.static_data.equip_slot,
160 });
161
162 if let CharacterState::Throw(c) = &mut update.character {
164 c.timer = Duration::default();
165 c.stage_section = StageSection::Action;
166 c.exhausted = true;
167 }
168 } else if self.timer < self.static_data.charge_duration
169 && input_is_pressed(data, self.static_data.ability_info.input)
170 {
171 if let CharacterState::Throw(c) = &mut update.character {
173 c.timer = tick_attack_or_default(data, self.timer, None);
174 }
175
176 update
178 .energy
179 .change_by(-self.static_data.energy_drain * data.dt.0);
180 } else if input_is_pressed(data, self.static_data.ability_info.input) {
181 if let CharacterState::Throw(c) = &mut update.character {
183 c.timer = tick_attack_or_default(data, self.timer, None);
184 }
185
186 update
188 .energy
189 .change_by(-self.static_data.energy_drain * data.dt.0 / 5.0);
190 }
191 },
192 StageSection::Action => {
193 if self.timer < self.static_data.throw_duration {
194 if let CharacterState::Throw(c) = &mut update.character {
196 c.timer = tick_attack_or_default(data, self.timer, None);
197 }
198 } else {
199 if let CharacterState::Throw(c) = &mut update.character {
201 c.timer = Duration::default();
202 c.stage_section = StageSection::Recover;
203 }
204 }
205 },
206 StageSection::Recover => {
207 if self.timer < self.static_data.recover_duration {
208 if let CharacterState::Throw(c) = &mut update.character {
210 c.timer = tick_attack_or_default(data, self.timer, None);
211 }
212 } else {
213 end_ability(data, &mut update);
215 }
216 },
217 _ => {
218 end_ability(data, &mut update);
220 },
221 }
222
223 handle_interrupts(data, &mut update, output_events);
225
226 update
227 }
228}