1use crate::{
2 Explosion, RadiusEffect, combat,
3 combat::{Attack, AttackDamage, Damage, DamageKind::Crushing, DamageSource, GroupTarget},
4 comp::{
5 CharacterState, MeleeConstructor, StateUpdate, character_state::OutputEvents, item::Reagent,
6 },
7 event::{ComboChangeEvent, ExplosionEvent},
8 states::{
9 behavior::{CharacterBehavior, JoinData},
10 utils::*,
11 },
12};
13use serde::{Deserialize, Serialize};
14use std::time::Duration;
15use vek::Vec3;
16
17#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
19pub struct StaticData {
20 pub buildup_duration: Duration,
22 pub swing_duration: Duration,
24 pub recover_duration: Duration,
26 pub melee_constructor: MeleeConstructor,
28 pub energy_cost: f32,
30 pub max_strikes: Option<u32>,
32 pub move_modifier: f32,
33 pub ori_modifier: f32,
34 pub minimum_combo: u32,
35 pub frontend_specifier: Option<FrontendSpecifier>,
38 pub ability_info: AbilityInfo,
40}
41
42#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
43pub struct Data {
44 pub static_data: StaticData,
47 pub timer: Duration,
49 pub current_strike: u32,
51 pub stage_section: StageSection,
53 pub exhausted: bool,
55}
56
57impl CharacterBehavior for Data {
58 fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
59 let mut update = StateUpdate::from(data);
60
61 handle_orientation(data, &mut update, self.static_data.ori_modifier, None);
62 handle_move(data, &mut update, self.static_data.move_modifier);
63 handle_interrupts(data, &mut update, output_events);
64
65 match self.stage_section {
66 StageSection::Buildup => {
67 if self.timer < self.static_data.buildup_duration {
68 if let CharacterState::RapidMelee(c) = &mut update.character {
70 c.timer = tick_attack_or_default(data, self.timer, None);
71 }
72 } else {
73 if let CharacterState::RapidMelee(c) = &mut update.character {
75 c.timer = Duration::default();
76 c.stage_section = StageSection::Action;
77 }
78 }
79 },
80 StageSection::Action => {
81 if !self.exhausted {
82 if let CharacterState::RapidMelee(c) = &mut update.character {
83 c.timer = Duration::default();
84 c.exhausted = true;
85 }
86
87 let precision_mult = combat::compute_precision_mult(data.inventory, data.msm);
88 let tool_stats = get_tool_stats(data, self.static_data.ability_info);
89
90 data.updater.insert(
91 data.entity,
92 self.static_data
93 .melee_constructor
94 .create_melee(precision_mult, tool_stats),
95 );
96 } else if self.timer < self.static_data.swing_duration {
97 if let CharacterState::RapidMelee(c) = &mut update.character {
99 c.timer = tick_attack_or_default(data, self.timer, None);
100 }
101 } else if match self.static_data.max_strikes {
102 Some(max) => self.current_strike < max,
103 None => input_is_pressed(data, self.static_data.ability_info.input),
104 } && update
105 .energy
106 .try_change_by(-self.static_data.energy_cost)
107 .is_ok()
108 {
109 if self.static_data.frontend_specifier == Some(FrontendSpecifier::CultistVortex)
110 {
111 let damage = AttackDamage::new(
112 Damage {
113 source: DamageSource::Explosion,
114 kind: Crushing,
115 value: 10.0,
116 },
117 Some(GroupTarget::OutOfGroup),
118 rand::random(),
119 );
120 let attack = Attack::default().with_damage(damage);
121 let explosion = Explosion {
122 effects: vec![RadiusEffect::Attack(attack)],
123 radius: data.body.max_radius() * 4.0,
124 reagent: Some(Reagent::Purple),
125 min_falloff: 0.5,
126 };
127 let pos =
128 data.pos.0 + (*data.ori.look_dir() * (data.body.max_radius() * 3.0));
129 let explosition =
130 Vec3::new(pos.x, pos.y, pos.z + (data.body.height() / 4.0));
131 output_events.emit_server(ExplosionEvent {
132 pos: explosition,
133 explosion,
134 owner: Some(*data.uid),
135 });
136 }
137 if let CharacterState::RapidMelee(c) = &mut update.character {
138 c.timer = Duration::default();
139 c.current_strike += 1;
140 c.exhausted = false;
141 }
142 } else {
143 if let CharacterState::RapidMelee(c) = &mut update.character {
145 c.timer = Duration::default();
146 c.stage_section = StageSection::Recover;
147 }
148 }
149
150 if self.static_data.minimum_combo > 0 {
152 output_events.emit_server(ComboChangeEvent {
153 entity: data.entity,
154 change: -data.combo.map_or(0, |c| c.counter() as i32),
155 });
156 }
157 },
158 StageSection::Recover => {
159 if self.timer < self.static_data.recover_duration {
160 if let CharacterState::RapidMelee(c) = &mut update.character {
162 c.timer = tick_attack_or_default(
163 data,
164 self.timer,
165 Some(data.stats.recovery_speed_modifier),
166 );
167 }
168 } else {
169 end_melee_ability(data, &mut update);
171 }
172 },
173 _ => {
174 end_melee_ability(data, &mut update);
176 },
177 }
178
179 update
180 }
181}
182
183#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
184pub enum FrontendSpecifier {
185 CultistVortex,
186 Whirlwind,
187}