1use crate::{
2 comp::{CharacterState, StateUpdate, character_state::OutputEvents},
3 event::{CreateSpriteEvent, LocalEvent},
4 outcome::Outcome,
5 spiral::Spiral2d,
6 states::{
7 behavior::{CharacterBehavior, JoinData},
8 utils::*,
9 },
10 terrain::{Block, SpriteKind},
11 vol::ReadVol,
12};
13use rand::{Rng, thread_rng};
14use serde::{Deserialize, Serialize};
15use std::time::Duration;
16use vek::*;
17
18#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
19pub enum SpriteSummonAnchor {
20 #[default]
21 Summoner,
22 Target,
23}
24
25#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
27pub struct StaticData {
28 pub buildup_duration: Duration,
30 pub cast_duration: Duration,
32 pub recover_duration: Duration,
34 pub sprite: SpriteKind,
36 pub del_timeout: Option<(f32, f32)>,
39 pub summon_distance: (f32, f32),
41 pub anchor: SpriteSummonAnchor,
43 pub sparseness: f64,
45 pub angle: f32,
47 pub move_efficiency: f32,
49 pub ori_modifier: f32,
51 pub ability_info: AbilityInfo,
53}
54
55#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
56pub struct Data {
57 pub static_data: StaticData,
60 pub timer: Duration,
62 pub stage_section: StageSection,
64 pub achieved_radius: i32,
66}
67
68impl CharacterBehavior for Data {
69 fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate {
70 let mut update = StateUpdate::from(data);
71
72 handle_orientation(data, &mut update, self.static_data.ori_modifier, None);
73 handle_move(data, &mut update, self.static_data.move_efficiency);
74
75 let target_pos = || {
76 data.controller
77 .queued_inputs
78 .get(&self.static_data.ability_info.input)
79 .or(self.static_data.ability_info.input_attr.as_ref())
80 .and_then(|input| input.select_pos)
81 };
82
83 match self.stage_section {
84 StageSection::Buildup => {
85 if self.timer < self.static_data.buildup_duration {
86 update.character = CharacterState::SpriteSummon(Data {
88 timer: tick_attack_or_default(data, self.timer, None),
89 ..*self
90 });
91 match self.static_data.sprite {
93 SpriteKind::Empty => output_events.emit_local(LocalEvent::CreateOutcome(
94 Outcome::TerracottaStatueCharge {
95 pos: data.pos.0 + *data.ori.look_dir() * (data.body.max_radius()),
96 },
97 )),
98 SpriteKind::FireBlock => {
99 output_events.emit_local(LocalEvent::CreateOutcome(Outcome::Charge {
100 pos: data.pos.0 + *data.ori.look_dir() * (data.body.max_radius()),
101 }))
102 },
103 _ => {},
104 }
105 } else {
106 update.character = CharacterState::SpriteSummon(Data {
108 timer: Duration::default(),
109 stage_section: StageSection::Action,
110 ..*self
111 });
112 }
113 },
114 StageSection::Action => {
115 if self.timer < self.static_data.cast_duration {
116 let timer_frac =
117 self.timer.as_secs_f32() / self.static_data.cast_duration.as_secs_f32();
118
119 let anchor_pos = match self.static_data.anchor {
120 SpriteSummonAnchor::Summoner => data.pos.0,
121 SpriteSummonAnchor::Target => target_pos().unwrap_or(data.pos.0),
124 };
125 let achieved_radius = create_sprites(
126 data,
127 output_events,
128 self.static_data.sprite,
129 timer_frac,
130 self.static_data.summon_distance,
131 self.achieved_radius,
132 self.static_data.angle,
133 self.static_data.sparseness,
134 anchor_pos,
135 matches!(self.static_data.anchor, SpriteSummonAnchor::Target),
136 self.static_data.del_timeout,
137 );
138
139 update.character = CharacterState::SpriteSummon(Data {
140 timer: tick_attack_or_default(data, self.timer, None),
141 achieved_radius,
142 ..*self
143 });
144 match self.static_data.sprite {
146 SpriteKind::IceSpike => {
147 let summoner_pos =
148 data.pos.0 + *data.ori.look_dir() * data.body.max_radius();
149 output_events.emit_local(LocalEvent::CreateOutcome(
150 Outcome::IceCrack {
151 pos: match self.static_data.anchor {
152 SpriteSummonAnchor::Summoner => summoner_pos,
153 SpriteSummonAnchor::Target => {
154 target_pos().unwrap_or(summoner_pos)
155 },
156 },
157 },
158 ));
159 },
160 SpriteKind::IronSpike => {
161 output_events.emit_local(LocalEvent::CreateOutcome(Outcome::Whoosh {
162 pos: data.pos.0,
163 }));
164 },
165 SpriteKind::FireBlock => {
166 output_events.emit_local(LocalEvent::CreateOutcome(Outcome::Bleep {
167 pos: data.pos.0 + *data.ori.look_dir() * (data.body.max_radius()),
168 }));
169 },
170 _ => {},
171 }
172 } else {
173 update.character = CharacterState::SpriteSummon(Data {
175 timer: Duration::default(),
176 stage_section: StageSection::Recover,
177 ..*self
178 });
179 }
180 },
181 StageSection::Recover => {
182 if self.timer < self.static_data.recover_duration {
183 update.character = CharacterState::SpriteSummon(Data {
185 timer: tick_attack_or_default(
186 data,
187 self.timer,
188 Some(data.stats.recovery_speed_modifier),
189 ),
190 ..*self
191 });
192 } else {
193 end_ability(data, &mut update);
195 }
196 },
197 _ => {
198 end_ability(data, &mut update);
200 },
201 }
202
203 update
204 }
205}
206
207pub fn create_sprites(
209 data: &JoinData,
210 output_events: &mut OutputEvents,
211 sprite: SpriteKind,
212 timer_frac: f32,
213 summon_distance: (f32, f32),
214 achieved_radius: i32,
215 angle: f32,
216 sparseness: f64,
217 anchor_pos: Vec3<f32>,
218 stack_sprites: bool,
219 del_timeout: Option<(f32, f32)>,
220) -> i32 {
221 let summon_distance = timer_frac * (summon_distance.1 - summon_distance.0) + summon_distance.0;
224 let summon_distance = summon_distance.round() as i32;
225
226 for radius in achieved_radius..=summon_distance {
228 let radius = radius + 1;
230 let spiral = Spiral2d::with_edge_radius(radius);
232 for point in spiral {
233 if data
235 .ori
236 .look_vec()
237 .xy()
238 .angle_between(point.as_())
239 .to_degrees()
240 <= (angle / 2.0)
241 && !thread_rng().gen_bool(sparseness)
242 {
243 let sprite_pos = Vec3::new(
245 anchor_pos.x.floor() as i32 + point.x,
246 anchor_pos.y.floor() as i32 + point.y,
247 anchor_pos.z.floor() as i32,
248 );
249
250 let (obstacle_z, obstacle_z_result) = data
252 .terrain
253 .ray(
254 sprite_pos.map(|x| x as f32 + 0.5) + Vec3::unit_z() * 10.0,
255 sprite_pos.map(|x| x as f32 + 0.5) - Vec3::unit_z() * 10.0,
256 )
257 .until(|b| {
258 Block::is_solid(b) && b.get_sprite() != Some(sprite)
261 })
262 .cast();
263
264 let z = match sprite {
265 SpriteKind::Empty => sprite_pos.z + (10.5 - obstacle_z).ceil() as i32 - 1,
267 _ => {
268 sprite_pos.z
269 + if let (true, Ok(None)) = (stack_sprites, obstacle_z_result) {
270 0
271 } else {
272 (10.5 - obstacle_z).ceil() as i32
273 }
274 },
275 };
276
277 let sprite_pos = Vec3::new(sprite_pos.x, sprite_pos.y, z);
279 let layers = match sprite {
281 SpriteKind::SeaUrchin => 2,
282 _ => 1,
283 };
284 for i in 0..layers {
285 output_events.emit_server(CreateSpriteEvent {
287 pos: Vec3::new(sprite_pos.x, sprite_pos.y, z + i),
288 sprite,
289 del_timeout,
290 });
291 }
292 }
293 }
294 }
295 summon_distance
296}