diff --git a/assets/common/abilities/custom/birdlargebasic/summontornadoes.ron b/assets/common/abilities/custom/birdlargebasic/summontornadoes.ron
index 9ad07751b4..21c90ae04a 100644
--- a/assets/common/abilities/custom/birdlargebasic/summontornadoes.ron
+++ b/assets/common/abilities/custom/birdlargebasic/summontornadoes.ron
@@ -3,11 +3,16 @@ BasicSummon(
     cast_duration: 1.0,
     recover_duration: 0.5,
     summon_amount: 6,
+    summon_distance: (1, 5),
     summon_info: (
         body: Object(Tornado),
         scale: None,
-        health_scaling: 80,
+        health_scaling: None,
         loadout_config: None,
         skillset_config: None,
     ),
+    duration: Some((
+        secs: 5,
+        nanos: 0,
+    )),
 )
\ No newline at end of file
diff --git a/assets/common/abilities/custom/mindflayer/summonminions.ron b/assets/common/abilities/custom/mindflayer/summonminions.ron
index 99117ce2c0..7fdfb01ec1 100644
--- a/assets/common/abilities/custom/mindflayer/summonminions.ron
+++ b/assets/common/abilities/custom/mindflayer/summonminions.ron
@@ -3,14 +3,16 @@ BasicSummon(
     cast_duration: 1.0,
     recover_duration: 0.5,
     summon_amount: 6,
+    summon_distance: (3, 3),
     summon_info: (
         body: BipedSmall((
             species: Husk,
             body_type: Male,
         )),
         scale: None,
-        health_scaling: 80,
+        health_scaling: Some(80),
         loadout_config: Some(HuskSummon),
         skillset_config: None,
     ),
+    duration: None,
 )
diff --git a/assets/common/abilities/custom/tidalwarrior/totem.ron b/assets/common/abilities/custom/tidalwarrior/totem.ron
index 31df64a7cd..c997b8954c 100644
--- a/assets/common/abilities/custom/tidalwarrior/totem.ron
+++ b/assets/common/abilities/custom/tidalwarrior/totem.ron
@@ -3,11 +3,13 @@ BasicSummon(
     cast_duration: 1.0,
     recover_duration: 0.5,
     summon_amount: 1,
+    summon_distance: (1, 1),
     summon_info: (
         body: Object(SeaLantern),
         scale: None,
-        health_scaling: 0,
+        health_scaling: Some(0),
         loadout_config: None,
         skillset_config: None,
     ),
+    duration: None,
 )
