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::{RngExt, 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(data, self.timer, None),
186 ..*self
187 });
188 } else {
189 end_ability(data, &mut update);
191 }
192 },
193 _ => {
194 end_ability(data, &mut update);
196 },
197 }
198
199 update
200 }
201}
202
203pub fn create_sprites(
205 data: &JoinData,
206 output_events: &mut OutputEvents,
207 sprite: SpriteKind,
208 timer_frac: f32,
209 summon_distance: (f32, f32),
210 achieved_radius: i32,
211 angle: f32,
212 sparseness: f64,
213 anchor_pos: Vec3<f32>,
214 stack_sprites: bool,
215 del_timeout: Option<(f32, f32)>,
216) -> i32 {
217 let summon_distance = timer_frac * (summon_distance.1 - summon_distance.0) + summon_distance.0;
220 let summon_distance = summon_distance.round() as i32;
221
222 for radius in achieved_radius..=summon_distance {
224 let radius = radius + 1;
226 let spiral = Spiral2d::with_edge_radius(radius);
228 for point in spiral {
229 if data
231 .ori
232 .look_vec()
233 .xy()
234 .angle_between(point.as_())
235 .to_degrees()
236 <= (angle / 2.0)
237 && !rng().random_bool(sparseness)
238 {
239 let sprite_pos = Vec3::new(
241 anchor_pos.x.floor() as i32 + point.x,
242 anchor_pos.y.floor() as i32 + point.y,
243 anchor_pos.z.floor() as i32,
244 );
245
246 let (obstacle_z, obstacle_z_result) = data
248 .terrain
249 .ray(
250 sprite_pos.map(|x| x as f32 + 0.5) + Vec3::unit_z() * 10.0,
251 sprite_pos.map(|x| x as f32 + 0.5) - Vec3::unit_z() * 10.0,
252 )
253 .until(|b| {
254 Block::is_solid(b) && b.get_sprite() != Some(sprite)
257 })
258 .cast();
259
260 let z = match sprite {
261 SpriteKind::Empty => sprite_pos.z + (10.5 - obstacle_z).ceil() as i32 - 1,
263 _ => {
264 sprite_pos.z
265 + if let (true, Ok(None)) = (stack_sprites, obstacle_z_result) {
266 0
267 } else {
268 (10.5 - obstacle_z).ceil() as i32
269 }
270 },
271 };
272
273 let sprite_pos = Vec3::new(sprite_pos.x, sprite_pos.y, z);
275 let layers = match sprite {
277 SpriteKind::SeaUrchin => 2,
278 _ => 1,
279 };
280 for i in 0..layers {
281 output_events.emit_server(CreateSpriteEvent {
283 pos: Vec3::new(sprite_pos.x, sprite_pos.y, z + i),
284 sprite,
285 del_timeout,
286 });
287 }
288 }
289 }
290 }
291 summon_distance
292}