diff --git a/patches/api/0001-Add-BlockDestroyedByNeighborEvent.patch b/patches/api/0001-Add-BlockDestroyedByNeighborEvent.patch new file mode 100644 index 000000000..13e6641c8 --- /dev/null +++ b/patches/api/0001-Add-BlockDestroyedByNeighborEvent.patch @@ -0,0 +1,79 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Cryptite +Date: Sat, 9 Nov 2024 09:48:21 -0600 +Subject: [PATCH] Add BlockDestroyedByNeighborEvent + + +diff --git a/src/main/java/io/papermc/paper/event/block/BlockDestroyedByNeighborEvent.java b/src/main/java/io/papermc/paper/event/block/BlockDestroyedByNeighborEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..7db2789d54a609b023ea6deff87b45d717aabbf0 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/event/block/BlockDestroyedByNeighborEvent.java +@@ -0,0 +1,67 @@ ++package io.papermc.paper.event.block; ++ ++import org.bukkit.block.Block; ++import org.bukkit.entity.Player; ++import org.bukkit.event.Cancellable; ++import org.bukkit.event.HandlerList; ++import org.bukkit.event.block.BlockEvent; ++import org.jetbrains.annotations.NotNull; ++import org.jetbrains.annotations.Nullable; ++ ++/** ++ * Called when a block is broken another block. This is generally the result of BlockPhysicsEvent propagation ++ */ ++public class BlockDestroyedByNeighborEvent extends BlockEvent implements Cancellable { ++ private static final HandlerList handlers = new HandlerList(); ++ private final Player player; ++ private final Block sourceBlock; ++ private boolean cancel; ++ ++ public BlockDestroyedByNeighborEvent(@NotNull final Block theBlock, @Nullable Player player, @NotNull final Block sourceBlock) { ++ super(theBlock); ++ ++ this.player = player; ++ this.sourceBlock = sourceBlock; ++ } ++ ++ /** ++ * Gets the Player that caused this ++ * ++ * @return The Player that is breaking the block involved in this event ++ */ ++ @Nullable ++ public Player getPlayer() { ++ return player; ++ } ++ ++ /** ++ * Gets the source block that caused this block break ++ * ++ * @return The Source Block which block is involved in this event ++ */ ++ @NotNull ++ public final Block getSourceBlock() { ++ return sourceBlock; ++ } ++ ++ @Override ++ public boolean isCancelled() { ++ return cancel; ++ } ++ ++ @Override ++ public void setCancelled(boolean cancel) { ++ this.cancel = cancel; ++ } ++ ++ @Override ++ @NotNull ++ public HandlerList getHandlers() { ++ return handlers; ++ } ++ ++ @NotNull ++ public static HandlerList getHandlerList() { ++ return handlers; ++ } ++} diff --git a/patches/api/0013-Expose-getBlockPosBelowThatAffectsMyMovement.patch b/patches/api/0013-Expose-getBlockPosBelowThatAffectsMyMovement.patch new file mode 100644 index 000000000..932fbe463 --- /dev/null +++ b/patches/api/0013-Expose-getBlockPosBelowThatAffectsMyMovement.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Cryptite +Date: Sun, 17 Nov 2024 08:24:22 -0600 +Subject: [PATCH] Expose getBlockPosBelowThatAffectsMyMovement + + +diff --git a/src/main/java/org/bukkit/entity/Entity.java b/src/main/java/org/bukkit/entity/Entity.java +index 6324e8f11a382288fc0a6c30f47760ea50e231d5..8188b3a27fddac18cf49db5a8049149e784f4cbc 100644 +--- a/src/main/java/org/bukkit/entity/Entity.java ++++ b/src/main/java/org/bukkit/entity/Entity.java +@@ -1187,4 +1187,12 @@ public interface Entity extends Metadatable, CommandSender, Nameable, Persistent + */ + void setVanished(boolean vanished); + // Slice end ++ ++ // Slice start ++ /** ++ * Returns the Block that is currently supporting the player, particularly in the case of ++ * crouching over the edge of a block. ++ */ ++ org.bukkit.block.Block getBlockStandingOn(); ++ // Slice end + } diff --git a/patches/api/0014-Add-DimensionDataStorageEvents.patch b/patches/api/0014-Add-DimensionDataStorageEvents.patch new file mode 100644 index 000000000..d1f0d870e --- /dev/null +++ b/patches/api/0014-Add-DimensionDataStorageEvents.patch @@ -0,0 +1,140 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Cryptite +Date: Sun, 17 Nov 2024 09:19:41 -0600 +Subject: [PATCH] Add DimensionDataStorageEvents + + +diff --git a/src/main/java/io/papermc/paper/event/server/DimensionDataStorageLoadEvent.java b/src/main/java/io/papermc/paper/event/server/DimensionDataStorageLoadEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..f85074ae3bb7eb558fc07a4e1877f130aec33d39 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/event/server/DimensionDataStorageLoadEvent.java +@@ -0,0 +1,61 @@ ++package io.papermc.paper.event.server; ++ ++import com.google.gson.JsonElement; ++import org.bukkit.event.Cancellable; ++import org.bukkit.event.HandlerList; ++import org.bukkit.event.server.ServerEvent; ++import org.jetbrains.annotations.ApiStatus; ++import org.jspecify.annotations.NullMarked; ++ ++import java.nio.file.Path; ++ ++/** ++ * Called when resources such as datapacks are reloaded (e.g. /minecraft:reload) ++ *

