1use crate::{
2 combat::{
3 Attack, AttackDamage, AttackEffect, CombatBuff, CombatEffect, CombatRequirement, Damage,
4 DamageKind, GroupTarget, Knockback, KnockbackDir,
5 },
6 comp::{
7 ArcProperties, CapsulePrism, FrontendMarker, Stats,
8 ability::Dodgeable,
9 item::{Reagent, tool},
10 pool::PoolProperties,
11 },
12 consts::GRAVITY,
13 explosion::{ColorPreset, Explosion, RadiusEffect},
14 resources::{Secs, Time},
15 states::utils::AbilityInfo,
16 uid::Uid,
17 util::Dir,
18};
19use common_base::dev_panic;
20use serde::{Deserialize, Serialize};
21use specs::Component;
22use std::time::Duration;
23use vek::*;
24
25#[derive(Clone, Debug, Serialize, Deserialize)]
26pub enum Effect {
27 Attack(Attack),
28 Explode(Explosion),
29 Vanish,
30 Possess,
31 Bonk, Firework(Reagent),
33 SurpriseEgg,
34 TrainingDummy,
35 Arc(ArcProperties),
36 Split(SplitOptions),
37 Pool(PoolProperties),
38}
39
40#[derive(Clone, Debug)]
41pub struct Projectile {
42 pub hit_solid: Vec<Effect>,
44 pub hit_entity: Vec<Effect>,
45 pub timeout: Vec<Effect>,
46 pub time_left: Duration,
48 pub init_time: Secs,
51 pub owner: Option<Uid>,
52 pub ignore_group: bool,
55 pub is_sticky: bool,
57 pub is_point: bool,
59 pub homing: Option<(Uid, f32)>,
62 pub pierce_entities: bool,
65 pub hit_entities: Vec<Uid>,
68 pub limit_per_ability: bool,
71 pub override_collider: Option<CapsulePrism>,
74}
75
76impl Component for Projectile {
77 type Storage = specs::DenseVecStorage<Self>;
78}
79
80impl Projectile {
81 pub fn is_blockable(&self) -> bool {
82 !self.hit_entity.iter().any(|effect| {
83 matches!(
84 effect,
85 Effect::Attack(Attack {
86 blockable: false,
87 ..
88 })
89 )
90 })
91 }
92}
93
94#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
95#[serde(deny_unknown_fields)]
96pub struct ProjectileConstructor {
97 pub kind: ProjectileConstructorKind,
98 pub attack: Option<ProjectileAttack>,
99 pub scaled: Option<Scaled>,
100 pub homing_rate: Option<f32>,
102 pub split: Option<SplitOptions>,
103 pub lifetime_override: Option<Secs>,
104 #[serde(default)]
105 pub limit_per_ability: bool,
106 pub override_collider: Option<CapsulePrism>,
107 #[serde(default)]
108 pub pierce_entities: bool,
109 #[serde(default = "default_true")]
110 pub is_point: bool,
111 #[serde(default = "default_true")]
112 pub is_sticky: bool,
113 #[serde(default)]
114 pub hazard: bool,
115}
116
117#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
118#[serde(deny_unknown_fields)]
119pub struct SplitOptions {
120 pub split_on_terrain: bool,
121 pub amount: u32,
122 pub spread: f32,
123 pub new_lifetime: Secs,
124 pub override_collider: Option<CapsulePrism>,
127}
128
129#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
130#[serde(deny_unknown_fields)]
131pub struct Scaled {
132 damage: f32,
133 poise: Option<f32>,
134 knockback: Option<f32>,
135 energy: Option<f32>,
136 damage_effect: Option<f32>,
137}
138
139fn default_true() -> bool { true }
140
141#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
142#[serde(deny_unknown_fields)]
143pub struct ProjectileAttack {
144 pub damage: f32,
145 pub poise: Option<f32>,
146 pub knockback: Option<f32>,
147 pub energy: Option<f32>,
148 pub buff: Option<CombatBuff>,
149 #[serde(default)]
150 pub friendly_fire: bool,
151 #[serde(default = "default_true")]
152 pub blockable: bool,
153 pub damage_effect: Option<CombatEffect>,
154 pub attack_effect: Option<(CombatEffect, CombatRequirement)>,
155 #[serde(default)]
156 pub without_combo: bool,
157 pub damage_kind: DamageKind,
158}
159
160#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
161pub struct ProjectileArcingProperties {
162 pub distance: f32,
163 pub arcs: u32,
164 pub min_delay: Secs,
165 pub max_delay: Secs,
166 #[serde(default)]
167 pub targets_owner: bool,
168}
169
170#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
171pub enum ProjectileConstructorEffectKind {
172 AttackEffect(AttackEffect),
173 ConvertKindToArcing(ProjectileArcingProperties),
174 Marker(FrontendMarker),
175}
176
177#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
178pub struct ProjectileConstructorEffect {
179 pub kind: ProjectileConstructorEffectKind,
180 pub tool_filter: Option<tool::ToolKind>,
181}
182
183fn default_both() -> ProjectileExplosionTarget { ProjectileExplosionTarget::Both }
184
185#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
186#[serde(deny_unknown_fields)]
187pub enum ProjectileConstructorKind {
188 Simple,
190 Explosive {
191 radius: f32,
192 min_falloff: f32,
193 reagent: Option<Reagent>,
194 terrain: Option<(f32, ColorPreset)>,
195 #[serde(default = "default_both")]
196 target: ProjectileExplosionTarget,
197 },
198 Arcing(ProjectileArcingProperties),
199 Possess,
200 Firework(Reagent),
201 SurpriseEgg,
202 TrainingDummy,
203 Pool {
204 radius: f32,
205 tick_dur: Secs,
206 duration: Secs,
207 #[serde(default)]
208 dodgeable: Dodgeable,
209 },
210}
211
212impl ProjectileConstructor {
213 pub fn create_projectile(
214 self,
215 owner: Option<Uid>,
216 precision_mult: f32,
217 ability_info: Option<AbilityInfo>,
218 attacker_stats: Option<&Stats>,
219 ) -> (Projectile, Option<FrontendMarker>) {
220 if self.scaled.is_some() {
221 dev_panic!(
222 "Attempted to create a projectile that had a provided scaled value without \
223 scaling the projectile."
224 )
225 }
226
227 let instance = rand::random();
228 let marker = None;
229 let attack = self.attack.map(|a| {
230 let target = if a.friendly_fire {
231 Some(GroupTarget::All)
232 } else {
233 Some(GroupTarget::OutOfGroup)
234 };
235
236 let poise = a.poise.map(|poise| {
237 AttackEffect::new(target, CombatEffect::Poise(poise))
238 .with_requirement(CombatRequirement::AnyDamage)
239 });
240
241 let knockback = a.knockback.map(|kb| {
242 AttackEffect::new(
243 target,
244 CombatEffect::Knockback(Knockback {
245 strength: kb,
246 direction: KnockbackDir::Away,
247 }),
248 )
249 .with_requirement(CombatRequirement::AnyDamage)
250 });
251
252 let energy = a.energy.map(|energy| {
253 AttackEffect::new(None, CombatEffect::EnergyReward(energy))
254 .with_requirement(CombatRequirement::AnyDamage)
255 });
256
257 let buff = a.buff.map(CombatEffect::Buff);
258
259 let mut damage = AttackDamage::new(
260 Damage {
261 kind: a.damage_kind,
262 value: a.damage,
263 },
264 target,
265 instance,
266 );
267
268 if let Some(buff) = buff {
269 damage = damage.with_effect(buff);
270 }
271
272 if let Some(damage_effect) = a.damage_effect {
273 damage = damage.with_effect(damage_effect);
274 }
275
276 let mut attack = Attack::new(ability_info)
277 .with_damage(damage)
278 .with_precision(
279 precision_mult
280 * ability_info
281 .and_then(|ai| ai.ability_meta.precision_power_mult)
282 .unwrap_or(1.0),
283 )
284 .with_blockable(a.blockable);
285
286 if !a.without_combo {
287 attack = attack.with_combo_increment();
288 }
289
290 if let Some(poise) = poise {
291 attack = attack.with_effect(poise);
292 }
293
294 if let Some(knockback) = knockback {
295 attack = attack.with_effect(knockback);
296 }
297
298 if let Some(energy) = energy {
299 attack = attack.with_effect(energy);
300 }
301
302 if let Some((effect, requirement)) = a.attack_effect {
303 let effect = AttackEffect::new(Some(GroupTarget::OutOfGroup), effect)
304 .with_requirement(requirement);
305 attack = attack.with_effect(effect);
306 }
307
308 attack
309 });
310
311 let (proj_kind, attack, marker) = {
312 let mut proj_kind = self.kind;
313 let mut attack = attack;
314 let mut marker = marker;
315
316 for effect in attacker_stats
317 .iter()
318 .flat_map(|s| s.projectile_constructor_effects.iter())
319 {
320 if effect
321 .tool_filter
322 .is_none_or(|tk| Some(tk) == ability_info.and_then(|ai| ai.tool))
323 {
324 match &effect.kind {
325 ProjectileConstructorEffectKind::ConvertKindToArcing(arc) => {
326 proj_kind = ProjectileConstructorKind::Arcing(*arc);
327 },
328 ProjectileConstructorEffectKind::AttackEffect(effect) => {
329 attack = attack.map(|a| a.with_effect(effect.clone()));
330 },
331 ProjectileConstructorEffectKind::Marker(mark) => {
332 marker = Some(*mark);
333 },
334 }
335 }
336 }
337
338 (proj_kind, attack, marker)
339 };
340
341 let homing = ability_info
342 .and_then(|a| a.input_attr)
343 .and_then(|i| i.target_entity)
344 .zip(self.homing_rate);
345
346 let mut timeout = Vec::new();
347 let mut hit_solid = Vec::new();
348
349 if let Some(split) = self.split {
350 timeout.push(Effect::Split(split));
351 if split.split_on_terrain {
352 hit_solid.push(Effect::Split(split));
353 }
354 }
355
356 let default_lifetime = Secs(match proj_kind {
357 ProjectileConstructorKind::Firework(_) => 3.0,
358 _ => 15.0,
359 });
360
361 let lifetime = self.lifetime_override.unwrap_or(default_lifetime);
362
363 let projectile = match proj_kind {
364 ProjectileConstructorKind::Simple => {
365 hit_solid.push(Effect::Bonk);
366
367 let mut hit_entity = Vec::new();
368
369 if !self.pierce_entities {
370 hit_entity.push(Effect::Vanish);
371 }
372
373 if let Some(attack) = attack {
374 hit_entity.push(Effect::Attack(attack));
375 }
376
377 Projectile {
378 hit_solid,
379 hit_entity,
380 timeout,
381 time_left: Duration::from_secs_f64(lifetime.0),
382 init_time: lifetime,
383 owner,
384 ignore_group: true,
385 is_sticky: self.is_sticky,
386 is_point: self.is_point,
387 homing,
388 pierce_entities: self.pierce_entities,
389 hit_entities: Vec::new(),
390 limit_per_ability: self.limit_per_ability,
391 override_collider: self.override_collider,
392 }
393 },
394 ProjectileConstructorKind::Explosive {
395 radius,
396 min_falloff,
397 reagent,
398 terrain,
399 target,
400 } => {
401 let mut hit_entity = Vec::new();
402
403 let terrain =
404 terrain.map(|(pow, col)| RadiusEffect::TerrainDestruction(pow, col.to_rgb()));
405
406 let mut effects = Vec::new();
407
408 if let Some(attack) = attack {
409 if matches!(target, ProjectileExplosionTarget::SolidOnlyEntityAttack) {
410 hit_entity.push(Effect::Attack(attack.clone()));
411 }
412 effects.push(RadiusEffect::Attack {
413 attack,
414 dodgeable: Dodgeable::Roll,
415 });
416 }
417
418 if let Some(terrain) = terrain {
419 effects.push(terrain);
420 }
421
422 let explosion = Explosion {
423 effects,
424 radius,
425 reagent,
426 min_falloff,
427 };
428
429 match target {
430 ProjectileExplosionTarget::EntityOnly => {
431 hit_entity.push(Effect::Explode(explosion));
432 },
433 ProjectileExplosionTarget::SolidOnly
434 | ProjectileExplosionTarget::SolidOnlyEntityAttack => {
435 hit_solid.push(Effect::Explode(explosion));
436 },
437 ProjectileExplosionTarget::Both => {
438 hit_entity.push(Effect::Explode(explosion.clone()));
439 hit_solid.push(Effect::Explode(explosion));
440 },
441 }
442
443 if !self.hazard {
444 hit_solid.push(Effect::Vanish);
445 }
446 hit_entity.push(Effect::Vanish);
447
448 Projectile {
449 hit_solid,
450 hit_entity,
451 timeout,
452 time_left: Duration::from_secs_f64(lifetime.0),
453 init_time: lifetime,
454 owner,
455 ignore_group: true,
456 is_sticky: self.is_sticky,
457 is_point: self.is_point,
458 homing,
459 pierce_entities: self.pierce_entities,
460 hit_entities: Vec::new(),
461 limit_per_ability: self.limit_per_ability,
462 override_collider: self.override_collider,
463 }
464 },
465 ProjectileConstructorKind::Arcing(ProjectileArcingProperties {
466 distance,
467 arcs,
468 min_delay,
469 max_delay,
470 targets_owner,
471 }) => {
472 let mut hit_entity = vec![Effect::Vanish];
473
474 if let Some(attack) = attack {
475 hit_entity.push(Effect::Attack(attack.clone()));
476
477 let arc = ArcProperties {
478 attack,
479 distance,
480 arcs,
481 min_delay,
482 max_delay,
483 targets_owner,
484 };
485
486 hit_entity.push(Effect::Arc(arc));
487 }
488
489 Projectile {
490 hit_solid,
491 hit_entity,
492 timeout,
493 time_left: Duration::from_secs_f64(lifetime.0),
494 init_time: lifetime,
495 owner,
496 ignore_group: true,
497 is_sticky: self.is_sticky,
498 is_point: self.is_point,
499 homing,
500 pierce_entities: self.pierce_entities,
501 hit_entities: Vec::new(),
502 limit_per_ability: self.limit_per_ability,
503 override_collider: self.override_collider,
504 }
505 },
506 ProjectileConstructorKind::Possess => Projectile {
507 hit_solid,
508 hit_entity: vec![Effect::Possess],
509 timeout,
510 time_left: Duration::from_secs_f64(lifetime.0),
511 init_time: lifetime,
512 owner,
513 ignore_group: false,
514 is_sticky: self.is_sticky,
515 is_point: self.is_point,
516 homing,
517 pierce_entities: self.pierce_entities,
518 hit_entities: Vec::new(),
519 limit_per_ability: self.limit_per_ability,
520 override_collider: self.override_collider,
521 },
522 ProjectileConstructorKind::Firework(reagent) => {
523 timeout.push(Effect::Firework(reagent));
524
525 Projectile {
526 hit_solid,
527 hit_entity: Vec::new(),
528 timeout,
529 time_left: Duration::from_secs_f64(lifetime.0),
530 init_time: lifetime,
531 owner,
532 ignore_group: true,
533 is_sticky: self.is_sticky,
534 is_point: self.is_point,
535 homing,
536 pierce_entities: self.pierce_entities,
537 hit_entities: Vec::new(),
538 limit_per_ability: self.limit_per_ability,
539 override_collider: self.override_collider,
540 }
541 },
542 ProjectileConstructorKind::SurpriseEgg => {
543 hit_solid.push(Effect::SurpriseEgg);
544 hit_solid.push(Effect::Vanish);
545
546 Projectile {
547 hit_solid,
548 hit_entity: vec![Effect::SurpriseEgg, Effect::Vanish],
549 timeout,
550 time_left: Duration::from_secs_f64(lifetime.0),
551 init_time: lifetime,
552 owner,
553 ignore_group: true,
554 is_sticky: self.is_sticky,
555 is_point: self.is_point,
556 homing,
557 pierce_entities: self.pierce_entities,
558 hit_entities: Vec::new(),
559 limit_per_ability: self.limit_per_ability,
560 override_collider: self.override_collider,
561 }
562 },
563 ProjectileConstructorKind::Pool {
564 radius,
565 tick_dur,
566 duration,
567 dodgeable,
568 } => {
569 let pool_props = attack.map(|atk| PoolProperties {
570 attack: atk,
571 radius,
572 tick_dur,
573 duration,
574 dodgeable,
575 });
576
577 let lifetime = self.lifetime_override.unwrap_or(Secs(10.0));
578
579 let mut hit_entity = vec![Effect::Vanish];
580
581 if let Some(props) = pool_props {
582 hit_solid.push(Effect::Pool(props.clone()));
583 hit_solid.push(Effect::Vanish);
584 hit_entity.push(Effect::Pool(props));
585 }
586
587 Projectile {
588 hit_solid,
589 hit_entity,
590 timeout,
591 time_left: Duration::from_secs_f64(lifetime.0),
592 init_time: lifetime,
593 owner,
594 ignore_group: true,
595 is_sticky: true,
596 is_point: true,
597 homing,
598 pierce_entities: false,
599 hit_entities: Vec::new(),
600 limit_per_ability: self.limit_per_ability,
601 override_collider: self.override_collider,
602 }
603 },
604 ProjectileConstructorKind::TrainingDummy => {
605 hit_solid.push(Effect::TrainingDummy);
606 hit_solid.push(Effect::Vanish);
607
608 timeout.push(Effect::TrainingDummy);
609
610 Projectile {
611 hit_solid,
612 hit_entity: vec![Effect::TrainingDummy, Effect::Vanish],
613 timeout,
614 time_left: Duration::from_secs_f64(lifetime.0),
615 init_time: lifetime,
616 owner,
617 ignore_group: true,
618 is_sticky: self.is_sticky,
619 is_point: self.is_point,
620 homing,
621 pierce_entities: self.pierce_entities,
622 hit_entities: Vec::new(),
623 limit_per_ability: self.limit_per_ability,
624 override_collider: self.override_collider,
625 }
626 },
627 };
628 (projectile, marker)
629 }
630
631 pub fn handle_scaling(mut self, scaling: f32) -> Self {
632 let scale_values = |a, b| a + b * scaling;
633
634 if let Some(scaled) = self.scaled {
635 if let Some(ref mut attack) = self.attack {
636 attack.damage = scale_values(attack.damage, scaled.damage);
637 if let Some(s_poise) = scaled.poise {
638 attack.poise = Some(scale_values(attack.poise.unwrap_or(0.0), s_poise));
639 }
640 if let Some(s_kb) = scaled.knockback {
641 attack.knockback = Some(scale_values(attack.knockback.unwrap_or(0.0), s_kb));
642 }
643 if let Some(s_energy) = scaled.energy {
644 attack.energy = Some(scale_values(attack.energy.unwrap_or(0.0), s_energy));
645 }
646 if let Some(s_dmg_eff) = scaled.damage_effect {
647 if attack.damage_effect.is_some() {
648 attack.damage_effect =
649 attack.damage_effect.as_ref().cloned().map(|dmg_eff| {
650 dmg_eff.apply_multiplier(scale_values(1.0, s_dmg_eff))
651 });
652 } else {
653 dev_panic!(
654 "Attempted to scale damage effect on a projectile that doesn't have a \
655 damage effect."
656 )
657 }
658 }
659 } else {
660 dev_panic!("Attempted to scale on a projectile that has no attack to scale.")
661 }
662 } else {
663 dev_panic!("Attempted to scale on a projectile that has no provided scaling value.")
664 }
665
666 self.scaled = None;
667
668 self
669 }
670
671 pub fn adjusted_by_stats(mut self, stats: tool::Stats) -> Self {
672 self.attack = self.attack.map(|mut a| {
673 a.damage *= stats.power;
674 a.poise = a.poise.map(|poise| poise * stats.effect_power);
675 a.knockback = a.knockback.map(|kb| kb * stats.effect_power);
676 a.buff = a.buff.map(|mut b| {
677 b.strength *= stats.buff_strength;
678 b
679 });
680 a.damage_effect = a.damage_effect.map(|de| de.adjusted_by_stats(stats));
681 a.attack_effect = a
682 .attack_effect
683 .map(|(e, r)| (e.adjusted_by_stats(stats), r));
684 a
685 });
686
687 self.scaled = self.scaled.map(|mut s| {
688 s.damage *= stats.power;
689 s.poise = s.poise.map(|poise| poise * stats.effect_power);
690 s.knockback = s.knockback.map(|kb| kb * stats.effect_power);
691 s
692 });
693
694 match self.kind {
695 ProjectileConstructorKind::Simple
696 | ProjectileConstructorKind::Possess
697 | ProjectileConstructorKind::Firework(_)
698 | ProjectileConstructorKind::SurpriseEgg
699 | ProjectileConstructorKind::TrainingDummy => {},
700 ProjectileConstructorKind::Explosive { ref mut radius, .. }
701 | ProjectileConstructorKind::Pool { ref mut radius, .. } => {
702 *radius *= stats.range;
703 },
704 ProjectileConstructorKind::Arcing(ProjectileArcingProperties {
705 ref mut distance,
706 ..
707 }) => {
708 *distance *= stats.range;
709 },
710 }
711
712 self.split = self.split.map(|mut s| {
713 s.amount = (s.amount as f32 * stats.effect_power).ceil().max(0.0) as u32;
714 s
715 });
716
717 self
718 }
719
720 pub fn legacy_modified_by_skills(
723 mut self,
724 power: f32,
725 regen: f32,
726 range: f32,
727 kb: f32,
728 ) -> Self {
729 self.attack = self.attack.map(|mut a| {
730 a.damage *= power;
731 a.knockback = a.knockback.map(|k| k * kb);
732 a.energy = a.energy.map(|e| e * regen);
733 a
734 });
735 self.scaled = self.scaled.map(|mut s| {
736 s.damage *= power;
737 s.knockback = s.knockback.map(|k| k * kb);
738 s.energy = s.energy.map(|e| e * regen);
739 s
740 });
741 if let ProjectileConstructorKind::Explosive { ref mut radius, .. } = self.kind {
742 *radius *= range;
743 }
744 self
745 }
746
747 pub fn is_explosive(&self) -> bool {
748 match self.kind {
749 ProjectileConstructorKind::Simple
750 | ProjectileConstructorKind::Possess
751 | ProjectileConstructorKind::Firework(_)
752 | ProjectileConstructorKind::SurpriseEgg
753 | ProjectileConstructorKind::TrainingDummy
754 | ProjectileConstructorKind::Arcing(_)
755 | ProjectileConstructorKind::Pool { .. } => false,
756 ProjectileConstructorKind::Explosive { .. } => true,
757 }
758 }
759
760 pub fn agent_aim_z_offset(&self, tgt_eye_offset: f32) -> f32 {
761 if self.hazard || matches!(self.kind, ProjectileConstructorKind::Explosive { .. }) {
762 0.0
763 } else {
764 tgt_eye_offset
765 }
766 }
767}
768
769pub fn aim_projectile(speed: f32, pos: Vec3<f32>, tgt: Vec3<f32>, high_arc: bool) -> Option<Dir> {
772 let mut to_tgt = tgt - pos;
773 let dist_sqrd = to_tgt.xy().magnitude_squared();
774 let u_sqrd = speed.powi(2);
775 if high_arc {
776 to_tgt.z = (u_sqrd
777 + (u_sqrd.powi(2) - GRAVITY * (GRAVITY * dist_sqrd + 2.0 * to_tgt.z * u_sqrd))
778 .sqrt()
779 .max(0.0))
780 / GRAVITY;
781 } else {
782 to_tgt.z = (u_sqrd
783 - (u_sqrd.powi(2) - GRAVITY * (GRAVITY * dist_sqrd + 2.0 * to_tgt.z * u_sqrd))
784 .sqrt()
785 .max(0.0))
786 / GRAVITY;
787 }
788 Dir::from_unnormalized(to_tgt)
789}
790
791#[derive(Clone, Debug, Default)]
792pub struct ProjectileHitEntities {
793 pub hit_entities: Vec<(Uid, Time)>,
794}
795
796impl Component for ProjectileHitEntities {
797 type Storage = specs::DenseVecStorage<Self>;
798}
799
800#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
801pub enum ProjectileExplosionTarget {
802 EntityOnly,
803 SolidOnly,
804 SolidOnlyEntityAttack,
805 Both,
806}