1use crate::{
2 Explosion, RadiusEffect,
3 combat::{self, Attack, AttackDamage, Damage, DamageKind::Crushing, DamageSource, 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(Copy, 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(Copy, 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
94 .melee_constructor
95 .create_melee(precision_mult, tool_stats),
96 );
97 } else if self.timer < self.static_data.swing_duration {
98 if let CharacterState::RapidMelee(c) = &mut update.character {
100 c.timer = tick_attack_or_default(data, self.timer, None);
101 }
102 } else if match self.static_data.max_strikes {
103 Some(max) => self.current_strike < max,
104 None => input_is_pressed(data, self.static_data.ability_info.input),
105 } && update
106 .energy
107 .try_change_by(-self.static_data.energy_cost)
108 .is_ok()
109 {
110 if self.static_data.frontend_specifier == Some(FrontendSpecifier::CultistVortex)
111 {
112 let damage = AttackDamage::new(
113 Damage {
114 source: DamageSource::Explosion,
115 kind: Crushing,
116 value: 10.0,
117 },
118 Some(GroupTarget::OutOfGroup),
119 rand::random(),
120 );
121 let attack = Attack::default().with_damage(damage);
122 let explosion = Explosion {
123 effects: vec![RadiusEffect::Attack {
124 attack,
125 dodgeable: Dodgeable::Roll,
126 }],
127 radius: data.body.max_radius() * 4.0,
128 reagent: Some(Reagent::Purple),
129 min_falloff: 0.5,
130 };
131 let pos =
132 data.pos.0 + (*data.ori.look_dir() * (data.body.max_radius() * 3.0));
133 let explosition =
134 Vec3::new(pos.x, pos.y, pos.z + (data.body.height() / 4.0));
135 output_events.emit_server(ExplosionEvent {
136 pos: explosition,
137 explosion,
138 owner: Some(*data.uid),
139 });
140 }
141 if let CharacterState::RapidMelee(c) = &mut update.character {
142 c.timer = Duration::default();
143 c.current_strike += 1;
144 c.exhausted = false;
145 }
146 } else {
147 if let CharacterState::RapidMelee(c) = &mut update.character {
149 c.timer = Duration::default();
150 c.stage_section = StageSection::Recover;
151 }
152 }
153
154 if self.static_data.minimum_combo > 0 {
156 output_events.emit_server(ComboChangeEvent {
157 entity: data.entity,
158 change: -data.combo.map_or(0, |c| c.counter() as i32),
159 });
160 }
161 },
162 StageSection::Recover => {
163 if self.timer < self.static_data.recover_duration {
164 if let CharacterState::RapidMelee(c) = &mut update.character {
166 c.timer = tick_attack_or_default(
167 data,
168 self.timer,
169 Some(data.stats.recovery_speed_modifier),
170 );
171 }
172 } else {
173 end_melee_ability(data, &mut update);
175 }
176 },
177 _ => {
178 end_melee_ability(data, &mut update);
180 },
181 }
182
183 update
184 }
185}
186
187#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
188pub enum FrontendSpecifier {
189 CultistVortex,
190 IceWhirlwind,
191}