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