diff --git a/assets/common/abilities/custom/tornado/spin.ron b/assets/common/abilities/custom/tornado/spin.ron
index df0320d9c2..de072c981e 100644
--- a/assets/common/abilities/custom/tornado/spin.ron
+++ b/assets/common/abilities/custom/tornado/spin.ron
@@ -1,13 +1,13 @@
 SpinMelee(
-    buildup_duration: 0.2,
-    swing_duration: 0.6,
-    recover_duration: 0.2,
-    base_damage: 70,
-    base_poise_damage: 25,
-    knockback: ( strength: 0.0, direction: Away),
+    buildup_duration: 0.0,
+    swing_duration: 0.5,
+    recover_duration: 0.0,
+    base_damage: 15000,
+    base_poise_damage: 200,
+    knockback: ( strength: 200.0, direction: Away),
     range: 3.5,
     damage_effect: None,
-    energy_cost: 100,
+    energy_cost: 0,
     is_infinite: true,
     movement_behavior: AxeHover,
     is_interruptible: false,
diff --git a/assets/common/items/npc_weapons/unique/tornado.ron b/assets/common/items/npc_weapons/unique/tornado.ron
new file mode 100644
index 0000000000..14c1054395
--- /dev/null
+++ b/assets/common/items/npc_weapons/unique/tornado.ron
@@ -0,0 +1,19 @@
+ItemDef(
+    name: "Tornado",
+    description: "Tornado weapon",
+    kind: Tool((
+        kind: Natural,
+        hands: Two,
+        stats: Direct((
+            equip_time_secs: 0.01,
+            power: 1.0,
+            poise_strength: 1.0,
+            speed: 1.0,
+            crit_chance: 0.0,
+            crit_mult: 0.0,
+        )),
+    )),
+    quality: Low,
+    tags: [],
+    ability_spec: Some(Custom("Tornado")),
+)
\ No newline at end of file
diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs
index d224061a8e..f755fe58d5 100644
--- a/common/src/comp/ability.rs
+++ b/common/src/comp/ability.rs
@@ -280,7 +280,9 @@ pub enum CharacterAbility {
         cast_duration: f32,
         recover_duration: f32,
         summon_amount: u32,
+        summon_distance: (f32, f32),
         summon_info: basic_summon::SummonInfo,
+        duration: Option<Duration>,
     },
     SelfBuff {
         buildup_duration: f32,
@@ -1737,15 +1739,19 @@ impl From<(&CharacterAbility, AbilityInfo)> for CharacterState {
                 cast_duration,
                 recover_duration,
                 summon_amount,
+                summon_distance,
                 summon_info,
+                duration,
             } => CharacterState::BasicSummon(basic_summon::Data {
                 static_data: basic_summon::StaticData {
                     buildup_duration: Duration::from_secs_f32(*buildup_duration),
                     cast_duration: Duration::from_secs_f32(*cast_duration),
                     recover_duration: Duration::from_secs_f32(*recover_duration),
                     summon_amount: *summon_amount,
+                    summon_distance: *summon_distance,
                     summon_info: *summon_info,
                     ability_info,
+                    duration: *duration,
                 },
                 summon_count: 0,
                 timer: Duration::default(),
diff --git a/common/src/comp/inventory/loadout_builder.rs b/common/src/comp/inventory/loadout_builder.rs
index 6f316f1e62..1978cf3fd9 100644
--- a/common/src/comp/inventory/loadout_builder.rs
+++ b/common/src/comp/inventory/loadout_builder.rs
@@ -306,6 +306,9 @@ fn default_main_tool(body: &Body) -> Item {
             object::Body::SeaLantern => Some(Item::new_from_asset_expect(
                 "common.items.npc_weapons.unique.tidal_totem",
             )),
+            object::Body::Tornado => Some(Item::new_from_asset_expect(
+                "common.items.npc_weapons.unique.tornado",
+            )),
             _ => None,
         },
         Body::BipedSmall(biped_small) => match (biped_small.species, biped_small.body_type) {
diff --git a/common/src/event.rs b/common/src/event.rs
index d21eee05c5..ffa178fdd4 100644
--- a/common/src/event.rs
+++ b/common/src/event.rs
@@ -117,7 +117,7 @@ pub enum ServerEvent {
         pos: comp::Pos,
         stats: comp::Stats,
         skill_set: comp::SkillSet,
-        health: comp::Health,
+        health: Option<comp::Health>,
         poise: comp::Poise,
         loadout: comp::inventory::loadout::Loadout,
         body: comp::Body,
@@ -127,6 +127,7 @@ pub enum ServerEvent {
         home_chunk: Option<comp::HomeChunk>,
         drop_item: Option<Item>,
         rtsim_entity: Option<RtSimEntity>,
+        projectile: Option<comp::Projectile>,
     },
     CreateShip {
         pos: comp::Pos,
diff --git a/common/src/states/basic_summon.rs b/common/src/states/basic_summon.rs
index 6a49e33c60..44c71b9f47 100644
--- a/common/src/states/basic_summon.rs
+++ b/common/src/states/basic_summon.rs
@@ -2,7 +2,7 @@ use crate::{
     comp::{
         self,
         inventory::loadout_builder::{self, LoadoutBuilder},
-        Behavior, BehaviorCapability, CharacterState, StateUpdate,
+        Behavior, BehaviorCapability, CharacterState, Projectile, StateUpdate,
     },
     event::{LocalEvent, ServerEvent},
     outcome::Outcome,
@@ -11,9 +11,13 @@ use crate::{
         behavior::{CharacterBehavior, JoinData},
         utils::*,
     },
+    terrain::Block,
+    vol::ReadVol,
 };
+use rand::Rng;
 use serde::{Deserialize, Serialize};
-use std::time::Duration;
+use std::{f32::consts::PI, time::Duration};
+use vek::*;
 
 /// Separated out to condense update portions of character state
 #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
@@ -26,10 +30,14 @@ pub struct StaticData {
     pub recover_duration: Duration,
     /// How many creatures the state should summon
     pub summon_amount: u32,
+    /// Range of the summons relative to the summonner
+    pub summon_distance: (f32, f32),
     /// Information about the summoned creature
     pub summon_info: SummonInfo,
     /// Miscellaneous information about the ability
     pub ability_info: AbilityInfo,
+    /// Duration of the summoned entity
+    pub duration: Option<Duration>,
 }
 
 #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
@@ -102,15 +110,67 @@ impl CharacterBehavior for Data {
                         };
 
                         let stats = comp::Stats::new("Summon".to_string());
+
+                        let health_scaling = self
+                            .static_data
+                            .summon_info
+                            .health_scaling
+                            .map(|health_scaling| comp::Health::new(body, health_scaling));
+
+                        // Ray cast to check where summon should happen
+                        let summon_frac =
+                            self.summon_count as f32 / self.static_data.summon_amount as f32;
+
+                        let length = rand::thread_rng().gen_range(
+                            self.static_data.summon_distance.0..self.static_data.summon_distance.1,
+                        );
+
+                        // Summon in a clockwise fashion
+                        let ray_vector = Vec3::new(
+                            (summon_frac * 2.0 * PI).sin() * length,
+                            (summon_frac * 2.0 * PI).cos() * length,
+                            data.body.eye_height(),
+                        );
+
+                        // Check for collision on the xy plane
+                        let obstacle_xy = data
+                            .terrain
+                            .ray(data.pos.0, data.pos.0 + length * ray_vector)
+                            .until(Block::is_solid)
+                            .cast()
+                            .0;
+
+                        let collision_vector = Vec3::new(
+                            data.pos.0.x + (summon_frac * 2.0 * PI).sin() * obstacle_xy,
+                            data.pos.0.y + (summon_frac * 2.0 * PI).cos() * obstacle_xy,
+                            data.pos.0.z,
+                        );
+
+                        // Check for collision in z up to 50 blocks
+                        let obstacle_z = data
+                            .terrain
+                            .ray(collision_vector, collision_vector - Vec3::unit_z() * 50.0)
+                            .until(Block::is_solid)
+                            .cast()
+                            .0;
+
+                        // If a duration is specified, create a projectile componenent for the npc
+                        let projectile = self.static_data.duration.map(|duration| Projectile {
+                            hit_solid: Vec::new(),
+                            hit_entity: Vec::new(),
+                            time_left: duration,
+                            owner: Some(*data.uid),
+                            ignore_group: true,
+                            is_sticky: false,
+                            is_point: false,
+                        });
+
                         // Send server event to create npc
                         update.server_events.push_front(ServerEvent::CreateNpc {
-                            pos: *data.pos,
+                            pos: comp::Pos(collision_vector - Vec3::unit_z() * obstacle_z),
                             stats,
                             skill_set,
-                            health: comp::Health::new(
-                                body,
-                                self.static_data.summon_info.health_scaling,
-                            ),
+                            health: health_scaling,
                             poise: comp::Poise::new(body),
                             loadout,
                             body,
@@ -129,6 +189,7 @@ impl CharacterBehavior for Data {
                             home_chunk: None,
                             drop_item: None,
                             rtsim_entity: None,
+                            projectile,
                         });
 
                         // Send local event used for frontend shenanigans
@@ -186,7 +247,7 @@ impl CharacterBehavior for Data {
 pub struct SummonInfo {
     body: comp::Body,
     scale: Option<comp::Scale>,
-    health_scaling: u16,
+    health_scaling: Option<u16>,
     // TODO: use assets for specifying skills and loadout?
     loadout_config: Option<loadout_builder::Preset>,
     skillset_config: Option<skillset_builder::Preset>,
diff --git a/common/src/states/behavior.rs b/common/src/states/behavior.rs
index a1fea34724..d66c827b9a 100644
--- a/common/src/states/behavior.rs
+++ b/common/src/states/behavior.rs
@@ -5,6 +5,7 @@ use crate::{
         InventoryAction, Mass, Melee, Ori, PhysicsState, Pos, SkillSet, StateUpdate, Stats, Vel,
     },
     resources::DeltaTime,
+    terrain::TerrainGrid,
     uid::Uid,
 };
 use specs::{
@@ -96,6 +97,7 @@ pub struct JoinData<'a> {
     pub msm: &'a MaterialStatManifest,
     pub combo: &'a Combo,
     pub alignment: Option<&'a comp::Alignment>,
+    pub terrain: &'a TerrainGrid,
 }
 
 type RestrictedMut<'a, C> = PairedStorage<
@@ -128,6 +130,7 @@ pub struct JoinStruct<'a> {
     pub skill_set: &'a SkillSet,
     pub combo: &'a Combo,
     pub alignment: Option<&'a comp::Alignment>,
+    pub terrain: &'a TerrainGrid,
 }
 
 impl<'a> JoinData<'a> {
@@ -161,6 +164,7 @@ impl<'a> JoinData<'a> {
             msm,
             combo: j.combo,
             alignment: j.alignment,
+            terrain: j.terrain,
         }
     }
 }
diff --git a/common/systems/src/character_behavior.rs b/common/systems/src/character_behavior.rs
index e7db1e0b36..382067f7d7 100644
--- a/common/systems/src/character_behavior.rs
+++ b/common/systems/src/character_behavior.rs
@@ -1,6 +1,6 @@
 use specs::{
-    shred::ResourceId, Entities, Join, LazyUpdate, Read, ReadStorage, SystemData, World, Write,
-    WriteStorage,
+    shred::ResourceId, Entities, Join, LazyUpdate, Read, ReadExpect, ReadStorage, SystemData,
+    World, Write, WriteStorage,
 };
 
 use common::{
@@ -16,6 +16,7 @@ use common::{
         self,
         behavior::{CharacterBehavior, JoinData, JoinStruct},
     },
+    terrain::TerrainGrid,
     uid::Uid,
 };
 use common_ecs::{Job, Origin, Phase, System};
@@ -67,6 +68,7 @@ pub struct ReadData<'a> {
     msm: Read<'a, MaterialStatManifest>,
     combos: ReadStorage<'a, Combo>,
     alignments: ReadStorage<'a, comp::Alignment>,
+    terrain: ReadExpect<'a, TerrainGrid>,
 }
 
 /// ## Character Behavior System
@@ -280,6 +282,7 @@ impl<'a> System<'a> for Sys {
                 skill_set: &skill_set,
                 combo: &combo,
                 alignment: read_data.alignments.get(entity),
+                terrain: &read_data.terrain,
             };
 
             for action in actions {
diff --git a/server/src/cmd.rs b/server/src/cmd.rs
index b8d56c4fbb..fcdd197e1a 100644
--- a/server/src/cmd.rs
+++ b/server/src/cmd.rs
@@ -1014,7 +1014,7 @@ fn handle_spawn(
                         pos,
                         comp::Stats::new(get_npc_name(id, npc::BodyType::from_body(body))),
                         comp::SkillSet::default(),
-                        comp::Health::new(body, 1),
+                        Some(comp::Health::new(body, 1)),
                         comp::Poise::new(body),
                         inventory,
                         body,
@@ -1116,7 +1116,7 @@ fn handle_spawn_training_dummy(
             pos,
             stats,
             skill_set,
-            health,
+            Some(health),
             poise,
             Inventory::new_empty(),
             body,
diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs
index c66317cf0e..014ed849a7 100644
--- a/server/src/events/entity_creation.rs
+++ b/server/src/events/entity_creation.rs
@@ -53,7 +53,7 @@ pub fn handle_create_npc(
     pos: Pos,
     stats: Stats,
     skill_set: SkillSet,
-    health: Health,
+    health: Option<Health>,
     poise: Poise,
     loadout: Loadout,
     body: Body,
@@ -63,6 +63,7 @@ pub fn handle_create_npc(
     drop_item: Option<Item>,
     home_chunk: Option<HomeChunk>,
     rtsim_entity: Option<RtSimEntity>,
+    projectile: Option<Projectile>,
 ) {
     let inventory = Inventory::new_with_loadout(loadout);
 
@@ -96,6 +97,12 @@ pub fn handle_create_npc(
         entity
     };
 
+    let entity = if let Some(projectile) = projectile {
+        entity.with(projectile)
+    } else {
+        entity
+    };
+
     let new_entity = entity.build();
 
     // Add to group system if a pet
diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs
index 7079b3bda3..7ab52e6b60 100644
--- a/server/src/events/mod.rs
+++ b/server/src/events/mod.rs
@@ -147,6 +147,7 @@ impl Server {
                     home_chunk,
                     drop_item,
                     rtsim_entity,
+                    projectile,
                 } => handle_create_npc(
                     self,
                     pos,
@@ -162,6 +163,7 @@ impl Server {
                     drop_item,
                     home_chunk,
                     rtsim_entity,
+                    projectile,
                 ),
                 ServerEvent::CreateShip {
                     pos,
diff --git a/server/src/rtsim/tick.rs b/server/src/rtsim/tick.rs
index c729445fa9..70f0608f61 100644
--- a/server/src/rtsim/tick.rs
+++ b/server/src/rtsim/tick.rs
@@ -127,7 +127,7 @@ impl<'a> System<'a> for Sys {
                     pos: comp::Pos(spawn_pos),
                     stats: comp::Stats::new(entity.get_name()),
                     skill_set: comp::SkillSet::default(),
-                    health: comp::Health::new(body, 10),
+                    health: Some(comp::Health::new(body, 10)),
                     loadout: match body {
                         comp::Body::Humanoid(_) => entity.get_loadout(),
                         _ => LoadoutBuilder::new().build(),
@@ -146,6 +146,7 @@ impl<'a> System<'a> for Sys {
                     drop_item: None,
                     home_chunk: None,
                     rtsim_entity,
+                    projectile: None,
                 },
             };
             server_emitter.emit(event);
diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs
index a929d8224d..339ec7b921 100644
--- a/server/src/state_ext.rs
+++ b/server/src/state_ext.rs
@@ -39,7 +39,7 @@ pub trait StateExt {
         pos: comp::Pos,
         stats: comp::Stats,
         skill_set: comp::SkillSet,
-        health: comp::Health,
+        health: Option<comp::Health>,
         poise: comp::Poise,
         inventory: comp::Inventory,
         body: comp::Body,
@@ -175,7 +175,7 @@ impl StateExt for State {
         pos: comp::Pos,
         stats: comp::Stats,
         skill_set: comp::SkillSet,
-        health: comp::Health,
+        health: Option<comp::Health>,
         poise: comp::Poise,
         inventory: comp::Inventory,
         body: comp::Body,
@@ -215,7 +215,7 @@ impl StateExt for State {
             ))
             .with(stats)
             .with(skill_set)
-            .with(health)
+            .maybe_with(health)
             .with(poise)
             .with(comp::Alignment::Npc)
             .with(comp::CharacterState::default())
diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs
index 458c710b28..eb668c7690 100644
--- a/server/src/sys/terrain.rs
+++ b/server/src/sys/terrain.rs
@@ -239,7 +239,7 @@ impl<'a> System<'a> for Sys {
                     loadout_builder.build()
                 };
 
-                let health = comp::Health::new(body, entity.level.unwrap_or(0));
+                let health = Some(comp::Health::new(body, entity.level.unwrap_or(0)));
                 let poise = comp::Poise::new(body);
 
                 let can_speak = match body {
@@ -293,6 +293,7 @@ impl<'a> System<'a> for Sys {
                     home_chunk: Some(comp::HomeChunk(key)),
                     drop_item: entity.loot_drop,
                     rtsim_entity: None,
+                    projectile: None,
                 })
             }
 
diff --git a/voxygen/anim/src/bird_large/mod.rs b/voxygen/anim/src/bird_large/mod.rs
index 1c4c83663e..67e7da5166 100644
--- a/voxygen/anim/src/bird_large/mod.rs
+++ b/voxygen/anim/src/bird_large/mod.rs
@@ -209,7 +209,7 @@ impl<'a> From<&'a Body> for SkeletonAttr {
             feed: match (body.species, body.body_type) {
                 (Phoenix, _) => (-0.65),
                 (Cockatrice, _) => (-0.5),
-                (Roc, _) => (-0.65),
+                (Roc, _) => (-0.4),
             },
         }
     }