--- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java
+++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java
@@ -34,7 +_,7 @@
     private static final int[][] CACHED_SLOTS = new int[54][];
     private NonNullList<ItemStack> items = NonNullList.withSize(5, ItemStack.EMPTY);
     public int cooldownTime = -1;
-    private long tickedGameTime;
+    private long tickedGameTime = Long.MIN_VALUE; // Folia - region threading
     private Direction facing;
 
     // CraftBukkit start - add fields and methods
@@ -67,6 +_,15 @@
     }
     // CraftBukkit end
 
+    // Folia start - region threading
+    @Override
+    public void updateTicks(final long fromTickOffset, final long fromRedstoneTimeOffset) {
+        super.updateTicks(fromTickOffset, fromRedstoneTimeOffset);
+        if (this.tickedGameTime != Long.MIN_VALUE) {
+            this.tickedGameTime += fromRedstoneTimeOffset;
+        }
+    }
+    // Folia end - region threading
 
     public HopperBlockEntity(BlockPos pos, BlockState blockState) {
         super(BlockEntityType.HOPPER, pos, blockState);
@@ -125,7 +_,7 @@
 
     public static void pushItemsTick(Level level, BlockPos pos, BlockState state, HopperBlockEntity blockEntity) {
         blockEntity.cooldownTime--;
-        blockEntity.tickedGameTime = level.getGameTime();
+        blockEntity.tickedGameTime = level.getRedstoneGameTime(); // Folia - region threading
         if (!blockEntity.isOnCooldown()) {
             blockEntity.setCooldown(0);
             // Spigot start
@@ -213,12 +_,11 @@
     }
 
     // Paper start - Perf: Optimize Hoppers
-    public static boolean skipHopperEvents;
-    private static boolean skipPullModeEventFire;
-    private static boolean skipPushModeEventFire;
+    // Folia - region threading - moved to RegionizedWorldData
 
     private static boolean hopperPush(final Level level, final Container destination, final Direction direction, final HopperBlockEntity hopper) {
-        skipPushModeEventFire = skipHopperEvents;
+        io.papermc.paper.threadedregions.RegionizedWorldData worldData = level.getCurrentWorldData(); // Folia - region threading
+        worldData.skipPushModeEventFire = worldData.skipHopperEvents; // Folia - region threading
         boolean foundItem = false;
         for (int i = 0; i < hopper.getContainerSize(); ++i) {
             final ItemStack item = hopper.getItem(i);
@@ -233,7 +_,7 @@
 
                 // We only need to fire the event once to give protection plugins a chance to cancel this event
                 // Because nothing uses getItem, every event call should end up the same result.
-                if (!skipPushModeEventFire) {
+                if (!worldData.skipPushModeEventFire) { // Folia - region threading
                     movedItem = callPushMoveEvent(destination, movedItem, hopper);
                     if (movedItem == null) { // cancelled
                         origItemStack.setCount(originalItemCount);
@@ -263,13 +_,14 @@
     }
 
     private static boolean hopperPull(final Level level, final Hopper hopper, final Container container, ItemStack origItemStack, final int i) {
+        io.papermc.paper.threadedregions.RegionizedWorldData worldData = level.getCurrentWorldData(); // Folia - region threading
         ItemStack movedItem = origItemStack;
         final int originalItemCount = origItemStack.getCount();
         final int movedItemCount = Math.min(level.spigotConfig.hopperAmount, originalItemCount);
         container.setChanged(); // original logic always marks source inv as changed even if no move happens.
         movedItem.setCount(movedItemCount);
 
-        if (!skipPullModeEventFire) {
+        if (!worldData.skipPullModeEventFire) { // Folia - region threading
             movedItem = callPullMoveEvent(hopper, container, movedItem);
             if (movedItem == null) { // cancelled
                 origItemStack.setCount(originalItemCount);
@@ -289,9 +_,9 @@
                 origItemStack.setCount(originalItemCount - movedItemCount + remainingItemCount);
             }
 
-            ignoreBlockEntityUpdates = true;
+            IGNORE_TILE_UPDATES.set(true); // Folia - region threading
             container.setItem(i, origItemStack);
-            ignoreBlockEntityUpdates = false;
+            IGNORE_TILE_UPDATES.set(false); // Folia - region threading
             container.setChanged();
             return true;
         }
@@ -306,6 +_,7 @@
 
     @Nullable
     private static ItemStack callPushMoveEvent(Container destination, ItemStack itemStack, HopperBlockEntity hopper) {
+        io.papermc.paper.threadedregions.RegionizedWorldData worldData = io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData(); // Folia - region threading
         final org.bukkit.inventory.Inventory destinationInventory = getInventory(destination);
         final io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent event = new io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent(
             hopper.getOwner(false).getInventory(),
@@ -315,7 +_,7 @@
         );
         final boolean result = event.callEvent();
         if (!event.calledGetItem && !event.calledSetItem) {
-            skipPushModeEventFire = true;
+            worldData.skipPushModeEventFire = true; // Folia - region threading
         }
         if (!result) {
             applyCooldown(hopper);
@@ -331,6 +_,7 @@
 
     @Nullable
     private static ItemStack callPullMoveEvent(final Hopper hopper, final Container container, final ItemStack itemstack) {
+        io.papermc.paper.threadedregions.RegionizedWorldData worldData = io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData(); // Folia - region threading
         final org.bukkit.inventory.Inventory sourceInventory = getInventory(container);
         final org.bukkit.inventory.Inventory destination = getInventory(hopper);
 
@@ -338,7 +_,7 @@
         final io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent event = new io.papermc.paper.event.inventory.PaperInventoryMoveItemEvent(sourceInventory, org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(itemstack), destination, false);
         final boolean result = event.callEvent();
         if (!event.calledGetItem && !event.calledSetItem) {
-            skipPullModeEventFire = true;
+            worldData.skipPullModeEventFire = true; // Folia - region threading
         }
         if (!result) {
             applyCooldown(hopper);
@@ -524,12 +_,13 @@
     }
 
     public static boolean suckInItems(Level level, Hopper hopper) {
+        io.papermc.paper.threadedregions.RegionizedWorldData worldData = io.papermc.paper.threadedregions.TickRegionScheduler.getCurrentRegionizedWorldData(); // Folia - region threading
         BlockPos blockPos = BlockPos.containing(hopper.getLevelX(), hopper.getLevelY() + 1.0, hopper.getLevelZ());
         BlockState blockState = level.getBlockState(blockPos);
         Container sourceContainer = getSourceContainer(level, hopper, blockPos, blockState);
         if (sourceContainer != null) {
             Direction direction = Direction.DOWN;
-            skipPullModeEventFire = skipHopperEvents; // Paper - Perf: Optimize Hoppers
+            worldData.skipPullModeEventFire = worldData.skipHopperEvents; // Paper - Perf: Optimize Hoppers // Folia - region threading
 
             for (int i : getSlots(sourceContainer, direction)) {
                 if (tryTakeInItemFromSlot(hopper, sourceContainer, i, direction, level)) { // Spigot
@@ -678,9 +_,9 @@
                     stack = stack.split(destination.getMaxStackSize());
                 }
                 // Spigot end
-                ignoreBlockEntityUpdates = true; // Paper - Perf: Optimize Hoppers
+                IGNORE_TILE_UPDATES.set(Boolean.TRUE); // Paper - Perf: Optimize Hoppers // Folia - region threading
                 destination.setItem(slot, stack);
-                ignoreBlockEntityUpdates = false; // Paper - Perf: Optimize Hoppers
+                IGNORE_TILE_UPDATES.set(Boolean.FALSE); // Paper - Perf: Optimize Hoppers // Folia - region threading
                 stack = leftover; // Paper - Make hoppers respect inventory max stack size
                 flag = true;
             } else if (canMergeItems(item, stack)) {
