1use crate::{
2 combat::{
3 self, Attack, AttackDamage, AttackEffect, CombatEffect, CombatRequirement, Damage,
4 DamageKind, DamageSource, GroupTarget,
5 },
6 comp::{
7 Body, CharacterState, StateUpdate,
8 ability::Dodgeable,
9 beam,
10 body::{biped_large, bird_large, golem},
11 character_state::OutputEvents,
12 object::Body::{Flamethrower, Lavathrower},
13 },
14 event::LocalEvent,
15 outcome::Outcome,
16 resources::Secs,
17 states::{
18 behavior::{CharacterBehavior, JoinData},
19 utils::*,
20 },
21 terrain::Block,
22 util::Dir,
23};
24use hashbrown::HashMap;
25use serde::{Deserialize, Serialize};
26use std::time::Duration;
27use vek::*;
28
29#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
31pub struct StaticData {
32 pub buildup_duration: Duration,
34 pub recover_duration: Duration,
36 pub beam_duration: Secs,
38 pub damage: f32,
40 pub tick_rate: f32,
42 pub range: f32,
44 #[serde(default)]
46 pub dodgeable: Dodgeable,
47 pub end_radius: f32,
50 pub damage_effect: Option<CombatEffect>,
52 pub energy_regen: f32,
54 pub energy_drain: f32,
56 pub ori_rate: f32,
58 pub move_efficiency: f32,
60 pub ability_info: AbilityInfo,
62 pub specifier: beam::FrontendSpecifier,
64}
65
66#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
67pub struct Data {
68 pub static_data: StaticData,
71 pub timer: Duration,
73 pub stage_section: StageSection,
75 pub aim_dir: Dir,
77 pub beam_offset: Vec3<f32>,
79}
80
81impl CharacterBehavior for Data {
82 fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
83 let mut update = StateUpdate::from(data);
84
85 let ori_rate = self.static_data.ori_rate;
86
87 handle_orientation(data, &mut update, ori_rate, None);
88 handle_move(data, &mut update, self.static_data.move_efficiency);
89 handle_jump(data, output_events, &mut update, 1.0);
90
91 let rel_vel = data.vel.0 - data.physics.ground_vel;
93 let body_offsets = beam_offsets(
95 data.body,
96 data.inputs.look_dir,
97 update.ori.look_vec(),
98 rel_vel,
99 data.physics.on_ground,
100 );
101
102 match self.stage_section {
103 StageSection::Buildup => {
104 if self.timer < self.static_data.buildup_duration {
105 update.character = CharacterState::BasicBeam(Data {
107 timer: tick_attack_or_default(data, self.timer, None),
108 ..*self
109 });
110 if matches!(data.body, Body::Object(Flamethrower | Lavathrower)) {
111 output_events.emit_local(LocalEvent::CreateOutcome(
113 Outcome::FlamethrowerCharge {
114 pos: data.pos.0 + *data.ori.look_dir() * (data.body.max_radius()),
115 },
116 ));
117 }
118 } else {
119 let attack = {
120 let energy = AttackEffect::new(
121 None,
122 CombatEffect::EnergyReward(self.static_data.energy_regen),
123 )
124 .with_requirement(CombatRequirement::AnyDamage);
125 let mut damage = AttackDamage::new(
126 Damage {
127 source: DamageSource::Energy,
128 kind: DamageKind::Energy,
129 value: self.static_data.damage,
130 },
131 Some(GroupTarget::OutOfGroup),
132 rand::random(),
133 );
134 if let Some(effect) = self.static_data.damage_effect {
135 damage = damage.with_effect(effect);
136 }
137 let precision_mult =
138 combat::compute_precision_mult(data.inventory, data.msm);
139 Attack::default()
140 .with_damage(damage)
141 .with_precision(precision_mult)
142 .with_effect(energy)
143 .with_combo_increment()
144 };
145
146 data.updater.insert(data.entity, beam::Beam {
148 attack,
149 dodgeable: self.static_data.dodgeable,
150 start_radius: 0.0,
151 end_radius: self.static_data.end_radius,
152 range: self.static_data.range,
153 duration: self.static_data.beam_duration,
154 tick_dur: Secs(1.0 / self.static_data.tick_rate as f64),
155 hit_entities: Vec::new(),
156 hit_durations: HashMap::new(),
157 specifier: self.static_data.specifier,
158 bezier: QuadraticBezier3 {
159 start: data.pos.0 + body_offsets,
160 ctrl: data.pos.0 + body_offsets,
161 end: data.pos.0 + body_offsets,
162 },
163 });
164 update.character = CharacterState::BasicBeam(Data {
166 beam_offset: body_offsets,
167 timer: Duration::default(),
168 stage_section: StageSection::Action,
169 ..*self
170 });
171 }
172 },
173 StageSection::Action => {
174 if input_is_pressed(data, self.static_data.ability_info.input)
175 && (self.static_data.energy_drain <= f32::EPSILON
176 || update.energy.current() > 0.0)
177 {
178 let beam_dir = data.inputs.look_dir.merge_z(data.ori.look_dir());
182
183 update.character = CharacterState::BasicBeam(Data {
184 beam_offset: body_offsets,
185 aim_dir: beam_dir,
186 timer: tick_attack_or_default(data, self.timer, None),
187 ..*self
188 });
189
190 update
192 .energy
193 .change_by(-self.static_data.energy_drain * data.dt.0);
194 } else {
195 update.character = CharacterState::BasicBeam(Data {
196 timer: Duration::default(),
197 stage_section: StageSection::Recover,
198 ..*self
199 });
200 }
201 },
202 StageSection::Recover => {
203 if self.timer < self.static_data.recover_duration {
204 update.character = CharacterState::BasicBeam(Data {
205 timer: tick_attack_or_default(
206 data,
207 self.timer,
208 Some(data.stats.recovery_speed_modifier),
209 ),
210 ..*self
211 });
212 } else {
213 end_ability(data, &mut update);
215 data.updater.remove::<beam::Beam>(data.entity);
217 }
218 },
219 _ => {
220 end_ability(data, &mut update);
222 data.updater.remove::<beam::Beam>(data.entity);
224 },
225 }
226
227 handle_interrupts(data, &mut update, output_events);
229
230 update
231 }
232}
233
234fn height_offset(body: &Body, look_dir: Dir, velocity: Vec3<f32>, on_ground: Option<Block>) -> f32 {
235 match body {
236 Body::BirdLarge(b) => {
238 let height_factor = match b.species {
239 bird_large::Species::Phoenix => 0.5,
240 bird_large::Species::Cockatrice => 0.4,
241 _ => 0.3,
242 };
243 body.height() * height_factor
244 + if on_ground.is_none() {
245 (2.0 - velocity.xy().magnitude() * 0.25).max(-1.0)
246 } else {
247 0.0
248 }
249 },
250 Body::Golem(b) => {
251 let height_factor = match b.species {
252 golem::Species::Mogwai => 0.4,
253 _ => 0.9,
254 };
255 const DIR_COEFF: f32 = 2.0;
256 body.height() * height_factor + look_dir.z * DIR_COEFF
257 },
258 Body::BipedLarge(b) => match b.species {
259 biped_large::Species::Mindflayer => body.height() * 0.6,
260 biped_large::Species::SeaBishop => body.height() * 0.4,
261 biped_large::Species::Cursekeeper => body.height() * 0.8,
262 biped_large::Species::Gigasfire => body.height() * 0.18,
263 _ => body.height() * 0.5,
264 },
265 _ => body.height() * 0.5,
266 }
267}
268
269pub fn beam_offsets(
270 body: &Body,
271 look_dir: Dir,
272 ori: Vec3<f32>,
273 velocity: Vec3<f32>,
274 on_ground: Option<Block>,
275) -> Vec3<f32> {
276 let dim = body.dimensions();
277 let (width, length) = (dim.x, dim.y);
279 let body_radius = if length > width {
280 body.max_radius()
282 } else {
283 body.min_radius()
285 };
286 let body_offsets_z = height_offset(body, look_dir, velocity, on_ground);
287 Vec3::new(
288 body_radius * ori.x * 1.1,
289 body_radius * ori.y * 1.1,
290 body_offsets_z,
291 )
292}