diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs
index 2faf68ce69..dc293fbc6c 100644
--- a/common/src/comp/ability.rs
+++ b/common/src/comp/ability.rs
@@ -1,6 +1,7 @@
 use crate::{
-    comp::{Body, CharacterState, Item, Projectile, ToolData},
+    comp::{Body, CharacterState, EnergySource, Item, Projectile, StateUpdate, ToolData},
     states::*,
+    sys::character_behavior::JoinData,
 };
 use specs::{Component, DenseVecStorage, FlaggedStorage, HashMapStorage};
 use std::time::Duration;
@@ -37,6 +38,31 @@ pub enum CharacterAbility {
     },
 }
 
+impl CharacterAbility {
+    pub fn test_requirements(&self, data: &JoinData, update: &mut StateUpdate) -> bool {
+        match self {
+            CharacterAbility::Roll => {
+                data.physics.on_ground
+                    && !data.physics.in_fluid
+                    && data.body.is_humanoid()
+                    && update
+                        .energy
+                        .try_change_by(-200, EnergySource::Ability)
+                        .is_ok()
+            },
+            CharacterAbility::DashMelee { .. } => {
+                data.physics.on_ground
+                    && !data.physics.in_fluid
+                    && update
+                        .energy
+                        .try_change_by(-300, EnergySource::Ability)
+                        .is_ok()
+            },
+            _ => true,
+        }
+    }
+}
+
 impl Component for CharacterAbility {
     type Storage = DenseVecStorage<Self>;
 }
diff --git a/common/src/comp/energy.rs b/common/src/comp/energy.rs
index cb7fabee44..bbc481daca 100644
--- a/common/src/comp/energy.rs
+++ b/common/src/comp/energy.rs
@@ -11,8 +11,7 @@ pub struct Energy {
 
 #[derive(Clone, Copy, Debug, Serialize, Deserialize)]
 pub enum EnergySource {
-    CastSpell,
-    Roll,
+    Ability,
     Climb,
     LevelUp,
     HitEnemy,
diff --git a/common/src/states/utils.rs b/common/src/states/utils.rs
index e3c36f1887..c039db1310 100644
--- a/common/src/states/utils.rs
+++ b/common/src/states/utils.rs
@@ -183,78 +183,53 @@ pub fn handle_jump(data: &JoinData, update: &mut StateUpdate) {
 }
 
 /// If `inputs.primary` is pressed and in `Wielding` state,
-/// will attempt to go into `ability_pool.primary`
+/// will attempt to go into `loadout.active_item.primary_ability`
 pub fn handle_primary_input(data: &JoinData, update: &mut StateUpdate) {
     if data.inputs.primary.is_pressed() {
-        if let CharacterState::Wielding { .. } = update.character {
-            attempt_primary_ability(data, update);
+        if let Some(ability) = data
+            .loadout
+            .active_item
+            .as_ref()
+            .and_then(|i| i.primary_ability.as_ref())
+            .filter(|ability| ability.test_requirements(data, update))
+        {
+            update.character = ability.into();
         }
     }
 }
 
-/// Attempts to go into `ability_pool.primary` if is `Some()` on `AbilityPool`
-pub fn attempt_primary_ability(data: &JoinData, update: &mut StateUpdate) {
-    if let Some(ability) = data
-        .loadout
-        .active_item
-        .as_ref()
-        .and_then(|i| i.primary_ability.as_ref())
-    {
-        update.character = ability.into();
-    }
-}
-
 /// If `inputs.secondary` is pressed and in `Wielding` state,
-/// will attempt to go into `ability_pool.secondary`
+/// will attempt to go into `loadout.active_item.secondary_ability`
 pub fn handle_secondary_input(data: &JoinData, update: &mut StateUpdate) {
     if data.inputs.secondary.is_pressed() {
-        if let CharacterState::Wielding { .. } = update.character {
-            attempt_secondary_ability(data, update);
+        if let Some(ability) = data
+            .loadout
+            .active_item
+            .as_ref()
+            .and_then(|i| i.secondary_ability.as_ref())
+            .filter(|ability| ability.test_requirements(data, update))
+        {
+            update.character = ability.into();
         }
     }
 }
 
-/// Attempts to go into `ability_pool.secondary` if is `Some()` on `AbilityPool`
-pub fn attempt_secondary_ability(data: &JoinData, update: &mut StateUpdate) {
-    if let Some(ability) = data
-        .loadout
-        .active_item
-        .as_ref()
-        .and_then(|i| i.secondary_ability.as_ref())
-    {
-        update.character = ability.into();
-    }
-}
-
 /// Checks that player can perform a dodge, then
-/// attempts to go into `ability_pool.dodge`
+/// attempts to go into `loadout.active_item.dodge_ability`
 pub fn handle_dodge_input(data: &JoinData, update: &mut StateUpdate) {
-    if let CharacterState::Idle { .. } | CharacterState::Wielding { .. } = update.character {
-        if data.inputs.roll.is_pressed()
-            && data.physics.on_ground
-            && !data.physics.in_fluid
-            && data.body.is_humanoid()
-            && update
-                .energy
-                .try_change_by(-200, EnergySource::Roll)
-                .is_ok()
+    if data.inputs.roll.is_pressed() {
+        if let Some(ability) = data
+            .loadout
+            .active_item
+            .as_ref()
+            .and_then(|i| i.dodge_ability.as_ref())
+            .filter(|ability| ability.test_requirements(data, update))
         {
-            attempt_dodge_ability(data, update);
+            update.character = ability.into();
         }
     }
 }
 
-pub fn attempt_dodge_ability(data: &JoinData, update: &mut StateUpdate) {
-    if let Some(ability) = data
-        .loadout
-        .active_item
-        .as_ref()
-        .and_then(|i| i.dodge_ability.as_ref())
-    {
-        update.character = ability.into();
-    }
-}
-
 pub fn unwrap_tool_data<'a>(data: &'a JoinData) -> Option<&'a ToolData> {
     if let Some(Tool(tool)) = data.loadout.active_item.as_ref().map(|i| &i.item.kind) {
         Some(tool)