diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/Movecraft.java b/Movecraft/src/main/java/net/countercraft/movecraft/Movecraft.java
index e68d9c091..4af4ae27c 100644
--- a/Movecraft/src/main/java/net/countercraft/movecraft/Movecraft.java
+++ b/Movecraft/src/main/java/net/countercraft/movecraft/Movecraft.java
@@ -26,6 +26,8 @@
 import net.countercraft.movecraft.features.contacts.ContactsCommand;
 import net.countercraft.movecraft.features.contacts.ContactsManager;
 import net.countercraft.movecraft.features.contacts.ContactsSign;
+import net.countercraft.movecraft.features.status.StatusManager;
+import net.countercraft.movecraft.features.status.StatusSign;
 import net.countercraft.movecraft.listener.BlockListener;
 import net.countercraft.movecraft.listener.InteractListener;
 import net.countercraft.movecraft.listener.PlayerListener;
@@ -217,7 +219,6 @@ public void onEnable() {
         getServer().getPluginManager().registerEvents(new ReleaseSign(), this);
         getServer().getPluginManager().registerEvents(new RemoteSign(), this);
         getServer().getPluginManager().registerEvents(new SpeedSign(), this);
-        getServer().getPluginManager().registerEvents(new StatusSign(), this);
         getServer().getPluginManager().registerEvents(new SubcraftRotateSign(), this);
         getServer().getPluginManager().registerEvents(new TeleportSign(), this);
         getServer().getPluginManager().registerEvents(new ScuttleSign(), this);
@@ -225,9 +226,14 @@ public void onEnable() {
         var contactsManager = new ContactsManager();
         contactsManager.runTaskTimerAsynchronously(this, 0, 20);
         getServer().getPluginManager().registerEvents(contactsManager, this);
-        getServer().getPluginManager().registerEvents(new ContactsSign(contactsManager), this);
+        getServer().getPluginManager().registerEvents(new ContactsSign(), this);
         getCommand("contacts").setExecutor(new ContactsCommand());
 
+        var statusManager = new StatusManager();
+        statusManager.runTaskTimerAsynchronously(this, 0, 1);
+        getServer().getPluginManager().registerEvents(statusManager, this);
+        getServer().getPluginManager().registerEvents(new StatusSign(), this);
+
         logger.info("[V " + getDescription().getVersion() + "] has been enabled.");
     }
 
diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/async/AsyncManager.java b/Movecraft/src/main/java/net/countercraft/movecraft/async/AsyncManager.java
index c71dec0f9..fc2cfe889 100644
--- a/Movecraft/src/main/java/net/countercraft/movecraft/async/AsyncManager.java
+++ b/Movecraft/src/main/java/net/countercraft/movecraft/async/AsyncManager.java
@@ -24,44 +24,23 @@
 import net.countercraft.movecraft.async.rotation.RotationTask;
 import net.countercraft.movecraft.async.translation.TranslationTask;
 import net.countercraft.movecraft.config.Settings;
-import net.countercraft.movecraft.craft.Craft;
-import net.countercraft.movecraft.craft.CraftManager;
-import net.countercraft.movecraft.craft.CraftStatus;
-import net.countercraft.movecraft.craft.PilotedCraft;
-import net.countercraft.movecraft.craft.PlayerCraft;
-import net.countercraft.movecraft.craft.SinkingCraft;
+import net.countercraft.movecraft.craft.*;
 import net.countercraft.movecraft.craft.type.CraftType;
-import net.countercraft.movecraft.craft.type.RequiredBlockEntry;
 import net.countercraft.movecraft.events.CraftReleaseEvent;
-import net.countercraft.movecraft.exception.EmptyHitBoxException;
-import net.countercraft.movecraft.localisation.I18nSupport;
 import net.countercraft.movecraft.mapUpdater.MapUpdateManager;
 import net.countercraft.movecraft.mapUpdater.update.BlockCreateCommand;
 import net.countercraft.movecraft.mapUpdater.update.UpdateCommand;
-import net.countercraft.movecraft.util.Counter;
-import net.countercraft.movecraft.util.Tags;
 import net.countercraft.movecraft.util.hitboxes.HitBox;
-import net.kyori.adventure.key.Key;
-import net.kyori.adventure.sound.Sound;
 import net.kyori.adventure.text.Component;
-import org.bukkit.Bukkit;
 import org.bukkit.Location;
 import org.bukkit.Material;
 import org.bukkit.World;
 import org.bukkit.block.data.BlockData;
 import org.bukkit.entity.Player;
-import org.bukkit.inventory.InventoryHolder;
-import org.bukkit.inventory.ItemStack;
 import org.bukkit.scheduler.BukkitRunnable;
 import org.jetbrains.annotations.NotNull;
 
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.WeakHashMap;
+import java.util.*;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
 
@@ -319,40 +298,6 @@ else if (dive) {
         }
     }
 