++ * Intended for use to re-register custom recipes, advancements that may be lost during a reload like this. ++ */ ++@NullMarked ++public class DimensionDataStorageLoadEvent extends ServerEvent implements Cancellable { ++ public static final HandlerList HANDLER_LIST = new HandlerList(); ++ ++ private final Path path; ++ private final JsonElement json; ++ private boolean cancelled; ++ @ApiStatus.Internal ++ public DimensionDataStorageLoadEvent(final Path path, final JsonElement json) { ++ this.path = path; ++ this.json = json; ++ } ++ ++ public Path getPath() { ++ return path; ++ } ++ ++ public JsonElement getJson() { ++ return json; ++ } ++ ++ @Override ++ public boolean isCancelled() { ++ return this.cancelled; ++ } ++ ++ @Override ++ public void setCancelled(final boolean cancel) { ++ this.cancelled = cancel; ++ } ++ ++ public static HandlerList getHandlerList() { ++ return HANDLER_LIST; ++ } ++ ++ @Override ++ public HandlerList getHandlers() { ++ return HANDLER_LIST; ++ } ++ ++ public enum Cause { ++ COMMAND, ++ PLUGIN, ++ } ++} +diff --git a/src/main/java/io/papermc/paper/event/server/DimensionDataStorageSaveEvent.java b/src/main/java/io/papermc/paper/event/server/DimensionDataStorageSaveEvent.java +new file mode 100644 +index 0000000000000000000000000000000000000000..2b29981621c959dd6027a072dab06339ebf315bf +--- /dev/null ++++ b/src/main/java/io/papermc/paper/event/server/DimensionDataStorageSaveEvent.java +@@ -0,0 +1,61 @@ ++package io.papermc.paper.event.server; ++ ++import org.bukkit.event.Cancellable; ++import org.bukkit.event.HandlerList; ++import org.bukkit.event.server.ServerEvent; ++import org.jetbrains.annotations.ApiStatus; ++import org.jspecify.annotations.NullMarked; ++ ++import java.io.ByteArrayOutputStream; ++import java.nio.file.Path; ++ ++/** ++ * Called when resources such as datapacks are reloaded (e.g. /minecraft:reload) ++ *

++ * Intended for use to re-register custom recipes, advancements that may be lost during a reload like this. ++ */ ++@NullMarked ++public class DimensionDataStorageSaveEvent extends ServerEvent implements Cancellable { ++ public static final HandlerList HANDLER_LIST = new HandlerList(); ++ ++ private final String id; ++ private final ByteArrayOutputStream output; ++ private boolean cancelled; ++ @ApiStatus.Internal ++ public DimensionDataStorageSaveEvent(final String id, final ByteArrayOutputStream output) { ++ this.id = id; ++ this.output = output; ++ } ++ ++ public String getId() { ++ return id; ++ } ++ ++ public ByteArrayOutputStream getOutput() { ++ return output; ++ } ++ ++ @Override ++ public boolean isCancelled() { ++ return this.cancelled; ++ } ++ ++ @Override ++ public void setCancelled(final boolean cancel) { ++ this.cancelled = cancel; ++ } ++ ++ public static HandlerList getHandlerList() { ++ return HANDLER_LIST; ++ } ++ ++ @Override ++ public HandlerList getHandlers() { ++ return HANDLER_LIST; ++ } ++ ++ public enum Cause { ++ COMMAND, ++ PLUGIN, ++ } ++} diff --git a/patches/server/0002-Add-BlockDestroyedByNeighborEvent.patch b/patches/server/0002-Add-BlockDestroyedByNeighborEvent.patch new file mode 100644 index 000000000..96b4e04e4 --- /dev/null +++ b/patches/server/0002-Add-BlockDestroyedByNeighborEvent.patch @@ -0,0 +1,144 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Cryptite +Date: Sat, 9 Nov 2024 09:48:21 -0600 +Subject: [PATCH] Add BlockDestroyedByNeighborEvent + + +diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +index a96f859a5d0c6ec692d4627a69f3c9ee49199dbc..7fe358dac8740ac6338a942f4189bca300d1f1be 100644 +--- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java ++++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +@@ -425,6 +425,7 @@ public class ServerPlayerGameMode { + this.level.captureDrops = new ArrayList<>(); + // CraftBukkit end + BlockState iblockdata1 = block.playerWillDestroy(this.level, pos, iblockdata, this.player); ++ level.pendingPlayerBlockEvents.put(pos, new Level.PendingBlockEvent(pos, this.player)); // Paper + boolean flag = this.level.removeBlock(pos, false); + + if (flag) { +@@ -452,6 +453,7 @@ public class ServerPlayerGameMode { + // CraftBukkit start + java.util.List itemsToDrop = this.level.captureDrops; // Paper - capture all item additions to the world + this.level.captureDrops = null; // Paper - capture all item additions to the world; Remove this earlier so that we can actually drop stuff ++ level.pendingPlayerBlockEvents.remove(pos); // Paper + if (event.isDropItems()) { + org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDropItemEvent(bblock, state, this.player, itemsToDrop); // Paper - capture all item additions to the world + } +diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java +index 33e7d2884195677c4d6340d8b84c1dd85c636ec1..04f8269abe339610795237652edcd54ba31bc5fc 100644 +--- a/src/main/java/net/minecraft/world/item/ItemStack.java ++++ b/src/main/java/net/minecraft/world/item/ItemStack.java +@@ -424,6 +424,7 @@ public final class ItemStack implements DataComponentHolder { + DataComponentPatch oldData = this.components.asPatch(); + int oldCount = this.getCount(); + ServerLevel world = (ServerLevel) context.getLevel(); ++ if (entityhuman != null) world.pendingPlayerBlockEvents.put(blockposition, new Level.PendingBlockEvent(blockposition, entityhuman)); // Paper + + if (!(item instanceof BucketItem/* || item instanceof SolidBucketItem*/)) { // if not bucket // Paper - Fix cancelled powdered snow bucket placement + world.captureBlockStates = true; +diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java +index 022de445bbbb869c38be4972c98dcf1c665539ec..abdbfb6bc3989bdc9332712be3c71b2afbf43c48 100644 +--- a/src/main/java/net/minecraft/world/level/Level.java ++++ b/src/main/java/net/minecraft/world/level/Level.java +@@ -181,6 +181,28 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + public final Map explosionDensityCache = new HashMap<>(); // Paper - Optimize explosions + public java.util.ArrayDeque redstoneUpdateInfos; // Paper - Faster redstone torch rapid clock removal; Move from Map in BlockRedstoneTorch to here + ++ // Paper start - Holder class used to track what Player is responsible the last block event ++ public static class PendingBlockEvent { ++ ++ public final BlockPos block; ++ public final Player player; ++ public @Nullable BlockPos sourceBlock; ++ ++ public PendingBlockEvent(BlockPos block, Player player) { ++ this(block, player, null); ++ } ++ ++ public PendingBlockEvent(BlockPos block, Player player, @Nullable BlockPos sourceBlock) { ++ this.block = block; ++ this.player = player; ++ this.sourceBlock = sourceBlock; ++ } ++ ++ } ++ public final Map pendingPlayerBlockEvents = new HashMap<>(); ++ // Paper end ++ ++ + public CraftWorld getWorld() { + return this.world; + } +@@ -1225,6 +1247,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + if (!this.preventPoiUpdated) { + this.onBlockStateChange(blockposition, iblockdata1, iblockdata2); + } ++ pendingPlayerBlockEvents.remove(blockposition); // Paper + // CraftBukkit end + } + } +@@ -1246,6 +1269,17 @@ public abstract class Level implements LevelAccessor, AutoCloseable, ca.spottedl + if (iblockdata.isAir()) { + return false; + } else { ++ // Paper start ++ PendingBlockEvent blockEvent = pendingPlayerBlockEvents.get(pos); ++ if (blockEvent != null && blockEvent.sourceBlock != null) { ++ io.papermc.paper.event.block.BlockDestroyedByNeighborEvent event = ++ new io.papermc.paper.event.block.BlockDestroyedByNeighborEvent(org.bukkit.craftbukkit.block.CraftBlock.at(this, pos), ++ (org.bukkit.entity.Player) blockEvent.player.getBukkitEntity(), ++ org.bukkit.craftbukkit.block.CraftBlock.at(this, blockEvent.sourceBlock)); ++ event.callEvent(); ++ } ++ // Paper end ++ + FluidState fluid = this.getFluidState(pos); + // Paper start - BlockDestroyEvent; while the above setAir method is named same and looks very similar + // they are NOT used with same intent and the above should not fire this event. The above method is more of a BlockSetToAirEvent, +diff --git a/src/main/java/net/minecraft/world/level/block/DoublePlantBlock.java b/src/main/java/net/minecraft/world/level/block/DoublePlantBlock.java +index 4fe83bd0f355549847b66afb7e61f6f2a6d97016..be6e6247d1a94271926544b128a52f501bc2cad5 100644 +--- a/src/main/java/net/minecraft/world/level/block/DoublePlantBlock.java ++++ b/src/main/java/net/minecraft/world/level/block/DoublePlantBlock.java +@@ -114,6 +114,15 @@ public class DoublePlantBlock extends BushBlock { + BlockPos blockposition1 = pos.below(); + BlockState iblockdata1 = world.getBlockState(blockposition1); + ++ Level.PendingBlockEvent blockEvent = world.pendingPlayerBlockEvents.remove(pos); ++ if (blockEvent != null) { ++ io.papermc.paper.event.block.BlockDestroyedByNeighborEvent event = ++ new io.papermc.paper.event.block.BlockDestroyedByNeighborEvent(org.bukkit.craftbukkit.block.CraftBlock.at(world, blockposition1), ++ (org.bukkit.entity.Player) blockEvent.player.getBukkitEntity(), ++ org.bukkit.craftbukkit.block.CraftBlock.at(world, pos)); ++ if (!event.callEvent()) return; ++ } ++ + if (iblockdata1.is(state.getBlock()) && iblockdata1.getValue(DoublePlantBlock.HALF) == DoubleBlockHalf.LOWER) { + BlockState iblockdata2 = iblockdata1.getFluidState().is((Fluid) Fluids.WATER) ? Blocks.WATER.defaultBlockState() : Blocks.AIR.defaultBlockState(); + +diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +index 99fd67a78539133adf78d65e2c520ff3dd260301..12d9c6769c8cb705d232bc01b50b04df503e4c3a 100644 +--- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java ++++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +@@ -1237,11 +1237,22 @@ public abstract class BlockBehaviour implements FeatureElement { + BlockPos.MutableBlockPos blockposition_mutableblockposition = new BlockPos.MutableBlockPos(); + Direction[] aenumdirection = BlockBehaviour.UPDATE_SHAPE_ORDER; + int k = aenumdirection.length; ++ BlockState blockState = world.getBlockState(pos); // Slice + + for (int l = 0; l < k; ++l) { + Direction enumdirection = aenumdirection[l]; + + blockposition_mutableblockposition.setWithOffset(pos, enumdirection); ++ // Paper start - Propagate the PendingBlockEvent from the current blockPos to the next block ++ // if it will break (!canSurvive) ++ if (blockState.getBukkitMaterial() == org.bukkit.Material.AIR && !world.getBlockState(blockposition_mutableblockposition).canSurvive(world, blockposition_mutableblockposition)) { ++ Level.PendingBlockEvent blockEvent = ((Level) world).pendingPlayerBlockEvents.get(pos); ++ if (blockEvent != null) { ++ BlockPos blockPosCopy = blockposition_mutableblockposition.immutable(); ++ ((Level) world).pendingPlayerBlockEvents.put(blockPosCopy, new Level.PendingBlockEvent(blockPosCopy, blockEvent.player, pos)); ++ } ++ } ++ // Paper end + world.neighborShapeChanged(enumdirection.getOpposite(), blockposition_mutableblockposition, pos, this.asState(), flags, maxUpdateDepth); + } + diff --git a/patches/server/0020-Packet-obfuscation-and-reduction.patch b/patches/server/0020-Packet-obfuscation-and-reduction.patch index b5d69f47c..72116e446 100644 --- a/patches/server/0020-Packet-obfuscation-and-reduction.patch +++ b/patches/server/0020-Packet-obfuscation-and-reduction.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Packet obfuscation and reduction diff --git a/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java b/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java -index 0f99733660f91280e4c6262cf75b3c9cae86f65a..fd28ad10df3cfcfdb44cafc4182ead53a4200027 100644 +index 0f99733660f91280e4c6262cf75b3c9cae86f65a..8845497071ca3be0e439b454f1f2d14f0f74e842 100644 --- a/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java +++ b/src/main/java/net/minecraft/network/syncher/SynchedEntityData.java @@ -63,15 +63,29 @@ public class SynchedEntityData { @@ -48,7 +48,7 @@ index 0f99733660f91280e4c6262cf75b3c9cae86f65a..fd28ad10df3cfcfdb44cafc4182ead53 + public List> packForeignDirty() { + List> list = null; + -+ for (DataItem dataItem : this.itemsById.values()) { ++ for (DataItem dataItem : this.itemsById) { + if (dataItem.isDirty(true)) { + dataItem.setForeignDirty(false); + if (list == null) { diff --git a/patches/server/0022-Expose-getBlockPosBelowThatAffectsMyMovement.patch b/patches/server/0022-Expose-getBlockPosBelowThatAffectsMyMovement.patch new file mode 100644 index 000000000..836b14fde --- /dev/null +++ b/patches/server/0022-Expose-getBlockPosBelowThatAffectsMyMovement.patch @@ -0,0 +1,31 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Cryptite +Date: Sun, 17 Nov 2024 08:24:22 -0600 +Subject: [PATCH] Expose getBlockPosBelowThatAffectsMyMovement + + +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +index aefa94ffd630e2dd6aefd547664ae25d2a81420c..0a34c8fb259ac9d3bfba518521d9137495509b96 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +@@ -28,6 +28,7 @@ import org.bukkit.Location; + import org.bukkit.Server; + import org.bukkit.Sound; + import org.bukkit.World; ++import org.bukkit.block.Block; + import org.bukkit.block.BlockFace; + import org.bukkit.block.PistonMoveReaction; + import org.bukkit.craftbukkit.CraftServer; +@@ -1318,4 +1319,12 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { + this.entity.vanished = vanished; + } + // Slice end ++ ++ // Slice start ++ @Override ++ public Block getBlockStandingOn() { ++ net.minecraft.core.BlockPos pos = this.entity.getBlockPosBelowThatAffectsMyMovement(); ++ return this.entity.level().getWorld().getBlockAt(pos.getX(), pos.getY(), pos.getZ()); ++ } ++ // Slice end + } diff --git a/patches/server/0023-Add-DimensionDataStorageEvents.patch b/patches/server/0023-Add-DimensionDataStorageEvents.patch new file mode 100644 index 000000000..edc8182dc --- /dev/null +++ b/patches/server/0023-Add-DimensionDataStorageEvents.patch @@ -0,0 +1,68 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Cryptite +Date: Sun, 17 Nov 2024 09:19:41 -0600 +Subject: [PATCH] Add DimensionDataStorageEvents + + +diff --git a/src/main/java/net/minecraft/world/level/storage/DimensionDataStorage.java b/src/main/java/net/minecraft/world/level/storage/DimensionDataStorage.java +index d16f124e0371ce943298c8d7d9bfac21e98cf885..74de7e040ebccb000aa258238ed0bc41dec3754f 100644 +--- a/src/main/java/net/minecraft/world/level/storage/DimensionDataStorage.java ++++ b/src/main/java/net/minecraft/world/level/storage/DimensionDataStorage.java +@@ -1,12 +1,12 @@ + package net.minecraft.world.level.storage; + ++import ca.spottedleaf.dataconverter.minecraft.converters.custom.V3818_Commands; + import com.mojang.datafixers.DataFixer; + import com.mojang.logging.LogUtils; ++import io.papermc.paper.event.server.DimensionDataStorageSaveEvent; + import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; +-import java.io.DataInputStream; +-import java.io.IOException; +-import java.io.InputStream; +-import java.io.PushbackInputStream; ++ ++import java.io.*; + import java.nio.file.Files; + import java.nio.file.Path; + import java.util.HashMap; +@@ -128,29 +128,34 @@ public class DimensionDataStorage implements AutoCloseable { + } + + public CompletableFuture scheduleSave() { +- Map map = this.collectDirtyTagsToSave(); ++ Map map = this.collectDirtyTagsToSave(); + if (map.isEmpty()) { + return CompletableFuture.completedFuture(null); + } else { + this.pendingWriteFuture = this.pendingWriteFuture + .thenCompose( + v -> CompletableFuture.allOf( +- map.entrySet().stream().map(entry -> tryWriteAsync(entry.getKey(), entry.getValue())).toArray(CompletableFuture[]::new) ++ map.entrySet().stream().map(entry -> tryWriteAsync(entry.getKey(), this.getDataFile(entry.getKey()), entry.getValue())).toArray(CompletableFuture[]::new) + ) + ); + return this.pendingWriteFuture; + } + } + +- private Map collectDirtyTagsToSave() { +- Map map = new Object2ObjectArrayMap<>(); +- this.cache.forEach((id, state) -> state.filter(SavedData::isDirty).ifPresent(state2 -> map.put(this.getDataFile(id), state2.save(this.registries)))); ++ private Map collectDirtyTagsToSave() { ++ Map map = new Object2ObjectArrayMap<>(); ++ this.cache.forEach((id, state) -> state.filter(SavedData::isDirty).ifPresent(state2 -> map.put(id, state2.save(this.registries)))); + return map; + } + +- private static CompletableFuture tryWriteAsync(Path path, CompoundTag nbt) { ++ private static CompletableFuture tryWriteAsync(String id, Path path, CompoundTag nbt) { + return CompletableFuture.runAsync(() -> { + try { ++ ByteArrayOutputStream buf = new ByteArrayOutputStream(); ++ NbtIo.writeCompressed(nbt, buf); ++ DimensionDataStorageSaveEvent event = new DimensionDataStorageSaveEvent(id, buf); ++ if (!event.callEvent()) return; ++ + NbtIo.writeCompressed(nbt, path); + } catch (IOException var3) { + LOGGER.error("Could not save data to {}", path.getFileName(), var3);