-    private void detectSinking(){
-        for(Craft craft : CraftManager.getInstance()) {
-            if (craft instanceof SinkingCraft)
-                continue;
-            if (craft.getType().getDoubleProperty(CraftType.SINK_PERCENT) == 0.0 || !craft.isNotProcessing())
-                continue;
-            long ticksElapsed = (System.currentTimeMillis() - craft.getLastBlockCheck()) / 50;
-
-            if (ticksElapsed <= Settings.SinkCheckTicks)
-                continue;
-
-            CraftStatus status = checkCraftStatus(craft);
-            //If the craft is disabled, play a sound and disable it.
-            //Only do this if the craft isn't already disabled.
-            if (status.isDisabled() && craft.isNotProcessing() && !craft.getDisabled()) {
-                craft.setDisabled(true);
-                craft.getAudience().playSound(Sound.sound(Key.key("entity.iron_golem.death"), Sound.Source.NEUTRAL, 5.0f, 5.0f));
-            }
-
-
-            // if the craft is sinking, let the player
-            // know and release the craft. Otherwise
-            // update the time for the next check
-            if (status.isSinking() && craft.isNotProcessing()) {
-                craft.getAudience().sendMessage(I18nSupport.getInternationalisedComponent("Player - Craft is sinking"));
-                craft.setCruising(false);
-                CraftManager.getInstance().sink(craft);
-            }
-            else {
-                craft.setLastBlockCheck(System.currentTimeMillis());
-            }
-        }
-    }
-
     //Controls sinking crafts
     private void processSinking() {
         //copy the crafts before iteration to prevent concurrent modifications
@@ -440,7 +385,6 @@ public void run() {
         clearAll();
 
         processCruise();
-        detectSinking();
         processSinking();
         processFadingBlocks();
         processAlgorithmQueue();
@@ -472,95 +416,4 @@ private void clearAll() {
 
         clearanceSet.clear();
     }
-
-    public CraftStatus checkCraftStatus(@NotNull Craft craft) {
-        boolean isSinking = false;
-        boolean isDisabled = false;
-
-        // Create counters and populate with required block entries
-        Counter<RequiredBlockEntry> flyBlocks = new Counter<>();
-        flyBlocks.putAll(craft.getType().getRequiredBlockProperty(CraftType.FLY_BLOCKS));
-        Counter<RequiredBlockEntry> moveBlocks = new Counter<>();
-        moveBlocks.putAll(craft.getType().getRequiredBlockProperty(CraftType.MOVE_BLOCKS));
-
-        Counter<Material> materials = new Counter<>();
-        var v = craft.getType().getObjectProperty(CraftType.FUEL_TYPES);
-        if(!(v instanceof Map<?, ?>))
-            throw new IllegalStateException("FUEL_TYPES must be of type Map");
-        var fuelTypes = (Map<?, ?>) v;
-        for(var e : fuelTypes.entrySet()) {
-            if(!(e.getKey() instanceof Material))
-                throw new IllegalStateException("Keys in FUEL_TYPES must be of type Material");
-            if(!(e.getValue() instanceof Double))
-                throw new IllegalStateException("Values in FUEL_TYPES must be of type Double");
-        }
-
-        // go through each block in the HitBox, and if it's in the FlyBlocks or MoveBlocks, increment the counter
-        int totalNonNegligibleBlocks = 0;
-        int totalNonNegligibleWaterBlocks = 0;
-        double fuel = 0;
-        for (MovecraftLocation l : craft.getHitBox()) {
-            Material type = craft.getWorld().getBlockAt(l.getX(), l.getY(), l.getZ()).getType();
-            for(RequiredBlockEntry entry : flyBlocks.getKeySet()) {
-                if(entry.contains(type))
-                    flyBlocks.add(entry);
-            }
-            for(RequiredBlockEntry entry : moveBlocks.getKeySet()) {
-                if(entry.contains(type))
-                    moveBlocks.add(entry);
-            }
-            materials.add(type);
-
-            if (type != Material.FIRE && !type.isAir()) {
-                totalNonNegligibleBlocks++;
-            }
-            if (type != Material.FIRE && !type.isAir() && !Tags.FLUID.contains(type)) {
-                totalNonNegligibleWaterBlocks++;
-            }
-
-            if(Tags.FURNACES.contains(type)) {
-                InventoryHolder inventoryHolder = (InventoryHolder) craft.getWorld().getBlockAt(l.getX(), l.getY(), l.getZ()).getState();
-                for (ItemStack iStack : inventoryHolder.getInventory()) {
-                    if (iStack == null || !fuelTypes.containsKey(iStack.getType()))
-                        continue;
-                    fuel += iStack.getAmount() * (double) fuelTypes.get(iStack.getType());
-                }
-            }
-        }
-
-        // now see if any of the resulting percentages
-        // are below the threshold specified in sinkPercent
-        double sinkPercent = craft.getType().getDoubleProperty(CraftType.SINK_PERCENT) / 100.0;
-        for(RequiredBlockEntry entry : flyBlocks.getKeySet()) {
-            if(!entry.check(flyBlocks.get(entry), totalNonNegligibleBlocks, sinkPercent))
-                isSinking = true;
-        }
-        for(RequiredBlockEntry entry : moveBlocks.getKeySet()) {
-            if(!entry.check(moveBlocks.get(entry), totalNonNegligibleBlocks, sinkPercent))
-                isDisabled = !craft.getDisabled() && craft.isNotProcessing();
-        }
-
-        // And check the OverallSinkPercent
-        if (craft.getType().getDoubleProperty(CraftType.OVERALL_SINK_PERCENT) != 0.0) {
-            double percent;
-            if (craft.getType().getBoolProperty(CraftType.BLOCKED_BY_WATER)) {
-                percent = (double) totalNonNegligibleBlocks
-                        / (double) craft.getOrigBlockCount();
-            }
-            else {
-                percent = (double) totalNonNegligibleWaterBlocks
-                        / (double) craft.getOrigBlockCount();
-            }
-            if (percent * 100.0 < craft.getType().getDoubleProperty(CraftType.OVERALL_SINK_PERCENT))
-                isSinking = true;
-        }
-
-        if (totalNonNegligibleBlocks == 0)
-            isSinking = true;
-
-        craft.updateMaterials(materials);
-        craft.setTotalFuel(fuel);
-
-        return CraftStatus.of(isSinking, isDisabled);
-    }
 }
diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/craft/BaseCraft.java b/Movecraft/src/main/java/net/countercraft/movecraft/craft/BaseCraft.java
index 7622673a2..df8f4b336 100644
--- a/Movecraft/src/main/java/net/countercraft/movecraft/craft/BaseCraft.java
+++ b/Movecraft/src/main/java/net/countercraft/movecraft/craft/BaseCraft.java
@@ -42,8 +42,6 @@ public abstract class BaseCraft implements Craft {
     @NotNull
     protected final MutableHitBox collapsedHitBox;
     @NotNull
-    protected Counter<Material> materials;
-    @NotNull
     private final AtomicBoolean processing = new AtomicBoolean();
     private final long origPilotTime;
     @NotNull
@@ -65,7 +63,6 @@ public abstract class BaseCraft implements Craft {
     private int currentGear = 1;
     private double burningFuel;
     private int origBlockCount;
-    private double totalFuel = 0;
     @NotNull
     private Audience audience;
     @NotNull
@@ -85,7 +82,6 @@ public BaseCraft(@NotNull CraftType type, @NotNull World world) {
         cruising = false;
         disabled = false;
         origPilotTime = System.currentTimeMillis();
-        materials = new Counter<>();
         audience = Audience.empty();
     }
 
@@ -324,17 +320,15 @@ public int getTickCooldown() {
         if (this instanceof SinkingCraft)
             return type.getIntProperty(CraftType.SINK_RATE_TICKS);
 
-        if (materials.isEmpty()) {
-            for (MovecraftLocation location : hitBox) {
-                materials.add(location.toBukkit(w).getBlock().getType());
-            }
-        }
+        Counter<Material> materials = getDataTag(Craft.MATERIALS);
 
         int chestPenalty = 0;
-        for (Material m : Tags.CHESTS) {
-            chestPenalty += materials.get(m);
+        if (!materials.isEmpty()) {
+            for (Material m : Tags.CHESTS) {
+                chestPenalty += materials.get(m);
+            }
         }
-        chestPenalty *= type.getDoubleProperty(CraftType.CHEST_PENALTY);
+        chestPenalty *= (int) type.getDoubleProperty(CraftType.CHEST_PENALTY);
         if (!cruising)
             return ((int) type.getPerWorldProperty(CraftType.PER_WORLD_TICK_COOLDOWN, w) + chestPenalty) * (type.getBoolProperty(CraftType.GEAR_SHIFTS_AFFECT_TICK_COOLDOWN) ? currentGear : 1);
 
@@ -345,6 +339,9 @@ public int getTickCooldown() {
         // Dynamic Fly Block Speed
         int cruiseTickCooldown = (int) type.getPerWorldProperty(CraftType.PER_WORLD_CRUISE_TICK_COOLDOWN, w);
         if (type.getDoubleProperty(CraftType.DYNAMIC_FLY_BLOCK_SPEED_FACTOR) != 0) {
+            if (materials.isEmpty()) {
+                return ((int) type.getPerWorldProperty(CraftType.PER_WORLD_TICK_COOLDOWN, w) + chestPenalty) * (type.getBoolProperty(CraftType.GEAR_SHIFTS_AFFECT_TICK_COOLDOWN) ? currentGear : 1);
+            }
             EnumSet<Material> flyBlockMaterials = type.getMaterialSetProperty(CraftType.DYNAMIC_FLY_BLOCK);
             double count = 0;
             for (Material m : flyBlockMaterials) {
@@ -536,25 +533,6 @@ public void setAudience(@NotNull Audience audience) {
         this.audience = audience;
     }
 
-    public void updateMaterials (Counter<Material> counter) {
-        materials = counter;
-    }
-
-    @Override
-    public Counter<Material> getMaterials() {
-        return materials;
-    }
-
-    @Override
-    public void setTotalFuel(double fuel) {
-        totalFuel = fuel;
-    }
-
-    @Override
-    public double getTotalFuel() {
-        return totalFuel;
-    }
-
     @Override
     public CraftDataTagContainer getDataTagContainer() {
         return dataTagContainer;
diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/features/contacts/ContactsCommand.java b/Movecraft/src/main/java/net/countercraft/movecraft/features/contacts/ContactsCommand.java
index 3307022c8..08e5cb1a9 100644
--- a/Movecraft/src/main/java/net/countercraft/movecraft/features/contacts/ContactsCommand.java
+++ b/Movecraft/src/main/java/net/countercraft/movecraft/features/contacts/ContactsCommand.java
@@ -57,7 +57,7 @@ public boolean onCommand(@NotNull CommandSender commandSender, @NotNull Command
         ComponentPaginator paginator = new ComponentPaginator(
                 I18nSupport.getInternationalisedComponent("Contacts"),
                 (pageNumber) -> "/contacts " + pageNumber);
-        for (Craft target : base.getDataTag(ContactsManager.CONTACTS)) {
+        for (Craft target : base.getDataTag(Craft.CONTACTS)) {
             if (target.getHitBox().isEmpty())
                 continue;
 
diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/features/contacts/ContactsManager.java b/Movecraft/src/main/java/net/countercraft/movecraft/features/contacts/ContactsManager.java
index 2097b4a1b..359493f47 100644
--- a/Movecraft/src/main/java/net/countercraft/movecraft/features/contacts/ContactsManager.java
+++ b/Movecraft/src/main/java/net/countercraft/movecraft/features/contacts/ContactsManager.java
@@ -27,7 +27,6 @@
 import java.util.*;
 
 public class ContactsManager extends BukkitRunnable implements Listener {
-    public static final CraftDataTagKey<List<Craft>> CONTACTS = CraftDataTagContainer.tryRegisterTagKey(new NamespacedKey("movecraft", "contacts"), craft -> new ArrayList<>(0));
     private static final CraftDataTagKey<Map<Craft, Long>> RECENT_CONTACTS = CraftDataTagContainer.tryRegisterTagKey(new NamespacedKey("movecraft", "recent-contacts"), craft -> new WeakHashMap<>());
 
     @Override
@@ -52,7 +51,7 @@ private void runContacts() {
     }
 
     private void update(@NotNull Craft base, @NotNull Set<Craft> craftsInWorld) {
-        List<Craft> previousContacts = base.getDataTag(CONTACTS);
+        List<Craft> previousContacts = base.getDataTag(Craft.CONTACTS);
         if (previousContacts == null)
             previousContacts = new ArrayList<>(0);
         List<Craft> futureContacts = get(base, craftsInWorld);
@@ -71,7 +70,7 @@ private void update(@NotNull Craft base, @NotNull Set<Craft> craftsInWorld) {
             Bukkit.getServer().getPluginManager().callEvent(event);
         }
 
-        base.setDataTag(CONTACTS, futureContacts);
+        base.setDataTag(Craft.CONTACTS, futureContacts);
     }
 
     private @NotNull List<Craft> get(Craft base, @NotNull Set<Craft> craftsInWorld) {
@@ -126,7 +125,7 @@ private void runRecentContacts() {
                 if (base.getHitBox().isEmpty())
                     continue;
 
-                for (Craft target : base.getDataTag(CONTACTS)) {
+                for (Craft target : base.getDataTag(Craft.CONTACTS)) {
                     // has the craft not been seen in the last minute?
                     if (System.currentTimeMillis() - base.getDataTag(RECENT_CONTACTS).getOrDefault(target, 0L) <= 60000)
                         continue;
@@ -235,12 +234,12 @@ public void onCraftSink(@NotNull CraftSinkEvent e) {
 
     private void remove(Craft base) {
         for (Craft other : CraftManager.getInstance().getCrafts()) {
-            List<Craft> contacts = other.getDataTag(CONTACTS);
+            List<Craft> contacts = other.getDataTag(Craft.CONTACTS);
             if (contacts.contains(base))
                 continue;
 
             contacts.remove(base);
-            other.setDataTag(CONTACTS, contacts);
+            other.setDataTag(Craft.CONTACTS, contacts);
         }
 
         for (Craft other : CraftManager.getInstance().getCrafts()) {
diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/features/contacts/ContactsSign.java b/Movecraft/src/main/java/net/countercraft/movecraft/features/contacts/ContactsSign.java
index 4bcef2711..83f52d845 100644
--- a/Movecraft/src/main/java/net/countercraft/movecraft/features/contacts/ContactsSign.java
+++ b/Movecraft/src/main/java/net/countercraft/movecraft/features/contacts/ContactsSign.java
@@ -20,11 +20,6 @@
 
 public class ContactsSign implements Listener {
     private static final String HEADER = "Contacts:";
-    private final ContactsManager contactsManager;
-
-    public ContactsSign(ContactsManager contactsManager) {
-        this.contactsManager = contactsManager;
-    }
 
     @EventHandler
     public void onCraftDetect(@NotNull CraftDetectEvent event) {
@@ -55,7 +50,7 @@ public final void onSignTranslateEvent(@NotNull SignTranslateEvent event) {
 
         Craft base = event.getCraft();
         int line = 1;
-        for (Craft target : base.getDataTag(ContactsManager.CONTACTS)) {
+        for (Craft target : base.getDataTag(Craft.CONTACTS)) {
             if (line > 3)
                 break;
 
diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/features/status/StatusManager.java b/Movecraft/src/main/java/net/countercraft/movecraft/features/status/StatusManager.java
new file mode 100644
index 000000000..08b2b2afb
--- /dev/null
+++ b/Movecraft/src/main/java/net/countercraft/movecraft/features/status/StatusManager.java
@@ -0,0 +1,177 @@
+package net.countercraft.movecraft.features.status;
+
+import net.countercraft.movecraft.MovecraftLocation;
+import net.countercraft.movecraft.config.Settings;
+import net.countercraft.movecraft.craft.Craft;
+import net.countercraft.movecraft.craft.CraftManager;
+import net.countercraft.movecraft.craft.SinkingCraft;
+import net.countercraft.movecraft.craft.datatag.CraftDataTagContainer;
+import net.countercraft.movecraft.craft.datatag.CraftDataTagKey;
+import net.countercraft.movecraft.craft.type.CraftType;
+import net.countercraft.movecraft.craft.type.RequiredBlockEntry;
+import net.countercraft.movecraft.features.status.events.CraftStatusUpdateEvent;
+import net.countercraft.movecraft.localisation.I18nSupport;
+import net.countercraft.movecraft.processing.WorldManager;
+import net.countercraft.movecraft.processing.effects.Effect;
+import net.countercraft.movecraft.util.Counter;
+import net.countercraft.movecraft.util.Tags;
+import net.kyori.adventure.key.Key;
+import net.kyori.adventure.sound.Sound;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.NamespacedKey;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.inventory.InventoryHolder;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.scheduler.BukkitRunnable;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Map;
+import java.util.function.Supplier;
+
+public class StatusManager extends BukkitRunnable implements Listener {
+    private static final CraftDataTagKey<Long> LAST_STATUS_CHECK = CraftDataTagContainer.tryRegisterTagKey(new NamespacedKey("movecraft", "last-status-check"), craft -> System.currentTimeMillis());
+
+    @Override
+    public void run() {
+        for (Craft c : CraftManager.getInstance().getCrafts()) {
+            long ticksElapsed = (System.currentTimeMillis() - c.getDataTag(LAST_STATUS_CHECK)) / 50;
+            if (ticksElapsed <= Settings.SinkCheckTicks)
+                continue;
+
+            c.setDataTag(LAST_STATUS_CHECK, System.currentTimeMillis());
+            WorldManager.INSTANCE.submit(new StatusUpdateTask(c));
+        }
+    }
+
+    private static final class StatusUpdateTask implements Supplier<Effect> {
+        private final Craft craft;
+        private final Map<Material, Double> fuelTypes;
+
+        private StatusUpdateTask(@NotNull Craft craft) {
+            this.craft = craft;
+
+            Object object = craft.getType().getObjectProperty(CraftType.FUEL_TYPES);
+            if(!(object instanceof Map<?, ?> map))
+                throw new IllegalStateException("FUEL_TYPES must be of type Map");
+            for(var e : map.entrySet()) {
+                if(!(e.getKey() instanceof Material))
+                    throw new IllegalStateException("Keys in FUEL_TYPES must be of type Material");
+                if(!(e.getValue() instanceof Double))
+                    throw new IllegalStateException("Values in FUEL_TYPES must be of type Double");
+            }
+            fuelTypes = (Map<Material, Double>) map;
+        }
+
+        @Override
+        public @Nullable Effect get() {
+            Counter<Material> materials = new Counter<>();
+            int nonNegligibleBlocks = 0;
+            int nonNegligibleSolidBlocks = 0;
+            double fuel = 0;
+            for (MovecraftLocation l : craft.getHitBox()) {
+                Material type = craft.getMovecraftWorld().getMaterial(l);
+                materials.add(type);
+
+                if (type != Material.FIRE && !type.isAir()) {
+                    nonNegligibleBlocks++;
+                }
+                if (type != Material.FIRE && !type.isAir() && !Tags.FLUID.contains(type)) {
+                    nonNegligibleSolidBlocks++;
+                }
+
+                if (Tags.FURNACES.contains(type)) {
+                    InventoryHolder inventoryHolder = (InventoryHolder) craft.getMovecraftWorld().getState(l);
+                    for (ItemStack iStack : inventoryHolder.getInventory()) {
+                        if (iStack == null || !fuelTypes.containsKey(iStack.getType()))
+                            continue;
+                        fuel += iStack.getAmount() * fuelTypes.get(iStack.getType());
+                    }
+                }
+            }
+
+            craft.setDataTag(Craft.FUEL, fuel);
+            craft.setDataTag(Craft.MATERIALS, materials);
+            craft.setDataTag(Craft.NON_NEGLIGIBLE_BLOCKS, nonNegligibleBlocks);
+            craft.setDataTag(Craft.NON_NEGLIGIBLE_SOLID_BLOCKS, nonNegligibleSolidBlocks);
+            craft.setDataTag(LAST_STATUS_CHECK, System.currentTimeMillis());
+            return () -> Bukkit.getPluginManager().callEvent(new CraftStatusUpdateEvent(craft));
+        }
+    }
+
+    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+    public void onCraftStatusUpdate(@NotNull CraftStatusUpdateEvent e) {
+        Craft craft = e.getCraft();
+        if (craft instanceof SinkingCraft)
+            return;
+        if (craft.getType().getDoubleProperty(CraftType.SINK_PERCENT) == 0.0)
+            return;
+
+        boolean sinking = false;
+        boolean disabled = false;
+        Counter<Material> materials = craft.getDataTag(Craft.MATERIALS);
+        int nonNegligibleBlocks = craft.getDataTag(Craft.NON_NEGLIGIBLE_BLOCKS);
+        int nonNegligibleSolidBlocks = craft.getDataTag(Craft.NON_NEGLIGIBLE_SOLID_BLOCKS);
+
+        // Build up counters of the fly and move blocks
+        Counter<RequiredBlockEntry> flyBlocks = new Counter<>();
+        flyBlocks.putAll(craft.getType().getRequiredBlockProperty(CraftType.FLY_BLOCKS));
+        Counter<RequiredBlockEntry> moveBlocks = new Counter<>();
+        moveBlocks.putAll(craft.getType().getRequiredBlockProperty(CraftType.MOVE_BLOCKS));
+        for (Material m : materials.getKeySet()) {
+            for (RequiredBlockEntry entry : flyBlocks.getKeySet()) {
+                if(entry.contains(m))
+                    flyBlocks.add(entry, materials.get(m));
+            }
+            for (RequiredBlockEntry entry : moveBlocks.getKeySet()) {
+                if(entry.contains(m))
+                    moveBlocks.add(entry, materials.get(m));
+            }
+        }
+
+        // now see if any of the resulting percentages are below the threshold specified in sinkPercent
+        double sinkPercent = craft.getType().getDoubleProperty(CraftType.SINK_PERCENT) / 100.0;
+        for (RequiredBlockEntry entry : flyBlocks.getKeySet()) {
+            if(!entry.check(flyBlocks.get(entry), nonNegligibleBlocks, sinkPercent))
+                sinking = true;
+        }
+        for (RequiredBlockEntry entry : moveBlocks.getKeySet()) {
+            if (!entry.check(moveBlocks.get(entry), nonNegligibleBlocks, sinkPercent))
+                disabled = true;
+        }
+
+        // And check the OverallSinkPercent
+        if (craft.getType().getDoubleProperty(CraftType.OVERALL_SINK_PERCENT) != 0.0) {
+            double percent;
+            if (craft.getType().getBoolProperty(CraftType.BLOCKED_BY_WATER)) {
+                percent = (double) nonNegligibleBlocks
+                        / (double) craft.getOrigBlockCount();
+            }
+            else {
+                percent = (double) nonNegligibleSolidBlocks
+                        / (double) craft.getOrigBlockCount();
+            }
+            if (percent * 100.0 < craft.getType().getDoubleProperty(CraftType.OVERALL_SINK_PERCENT))
+                sinking = true;
+        }
+
+        if (nonNegligibleBlocks == 0)
+            sinking = true;
+
+        // If the craft is disabled, play a sound and disable it.
+        if (disabled && !craft.getDisabled()) {
+            craft.setDisabled(true);
+            craft.getAudience().playSound(Sound.sound(Key.key("entity.iron_golem.death"), Sound.Source.NEUTRAL, 5.0f, 5.0f));
+        }
+
+        // If the craft is sinking, let the player know and sink the craft.
+        if (sinking) {
+            craft.getAudience().sendMessage(I18nSupport.getInternationalisedComponent("Player - Craft is sinking"));
+            craft.setCruising(false);
+            CraftManager.getInstance().sink(craft);
+        }
+    }
+}
diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/sign/StatusSign.java b/Movecraft/src/main/java/net/countercraft/movecraft/features/status/StatusSign.java
similarity index 96%
rename from Movecraft/src/main/java/net/countercraft/movecraft/sign/StatusSign.java
rename to Movecraft/src/main/java/net/countercraft/movecraft/features/status/StatusSign.java
index f7ab7df41..1246c1381 100644
--- a/Movecraft/src/main/java/net/countercraft/movecraft/sign/StatusSign.java
+++ b/Movecraft/src/main/java/net/countercraft/movecraft/features/status/StatusSign.java
@@ -1,4 +1,4 @@
-package net.countercraft.movecraft.sign;
+package net.countercraft.movecraft.features.status;
 
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
@@ -10,6 +10,7 @@
 import net.countercraft.movecraft.craft.type.RequiredBlockEntry;
 import net.countercraft.movecraft.events.CraftDetectEvent;
 import net.countercraft.movecraft.events.SignTranslateEvent;
+import net.countercraft.movecraft.features.status.StatusManager;
 import net.countercraft.movecraft.util.Counter;
 import net.countercraft.movecraft.util.Tags;
 import org.bukkit.ChatColor;
@@ -63,11 +64,11 @@ public final void onSignTranslate(SignTranslateEvent event) {
         if (!ChatColor.stripColor(event.getLine(0)).equalsIgnoreCase("Status:")) {
             return;
         }
-        double fuel = craft.getTotalFuel();
+        double fuel = craft.getDataTag(Craft.FUEL);
 
         int totalNonNegligibleBlocks = 0;
         int totalNonNegligibleWaterBlocks = 0;
-        Counter<Material> materials = craft.getMaterials();
+        Counter<Material> materials = craft.getDataTag(Craft.MATERIALS);
         if (materials.isEmpty()) {
             return;
         }
diff --git a/Movecraft/src/main/java/net/countercraft/movecraft/features/status/events/CraftStatusUpdateEvent.java b/Movecraft/src/main/java/net/countercraft/movecraft/features/status/events/CraftStatusUpdateEvent.java
new file mode 100644
index 000000000..ea5d5588d
--- /dev/null
+++ b/Movecraft/src/main/java/net/countercraft/movecraft/features/status/events/CraftStatusUpdateEvent.java
@@ -0,0 +1,24 @@
+package net.countercraft.movecraft.features.status.events;
+
+import net.countercraft.movecraft.craft.Craft;
+import net.countercraft.movecraft.events.CraftEvent;
+import org.bukkit.event.HandlerList;
+import org.jetbrains.annotations.NotNull;
+
+public class CraftStatusUpdateEvent extends CraftEvent {
+    private static final HandlerList HANDLERS = new HandlerList();
+
+    public CraftStatusUpdateEvent(@NotNull Craft craft) {
+        super(craft);
+    }
+
+    @SuppressWarnings("unused")
+    public static HandlerList getHandlerList() {
+        return HANDLERS;
+    }
+
+    @Override
+    public HandlerList getHandlers() {
+        return HANDLERS;
+    }
+}
diff --git a/api/src/main/java/net/countercraft/movecraft/craft/Craft.java b/api/src/main/java/net/countercraft/movecraft/craft/Craft.java
index f53aa96a0..99af4910d 100644
--- a/api/src/main/java/net/countercraft/movecraft/craft/Craft.java
+++ b/api/src/main/java/net/countercraft/movecraft/craft/Craft.java
@@ -30,14 +30,22 @@
 import net.kyori.adventure.audience.Audience;
 import org.bukkit.Location;
 import org.bukkit.Material;
+import org.bukkit.NamespacedKey;
 import org.bukkit.World;
 import org.bukkit.block.Sign;
 import org.bukkit.block.data.BlockData;
 import org.jetbrains.annotations.NotNull;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 
 public interface Craft {
+    CraftDataTagKey<List<Craft>> CONTACTS = CraftDataTagContainer.tryRegisterTagKey(new NamespacedKey("movecraft", "contacts"), craft -> new ArrayList<>(0));
+    CraftDataTagKey<Double> FUEL = CraftDataTagContainer.tryRegisterTagKey(new NamespacedKey("movecraft", "fuel"), craft -> 0D);
+    CraftDataTagKey<Counter<Material>> MATERIALS = CraftDataTagContainer.tryRegisterTagKey(new NamespacedKey("movecraft", "materials"), craft -> new Counter<>());
+    CraftDataTagKey<Integer> NON_NEGLIGIBLE_BLOCKS = CraftDataTagContainer.tryRegisterTagKey(new NamespacedKey("movecraft", "non-negligible-blocks"), Craft::getOrigBlockCount);
+    CraftDataTagKey<Integer> NON_NEGLIGIBLE_SOLID_BLOCKS = CraftDataTagContainer.tryRegisterTagKey(new NamespacedKey("movecraft", "non-negligible-solid-blocks"), Craft::getOrigBlockCount);
 
     @Deprecated
     boolean isNotProcessing();
@@ -249,14 +257,6 @@ default void setLastDZ(int dZ){}
 
     void setAudience(Audience audience);
 
-    Counter<Material> getMaterials ();
-
-    void updateMaterials (Counter<Material> materials);
-
-    double getTotalFuel ();
-
-    void setTotalFuel (double fuel);
-
     public default CraftDataTagContainer getDataTagContainer() {
         return null;
     }
diff --git a/api/src/main/java/net/countercraft/movecraft/craft/CraftStatus.java b/api/src/main/java/net/countercraft/movecraft/craft/CraftStatus.java
deleted file mode 100644
index 5e16ac713..000000000
--- a/api/src/main/java/net/countercraft/movecraft/craft/CraftStatus.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package net.countercraft.movecraft.craft;
-
-public enum CraftStatus {
-    NORMAL,
-    SINKING,
-    DISABLED,
-    SINKING_DISABLED;
-
-    public boolean isSinking() {
-        return (this == SINKING || this == SINKING_DISABLED);
-    }
-
-    public boolean isDisabled() {
-        return (this == DISABLED || this == SINKING_DISABLED);
-    }
-
-    public static CraftStatus of (boolean sinking, boolean disabled) {
-        if (!sinking && !disabled) {
-            return NORMAL;
-        }
-        if (!disabled) {
-            return SINKING;
-        }
-        if (!sinking) {
-            return DISABLED;
-        }
-        return SINKING_DISABLED;
-    }
-}
\ No newline at end of file
diff --git a/api/src/main/java/net/countercraft/movecraft/processing/WorldManager.java b/api/src/main/java/net/countercraft/movecraft/processing/WorldManager.java
index d0db41e65..ecef2e3df 100644
--- a/api/src/main/java/net/countercraft/movecraft/processing/WorldManager.java
+++ b/api/src/main/java/net/countercraft/movecraft/processing/WorldManager.java
@@ -45,9 +45,10 @@ public void run() {
         if(tasks.isEmpty())
             return;
         running = true;
-        int remaining = tasks.size();
+        int remaining = 0;
         List<CompletableFuture<Effect>> inProgress = new ArrayList<>();
         while(!tasks.isEmpty()){
+            remaining++;
             inProgress.add(CompletableFuture.supplyAsync(tasks.poll()).whenComplete((effect, exception) -> {
                 poison();
                 if(exception != null){
diff --git a/buildSrc/src/main/kotlin/buildlogic.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/buildlogic.java-conventions.gradle.kts
index 9087d9dbe..91e1dbe0f 100644
--- a/buildSrc/src/main/kotlin/buildlogic.java-conventions.gradle.kts
+++ b/buildSrc/src/main/kotlin/buildlogic.java-conventions.gradle.kts
@@ -11,7 +11,7 @@ repositories {
 }
 
 group = "net.countercraft"
-version = "8.0.0_beta-4"
+version = "8.0.0_beta-5_dev-1"
 
 tasks.withType<JavaCompile>() {
     options.encoding = "UTF-8"